13

I'm finally getting used to Git and, after the initial steep learning curve, I must say it's quite good (I just miss the single file externals, but that's another story). I have, however, an issue that I can't solve: I'm currently working on a dozen projects at the same time. They are all interconnected and I must jump from one to the other, making changes here and there.

All good so far, I can "juggle" them quite easily, I commit changes regularly and so on. The issue is that, after several hours, I don't remember which projects I pushed to the remote repository. Of course, I can "cd" into each project's directory and issue the push command, but it's tedious. I was wondering if there would be a way, using bash, to run something like a git find all unpushed commits in all projects in this directory. Such command would be run from a directory which is not a Git repository, but which contains a tree of all the projects.

The logic is simple, but the implementation seems quite complicated, also because, although the root directory contains all the projects, the actual Git repositories can be found at any level from the starting directory. For example:

  • projects directory
    • customer X
    • project1 (Git repo)
    • customer U
    • project2 (Git repo)
    • project3
      • somelibrary (Git repo)
      • theme (Git repo)

In this example, only directories in bold are Git repositories, therefore the check should be run inside them. I'm aware that the structure looks a bit messy, but it's actually quite easy to handle.

Any help is welcome, thanks.

Diego
  • 7,312
  • 5
  • 31
  • 38

7 Answers7

12

You can find unpushed commits with git cherry, so you should be able to write a bash script like

#!/bin/bash
for file in `find ./ -type d -maxdepth 3` ; do
    cd $file
    git cherry -v
    cd -
done

which will find 3 layers of subdirectories, but it's probably not very nice to look at.

EDIT

As Evan suggests you can use a subshell to avoid cd-ing back a directory, like so

#!/bin/bash
for file in `find ./ -type d -maxdepth 3` ; do
    (cd $file && git cherry -v)
done

You might want to put a pwd command in here somewhere to see which file/directory you're in...

Community
  • 1
  • 1
JKirchartz
  • 17,612
  • 7
  • 60
  • 88
  • 2
    Instead of using `cd -` to change to the previous directory, you could just make the loop's body a subshell. –  Apr 10 '13 at 22:31
  • Just using `find` + `git`, without need for shell: `find . -name .git -type d -print -exec git --git-dir={} --work-tree={}/.. cherry -v \;` – Ruslan Nov 06 '21 at 17:29
9

You could have a look at git submodules but personally I don't find them pleasant to work with.Some time ago I thus created a script to work on a collection of git repos: https://github.com/mnagel/clustergit

clustergit allows you to run git commands on multiple repositories at once. It is especially useful to run git status recursively on one folder. clustergit supports git status, git pull, git push, and more.

enter image description here

mnagel
  • 6,729
  • 4
  • 31
  • 66
6

Make the top level a git repo and make each project a submodule. Now you can run

git submodule foreach --recursive git status # or any other command

recursive is not needed if you don't have any submodules within your projects.

Adam Dymitruk
  • 124,556
  • 26
  • 146
  • 141
6

You can use the following one-liner, which you can use to search from a directory which is not a Git repository, as you requested. This will list the absolute paths to the git repositories which have unpushed commits:

find . -type d -iname '.git' -exec sh -c 'cd "${0}/../" && git status | grep -q "is ahead of" && pwd' "{}" \;

This works by recursively searching for .git directories, and when found cd to the parent directory, which is the git project root. Then run git status and grep for the "is ahead of" phrase. If this is true the command after && is executed, in this case the 'pwd' command. It is here where you can insert you own commands (for example to show the unpushed commits).

Note: only works for unpushed commits on the active branch

Qetesh
  • 100
  • 1
  • 5
  • Does this work for all branches or just the active branch? – Dan Stevens Apr 25 '17 at 09:10
  • 1
    @DanStevens unfortunately not, just tested. Tried to find a way of showing only unpushed commits on remote tracking branches but haven't found a correct way yet. – Qetesh May 01 '17 at 18:53
4

The following commands support paths with spaces also, and will only check directories that contain a .git subdir.

Number of unpushed changes per repo (for all branches):

while IFS= read -r -d '' dir; 
    do 
        ([ -d "$dir/.git" ] && cd "$dir" && echo -n `pwd`\
        && echo " [ UNPUSHED: "`git log --branches --not --remotes | grep "^commit" | wc -l`" ]" ); 
    done < <(find -type d -print0)

Number of uncommitted files per repo (staged and unstaged):

while IFS= read -r -d '' dir; 
    do 
        ([ -d "$dir/.git" ] && cd "$dir" && echo -n `pwd`\
         && echo " [ UNCOMMITTED: "`git status -s | wc -l`" ]"); 
    done < <(find -type d -print0)

Note git cherry does not work if upstream (-u) has not been set, while git log above does.

Marinos An
  • 9,481
  • 6
  • 63
  • 96
2

Add this alias to your .gitconfig file and then you can run git status-all from a parent directory to get the status of all git repositories recursively.

[alias]
status-all = "!for d in `find . -name \".git\"`; do echo \"\n*** Repository: $d ***\" && git --git-dir=$d --work-tree=$d/.. status; done"

See also this post.

Community
  • 1
  • 1
BrianV
  • 961
  • 8
  • 9
2

Here is an answer for those of you who use fish. Based on the answer by JKirchartz with a few tweaks:

for file in (find . -type d -maxdepth 3)
  if test -d "$file/.git"
    fish -c "cd $file; and git cherry -v; and pwd"
  end
end
Johannes
  • 1,370
  • 16
  • 15