|
10 | 10 | importos
|
11 | 11 | importsys
|
12 | 12 | importweakref
|
| 13 | +importshutil |
13 | 14 |
|
14 | 15 | __all__= ("Submodule","RootModule")
|
15 | 16 |
|
16 | 17 | #{ Utilities
|
17 | 18 |
|
18 |
| -defsm_section(path): |
| 19 | +defsm_section(name): |
19 | 20 | """:return: section title used in .gitmodules configuration file"""
|
20 |
| -return'submodule "%s"'%path |
| 21 | +return'submodule "%s"'%name |
21 | 22 |
|
22 | 23 | defsm_name(section):
|
23 | 24 | """:return: name of the submodule as parsed from the section name"""
|
@@ -223,6 +224,7 @@ def update(self, recursive=False, init=True, to_latest_revision=False):
|
223 | 224 | if the remote repository had a master branch, or of the 'branch' option
|
224 | 225 | was specified for this submodule and the branch existed remotely
|
225 | 226 | :note: does nothing in bare repositories
|
| 227 | +:note: method is definitely not atomic if recurisve is True |
226 | 228 | :return: self"""
|
227 | 229 | ifself.repo.bare:
|
228 | 230 | returnself
|
@@ -329,6 +331,111 @@ def update(self, recursive=False, init=True, to_latest_revision=False):
|
329 | 331 |
|
330 | 332 | returnself
|
331 | 333 |
|
| 334 | +defremove(self,module=True,force=False,configuration=True,dry_run=False): |
| 335 | +"""Remove this submodule from the repository. This will remove our entry |
| 336 | +from the .gitmodules file and the entry in the .git/config file. |
| 337 | +:param module: If True, the module we point to will be deleted |
| 338 | +as well. If the module is currently on a commit which is not part |
| 339 | +of any branch in the remote, if the currently checked out branch |
| 340 | +is ahead of its tracking branch, if you have modifications in the |
| 341 | +working tree, or untracked files, |
| 342 | +In case the removal of the repository fails for these reasons, the |
| 343 | +submodule status will not have been altered. |
| 344 | +If this submodule has child-modules on its own, these will be deleted |
| 345 | +prior to touching the own module. |
| 346 | +:param force: Enforces the deletion of the module even though it contains |
| 347 | +modifications. This basically enforces a brute-force file system based |
| 348 | +deletion. |
| 349 | +:param configuration: if True, the submodule is deleted from the configuration, |
| 350 | +otherwise it isn't. Although this should be enabled most of the times, |
| 351 | +this flag enables you to safely delete the repository of your submodule. |
| 352 | +:param dry_run: if True, we will not actually do anything, but throw the errors |
| 353 | +we would usually throw |
| 354 | +:note: doesn't work in bare repositories |
| 355 | +:raise InvalidGitRepositoryError: thrown if the repository cannot be deleted |
| 356 | +:raise OSError: if directories or files could not be removed""" |
| 357 | +ifself.repo.bare: |
| 358 | +raiseInvalidGitRepositoryError("Cannot delete a submodule in bare repository") |
| 359 | +# END handle bare mode |
| 360 | + |
| 361 | +ifnot (module+configuration): |
| 362 | +raiseValueError("Need to specify to delete at least the module, or the configuration") |
| 363 | +# END handle params |
| 364 | + |
| 365 | +# DELETE MODULE REPOSITORY |
| 366 | +########################## |
| 367 | +ifmoduleandself.module_exists(): |
| 368 | +ifforce: |
| 369 | +# take the fast lane and just delete everything in our module path |
| 370 | +# TODO: If we run into permission problems, we have a highly inconsistent |
| 371 | +# state. Delete the .git folders last, start with the submodules first |
| 372 | +mp=self.module_path() |
| 373 | +method=None |
| 374 | +ifos.path.islink(mp): |
| 375 | +method=os.remove |
| 376 | +elifos.path.isdir(mp): |
| 377 | +method=shutil.rmtree |
| 378 | +elifos.path.exists(mp): |
| 379 | +raiseAssertionError("Cannot forcibly delete repository as it was neither a link, nor a directory") |
| 380 | +#END handle brutal deletion |
| 381 | +ifnotdry_run: |
| 382 | +assertmethod |
| 383 | +method(mp) |
| 384 | +#END apply deletion method |
| 385 | +else: |
| 386 | +# verify we may delete our module |
| 387 | +mod=self.module() |
| 388 | +ifmod.is_dirty(untracked_files=True): |
| 389 | +raiseInvalidGitRepositoryError("Cannot delete module at %s with any modifications, unless force is specified"%mod.working_tree_dir) |
| 390 | +# END check for dirt |
| 391 | + |
| 392 | +# figure out whether we have new commits compared to the remotes |
| 393 | +# NOTE: If the user pulled all the time, the remote heads might |
| 394 | +# not have been updated, so commits coming from the remote look |
| 395 | +# as if they come from us. But we stay strictly read-only and |
| 396 | +# don't fetch beforhand. |
| 397 | +forremoteinmod.remotes: |
| 398 | +num_branches_with_new_commits=0 |
| 399 | +rrefs=remote.refs |
| 400 | +forrrefinrrefs: |
| 401 | +num_branches_with_new_commits=len(mod.git.cherry(rref))!=0 |
| 402 | +# END for each remote ref |
| 403 | +# not a single remote branch contained all our commits |
| 404 | +ifnum_branches_with_new_commits==len(rrefs): |
| 405 | +raiseInvalidGitRepositoryError("Cannot delete module at %s as there are new commits"%mod.working_tree_dir) |
| 406 | +# END handle new commits |
| 407 | +# END for each remote |
| 408 | + |
| 409 | +# gently remove all submodule repositories |
| 410 | +forsminself.children(): |
| 411 | +sm.remove(module=True,force=False,configuration=False,dry_run=dry_run) |
| 412 | +# END for each child-submodule |
| 413 | + |
| 414 | +# finally delete our own submodule |
| 415 | +ifnotdry_run: |
| 416 | +shutil.rmtree(mod.working_tree_dir) |
| 417 | +# END delete tree if possible |
| 418 | +# END handle force |
| 419 | +# END handle module deletion |
| 420 | + |
| 421 | +# DELETE CONFIGURATION |
| 422 | +###################### |
| 423 | +ifconfigurationandnotdry_run: |
| 424 | +# first the index-entry |
| 425 | +index=self.repo.index |
| 426 | +try: |
| 427 | +del(index.entries[index.entry_key(self.path,0)]) |
| 428 | +exceptKeyError: |
| 429 | +pass |
| 430 | +#END delete entry |
| 431 | +index.write() |
| 432 | + |
| 433 | +# now git config - need the config intact, otherwise we can't query |
| 434 | +# inforamtion anymore |
| 435 | +self.repo.config_writer().remove_section(sm_section(self.name)) |
| 436 | +self.config_writer().remove_section() |
| 437 | +# END delete configuration |
| 438 | + |
332 | 439 | defset_parent_commit(self,commit,check=True):
|
333 | 440 | """Set this instance to use the given commit whose tree is supposed to
|
334 | 441 | contain the .gitmodules blob.
|
@@ -410,10 +517,23 @@ def module_exists(self):
|
410 | 517 | try:
|
411 | 518 | self.module()
|
412 | 519 | returnTrue
|
413 |
| -exceptInvalidGitRepositoryError: |
| 520 | +exceptException: |
414 | 521 | returnFalse
|
415 | 522 | # END handle exception
|
416 | 523 |
|
| 524 | +defexists(self): |
| 525 | +""":return: True if the submodule exists, False otherwise. Please note that |
| 526 | +a submodule may exist (in the .gitmodules file) even though its module |
| 527 | +doesn't exist""" |
| 528 | +self._clear_cache() |
| 529 | +try: |
| 530 | +self.path |
| 531 | +returnTrue |
| 532 | +exceptException: |
| 533 | +# we raise if the path cannot be restored from configuration |
| 534 | +returnFalse |
| 535 | +# END handle exceptions |
| 536 | + |
417 | 537 | @property
|
418 | 538 | defbranch(self):
|
419 | 539 | """:return: The branch name that we are to checkout"""
|
|