Carcophan
Carcophan

Reputation: 1590

Generate patch of uncommitted changes for git repo and submodules

I have a git repo foo that has several submodules in their own folders bar and baz (the actual repo is eclipse.platform.releng.aggregator). I need to generate patches for changes to those repos.

I can generate a patch with git --no-pager diff > file.patch which is fine for changes to foo but doesn't report changes in the bar or baz, the submodules.

Using git submodule --quiet foreach --recursive git --no-pager diff > file (cribbed from this answer) nearly works but the diffs don't include the names of the folders the subrepos live in.

Presently I work round this by hacking the paths into the patches by hand but that only works for simple patches and the scripting solutions I can think of (e.g. getting the subrepo names from git, changing to the folders, generating the patches in folders) mean I can't use a simple git apply to reapply the patches later.

Is there a way to get git to diff the subrepos directly rather than through foreach so the generated patches can be simply applied?

Upvotes: 0

Views: 1696

Answers (3)

Dr.Dax
Dr.Dax

Reputation: 191

In source repository run

  • to generate patch file without nested submodules (include only top level modules):
(git --no-pager diff --src-prefix= --dst-prefix=;\
  git submodule foreach --quiet 'git --no-pager diff --src-prefix=$sm_path/ --dst-prefix=$sm_path/' \
) > uncommited.patch
  • to generate patch file with nested submodules:
(git --no-pager diff --src-prefix= --dst-prefix=;\
 GIT_PROJECT_PATH=$PWD git submodule foreach --quiet --recursive 'git --no-pager diff --src-prefix=$(realpath --relative-to=$GIT_PROJECT_PATH $PWD)/ --dst-prefix=$(realpath --relative-to=$GIT_PROJECT_PATH $PWD)/' \
) > uncommited.patch

In destination repository run to apply patch:

git apply -p 0 uncommited.patch
Description
  • --no-pager prevents diff interactive mode (notice it is git parameter, not diff).
  • --src-prefix= and --dst-prefix= (empty value; ; is command separator, not value) remove default path prefix, so they contain only relative values.
  • --quiet prevents lines starting with Entering for each folder to appear in patch file.
  • --recursive dives into nested submodules.
  • realpath generates relative path to source root.
  • $sm_path is environment variable provided by git submodule foreach containing relative module folder path.
  • $PWD (when used in foreach) is environment variable with current module folder absolute path.
  • GIT_PROJECT_PATH is arbitrary named environment variable with source root absolute path (exists only during command execution). When modules are not nested, foreach provides $toplevel variable with same value.
  • uncommited.patch is file with diff for source root and all submodules with paths relative to source root.
  • -p 0 treats paths as relative to source root when applying (disables leading slashes stripping in paths).

Upvotes: 4

Carcophan
Carcophan

Reputation: 1590

With the clue offered by @larsks I managed to find the following one liner which does what I need:

git submodule --quiet foreach --recursive 'export NAME="${PWD##*/}"; git --no-pager diff --src-prefix="a/${NAME}/" --dst-prefix="b/${NAME}/"'

This adds the folder name of the subfolders into the patch generated with git diff so I can just run git apply in the parent folder even if the patch spans more than one of the submodules. Note however that it only works because my submodules are all one folder below the main repo folder. That could be overcome if needed by changing the code used to set NAME to something a little more clever.

Upvotes: 3

kan
kan

Reputation: 28981

With submodules the idea is that each repo is independent, it may be supported by different maintainers and have different process of approvals. Moreover, the repos could be checked out separately and even be part of completely different repo as submodule.

So, the approach here is to create patch-sets independently for each repo and pass them through independently. It makes more sense if you create patches after commits with git format-patch.

Upvotes: 0

Related Questions