def test_git_work_tree_dotgit(self, rw_dir): """Check that we find .git as a worktree file and find the worktree based on it.""" git = Git(rw_dir) if git.version_info[:3] < (2, 5, 1): raise SkipTest("worktree feature unsupported") rw_master = self.rorepo.clone(join_path_native(rw_dir, 'master_repo')) branch = rw_master.create_head('aaaaaaaa') worktree_path = join_path_native(rw_dir, 'worktree_repo') if Git.is_cygwin(): worktree_path = cygpath(worktree_path) rw_master.git.worktree('add', worktree_path, branch.name) # this ensures that we can read the repo's gitdir correctly repo = Repo(worktree_path) self.assertIsInstance(repo, Repo) # this ensures we're able to actually read the refs in the tree, which # means we can read commondir correctly. commit = repo.head.commit self.assertIsInstance(commit, Object) # this ensures we can read the remotes, which confirms we're reading # the config correctly. origin = repo.remotes.origin self.assertIsInstance(origin, Remote) self.assertIsInstance(repo.heads['aaaaaaaa'], Head)
def test_untracked_files(self): base = self.rorepo.working_tree_dir files = (join_path_native(base, "__test_myfile"), join_path_native(base, "__test_other_file")) num_recently_untracked = 0 try: for fpath in files: fd = open(fpath, "wb") fd.close() # END for each filename untracked_files = self.rorepo.untracked_files num_recently_untracked = len(untracked_files) # assure we have all names - they are relative to the git-dir num_test_untracked = 0 for utfile in untracked_files: num_test_untracked += join_path_native(base, utfile) in files assert len(files) == num_test_untracked finally: for fpath in files: if os.path.isfile(fpath): os.remove(fpath) # END handle files assert len(self.rorepo.untracked_files) == (num_recently_untracked - len(files))
def test_git_work_tree_env(self, rw_dir): """Check that we yield to GIT_WORK_TREE""" # clone a repo # move .git directory to a subdirectory # set GIT_DIR and GIT_WORK_TREE appropriately # check that repo.working_tree_dir == rw_dir self.rorepo.clone(join_path_native(rw_dir, 'master_repo')) repo_dir = join_path_native(rw_dir, 'master_repo') old_git_dir = join_path_native(repo_dir, '.git') new_subdir = join_path_native(repo_dir, 'gitdir') new_git_dir = join_path_native(new_subdir, 'git') os.mkdir(new_subdir) os.rename(old_git_dir, new_git_dir) oldenv = os.environ.copy() os.environ['GIT_DIR'] = new_git_dir os.environ['GIT_WORK_TREE'] = repo_dir try: r = Repo() self.assertEqual(r.working_tree_dir, repo_dir) self.assertEqual(r.working_dir, repo_dir) finally: os.environ = oldenv
def test_untracked_files(self, rwrepo): for run, (repo_add, is_invoking_git) in enumerate(( (rwrepo.index.add, False), (rwrepo.git.add, True), )): base = rwrepo.working_tree_dir files = (join_path_native(base, u"%i_test _myfile" % run), join_path_native(base, "%i_test_other_file" % run), join_path_native(base, u"%i__çava verböten" % run), join_path_native(base, u"%i_çava-----verböten" % run)) num_recently_untracked = 0 for fpath in files: with open(fpath, "wb"): pass untracked_files = rwrepo.untracked_files num_recently_untracked = len(untracked_files) # assure we have all names - they are relative to the git-dir num_test_untracked = 0 for utfile in untracked_files: num_test_untracked += join_path_native(base, utfile) in files self.assertEqual(len(files), num_test_untracked) if is_win and not PY3 and is_invoking_git: ## On Windows, shell needed when passing unicode cmd-args. # repo_add = fnt.partial(repo_add, shell=True) untracked_files = [win_encode(f) for f in untracked_files] repo_add(untracked_files) self.assertEqual(len(rwrepo.untracked_files), (num_recently_untracked - len(files)))
def test_untracked_files(self, rwrepo): for (run, repo_add) in enumerate((rwrepo.index.add, rwrepo.git.add)): base = rwrepo.working_tree_dir files = (join_path_native(base, u"%i_test _myfile" % run), join_path_native(base, "%i_test_other_file" % run), join_path_native(base, u"%i__çava verböten" % run), join_path_native(base, u"%i_çava-----verböten" % run)) num_recently_untracked = 0 for fpath in files: fd = open(fpath, "wb") fd.close() # END for each filename untracked_files = rwrepo.untracked_files num_recently_untracked = len(untracked_files) # assure we have all names - they are relative to the git-dir num_test_untracked = 0 for utfile in untracked_files: num_test_untracked += join_path_native(base, utfile) in files assert len(files) == num_test_untracked repo_add(untracked_files) assert len(rwrepo.untracked_files) == (num_recently_untracked - len(files))
def test_untracked_files(self, rwrepo): for (run, repo_add) in enumerate((rwrepo.index.add, rwrepo.git.add)): base = rwrepo.working_tree_dir files = ( join_path_native(base, u"%i_test _myfile" % run), join_path_native(base, "%i_test_other_file" % run), join_path_native(base, u"%i__çava verböten" % run), join_path_native(base, u"%i_çava-----verböten" % run), ) num_recently_untracked = 0 for fpath in files: fd = open(fpath, "wb") fd.close() # END for each filename untracked_files = rwrepo.untracked_files num_recently_untracked = len(untracked_files) # assure we have all names - they are relative to the git-dir num_test_untracked = 0 for utfile in untracked_files: num_test_untracked += join_path_native(base, utfile) in files assert len(files) == num_test_untracked repo_add(untracked_files) assert len(rwrepo.untracked_files) == (num_recently_untracked - len(files))
def test_untracked_files(self): base = self.rorepo.working_tree_dir files = ( join_path_native(base, "__test_myfile"), join_path_native(base, "__test_other_file") ) num_recently_untracked = 0 try: for fpath in files: fd = open(fpath,"wb") fd.close() # END for each filename untracked_files = self.rorepo.untracked_files num_recently_untracked = len(untracked_files) # assure we have all names - they are relative to the git-dir num_test_untracked = 0 for utfile in untracked_files: num_test_untracked += join_path_native(base, utfile) in files assert len(files) == num_test_untracked finally: for fpath in files: if os.path.isfile(fpath): os.remove(fpath) # END handle files assert len(self.rorepo.untracked_files) == (num_recently_untracked - len(files))
def test_work_tree_unsupported(self, rw_dir): git = Git(rw_dir) if git.version_info[:3] < (2, 5, 1): raise SkipTest("worktree feature unsupported") rw_master = self.rorepo.clone(join_path_native(rw_dir, 'master_repo')) rw_master.git.checkout('HEAD~10') worktree_path = join_path_native(rw_dir, 'worktree_repo') rw_master.git.worktree('add', worktree_path, 'master') self.failUnlessRaises(InvalidGitRepositoryError, Repo, worktree_path)
def test_git_file(self, rwrepo): # Move the .git directory to another location and create the .git file. real_path_abs = os.path.abspath(join_path_native(rwrepo.working_tree_dir, '.real')) os.rename(rwrepo.git_dir, real_path_abs) git_file_path = join_path_native(rwrepo.working_tree_dir, '.git') open(git_file_path, 'wb').write(fixture('git_file')) # Create a repo and make sure it's pointing to the relocated .git directory. git_file_repo = Repo(rwrepo.working_tree_dir) assert os.path.abspath(git_file_repo.git_dir) == real_path_abs # Test using an absolute gitdir path in the .git file. open(git_file_path, 'wb').write(('gitdir: %s\n' % real_path_abs).encode('ascii')) git_file_repo = Repo(rwrepo.working_tree_dir) assert os.path.abspath(git_file_repo.git_dir) == real_path_abs
def _iter_items(cls, repo, common_path=None): if common_path is None: common_path = cls._common_path_default rela_paths = set() # walk loose refs # Currently we do not follow links for root, dirs, files in os.walk(join_path_native(repo.common_dir, common_path)): if 'refs' not in root.split(os.sep): # skip non-refs subfolders refs_id = [d for d in dirs if d == 'refs'] if refs_id: dirs[0:] = ['refs'] # END prune non-refs folders for f in files: if f == 'packed-refs': continue abs_path = to_native_path_linux(join_path(root, f)) rela_paths.add(abs_path.replace(to_native_path_linux(repo.common_dir) + '/', "")) # END for each file in root directory # END for each directory to walk # read packed refs for _sha, rela_path in cls._iter_packed_refs(repo): if rela_path.startswith(common_path): rela_paths.add(rela_path) # END relative path matches common path # END packed refs reading # return paths in sorted order for path in sorted(rela_paths): try: yield cls.from_path(repo, path) except ValueError: continue
def test_rename(self, rwdir): parent = git.Repo.init(osp.join(rwdir, 'parent')) sm_name = 'mymodules/myname' sm = parent.create_submodule(sm_name, sm_name, url=self._small_repo_url()) parent.index.commit("Added submodule") assert sm.rename(sm_name) is sm and sm.name == sm_name assert not sm.repo.is_dirty( index=True, working_tree=False, untracked_files=False) new_path = 'renamed/myname' assert sm.move(new_path).name == new_path new_sm_name = "shortname" assert sm.rename(new_sm_name) is sm assert sm.repo.is_dirty(index=True, working_tree=False, untracked_files=False) assert sm.exists() sm_mod = sm.module() if osp.isfile(osp.join(sm_mod.working_tree_dir, '.git')) == sm._need_gitfile_submodules( parent.git): assert sm_mod.git_dir.endswith( join_path_native('.git', 'modules', new_sm_name))
def _iter_items(cls, repo, common_path=None): if common_path is None: common_path = cls._common_path_default rela_paths = set() # walk loose refs # Currently we do not follow links for root, dirs, files in os.walk(join_path_native(repo.git_dir, common_path)): if 'refs/' not in root: # skip non-refs subfolders refs_id = [d for d in dirs if d == 'refs'] if refs_id: dirs[0:] = ['refs'] # END prune non-refs folders for f in files: abs_path = to_native_path_linux(join_path(root, f)) rela_paths.add(abs_path.replace(to_native_path_linux(repo.git_dir) + '/', "")) # END for each file in root directory # END for each directory to walk # read packed refs for sha, rela_path in cls._iter_packed_refs(repo): if rela_path.startswith(common_path): rela_paths.add(rela_path) # END relative path matches common path # END packed refs reading # return paths in sorted order for path in sorted(rela_paths): try: yield cls.from_path(repo, path) except ValueError: continue
def test_work_tree_unsupported(self, rw_dir): git = Git(rw_dir) if git.version_info[:3] < (2, 5, 1): raise SkipTest("worktree feature unsupported") rw_master = self.rorepo.clone(join_path_native(rw_dir, 'master_repo')) rw_master.git.checkout('HEAD~10') worktree_path = join_path_native(rw_dir, 'worktree_repo') if Git.is_cygwin(): worktree_path = cygpath(worktree_path) try: rw_master.git.worktree('add', worktree_path, 'master') except Exception as ex: raise AssertionError(ex, "It's ok if TC not running from `master`.") self.failUnlessRaises(InvalidGitRepositoryError, Repo, worktree_path)
def abspath(self): """ :return: Absolute path to this index object in the file system ( as opposed to the .path field which is a path relative to the git repository ). The returned path will be native to the system and contains '\' on windows. """ return join_path_native(self.repo.working_tree_dir, self.path)
def test_submodule_update(self, rwrepo): # fails in bare mode rwrepo._bare = True self.failUnlessRaises(InvalidGitRepositoryError, rwrepo.submodule_update) rwrepo._bare = False # test create submodule sm = rwrepo.submodules[0] sm = rwrepo.create_submodule("my_new_sub", "some_path", join_path_native(self.rorepo.working_tree_dir, sm.path)) assert isinstance(sm, Submodule)
def test_submodule_update(self, rwrepo): # fails in bare mode rwrepo._bare = True self.failUnlessRaises(InvalidGitRepositoryError, rwrepo.submodule_update) rwrepo._bare = False # test create submodule sm = rwrepo.submodules[0] sm = rwrepo.create_submodule("my_new_sub", "some_path", join_path_native(self.rorepo.working_tree_dir, sm.path)) self.assertIsInstance(sm, Submodule)
def abspath(self) -> PathLike: """ :return: Absolute path to this index object in the file system ( as opposed to the .path field which is a path relative to the git repository ). The returned path will be native to the system and contains '\' on windows. """ if self.repo.working_tree_dir is not None: return join_path_native(self.repo.working_tree_dir, self.path) else: raise WorkTreeRepositoryUnsupported("Working_tree_dir was None or empty")
def test_linked_worktree_traversal(self, rw_dir): """Check that we can identify a linked worktree based on a .git file""" git = Git(rw_dir) if git.version_info[:3] < (2, 5, 1): raise SkipTest("worktree feature unsupported") rw_master = self.rorepo.clone(join_path_native(rw_dir, 'master_repo')) branch = rw_master.create_head('aaaaaaaa') worktree_path = join_path_native(rw_dir, 'worktree_repo') if Git.is_cygwin(): worktree_path = cygpath(worktree_path) rw_master.git.worktree('add', worktree_path, branch.name) dotgit = osp.join(worktree_path, ".git") statbuf = stat(dotgit) self.assertTrue(statbuf.st_mode & S_IFREG) gitdir = find_worktree_git_dir(dotgit) self.assertIsNotNone(gitdir) statbuf = stat(gitdir) self.assertTrue(statbuf.st_mode & S_IFDIR)
def test_untracked_files(self, rwrepo): for run, repo_add in enumerate((rwrepo.index.add, rwrepo.git.add)): base = rwrepo.working_tree_dir files = (join_path_native(base, u"%i_test _myfile" % run), join_path_native(base, "%i_test_other_file" % run), join_path_native(base, u"%i__çava verböten" % run), join_path_native(base, u"%i_çava-----verböten" % run)) num_recently_untracked = 0 for fpath in files: with open(fpath, "wb"): pass untracked_files = rwrepo.untracked_files num_recently_untracked = len(untracked_files) # assure we have all names - they are relative to the git-dir num_test_untracked = 0 for utfile in untracked_files: num_test_untracked += join_path_native(base, utfile) in files self.assertEqual(len(files), num_test_untracked) repo_add(untracked_files) self.assertEqual(len(rwrepo.untracked_files), (num_recently_untracked - len(files)))
def test_submodule_update(self, rwrepo): # fails in bare mode rwrepo._bare = True # special handling: there are repo implementations which have a bare attribute. IN that case, set it directly if not rwrepo.bare: rwrepo.bare = True self.failUnlessRaises(InvalidGitRepositoryError, rwrepo.submodule_update) rwrepo._bare = False if rwrepo.bare: rwrepo.bare = False #END special repo handling # test create submodule sm = rwrepo.submodules[0] sm = rwrepo.create_submodule("my_new_sub", "some_path", join_path_native(self.rorepo.working_tree_dir, sm.path)) assert isinstance(sm, Submodule)
def test_submodule_update(self, rwrepo): # fails in bare mode rwrepo._bare = True # special handling: there are repo implementations which have a bare attribute. IN that case, set it directly if not rwrepo.bare: rwrepo.bare = True self.failUnlessRaises(InvalidGitRepositoryError, rwrepo.submodule_update) rwrepo._bare = False if rwrepo.bare: rwrepo.bare = False # END special repo handling # test create submodule sm = rwrepo.submodules[0] sm = rwrepo.create_submodule("my_new_sub", "some_path", join_path_native(self.rorepo.working_tree_dir, sm.path)) assert isinstance(sm, Submodule)
def abspath(self): """ :return: Absolute path to this index object in the file system ( as opposed to the .path field which is a path relative to the git repository ). The returned path will be native to the system and contains '\' on windows. :raise UnsupportedOperation: if underlying odb does not support the required method to obtain a working dir""" # TODO: Here we suddenly need something better than a plain object database # which indicates our odb should better be named repo ! root = '' if isinstance(self.odb, RepositoryPathsMixin): root = self.odb.working_tree_dir else: raise UnsupportedOperation("Cannot provide absolute path from a database without Repository path support") #END handle odb type return join_path_native(root, self.path)
def abspath(self): """ :return: Absolute path to this index object in the file system ( as opposed to the .path field which is a path relative to the git repository ). The returned path will be native to the system and contains '\' on windows. :raise UnsupportedOperation: if underlying odb does not support the required method to obtain a working dir""" # TODO: Here we suddenly need something better than a plain object database # which indicates our odb should better be named repo ! root = '' if isinstance(self.odb, RepositoryPathsMixin): root = self.odb.working_tree_dir else: raise UnsupportedOperation( "Cannot provide absolute path from a database without Repository path support" ) # END handle odb type return join_path_native(root, self.path)
def test_rename(self, rwdir): parent = git.Repo.init(osp.join(rwdir, 'parent')) sm_name = 'mymodules/myname' sm = parent.create_submodule(sm_name, sm_name, url=self._small_repo_url()) parent.index.commit("Added submodule") assert sm.rename(sm_name) is sm and sm.name == sm_name assert not sm.repo.is_dirty(index=True, working_tree=False, untracked_files=False) new_path = 'renamed/myname' assert sm.move(new_path).name == new_path new_sm_name = "shortname" assert sm.rename(new_sm_name) is sm assert sm.repo.is_dirty(index=True, working_tree=False, untracked_files=False) assert sm.exists() sm_mod = sm.module() if osp.isfile(osp.join(sm_mod.working_tree_dir, '.git')) == sm._need_gitfile_submodules(parent.git): assert sm_mod.git_dir.endswith(join_path_native('.git', 'modules', new_sm_name))
def _generate_async_local_path(self): return to_native_path_linux(join_path_native(self.rorepo.working_tree_dir, 'git/ext/async'))
def _index_path(self): return join_path_native(self.repo.git_dir, "index")
def from_tree(cls, repo, *treeish, **kwargs): """Merge the given treeish revisions into a new index which is returned. The original index will remain unaltered :param repo: The repository treeish are located in. :param treeish: One, two or three Tree Objects, Commits or 40 byte hexshas. The result changes according to the amount of trees. If 1 Tree is given, it will just be read into a new index If 2 Trees are given, they will be merged into a new index using a two way merge algorithm. Tree 1 is the 'current' tree, tree 2 is the 'other' one. It behaves like a fast-forward. If 3 Trees are given, a 3-way merge will be performed with the first tree being the common ancestor of tree 2 and tree 3. Tree 2 is the 'current' tree, tree 3 is the 'other' one :param kwargs: Additional arguments passed to git-read-tree :return: New IndexFile instance. It will point to a temporary index location which does not exist anymore. If you intend to write such a merged Index, supply an alternate file_path to its 'write' method. :note: In the three-way merge case, --aggressive will be specified to automatically resolve more cases in a commonly correct manner. Specify trivial=True as kwarg to override that. As the underlying git-read-tree command takes into account the current index, it will be temporarily moved out of the way to assure there are no unsuspected interferences.""" if len(treeish) == 0 or len(treeish) > 3: raise ValueError("Please specify between 1 and 3 treeish, got %i" % len(treeish)) arg_list = list() # ignore that working tree and index possibly are out of date if len(treeish) > 1: # drop unmerged entries when reading our index and merging arg_list.append("--reset") # handle non-trivial cases the way a real merge does arg_list.append("--aggressive") # END merge handling # tmp file created in git home directory to be sure renaming # works - /tmp/ dirs could be on another device tmp_index = tempfile.mktemp('', '', repo.git_dir) arg_list.append("--index-output=%s" % tmp_index) arg_list.extend(treeish) # move current index out of the way - otherwise the merge may fail # as it considers existing entries. moving it essentially clears the index. # Unfortunately there is no 'soft' way to do it. # The TemporaryFileSwap assure the original file get put back index_handler = TemporaryFileSwap( join_path_native(repo.git_dir, 'index')) try: repo.git.read_tree(*arg_list, **kwargs) index = cls(repo, tmp_index) index.entries # force it to read the file as we will delete the temp-file del (index_handler) # release as soon as possible finally: if os.path.exists(tmp_index): os.remove(tmp_index) # END index merge handling return index
def _generate_async_local_path(self): return to_native_path_linux(join_path_native(self.rorepo.working_tree_dir, "git/ext/async"))
def abspath(self): return join_path_native(self.repo.git_dir, self.path)
def move(self, module_path, configuration=True, module=True): """Move the submodule to a another module path. This involves physically moving the repository at our current path, changing the configuration, as well as adjusting our index entry accordingly. :param module_path: the path to which to move our module, given as repository-relative path. Intermediate directories will be created accordingly. If the path already exists, it must be empty. Trailling (back)slashes are removed automatically :param configuration: if True, the configuration will be adjusted to let the submodule point to the given path. :param module: if True, the repository managed by this submodule will be moved, not the configuration. This will effectively leave your repository in an inconsistent state unless the configuration and index already point to the target location. :return: self :raise ValueError: if the module path existed and was not empty, or was a file :note: Currently the method is not atomic, and it could leave the repository in an inconsistent state if a sub-step fails for some reason """ if module + configuration < 1: raise ValueError( "You must specify to move at least the module or the configuration of the submodule" ) #END handle input module_path = to_native_path_linux(module_path) if module_path.endswith('/'): module_path = module_path[:-1] # END handle trailing slash # VERIFY DESTINATION if module_path == self.path: return self #END handle no change dest_path = join_path_native(self.repo.working_tree_dir, module_path) if os.path.isfile(dest_path): raise ValueError("Cannot move repository onto a file: %s" % dest_path) # END handle target files index = self.repo.index tekey = index.entry_key(module_path, 0) # if the target item already exists, fail if configuration and tekey in index.entries: raise ValueError("Index entry for target path did alredy exist") #END handle index key already there # remove existing destination if module: if os.path.exists(dest_path): if len(os.listdir(dest_path)): raise ValueError( "Destination module directory was not empty") #END handle non-emptyness if os.path.islink(dest_path): os.remove(dest_path) else: os.rmdir(dest_path) #END handle link else: # recreate parent directories # NOTE: renames() does that now pass #END handle existance # END handle module # move the module into place if possible cur_path = self.abspath renamed_module = False if module and os.path.exists(cur_path): os.renames(cur_path, dest_path) renamed_module = True #END move physical module # rename the index entry - have to manipulate the index directly as # git-mv cannot be used on submodules ... yeah try: if configuration: try: ekey = index.entry_key(self.path, 0) entry = index.entries[ekey] del (index.entries[ekey]) nentry = git.IndexEntry(entry[:3] + (module_path, ) + entry[4:]) index.entries[tekey] = nentry except KeyError: raise InvalidGitRepositoryError( "Submodule's entry at %r did not exist" % (self.path)) #END handle submodule doesn't exist # update configuration writer = self.config_writer(index=index) # auto-write writer.set_value('path', module_path) self.path = module_path del (writer) # END handle configuration flag except Exception: if renamed_module: os.renames(dest_path, cur_path) # END undo module renaming raise #END handle undo rename return self
def abspath(self): return join_path_native(_git_dir(self.repo, self.path), self.path)
def test_root_module(self, rwrepo): # Can query everything without problems rm = RootModule(self.rorepo) assert rm.module() is self.rorepo # try attributes rm.binsha rm.mode rm.path assert rm.name == rm.k_root_name assert rm.parent_commit == self.rorepo.head.commit rm.url rm.branch assert len(rm.list_items(rm.module())) == 1 rm.config_reader() rm.config_writer() # deep traversal gitdb / async rsmsp = [sm.path for sm in rm.traverse()] assert len(rsmsp) == 2 # gitdb and async, async being a child of gitdb # cannot set the parent commit as root module's path didn't exist self.failUnlessRaises(ValueError, rm.set_parent_commit, 'HEAD') # TEST UPDATE ############# # setup commit which remove existing, add new and modify existing submodules rm = RootModule(rwrepo) assert len(rm.children()) == 1 # modify path without modifying the index entry # ( which is what the move method would do properly ) #================================================== sm = rm.children()[0] pp = "path/prefix" fp = join_path_native(pp, sm.path) prep = sm.path assert not sm.module_exists() # was never updated after rwrepo's clone # assure we clone from a local source sm.config_writer().set_value( 'url', to_native_path_linux( join_path_native(self.rorepo.working_tree_dir, sm.path))) # dry-run does nothing sm.update(recursive=False, dry_run=True, progress=prog) assert not sm.module_exists() sm.update(recursive=False) assert sm.module_exists() sm.config_writer().set_value( 'path', fp) # change path to something with prefix AFTER url change # update fails as list_items in such a situations cannot work, as it cannot # find the entry at the changed path self.failUnlessRaises(InvalidGitRepositoryError, rm.update, recursive=False) # move it properly - doesn't work as it its path currently points to an indexentry # which doesn't exist ( move it to some path, it doesn't matter here ) self.failUnlessRaises(InvalidGitRepositoryError, sm.move, pp) # reset the path(cache) to where it was, now it works sm.path = prep sm.move(fp, module=False) # leave it at the old location assert not sm.module_exists() cpathchange = rwrepo.index.commit( "changed sm path") # finally we can commit # update puts the module into place rm.update(recursive=False, progress=prog) sm.set_parent_commit(cpathchange) assert sm.module_exists() # add submodule #================ nsmn = "newsubmodule" nsmp = "submrepo" async_url = to_native_path_linux( join_path_native(self.rorepo.working_tree_dir, rsmsp[0], rsmsp[1])) nsm = Submodule.add(rwrepo, nsmn, nsmp, url=async_url) csmadded = rwrepo.index.commit( "Added submodule" ).hexsha # make sure we don't keep the repo reference nsm.set_parent_commit(csmadded) assert nsm.module_exists() # in our case, the module should not exist, which happens if we update a parent # repo and a new submodule comes into life nsm.remove(configuration=False, module=True) assert not nsm.module_exists() and nsm.exists() # dry-run does nothing rm.update(recursive=False, dry_run=True, progress=prog) # otherwise it will work rm.update(recursive=False, progress=prog) assert nsm.module_exists() # remove submodule - the previous one #==================================== sm.set_parent_commit(csmadded) smp = sm.abspath assert not sm.remove(module=False).exists() assert os.path.isdir(smp) # module still exists csmremoved = rwrepo.index.commit("Removed submodule") # an update will remove the module # not in dry_run rm.update(recursive=False, dry_run=True) assert os.path.isdir(smp) rm.update(recursive=False) assert not os.path.isdir(smp) # change url #============= # to the first repository, this way we have a fast checkout, and a completely different # repository at the different url nsm.set_parent_commit(csmremoved) nsmurl = to_native_path_linux( join_path_native(self.rorepo.working_tree_dir, rsmsp[0])) nsm.config_writer().set_value('url', nsmurl) csmpathchange = rwrepo.index.commit("changed url") nsm.set_parent_commit(csmpathchange) prev_commit = nsm.module().head.commit # dry-run does nothing rm.update(recursive=False, dry_run=True, progress=prog) assert nsm.module().remotes.origin.url != nsmurl rm.update(recursive=False, progress=prog) assert nsm.module().remotes.origin.url == nsmurl # head changed, as the remote url and its commit changed assert prev_commit != nsm.module().head.commit # add the submodule's changed commit to the index, which is what the # user would do # beforehand, update our instance's binsha with the new one nsm.binsha = nsm.module().head.commit.binsha rwrepo.index.add([nsm]) # change branch #================= # we only have one branch, so we switch to a virtual one, and back # to the current one to trigger the difference cur_branch = nsm.branch nsmm = nsm.module() prev_commit = nsmm.head.commit for branch in ("some_virtual_branch", cur_branch.name): nsm.config_writer().set_value(Submodule.k_head_option, git.Head.to_full_path(branch)) csmbranchchange = rwrepo.index.commit("changed branch to %s" % branch) nsm.set_parent_commit(csmbranchchange) # END for each branch to change # Lets remove our tracking branch to simulate some changes nsmmh = nsmm.head assert nsmmh.ref.tracking_branch() is None # never set it up until now assert not nsmmh.is_detached #dry run does nothing rm.update(recursive=False, dry_run=True, progress=prog) assert nsmmh.ref.tracking_branch() is None # the real thing does rm.update(recursive=False, progress=prog) assert nsmmh.ref.tracking_branch() is not None assert not nsmmh.is_detached # recursive update # ================= # finally we recursively update a module, just to run the code at least once # remove the module so that it has more work assert len(nsm.children()) == 1 assert nsm.exists() and nsm.module_exists() and len( nsm.children()) == 1 # assure we pull locally only nsmc = nsm.children()[0] nsmc.config_writer().set_value('url', async_url) rm.update(recursive=True, progress=prog, dry_run=True) # just to run the code rm.update(recursive=True, progress=prog) assert len(nsm.children()) == 1 and nsmc.module_exists()
def _do_base_tests(self, rwrepo): """Perform all tests in the given repository, it may be bare or nonbare""" # manual instantiation smm = Submodule(rwrepo, "\0"*20) # name needs to be set in advance self.failUnlessRaises(AttributeError, getattr, smm, 'name') # iterate - 1 submodule sms = Submodule.list_items(rwrepo, self.k_subm_current) assert len(sms) == 1 sm = sms[0] # at a different time, there is None assert len(Submodule.list_items(rwrepo, self.k_no_subm_tag)) == 0 assert sm.path == 'git/ext/gitdb' assert sm.path != sm.name # in our case, we have ids there, which don't equal the path assert sm.url == 'git://github.com/gitpython-developers/gitdb.git' assert sm.branch_path == 'refs/heads/master' # the default ... assert sm.branch_name == 'master' assert sm.parent_commit == rwrepo.head.commit # size is always 0 assert sm.size == 0 # the module is not checked-out yet self.failUnlessRaises(InvalidGitRepositoryError, sm.module) # which is why we can't get the branch either - it points into the module() repository self.failUnlessRaises(InvalidGitRepositoryError, getattr, sm, 'branch') # branch_path works, as its just a string assert isinstance(sm.branch_path, basestring) # some commits earlier we still have a submodule, but its at a different commit smold = Submodule.iter_items(rwrepo, self.k_subm_changed).next() assert smold.binsha != sm.binsha assert smold != sm # the name changed # force it to reread its information del(smold._url) smold.url == sm.url # test config_reader/writer methods sm.config_reader() new_smclone_path = None # keep custom paths for later new_csmclone_path = None # if rwrepo.bare: self.failUnlessRaises(InvalidGitRepositoryError, sm.config_writer) else: writer = sm.config_writer() # for faster checkout, set the url to the local path new_smclone_path = to_native_path_linux(join_path_native(self.rorepo.working_tree_dir, sm.path)) writer.set_value('url', new_smclone_path) del(writer) assert sm.config_reader().get_value('url') == new_smclone_path assert sm.url == new_smclone_path # END handle bare repo smold.config_reader() # cannot get a writer on historical submodules if not rwrepo.bare: self.failUnlessRaises(ValueError, smold.config_writer) # END handle bare repo # make the old into a new - this doesn't work as the name changed prev_parent_commit = smold.parent_commit self.failUnlessRaises(ValueError, smold.set_parent_commit, self.k_subm_current) # the sha is properly updated smold.set_parent_commit(self.k_subm_changed+"~1") assert smold.binsha != sm.binsha # raises if the sm didn't exist in new parent - it keeps its # parent_commit unchanged self.failUnlessRaises(ValueError, smold.set_parent_commit, self.k_no_subm_tag) # TEST TODO: if a path in the gitmodules file, but not in the index, it raises # TEST UPDATE ############## # module retrieval is not always possible if rwrepo.bare: self.failUnlessRaises(InvalidGitRepositoryError, sm.module) self.failUnlessRaises(InvalidGitRepositoryError, sm.remove) self.failUnlessRaises(InvalidGitRepositoryError, sm.add, rwrepo, 'here', 'there') else: # its not checked out in our case self.failUnlessRaises(InvalidGitRepositoryError, sm.module) assert not sm.module_exists() # currently there is only one submodule assert len(list(rwrepo.iter_submodules())) == 1 assert sm.binsha != "\0"*20 # TEST ADD ########### # preliminary tests # adding existing returns exactly the existing sma = Submodule.add(rwrepo, sm.name, sm.path) assert sma.path == sm.path # no url and no module at path fails self.failUnlessRaises(ValueError, Submodule.add, rwrepo, "newsubm", "pathtorepo", url=None) # CONTINUE UPDATE ################# # lets update it - its a recursive one too newdir = os.path.join(sm.abspath, 'dir') os.makedirs(newdir) # update fails if the path already exists non-empty self.failUnlessRaises(OSError, sm.update) os.rmdir(newdir) # dry-run does nothing sm.update(dry_run=True, progress=prog) assert not sm.module_exists() assert sm.update() is sm sm_repopath = sm.path # cache for later assert sm.module_exists() assert isinstance(sm.module(), git.Repo) assert sm.module().working_tree_dir == sm.abspath # INTERLEAVE ADD TEST ##################### # url must match the one in the existing repository ( if submodule name suggests a new one ) # or we raise self.failUnlessRaises(ValueError, Submodule.add, rwrepo, "newsubm", sm.path, "git://someurl/repo.git") # CONTINUE UPDATE ################# # we should have setup a tracking branch, which is also active assert sm.module().head.ref.tracking_branch() is not None # delete the whole directory and re-initialize shutil.rmtree(sm.abspath) assert len(sm.children()) == 0 # dry-run does nothing sm.update(dry_run=True, recursive=False, progress=prog) assert len(sm.children()) == 0 sm.update(recursive=False) assert len(list(rwrepo.iter_submodules())) == 2 assert len(sm.children()) == 1 # its not checked out yet csm = sm.children()[0] assert not csm.module_exists() csm_repopath = csm.path # adjust the path of the submodules module to point to the local destination new_csmclone_path = to_native_path_linux(join_path_native(self.rorepo.working_tree_dir, sm.path, csm.path)) csm.config_writer().set_value('url', new_csmclone_path) assert csm.url == new_csmclone_path # dry-run does nothing assert not csm.module_exists() sm.update(recursive=True, dry_run=True, progress=prog) assert not csm.module_exists() # update recursively again sm.update(recursive=True) assert csm.module_exists() # tracking branch once again csm.module().head.ref.tracking_branch() is not None # this flushed in a sub-submodule assert len(list(rwrepo.iter_submodules())) == 2 # reset both heads to the previous version, verify that to_latest_revision works smods = (sm.module(), csm.module()) for repo in smods: repo.head.reset('HEAD~2', working_tree=1) # END for each repo to reset # dry run does nothing sm.update(recursive=True, dry_run=True, progress=prog) for repo in smods: assert repo.head.commit != repo.head.ref.tracking_branch().commit # END for each repo to check sm.update(recursive=True, to_latest_revision=True) for repo in smods: assert repo.head.commit == repo.head.ref.tracking_branch().commit # END for each repo to check del(smods) # if the head is detached, it still works ( but warns ) smref = sm.module().head.ref sm.module().head.ref = 'HEAD~1' # if there is no tracking branch, we get a warning as well csm_tracking_branch = csm.module().head.ref.tracking_branch() csm.module().head.ref.set_tracking_branch(None) sm.update(recursive=True, to_latest_revision=True) # to_latest_revision changes the child submodule's commit, it needs an # update now csm.set_parent_commit(csm.repo.head.commit) # undo the changes sm.module().head.ref = smref csm.module().head.ref.set_tracking_branch(csm_tracking_branch) # REMOVAL OF REPOSITOTRY ######################## # must delete something self.failUnlessRaises(ValueError, csm.remove, module=False, configuration=False) # We have modified the configuration, hence the index is dirty, and the # deletion will fail # NOTE: As we did a few updates in the meanwhile, the indices were reset # Hence we create some changes csm.set_parent_commit(csm.repo.head.commit) sm.config_writer().set_value("somekey", "somevalue") csm.config_writer().set_value("okey", "ovalue") self.failUnlessRaises(InvalidGitRepositoryError, sm.remove) # if we remove the dirty index, it would work sm.module().index.reset() # still, we have the file modified self.failUnlessRaises(InvalidGitRepositoryError, sm.remove, dry_run=True) sm.module().index.reset(working_tree=True) # enforce the submodule to be checked out at the right spot as well. csm.update() # this would work assert sm.remove(dry_run=True) is sm assert sm.module_exists() sm.remove(force=True, dry_run=True) assert sm.module_exists() # but ... we have untracked files in the child submodule fn = join_path_native(csm.module().working_tree_dir, "newfile") open(fn, 'w').write("hi") self.failUnlessRaises(InvalidGitRepositoryError, sm.remove) # forcibly delete the child repository prev_count = len(sm.children()) assert csm.remove(force=True) is csm assert not csm.exists() assert not csm.module_exists() assert len(sm.children()) == prev_count - 1 # now we have a changed index, as configuration was altered. # fix this sm.module().index.reset(working_tree=True) # now delete only the module of the main submodule assert sm.module_exists() sm.remove(configuration=False) assert sm.exists() assert not sm.module_exists() assert sm.config_reader().get_value('url') # delete the rest sm.remove() assert not sm.exists() assert not sm.module_exists() assert len(rwrepo.submodules) == 0 # ADD NEW SUBMODULE ################### # add a simple remote repo - trailing slashes are no problem smid = "newsub" osmid = "othersub" nsm = Submodule.add(rwrepo, smid, sm_repopath, new_smclone_path+"/", None, no_checkout=True) assert nsm.name == smid assert nsm.module_exists() assert nsm.exists() # its not checked out assert not os.path.isfile(join_path_native(nsm.module().working_tree_dir, Submodule.k_modules_file)) assert len(rwrepo.submodules) == 1 # add another submodule, but into the root, not as submodule osm = Submodule.add(rwrepo, osmid, csm_repopath, new_csmclone_path, Submodule.k_head_default) assert osm != nsm assert osm.module_exists() assert osm.exists() assert os.path.isfile(join_path_native(osm.module().working_tree_dir, 'setup.py')) assert len(rwrepo.submodules) == 2 # commit the changes, just to finalize the operation rwrepo.index.commit("my submod commit") assert len(rwrepo.submodules) == 2 # needs update as the head changed, it thinks its in the history # of the repo otherwise nsm.set_parent_commit(rwrepo.head.commit) osm.set_parent_commit(rwrepo.head.commit) # MOVE MODULE ############# # invalid inptu self.failUnlessRaises(ValueError, nsm.move, 'doesntmatter', module=False, configuration=False) # renaming to the same path does nothing assert nsm.move(sm.path) is nsm # rename a module nmp = join_path_native("new", "module", "dir") + "/" # new module path pmp = nsm.path abspmp = nsm.abspath assert nsm.move(nmp) is nsm nmp = nmp[:-1] # cut last / nmpl = to_native_path_linux(nmp) assert nsm.path == nmpl assert rwrepo.submodules[0].path == nmpl mpath = 'newsubmodule' absmpath = join_path_native(rwrepo.working_tree_dir, mpath) open(absmpath, 'w').write('') self.failUnlessRaises(ValueError, nsm.move, mpath) os.remove(absmpath) # now it works, as we just move it back nsm.move(pmp) assert nsm.path == pmp assert rwrepo.submodules[0].path == pmp # TODO lowprio: test remaining exceptions ... for now its okay, the code looks right # REMOVE 'EM ALL ################ # if a submodule's repo has no remotes, it can't be added without an explicit url osmod = osm.module() osm.remove(module=False) for remote in osmod.remotes: remote.remove(osmod, remote.name) assert not osm.exists() self.failUnlessRaises(ValueError, Submodule.add, rwrepo, osmid, csm_repopath, url=None) # END handle bare mode # Error if there is no submodule file here self.failUnlessRaises(IOError, Submodule._config_parser, rwrepo, rwrepo.commit(self.k_no_subm_tag), True)
def move(self, module_path, configuration=True, module=True): """Move the submodule to a another module path. This involves physically moving the repository at our current path, changing the configuration, as well as adjusting our index entry accordingly. :param module_path: the path to which to move our module, given as repository-relative path. Intermediate directories will be created accordingly. If the path already exists, it must be empty. Trailling (back)slashes are removed automatically :param configuration: if True, the configuration will be adjusted to let the submodule point to the given path. :param module: if True, the repository managed by this submodule will be moved, not the configuration. This will effectively leave your repository in an inconsistent state unless the configuration and index already point to the target location. :return: self :raise ValueError: if the module path existed and was not empty, or was a file :note: Currently the method is not atomic, and it could leave the repository in an inconsistent state if a sub-step fails for some reason """ if module + configuration < 1: raise ValueError("You must specify to move at least the module or the configuration of the submodule") #END handle input module_path = to_native_path_linux(module_path) if module_path.endswith('/'): module_path = module_path[:-1] # END handle trailing slash # VERIFY DESTINATION if module_path == self.path: return self #END handle no change dest_path = join_path_native(self.repo.working_tree_dir, module_path) if os.path.isfile(dest_path): raise ValueError("Cannot move repository onto a file: %s" % dest_path) # END handle target files index = self.repo.index tekey = index.entry_key(module_path, 0) # if the target item already exists, fail if configuration and tekey in index.entries: raise ValueError("Index entry for target path did alredy exist") #END handle index key already there # remove existing destination if module: if os.path.exists(dest_path): if len(os.listdir(dest_path)): raise ValueError("Destination module directory was not empty") #END handle non-emptyness if os.path.islink(dest_path): os.remove(dest_path) else: os.rmdir(dest_path) #END handle link else: # recreate parent directories # NOTE: renames() does that now pass #END handle existance # END handle module # move the module into place if possible cur_path = self.abspath renamed_module = False if module and os.path.exists(cur_path): os.renames(cur_path, dest_path) renamed_module = True #END move physical module # rename the index entry - have to manipulate the index directly as # git-mv cannot be used on submodules ... yeah try: if configuration: try: ekey = index.entry_key(self.path, 0) entry = index.entries[ekey] del(index.entries[ekey]) nentry = git.IndexEntry(entry[:3]+(module_path,)+entry[4:]) index.entries[tekey] = nentry except KeyError: raise InvalidGitRepositoryError("Submodule's entry at %r did not exist" % (self.path)) #END handle submodule doesn't exist # update configuration writer = self.config_writer(index=index) # auto-write writer.set_value('path', module_path) self.path = module_path del(writer) # END handle configuration flag except Exception: if renamed_module: os.renames(dest_path, cur_path) # END undo module renaming raise #END handle undo rename return self
def update(self, recursive=False, init=True, to_latest_revision=False, progress=None, dry_run=False, ): """Update the repository of this submodule to point to the checkout we point at with the binsha of this instance. :param recursive: if True, we will operate recursively and update child- modules as well. :param init: if True, the module repository will be cloned into place if necessary :param to_latest_revision: if True, the submodule's sha will be ignored during checkout. Instead, the remote will be fetched, and the local tracking branch updated. This only works if we have a local tracking branch, which is the case if the remote repository had a master branch, or of the 'branch' option was specified for this submodule and the branch existed remotely :param progress: UpdateProgress instance or None of no progress should be shown :param dry_run: if True, the operation will only be simulated, but not performed. All performed operations are read-only :note: does nothing in bare repositories :note: method is definitely not atomic if recurisve is True :return: self""" if self.repo.bare: return self #END pass in bare mode if progress is None: progress = UpdateProgress() #END handle progress prefix = '' if dry_run: prefix = "DRY-RUN: " #END handle prefix # to keep things plausible in dry-run mode if dry_run: mrepo = None #END init mrepo # ASSURE REPO IS PRESENT AND UPTODATE ##################################### try: mrepo = self.module() rmts = mrepo.remotes len_rmts = len(rmts) for i, remote in enumerate(rmts): op = FETCH if i == 0: op |= BEGIN #END handle start progress.update(op, i, len_rmts, prefix+"Fetching remote %s of submodule %r" % (remote, self.name)) #=============================== if not dry_run: remote.fetch(progress=progress) #END handle dry-run #=============================== if i == len_rmts-1: op |= END #END handle end progress.update(op, i, len_rmts, prefix+"Done fetching remote of submodule %r" % self.name) #END fetch new data except InvalidGitRepositoryError: if not init: return self # END early abort if init is not allowed # there is no git-repository yet - but delete empty paths module_path = join_path_native(self.repo.working_tree_dir, self.path) if not dry_run and os.path.isdir(module_path): try: os.rmdir(module_path) except OSError: raise OSError("Module directory at %r does already exist and is non-empty" % module_path) # END handle OSError # END handle directory removal # don't check it out at first - nonetheless it will create a local # branch according to the remote-HEAD if possible progress.update(BEGIN|CLONE, 0, 1, prefix+"Cloning %s to %s in submodule %r" % (self.url, module_path, self.name)) if not dry_run: mrepo = type(self.repo).clone_from(self.url, module_path, n=True) #END handle dry-run progress.update(END|CLONE, 0, 1, prefix+"Done cloning to %s" % module_path) if not dry_run: # see whether we have a valid branch to checkout try: # find a remote which has our branch - we try to be flexible remote_branch = find_first_remote_branch(mrepo.remotes, self.branch_name) local_branch = mkhead(mrepo, self.branch_path) # have a valid branch, but no checkout - make sure we can figure # that out by marking the commit with a null_sha local_branch.set_object(util.Object(mrepo, self.NULL_BIN_SHA)) # END initial checkout + branch creation # make sure HEAD is not detached mrepo.head.set_reference(local_branch, logmsg="submodule: attaching head to %s" % local_branch) mrepo.head.ref.set_tracking_branch(remote_branch) except IndexError: print >> sys.stderr, "Warning: Failed to checkout tracking branch %s" % self.branch_path #END handle tracking branch # NOTE: Have to write the repo config file as well, otherwise # the default implementation will be offended and not update the repository # Maybe this is a good way to assure it doesn't get into our way, but # we want to stay backwards compatible too ... . Its so redundant ! self.repo.config_writer().set_value(sm_section(self.name), 'url', self.url) #END handle dry_run #END handle initalization # DETERMINE SHAS TO CHECKOUT ############################ binsha = self.binsha hexsha = self.hexsha if mrepo is not None: # mrepo is only set if we are not in dry-run mode or if the module existed is_detached = mrepo.head.is_detached #END handle dry_run if mrepo is not None and to_latest_revision: msg_base = "Cannot update to latest revision in repository at %r as " % mrepo.working_dir if not is_detached: rref = mrepo.head.ref.tracking_branch() if rref is not None: rcommit = rref.commit binsha = rcommit.binsha hexsha = rcommit.hexsha else: print >> sys.stderr, "%s a tracking branch was not set for local branch '%s'" % (msg_base, mrepo.head.ref) # END handle remote ref else: print >> sys.stderr, "%s there was no local tracking branch" % msg_base # END handle detached head # END handle to_latest_revision option # update the working tree # handles dry_run if mrepo is not None and mrepo.head.commit.binsha != binsha: progress.update(BEGIN|UPDWKTREE, 0, 1, prefix+"Updating working tree at %s for submodule %r to revision %s" % (self.path, self.name, hexsha)) if not dry_run: if is_detached: # NOTE: for now we force, the user is no supposed to change detached # submodules anyway. Maybe at some point this becomes an option, to # properly handle user modifications - see below for future options # regarding rebase and merge. mrepo.git.checkout(hexsha, force=True) else: # TODO: allow to specify a rebase, merge, or reset # TODO: Warn if the hexsha forces the tracking branch off the remote # branch - this should be prevented when setting the branch option mrepo.head.reset(hexsha, index=True, working_tree=True) # END handle checkout #END handle dry_run progress.update(END|UPDWKTREE, 0, 1, prefix+"Done updating working tree for submodule %r" % self.name) # END update to new commit only if needed # HANDLE RECURSION ################## if recursive: # in dry_run mode, the module might not exist if mrepo is not None: for submodule in self.iter_items(self.module()): submodule.update(recursive, init, to_latest_revision, progress=progress, dry_run=dry_run) # END handle recursive update #END handle dry run # END for each submodule return self
def from_tree(cls, repo, *treeish, **kwargs): """Merge the given treeish revisions into a new index which is returned. The original index will remain unaltered :param repo: The repository treeish are located in. :param treeish: One, two or three Tree Objects, Commits or 40 byte hexshas. The result changes according to the amount of trees. If 1 Tree is given, it will just be read into a new index If 2 Trees are given, they will be merged into a new index using a two way merge algorithm. Tree 1 is the 'current' tree, tree 2 is the 'other' one. It behaves like a fast-forward. If 3 Trees are given, a 3-way merge will be performed with the first tree being the common ancestor of tree 2 and tree 3. Tree 2 is the 'current' tree, tree 3 is the 'other' one :param kwargs: Additional arguments passed to git-read-tree :return: New IndexFile instance. It will point to a temporary index location which does not exist anymore. If you intend to write such a merged Index, supply an alternate file_path to its 'write' method. :note: In the three-way merge case, --aggressive will be specified to automatically resolve more cases in a commonly correct manner. Specify trivial=True as kwarg to override that. As the underlying git-read-tree command takes into account the current index, it will be temporarily moved out of the way to assure there are no unsuspected interferences.""" if len(treeish) == 0 or len(treeish) > 3: raise ValueError("Please specify between 1 and 3 treeish, got %i" % len(treeish)) arg_list = list() # ignore that working tree and index possibly are out of date if len(treeish)>1: # drop unmerged entries when reading our index and merging arg_list.append("--reset") # handle non-trivial cases the way a real merge does arg_list.append("--aggressive") # END merge handling # tmp file created in git home directory to be sure renaming # works - /tmp/ dirs could be on another device tmp_index = tempfile.mktemp('','',repo.git_dir) arg_list.append("--index-output=%s" % tmp_index) arg_list.extend(treeish) # move current index out of the way - otherwise the merge may fail # as it considers existing entries. moving it essentially clears the index. # Unfortunately there is no 'soft' way to do it. # The TemporaryFileSwap assure the original file get put back index_handler = TemporaryFileSwap(join_path_native(repo.git_dir, 'index')) try: repo.git.read_tree(*arg_list, **kwargs) index = cls(repo, tmp_index) index.entries # force it to read the file as we will delete the temp-file del(index_handler) # release as soon as possible finally: if os.path.exists(tmp_index): os.remove(tmp_index) # END index merge handling return index
def test_root_module(self, rwrepo): # Can query everything without problems rm = RootModule(self.rorepo) assert rm.module() is self.rorepo # try attributes rm.binsha rm.mode rm.path assert rm.name == rm.k_root_name assert rm.parent_commit == self.rorepo.head.commit rm.url rm.branch assert len(rm.list_items(rm.module())) == 1 rm.config_reader() with rm.config_writer(): pass # deep traversal gitdb / async rsmsp = [sm.path for sm in rm.traverse()] assert len(rsmsp) >= 2 # gitdb and async [and smmap], async being a child of gitdb # cannot set the parent commit as root module's path didn't exist self.failUnlessRaises(ValueError, rm.set_parent_commit, 'HEAD') # TEST UPDATE ############# # setup commit which remove existing, add new and modify existing submodules rm = RootModule(rwrepo) assert len(rm.children()) == 1 # modify path without modifying the index entry # ( which is what the move method would do properly ) #================================================== sm = rm.children()[0] pp = "path/prefix" fp = join_path_native(pp, sm.path) prep = sm.path assert not sm.module_exists() # was never updated after rwrepo's clone # assure we clone from a local source with sm.config_writer() as writer: writer.set_value('url', Git.polish_url(osp.join(self.rorepo.working_tree_dir, sm.path))) # dry-run does nothing sm.update(recursive=False, dry_run=True, progress=prog) assert not sm.module_exists() sm.update(recursive=False) assert sm.module_exists() with sm.config_writer() as writer: writer.set_value('path', fp) # change path to something with prefix AFTER url change # update fails as list_items in such a situations cannot work, as it cannot # find the entry at the changed path self.failUnlessRaises(InvalidGitRepositoryError, rm.update, recursive=False) # move it properly - doesn't work as it its path currently points to an indexentry # which doesn't exist ( move it to some path, it doesn't matter here ) self.failUnlessRaises(InvalidGitRepositoryError, sm.move, pp) # reset the path(cache) to where it was, now it works sm.path = prep sm.move(fp, module=False) # leave it at the old location assert not sm.module_exists() cpathchange = rwrepo.index.commit("changed sm path") # finally we can commit # update puts the module into place rm.update(recursive=False, progress=prog) sm.set_parent_commit(cpathchange) assert sm.module_exists() # add submodule #================ nsmn = "newsubmodule" nsmp = "submrepo" subrepo_url = Git.polish_url(osp.join(self.rorepo.working_tree_dir, rsmsp[0], rsmsp[1])) nsm = Submodule.add(rwrepo, nsmn, nsmp, url=subrepo_url) csmadded = rwrepo.index.commit("Added submodule").hexsha # make sure we don't keep the repo reference nsm.set_parent_commit(csmadded) assert nsm.module_exists() # in our case, the module should not exist, which happens if we update a parent # repo and a new submodule comes into life nsm.remove(configuration=False, module=True) assert not nsm.module_exists() and nsm.exists() # dry-run does nothing rm.update(recursive=False, dry_run=True, progress=prog) # otherwise it will work rm.update(recursive=False, progress=prog) assert nsm.module_exists() # remove submodule - the previous one #==================================== sm.set_parent_commit(csmadded) smp = sm.abspath assert not sm.remove(module=False).exists() assert osp.isdir(smp) # module still exists csmremoved = rwrepo.index.commit("Removed submodule") # an update will remove the module # not in dry_run rm.update(recursive=False, dry_run=True, force_remove=True) assert osp.isdir(smp) # when removing submodules, we may get new commits as nested submodules are auto-committing changes # to allow deletions without force, as the index would be dirty otherwise. # QUESTION: Why does this seem to work in test_git_submodule_compatibility() ? self.failUnlessRaises(InvalidGitRepositoryError, rm.update, recursive=False, force_remove=False) rm.update(recursive=False, force_remove=True) assert not osp.isdir(smp) # 'apply work' to the nested submodule and assure this is not removed/altered during updates # Need to commit first, otherwise submodule.update wouldn't have a reason to change the head touch(osp.join(nsm.module().working_tree_dir, 'new-file')) # We cannot expect is_dirty to even run as we wouldn't reset a head to the same location assert nsm.module().head.commit.hexsha == nsm.hexsha nsm.module().index.add([nsm]) nsm.module().index.commit("added new file") rm.update(recursive=False, dry_run=True, progress=prog) # would not change head, and thus doens't fail # Everything we can do from now on will trigger the 'future' check, so no is_dirty() check will even run # This would only run if our local branch is in the past and we have uncommitted changes prev_commit = nsm.module().head.commit rm.update(recursive=False, dry_run=False, progress=prog) assert prev_commit == nsm.module().head.commit, "head shouldn't change, as it is in future of remote branch" # this kills the new file rm.update(recursive=True, progress=prog, force_reset=True) assert prev_commit != nsm.module().head.commit, "head changed, as the remote url and its commit changed" # change url ... #=============== # ... to the first repository, this way we have a fast checkout, and a completely different # repository at the different url nsm.set_parent_commit(csmremoved) nsmurl = Git.polish_url(osp.join(self.rorepo.working_tree_dir, rsmsp[0])) with nsm.config_writer() as writer: writer.set_value('url', nsmurl) csmpathchange = rwrepo.index.commit("changed url") nsm.set_parent_commit(csmpathchange) # Now nsm head is in the future of the tracked remote branch prev_commit = nsm.module().head.commit # dry-run does nothing rm.update(recursive=False, dry_run=True, progress=prog) assert nsm.module().remotes.origin.url != nsmurl rm.update(recursive=False, progress=prog, force_reset=True) assert nsm.module().remotes.origin.url == nsmurl assert prev_commit != nsm.module().head.commit, "Should now point to gitdb" assert len(rwrepo.submodules) == 1 assert not rwrepo.submodules[0].children()[0].module_exists(), "nested submodule should not be checked out" # add the submodule's changed commit to the index, which is what the # user would do # beforehand, update our instance's binsha with the new one nsm.binsha = nsm.module().head.commit.binsha rwrepo.index.add([nsm]) # change branch #================= # we only have one branch, so we switch to a virtual one, and back # to the current one to trigger the difference cur_branch = nsm.branch nsmm = nsm.module() prev_commit = nsmm.head.commit for branch in ("some_virtual_branch", cur_branch.name): with nsm.config_writer() as writer: writer.set_value(Submodule.k_head_option, git.Head.to_full_path(branch)) csmbranchchange = rwrepo.index.commit("changed branch to %s" % branch) nsm.set_parent_commit(csmbranchchange) # END for each branch to change # Lets remove our tracking branch to simulate some changes nsmmh = nsmm.head assert nsmmh.ref.tracking_branch() is None # never set it up until now assert not nsmmh.is_detached # dry run does nothing rm.update(recursive=False, dry_run=True, progress=prog) assert nsmmh.ref.tracking_branch() is None # the real thing does rm.update(recursive=False, progress=prog) assert nsmmh.ref.tracking_branch() is not None assert not nsmmh.is_detached # recursive update # ================= # finally we recursively update a module, just to run the code at least once # remove the module so that it has more work assert len(nsm.children()) >= 1 # could include smmap assert nsm.exists() and nsm.module_exists() and len(nsm.children()) >= 1 # assure we pull locally only nsmc = nsm.children()[0] with nsmc.config_writer() as writer: writer.set_value('url', subrepo_url) rm.update(recursive=True, progress=prog, dry_run=True) # just to run the code rm.update(recursive=True, progress=prog) # gitdb: has either 1 or 2 submodules depending on the version assert len(nsm.children()) >= 1 and nsmc.module_exists()
def _do_base_tests(self, rwrepo): """Perform all tests in the given repository, it may be bare or nonbare""" # manual instantiation smm = Submodule(rwrepo, "\0" * 20) # name needs to be set in advance self.failUnlessRaises(AttributeError, getattr, smm, 'name') # iterate - 1 submodule sms = Submodule.list_items(rwrepo, self.k_subm_current) assert len(sms) == 1 sm = sms[0] # at a different time, there is None assert len(Submodule.list_items(rwrepo, self.k_no_subm_tag)) == 0 assert sm.path == 'git/ext/gitdb' assert sm.path != sm.name # in our case, we have ids there, which don't equal the path assert sm.url == 'git://github.com/gitpython-developers/gitdb.git' assert sm.branch_path == 'refs/heads/master' # the default ... assert sm.branch_name == 'master' assert sm.parent_commit == rwrepo.head.commit # size is always 0 assert sm.size == 0 # the module is not checked-out yet self.failUnlessRaises(InvalidGitRepositoryError, sm.module) # which is why we can't get the branch either - it points into the module() repository self.failUnlessRaises(InvalidGitRepositoryError, getattr, sm, 'branch') # branch_path works, as its just a string assert isinstance(sm.branch_path, basestring) # some commits earlier we still have a submodule, but its at a different commit smold = Submodule.iter_items(rwrepo, self.k_subm_changed).next() assert smold.binsha != sm.binsha assert smold != sm # the name changed # force it to reread its information del (smold._url) smold.url == sm.url # test config_reader/writer methods sm.config_reader() new_smclone_path = None # keep custom paths for later new_csmclone_path = None # if rwrepo.bare: self.failUnlessRaises(InvalidGitRepositoryError, sm.config_writer) else: writer = sm.config_writer() # for faster checkout, set the url to the local path new_smclone_path = to_native_path_linux( join_path_native(self.rorepo.working_tree_dir, sm.path)) writer.set_value('url', new_smclone_path) del (writer) assert sm.config_reader().get_value('url') == new_smclone_path assert sm.url == new_smclone_path # END handle bare repo smold.config_reader() # cannot get a writer on historical submodules if not rwrepo.bare: self.failUnlessRaises(ValueError, smold.config_writer) # END handle bare repo # make the old into a new - this doesn't work as the name changed prev_parent_commit = smold.parent_commit self.failUnlessRaises(ValueError, smold.set_parent_commit, self.k_subm_current) # the sha is properly updated smold.set_parent_commit(self.k_subm_changed + "~1") assert smold.binsha != sm.binsha # raises if the sm didn't exist in new parent - it keeps its # parent_commit unchanged self.failUnlessRaises(ValueError, smold.set_parent_commit, self.k_no_subm_tag) # TEST TODO: if a path in the gitmodules file, but not in the index, it raises # TEST UPDATE ############## # module retrieval is not always possible if rwrepo.bare: self.failUnlessRaises(InvalidGitRepositoryError, sm.module) self.failUnlessRaises(InvalidGitRepositoryError, sm.remove) self.failUnlessRaises(InvalidGitRepositoryError, sm.add, rwrepo, 'here', 'there') else: # its not checked out in our case self.failUnlessRaises(InvalidGitRepositoryError, sm.module) assert not sm.module_exists() # currently there is only one submodule assert len(list(rwrepo.iter_submodules())) == 1 assert sm.binsha != "\0" * 20 # TEST ADD ########### # preliminary tests # adding existing returns exactly the existing sma = Submodule.add(rwrepo, sm.name, sm.path) assert sma.path == sm.path # no url and no module at path fails self.failUnlessRaises(ValueError, Submodule.add, rwrepo, "newsubm", "pathtorepo", url=None) # CONTINUE UPDATE ################# # lets update it - its a recursive one too newdir = os.path.join(sm.abspath, 'dir') os.makedirs(newdir) # update fails if the path already exists non-empty self.failUnlessRaises(OSError, sm.update) os.rmdir(newdir) # dry-run does nothing sm.update(dry_run=True, progress=prog) assert not sm.module_exists() assert sm.update() is sm sm_repopath = sm.path # cache for later assert sm.module_exists() assert isinstance(sm.module(), git.Repo) assert sm.module().working_tree_dir == sm.abspath # INTERLEAVE ADD TEST ##################### # url must match the one in the existing repository ( if submodule name suggests a new one ) # or we raise self.failUnlessRaises(ValueError, Submodule.add, rwrepo, "newsubm", sm.path, "git://someurl/repo.git") # CONTINUE UPDATE ################# # we should have setup a tracking branch, which is also active assert sm.module().head.ref.tracking_branch() is not None # delete the whole directory and re-initialize shutil.rmtree(sm.abspath) assert len(sm.children()) == 0 # dry-run does nothing sm.update(dry_run=True, recursive=False, progress=prog) assert len(sm.children()) == 0 sm.update(recursive=False) assert len(list(rwrepo.iter_submodules())) == 2 assert len(sm.children()) == 1 # its not checked out yet csm = sm.children()[0] assert not csm.module_exists() csm_repopath = csm.path # adjust the path of the submodules module to point to the local destination new_csmclone_path = to_native_path_linux( join_path_native(self.rorepo.working_tree_dir, sm.path, csm.path)) csm.config_writer().set_value('url', new_csmclone_path) assert csm.url == new_csmclone_path # dry-run does nothing assert not csm.module_exists() sm.update(recursive=True, dry_run=True, progress=prog) assert not csm.module_exists() # update recursively again sm.update(recursive=True) assert csm.module_exists() # tracking branch once again csm.module().head.ref.tracking_branch() is not None # this flushed in a sub-submodule assert len(list(rwrepo.iter_submodules())) == 2 # reset both heads to the previous version, verify that to_latest_revision works smods = (sm.module(), csm.module()) for repo in smods: repo.head.reset('HEAD~2', working_tree=1) # END for each repo to reset # dry run does nothing sm.update(recursive=True, dry_run=True, progress=prog) for repo in smods: assert repo.head.commit != repo.head.ref.tracking_branch( ).commit # END for each repo to check sm.update(recursive=True, to_latest_revision=True) for repo in smods: assert repo.head.commit == repo.head.ref.tracking_branch( ).commit # END for each repo to check del (smods) # if the head is detached, it still works ( but warns ) smref = sm.module().head.ref sm.module().head.ref = 'HEAD~1' # if there is no tracking branch, we get a warning as well csm_tracking_branch = csm.module().head.ref.tracking_branch() csm.module().head.ref.set_tracking_branch(None) sm.update(recursive=True, to_latest_revision=True) # to_latest_revision changes the child submodule's commit, it needs an # update now csm.set_parent_commit(csm.repo.head.commit) # undo the changes sm.module().head.ref = smref csm.module().head.ref.set_tracking_branch(csm_tracking_branch) # REMOVAL OF REPOSITOTRY ######################## # must delete something self.failUnlessRaises(ValueError, csm.remove, module=False, configuration=False) # We have modified the configuration, hence the index is dirty, and the # deletion will fail # NOTE: As we did a few updates in the meanwhile, the indices were reset # Hence we create some changes csm.set_parent_commit(csm.repo.head.commit) sm.config_writer().set_value("somekey", "somevalue") csm.config_writer().set_value("okey", "ovalue") self.failUnlessRaises(InvalidGitRepositoryError, sm.remove) # if we remove the dirty index, it would work sm.module().index.reset() # still, we have the file modified self.failUnlessRaises(InvalidGitRepositoryError, sm.remove, dry_run=True) sm.module().index.reset(working_tree=True) # this would work assert sm.remove(dry_run=True) is sm assert sm.module_exists() sm.remove(force=True, dry_run=True) assert sm.module_exists() # but ... we have untracked files in the child submodule fn = join_path_native(csm.module().working_tree_dir, "newfile") open(fn, 'w').write("hi") self.failUnlessRaises(InvalidGitRepositoryError, sm.remove) # forcibly delete the child repository assert csm.remove(force=True) is csm assert not csm.exists() assert not csm.module_exists() assert len(sm.children()) == 0 # now we have a changed index, as configuration was altered. # fix this sm.module().index.reset(working_tree=True) # now delete only the module of the main submodule assert sm.module_exists() sm.remove(configuration=False) assert sm.exists() assert not sm.module_exists() assert sm.config_reader().get_value('url') # delete the rest sm.remove() assert not sm.exists() assert not sm.module_exists() assert len(rwrepo.submodules) == 0 # ADD NEW SUBMODULE ################### # add a simple remote repo - trailing slashes are no problem smid = "newsub" osmid = "othersub" nsm = Submodule.add(rwrepo, smid, sm_repopath, new_smclone_path + "/", None, no_checkout=True) assert nsm.name == smid assert nsm.module_exists() assert nsm.exists() # its not checked out assert not os.path.isfile( join_path_native(nsm.module().working_tree_dir, Submodule.k_modules_file)) assert len(rwrepo.submodules) == 1 # add another submodule, but into the root, not as submodule osm = Submodule.add(rwrepo, osmid, csm_repopath, new_csmclone_path, Submodule.k_head_default) assert osm != nsm assert osm.module_exists() assert osm.exists() assert os.path.isfile( join_path_native(osm.module().working_tree_dir, 'setup.py')) assert len(rwrepo.submodules) == 2 # commit the changes, just to finalize the operation rwrepo.index.commit("my submod commit") assert len(rwrepo.submodules) == 2 # needs update as the head changed, it thinks its in the history # of the repo otherwise nsm.set_parent_commit(rwrepo.head.commit) osm.set_parent_commit(rwrepo.head.commit) # MOVE MODULE ############# # invalid inptu self.failUnlessRaises(ValueError, nsm.move, 'doesntmatter', module=False, configuration=False) # renaming to the same path does nothing assert nsm.move(sm.path) is nsm # rename a module nmp = join_path_native("new", "module", "dir") + "/" # new module path pmp = nsm.path abspmp = nsm.abspath assert nsm.move(nmp) is nsm nmp = nmp[:-1] # cut last / assert nsm.path == nmp assert rwrepo.submodules[0].path == nmp mpath = 'newsubmodule' absmpath = join_path_native(rwrepo.working_tree_dir, mpath) open(absmpath, 'w').write('') self.failUnlessRaises(ValueError, nsm.move, mpath) os.remove(absmpath) # now it works, as we just move it back nsm.move(pmp) assert nsm.path == pmp assert rwrepo.submodules[0].path == pmp # TODO lowprio: test remaining exceptions ... for now its okay, the code looks right # REMOVE 'EM ALL ################ # if a submodule's repo has no remotes, it can't be added without an explicit url osmod = osm.module() osm.remove(module=False) for remote in osmod.remotes: remote.remove(osmod, remote.name) assert not osm.exists() self.failUnlessRaises(ValueError, Submodule.add, rwrepo, osmid, csm_repopath, url=None) # END handle bare mode # Error if there is no submodule file here self.failUnlessRaises(IOError, Submodule._config_parser, rwrepo, rwrepo.commit(self.k_no_subm_tag), True)
def update(self, recursive=False, init=True, to_latest_revision=False, progress=None, dry_run=False): """Update the repository of this submodule to point to the checkout we point at with the binsha of this instance. :param recursive: if True, we will operate recursively and update child- modules as well. :param init: if True, the module repository will be cloned into place if necessary :param to_latest_revision: if True, the submodule's sha will be ignored during checkout. Instead, the remote will be fetched, and the local tracking branch updated. This only works if we have a local tracking branch, which is the case if the remote repository had a master branch, or of the 'branch' option was specified for this submodule and the branch existed remotely :param progress: UpdateProgress instance or None of no progress should be shown :param dry_run: if True, the operation will only be simulated, but not performed. All performed operations are read-only :note: does nothing in bare repositories :note: method is definitely not atomic if recurisve is True :return: self""" if self.repo.bare: return self #END pass in bare mode if progress is None: progress = UpdateProgress() #END handle progress prefix = '' if dry_run: prefix = "DRY-RUN: " #END handle prefix # to keep things plausible in dry-run mode if dry_run: mrepo = None #END init mrepo # ASSURE REPO IS PRESENT AND UPTODATE ##################################### try: mrepo = self.module() rmts = mrepo.remotes len_rmts = len(rmts) for i, remote in enumerate(rmts): op = FETCH if i == 0: op |= BEGIN #END handle start progress.update( op, i, len_rmts, prefix + "Fetching remote %s of submodule %r" % (remote, self.name)) #=============================== if not dry_run: remote.fetch(progress=progress) #END handle dry-run #=============================== if i == len_rmts - 1: op |= END #END handle end progress.update( op, i, len_rmts, prefix + "Done fetching remote of submodule %r" % self.name) #END fetch new data except InvalidGitRepositoryError: if not init: return self # END early abort if init is not allowed import git # there is no git-repository yet - but delete empty paths module_path = join_path_native(self.repo.working_tree_dir, self.path) if not dry_run and os.path.isdir(module_path): try: os.rmdir(module_path) except OSError: raise OSError( "Module directory at %r does already exist and is non-empty" % module_path) # END handle OSError # END handle directory removal # don't check it out at first - nonetheless it will create a local # branch according to the remote-HEAD if possible progress.update( BEGIN | CLONE, 0, 1, prefix + "Cloning %s to %s in submodule %r" % (self.url, module_path, self.name)) if not dry_run: mrepo = git.Repo.clone_from(self.url, module_path, n=True) #END handle dry-run progress.update(END | CLONE, 0, 1, prefix + "Done cloning to %s" % module_path) if not dry_run: # see whether we have a valid branch to checkout try: # find a remote which has our branch - we try to be flexible remote_branch = find_first_remote_branch( mrepo.remotes, self.branch_name) local_branch = mkhead(mrepo, self.branch_path) # have a valid branch, but no checkout - make sure we can figure # that out by marking the commit with a null_sha local_branch.set_object( util.Object(mrepo, self.NULL_BIN_SHA)) # END initial checkout + branch creation # make sure HEAD is not detached mrepo.head.set_reference( local_branch, logmsg="submodule: attaching head to %s" % local_branch) mrepo.head.ref.set_tracking_branch(remote_branch) except IndexError: print >> sys.stderr, "Warning: Failed to checkout tracking branch %s" % self.branch_path #END handle tracking branch # NOTE: Have to write the repo config file as well, otherwise # the default implementation will be offended and not update the repository # Maybe this is a good way to assure it doesn't get into our way, but # we want to stay backwards compatible too ... . Its so redundant ! self.repo.config_writer().set_value(sm_section(self.name), 'url', self.url) #END handle dry_run #END handle initalization # DETERMINE SHAS TO CHECKOUT ############################ binsha = self.binsha hexsha = self.hexsha if mrepo is not None: # mrepo is only set if we are not in dry-run mode or if the module existed is_detached = mrepo.head.is_detached #END handle dry_run if mrepo is not None and to_latest_revision: msg_base = "Cannot update to latest revision in repository at %r as " % mrepo.working_dir if not is_detached: rref = mrepo.head.ref.tracking_branch() if rref is not None: rcommit = rref.commit binsha = rcommit.binsha hexsha = rcommit.hexsha else: print >> sys.stderr, "%s a tracking branch was not set for local branch '%s'" % ( msg_base, mrepo.head.ref) # END handle remote ref else: print >> sys.stderr, "%s there was no local tracking branch" % msg_base # END handle detached head # END handle to_latest_revision option # update the working tree # handles dry_run if mrepo is not None and mrepo.head.commit.binsha != binsha: progress.update( BEGIN | UPDWKTREE, 0, 1, prefix + "Updating working tree at %s for submodule %r to revision %s" % (self.path, self.name, hexsha)) if not dry_run: if is_detached: # NOTE: for now we force, the user is no supposed to change detached # submodules anyway. Maybe at some point this becomes an option, to # properly handle user modifications - see below for future options # regarding rebase and merge. mrepo.git.checkout(hexsha, force=True) else: # TODO: allow to specify a rebase, merge, or reset # TODO: Warn if the hexsha forces the tracking branch off the remote # branch - this should be prevented when setting the branch option mrepo.head.reset(hexsha, index=True, working_tree=True) # END handle checkout #END handle dry_run progress.update( END | UPDWKTREE, 0, 1, prefix + "Done updating working tree for submodule %r" % self.name) # END update to new commit only if needed # HANDLE RECURSION ################## if recursive: # in dry_run mode, the module might not exist if mrepo is not None: for submodule in self.iter_items(self.module()): submodule.update(recursive, init, to_latest_revision, progress=progress, dry_run=dry_run) # END handle recursive update #END handle dry run # END for each submodule return self
def test_root_module(self, rwrepo): # Can query everything without problems rm = RootModule(self.rorepo) assert rm.module() is self.rorepo # try attributes rm.binsha rm.mode rm.path assert rm.name == rm.k_root_name assert rm.parent_commit == self.rorepo.head.commit rm.url rm.branch assert len(rm.list_items(rm.module())) == 1 rm.config_reader() rm.config_writer() # deep traversal gitdb / async rsmsp = [sm.path for sm in rm.traverse()] assert len(rsmsp) >= 2 # gitdb and async [and smmap], async being a child of gitdb # cannot set the parent commit as root module's path didn't exist self.failUnlessRaises(ValueError, rm.set_parent_commit, 'HEAD') # TEST UPDATE ############# # setup commit which remove existing, add new and modify existing submodules rm = RootModule(rwrepo) assert len(rm.children()) == 1 # modify path without modifying the index entry # ( which is what the move method would do properly ) #================================================== sm = rm.children()[0] pp = "path/prefix" fp = join_path_native(pp, sm.path) prep = sm.path assert not sm.module_exists() # was never updated after rwrepo's clone # assure we clone from a local source sm.config_writer().set_value('url', to_native_path_linux(join_path_native(self.rorepo.working_tree_dir, sm.path))) # dry-run does nothing sm.update(recursive=False, dry_run=True, progress=prog) assert not sm.module_exists() sm.update(recursive=False) assert sm.module_exists() sm.config_writer().set_value('path', fp) # change path to something with prefix AFTER url change # update fails as list_items in such a situations cannot work, as it cannot # find the entry at the changed path self.failUnlessRaises(InvalidGitRepositoryError, rm.update, recursive=False) # move it properly - doesn't work as it its path currently points to an indexentry # which doesn't exist ( move it to some path, it doesn't matter here ) self.failUnlessRaises(InvalidGitRepositoryError, sm.move, pp) # reset the path(cache) to where it was, now it works sm.path = prep sm.move(fp, module=False) # leave it at the old location assert not sm.module_exists() cpathchange = rwrepo.index.commit("changed sm path") # finally we can commit # update puts the module into place rm.update(recursive=False, progress=prog) sm.set_parent_commit(cpathchange) assert sm.module_exists() # add submodule #================ nsmn = "newsubmodule" nsmp = "submrepo" async_url = to_native_path_linux(join_path_native(self.rorepo.working_tree_dir, rsmsp[0], rsmsp[1])) nsm = Submodule.add(rwrepo, nsmn, nsmp, url=async_url) csmadded = rwrepo.index.commit("Added submodule").hexsha # make sure we don't keep the repo reference nsm.set_parent_commit(csmadded) assert nsm.module_exists() # in our case, the module should not exist, which happens if we update a parent # repo and a new submodule comes into life nsm.remove(configuration=False, module=True) assert not nsm.module_exists() and nsm.exists() # dry-run does nothing rm.update(recursive=False, dry_run=True, progress=prog) # otherwise it will work rm.update(recursive=False, progress=prog) assert nsm.module_exists() # remove submodule - the previous one #==================================== sm.set_parent_commit(csmadded) smp = sm.abspath assert not sm.remove(module=False).exists() assert os.path.isdir(smp) # module still exists csmremoved = rwrepo.index.commit("Removed submodule") # an update will remove the module # not in dry_run rm.update(recursive=False, dry_run=True) assert os.path.isdir(smp) rm.update(recursive=False) assert not os.path.isdir(smp) # change url #============= # to the first repository, this way we have a fast checkout, and a completely different # repository at the different url nsm.set_parent_commit(csmremoved) nsmurl = to_native_path_linux(join_path_native(self.rorepo.working_tree_dir, rsmsp[0])) nsm.config_writer().set_value('url', nsmurl) csmpathchange = rwrepo.index.commit("changed url") nsm.set_parent_commit(csmpathchange) prev_commit = nsm.module().head.commit # dry-run does nothing rm.update(recursive=False, dry_run=True, progress=prog) assert nsm.module().remotes.origin.url != nsmurl rm.update(recursive=False, progress=prog) assert nsm.module().remotes.origin.url == nsmurl # head changed, as the remote url and its commit changed assert prev_commit != nsm.module().head.commit # add the submodule's changed commit to the index, which is what the # user would do # beforehand, update our instance's binsha with the new one nsm.binsha = nsm.module().head.commit.binsha rwrepo.index.add([nsm]) # change branch #================= # we only have one branch, so we switch to a virtual one, and back # to the current one to trigger the difference cur_branch = nsm.branch nsmm = nsm.module() prev_commit = nsmm.head.commit for branch in ("some_virtual_branch", cur_branch.name): nsm.config_writer().set_value(Submodule.k_head_option, git.Head.to_full_path(branch)) csmbranchchange = rwrepo.index.commit("changed branch to %s" % branch) nsm.set_parent_commit(csmbranchchange) # END for each branch to change # Lets remove our tracking branch to simulate some changes nsmmh = nsmm.head assert nsmmh.ref.tracking_branch() is None # never set it up until now assert not nsmmh.is_detached #dry run does nothing rm.update(recursive=False, dry_run=True, progress=prog) assert nsmmh.ref.tracking_branch() is None # the real thing does rm.update(recursive=False, progress=prog) assert nsmmh.ref.tracking_branch() is not None assert not nsmmh.is_detached # recursive update # ================= # finally we recursively update a module, just to run the code at least once # remove the module so that it has more work assert len(nsm.children()) >= 1 # could include smmap assert nsm.exists() and nsm.module_exists() and len(nsm.children()) >= 1 # assure we pull locally only nsmc = nsm.children()[0] nsmc.config_writer().set_value('url', async_url) rm.update(recursive=True, progress=prog, dry_run=True) # just to run the code rm.update(recursive=True, progress=prog) # gitdb: has either 1 or 2 submodules depending on the version assert len(nsm.children()) >= 1 and nsmc.module_exists()
def test_root_module(self, rwrepo): # Can query everything without problems rm = RootModule(self.rorepo) assert rm.module() is self.rorepo # try attributes rm.binsha rm.mode rm.path assert rm.name == rm.k_root_name assert rm.parent_commit == self.rorepo.head.commit rm.url rm.branch assert len(rm.list_items(rm.module())) == 1 rm.config_reader() with rm.config_writer(): pass # deep traversal gitdb / async rsmsp = [sm.path for sm in rm.traverse()] assert len( rsmsp ) >= 2 # gitdb and async [and smmap], async being a child of gitdb # cannot set the parent commit as root module's path didn't exist self.failUnlessRaises(ValueError, rm.set_parent_commit, 'HEAD') # TEST UPDATE ############# # setup commit which remove existing, add new and modify existing submodules rm = RootModule(rwrepo) assert len(rm.children()) == 1 # modify path without modifying the index entry # ( which is what the move method would do properly ) #================================================== sm = rm.children()[0] pp = "path/prefix" fp = join_path_native(pp, sm.path) prep = sm.path assert not sm.module_exists() # was never updated after rwrepo's clone # assure we clone from a local source with sm.config_writer() as writer: writer.set_value( 'url', Git.polish_url(osp.join(self.rorepo.working_tree_dir, sm.path))) # dry-run does nothing sm.update(recursive=False, dry_run=True, progress=prog) assert not sm.module_exists() sm.update(recursive=False) assert sm.module_exists() with sm.config_writer() as writer: writer.set_value( 'path', fp) # change path to something with prefix AFTER url change # update doesn't fail, because list_items ignores the wrong path in such situations. rm.update(recursive=False) # move it properly - doesn't work as it its path currently points to an indexentry # which doesn't exist ( move it to some path, it doesn't matter here ) self.failUnlessRaises(InvalidGitRepositoryError, sm.move, pp) # reset the path(cache) to where it was, now it works sm.path = prep sm.move(fp, module=False) # leave it at the old location assert not sm.module_exists() cpathchange = rwrepo.index.commit( "changed sm path") # finally we can commit # update puts the module into place rm.update(recursive=False, progress=prog) sm.set_parent_commit(cpathchange) assert sm.module_exists() # add submodule #================ nsmn = "newsubmodule" nsmp = "submrepo" subrepo_url = Git.polish_url( osp.join(self.rorepo.working_tree_dir, rsmsp[0], rsmsp[1])) nsm = Submodule.add(rwrepo, nsmn, nsmp, url=subrepo_url) csmadded = rwrepo.index.commit( "Added submodule" ).hexsha # make sure we don't keep the repo reference nsm.set_parent_commit(csmadded) assert nsm.module_exists() # in our case, the module should not exist, which happens if we update a parent # repo and a new submodule comes into life nsm.remove(configuration=False, module=True) assert not nsm.module_exists() and nsm.exists() # dry-run does nothing rm.update(recursive=False, dry_run=True, progress=prog) # otherwise it will work rm.update(recursive=False, progress=prog) assert nsm.module_exists() # remove submodule - the previous one #==================================== sm.set_parent_commit(csmadded) smp = sm.abspath assert not sm.remove(module=False).exists() assert osp.isdir(smp) # module still exists csmremoved = rwrepo.index.commit("Removed submodule") # an update will remove the module # not in dry_run rm.update(recursive=False, dry_run=True, force_remove=True) assert osp.isdir(smp) # when removing submodules, we may get new commits as nested submodules are auto-committing changes # to allow deletions without force, as the index would be dirty otherwise. # QUESTION: Why does this seem to work in test_git_submodule_compatibility() ? self.failUnlessRaises(InvalidGitRepositoryError, rm.update, recursive=False, force_remove=False) rm.update(recursive=False, force_remove=True) assert not osp.isdir(smp) # 'apply work' to the nested submodule and assure this is not removed/altered during updates # Need to commit first, otherwise submodule.update wouldn't have a reason to change the head touch(osp.join(nsm.module().working_tree_dir, 'new-file')) # We cannot expect is_dirty to even run as we wouldn't reset a head to the same location assert nsm.module().head.commit.hexsha == nsm.hexsha nsm.module().index.add([nsm]) nsm.module().index.commit("added new file") rm.update( recursive=False, dry_run=True, progress=prog) # would not change head, and thus doens't fail # Everything we can do from now on will trigger the 'future' check, so no is_dirty() check will even run # This would only run if our local branch is in the past and we have uncommitted changes prev_commit = nsm.module().head.commit rm.update(recursive=False, dry_run=False, progress=prog) assert prev_commit == nsm.module( ).head.commit, "head shouldn't change, as it is in future of remote branch" # this kills the new file rm.update(recursive=True, progress=prog, force_reset=True) assert prev_commit != nsm.module( ).head.commit, "head changed, as the remote url and its commit changed" # change url ... #=============== # ... to the first repository, this way we have a fast checkout, and a completely different # repository at the different url nsm.set_parent_commit(csmremoved) nsmurl = Git.polish_url( osp.join(self.rorepo.working_tree_dir, rsmsp[0])) with nsm.config_writer() as writer: writer.set_value('url', nsmurl) csmpathchange = rwrepo.index.commit("changed url") nsm.set_parent_commit(csmpathchange) # Now nsm head is in the future of the tracked remote branch prev_commit = nsm.module().head.commit # dry-run does nothing rm.update(recursive=False, dry_run=True, progress=prog) assert nsm.module().remotes.origin.url != nsmurl rm.update(recursive=False, progress=prog, force_reset=True) assert nsm.module().remotes.origin.url == nsmurl assert prev_commit != nsm.module( ).head.commit, "Should now point to gitdb" assert len(rwrepo.submodules) == 1 assert not rwrepo.submodules[0].children()[0].module_exists( ), "nested submodule should not be checked out" # add the submodule's changed commit to the index, which is what the # user would do # beforehand, update our instance's binsha with the new one nsm.binsha = nsm.module().head.commit.binsha rwrepo.index.add([nsm]) # change branch #================= # we only have one branch, so we switch to a virtual one, and back # to the current one to trigger the difference cur_branch = nsm.branch nsmm = nsm.module() prev_commit = nsmm.head.commit for branch in ("some_virtual_branch", cur_branch.name): with nsm.config_writer() as writer: writer.set_value(Submodule.k_head_option, git.Head.to_full_path(branch)) csmbranchchange = rwrepo.index.commit("changed branch to %s" % branch) nsm.set_parent_commit(csmbranchchange) # END for each branch to change # Lets remove our tracking branch to simulate some changes nsmmh = nsmm.head assert nsmmh.ref.tracking_branch() is None # never set it up until now assert not nsmmh.is_detached # dry run does nothing rm.update(recursive=False, dry_run=True, progress=prog) assert nsmmh.ref.tracking_branch() is None # the real thing does rm.update(recursive=False, progress=prog) assert nsmmh.ref.tracking_branch() is not None assert not nsmmh.is_detached # recursive update # ================= # finally we recursively update a module, just to run the code at least once # remove the module so that it has more work assert len(nsm.children()) >= 1 # could include smmap assert nsm.exists() and nsm.module_exists() and len( nsm.children()) >= 1 # assure we pull locally only nsmc = nsm.children()[0] with nsmc.config_writer() as writer: writer.set_value('url', subrepo_url) rm.update(recursive=True, progress=prog, dry_run=True) # just to run the code rm.update(recursive=True, progress=prog) # gitdb: has either 1 or 2 submodules depending on the version assert len(nsm.children()) >= 1 and nsmc.module_exists()
def test_git_submodule_compatibility(self, rwdir): parent = git.Repo.init(osp.join(rwdir, 'parent')) sm_path = join_path_native('submodules', 'intermediate', 'one') sm = parent.create_submodule('mymodules/myname', sm_path, url=self._small_repo_url()) parent.index.commit("added submodule") def assert_exists(sm, value=True): assert sm.exists() == value assert sm.module_exists() == value # end # As git is backwards compatible itself, it would still recognize what we do here ... unless we really # muss it up. That's the only reason why the test is still here ... . assert len(parent.git.submodule().splitlines()) == 1 module_repo_path = osp.join(sm.module().working_tree_dir, '.git') assert module_repo_path.startswith(osp.join(parent.working_tree_dir, sm_path)) if not sm._need_gitfile_submodules(parent.git): assert osp.isdir(module_repo_path) assert not sm.module().has_separate_working_tree() else: assert osp.isfile(module_repo_path) assert sm.module().has_separate_working_tree() assert find_submodule_git_dir(module_repo_path) is not None, "module pointed to by .git file must be valid" # end verify submodule 'style' # test move new_sm_path = join_path_native('submodules', 'one') sm.move(new_sm_path) assert_exists(sm) # Add additional submodule level csm = sm.module().create_submodule('nested-submodule', join_path_native('nested-submodule', 'working-tree'), url=self._small_repo_url()) sm.module().index.commit("added nested submodule") sm_head_commit = sm.module().commit() assert_exists(csm) # Fails because there are new commits, compared to the remote we cloned from self.failUnlessRaises(InvalidGitRepositoryError, sm.remove, dry_run=True) assert_exists(sm) assert sm.module().commit() == sm_head_commit assert_exists(csm) # rename nested submodule # This name would move itself one level deeper - needs special handling internally new_name = csm.name + '/mine' assert csm.rename(new_name).name == new_name assert_exists(csm) assert csm.repo.is_dirty(index=True, working_tree=False), "index must contain changed .gitmodules file" csm.repo.index.commit("renamed module") # keep_going evaluation rsm = parent.submodule_update() assert_exists(sm) assert_exists(csm) with csm.config_writer().set_value('url', 'bar'): pass csm.repo.index.commit("Have to commit submodule change for algorithm to pick it up") assert csm.url == 'bar' self.failUnlessRaises(Exception, rsm.update, recursive=True, to_latest_revision=True, progress=prog) assert_exists(csm) rsm.update(recursive=True, to_latest_revision=True, progress=prog, keep_going=True) # remove sm_module_path = sm.module().git_dir for dry_run in (True, False): sm.remove(dry_run=dry_run, force=True) assert_exists(sm, value=dry_run) assert osp.isdir(sm_module_path) == dry_run
def test_git_submodule_compatibility(self, rwdir): parent = git.Repo.init(osp.join(rwdir, 'parent')) sm_path = join_path_native('submodules', 'intermediate', 'one') sm = parent.create_submodule('mymodules/myname', sm_path, url=self._small_repo_url()) parent.index.commit("added submodule") def assert_exists(sm, value=True): assert sm.exists() == value assert sm.module_exists() == value # end # As git is backwards compatible itself, it would still recognize what we do here ... unless we really # muss it up. That's the only reason why the test is still here ... . assert len(parent.git.submodule().splitlines()) == 1 module_repo_path = osp.join(sm.module().working_tree_dir, '.git') assert module_repo_path.startswith( osp.join(parent.working_tree_dir, sm_path)) if not sm._need_gitfile_submodules(parent.git): assert osp.isdir(module_repo_path) assert not sm.module().has_separate_working_tree() else: assert osp.isfile(module_repo_path) assert sm.module().has_separate_working_tree() assert find_submodule_git_dir( module_repo_path ) is not None, "module pointed to by .git file must be valid" # end verify submodule 'style' # test move new_sm_path = join_path_native('submodules', 'one') sm.move(new_sm_path) assert_exists(sm) # Add additional submodule level csm = sm.module().create_submodule('nested-submodule', join_path_native( 'nested-submodule', 'working-tree'), url=self._small_repo_url()) sm.module().index.commit("added nested submodule") sm_head_commit = sm.module().commit() assert_exists(csm) # Fails because there are new commits, compared to the remote we cloned from self.failUnlessRaises(InvalidGitRepositoryError, sm.remove, dry_run=True) assert_exists(sm) assert sm.module().commit() == sm_head_commit assert_exists(csm) # rename nested submodule # This name would move itself one level deeper - needs special handling internally new_name = csm.name + '/mine' assert csm.rename(new_name).name == new_name assert_exists(csm) assert csm.repo.is_dirty( index=True, working_tree=False), "index must contain changed .gitmodules file" csm.repo.index.commit("renamed module") # keep_going evaluation rsm = parent.submodule_update() assert_exists(sm) assert_exists(csm) with csm.config_writer().set_value('url', 'bar'): pass csm.repo.index.commit( "Have to commit submodule change for algorithm to pick it up") assert csm.url == 'bar' self.failUnlessRaises(Exception, rsm.update, recursive=True, to_latest_revision=True, progress=prog) assert_exists(csm) rsm.update(recursive=True, to_latest_revision=True, progress=prog, keep_going=True) # remove sm_module_path = sm.module().git_dir for dry_run in (True, False): sm.remove(dry_run=dry_run, force=True) assert_exists(sm, value=dry_run) assert osp.isdir(sm_module_path) == dry_run