|
| 1 | +readwise-link:: [Why everyone hates git submodules | Readwise](https://read.readwise.io/new/read/01k91vdy4mm9wz23x89acfymgh) |
| 2 | + |
| 3 | +- [Why everyone hates git submodules - YouTube](https://www.youtube.com/watch?v=JESI498HSMA) |
| 4 | + - ## [[My Notes]] |
| 5 | + - In old versions of git, you had to run `git submodule update --recursive` after changing a branch, running git pull, merging and rebase, etc. In recent versions of git, you can run these two commands once, which will make it so that when you switch branches or run a git pull or git push, all submodules will be updated automatically as necessary. |
| 6 | + - ~~~ |
| 7 | + git config --global submodule.recurse true |
| 8 | + git config --global push.recurseSubmodules on-demand |
| 9 | + ~~~ |
| 10 | + - Some recommended aliases for submodules |
| 11 | + - ~~~ |
| 12 | + git config --global alias.smu "submodule update --init --recursive" |
| 13 | + git config --global alias.sms "submodule status" |
| 14 | + git config --global alias.sma "submodule add" |
| 15 | + ~~~ |
| 16 | + - One alternative to using git submodules is using a package manager like [[npm]] or [[pip]] and keeping the two repos separate, but including the child repo in the parent repo's package.json or whatever is the equivalent. Most package managers allow you to include a custom URL to a repo. So even if your library is private, you can still use this method. This might actually be less confusing for some teams. |
| 17 | + - ~~~ |
| 18 | + { |
| 19 | + "dependencies": { |
| 20 | + "library1": "git+ssh://[email protected]/User/library1.git#v1.2.3" |
| 21 | + } |
| 22 | + } |
| 23 | + ~~~ |
| 24 | + - ## [[Video]] |
| 25 | + - {{video https://www.youtube.com/watch?v=JESI498HSMA}} |
| 26 | + - ### {{youtube-timestamp 0}} Introduction to Git Submodules |
| 27 | + - #### What Are Submodules? |
| 28 | + - Git submodules are a way to put one Git repository inside another, allowing you to have nested Git repos |
| 29 | + - Common use case: when your team has written a library that is shared among multiple different applications |
| 30 | + - Each submodule is **pinned to a specific commit** |
| 31 | + - #### Cloning Repositories with Submodules |
| 32 | + - When you clone a repo that includes submodules, the code in the submodule will **not be downloaded automatically** |
| 33 | + - To download everything, add the `--recursive` flag when cloning: |
| 34 | + - `git clone --recursive <repo-url>` |
| 35 | + - This ensures all submodules, including entire git history, are downloaded |
| 36 | + - Alternatively, if you've already cloned the repo, run from the parent repo's directory: |
| 37 | + - `git submodule update --init --recursive` |
| 38 | + - #### How Submodules Work |
| 39 | + - When you run `git submodule update --init --recursive`, Git: |
| 40 | + - Looks at the `.gitmodules` file (created automatically when submodule was first added) |
| 41 | + - Goes through added submodules in this file |
| 42 | + - Clones each submodule from the specified URL |
| 43 | + - Checks out the specific commit the submodule is pinned to |
| 44 | + - To see which commit hash the submodule is currently pinned to: |
| 45 | + - `git ls-tree HEAD <submodule-path>` |
| 46 | + - The commit hash is stored in the hidden `.git` folder of the parent repo (not in `.gitmodules`) |
| 47 | + - #### Recursive Flag |
| 48 | + - Sometimes a submodule can have another submodule nested in it |
| 49 | + - The `--recursive` flag ensures nested submodules are all downloaded as well |
| 50 | + - #### Submodule Behavior |
| 51 | + - Once downloaded, submodule behaves like a regular git repository |
| 52 | + - It doesn't have its own hidden `.git` folder like the parent repo |
| 53 | + - Instead has a `.git` file that points at the parent repo |
| 54 | + - Git commands from the submodule directory only affect that specific repo, not the parent repo |
| 55 | + - ### {{youtube-timestamp 136}} Making Changes to Submodules |
| 56 | + - #### The Detached Head Problem |
| 57 | + - Making changes to submodules is one of the **most confusing aspects** when working with submodules |
| 58 | + - Parent repo always pins the submodule to a specific commit hash |
| 59 | + - When Git downloads a submodule, it's put into a **detached HEAD state** |
| 60 | + - Detached HEAD means you've checked out a specific commit without any branch checked out |
| 61 | + - #### Switching to a Branch |
| 62 | + - To make changes properly, switch to the main branch (or appropriate branch): |
| 63 | + - `cd <submodule-directory>` |
| 64 | + - `git checkout main` |
| 65 | + - This switch doesn't change files since the commit is already the latest on main |
| 66 | + - The purpose is so when you commit changes, they get added to a new commit on the branch |
| 67 | + - #### Updating the Parent Repo |
| 68 | + - After committing changes in submodule, the parent repo doesn't know about the new commit yet |
| 69 | + - Running `git status` in the parent repo shows the submodule has changed |
| 70 | + - Another way this happens: running `git pull` in the submodule directory to get latest changes |
| 71 | + - To update the pinned commit in the parent repo: |
| 72 | + - Run `git add <submodule-path>` in the parent directory (like any other change) |
| 73 | + - Commit this change to the parent repo |
| 74 | + - You're telling the parent repo: change submodule from old commit hash to new commit hash |
| 75 | + - After pushing parent repo, the pinned commit changes on GitHub as well |
| 76 | + - ### {{youtube-timestamp 244}} Branching and Auto-Update Configuration |
| 77 | + - #### Switching Branches with Submodules |
| 78 | + - When switching branches in the parent repo, by default Git will **not automatically update the submodule** |
| 79 | + - Example: main branch has submodule pinned to commit A, older branch has it pinned to commit B |
| 80 | + - After switching branches, submodule should have old commit checked out, but Git won't do this automatically |
| 81 | + - Running `git status` in parent repo shows Git thinks there are newer commits in submodule |
| 82 | + - #### Manual Update Required (Old Git Versions) |
| 83 | + - To fix this, run: `git submodule update` |
| 84 | + - This moves the submodule state to the pinned commit |
| 85 | + - Also necessary when: |
| 86 | + - Switching to a branch where submodule didn't exist yet |
| 87 | + - Running `git pull` in the parent repo |
| 88 | + - In older versions of Git, you had to run `git submodule update` after all these operations, which was extremely annoying |
| 89 | + - #### Auto-Update Configuration (Newer Git Versions) |
| 90 | + - In newer versions of Git, you can configure automatic updates |
| 91 | + - Run these two commands once to enable in your global git config: |
| 92 | + - `git config --global submodule.recurse true` |
| 93 | + - `git config --global push.recurseSubmodules on-demand` |
| 94 | + - This makes submodules update automatically when you: |
| 95 | + - Switch branches |
| 96 | + - Run `git pull` |
| 97 | + - Run `git push` |
| 98 | + - **Highly recommended**: Update Git to latest version before working with submodules |
| 99 | + - #### Useful Aliases |
| 100 | + - Setting up aliases for common operations makes things easier: |
| 101 | + - `git config --global alias.smu "submodule update --init --recursive"` |
| 102 | + - `git config --global alias.sms "submodule status"` |
| 103 | + - `git config --global alias.sma "submodule add"` |
| 104 | + - ### {{youtube-timestamp 336}} Adding and Removing Submodules |
| 105 | + - #### Adding a Submodule |
| 106 | + - Run: `git submodule add <repo-url> <path>` |
| 107 | + - This will: |
| 108 | + - Download the submodule repo to the specified directory |
| 109 | + - Create the `.gitmodules` file if necessary |
| 110 | + - Add the new submodule to it |
| 111 | + - By default, submodule will be on the latest commit of the default branch (usually main) |
| 112 | + - To pin it to a different commit: |
| 113 | + - Switch to that commit or branch in the submodule directory |
| 114 | + - Then commit the change to the parent repo |
| 115 | + - #### Removing a Submodule |
| 116 | + - Run: `git rm <submodule-path>` and commit your changes |
| 117 | + - In older versions of Git, this required a few more steps, but latest version handles it automatically |
| 118 | + - ### {{youtube-timestamp 383}} Alternatives and Conclusion |
| 119 | + - #### Evaluation of Submodules |
| 120 | + - Under the hood, submodules are **really elegant and powerful** |
| 121 | + - Unfortunately, the user experience is **really confusing and awful** |
| 122 | + - Recent versions of Git have made working with them easier (thanks to auto-update settings) |
| 123 | + - #### Alternative 1: Package Managers |
| 124 | + - Use a package manager like [[npm]] or [[pip]] instead |
| 125 | + - Keep the two repos completely separate |
| 126 | + - Include the child repo in the parent repo's `package.json` (or equivalent) |
| 127 | + - Most package managers allow custom URLs to repos, even private ones |
| 128 | + - Example format: |
| 129 | + - ~~~ |
| 130 | + { |
| 131 | + "dependencies": { |
| 132 | + "library1": "git+ssh://[email protected]/User/library1.git#v1.2.3" |
| 133 | + } |
| 134 | + } |
| 135 | + ~~~ |
| 136 | + - **Recommendation**: Use this method over submodules if you're not already using them |
| 137 | + - Benefits: |
| 138 | + - Saves team from potential headaches |
| 139 | + - Uses tools team is already comfortable with |
| 140 | + - Limitation: Not always possible depending on package manager setup and programming language |
| 141 | + - #### Alternative 2: Monorepo Approach |
| 142 | + - Put all code in a single repo in separate modules |
| 143 | + - Comes with its own set of trade-offs |
0 commit comments