1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214
  215
  216
  217
  218
  219
  220
  221
  222
  223
  224
  225
  226
  227
  228
  229
  230
  231
  232
  233
  234
  235
  236
  237
  238
  239
  240
  241
  242
  243
  244
  245
  246
  247
  248
  249
  250
  251
  252
  253
  254
  255
  256
  257
  258
  259
  260
  261
  262
  263
  264
  265
  266
  267
  268
  269
  270
  271
  272
  273
  274
  275
  276
  277
  278
  279
  280
  281
  282
  283
  284
  285
  286
  287
  288
  289
  290
  291
  292

docs / git_submodules.md [blame]

# Git submodules

A Git submodule is a Git repository inside another Git repository. Chromium
project doesn't rely on Git submodules directly. Instead, gclient sync is used
to manage Git dependencies.

In 2023Q3, we started to move source of Git dependencies from DEPS files to Git
submodules. While we do our best to hide complexities of submodules, some will
be exposed.

[TOC]

## A quick introduction to Git submoduldes

[Git submodules](https://git-scm.com/docs/gitsubmodules) are managed via the
combination of `.gitmodules` files and gitlinks. `.gitmodules` is a text file
that configures submodules, and each submodule entry contains the path to the
submodule's worktree and the URL of the submodule. Gitlink is a special type of
file in the Git database that tracks a submodule commit.

You can find an example of Git dependency below. Note that gclient-condition is
a custom property used by gclient and not git. It's identical to `condition` in
`DEPS` and the allowed variables are defined in `vars = {` section of `DEPS`.

`.gitmodules`:

```
[submodule "third_party/catapult"]
	path = third_party/catapult
	url = https://chromium.googlesource.com/catapult.git
	gclient-condition = checkout_linux
```

gitlink entry, retrieved using `git ls-files -s -- third_party/catapult`:

```
160000 0b39a694c0b61392d1180520ed1c13e390029c41 0       third_party/catapult
```

Corresponding DEPS entry would look like:

```
  'third_party/catapult': {
    'url': 'https://chromium.googlesource.com/catapult.git@0b39a694c0b61392d1180520ed1c13e390029c41',
    'condition': 'checkout_linux',
}
```

## How to avoid accidental Git submodule updates?

The simplest approach is to always run gclient sync after updating chromium
checkout (e.g. after `git pull`, or `git checkout`). You can automate that by
adding post-checkout hook (example below). To confirm there are no changes, run
`git status`. If you use `git commit -a`, check the "Changes to be committed"
section that shows up in the edit commit message.

### Automatically run gclient sync after git pull / git checkout

We need to have Git two hooks: post-checkout and post-merge. In chromium/src
directory, edit `.git/hooks/post-checkout`:

```
#!/bin/sh

set -u
gclient sync
```

and set it to be executable: `chmod +x .git/hooks/post-checkout`. Repeat the
same for `.git/hooks/post-merge`.

More information about githooks can be found
[here](https://git-scm.com/docs/githooks).

### Git status shows modified dependencies. What does that mean?

If a submodule is checked out at a different commit than one tracked by its
parent, `git status` in the parent repo will show unstaged changes with "new
commits" in parenthesis, such as:

```
modified: <git deps name> (new commits)
```

Commands like `git commit -a` or `git add *|.|-A|u` WILL include this in your
commit and your CL (which is likely NOT what you want).

Instead you can:

```
# Run gclient sync to sync dependencies
gclient sync
# check git status again

# OR
git add <file> # for each file you want to stage
# Then commit your staged files
git commit -v -m "Fix foo/bar"
```

If a submodule has uncommitted changes (i.e. you made some manual changes to the
affected submodule), running `git status` in its parent repo will show them as
unstaged changes:

```
  modified: <git deps name> (modified content)

# or

  modified: <git deps name> (untracked content)
```

It's not possible to add those changes to the parent repository. You can ignore
such status, or you can cd into submodule and address it. E.g. you may delete
untracked files (content) or reset modified content to match HEAD.

## I accidentally staged Git submodule (not yet committed)

If you accidentally stage a Git submodule, you can unstage it by running `git
restore --staged <path to submodule>`.

## I accidentally committed Git submodule

We will need to create either a commit that sets it back to old value, or amend
the commit that added it. You can try to run `gclient sync` to bring the commit
back to what is expected. If that doesn't work, you can use `gclient setdep -r
<path>@<old hash>`, run `gclient gitmodules` to sync all submodules commits back
to what is in DEPS, or check detailed instructions in [Managing
dependencies](dependencies.md).

NOTE: setdep for chromium/src is always prefixed with src/. For example, if you
are updating v8, the command would be `gclient setdep -r src/v8@<hash>.

## Workflows with submodules

### Submodules during 'git status', 'git commit', and 'git add'

For `git status`, submodules that show up under `Changes not staged for commit`
can be hidden with `git -c diff.ignoreSubmodules=all status`

For `git commit -a` you can exclude all submodules with
`git -c diff.ignoreSubmodules=all commit -a`.

`git add` does NOT support `diff.ignoreSubmodules`. Submodules that were
hidden from you with `git -c diff.ignoreSubmodules=all status` would still
be staged with `git add .|--all|-A` and therefore committed with
`git -c diff.ignoreSubmodules=all commit`.

Instead you can run `git add ':(exclude,attr:builtin_objectmode=160000)'` which
will stage all changes except for submodules.

(git assigns `160000` as the objectmode submodules. You can read more about
[`builtin_objectmode`](https://kernel.googlesource.com/pub/scm/git/git/+/refs/heads/next/Documentation/gitattributes.txt#110)
and magic [pathspecs](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefpathspecapathspec))

To make these commands shorter, you can create git aliases for them by adding
the following to your $HOME/.gitconfig (globally) or src/.git/config file (just
chromium/src):
```
[alias]
        # 's', 'c', or whatever alias you want for each command
        s = -c diff.ignoreSubmodules=all status
        c = -c diff.ignoreSubmodules=all commit -a
        d = -c diff.ignoreSubmodules=all difftool --dir-diff
        a = add ':(exclude,attr:builtin_objectmode=160000)'
```
With the above, you can execute these commands by running `git s`, `git c`, etc.
Or you may also use the pre-commit git hook detailed below.

### Understanding diff.ignoreSubmodules

`git config diff.ignoreSubmodules` sets a default behavior for `diff`, `status`,
and several other git subcommands, using one of the [supported values of
`--ignore-submodules`](https://www.git-scm.com/docs/git-diff/#Documentation/git-diff.txt---ignore-submodulesltwhengt).

By default, `gclient sync` sets this to `dirty` as a local config in the
chromium checkout. This elides submodule output for `git status` in a clean
checkout, but will show submodules as modified when developers locally touch
them.

Manually setting this to `all` elides such output in all cases. This also omits
submodule changes from `git commit -a`, which can decrease the likelihood of
accidental submodule commits. However, it does not omit such changes from
`git add -A`, meaning developers who use this flow are actually _more_ likely to
commit accidental changes, since they'll be invisible beforehand unless
developers manually set `--ignore-submodules=dirty` or use a lower-level command
such as `git diff-tree`.

Because `all` can result in misleading output and doesn't fully prevent
accidental submodule commits, typical developers are likely better-served by
leaving this configured to `dirty` and installing the
[commit hook described below](#install-hook) to prevent such commits.
Accordingly, `gclient sync` will warn if it detects a different setting locally;
developers who understand the consequences can silence the warning via the
`GCLIENT_SUPPRESS_SUBMODULE_WARNING` environment variable.

### Submodules during a 'git rebase-update'
While resolving merge conflicts during a `git rebase-update` you may see
submodules show up in unexpected places.

#### Submodules under "Changes not staged for commit"
Submodules under this section can be safely ignored. This simply shows that the
local commits of these submodules do not match the latest pinned commits fetched
from remote. In other words, these submodules have been rolled since your last
`git rebase-update`.

If you use a diff tool like meld you can run:
`git -c diff.ignoreSubmodules=all difftool --dir-diff`
to prevent these submodules from showing up in your diff tool.

#### Submodules under "Unmerged paths"
If Submodules show up under this section it means that new revisions were
committed for those submodules (either intentional or unintentionally) and these
submodules were also rolled at remote. So now there is a conflict.

If you DID NOT intentionally make any submdoules changes, you should run:
`gclient gitmodules`. This will update the submdoules for you, to match whatever
commits are listed in DEPS (which you have just pulled from remote).

If you DID intentionally roll submodules, you can resolve this conflict just by
resetting it:
`gclient setdep -r {path}@{hash}`

## Install a hook to help detect unintentional submodule commits {#install-hook}

depot_tools provides an opt-in pre-commit hook to detect unintentional submodule
 changes during `git commit` and remove them from the commit.

To install the hook: `gclient installhooks`

If there is an existing pre-commit hook, gclient will instruct you how to update
it. If you have already installed this hook, gclient will do nothing.

To uninstall the hook, in `chromium/src` `rm .git/hooks/pre-commit` if you have
no other hooks. Otherwise update `.git/hooks/pre-commit` to remove the gclient
provided hook.

To bypass this hook run `git commit --no-verify` (which bypasses all hooks you
 may have) OR set the following environment variable: `SKIP_GITLINK_PRECOMMIT=1`
(which bypasses this specific hook).

Note that this is currently and best effort solution and does not guarantee
that unintentional commits will always be detected. The team will iterate
quickly on this hook to fill in other gaps and behavior is subject to change.
Please file an [issue](https://bugs.chromium.org/p/chromium/issues/entry?components=Infra%3ESDK&labels=submodules-feedback&cc=sokcevic@chromium.org,jojwang@chromium.org&description=Please%20steps%20to%20reproduce%20the%20problem:%0A%0ADo%20you%20have%20any%20custom%20environment%20setups%20like%20git%20hooks%20or%20git%20configs%20that%20you%20have%20set%20yourself%0A%0APlease%20attach%20output%20of:%0Agit%20config%20-l%0Agit%20map-branches%20-vv%0A%0AIf%20this%20is%20an%20issue%20with%20git%20cl%20upload%20please%20include%20the%20git%20trace%20file%20for%20the%20problematic%20run%20found%20in:%0A%3Cdepot_tools_path%3E/traces/%3Clatest%20trace%3E) for any feedback.

## FAQ

### Why do we have Git dependencies in both DEPS and Git submodules?

Lots of Chromium infrastructure already parse DEPS file directly. Instead of a
massive switch, it's easier to transition to Git submodules this way. Moreover,
unwanted Git submodule updates can be detected and developers can be warned.

### How do I manually roll Git submodule?

See the [dependencies](dependencies.md) page.

### I got a conflict on a submodule, how do I resolve it?

First, you will need to determine what is the right commit hash. If you
accidentally committed a gitlink, which got in the meantime updated, you most
likely want to restore the original updated gitlink. You can run `gclient
gitmodules`, which will take care of all unmerged submodule paths, and set it to
match DEPS file.

If you prefer to manually resolve it, under git status, you will see "Unmerged
paths". If those are submodules, you want to restore them by running the
following command:

```
git restore --staging <affected path>
```

### How do I see what revision is pinned?

`gclient getdep` will return whatever commit is pinned for the deps in `DEPS`
(unstaged, staged, or committed). If the repo is using git submodules only
(and has no git deps in `DEPS`) it will return the whatever pinned commit is
staged or committed.

```
gclient getdep -r <path>
```


If you want to keep your gitlink, then run `git add <affected path>`.

### How can I provide feedback?

Please file [a bug under Infra>SDK
component](https://bugs.chromium.org/p/chromium/issues/entry?components=Infra%3ESDK).