Find a Git branch containing changes to a given file

Git

Git Problem Overview


I have 57 local branches. I know I made a change to a certain file in one of them, but I'm not sure which one. Is there some kind of command I can run to find which branches contain changes to a certain file?

Git Solutions


Solution 1 - Git

Find all branches which contain a change to FILENAME (even if before the (non-recorded) branch point)

FILENAME="<filename>"
git log --all --format=%H $FILENAME | while read f; do git branch --contains $f; done | sort -u

Manually inspect:

gitk --all --date-order -- $FILENAME

Find all changes to FILENAME not merged to master:

git for-each-ref --format="%(refname:short)" refs/heads | grep -v master | while read br; do git cherry master $br | while read x h; do if [ "`git log -n 1 --format=%H $h -- $FILENAME`" = "$h" ]; then echo $br; fi; done; done | sort -u

Solution 2 - Git

All you need is

git log --all -- path/to/file/filename

If you want to know the branch right away you can also use:

git log --all --format=%5 -- path/to/file/filename | xargs -I{} -n 1 echo {} found in && git branch --contains {}

Further, if you had any renames, you may want to include --follow for the Git log command.

Solution 3 - Git

Looks like this is still a problem without an appropriate solution. I don't have enough credits to comment, so here's my little contribution.

Seth Robertson's 1st solution kinda worked for me, but only gave me local branches, among which where many false positives, probably because of merges from the stable branch.

Adam Dymitruk's 2nd solution didn't work for me at all. For starters, what's --format=%5? It isn't recognized by git, I couldn't find anything about it and I couldn't get it to work with other format options.

But his 1st solution combined with the --source option and with a simple grep proved helpful:

git log --all --source -- <filename> | grep -o "refs/.*" | sort -u

This gives me a number of remote tags and branches and one local branch, where I made the latest changes to the file. Not sure how complete this is.

UPDATE as per @nealmcb 's request, sorting branches by most recent change:

First, you could change the grep to "refs/heads/.*", which will give you the local branches only. If there are only a few branches, you could examine the latest commit of each one like this:

git log -1 <branch> -- <filename>

If there are more branches and you really want to automate this, you can combine the two commands using xargs, git log formatting and another sort into this one-liner:

git log --all --source -- <filename> | grep -o "refs/heads/.*" | sort -u | xargs -I '{}' git log -1 --format=%aI%x20%S '{}' -- <filename> | sort -r

This will result in output like this:

2020-05-07T15:10:59+02:00 refs/heads/branch1
2020-05-05T16:11:52+02:00 refs/heads/branch3
2020-03-27T11:45:48+00:00 refs/heads/branch2

Solution 4 - Git

I know this question is ancient, but I kept comming back to it before developing my own solution. I feel it's more elegant and thanks to use of merge base filters out unwanted branches.

#!/bin/bash

file=$1
base=${2:-master}

b=$(tput bold) # Pretty print
n=$(tput sgr0)

echo "Searching for branches with changes to $file related to the $base branch"

# We look through all the local branches using plumbing
for branch in $(git for-each-ref --format='%(refname:short)' refs/heads/); do
  # We're establishing a shared ancestor between base and branch, to only find forward changes.  
  merge_base=$(git merge-base $base $branch)
  # Check if there are any changes in a given path.
  changes=$(git diff $merge_base..$branch --stat -- $file)

  if [[ ! -z $changes ]]; then
    echo "Branch: ${b}$branch${n} | Merge Base: $merge_base"
    # Show change statistics pretty formatted
    git diff $merge_base..$branch --stat -- $file
  fi
done

If you put it in PATH as git-find-changes (with executable permissions) - you can then call it with git find-changes /path

Example output for git find-changes app/models/

Branch: update_callbacks | Merge base: db07d23b5d9600d88ba0864aca8fe79aad14e55b
 app/models/api/callback_config.rb | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)
Branch: repackaging | Merge base: 76578b9b7ee373fbe541a9e39cf93cf5ff150c73
 app/models/order.rb                 | 38 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)

Solution 5 - Git

The following is an inelegant brute-force method, but I expect it should work. Make sure you've stashed any uncommitted changes first as it will switch which branch you are currently on.

for branch in $(git for-each-ref --format="%(refname:short)" refs/heads); do
    git checkout $branch && git grep SOMETHING
done

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDustinView Question on Stackoverflow
Solution 1 - GitSeth RobertsonView Answer on Stackoverflow
Solution 2 - GitAdam DymitrukView Answer on Stackoverflow
Solution 3 - GitSimpletonView Answer on Stackoverflow
Solution 4 - GitMarcin RaczkowskiView Answer on Stackoverflow
Solution 5 - GitwhiteingeView Answer on Stackoverflow