| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent ea29541 commit 9fbae71
7 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -546,10 +546,12 @@ def set_value(self, section, option, value): | |||
| 546 | 546 | :param option: Name of the options whose value to set | |
| 547 | 547 | ||
| 548 | 548 | :param value: Value to set the option to. It must be a string or convertible | |
| 549 | - to a string""" | ||
| 549 | + to a string | ||
| 550 | + :return: this instance""" | ||
| 550 | 551 | if not self.has_section(section): | |
| 551 | 552 | self.add_section(section) | |
| 552 | 553 | self.set(section, option, self._value_to_string(value)) | |
| 554 | + return self | ||
| 553 | 555 | ||
| 554 | 556 | def rename_section(self, section, new_name): | |
| 555 | 557 | """rename the given section to new_name | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -84,3 +84,14 @@ def __init__(self, command, status, stdout, stderr): | |||
| 84 | 84 | def __str__(self): | |
| 85 | 85 | return ("'%s' hook returned with exit code %i\nstdout: '%s'\nstderr: '%s'" | |
| 86 | 86 | % (self.command, self.status, self.stdout, self.stderr)) | |
| 87 | + | ||
| 88 | + | ||
| 89 | + class RepositoryDirtyError(Exception): | ||
| 90 | + """Thrown whenever an operation on a repository fails as it has uncommited changes that would be overwritten""" | ||
| 91 | + | ||
| 92 | + def __init__(self, repo, message): | ||
| 93 | + self.repo = repo | ||
| 94 | + self.message = message | ||
| 95 | + | ||
| 96 | + def __str__(self): | ||
| 97 | + return "Operation cannot be performed on %r: %s" % (self.repo, self.message) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -24,7 +24,8 @@ | |||
| 24 | 24 | ) | |
| 25 | 25 | from git.exc import ( | |
| 26 | 26 | InvalidGitRepositoryError, | |
| 27 | - NoSuchPathError | ||
| 27 | + NoSuchPathError, | ||
| 28 | + RepositoryDirtyError | ||
| 28 | 29 | ) | |
| 29 | 30 | from git.compat import ( | |
| 30 | 31 | string_types, | |
@@ -36,6 +37,7 @@ | |||
| 36 | 37 | ||
| 37 | 38 | import os | |
| 38 | 39 | import logging | |
| 40 | + import tempfile | ||
| 39 | 41 | ||
| 40 | 42 | __all__ = ["Submodule", "UpdateProgress"] | |
| 41 | 43 | ||
@@ -424,8 +426,8 @@ def add(cls, repo, name, path, url=None, branch=None, no_checkout=False): | |||
| 424 | 426 | ||
| 425 | 427 | return sm | |
| 426 | 428 | ||
| 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): | ||
| 429 | 431 | """Update the repository of this submodule to point to the checkout | |
| 430 | 432 | we point at with the binsha of this instance. | |
| 431 | 433 | ||
@@ -440,6 +442,12 @@ def update(self, recursive=False, init=True, to_latest_revision=False, progress= | |||
| 440 | 442 | :param progress: UpdateProgress instance or None of no progress should be shown | |
| 441 | 443 | :param dry_run: if True, the operation will only be simulated, but not performed. | |
| 442 | 444 | 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. | ||
| 443 | 451 | :note: does nothing in bare repositories | |
| 444 | 452 | :note: method is definitely not atomic if recurisve is True | |
| 445 | 453 | :return: self""" | |
@@ -565,23 +573,45 @@ def update(self, recursive=False, init=True, to_latest_revision=False, progress= | |||
| 565 | 573 | # update the working tree | |
| 566 | 574 | # handles dry_run | |
| 567 | 575 | if 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 | ||
| 568 | 600 | progress.update(BEGIN | UPDWKTREE, 0, 1, prefix + | |
| 569 | 601 | "Updating working tree at %s for submodule %r to revision %s" | |
| 570 | 602 | % (self.path, self.name, hexsha)) | |
| 571 | - if not dry_run: | ||
| 603 | + | ||
| 604 | + if not dry_run and may_reset: | ||
| 572 | 605 | if is_detached: | |
| 573 | 606 | # NOTE: for now we force, the user is no supposed to change detached | |
| 574 | 607 | # submodules anyway. Maybe at some point this becomes an option, to | |
| 575 | 608 | # properly handle user modifications - see below for future options | |
| 576 | 609 | # regarding rebase and merge. | |
| 577 | - mrepo.git.checkout(hexsha, force=True) | ||
| 610 | + mrepo.git.checkout(hexsha, force=force) | ||
| 578 | 611 | else: | |
| 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 | ||
| 582 | 612 | mrepo.head.reset(hexsha, index=True, working_tree=True) | |
| 583 | 613 | # END handle checkout | |
| 584 | - # END handle dry_run | ||
| 614 | + # if we may reset/checkout | ||
| 585 | 615 | progress.update(END | UPDWKTREE, 0, 1, prefix + "Done updating working tree for submodule %r" % self.name) | |
| 586 | 616 | # END update to new commit only if needed | |
| 587 | 617 | ||
@@ -591,7 +621,8 @@ def update(self, recursive=False, init=True, to_latest_revision=False, progress= | |||
| 591 | 621 | # in dry_run mode, the module might not exist | |
| 592 | 622 | if mrepo is not None: | |
| 593 | 623 | for 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) | ||
| 595 | 626 | # END handle recursive update | |
| 596 | 627 | # END handle dry run | |
| 597 | 628 | # END for each submodule | |
@@ -748,7 +779,7 @@ def remove(self, module=True, force=False, configuration=True, dry_run=False): | |||
| 748 | 779 | csm.remove(module, force, configuration, dry_run) | |
| 749 | 780 | del(csm) | |
| 750 | 781 | # end | |
| 751 | - if nc > 0: | ||
| 782 | + if not dry_run and nc > 0: | ||
| 752 | 783 | # Assure we don't leave the parent repository in a dirty state, and commit our changes | |
| 753 | 784 | # It's important for recursive, unforced, deletions to work as expected | |
| 754 | 785 | self.module().index.commit("Removed submodule '%s'" % self.name) | |
@@ -854,8 +885,7 @@ def remove(self, module=True, force=False, configuration=True, dry_run=False): | |||
| 854 | 885 | def set_parent_commit(self, commit, check=True): | |
| 855 | 886 | """Set this instance to use the given commit whose tree is supposed to | |
| 856 | 887 | 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 | ||
| 859 | 889 | :param check: if True, relatively expensive checks will be performed to verify | |
| 860 | 890 | validity of the submodule. | |
| 861 | 891 | :raise ValueError: if the commit's tree didn't contain the .gitmodules blob. | |
@@ -939,7 +969,7 @@ def rename(self, new_name): | |||
| 939 | 969 | pw.release() | |
| 940 | 970 | ||
| 941 | 971 | # .gitmodules | |
| 942 | - cw = self.config_writer().config | ||
| 972 | + cw = self.config_writer(write=False).config | ||
| 943 | 973 | cw.rename_section(sm_section(self.name), sm_section(new_name)) | |
| 944 | 974 | cw.release() | |
| 945 | 975 | ||
@@ -948,9 +978,16 @@ def rename(self, new_name): | |||
| 948 | 978 | # .git/modules | |
| 949 | 979 | mod = self.module() | |
| 950 | 980 | if 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) | ||
| 954 | 991 | # end move separate git repository | |
| 955 | 992 | ||
| 956 | 993 | return self | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -61,7 +61,7 @@ def _clear_cache(self): | |||
| 61 | 61 | #{ Interface | |
| 62 | 62 | ||
| 63 | 63 | def update(self, previous_commit=None, recursive=True, force_remove=False, init=True, | |
| 64 | - to_latest_revision=False, progress=None, dry_run=False): | ||
| 64 | + to_latest_revision=False, progress=None, dry_run=False, force_reset=False): | ||
| 65 | 65 | """Update the submodules of this repository to the current HEAD commit. | |
| 66 | 66 | This method behaves smartly by determining changes of the path of a submodules | |
| 67 | 67 | repository, next to changes to the to-be-checked-out commit or the branch to be | |
@@ -80,10 +80,16 @@ def update(self, previous_commit=None, recursive=True, force_remove=False, init= | |||
| 80 | 80 | :param init: If we encounter a new module which would need to be initialized, then do it. | |
| 81 | 81 | :param to_latest_revision: If True, instead of checking out the revision pointed to | |
| 82 | 82 | by this submodule's sha, the checked out tracking branch will be merged with the | |
| 83 | - newest remote branch fetched from the repository's origin | ||
| 83 | + latest remote branch fetched from the repository's origin. | ||
| 84 | + Unless force_reset is specified, a local tracking branch will never be reset into its past, therefore | ||
| 85 | + the remote branch must be in the future for this to have an effect. | ||
| 86 | + :param force_reset: if True, submodules may checkout or reset their branch even if the repository has | ||
| 87 | + pending changes that would be overwritten, or if the local tracking branch is in the future of the | ||
| 88 | + remote tracking branch and would be reset into its past. | ||
| 84 | 89 | :param progress: RootUpdateProgress instance or None if no progress should be sent | |
| 85 | 90 | :param dry_run: if True, operations will not actually be performed. Progress messages | |
| 86 | - will change accordingly to indicate the WOULD DO state of the operation.""" | ||
| 91 | + will change accordingly to indicate the WOULD DO state of the operation. | ||
| 92 | + :return: self""" | ||
| 87 | 93 | if self.repo.bare: | |
| 88 | 94 | raise InvalidGitRepositoryError("Cannot update submodules in bare repositories") | |
| 89 | 95 | # END handle bare | |
@@ -134,9 +140,7 @@ def update(self, previous_commit=None, recursive=True, force_remove=False, init= | |||
| 134 | 140 | # of previous module. Trigger the cache to be updated before that | |
| 135 | 141 | progress.update(op, i, len_rrsm, prefix + "Removing submodule %r at %s" % (rsm.name, rsm.abspath)) | |
| 136 | 142 | rsm._parent_commit = repo.head.commit | |
| 137 | - if not dry_run: | ||
| 138 | - rsm.remove(configuration=False, module=True, force=force_remove) | ||
| 139 | - # END handle dry-run | ||
| 143 | + rsm.remove(configuration=False, module=True, force=force_remove, dry_run=dry_run) | ||
| 140 | 144 | ||
| 141 | 145 | if i == len_rrsm - 1: | |
| 142 | 146 | op |= END | |
@@ -311,7 +315,7 @@ def update(self, previous_commit=None, recursive=True, force_remove=False, init= | |||
| 311 | 315 | for sm in sms: | |
| 312 | 316 | # update the submodule using the default method | |
| 313 | 317 | sm.update(recursive=False, init=init, to_latest_revision=to_latest_revision, | |
| 314 | - progress=progress, dry_run=dry_run) | ||
| 318 | + progress=progress, dry_run=dry_run, force=force_reset) | ||
| 315 | 319 | ||
| 316 | 320 | # update recursively depth first - question is which inconsitent | |
| 317 | 321 | # state will be better in case it fails somewhere. Defective branch | |
@@ -322,11 +326,13 @@ def update(self, previous_commit=None, recursive=True, force_remove=False, init= | |||
| 322 | 326 | if sm.module_exists(): | |
| 323 | 327 | type(self)(sm.module()).update(recursive=True, force_remove=force_remove, | |
| 324 | 328 | init=init, to_latest_revision=to_latest_revision, | |
| 325 | - progress=progress, dry_run=dry_run) | ||
| 329 | + progress=progress, dry_run=dry_run, force_reset=force_reset) | ||
| 326 | 330 | # END handle dry_run | |
| 327 | 331 | # END handle recursive | |
| 328 | 332 | # END for each submodule to update | |
| 329 | 333 | ||
| 334 | + return self | ||
| 335 | + | ||
| 330 | 336 | def module(self): | |
| 331 | 337 | """:return: the actual repository containing the submodules""" | |
| 332 | 338 | return self.repo | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -296,7 +296,7 @@ def rev_parse(repo, rev): | |||
| 296 | 296 | raise ValueError("Invalid token: %r" % token) | |
| 297 | 297 | # END end handle tag | |
| 298 | 298 | except (IndexError, AttributeError): | |
| 299 | - raise BadObject("Invalid Revision in %s" % rev) | ||
| 299 | + raise BadName("Invalid revision spec '%s' - not enough parent commits to reach '%s%i'" % (rev, token, num)) | ||
| 300 | 300 | # END exception handling | |
| 301 | 301 | # END parse loop | |
| 302 | 302 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments