2424)
2525from git .exc import (
2626InvalidGitRepositoryError ,
27- NoSuchPathError
27+ NoSuchPathError ,
28+ RepositoryDirtyError
2829)
2930from git .compat import (
3031string_types ,
3637
3738import os
3839import logging
40+ import tempfile
3941
4042__all__ = ["Submodule" ,"UpdateProgress" ]
4143
@@ -424,8 +426,8 @@ def add(cls, repo, name, path, url=None, branch=None, no_checkout=False):
424426
425427return sm
426428
427- def update (self ,recursive = False ,init = True ,to_latest_revision = False ,progress = None ,
428- dry_run = False ):
429+ def update (self ,recursive = False ,init = True ,to_latest_revision = False ,progress = None ,dry_run = False ,
430+ force = False ):
429431"""Update the repository of this submodule to point to the checkout
430432 we point at with the binsha of this instance.
431433
@@ -440,6 +442,12 @@ def update(self, recursive=False, init=True, to_latest_revision=False, progress=
440442 :param progress: UpdateProgress instance or None of no progress should be shown
441443 :param dry_run: if True, the operation will only be simulated, but not performed.
442444 All performed operations are read-only
445+ :param force:
446+ If True, we may reset heads even if the repository in question is dirty. Additinoally we will be allowed
447+ to set a tracking branch which is ahead of its remote branch back into the past or the location of the
448+ remote branch. This will essentially 'forget' commits.
449+ If False, local tracking branches that are in the future of their respective remote branches will simply
450+ not be moved.
443451 :note: does nothing in bare repositories
444452 :note: method is definitely not atomic if recurisve is True
445453 :return: self"""
@@ -565,23 +573,45 @@ def update(self, recursive=False, init=True, to_latest_revision=False, progress=
565573# update the working tree
566574# handles dry_run
567575if mrepo is not None and mrepo .head .commit .binsha != binsha :
576+ # We must assure that our destination sha (the one to point to) is in the future of our current head.
577+ # Otherwise, we will reset changes that might have been done on the submodule, but were not yet pushed
578+ # We also handle the case that history has been rewritten, leaving no merge-base. In that case
579+ # we behave conservatively, protecting possible changes the user had done
580+ may_reset = True
581+ if mrepo .head .commit .binsha != self .NULL_BIN_SHA :
582+ base_commit = mrepo .merge_base (mrepo .head .commit ,hexsha )
583+ if len (base_commit )== 0 or base_commit [0 ].hexsha == hexsha :
584+ if force :
585+ log .debug ("Will force checkout or reset on local branch that is possibly in the future of" +
586+ "the commit it will be checked out to, effectively 'forgetting' new commits" )
587+ else :
588+ log .info ("Skipping %s on branch '%s' of submodule repo '%s' as it contains un-pushed commits" ,
589+ is_detached and "checkout" or "reset" ,mrepo .head ,mrepo )
590+ may_reset = False
591+ # end handle force
592+ # end handle if we are in the future
593+
594+ if may_reset and not force and mrepo .is_dirty (index = True ,working_tree = True ,untracked_files = True ):
595+ raise RepositoryDirtyError (mrepo ,"Cannot reset a dirty repository" )
596+ # end handle force and dirty state
597+ # end handle empty repo
598+
599+ # end verify future/past
568600progress .update (BEGIN | UPDWKTREE ,0 ,1 ,prefix +
569601"Updating working tree at %s for submodule %r to revision %s"
570602% (self .path ,self .name ,hexsha ))
571- if not dry_run :
603+
604+ if not dry_run and may_reset :
572605if is_detached :
573606# NOTE: for now we force, the user is no supposed to change detached
574607# submodules anyway. Maybe at some point this becomes an option, to
575608# properly handle user modifications - see below for future options
576609# regarding rebase and merge.
577- mrepo .git .checkout (hexsha ,force = True )
610+ mrepo .git .checkout (hexsha ,force = force )
578611else :
579- # TODO: allow to specify a rebase, merge, or reset
580- # TODO: Warn if the hexsha forces the tracking branch off the remote
581- # branch - this should be prevented when setting the branch option
582612mrepo .head .reset (hexsha ,index = True ,working_tree = True )
583613# END handle checkout
584- #END handle dry_run
614+ #if we may reset/checkout
585615progress .update (END | UPDWKTREE ,0 ,1 ,prefix + "Done updating working tree for submodule %r" % self .name )
586616# END update to new commit only if needed
587617
@@ -591,7 +621,8 @@ def update(self, recursive=False, init=True, to_latest_revision=False, progress=
591621# in dry_run mode, the module might not exist
592622if mrepo is not None :
593623for submodule in self .iter_items (self .module ()):
594- submodule .update (recursive ,init ,to_latest_revision ,progress = progress ,dry_run = dry_run )
624+ submodule .update (recursive ,init ,to_latest_revision ,progress = progress ,dry_run = dry_run ,
625+ force = force )
595626# END handle recursive update
596627# END handle dry run
597628# END for each submodule
@@ -748,7 +779,7 @@ def remove(self, module=True, force=False, configuration=True, dry_run=False):
748779csm .remove (module ,force ,configuration ,dry_run )
749780del (csm )
750781# end
751- if nc > 0 :
782+ if not dry_run and nc > 0 :
752783# Assure we don't leave the parent repository in a dirty state, and commit our changes
753784# It's important for recursive, unforced, deletions to work as expected
754785self .module ().index .commit ("Removed submodule '%s'" % self .name )
@@ -854,8 +885,7 @@ def remove(self, module=True, force=False, configuration=True, dry_run=False):
854885def set_parent_commit (self ,commit ,check = True ):
855886"""Set this instance to use the given commit whose tree is supposed to
856887 contain the .gitmodules blob.
857-
858- :param commit: Commit'ish reference pointing at the root_tree
888+ :param commit: Commit'ish reference pointing at the root_tree, or None always point to the most recent commit
859889 :param check: if True, relatively expensive checks will be performed to verify
860890 validity of the submodule.
861891 :raise ValueError: if the commit's tree didn't contain the .gitmodules blob.
@@ -939,7 +969,7 @@ def rename(self, new_name):
939969pw .release ()
940970
941971# .gitmodules
942- cw = self .config_writer ().config
972+ cw = self .config_writer (write = False ).config
943973cw .rename_section (sm_section (self .name ),sm_section (new_name ))
944974cw .release ()
945975
@@ -948,9 +978,16 @@ def rename(self, new_name):
948978# .git/modules
949979mod = self .module ()
950980if mod .has_separate_working_tree ():
951- module_abspath = self ._module_abspath (self .repo ,self .path ,new_name )
952- os .renames (mod .git_dir ,module_abspath )
953- self ._write_git_file_and_module_config (mod .working_tree_dir ,module_abspath )
981+ destination_module_abspath = self ._module_abspath (self .repo ,self .path ,new_name )
982+ source_dir = mod .git_dir
983+ # Let's be sure the submodule name is not so obviously tied to a directory
984+ if destination_module_abspath .startswith (mod .git_dir ):
985+ tmp_dir = self ._module_abspath (self .repo ,self .path ,os .path .basename (tempfile .mkdtemp ()))
986+ os .renames (source_dir ,tmp_dir )
987+ source_dir = tmp_dir
988+ # end handle self-containment
989+ os .renames (source_dir ,destination_module_abspath )
990+ self ._write_git_file_and_module_config (mod .working_tree_dir ,destination_module_abspath )
954991# end move separate git repository
955992
956993return self