Question

Summary

What is the fastest way of finding, in the current branch, the most recent commit also contained in any other branches?

Bonus: Can the technique allow for "any other branches" in the question above to be "any other remote branches" or "any other local branches"?


Background

I love using git rebase -i. My main use case for it is to re-organize commits before pushing to remote. Thus, I often do git rebase -i origin/master. So I've set up an alias to that operation called git rewrite.

The issue is that I don't always want to rebase against origin/master. For example, I may be working on a branch and instead want git rewrite to do git rebase -i origin/branch. Or I may be working a local branch and want git rewrite to do git rebase -i localbranch.

So in effect, I'm trying to make my git rewrite script do something like: "do an interactive rebase from the last commit contained in any other branch". What I've come up with is this: (which only works for finding remote branches)

#!/bin/bash

# Do a git rebase -i from the most recent common point between the
# current branch and any other remote branch.

# We go backwards in the branch's history and for each commit check if
# that commit is present in one of the remote branches. As soon as we
# find one that is, we stop and rebase on that.

commit=$(git rev-parse HEAD)
while [ true ]; do
   branch=$(git branch -r --contains $commit)
   if [ -n "$branch" ]; then
      # This commit exists in another remote branch!
      break
   fi
   # OK, let's try the previous commit
   commit=$(git log --pretty=%P -n 1 $commit)
   # Stupid heuristic, take only first commit if multiple parents
   commit=${commit%% *}
done

git rebase -i $commit

The problem with this method is that it's slow. It's also a bit inaccurate since it follows only one of the parents when a commit has multiple parents.

Does anyone know a better/faster/cleaner way of doing this?

Was it helpful?

Solution

The rewrite script might look like this (bash):

#!/bin/bash
count=0
for rev in $(git rev-list HEAD); do
    num=$(git branch --all --contains ${rev} | wc | awk '{print $1}')
    [ ${num} -eq 1 ] || break
    count=$(( count + 1 ))
done
if [ ${count} -gt 0 ]; then
    git rebase -i HEAD~${count}
fi

What I do a lot of times instead is (rebase from upstream point):

git rebase -i @{u}

The @{u} technique does not catch other branches however.

OTHER TIPS

I'd go for

git for-each-ref refs/heads --format='git merge-base %(refname) yourbranch' \
| sh >merge-bases
git log --first-parent --format='%H %s' \
| grep -f merge-bases -

refs/heads is the "any local branches" version, add or substitute refs/remotes for remote branches.

Process substitution apparently doesn't mix with pipelines on windows, on linux you can avoid the temporary file that way.

(edit: aaaannd the grep there's backlevel too, dumb that down)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top