Dustin
Dustin

Reputation: 4205

Find a Git branch containing changes to a given file

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?

Upvotes: 164

Views: 54481

Answers (6)

Dediqated
Dediqated

Reputation: 898

All the answers speak of utilizing commands to find the right commit, I have just used the UI of PhpStorm (and other JetBrains IDE's should work the same) to look through all branches and commits of one specific file:

Open the Git/Version control tab first, and then:

  1. Click on Path, then Select in Tree... and select your file
  2. Make sure the folder icon Show Only Affected Changes is selected
  3. Then on the left double click the branch you want to search in
  4. Finally look through the list of commits and double click any that might be worth looking into and a repository diff tab opens for you to inspect the file

Git/version control tab in PhpStorm

Unfortunately I couldn't find the change I was looking for, must have removed it before committing, but at least it gave me peace of mind that my search was thorough in all local and remote branches and commits. Hope it helps anyone!

Upvotes: 0

Adam Dymitruk
Adam Dymitruk

Reputation: 129782

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=%S -- 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.

Upvotes: 86

Marcin Raczkowski
Marcin Raczkowski

Reputation: 1638

I know this question is ancient, but I kept coming 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(+)

Upvotes: 7

Simpleton
Simpleton

Reputation: 713

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

Upvotes: 23

whiteinge
whiteinge

Reputation: 605

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

Upvotes: 0

Seth Robertson
Seth Robertson

Reputation: 31471

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

Upvotes: 142

Related Questions