Skip to content

Commit e492113

Browse files
committed
Impemented keep_going flag for Submodule.update()
Fixes #50
1 parent 80701fc commit e492113

File tree

3 files changed

+381
-352
lines changed

3 files changed

+381
-352
lines changed

‎git/objects/submodule/base.py

+159-146
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ def add(cls, repo, name, path, url=None, branch=None, no_checkout=False):
429429
return sm
430430

431431
def update(self, recursive=False, init=True, to_latest_revision=False, progress=None, dry_run=False,
432-
force=False):
432+
force=False, keep_going=False):
433433
"""Update the repository of this submodule to point to the checkout
434434
we point at with the binsha of this instance.
435435
@@ -450,6 +450,10 @@ def update(self, recursive=False, init=True, to_latest_revision=False, progress=
450450
remote branch. This will essentially 'forget' commits.
451451
If False, local tracking branches that are in the future of their respective remote branches will simply
452452
not be moved.
453+
:param keep_going: if True, we will ignore but log all errors, and keep going recursively.
454+
Unless dry_run is set as well, keep_going could cause subsequent/inherited errors you wouldn't see
455+
otherwise.
456+
In conjunction with dry_run, it can be useful to anticipate all errors when updating submodules
453457
:note: does nothing in bare repositories
454458
:note: method is definitely not atomic if recurisve is True
455459
:return: self"""
@@ -470,152 +474,158 @@ def update(self, recursive=False, init=True, to_latest_revision=False, progress=
470474
mrepo = None
471475
# END init mrepo
472476

473-
# ASSURE REPO IS PRESENT AND UPTODATE
474-
#####################################
475477
try:
476-
mrepo = self.module()
477-
rmts = mrepo.remotes
478-
len_rmts = len(rmts)
479-
for i, remote in enumerate(rmts):
480-
op = FETCH
481-
if i == 0:
482-
op |= BEGIN
483-
# END handle start
484-
485-
progress.update(op, i, len_rmts, prefix + "Fetching remote %s of submodule %r" % (remote, self.name))
486-
#===============================
478+
# ASSURE REPO IS PRESENT AND UPTODATE
479+
#####################################
480+
try:
481+
mrepo = self.module()
482+
rmts = mrepo.remotes
483+
len_rmts = len(rmts)
484+
for i, remote in enumerate(rmts):
485+
op = FETCH
486+
if i == 0:
487+
op |= BEGIN
488+
# END handle start
489+
490+
progress.update(op, i, len_rmts, prefix + "Fetching remote %s of submodule %r" % (remote, self.name))
491+
#===============================
492+
if not dry_run:
493+
remote.fetch(progress=progress)
494+
# END handle dry-run
495+
#===============================
496+
if i == len_rmts - 1:
497+
op |= END
498+
# END handle end
499+
progress.update(op, i, len_rmts, prefix + "Done fetching remote of submodule %r" % self.name)
500+
# END fetch new data
501+
except InvalidGitRepositoryError:
502+
if not init:
503+
return self
504+
# END early abort if init is not allowed
505+
506+
# there is no git-repository yet - but delete empty paths
507+
checkout_module_abspath = self.abspath
508+
if not dry_run and os.path.isdir(checkout_module_abspath):
509+
try:
510+
os.rmdir(checkout_module_abspath)
511+
except OSError:
512+
raise OSError("Module directory at %r does already exist and is non-empty"
513+
% checkout_module_abspath)
514+
# END handle OSError
515+
# END handle directory removal
516+
517+
# don't check it out at first - nonetheless it will create a local
518+
# branch according to the remote-HEAD if possible
519+
progress.update(BEGIN | CLONE, 0, 1, prefix + "Cloning url '%s' to '%s' in submodule %r" %
520+
(self.url, checkout_module_abspath, self.name))
487521
if not dry_run:
488-
remote.fetch(progress=progress)
522+
mrepo = self._clone_repo(self.repo, self.url, self.path, self.name, n=True)
489523
# END handle dry-run
490-
#===============================
491-
if i == len_rmts - 1:
492-
op |= END
493-
# END handle end
494-
progress.update(op, i, len_rmts, prefix + "Done fetching remote of submodule %r" % self.name)
495-
# END fetch new data
496-
except InvalidGitRepositoryError:
497-
if not init:
498-
return self
499-
# END early abort if init is not allowed
524+
progress.update(END | CLONE, 0, 1, prefix + "Done cloning to %s" % checkout_module_abspath)
500525

501-
# there is no git-repository yet - but delete empty paths
502-
checkout_module_abspath = self.abspath
503-
if not dry_run and os.path.isdir(checkout_module_abspath):
504-
try:
505-
os.rmdir(checkout_module_abspath)
506-
except OSError:
507-
raise OSError("Module directory at %r does already exist and is non-empty"
508-
% checkout_module_abspath)
509-
# END handle OSError
510-
# END handle directory removal
511-
512-
# don't check it out at first - nonetheless it will create a local
513-
# branch according to the remote-HEAD if possible
514-
progress.update(BEGIN | CLONE, 0, 1, prefix + "Cloning %s to %s in submodule %r" %
515-
(self.url, checkout_module_abspath, self.name))
516-
if not dry_run:
517-
mrepo = self._clone_repo(self.repo, self.url, self.path, self.name, n=True)
518-
# END handle dry-run
519-
progress.update(END | CLONE, 0, 1, prefix + "Done cloning to %s" % checkout_module_abspath)
520-
521-
if not dry_run:
522-
# see whether we have a valid branch to checkout
523-
try:
524-
# find a remote which has our branch - we try to be flexible
525-
remote_branch = find_first_remote_branch(mrepo.remotes, self.branch_name)
526-
local_branch = mkhead(mrepo, self.branch_path)
527-
528-
# have a valid branch, but no checkout - make sure we can figure
529-
# that out by marking the commit with a null_sha
530-
local_branch.set_object(util.Object(mrepo, self.NULL_BIN_SHA))
531-
# END initial checkout + branch creation
532-
533-
# make sure HEAD is not detached
534-
mrepo.head.set_reference(local_branch, logmsg="submodule: attaching head to %s" % local_branch)
535-
mrepo.head.ref.set_tracking_branch(remote_branch)
536-
except IndexError:
537-
log.warn("Failed to checkout tracking branch %s", self.branch_path)
538-
# END handle tracking branch
539-
540-
# NOTE: Have to write the repo config file as well, otherwise
541-
# the default implementation will be offended and not update the repository
542-
# Maybe this is a good way to assure it doesn't get into our way, but
543-
# we want to stay backwards compatible too ... . Its so redundant !
544-
writer = self.repo.config_writer()
545-
writer.set_value(sm_section(self.name), 'url', self.url)
546-
writer.release()
526+
if not dry_run:
527+
# see whether we have a valid branch to checkout
528+
try:
529+
# find a remote which has our branch - we try to be flexible
530+
remote_branch = find_first_remote_branch(mrepo.remotes, self.branch_name)
531+
local_branch = mkhead(mrepo, self.branch_path)
532+
533+
# have a valid branch, but no checkout - make sure we can figure
534+
# that out by marking the commit with a null_sha
535+
local_branch.set_object(util.Object(mrepo, self.NULL_BIN_SHA))
536+
# END initial checkout + branch creation
537+
538+
# make sure HEAD is not detached
539+
mrepo.head.set_reference(local_branch, logmsg="submodule: attaching head to %s" % local_branch)
540+
mrepo.head.ref.set_tracking_branch(remote_branch)
541+
except IndexError:
542+
log.warn("Failed to checkout tracking branch %s", self.branch_path)
543+
# END handle tracking branch
544+
545+
# NOTE: Have to write the repo config file as well, otherwise
546+
# the default implementation will be offended and not update the repository
547+
# Maybe this is a good way to assure it doesn't get into our way, but
548+
# we want to stay backwards compatible too ... . Its so redundant !
549+
writer = self.repo.config_writer()
550+
writer.set_value(sm_section(self.name), 'url', self.url)
551+
writer.release()
552+
# END handle dry_run
553+
# END handle initalization
554+
555+
# DETERMINE SHAS TO CHECKOUT
556+
############################
557+
binsha = self.binsha
558+
hexsha = self.hexsha
559+
if mrepo is not None:
560+
# mrepo is only set if we are not in dry-run mode or if the module existed
561+
is_detached = mrepo.head.is_detached
547562
# END handle dry_run
548-
# END handle initalization
549-
550-
# DETERMINE SHAS TO CHECKOUT
551-
############################
552-
binsha = self.binsha
553-
hexsha = self.hexsha
554-
if mrepo is not None:
555-
# mrepo is only set if we are not in dry-run mode or if the module existed
556-
is_detached = mrepo.head.is_detached
557-
# END handle dry_run
558-
559-
if mrepo is not None and to_latest_revision:
560-
msg_base = "Cannot update to latest revision in repository at %r as " % mrepo.working_dir
561-
if not is_detached:
562-
rref = mrepo.head.ref.tracking_branch()
563-
if rref is not None:
564-
rcommit = rref.commit
565-
binsha = rcommit.binsha
566-
hexsha = rcommit.hexsha
567-
else:
568-
log.error("%s a tracking branch was not set for local branch '%s'", msg_base, mrepo.head.ref)
569-
# END handle remote ref
570-
else:
571-
log.error("%s there was no local tracking branch", msg_base)
572-
# END handle detached head
573-
# END handle to_latest_revision option
574-
575-
# update the working tree
576-
# handles dry_run
577-
if mrepo is not None and mrepo.head.commit.binsha != binsha:
578-
# We must assure that our destination sha (the one to point to) is in the future of our current head.
579-
# Otherwise, we will reset changes that might have been done on the submodule, but were not yet pushed
580-
# We also handle the case that history has been rewritten, leaving no merge-base. In that case
581-
# we behave conservatively, protecting possible changes the user had done
582-
may_reset = True
583-
if mrepo.head.commit.binsha != self.NULL_BIN_SHA:
584-
base_commit = mrepo.merge_base(mrepo.head.commit, hexsha)
585-
if len(base_commit) == 0 or base_commit[0].hexsha == hexsha:
586-
if force:
587-
log.debug("Will force checkout or reset on local branch that is possibly in the future of" +
588-
"the commit it will be checked out to, effectively 'forgetting' new commits")
563+
564+
if mrepo is not None and to_latest_revision:
565+
msg_base = "Cannot update to latest revision in repository at %r as " % mrepo.working_dir
566+
if not is_detached:
567+
rref = mrepo.head.ref.tracking_branch()
568+
if rref is not None:
569+
rcommit = rref.commit
570+
binsha = rcommit.binsha
571+
hexsha = rcommit.hexsha
589572
else:
590-
log.info("Skipping %s on branch '%s' of submodule repo '%s' as it contains un-pushed commits",
591-
is_detached and "checkout" or "reset", mrepo.head, mrepo)
592-
may_reset = False
593-
# end handle force
594-
# end handle if we are in the future
595-
596-
if may_reset and not force and mrepo.is_dirty(index=True, working_tree=True, untracked_files=True):
597-
raise RepositoryDirtyError(mrepo, "Cannot reset a dirty repository")
598-
# end handle force and dirty state
599-
# end handle empty repo
600-
601-
# end verify future/past
602-
progress.update(BEGIN | UPDWKTREE, 0, 1, prefix +
603-
"Updating working tree at %s for submodule %r to revision %s"
604-
% (self.path, self.name, hexsha))
605-
606-
if not dry_run and may_reset:
607-
if is_detached:
608-
# NOTE: for now we force, the user is no supposed to change detached
609-
# submodules anyway. Maybe at some point this becomes an option, to
610-
# properly handle user modifications - see below for future options
611-
# regarding rebase and merge.
612-
mrepo.git.checkout(hexsha, force=force)
573+
log.error("%s a tracking branch was not set for local branch '%s'", msg_base, mrepo.head.ref)
574+
# END handle remote ref
613575
else:
614-
mrepo.head.reset(hexsha, index=True, working_tree=True)
615-
# END handle checkout
616-
# if we may reset/checkout
617-
progress.update(END | UPDWKTREE, 0, 1, prefix + "Done updating working tree for submodule %r" % self.name)
618-
# END update to new commit only if needed
576+
log.error("%s there was no local tracking branch", msg_base)
577+
# END handle detached head
578+
# END handle to_latest_revision option
579+
580+
# update the working tree
581+
# handles dry_run
582+
if mrepo is not None and mrepo.head.commit.binsha != binsha:
583+
# We must assure that our destination sha (the one to point to) is in the future of our current head.
584+
# Otherwise, we will reset changes that might have been done on the submodule, but were not yet pushed
585+
# We also handle the case that history has been rewritten, leaving no merge-base. In that case
586+
# we behave conservatively, protecting possible changes the user had done
587+
may_reset = True
588+
if mrepo.head.commit.binsha != self.NULL_BIN_SHA:
589+
base_commit = mrepo.merge_base(mrepo.head.commit, hexsha)
590+
if len(base_commit) == 0 or base_commit[0].hexsha == hexsha:
591+
if force:
592+
log.debug("Will force checkout or reset on local branch that is possibly in the future of" +
593+
"the commit it will be checked out to, effectively 'forgetting' new commits")
594+
else:
595+
log.info("Skipping %s on branch '%s' of submodule repo '%s' as it contains un-pushed commits",
596+
is_detached and "checkout" or "reset", mrepo.head, mrepo)
597+
may_reset = False
598+
# end handle force
599+
# end handle if we are in the future
600+
601+
if may_reset and not force and mrepo.is_dirty(index=True, working_tree=True, untracked_files=True):
602+
raise RepositoryDirtyError(mrepo, "Cannot reset a dirty repository")
603+
# end handle force and dirty state
604+
# end handle empty repo
605+
606+
# end verify future/past
607+
progress.update(BEGIN | UPDWKTREE, 0, 1, prefix +
608+
"Updating working tree at %s for submodule %r to revision %s"
609+
% (self.path, self.name, hexsha))
610+
611+
if not dry_run and may_reset:
612+
if is_detached:
613+
# NOTE: for now we force, the user is no supposed to change detached
614+
# submodules anyway. Maybe at some point this becomes an option, to
615+
# properly handle user modifications - see below for future options
616+
# regarding rebase and merge.
617+
mrepo.git.checkout(hexsha, force=force)
618+
else:
619+
mrepo.head.reset(hexsha, index=True, working_tree=True)
620+
# END handle checkout
621+
# if we may reset/checkout
622+
progress.update(END | UPDWKTREE, 0, 1, prefix + "Done updating working tree for submodule %r" % self.name)
623+
# END update to new commit only if needed
624+
except Exception as err:
625+
if not keep_going:
626+
raise
627+
log.error(str(err))
628+
# end handle keep_going
619629

620630
# HANDLE RECURSION
621631
##################
@@ -624,7 +634,7 @@ def update(self, recursive=False, init=True, to_latest_revision=False, progress=
624634
if mrepo is not None:
625635
for submodule in self.iter_items(self.module()):
626636
submodule.update(recursive, init, to_latest_revision, progress=progress, dry_run=dry_run,
627-
force=force)
637+
force=force, keep_going=keep_going)
628638
# END handle recursive update
629639
# END handle dry run
630640
# END for each submodule
@@ -887,13 +897,16 @@ def remove(self, module=True, force=False, configuration=True, dry_run=False):
887897
def set_parent_commit(self, commit, check=True):
888898
"""Set this instance to use the given commit whose tree is supposed to
889899
contain the .gitmodules blob.
890-
:param commit: Commit'ish reference pointing at the root_tree, or None to always point to the
900+
901+
:param commit:
902+
Commit'ish reference pointing at the root_tree, or None to always point to the
891903
most recent commit
892-
:param check: if True, relatively expensive checks will be performed to verify
904+
:param check:
905+
if True, relatively expensive checks will be performed to verify
893906
validity of the submodule.
894907
:raise ValueError: if the commit's tree didn't contain the .gitmodules blob.
895-
:raise ValueError: if the parent commit didn't store this submodule under the
896-
current path
908+
:raise ValueError:
909+
if the parent commit didn't store this submodule under the current path
897910
:return: self"""
898911
if commit is None:
899912
self._parent_commit = None
@@ -976,7 +989,7 @@ def rename(self, new_name):
976989
pw.release()
977990

978991
# .gitmodules
979-
cw = self.config_writer(write=False).config
992+
cw = self.config_writer(write=True).config
980993
cw.rename_section(sm_section(self.name), sm_section(new_name))
981994
cw.release()
982995

0 commit comments

Comments
 (0)