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.