0

git rev-parse HEAD show the current commit (commit the branch is "reset" to). But how do I get the last future commit regardless of "reset" commit?

Edit: git log --all -1 --pretty=format:"%H" seem to work, but do I really have to use a different command for this simple task?

Edit2: no, this does not work. It display 03317b1d158cd8a79ff4083884eaf3a2eee3b136, a commit between the last and the current...

Context: In TortoiseGit i select some old revision and select Reset "master" to this this = 051741bc532257903f3563d7d89c69cfdddaaf17 Latest commit = 19747382e3680bd90689e998834b492aa66c0730 After I do this, git rev-parse HEAD display 051741bc532257903f3563d7d89c69cfdddaaf17 How can I make it display 19747382e3680bd90689e998834b492aa66c0730?

osexpert
  • 485
  • 1
  • 6
  • 18
  • 1
    No `reset` involved in `rev-parse HEAD`. Can you give a bit more context? *(But to answer your next question, no you can't dynamically refer to a commit's children, only parents.)* – Romain Valeri Mar 05 '19 at 13:57
  • 2
    What do you mean by "latest _future_ commit"? – evolutionxbox Mar 05 '19 at 14:01
  • Future commits, by definition, don't exist yet. Commits get assigned their hash ID at the time you make them, by computing a checksum of their complete contents (which include the time stamp of the time when you make them). So future commits don't have hash IDs. I think you don't *mean* "future" commit at all. – torek Mar 05 '19 at 19:53

1 Answers1

2

I think you're laboring under a misconception here. In particular you do not want to use git reset in the first place. Having done so, you may need to use it again to fix the problem.

Note that git rev-parse master turns the name master into a commit hash ID. It does so by following the six-step process outlined in the gitrevisions documentation. This finds that master is short for refs/heads/master and that refs/heads/master identifies one particular commit, so, after all that work, it's that commit hash ID that git rev-parse produces.

The special name HEAD is always the current commit. But it may be the current commit in one of two ways:

  • The special name contains the (full) name of a branch, e.g., cat .git/HEAD produces ref: refs/heads/master. Then the name refs/heads/master actually holds the ID of the current commit, and git rev-parse HEAD first reads .git/HEAD to find this fact, and then translates refs/heads/master to a hash ID.

    That commit is the current commit.

  • Or, the special name HEAD contains a raw commit hash ID. That is, cat .git/HEAD produces a big ugly hash ID. You are in what Git calls detached HEAD mode, and git rev-parse HEAD produces that same hash ID.

    That commit is the current commit.

Note that this means that there are two ways you can ask Git about HEAD. One way is to ask: What branch name does HEAD hold? This question has an answer only if HEAD is not detached. The second way is to ask: What commit hash ID does HEAD mean? This question almost always has an answer.1

In any case, each commit stores the hash ID of its immediate predecessor, or parent commit (or commits if it has more than one immediate predecessor). Most commits have just one parent. So given a commit hash ID, you can easily go backwards. But you cannot go forwards, because a commit is frozen forever once it's made. The ID of any commit made after that commit is not available within that commit.

This means that in general, what one does is keep a name for the latest commit. Using the name, we can walk back, one commit at a time, to an earlier commit. If we want to use that commit, we use the detached HEAD mode to do that. And, if we are in detached HEAD mode, we can go back to the name to find the latest commit, then step backwards one commit at a time until we reach the detached-HEAD commit. Whatever commit we were on just before we stepped back to this commit, that's the next commit in the direction of the given name.

I think a picture makes this much clearer:

                I <-J   <-- br1
               /
... <-F <-G <-H   <-- HEAD
               \
                K <-L   <-- br2

Here we're in detached HEAD mode, with HEAD containing some hash ID H. So the current commit is just H itself. But there are two possible next commits. Starting from br1 we'll go to commit J, which says to go back to I, which says to go back to H. Or, starting from br2, we are at L which says to go back to K which says to go back to H. So the next commit forward after H is either K or L, and it's impossible to say which unless you pick the ending-point.

To pick the ending point, it's best to leave both br1 and br2 alone, so that they continue to point to J and L respectively. If we peel the label br1 off J and make it point to H, we won't know how to find I and J any more.

... In TortoiseGit i select some old revision and select Reset "master" to this

You did not mention TortoiseGit initially (nor in your tags) and I have never used it, but this web page implies that this reset invokes git reset.

What git reset does—well, one of many things it can do, and what it does for this particular case—is change the hash ID stored in a branch name. So if branch name master, whose full name is refs/heads/master, contained 19747382e3680bd90689e998834b492aa66c0730 before, but then you have Git overwrite it with 051741bc532257903f3563d7d89c69cfdddaaf17, you no longer have master containing the number 19747382e3680bd90689e998834b492aa66c0730. If no other name contains that number, the only way you have to remember that number is, well, to write it down somewhere.

With command-line Git, you could then run git reset master 19747382e3680bd90689e998834b492aa66c0730 to write that number back into master, since you have it written down. This works, but is not a good plan in general—you should have been using, or at least started with, detached HEAD mode instead, to access and work with the commit whose real name is 051741bc532257903f3563d7d89c69cfdddaaf17. (How you might do that in TortoiseGit, I have no idea.)

If you have command line Git, you can do that now—probably you should completely shut down TortoiseGit first, so that it doesn't attempt to undo it.

Note that command-line Git automatically "writes down" the last few (or many) values it has stored under each branch name, in what Git calls the reflog for that branch. From the command line you can run:

git reflog master

to view the contents of this log. If you had not saved the number 19747382e3680bd90689e998834b492aa66c0730 anywhere yourself, this might be the way to recover it. But, again, it's probably better to just use detached HEAD mode to examine old commits.

Note that after using detached HEAD mode to obtain and inspect some existing historical commit, you can create a new branch name at that commit so as to do new development starting from that point. To do that, use git branch or git checkout -b from the command line. The difference between the two is that git checkout -b first creates the new branch, then switches to it, so that HEAD contains the new branch name rather than the hash ID. In other words:

git checkout -b newbranch

is equivalent to:

git branch newbranch
git checkout newbranch

The first step creates the branch, storing in it the hash ID that git rev-parse HEAD would obtain. The second step stores that branch name in HEAD, so that HEAD contains ref: refs/heads/newbranch. In the second step, the currently checked out commit did not change, but the current branch did.

Community
  • 1
  • 1
torek
  • 448,244
  • 59
  • 642
  • 775
  • What I would like is from a list of commits in my log, select arbitrary commit between the first and the last, make my code look like that commit (aka. active commit), repeat for the next arbitrary commit and so on. While I'm doing all of this jumping around between commits (changing the active commit), the last commit does not change. But it seems in git the last and the current commit must be the same (HEAD). Edit: I'm sorry but I'm completely new to git. – osexpert Mar 06 '19 at 10:43
  • `HEAD` (the special, all-capitals name HEAD, not the lowercase string "head" that you'll see elsewhere in documentation in phrases like "branch head") is in fact always the current commit. This is how Git defines "the current commit". But in *detached HEAD* mode, `HEAD` can rove across all your commits. Pay attention to the drawing too though! There's often no *linear* way to move about. Commits I and K are both immediately after commit H because of the branching structure of the commit graph. – torek Mar 06 '19 at 16:32
  • Note that on Windows, because the OS insists that (e.g.) README.TXT is the same file as readme.txt, you can sometimes use all-lowercase `head`. This does not work on Linux, so if you plan to ever use both systems, don't fall into the habit of writing `head` instead of `HEAD`. – torek Mar 06 '19 at 16:33
  • Ok, thanks. It will probably take a while until I understand the answer and can tell if this solve my problem:-) – osexpert Mar 07 '19 at 20:49
  • BTW, for multiple tricks / ways to deal with this, see my (long, alas) answer [here](https://stackoverflow.com/a/39186879/1256452). – torek Mar 07 '19 at 21:32