diff options
author | Sebastian Thiel <byronimo@gmail.com> | 2015-01-19 16:57:11 +0100 |
---|---|---|
committer | Sebastian Thiel <byronimo@gmail.com> | 2015-01-19 16:57:11 +0100 |
commit | 20863cfe4a1b0c5bea18677470a969073570e41c (patch) | |
tree | 3e64226fb902a4558ba1765f7f4fa6d4cc8a2524 | |
parent | a223c7b7730c53c3fa1e4c019bd3daefbb8fd74b (diff) | |
download | gitpython-20863cfe4a1b0c5bea18677470a969073570e41c.tar.gz |
Implemented Submodule.rename()
A test verifies it's truly working.
Related to #238
-rw-r--r-- | doc/source/changes.rst | 2 | ||||
-rw-r--r-- | git/config.py | 22 | ||||
-rw-r--r-- | git/objects/submodule/base.py | 82 | ||||
-rw-r--r-- | git/repo/base.py | 9 | ||||
-rw-r--r-- | git/test/test_config.py | 13 | ||||
-rw-r--r-- | git/test/test_repo.py | 2 | ||||
-rw-r--r-- | git/test/test_submodule.py | 23 |
7 files changed, 147 insertions, 6 deletions
diff --git a/doc/source/changes.rst b/doc/source/changes.rst index d81f90a6..2277c4b7 100644 --- a/doc/source/changes.rst +++ b/doc/source/changes.rst @@ -6,6 +6,8 @@ Changelog ================ * Added `Repo.merge_base()` implementation. See the `respective issue on github <https://github.com/gitpython-developers/GitPython/issues/169>`_ * `[include]` sections in git configuration files are now respected +* Added `GitConfigParser.rename_section()` +* Added `Submodule.rename()` * A list of all issues can be found here: https://github.com/gitpython-developers/GitPython/issues?q=milestone%3A%22v0.3.6+-+Features%22+ 0.3.5 - Bugfixes diff --git a/git/config.py b/git/config.py index 4c4cb491..f41a86e6 100644 --- a/git/config.py +++ b/git/config.py @@ -478,8 +478,6 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje if self.read_only: raise IOError("Cannot execute non-constant method %s.%s" % (self, method_name)) - @needs_values - @set_dirty_and_flush_changes def add_section(self, section): """Assures added options will stay in order""" return super(GitConfigParser, self).add_section(section) @@ -546,3 +544,23 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje if not self.has_section(section): self.add_section(section) self.set(section, option, str(value)) + + def rename_section(self, section, new_name): + """rename the given section to new_name + :raise ValueError: if section doesn't exit + :raise ValueError: if a section with new_name does already exist + :return: this instance + """ + if not self.has_section(section): + raise ValueError("Source section '%s' doesn't exist" % section) + if self.has_section(new_name): + raise ValueError("Destination section '%s' already exists" % new_name) + + super(GitConfigParser, self).add_section(new_name) + for k, v in self.items(section): + self.set(new_name, k, str(v)) + # end for each value to copy + + # This call writes back the changes, which is why we don't have the respective decorator + self.remove_section(section) + return self diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 1d05609d..f5ff457d 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -109,7 +109,12 @@ class Submodule(util.IndexObject, Iterable, Traversable): def _set_cache_(self, attr): if attr == '_parent_commit': # set a default value, which is the root tree of the current head - self._parent_commit = self.repo.commit() + try: + self._parent_commit = self.repo.commit() + except ValueError: + # This fails in an empty repository. + self._parent_commit = None + # end exception handling elif attr in ('path', '_url', '_branch_path'): reader = self.config_reader() # default submodule values @@ -163,7 +168,13 @@ class Submodule(util.IndexObject, Iterable, Traversable): :raise IOError: If the .gitmodules file cannot be found, either locally or in the repository at the given parent commit. Otherwise the exception would be delayed until the first access of the config parser""" - parent_matches_head = repo.head.commit == parent_commit + try: + parent_matches_head = repo.head.commit == parent_commit + except ValueError: + # We are most likely in an empty repository, so the HEAD doesn't point to a valid ref + parent_matches_head = True + # end + if not repo.bare and parent_matches_head: fp_module = os.path.join(repo.working_tree_dir, cls.k_modules_file) else: @@ -370,6 +381,14 @@ class Submodule(util.IndexObject, Iterable, Traversable): mrepo = cls._clone_repo(repo, url, path, name, **kwargs) # END verify url + # It's important to add the URL to the parent config, to let `git submodule` know. + # otherwise there is a '-' character in front of the submodule listing + # a38efa84daef914e4de58d1905a500d8d14aaf45 mymodule (v0.9.0-1-ga38efa8) + # -a38efa84daef914e4de58d1905a500d8d14aaf45 submodules/intermediate/one + writer = sm.repo.config_writer() + writer.set_value(sm_section(name), 'url', url) + writer.release() + # update configuration and index index = sm.repo.index writer = sm.config_writer(index=index, write=False) @@ -386,11 +405,23 @@ class Submodule(util.IndexObject, Iterable, Traversable): del(writer) # we deliberatly assume that our head matches our index ! - pcommit = repo.head.commit - sm._parent_commit = pcommit + parent_repo_is_empty = False + try: + sm._parent_commit = repo.head.commit + except ValueError: + parent_repo_is_empty = True + # Can't set this yet, if the parent repo is empty. + # end sm.binsha = mrepo.head.commit.binsha index.add([sm], write=True) + if parent_repo_is_empty: + # The user is expected to make a commit, and this submodule will initialize itself when + # _parent_commit is required + del sm._parent_commit + log.debug("Will not set _parent_commit now as the parent repository has no commit yet.") + # end + return sm def update(self, recursive=False, init=True, to_latest_revision=False, progress=None, @@ -875,6 +906,49 @@ class Submodule(util.IndexObject, Iterable, Traversable): writer.config._auto_write = write return writer + @unbare_repo + def rename(self, new_name): + """Rename this submodule + :note: This method takes care of renaming the submodule in various places, such as + + * $parent_git_dir/config + * $working_tree_dir/.gitmodules + * (git >=v1.8.0: move submodule repository to new name) + + As .gitmodules will be changed, you would need to make a commit afterwards. The changed .gitmodules file + will already be added to the index + + :return: this submodule instance + """ + if self.name == new_name: + return self + + # .git/config + pw = self.repo.config_writer() + # As we ourselves didn't write anything about submodules into the parent .git/config, we will not require + # it to exist, and just ignore missing entries + if pw.has_section(sm_section(self.name)): + pw.rename_section(sm_section(self.name), sm_section(new_name)) + # end + pw.release() + + # .gitmodules + cw = self.config_writer().config + cw.rename_section(sm_section(self.name), sm_section(new_name)) + cw.release() + + self._name = new_name + + # .git/modules + mod = self.module() + if mod.has_separate_working_tree(): + module_abspath = self._module_abspath(self.repo, self.path, new_name) + os.renames(mod.git_dir, module_abspath) + self._write_git_file_and_module_config(mod.working_tree_dir, module_abspath) + # end move separate git repository + + return self + #} END edit interface #{ Query Interface diff --git a/git/repo/base.py b/git/repo/base.py index f3dd05b3..9ddb1ce8 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -868,6 +868,15 @@ class Repo(object): self.git.archive(treeish, *path, **kwargs) return self + def has_separate_working_tree(self): + """:return: True if our git_dir is not at the root of our working_tree_dir, but a .git file with a + platform agnositic symbolic link. Our git_dir will be whereever the .git file points to + :note: bare repositories will always return False here + """ + if self.bare: + return False + return os.path.isfile(os.path.join(self.working_tree_dir, '.git')) + rev_parse = rev_parse def __repr__(self): diff --git a/git/test/test_config.py b/git/test/test_config.py index 9a44d9e3..286d151e 100644 --- a/git/test/test_config.py +++ b/git/test/test_config.py @@ -193,3 +193,16 @@ class TestBase(TestCase): cr = GitConfigParser(fpa, read_only=True) check_test_value(cr, tv) cr.release() + + def test_rename(self): + file_obj = self._to_memcache(fixture_path('git_config')) + cw = GitConfigParser(file_obj, read_only=False, merge_includes=False) + + self.failUnlessRaises(ValueError, cw.rename_section, "doesntexist", "foo") + self.failUnlessRaises(ValueError, cw.rename_section, "core", "include") + + nn = "bee" + assert cw.rename_section('core', nn) is cw + assert not cw.has_section('core') + assert len(cw.items(nn)) == 4 + cw.release() diff --git a/git/test/test_repo.py b/git/test/test_repo.py index 226c1d26..c583fcae 100644 --- a/git/test/test_repo.py +++ b/git/test/test_repo.py @@ -164,6 +164,7 @@ class TestRepo(TestBase): r = Repo.init(path=path, bare=True) assert isinstance(r, Repo) assert r.bare is True + assert not r.has_separate_working_tree() assert os.path.isdir(r.git_dir) self._assert_empty_repo(r) @@ -200,6 +201,7 @@ class TestRepo(TestBase): os.chdir(git_dir_rela) r = Repo.init(bare=False) assert r.bare is False + assert not r.has_separate_working_tree() self._assert_empty_repo(r) finally: diff --git a/git/test/test_submodule.py b/git/test/test_submodule.py index 7cd86bd9..813b15da 100644 --- a/git/test/test_submodule.py +++ b/git/test/test_submodule.py @@ -670,8 +670,10 @@ class TestSubmodule(TestBase): assert module_repo_path.startswith(os.path.join(parent.working_tree_dir, sm_path)) if not sm._need_gitfile_submodules(parent.git): assert os.path.isdir(module_repo_path) + assert not sm.module().has_separate_working_tree() else: assert os.path.isfile(module_repo_path) + assert sm.module().has_separate_working_tree() assert find_git_dir(module_repo_path) is not None, "module pointed to by .git file must be valid" # end verify submodule 'style' @@ -689,6 +691,8 @@ class TestSubmodule(TestBase): # Fails because there are new commits, compared to the remote we cloned from self.failUnlessRaises(InvalidGitRepositoryError, sm.remove, dry_run=True) + # TODO: rename nested submodule + # remove sm_module_path = sm.module().git_dir @@ -698,3 +702,22 @@ class TestSubmodule(TestBase): assert sm.module_exists() == dry_run assert os.path.isdir(sm_module_path) == dry_run # end for each dry-run mode + + @with_rw_directory + def test_rename(self, rwdir): + parent = git.Repo.init(os.path.join(rwdir, 'parent')) + sm_name = 'mymodules/myname' + sm = parent.create_submodule(sm_name, 'submodules/intermediate/one', url=self._submodule_url()) + parent.index.commit("Added submodule") + assert sm._parent_commit is not None + + assert sm.rename(sm_name) is sm and sm.name == sm_name + + new_sm_name = "shortname" + assert sm.rename(new_sm_name) is sm + assert sm.exists() + + sm_mod = sm.module() + if os.path.isfile(os.path.join(sm_mod.working_tree_dir, '.git')) == sm._need_gitfile_submodules(parent.git): + assert sm_mod.git_dir.endswith(".git/modules/" + new_sm_name) + # end |