429

In Git, how could I search for a file or directory by path across a number of branches?

I've written something in a branch, but I don't remember which one. Now I need to find it.

Clarification: I'm looking for a file which I created on one of my branches. I'd like to find it by path, and not by its contents, as I don't remember what the contents are.

Belphegor
  • 4,456
  • 11
  • 34
  • 59
Peeja
  • 13,683
  • 11
  • 58
  • 77
  • I'm unclear about the question. Was this file in a branch that is now deleted? Was the file deleted? – Abizern Dec 16 '08 at 20:26

7 Answers7

581

git log + git branch will find it for you:

% git log --all -- somefile

commit 55d2069a092e07c56a6b4d321509ba7620664c63
Author: Dustin Sallings <dustin@spy.net>
Date:   Tue Dec 16 14:16:22 2008 -0800

    added somefile


% git branch -a --contains 55d2069
  otherbranch

Supports globbing, too:

% git log --all -- '**/my_file.png'

The single quotes are necessary (at least if using the Bash shell) so the shell passes the glob pattern to git unchanged, instead of expanding it (just like with Unix find).

StackzOfZtuff
  • 2,534
  • 1
  • 28
  • 25
Dustin
  • 89,080
  • 21
  • 111
  • 133
  • 3
    This works if you know the exact path to `somefile`: if you need regex search over the path/filename, for example, then you can use ididak's answer. – ShreevatsaR Feb 27 '12 at 09:05
  • 84
    Supports globbing, too: `git log --all -- **/my_file.png` – webmat Apr 25 '12 at 18:01
  • 5
    I used this for a slightly different problem. I was trying to find all the *.sql files I had committed to a particular branch. So I used `git log --grep='branch' --author='me' -- *.sql`. Worked like a charm. git version 1.7.11.1 – PREEB Sep 17 '12 at 22:57
  • 1
    Note that `gitk` supports the globbing as well. That is an excellent answer @webmat! If you want to see where it's been deleted / created /etc, you can use `git log --all --stat -- **/my_file.png`, that way you won't have to guess if you're checking it out from a commit that deleted it. – eacousineau Oct 04 '13 at 04:55
  • 1
    @webmat: I took the liberty of adding your information about globbing to the answer. Thanks for mentioning it! – sleske Dec 09 '14 at 09:51
  • which need to use two asterisk? only one would not be enought? – Alexandre N. Feb 26 '16 at 11:21
  • @AlexN. You use two asterisks to cover multiple levels of subdirectories. – Juha Palomäki Sep 30 '16 at 06:53
  • 1
    For those wondering (and make it this far down the comments!): `--all` => "Pretend as if all the refs in refs/ are listed on the command line as ". – Snowcrash Oct 02 '17 at 14:15
  • Note that you should write a file name like mentioned above (with asterisks): **/file.txt. Else it will show nothing. – CoolMind Jan 12 '18 at 11:31
  • This answer ought to mention that the (full) relative path is needed for the first Git command (`git log --all -- somefile`). – Peter Mortensen Jan 07 '19 at 17:25
  • 1
    if you only know the name, better run this command from the root of repository. As @PeterMortensen said you need to specify `the (full) relative path`, so `**/filename` won't work if you have to descend folders to find the file. probably there's a syntax for that? – arod Feb 25 '20 at 19:56
  • Jeez, you just saved my sanity. That moment, when you implemented something weeks ago, the branch is still open, and you simply can't find the regarding files in the mess of the other 100 branches. – Froxx Mar 03 '20 at 15:31
  • 1
    For Windows CMD use double quotes for globbing git log --all -- "**/file.name" – esmirnov Mar 03 '21 at 08:30
80

git ls-tree might help. To search across all existing branches:

for branch in `git for-each-ref --format="%(refname)" refs/heads`; do
  echo $branch :; git ls-tree -r --name-only $branch | grep '<foo>'
done

The advantage of this is that you can also search with regular expressions for the file name.

Petr Viktorin
  • 65,510
  • 9
  • 81
  • 81
ididak
  • 5,790
  • 1
  • 20
  • 21
  • 3
    A few hopefully helpful comments: (a) You probably want to add "-r" to "git ls-tree" so that it'll find the file even if it's in a subdirectory. (b) A perhaps neater alternative to the first line would be to use "git for-each ref", e.g. "for branch in `git for-each-ref --format="%(refname)" refs/heads`; do" (c) "git ls-tree --name-only" will make the output tidier (d) it might be worth pointing out in your answer that there's an advantage of this over Dustin's solution, namely that you don't need to exactly know the filename - you can search by regular expression matching any part of the path – Mark Longair Jun 28 '10 at 16:02
  • 12
    I wrapped this up in a script so you can run "gitfind.sh "; the gist is https://gist.github.com/62d981890eccb48a99dc – Handyman5 Sep 19 '11 at 17:12
  • 6
    Note that this will only search across *local* branches. To search remote-tracking branches, use `refs/remotes` instead of `refs/heads`. To search everything (local branches, remote-tracking branches and tags), just use `refs/`. – sleske Apr 23 '13 at 07:43
27

Although ididak's response is pretty cool, and Handyman5 provides a script to use it, I found it a little restricted to use that approach.

Sometimes you need to search for something that can appear/disappear over time, so why not search against all commits? Besides that, sometimes you need a verbose response, and other times only commit matches. Here are two versions of those options. Put these scripts on your path:

git-find-file

for branch in $(git rev-list --all)
do
  if (git ls-tree -r --name-only $branch | grep --quiet "$1")
  then
     echo $branch
  fi
done

git-find-file-verbose

for branch in $(git rev-list --all)
do
  git ls-tree -r --name-only $branch | grep "$1" | sed 's/^/'$branch': /'
done

Now you can do

$ git find-file <regex>
sha1
sha2

$ git find-file-verbose <regex>
sha1: path/to/<regex>/searched
sha1: path/to/another/<regex>/in/same/sha
sha2: path/to/other/<regex>/in/other/sha

See that using getopt you can modify that script to alternate searching all commits, refs, refs/heads, been verbose, etc.

$ git find-file <regex>
$ git find-file --verbose <regex>
$ git find-file --verbose --decorated --color <regex>

Checkout https://github.com/albfan/git-find-file for a possible implementation.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
albfan
  • 12,542
  • 4
  • 61
  • 80
  • Did you mean "Now you can do git-find-file "? I got it working, thank you! – Elijah Lynn Apr 01 '14 at 20:41
  • 2
    I personally find it much more useful to use $(git branch | cut -c 3-) instead of $(git rev-list --all). I don't care about finding a bunch of commits, I care about named branches. – Campadrenalin Jul 14 '14 at 20:15
  • I also changed the scripts to use $(git branch | cut -c 3-) since the loop over all commits was much too slow. – i4h Oct 26 '15 at 10:39
  • Seen all the fuss I thing is worth to create a repo for this. https://github.com/albfan/git-find-file. I have open some issues, feel free to propose more or send PR for it. – albfan Oct 28 '15 at 12:09
  • @ElijahLynn if you want to use `git find-file` you can do by adding it as git alias, just run `git config --global alias.find-file '!git-find-file'. – lumbric May 13 '16 at 13:19
  • 1
    @lumbric installing script on path works out of the box, its a git feature – albfan May 13 '16 at 14:09
  • To use branches only $(git branch | cut -c 3-) only lists local branches and needs to cut. Can use `$(git for-each-ref --format='%(refname:short)' refs/remotes refs/heads)` instead to list local and remote branches directly. In addition the sed line here contains '/' which will not work with '/' in the branch name, they can be changed to '~' which is an illegal char in a branch name to avoid that problem. – Zitrax May 20 '16 at 14:47
  • @Zitrax Thanks for comments. Now you can install script, have man page, color, refnames, all in a single command – albfan May 21 '16 at 09:35
  • Presuming what platform? Linux? – Peter Mortensen Jan 07 '19 at 17:30
16

Command Line

You could use gitk --all and search for commits "touching paths" and the pathname you are interested in.

UI

ui fields

(Credit to: @MikeW's suggestion.)

BenKoshy
  • 33,477
  • 14
  • 111
  • 80
Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
  • 3
    As stated, use gitk --all, then in View | New view, enable All Branches. Then set your search criteria: filenames (with wild cards) in the penultimate field. Finally: OK. – MikeW Jan 21 '16 at 15:20
11

Copy & paste this to use git find-file SEARCHPATTERN

Printing all searched branches:

git config --global alias.find-file '!for branch in `git for-each-ref --format="%(refname)" refs/heads`; do echo "${branch}:"; git ls-tree -r --name-only $branch | nl -bn -w3 | grep "$1"; done; :'

Print only branches with results:

git config --global alias.find-file '!for branch in $(git for-each-ref --format="%(refname)" refs/heads); do if git ls-tree -r --name-only $branch | grep "$1" > /dev/null; then  echo "${branch}:"; git ls-tree -r --name-only $branch | nl -bn -w3 | grep "$1"; fi; done; :'

These commands will add some minimal shell scripts directly to your ~/.gitconfig as global git alias.

lumbric
  • 7,644
  • 7
  • 42
  • 53
3

This command finds commits which introduced specified paths:

git log --source --all --diff-filter=A --name-only -- '**/my_file.png'
reddot
  • 764
  • 7
  • 15
-2

A quite decent implementation of the find command for Git repositories can be found here:

https://github.com/mirabilos/git-find

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Dominik George
  • 516
  • 2
  • 15