Pushing all submodules recursively

The git-powercommit script I wrote recently does push submodules recursively as a part of its workflow. Under the hood, there is a map-like iterator which uses git-status --porcelain=v2 to iterate over repository objects. Here is its definition:

mapchanges() {(
  set -e -x
  local filter="$1"
  local commiter="$2"
  git status --no-renames --porcelain=v2 | \
  while read N XY sub mH mI mW hH hI path ; do
    $filter $N $XY $sub $mH $mI $mW $hH $hI "$path"
  done | \
  sort -u --reverse | \
  while read path; do
    $commiter "$path"
  done
)}

In order to iterate over submodules, you need to provide it with the filter and the action callback functions. In your case, the filter function could be:

filter_subm() {
  # Inputs are according to `git status --porcelain=v2` spec. The function
  # filters submodules which has changed commits.
  local sub="$3"; shift 8; local path="$1"
  case "$sub" in
    SC??) echo "$path" ;;
    *) ;;
  esac
}

As for the action function, the origina script does commit the whole submodule, but in your case you could insert the push commands like the following

push_subm() {
  local path="$1"
  (cd -P "$path" && git push origin; )
}

Now, we bring everything together with the line like

mapchanges filter_subm push_subm

Please, consider reviewing the original script to find out the details on how to organize the recursion.


The command git push --recurse-submodules=on-demand does not work if you have submodules which contain submodules. (git version: 2.20.1)

Add the following alias in your ~/.gitconfig file:

[alias]
    push-all = "! find . -depth -name .git -exec dirname {} \\; 2> /dev/null | sort -n -r | xargs -I{} bash -c \"cd {}; git status | grep ahead > /dev/null && { echo '* Pushing: {}'; git push; }\""

Then issue git push-all in your parent git folder.

Explanation

  • !: We are issuing a non-git command.
  • find . -depth -name .git -exec dirname {} \\; 2> /dev/null : Find all submodules (and git repositories, which wouldn't harm)
  • | sort -n -r: Sort by path depth, deepest will be first.
  • | xargs -I{} bash -c \": Pass directories to the following commands:
    • cd {};: cd to the target directory.
    • git status | grep ahead > /dev/null: Test if it is necessary to push this repository.
    • && { echo '* Pushing: {}'; git push; }\"": Inform and push.

git1.7.11 ([ANNOUNCE] Git 1.7.11.rc1) mentions:

"git push --recurse-submodules" learned to optionally look into the histories of submodules bound to the superproject and push them out.

So you can use:

git push --recurse-submodules=on-demand

You can use git submodule foreach to run any desired command on each submodule, e.g.

git submodule foreach git push origin master

See: man git-submodule.