skip to content

What does git submodule update --remote do that plain git submodule update does not?

level: middleimportance: should knowfreq 35%

answer

  1. One reproduces, one advances
  2. Where does the target commit come from?
  3. The pin versus a branch tip
  4. Leaves a pending change you must commit

basics

~20 s

Plain update checks out the exact commit the superproject has pinned. With --remote, Git instead fetches the submodule and checks out the tip of its configured branch, leaving a changed pin in your working tree that you still have to commit.

solid answer

~40 s

`git submodule update` is the reproducing operation: it checks out the commit recorded in the superproject's gitlink, which is what you want after a clone or a branch switch. `git submodule update --remote` is the *bumping* operation: it fetches the submodule and checks out the tip of the branch named by `submodule.<name>.branch` in `.gitmodules`, falling back to the remote's default branch if none is set. The submodule now sits at a commit different from the pin, so `git submodule status` shows `+` and the superproject reports the path as modified. Nothing is recorded until you `git add` the submodule path and commit in the superproject — and you should read what you are pinning before you do, because you are advancing a dependency by an arbitrary number of commits.

code

console · 9 lines
console
$ git config -f .gitmodules submodule.vendor/liblog.branch main
$ git submodule update --remote vendor/liblog
Submodule path 'vendor/liblog': checked out '1a7fdc3b...'

$ git submodule status
+1a7fdc3b... vendor/liblog (v1.5.0)

$ git add vendor/liblog .gitmodules
$ git commit -m "chore: bump liblog to v1.5.0"

go deeper

for a junior

Remember the distinction in one line: without the flag you get the commit the project pinned, with it you get the latest from a branch. Know that the second one is a change you then have to commit.

for a middle

Explain where the target commit comes from in each case, which config names the branch, and the fact that the bump lives only in the working tree until the submodule path is staged and committed in the superproject.

for a senior

Show the review discipline: read the commit range you are adopting rather than trusting the one-line diff, make sure everything you pin is on the submodule's remote, and know why habitual use of the flag erodes the guarantee pinning provides.

for a principal

Own the update policy: who bumps submodules, on what cadence, with what review of the adopted commit range. Deliberate pinning to a reviewed commit or tag is usually a better default than tracking a moving branch tip.

## Two different intentions These commands look similar and do nearly opposite things. **`git submodule update`** answers "make my working tree match this superproject commit". It reads the gitlink recorded in the index, fetches the submodule if that object is missing, and checks it out. The result is reproducible: the same superproject commit always produces the same submodule content. This is the command you run after cloning, after pulling, and after switching branches. **`git submodule update --remote`** answers "give me the latest of this dependency". It ignores the recorded commit as a target, fetches the submodule's remote, and checks out the tip of a branch. The result depends on when you run it, which is exactly the property the pin exists to eliminate — so it is a deliberate act, not a routine one. ## Which branch --remote follows The branch comes from `submodule.<name>.branch`, normally set in the tracked `.gitmodules` so the whole team agrees. If it is unset, Git uses the submodule remote's default branch. The special value `.` means "the branch with the same name as the branch currently checked out in the superproject", which is occasionally useful when the two repositories share a branching scheme. You set it with: `git config -f .gitmodules submodule.vendor/liblog.branch main` and commit `.gitmodules`, because it is tracked content. ## What --remote leaves you with After it runs, the submodule's checkout no longer matches the pin. Three signals of this: - `git submodule status` prints `+` before the SHA for that submodule. - `git status` in the superproject shows the submodule path as modified — with `status.submoduleSummary` enabled it summarises the commits involved instead of saying only "new commits". - `git diff` in the superproject shows the `Subproject commit` line changing. Nothing has been recorded. The bump becomes real only when you `git add <submodule-path>` and commit in the superproject. Until then, a plain `git submodule update` will happily put it back. ## Useful companions - `--init` also initialises any submodule not yet set up, so `--init --remote` works on a fresh clone. - `--recursive` applies to nested submodules. - `--merge` and `--rebase` change what happens when the submodule has a local branch checked out: instead of detaching onto the target commit, Git merges or rebases it into the current branch. Useful if you actively develop inside the submodule. - `--no-fetch` skips the network when you know the objects are already present. ## Reviewing a bump properly The superproject diff is one line of two object ids, which tells a reviewer nothing. Before committing a `--remote` bump, look at what you are actually taking: inside the submodule, `git log <old-sha>..<new-sha>` lists the commits you are adopting. Setting `diff.submodule` to `log` makes the superproject's own diff show that summary instead of the bare ids, which is worth turning on for any repository that bumps submodules regularly. And remember the push ordering rule: if any commit you are pinning is not on the submodule's remote — for instance because you had local work in the submodule — the superproject commit will be unusable for everyone else. ## When to reach for it `--remote` is right when the submodule is a first-party dependency you intend to track, and you are performing a deliberate, reviewed bump. It is wrong as a habitual command: running it reflexively converts an exactly pinned dependency into a moving target, which defeats the reason to use a submodule instead of vendoring. Many teams never run it and instead bump by checking out a specific tag or commit inside the submodule and recording that — which gives the same result with an explicit target rather than "whatever the tip was at that moment".

  • After running an update with --remote, what still has to happen for the bump to be permanent?
    You must record it in the superproject: `git add <submodule-path>` stages the submodule's current HEAD as the new gitlink, then you commit and push. Until that commit exists the bump is only a working-tree state, and a plain `git submodule update` will move the submodule straight back to the previously pinned commit.
  • Which branch does --remote follow, and where is that configured?
    The branch named by `submodule.<name>.branch`, normally set in the tracked `.gitmodules` file so the whole team shares it; if unset, Git uses the submodule remote's default branch. The special value `.` means the branch whose name matches the superproject's currently checked-out branch, which suits repositories that mirror each other's branch names.
  • How can a reviewer see what a one-line submodule bump actually contains?
    Set `diff.submodule` to `log` so the superproject's diff lists the submodule commits between the old and new ids rather than printing two bare SHAs. Otherwise, inside the submodule, `git log <old>..<new>` shows exactly the commits being adopted. Either way the author should summarise the change in the superproject's commit message.

saying these in an interview costs you the question

  • Thinks plain update pulls the latest submodule changes
  • Believes --remote commits the new pin for you
  • Assumes --remote always follows the branch named main
  • Runs --remote habitually and loses the value of pinning
  • Confuses updating the submodule with updating the superproject

context