Home > front end >  Git: Show all branches that touch a certain file
Git: Show all branches that touch a certain file

Time:10-28

How can I obtain a list of all branches that introduce changes to a certain file?

Context: When starting to edit a certain file it might be interesting to see what unmerged changes exist on the same file.

We can assume that all branches that are already merged are also deleted. However, if we can filter on branches that are ahead of [main] it would be a plus.

CodePudding user response:

I am not aware of a pure git way to do it. However, you can achieve this by a Bash one-liner:

$ git branch --no-merged | xargs -I branch bash -c "git diff --stat master...branch | grep -q rails/apps/main/Gemfile.lock && echo branch"

git branch -a --no-merged will give you all unmerged branches; local and remote (-a) Pipe that to xargs to run the following command for every line of output and replace branch by the actual branch name (-I branch): bash -c "..." runs the quoted argument in a new Bash so we can use | and && on the output of the next command instead of the output of xargs. git diff --stat master...branch lists all files changed by that branch. Pipe that to grep -q path/to/Gemfile.lock which will either exit with success or failed (-q). On success (&&) echo the branch name.

CodePudding user response:

Update all remote tracking branches,

git fetch origin  refs/heads/*:refs/remotes/origin/*

List all remote tracking branches,

git for-each-ref refs/remotes/origin --format='%(refname:short)'

Check each remote tracking branch if it has some new commits, not reachable from main, that touch a certain file foo.bar. If any, print the branch name and commits. Combined with git for-each-ref,

git for-each-ref refs/remotes/origin --format='%(refname:short)' | while read ref;do
    commits=$(git log --pretty=%H main..${ref} --follow -- foo.bar)
    if [[ "$commits" != "" ]];then
        echo $ref $commits
    fi
done

CodePudding user response:

You can do this with a bash function, which you can optionally make into a Git alias.

function git_where_changed {
    for commit in $(git rev-list --all -- $1); do
        if ! git merge-base --is-ancestor $commit main; then
            git branch -a --contains $commit
        fi
    done | sort -u | sed -E 's/^  //'; 
}

After re-loading your .bashrc, you would run this as git_where_changed path/to/file.

git rev-list --all lists every commit that is part of any branch or tag. The -- $1 limits the results to commits that changed the file that matches the first argument to the function (path/to/file).

git merge-base --is-ancestor $commit main checks whether the first commit ($commit, taken from the rev-list output) is an ancestor to the second commit (the head of the main branch). If so, that commit is already merged and we can ignore it.

git branch -a --contains $commit produces a list of branches that $commit is part of. -a includes tracked remote branches; without this, only local branches will be included.

At the end of the for loop, there may be a lot of duplicate branch names, for instance if there is one unmerged branch that changed the file several times. sort -u sorts the branch names alphabetically and removes the duplicates. Finally, sed -E 's/^ //' removes the whitespace that git branch normally places in front of its output (-E enables extended regular expression syntax).

As an alias, it would look like this (under the [alias] heading in your .gitconfig):

where-changed = "!f() { for commit in $(git rev-list --all -- $1); do if ! git merge-base --is-ancestor $commit main; then git branch -a --contains $commit; fi; done | sort -u | sed -E 's/^  //'; };f"

This would allow you to run git where-changed path/to/file, which may be more convenient if you are using Git on Windows and not using Git Bash.

  • Related