55
66import logging
77
8+ # typing -------------------------------------------------------------------
9+ from typing import TYPE_CHECKING , Union
10+
811import git
912from git .exc import InvalidGitRepositoryError
13+ from git .types import Commit_ish
1014from git .util import IterableList
1115
1216from .base import Submodule , UpdateProgress
1317from .util import find_first_remote_branch
1418
15- # typing -------------------------------------------------------------------
16-
17- from typing import TYPE_CHECKING , Union
18-
19- from git .types import Commit_ish
20-
2119if TYPE_CHECKING :
2220 from git .repo import Repo
2321
@@ -87,6 +85,7 @@ def update( # type: ignore[override]
8785 dry_run : bool = False ,
8886 force_reset : bool = False ,
8987 keep_going : bool = False ,
88+ no_fetch : bool = False ,
9089 ) -> "RootModule" :
9190 """Update the submodules of this repository to the current HEAD commit.
9291
@@ -146,6 +145,11 @@ def update( # type: ignore[override]
146145 In conjunction with `dry_run`, this can be useful to anticipate all errors
147146 when updating submodules.
148147
148+ :param no_fetch:
149+ If ``True``, update using locally available objects and remote-tracking
150+ refs without fetching or cloning. Cached refs are preserved and used even
151+ when a submodule's URL changes.
152+
149153 :return:
150154 self
151155 """
@@ -254,7 +258,7 @@ def update( # type: ignore[override]
254258 # HANDLE URL CHANGE
255259 ###################
256260 if sm .url != psm .url :
257- # Add the new remote, remove the old one.
261+ # When fetching, add the new remote and remove the old one.
258262 # This way, if the url just changes, the commits will not have
259263 # to be re-retrieved.
260264 nn = "__new_origin__"
@@ -272,33 +276,19 @@ def update( # type: ignore[override]
272276 )
273277
274278 if not dry_run :
275- assert nn not in [r .name for r in rmts ]
276- smr = smm .create_remote (nn , sm .url )
277- smr .fetch (progress = progress )
278-
279- # If we have a tracking branch, it should be available
280- # in the new remote as well.
281- if len ([r for r in smr .refs if r .remote_head == sm .branch_name ]) == 0 :
282- raise ValueError (
283- "Submodule branch named %r was not available in new submodule remote at %r"
284- % (sm .branch_name , sm .url )
285- )
286- # END head is not detached
287-
288- # Now delete the changed one.
289- rmt_for_deletion = None
279+ previous_remote = None
290280 for remote in rmts :
291281 if remote .url == psm .url :
292- rmt_for_deletion = remote
282+ previous_remote = remote
293283 break
294284 # END if urls match
295285 # END for each remote
296286
297287 # If we didn't find a matching remote, but have exactly
298288 # one, we can safely use this one.
299- if rmt_for_deletion is None :
289+ if previous_remote is None :
300290 if len (rmts ) == 1 :
301- rmt_for_deletion = rmts [0 ]
291+ previous_remote = rmts [0 ]
302292 else :
303293 # If we have not found any remote with the
304294 # original URL we may not have a name. This is a
@@ -311,45 +301,64 @@ def update( # type: ignore[override]
311301 # END handle one single remote
312302 # END handle check we found a remote
313303
314- orig_name = rmt_for_deletion .name
315- smm .delete_remote (rmt_for_deletion )
316- # NOTE: Currently we leave tags from the deleted remotes
317- # as well as separate tracking branches in the possibly
318- # totally changed repository (someone could have changed
319- # the url to another project). At some point, one might
320- # want to clean it up, but the danger is high to remove
321- # stuff the user has added explicitly.
322-
323- # Rename the new remote back to what it was.
324- smr .rename (orig_name )
325-
326- # Early on, we verified that the our current tracking
327- # branch exists in the remote. Now we have to ensure
328- # that the sha we point to is still contained in the new
329- # remote tracking branch.
330- smsha = sm .binsha
331- found = False
332- rref = smr .refs [self .branch_name ]
333- for c in rref .commit .traverse ():
334- if c .binsha == smsha :
335- found = True
336- break
337- # END traverse all commits in search for sha
338- # END for each commit
339-
340- if not found :
341- # Adjust our internal binsha to use the one of the
342- # remote this way, it will be checked out in the
343- # next step. This will change the submodule relative
344- # to us, so the user will be able to commit the
345- # change easily.
346- _logger .warning (
347- "Current sha %s was not contained in the tracking\
304+ if no_fetch :
305+ # A new remote would have no cached refs. Preserve
306+ # the existing refs and tracking configuration for
307+ # offline updates instead of replacing the remote.
308+ previous_remote .set_url (git .Git .polish_url (sm .url , expand_vars = False ))
309+ else :
310+ assert nn not in [r .name for r in rmts ]
311+ smr = smm .create_remote (nn , sm .url )
312+ smr .fetch (progress = progress )
313+
314+ # If we have a tracking branch, it should be available
315+ # in the new remote as well.
316+ if len ([r for r in smr .refs if r .remote_head == sm .branch_name ]) == 0 :
317+ raise ValueError (
318+ "Submodule branch named %r was not available in new submodule remote at %r"
319+ % (sm .branch_name , sm .url )
320+ )
321+ # END head is not detached
322+
323+ orig_name = previous_remote .name
324+ smm .delete_remote (previous_remote )
325+ # NOTE: Currently we leave tags from the deleted remotes
326+ # as well as separate tracking branches in the possibly
327+ # totally changed repository (someone could have changed
328+ # the url to another project). At some point, one might
329+ # want to clean it up, but the danger is high to remove
330+ # stuff the user has added explicitly.
331+
332+ # Rename the new remote back to what it was.
333+ smr .rename (orig_name )
334+
335+ # Early on, we verified that the our current tracking
336+ # branch exists in the remote. Now we have to ensure
337+ # that the sha we point to is still contained in the new
338+ # remote tracking branch.
339+ smsha = sm .binsha
340+ found = False
341+ rref = smr .refs [self .branch_name ]
342+ for c in rref .commit .traverse ():
343+ if c .binsha == smsha :
344+ found = True
345+ break
346+ # END traverse all commits in search for sha
347+ # END for each commit
348+
349+ if not found :
350+ # Adjust our internal binsha to use the one of the
351+ # remote this way, it will be checked out in the
352+ # next step. This will change the submodule relative
353+ # to us, so the user will be able to commit the
354+ # change easily.
355+ _logger .warning (
356+ "Current sha %s was not contained in the tracking\
348357 branch at the new remote, setting it the the remote's tracking branch" ,
349- sm .hexsha ,
350- )
351- sm .binsha = rref .commit .binsha
352- # END reset binsha
358+ sm .hexsha ,
359+ )
360+ sm .binsha = rref .commit .binsha
361+ # END reset binsha
353362
354363 # NOTE: All checkout is performed by the base
355364 # implementation of update.
@@ -379,11 +388,12 @@ def update( # type: ignore[override]
379388 if not dry_run :
380389 smm = sm .module ()
381390 smmr = smm .remotes
382- # As the branch might not exist yet, we will have to fetch
383- # all remotes to be sure...
384- for remote in smmr :
385- remote .fetch (progress = progress )
386- # END for each remote
391+ # As the branch might not exist yet, fetch all remotes
392+ # unless restricted to locally cached refs.
393+ if not no_fetch :
394+ for remote in smmr :
395+ remote .fetch (progress = progress )
396+ # END for each remote
387397
388398 try :
389399 tbr = git .Head .create (
@@ -433,6 +443,7 @@ def update( # type: ignore[override]
433443 dry_run = dry_run ,
434444 force = force_reset ,
435445 keep_going = keep_going ,
446+ no_fetch = no_fetch ,
436447 )
437448
438449 # Update recursively depth first - question is which inconsistent state will
@@ -451,6 +462,7 @@ def update( # type: ignore[override]
451462 dry_run = dry_run ,
452463 force_reset = force_reset ,
453464 keep_going = keep_going ,
465+ no_fetch = no_fetch ,
454466 )
455467 # END handle dry_run
456468 # END handle recursive
0 commit comments