def check_subversion_version(): """Check that Subversion is compatible. """ # Installed ? from subvertpy import ra, __version__ as subvertpy_version ra_version = ra.version() if (ra_version[1] >= 5 and getattr(ra, 'SVN_REVISION', None) and 27729 <= ra.SVN_REVISION < 31470): raise DependencyNotPresent( "subvertpy", 'bzr-svn: Installed Subversion has buggy svn.ra.get_log() ' 'implementation, please install newer.') from breezy.trace import mutter versions = ["Subversion %d.%d.%d (%s)" % ra_version] if getattr(ra, "api_version", None) is not None and ra.api_version() != ra_version: versions.append("Subversion API %d.%d.%d (%s)" % ra.api_version()) versions.append("subvertpy %d.%d.%d" % subvertpy_version) mutter("bzr-svn: using " + ", ".join(versions)) if subvertpy_version < subvertpy_minimum_version: raise DependencyNotPresent( "subvertpy", "bzr-svn: at least subvertpy %d.%d.%d is required, %d.%d.%d is installed." % (subvertpy_minimum_version + subvertpy_version))
def expand(self, begin, todo): """Expand :param begin: List of path elements currently opened. :param todo: List of path elements to still evaluate (including wildcards) """ mutter('expand branches: %r, %r', begin, todo) path = u"/".join(begin) if (self.project is not None and not self.project.startswith(path) and not path.startswith(self.project)): return [] # If all elements have already been handled, just check the path exists if len(todo) == 0: revnum = self.get_latest_change(path) if revnum is not None: return [(path, None, int(revnum))] else: return [] # Not a wildcard? Just expand next bits if todo[0] != u"*": return self.expand(begin + [todo[0]], todo[1:]) children = self.get_children(path) if children is None: return [] ret = [] with ui.ui_factory.nested_progress_bar() as pb: for idx, (c, has_props, revnum) in enumerate(children): pb.update("browsing branches", idx, len(children)) if len(todo) == 1: # Last path element, so return directly ret.append((u"/".join(begin + [c]), has_props, revnum)) else: ret += self.expand(begin + [c], todo[1:]) return ret
def __init__(self, workingtree): mutter("opening basistree for %r", workingtree) self.workingtree = workingtree self._bzr_inventory = None self._repository = workingtree.branch.repository self.id_map = self.workingtree.basis_idmap self.mapping = self.workingtree.branch.mapping self._real_tree = None
def test_fetch_odd(self): repos_url = self.make_repository('d') dc = self.get_commit_editor(repos_url) trunk = dc.add_dir("trunk") trunk.add_file("trunk/hosts").modify() dc.close() dc = self.get_commit_editor(repos_url) trunk = dc.open_dir("trunk") trunk.open_file("trunk/hosts").modify() dc.close() dc = self.get_commit_editor(repos_url) dc.open_file("trunk/hosts").modify() dc.close() dc = self.get_commit_editor(repos_url) dc.add_dir("branches") dc.close() dc = self.get_commit_editor(repos_url) branches = dc.open_dir("branches") branches.add_dir("branches/foobranch", "trunk") dc.close() dc = self.get_commit_editor(repos_url) branches = dc.open_dir("branches") foobranch = branches.open_dir("branches/foobranch") foobranch.open_file("branches/foobranch/hosts").modify() dc.close() os.mkdir("new") url = repos_url + "/branches/foobranch" mutter('open %r' % url) olddir = ControlDir.open(url) newdir = olddir.sprout("new") newbranch = newdir.open_branch() oldbranch = olddir.open_branch() uuid = olddir.find_repository().uuid tree = newbranch.repository.revision_tree( oldbranch.generate_revision_id(6)) transaction = newbranch.repository.get_transaction() with newbranch.repository.lock_read(): texts = newbranch.repository.texts host_fileid = tree.path2id("hosts") mapping = oldbranch.repository.get_mapping() self.assertVersionsPresentEquals(texts, host_fileid, [ mapping.revision_id_foreign_to_bzr((uuid, u"trunk", 1)), mapping.revision_id_foreign_to_bzr((uuid, u"trunk", 2)), mapping.revision_id_foreign_to_bzr((uuid, u"trunk", 3)), oldbranch.generate_revision_id(6) ])
def copy_content(self, revision_id=None, basis=None): """See InterRepository.copy_content. Partial implementation of that. To date the basis parameter is not supported. """ with self.lock_write(): if basis is not None: trace.mutter('Ignoring basis argument %r', basis) self.target.fetch(self.source, revision_id=revision_id)
def _invoke(self, command): trace.mutter('Will msgmerge: %s' % (command,)) # We use only absolute paths so we don't care about the cwd proc = subprocess.Popen(cmdline.split(command), stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) out, err = proc.communicate() return proc.returncode, out, err
def repository_guess_scheme(repository, last_revnum, branch_path=None): with ui.ui_factory.nested_progress_bar() as pb: (guessed_scheme, scheme) = guess_scheme_from_history( repository._log.iter_changes( None, last_revnum, max(0, last_revnum - GUESS_SAMPLE_SIZE)), last_revnum, branch_path) mutter("Guessed branching scheme: %r, guess scheme to use: %r" % (guessed_scheme, scheme)) return (guessed_scheme, scheme)
def set_file_attr_hidden(path): """Set file attributes to hidden if possible""" if has_win32file: SetFileAttributes = win32file.SetFileAttributesW try: SetFileAttributes(path, win32file.FILE_ATTRIBUTE_HIDDEN) except pywintypes.error as e: from . import trace trace.mutter('Unable to set hidden attribute on %r: %s', path, e)
def _get_realm(self, credentials): if credentials.get('port') is None: import socket try: credentials['port'] = socket.getservbyname(credentials['scheme']) except socket.error: mutter("Unable to look up default port for %(scheme)s" % credentials) return None return "<%(scheme)s://%(host)s:%(port)s> %(realm)s" % credentials
def get_svn_username(self, realm, may_save): """Look up a Subversion user name in the Bazaar authentication cache. :param realm: Authentication realm (optional) :param may_save: Whether or not the username should be saved. """ mutter("Obtaining username for SVN connection") username = self.get_user(self.scheme, host=self.host, path=self.path, realm=realm, ask=True) return (username.encode('utf-8'), False)
def replay_range(self, start_revision, end_revision, low_water_mark, cbs, send_deltas=True): mutter("svn replay-range %d -> %d (low water mark: %d)" % (start_revision, end_revision, low_water_mark)) return self.actual.replay_range(start_revision, end_revision, low_water_mark, cbs, send_deltas)
def test_commit_remove(self): wt = self.newdir.open_workingtree() self.build_tree({'dc/foob': "data"}) wt.add("foob") wt.commit(message="data") wt.remove(["foob"]) wt.commit(message="doe") self.olddir.open_branch().pull(self.newdir.open_branch()) paths = self.client_log(self.repos_url, 3, 0)[3][0] mutter('paths %r' % paths) self.assertEquals('D', paths["/foob"][0])
def parse_merge_property(line): """Parse a bzr:merge property value. :param line: Line to parse :return: List of revisions merged """ if ' ' in line: mutter('invalid revision id %r in merged property, skipping', line) return () return tuple(filter(lambda x: x != "", line.split("\t")))
def get_roundtrip_ancestor_revids(fileprops): for propname, propvalue in fileprops.items(): if not propname.startswith(SVN_PROP_BZR_REVISION_ID): continue mapping_name = propname[len(SVN_PROP_BZR_REVISION_ID):] for line in propvalue.splitlines(): try: (revno, revid) = parse_revid_property(line) yield (revid, revno, mapping_name) except errors.InvalidPropertyValue as ie: mutter(str(ie))
def _map_property(self, name, value): if name == "svn:eol-style": if value in eol_style: return ("eol", eol_style[value]) mutter("Unknown svn:eol-style setting '%r'", value) return None elif name == "svn:keywords": return ("svn-keywords", value) else: # Unknown or boring setting return None
def helper(self, param=''): self._prepare_tree() # change dir # revert to default revision for file in subdir does work os.chdir('dir') mutter('cd dir\n') self.assertEqual('1\n', self.run_bzr('revno')[0]) self.run_bzr('revert %s file' % param) with open('file', 'rb') as f: self.assertEqual(b'spam', f.read())
def set_file_attr_hidden(path): """Set file attributes to hidden if possible""" from ctypes.wintypes import BOOL, DWORD, LPCWSTR _kernel32 = ctypes.windll.kernel32 # <https://docs.microsoft.com/windows/desktop/api/fileapi/nf-fileapi-setfileattributesw> _SetFileAttributesW = ctypes.WINFUNCTYPE(BOOL, LPCWSTR, DWORD)( ("SetFileAttributesW", _kernel32)) FILE_ATTRIBUTE_HIDDEN = 2 if not SetFileAttributes(path, FILE_ATTRIBUTE_HIDDEN): e = ctypes.WinError() from . import trace trace.mutter('Unable to set hidden attribute on %r: %s', path, e)
def _run_command(command, basedir, msg, error_msg, not_installed_msg=None, env=None, success_exit_codes=None, indata=None): """ Run a command in a subprocess. :param command: list with command and parameters :param msg: message to display to the user :param error_msg: message to display if something fails. :param not_installed_msg: the message to display if the command isn't available. :param env: Optional environment to use rather than os.environ. :param success_exit_codes: Exit codes to consider succesfull, defaults to [0]. :param indata: Data to write to standard input """ def subprocess_setup(): signal.signal(signal.SIGPIPE, signal.SIG_DFL) trace.note(msg) # Hide output if -q is in use. quiet = trace.is_quiet() if quiet: kwargs = {"stderr": subprocess.STDOUT, "stdout": subprocess.PIPE} else: kwargs = {} if env is not None: kwargs["env"] = env trace.mutter("running: %r", command) try: proc = subprocess.Popen(command, cwd=basedir, stdin=subprocess.PIPE, preexec_fn=subprocess_setup, **kwargs) except OSError as e: if e.errno != errno.ENOENT: raise if not_installed_msg is None: raise raise MissingDependency(msg=not_installed_msg) output = proc.communicate(indata) if success_exit_codes is None: success_exit_codes = [0] if proc.returncode not in success_exit_codes: if quiet: raise errors.BzrCommandError("%s: %s" % (error_msg, output)) else: raise errors.BzrCommandError(error_msg)
def set_file_attr_hidden(path): """Set file attributes to hidden if possible""" from ctypes.wintypes import BOOL, DWORD, LPWSTR # <https://docs.microsoft.com/windows/desktop/api/fileapi/nf-fileapi-setfileattributesw> SetFileAttributes = ctypes.windll.kernel32.SetFileAttributesW SetFileAttributes.argtypes = LPWSTR, DWORD SetFileAttributes.restype = BOOL FILE_ATTRIBUTE_HIDDEN = 2 if not SetFileAttributes(path, FILE_ATTRIBUTE_HIDDEN): e = ctypes.WinError() from . import trace trace.mutter('Unable to set hidden attribute on %r: %s', path, e)
def copy_content_into(self, revision_id=None): if revision_id is None: revision_id = self.source.last_revision() with self.source.lock_read(): with self.target.lock_write(): self._push(revision_id, overwrite=True, push_metadata=True) try: parent = self.source.get_parent() except InaccessibleParent as e: trace.mutter('parent was not accessible to copy: %s', e) else: if parent: self.target.set_parent(parent)
def change_prop(self, name, value): self.tree.file_properties[self.file_id][name] = value if name in (properties.PROP_ENTRY_COMMITTED_DATE, properties.PROP_ENTRY_LAST_AUTHOR, properties.PROP_ENTRY_LOCK_TOKEN, properties.PROP_ENTRY_COMMITTED_REV, properties.PROP_ENTRY_UUID, properties.PROP_IGNORE, properties.PROP_EXECUTABLE): pass elif name.startswith(properties.PROP_WC_PREFIX): pass elif name.startswith(properties.PROP_PREFIX): mutter('unsupported dir property %r', name)
def test_subdir_commit(self): """Test committing a subdirectory, and committing a directory.""" tree = self.make_branch_and_tree('.') b = tree.branch self.build_tree(['a/', 'b/']) def set_contents(contents): self.build_tree_contents([ ('a/one', contents), ('b/two', contents), ('top', contents), ]) set_contents(b'old contents') tree.smart_add(['.']) tree.commit('first revision') set_contents(b'new contents') mutter('start selective subdir commit') self.run_bzr(['commit', 'a', '-m', 'commit a only']) new = b.repository.revision_tree(b.get_rev_id(2)) new.lock_read() def get_text_by_path(tree, path): return tree.get_file_text(path) self.assertEqual(get_text_by_path(new, 'b/two'), b'old contents') self.assertEqual(get_text_by_path(new, 'top'), b'old contents') self.assertEqual(get_text_by_path(new, 'a/one'), b'new contents') new.unlock() # commit from here should do nothing self.run_bzr( ['commit', '.', '-m', 'commit subdir only', '--unchanged'], working_dir='a') v3 = b.repository.revision_tree(b.get_rev_id(3)) v3.lock_read() self.assertEqual(get_text_by_path(v3, 'b/two'), b'old contents') self.assertEqual(get_text_by_path(v3, 'top'), b'old contents') self.assertEqual(get_text_by_path(v3, 'a/one'), b'new contents') v3.unlock() # commit in subdirectory commits whole tree self.run_bzr(['commit', '-m', 'commit whole tree from subdir'], working_dir='a') v4 = b.repository.revision_tree(b.get_rev_id(4)) v4.lock_read() self.assertEqual(get_text_by_path(v4, 'b/two'), b'new contents') self.assertEqual(get_text_by_path(v4, 'top'), b'new contents') v4.unlock()
def guess_scheme_from_history(changed_paths, last_revnum, relpath=None): """Try to determine the best fitting branching scheme. :param changed_paths: Iterator over (branch_path, changes, revnum, revprops) as returned from LogWalker.iter_changes(). :param last_revnum: Number of entries in changed_paths. :param relpath: Branch path that should be accepted by the branching scheme as a branch. :return: Tuple with branching scheme that best matches history and branching scheme instance that best matches but also considers relpath a valid branch path. """ potentials = {} scheme_cache = {} with ui.ui_factory.nested_progress_bar() as pb: for (revpaths, revnum, revprops) in changed_paths: assert isinstance(revpaths, dict) pb.update("analyzing repository layout", last_revnum-revnum, last_revnum) if revpaths == {}: continue for path in find_commit_paths([revpaths]): scheme = guess_scheme_from_path(path) if str(scheme) not in potentials: potentials[str(scheme)] = 0 potentials[str(scheme)] += 1 scheme_cache[str(scheme)] = scheme entries = list(potentials.items()) entries.sort(key=operator.itemgetter(1)) mutter('potential branching schemes: %r' % entries) if len(entries) > 0: best_match = scheme_cache[entries[0][0]] else: best_match = None if relpath is None: if best_match is None: return (None, NoBranchingScheme()) return (best_match, best_match) for (schemename, _) in entries: scheme = scheme_cache[schemename] if scheme.is_branch(relpath): return (best_match, scheme) return (best_match, guess_scheme_from_branch_path(relpath))
def do_diff(self, revision_to_update, diff_target, versus_url, diff_editor, recurse=True, ignore_ancestry=False, text_deltas=False, depth=None): mutter("svn diff -r%d %s -> %s" % (revision_to_update, diff_target, versus_url)) return self.actual.do_diff(revision_to_update, diff_target, versus_url, diff_editor, recurse, ignore_ancestry, text_deltas)
def get_rhs_parents_fileprops(self, fileprops): bzr_merges = fileprops.get(SVN_PROP_BZR_ANCESTRY + self.name, None) if bzr_merges is not None: try: new_lines = find_new_lines(*bzr_merges) except ValueError as e: mutter(str(e)) return () if len(new_lines) != 1: mutter("unexpected number of lines in bzr merge property: %r", new_lines) return () return parse_merge_property(new_lines[0]) return ()
def push_ancestors(self, layout, project, parent_revids, lossy=False, exclude=None): """Push the ancestors of a revision. :param layout: Subversion layout :param project: Project name :param parent_revids: The revision ids of the basic ancestors to push """ present_rhs_parents = self.target.has_revisions(parent_revids[1:]) unique_ancestors = set() missing_rhs_parents = set(parent_revids[1:]) - present_rhs_parents graph = self.get_graph() for parent_revid in missing_rhs_parents: # Push merged revisions ancestors = graph.find_unique_ancestors(parent_revid, [parent_revids[0]]) unique_ancestors.update(ancestors) for x in self.get_graph().iter_topo_order(unique_ancestors): if self._target_has_revision(x, project): continue rev = self.source.get_revision(x) rhs_branch_path = determine_branch_path(rev, layout, project) mutter("pushing ancestor %r to %s", x, rhs_branch_path) if rev.parent_ids: parent_revid = rev.parent_ids[0] else: parent_revid = NULL_REVISION base_foreign_revid, base_mapping = self._get_foreign_revision_info( parent_revid) if base_foreign_revid is None: target_project = None else: (_, target_project, _, _) = layout.parse(base_foreign_revid[1]) bp = determine_branch_path(rev, layout, target_project, exclude) target_config = self._get_branch_config(bp) push_merged = (layout.push_merged_revisions(target_project) and target_config.get('push_merged_revisions')) root_action = self._get_root_action(bp, rev.parent_ids, overwrite=True, append_revisions_only=(target_config.get('append_revisions_only') or False), create_prefix=True) self.push_revision_inclusive(bp, target_config, rev, push_metadata=not lossy, push_merged=push_merged, layout=layout, project=target_project, root_action=root_action, base_foreign_info=(base_foreign_revid, base_mapping))
def test_commit_rename_remove_parent(self): wt = self.newdir.open_workingtree() self.build_tree({'dc/adir/foob': "data"}) wt.add("adir") wt.add("adir/foob") wt.commit(message="data") wt.rename_one("adir/foob", "bar") wt.remove(["adir"]) wt.commit(message="doe") self.olddir.open_branch().pull(self.newdir.open_branch()) paths = self.client_log(self.repos_url, 3, 0)[3][0] mutter('paths %r' % paths) self.assertEquals('D', paths["/adir"][0]) self.assertEquals('A', paths["/bar"][0]) self.assertEquals('/adir/foob', paths["/bar"][1]) self.assertEquals(2, paths["/bar"][2])
def test_log_transport(self): base_transport = self.get_transport('') logging_transport = transport.get_transport('log+' + base_transport.base) # operations such as mkdir are logged mutter('where are you?') logging_transport.mkdir('subdir') log = self.get_log() # GZ 2017-05-24: Used to expect abspath logged, logger needs fixing. self.assertContainsRe(log, r'mkdir subdir') self.assertContainsRe(log, ' --> None') # they have the expected effect self.assertTrue(logging_transport.has('subdir')) # and they operate on the underlying transport self.assertTrue(base_transport.has('subdir'))
def test_commit_rename_file_from_directory(self): wt = self.newdir.open_workingtree() self.build_tree({'dc/adir/foo': "data"}) wt.add("adir") wt.add("adir/foo") wt.commit(message="data") wt.rename_one("adir/foo", "bar") self.assertTrue(wt.has_filename("bar")) self.assertFalse(wt.has_filename("adir/foo")) wt.commit(message="doe") self.olddir.open_branch().pull(self.newdir.open_branch()) paths = self.client_log(self.repos_url, 3, 0)[3][0] mutter('paths %r' % paths) self.assertEquals('D', paths["/adir/foo"][0]) self.assertEquals('A', paths["/bar"][0]) self.assertEquals('/adir/foo', paths["/bar"][1]) self.assertEquals(2, paths["/bar"][2])
def set_tag(self, tag_name, tag_target): """Set a new tag in a Subversion repository.""" path = self.branch.layout.get_tag_path(tag_name, self.branch.project) parent = urlutils.dirname(path) try: (from_uuid, from_bp, from_revnum), mapping = self.repository.lookup_bzr_revision_id( tag_target, project=self.branch.project) except bzr_errors.NoSuchRevision: mutter("not setting tag %s; unknown revision %s", tag_name, tag_target) if GhostTagsNotSupported is not None: raise GhostTagsNotSupported(self.branch._format) return self._ensure_tag_parent_exists(parent) try: current_from_foreign_revid = self._lookup_tag_revmeta( path).metarev.get_foreign_revid() deletefirst = True except KeyError: current_from_foreign_revid = None deletefirst = False if current_from_foreign_revid == (from_uuid, from_bp, from_revnum): # Already present return mutter("setting tag %s from %r (deletefirst: %r)", path, (from_uuid, from_bp, from_revnum), deletefirst) conn = self.repository.svn_transport.get_connection(parent) try: with svn_errors.convert_svn_error( conn.get_commit_editor)(self._revprops( "Add tag %s" % tag_name.encode("utf-8"), {tag_name.encode("utf-8"): tag_target})) as ci: root = ci.open_root() if deletefirst: root.delete_entry(urlutils.basename(path)) tag_dir = root.add_directory( urlutils.basename(path), urlutils.join(self.repository.base, from_bp), from_revnum) tag_dir.close() root.close() # FIXME: This shouldn't have to remove the entire cache, just update it self.repository._clear_cached_state() finally: self.repository.svn_transport.add_connection(conn)