0

Question

Using git, is there a built-in command which would give me a sequence of commits, leading from one source commit to a target commit ?

more details

Suppose I have a repo with a history which is a bit intricate :

*   175015a (HEAD -> master) Merge branch 'br1'  # target commit
|\  
| *   bc03512 Merge branch 'br2' into br1
| |\  
| | *   077f584 Merge branch 'br3' into br2
| | |\  
| | | * 923b15a jjj
| | * | cec6734 iii
| | | * c8259a2 hhh
| | |/  
| * | 45cb234 ggg
| | *   b1dd3c7 Merge branch 'br3' into br2
| | |\  
| | | * 48af382 fff
| | * | 778a504 eee                              # source commit
| | | * f918010 ddd
| | |/  
| | * 9706f28 ccc
| |/  
| * 483f665 bbb
|/  
* 795fa22 aaa
* fd4bef2 first commit

and that for some reason, I would like to inspect a sequence of commits, which led from commit eee to the current HEAD.

I mean a sequence of commits where :

  • the first commit is the target commit (175015a (master) in my example)
  • each commit on line n+1 is a parent of commit on line n
  • the last commit is the source commit (778a504 eee in my example)

With the above example :

             # path illustration from the diagram above
175015a      *   175015a (HEAD -> master) Merge branch 'br1'  # target commit
bc03512      \-*   bc03512 Merge branch 'br2' into br1
077f584        \-*   077f584 Merge branch 'br3' into br2
cec6734          *   cec6734 iii
b1dd3c7          *   b1dd3c7 Merge branch 'br3' into br2
778a504          *   778a504 eee                              # source commit

would be acceptable.

I am looking for a set of options to pass to git rev-list or git log or any other git command.


things I tried

git log has a big bunch of options, but I didn't find what I am looking for :

  • if I run git log 778a504..master, it keeps many commits which are not between the two commits (commits fff and ddd would be mentioned, for example)

  • if I also add git log --ancestry-path 778a504..master, some of the "parasites" commits are dropped, but I still get all the merged branches between the two commits (in the example : I would get both hhh and jjj on one side, and iii on the other side, I would like to only have one of those two sets of commits)

  • if I use git log --first-parent 778a504..master starting from master : I cannot get commits outside the leftmost line in the diagram (I would only get master)

  • some paths are bound to pass through merge commits, so I don't want to exclude them : no --no-merges either

note : the answer suggested as duplicate indicates to use --no-merges and possibly --first-parent, so it isn't a fit for this question.

LeGEC
  • 46,477
  • 5
  • 57
  • 104
  • Which command did you use to get the commit graph you have shown? – mkrieger1 Aug 18 '20 at 21:59
  • `git log --oneline --graph` – LeGEC Aug 18 '20 at 21:59
  • And why is that not already the answer to your question? I don't quite understand what you are asking. – mkrieger1 Aug 18 '20 at 22:00
  • I mean, what is wrong with `git log source..target`? – mkrieger1 Aug 18 '20 at 22:01
  • ah : you would get all the merge commits and forking branches in between. I would like to highlight one single path. – LeGEC Aug 18 '20 at 22:03
  • I would go for any path : leftmost, one of the shortest, rightmost ... any path would do. Just the equivalent of following one single line in the above graph. – LeGEC Aug 18 '20 at 22:04
  • Does this answer your question? [Showing commits made directly to a branch, ignoring merges in Git](https://stackoverflow.com/questions/8527139/showing-commits-made-directly-to-a-branch-ignoring-merges-in-git) – mkrieger1 Aug 18 '20 at 22:04
  • `--no-merges` would exclude commits which would be part of that path, so no this is not the option I am looking for. – LeGEC Aug 18 '20 at 22:07
  • and if you use `--first-parent`, you can't leave the leftmost line, so it wouldn't allow to lead to a commit which is not in a `~n` sequence. – LeGEC Aug 18 '20 at 22:08

2 Answers2

0

[update] The conclusion is that there isn't a way to do this with built-in commands, you currently have to use some external script. I'll mark this answer as accepted.


I am asking to see if there is a solution using only built-in commands and options.

It is possible to write a script to extract a path, here is an example written in perl :

#!/usr/bin/perl

use strict;
use warnings;

# This script implements a breadth first search, starting from $target,
# following the 'child -> parent' links, until $source is found or the
# complete history of $target is traversed.

my $source = $ARGV[0];
my $target = $ARGV[1];

my $srcsha = `git rev-parse -q --verify "$source"` || die "'$source' is not a valid source";
chomp($srcsha);
my $tgtsha = `git rev-parse -q --verify "$target"` || die "'$target' is not a valid target";
chomp($tgtsha);

# %seen stores the commits seen so far, linked to the child we are interested in
my %seen = ();
$seen{$tgtsha} = "";

# @stack lists the commits to visit
my @stack = ();
push @stack, $tgtsha;


# print_path : print the path from '$target' down to '$source'
sub print_path {
  my ($source) = @_;

  my @path = ();

  my $sha = $source;
  while ($sha) {
    unshift @path, $sha;
    $sha = $seen{$sha};
  }

  print "$_\n" for @path;
}


# main body :
# as long as there is something to scan, go for it,
# if $source is found along the way, print the path and exit with success
# otherwise, end the loop, and exit with failure

while( scalar(@stack) > 0 ) {
  my $sha = shift @stack;

  # extract parent lines from 'git cat-file -p commit'
  my @parents = `git cat-file -p $sha | grep '^parent ' | cut -c8-`;
  
  foreach my $p (@parents) {
    chomp($p);
    # for each parent, if not seen yet :
    #   * store it as a parent of $sha
    #   * put it on the list of commits to explore next
    if (!$seen{$p}) {
      $seen{$p} = $sha;
      push @stack, $p;

      if ($p eq $srcsha) {
        # if we reached the 'source' commit : stop here
        print_path $p;
        exit 0;
      }
    }
  }
}

# no path found
exit 1;

Sample usage :

$ ./git-path.pl 778a504 master
175015a5a00bbb9ad2ee4de23254ace4dbc645eb
bc03512e6082badab86a00cd320d89339741bb7b
077f584c10852cbadeef6c48886fd600cab61aa6
cec6734db31a1053b1b71674671512e1fe1592b1
b1dd3c71e42ca421f306640d4f0fdc69a00aa2c7
778a504c718f30d0dc2c72a30c885f10847f46a8
LeGEC
  • 46,477
  • 5
  • 57
  • 104
  • There are a bunch of graph algorithms for doing what you want, but if you can take *any* path a simple bidirectional breadth-first-search is probably the way to go. Git won't do this for you; `git log --ancestry-path start..end` will get you a path, but may include commits you don't care to look at. – torek Aug 19 '20 at 05:13
-1

You might get what you want using -- after your git log or your git rev-list:

git log --all -- e.txt
git rev-list --all -- e.txt

Should do the trick

Note: it seems this answer has been answered already here: Find commits that modify file names matching a pattern in a GIT repository

leddzip
  • 99
  • 2
  • I created some phony commits, and put the file name in the commit message, this may have been misleading. – LeGEC Aug 19 '20 at 06:47