git log history simplification

Viewed 4820

Let's say I have the following history

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

git log --ancestry-path D..M will give me

            E-------F
             \       \
              G---H---I---J
                           \
                            L--M

However, I would like just the following

            E
             \       
              G---H---I---J
                           \
                            L--M

Or

            E-------F
                     \
                      I---J
                           \
                            L--M

Essentially, I would like to traverse down only one path, not two.

Is this possible? And if so, what is the command?

Edit:

I've tried using --first-parent, but this isn't exactly it. git log --first-parent G..M gives me

                    F
                     \
                  H---I---J
                           \
                            L--M

It includes F, because F is the first parent of I. Instead I'd like

                  H---I---J
                           \
                            L--M

Any help would be appreciated

Solution (that worked for me):

As @VonC stated, there isn't a single one-liner that does this. So I ended up using a bash script.

  1. For each commit in 'git log --ancestry-path G..M'
  2. Determine if $commit's parent includes the commit we were previously on
  3. If yes, continue. do something interesting.
  4. If no, skip that commit.

For example, git log --first-commit G..M is

H - F - I - J - L - M

However, F's parent is E, not H. So we omit F, giving me

H - I - J - L - M

Yay!

3 Answers

I have to admit I didn't understand your solution - it didn't work for my example - but if I understood your use-case correctly (given a pair of commits, you want an arbitrary linear path between them, with no splits), I have the same problem, and the following solution seems to work:

  • Run the log with --ancestry-path, and making sure you take note of the children of each commit
  • Iterate through the results, keeping track of the "last child accepted", and updating it every time a commit references an accepted child (or there is no accepted child yet - initial case)
  • Actually print the resulting "accepted" entries in some useful way

A resulting script looks like:

#!/bin/bash
output_set=""; child_to_match=""; # init
while read -r; do
  if { [ -n "$REPLY" ]; } && { [[ "${REPLY:41}" =~ "$child_to_match" ]] || [ -z "$child_to_match" ]; }; then
    child_to_match=${REPLY:0:40}
    output_set="$output_set $child_to_match"
  fi
done <<<  "$(git rev-list --ancestry-path --children $1)"
if [[ -n $output_set ]]; then
  git show -s $output_set "${@:2}"
fi

It can be called like single-ancestry-path.sh RANGE_EXPRESSION DECORATION_ARGS, supporting generally the same decoration arguments as git log (it is in fact git show, being called once per commit), so taking the famous lg2 example from https://stackoverflow.com/a/9074343/74296, the call might look like this: eg:

single-ancestry-path.sh master..MyBranch --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n''          %C(white)%s%C(reset) %C(dim white)- %an%C(reset)'

It's been 9 years, so I would have hoped there would be an easier answer, but I can't find one.

I too dislike the problems that result from merging and have dispensed with having it in my mainstream history. Whenever there is a large merge onto a main branch I will recommit it with identical contents but as a single commit.

    D---E--------F                           Co-Developer
   /               
  B---C---G'---H---I'--J                     Team Leader
 /                       
A-------K----------------L'--M               Main Stream

Here, G', I' and L' would be points where I have re-commited merge results. The branch descriptions simply describe a scenario where I can visualize the problem tree occurring. So the contents of G and G' (similarly I and I') would be the same, the team leader having merged in the work-to-date of the developer. And L' the same as L, the feature integrated onto the mainstream.

I totally understand that avoiding a problem is not the same as solving it, and sympathize with those facing the problem now.

Related