1414 * 1. Links `config.json` / `config.js` from the main checkout
1515 * 2. Checks out each asset submodule at the commit this branch pins, as a git
1616 * worktree of the main checkout's submodule repo (no network, no re-clone)
17+ * and moves existing clean, detached checkouts to the pinned commit
1718 * 3. Links the ignored minecraft texture pack from the main checkout
1819 * 4. Installs dependencies (this also blurs the public backgrounds)
1920 * 5. Restores the blurred private backgrounds from a cache, or generates them
@@ -130,12 +131,78 @@ function submodulePaths() {
130131 return output . split ( "\n" ) . map ( ( line ) => line . split ( " " ) [ 1 ] ) ;
131132}
132133
134+ /**
135+ * Checks that a submodule repo has a commit, fetching it when it is missing.
136+ * @param {string[] } repo git arguments that select the submodule repo
137+ * @param {string } sha
138+ * @returns {boolean } whether the commit is available
139+ */
140+ function hasCommit ( repo , sha ) {
141+ const exists = ( ) => {
142+ try {
143+ git ( [ ...repo , "cat-file" , "-e" , `${ sha } ^{commit}` ] ) ;
144+ return true ;
145+ } catch {
146+ return false ;
147+ }
148+ } ;
149+
150+ if ( exists ( ) ) return true ;
151+
152+ try {
153+ run ( "git" , [ ...repo , "fetch" , "origin" ] ) ;
154+ } catch {
155+ return false ;
156+ }
157+
158+ return exists ( ) ;
159+ }
160+
161+ /**
162+ * Moves an existing submodule checkout to the commit this branch pins, unless it is
163+ * on a branch or has local changes.
164+ * @param {string } path
165+ * @param {string } sha
166+ */
167+ function updateSubmodule ( path , sha ) {
168+ const target = join ( ROOT , path ) ;
169+ const head = git ( [ "rev-parse" , "HEAD" ] , target ) ;
170+ if ( head === sha ) return ;
171+
172+ let onBranch = true ;
173+
174+ try {
175+ git ( [ "symbolic-ref" , "-q" , "HEAD" ] , target ) ;
176+ } catch {
177+ onBranch = false ;
178+ }
179+
180+ if ( onBranch || git ( [ "status" , "--porcelain" ] , target ) !== "" ) {
181+ log (
182+ "Submodule" ,
183+ `${ path } is at ${ head . slice ( 0 , 8 ) } but this branch pins ${ sha . slice ( 0 , 8 ) } , leaving it because it is on a branch or has local changes` ,
184+ ) ;
185+ return ;
186+ }
187+
188+ if ( ! hasCommit ( [ "-C" , target ] , sha ) ) {
189+ log ( "Submodule" , `could not fetch ${ path } at ${ sha . slice ( 0 , 8 ) } ` ) ;
190+ return ;
191+ }
192+
193+ git ( [ "checkout" , "--detach" , sha ] , target ) ;
194+ log ( "Submodule" , `updated ${ path } to ${ sha . slice ( 0 , 8 ) } ` ) ;
195+ }
196+
133197/**
134198 * @param {string } path
135199 */
136200function setupSubmodule ( path ) {
137201 const target = join ( ROOT , path ) ;
138- if ( existsSync ( join ( target , ".git" ) ) ) return ;
202+ const sha = git ( [ "ls-tree" , "HEAD" , path ] ) . split ( / \s + / ) [ 2 ] ;
203+ if ( ! sha ) return ;
204+
205+ if ( existsSync ( join ( target , ".git" ) ) ) return updateSubmodule ( path , sha ) ;
139206
140207 const moduleDir = join ( COMMON_DIR , "modules" , path ) ;
141208
@@ -149,12 +216,9 @@ function setupSubmodule(path) {
149216 return ;
150217 }
151218
152- const sha = git ( [ "ls-tree" , "HEAD" , path ] ) . split ( / \s + / ) [ 2 ] ;
153-
154- try {
155- git ( [ "--git-dir" , moduleDir , "cat-file" , "-e" , `${ sha } ^{commit}` ] ) ;
156- } catch {
157- run ( "git" , [ "--git-dir" , moduleDir , "fetch" , "origin" ] ) ;
219+ if ( ! hasCommit ( [ "--git-dir" , moduleDir ] , sha ) ) {
220+ log ( "Submodule" , `could not fetch ${ path } , continuing without it` ) ;
221+ return ;
158222 }
159223
160224 // Worktrees that were deleted without `git worktree remove` leave stale entries behind
0 commit comments