11

I am at the commit on master, and I need to get to the next commit wrt origin/master.

git log --ancestry-path --date-order --reverse HEAD..origin/master

or a similar git rev-list command gives me the right commits, properly reversed, so I could |head -n 1 that and be done with it.

However, I wonder it it's possible to get it by using just one process (one git invokation). Limiting with -1 limits first, and then reverses the list, which I not what I need.

How to accomplish this?

I know what a DAG is, I also know enough graph theory to understand why -1 behaves like that. What I'm asking here is not a matter of theory, it's a matter of using a tool used in software development.

Flavius
  • 13,566
  • 13
  • 80
  • 126
  • Without the `--reverse`, you could do `| tail -n 1`, but your specification of 'just one process' excludes that. That seems unnecessarily strict, but your question, your rules. Anyway, have you considered reporting the poor behavior in combining `-1` and `--reverse` as a bug? – Phil Miller Jun 14 '15 at 18:19
  • @Novelocrat I have considered it, but that was before I read the mailing list archive. – Flavius Jun 14 '15 at 18:57

3 Answers3

9

I'm 90% confident this can't be done in a single Git invocation for two reasons.

  1. It just doesn't look like they've implemented it. Commits have pointers to their parents, not their children, which is why --max-count (-1) and --skip (which I also tried a bit) run before --reverse. Since Git's capable of printing in --reverse or not, it seems like running -1 afterwards should be technically feasible, but perhaps there's an underlying reason it's not. Or perhaps they considered it and decided anyone could just --reverse | head -n 1 like you're doing.

  2. More importantly, you're not guaranteed a unique next commit, even when using --ancestry-path, so picking --reverse -1 is ambiguous. Here's an example from the --ancestry-path description in the git-log docs, where your HEAD could be commit E. If you're happy with --date-order, it's more of an academic issue, but the DAG nature of Git makes the whole "next commit" concept unsound.

As an example use case, consider the following commit history:

    D---E-------F
   /     \       \
  B---C---G---H---I---J
 /                     \
A-------K---------------L--M

A regular D..M computes the set of commits that are ancestors of M, but excludes the ones that are ancestors of D. This is useful to see what happened to the history leading to M since D, in the sense that “what does M have that did not exist in D”. The result in this example would be all the commits, except A and B (and D itself, of course).

When we want to find out what commits in M are contaminated with the bug introduced by D and need fixing, however, we might want to view only the subset of D..M that are actually descendants of D, i.e. excluding C and K. This is exactly what the --ancestry-path option does. Applied to the D..M range, it results in:

E-------F
 \       \
  G---H---I---J
               \
                L--M
Kristján
  • 18,165
  • 5
  • 50
  • 62
  • Hi, thanks, however (see my update): "I know what a DAG is, I also know enough graph theory to understand why `-1` behaves like that. What I'm asking here is not a matter of theory, it's a matter of using a tool used in software development.". +1 however for the effort. – Flavius Jun 14 '15 at 18:53
  • It still just appears unimplemented. I'l be pleased if someone discovers otherwise. – Kristján Jun 14 '15 at 19:02
3

This does what you want:

git rev-list --children HEAD...origin/master^ | tail -2 | head -1 | cut -d" " -f 2-

This will even print N children, if the history forks at this point.

You could wrap this into a git alias to make a single command.

For other solutions, see also How do I find the next commit in git?

Community
  • 1
  • 1
Ewan Mellor
  • 6,747
  • 1
  • 24
  • 39
  • Yes @Flavius, it is. And one invocation of git. And when turned into an alias, it would be one command. – Ewan Mellor Jun 14 '15 at 19:26
  • @Flavius If this is not a solution, please explain why? – Nick Volynkin Jun 14 '15 at 22:10
  • You could replace the tail/head/cut with a single call to `sed` or `awk`. – o11c Jun 15 '15 at 00:21
  • No, none of the presented answers is a solution ***in one git invokation***. Try to log a full commit entry (not an SHA). Yes, I know `... --reverse | grep -m2 -B99 "^commit ... ` Great, but still this is not git itself figuring it out and not very elegant though. Whereas implementing hypothetical `git log --reverse -1` with the behavior like some here expected would do the job for any format. – bloody Jan 14 '21 at 12:56
  • @bloody while it may be complicated, git is only invoked once. Plenty of git commands are implemented as pipes through various UNIX utilities. – CervEd Jun 05 '21 at 09:18
  • @EwanMellor, what's the rationale behind `...` vs `..` here? I found the answer helpful to my needs but it didn't log the commit I expected. Instead I used `| tail -1 | cut -d" " -f -1` and then `| xargs git log -1`. I'd probably also switch cut to `awk '{ print $1 }'` – CervEd Jun 05 '21 at 09:27
  • You didn't get the point. Your answer may be a single invokation but it is NOT a reliable SOLUTION for the problem :D Read my comment with focus. – bloody Jun 08 '21 at 13:01
0

I was also surprised that -n 1 or --max-count 1 applies before --reverse instead of after it. This was discussed on the git list some time back: https://git.vger.kernel.narkive.com/e6fDRBef/patch-fix-git-rev-list-reverse-max-count-n "I talked to Junio and he says the current behavior is here to stay."

Ed Avis
  • 1,350
  • 17
  • 36