In Git, I have submodule B in module A. I want to go to a specific commit in B and then find the corresponding commits in A using that specific commit of B (if any). For example the latest commit of A using that specific B version would be ok. How to do this ?
3 Answers
With reasonable limitations this commit can be found using git bisect
:
#!/bin/bash
#
# Find superproject commit introducing given submodule's commit
#
# Usage: git submodule-introduced path/to/submodule <submodule-commit>
#
# Bisect is performed. On success found commit is displayed and written to the
# 'bisect/containing' ref
#
# Limitations:
# 1. Submodule must have monotonous history (all submodule updates are fast-forward)
# 2. Commit adding a submodule must have parents
set -e
export submodule=${1?specify submodule path}
export commit=${2?specify submodule commit}
from=$(git rev-list HEAD -- $submodule | tail -n 1)
git bisect start --no-checkout --term-bad=containing HEAD $from~ -- $submodule
git bisect run sh -c '! git -C $submodule merge-base --is-ancestor $commit $(git rev-parse BISECT_HEAD:$submodule)'

- 997
- 8
- 13
This question and @Max O's answer motivated me to create mod-from-sub.bash (disclaimer: this is my personal github).
Script goal: find the most recent module's ancestor commit for a given submodule's hash/branch
.
Sample output below (Module=A, Submodule=B) with the current working directory (cwd) as A's root dir:
$ PATH_CONTAINING_SHELL_DIR/shell/bash/git/mod-from-sub.bash --submodule 'relative/path/A/to/B'
INFO: mod-from-sub.bash: module is empty, using cwd='/path/to/A'.
INFO: mod-from-sub.bash: remote_module is empty, using 'origin'.
INFO: mod-from-sub.bash: ref_module is empty, using 'origin/master'.
INFO: mod-from-sub.bash: ref_sub is empty, using 'HEAD'.
################################################################################
INFO: mod-from-sub.bash: SUCCESS: submodule='relative/path/from/A/to/B' with ref='HEAD' has nearest ancestor for module='/path/to/A' on the below line.
90f6c749140e5efe2361b40ae7612c25f869b116
Caveats: This does not get the commit from A that introduced B's commit (not every commit from B will even have a corresponding commit from A)
With the below graph, If we look for the most recent ancestor of B2 we obtain A1, rather than A2. I have yet to have a real-world purpose to obtain A2 but if that is desired feel free to comment something and ill try to give you something serviceable.
A2 -> B3
| |
| B2
| |
A1 -> B1
Note: We still obtain A1 even if there was a commit A0 that pointed to B1, as A1 is then the most recent ancestor of B2.
Additionally, as of current, this is not a standalone script. The entire shell directory (pretty small set of .bash and .sh files) is needed; at some point I plan on making a mechanism to deploy scripts and their includes as standalone scripts, but I have not gotten around to it.

- 133
- 1
- 6
There's no guarantee that such a commit exists. If one does exist, it may not be unique (there may be more than one).
Of course, there's no guarantee that anything ever works, but that's not what I mean here. The design principle behind submodules goes the other way: the superproject, "module A" in your case, is considered to be the controlling item. You check out some given superproject commit—by branch name, or tag name, or raw hash ID, or whatever—and within that checked-out commit, the superproject contains the hash ID for a particular commit in the submodule.
Hence, for each commit in module A (or each commit that uses B anyway), there is some particular hash ID for B. Let's assume for the moment that there are just three commits in A, numbered A1, A2, and A3, and three commits in B, numbered B1, B2, and B3. If we check out A1, we get a B number. Let's say that's B2. If we check out A2, we get another B number. Let's say that's B2 again. Last, if we check out A3, we get another B number ... which may well be B2 again.
In this case, set of the "A" commits that correspond to commits B1 and B3 are both empty, and the set of "A" commits that correspond to B2 has cardinality 3. For some B commits, there is no A commit, and for other B commits, there is no unique A commit.
This is typically true of submodule usage, so given some B commit hash, it's not possible to find the corresponding A commit. You can, however, look through any number of A commits and find their corresponding B hashes. These are the gitlink entries in the tree for the commit see How can I get a git submodule's associated commit ID from a past commit in the parent clone? (note the git rev-parse
based answer, which is the way to go if you know the specific path to the submodule's gitlink). If you make a complete table of all A->B mappings it will be easy to invert the table and find the set of all A values for a given B value, and hence whether the set is empty.
-
Not an answer to my problem, sorry. First 4 paragraphs I knew about, fixed my question's phrasing so people don't focus on that. The last paragraph solves the opposite problem : given a commit of A, which commit of B ? But this I know how to do. Only the end hints at what I need without actually suggesting a method to do it. – Charles May 10 '17 at 11:11
-
That's because there *isn't* a method to do it! – torek May 10 '17 at 19:16
-
Hm ok really ? In the entire git and all its clones and extensions and add-ons ? Ok. Can't we then devise a home-made thing where we loop through all commits of A and filter corresponding commits of B for the one I want, and output the A commits which had a hit ? Even if it's none, it's okay. I don't have enough git bash knowledge to it myself. – Charles May 11 '17 at 13:02
-
1Yes, I fear you have to write a bit of code. You know the commit hash in B, so what you need is probably (this is entirely untested): `git rev-list --all | while read ahash; do abhash=$(git rev-parse --quiet --verify ${ahash}:path/to/b/submodule) || continue; test $abhash = $bhash && echo "$bhash found in $ahash"; done`. This assumes you've set `bhash` to the desired hash ID. If you're going to do this repeatedly, instead of looping over all hashes each time checking to see if the submodule is there at all, loop over it once with the check, write all the (ahash, abhash) pairs into [continued] – torek May 11 '17 at 16:58
-
1... into a map file (this is your map that you can invert). Then, e.g., just `grep ${bhash}\$ mapfile` to find lines ending with the desired B hash. – torek May 11 '17 at 17:00