def test_iter_nodes(self): self._git_init(data=False) self._git_fast_import(self._data_iter_nodes) self._add_repository('gitrepos') repos = self._repomgr.get_repository('gitrepos') repos.sync() mod = BrowserModule(self.env) root_node = repos.get_node('') nodes = list(mod._iter_nodes(root_node)) self.assertEqual(['79dff4ccf842f8e2d2da2ee3e7a2149df63b099b'] * 7, [node.rev for node in nodes]) self.assertEqual([ ('79dff4ccf842f8e2d2da2ee3e7a2149df63b099b', ''), ('64e12f96b6b3040cd9edc225734ab2b26a03758b', 'A'), ('64e12f96b6b3040cd9edc225734ab2b26a03758b', 'A/a1.txt'), ('67fdcf11e2d083b123b9a79be4fce0600f313f81', 'A/a2.txt'), ('42fbe758709b2a65aba33e56b2f53cd126c190e3', 'B'), ('998bf23843c8fd982bbc23f88ec33c4d08114557', 'B/b1.txt'), ('42fbe758709b2a65aba33e56b2f53cd126c190e3', 'B/b2.txt'), ], [(node.created_rev, node.path) for node in nodes]) root_node = repos.get_node('', '86387120095e9e43573bce61b9da70a8c5d1c1b9') nodes = list(mod._iter_nodes(root_node)) self.assertEqual(['86387120095e9e43573bce61b9da70a8c5d1c1b9'] * 7, [node.rev for node in nodes]) self.assertEqual([ ('86387120095e9e43573bce61b9da70a8c5d1c1b9', ''), ('64e12f96b6b3040cd9edc225734ab2b26a03758b', 'A'), ('64e12f96b6b3040cd9edc225734ab2b26a03758b', 'A/a1.txt'), ('67fdcf11e2d083b123b9a79be4fce0600f313f81', 'A/a2.txt'), ('24d94dc08eb77438e4ead192b3f7d1c7bdf1a9e1', 'B'), ('998bf23843c8fd982bbc23f88ec33c4d08114557', 'B/b1.txt'), ('24d94dc08eb77438e4ead192b3f7d1c7bdf1a9e1', 'B/b2.txt'), ], [(node.created_rev, node.path) for node in nodes]) root_commit = 'c5b01c74e125aa034a1d4ae31dc16f1897a73779' root_node = repos.get_node('', root_commit) nodes = list(mod._iter_nodes(root_node)) self.assertEqual([root_commit] * 7, [node.rev for node in nodes]) self.assertEqual([ (root_commit, ''), (root_commit, 'A'), (root_commit, 'A/a1.txt'), (root_commit, 'A/a2.txt'), (root_commit, 'B'), (root_commit, 'B/b1.txt'), (root_commit, 'B/b2.txt'), ], [(node.created_rev, node.path) for node in nodes])
def _test_on_empty_repos(self, cached_repository): self.env.config.set('git', 'persistent_cache', 'false') self.env.config.set('git', 'cached_repository', 'true' if cached_repository else 'false') self._git_init(data=False, bare=True) self._add_repository(bare=True) repos = self._repomgr.get_repository('gitrepos') if cached_repository: # call sync() thrice with empty repository (#11851) for i in xrange(3): repos.sync() rows = self.env.db_query("SELECT value FROM repository " "WHERE id=%s AND name=%s", (repos.id, 'youngest_rev')) self.assertEqual('', rows[0][0]) else: repos.sync() youngest_rev = repos.youngest_rev self.assertIsNone(youngest_rev) self.assertIsNone(repos.oldest_rev) self.assertIsNone(repos.normalize_rev('')) self.assertIsNone(repos.normalize_rev(None)) self.assertIsNone(repos.display_rev('')) self.assertIsNone(repos.display_rev(None)) self.assertIsNone(repos.short_rev('')) self.assertIsNone(repos.short_rev(None)) node = repos.get_node('/', youngest_rev) self.assertEqual([], list(node.get_entries())) self.assertEqual([], list(node.get_history())) self.assertRaises(NoSuchNode, repos.get_node, '/path', youngest_rev) req = MockRequest(self.env, path_info='/browser/gitrepos') browser_mod = BrowserModule(self.env) self.assertTrue(browser_mod.match_request(req)) rv = browser_mod.process_request(req) self.assertEqual('browser.html', rv[0]) self.assertIsNone(rv[1]['rev']) req = MockRequest(self.env, path_info='/log/gitrepos') log_mod = LogModule(self.env) self.assertTrue(log_mod.match_request(req)) rv = log_mod.process_request(req) self.assertEqual('revisionlog.html', rv[0]) self.assertEqual([], rv[1]['items'])
def _test_on_empty_repos(self, cached_repository): self.env.config.set('git', 'persistent_cache', 'false') self.env.config.set('git', 'cached_repository', 'true' if cached_repository else 'false') self._git_init(data=False, bare=True) self._add_repository(bare=True) repos = self._repomgr.get_repository('gitrepos') if cached_repository: # call sync() thrice with empty repository (#11851) for i in xrange(3): repos.sync() rows = self.env.db_query("SELECT value FROM repository " "WHERE id=%s AND name=%s", (repos.id, 'youngest_rev')) self.assertEqual('', rows[0][0]) else: repos.sync() youngest_rev = repos.youngest_rev self.assertEqual(None, youngest_rev) self.assertEqual(None, repos.oldest_rev) self.assertEqual(None, repos.normalize_rev('')) self.assertEqual(None, repos.normalize_rev(None)) node = repos.get_node('/', youngest_rev) self.assertEqual([], list(node.get_entries())) self.assertEqual([], list(node.get_history())) self.assertRaises(NoSuchNode, repos.get_node, '/path', youngest_rev) req = self._create_req(path_info='/browser/gitrepos') browser_mod = BrowserModule(self.env) self.assertTrue(browser_mod.match_request(req)) rv = browser_mod.process_request(req) self.assertEqual('browser.html', rv[0]) self.assertEqual(None, rv[1]['rev']) req = self._create_req(path_info='/log/gitrepos') log_mod = LogModule(self.env) self.assertTrue(log_mod.match_request(req)) rv = log_mod.process_request(req) self.assertEqual('revisionlog.html', rv[0]) self.assertEqual([], rv[1]['items'])
def _render_html(self, req, repos, chgset, restricted, xhr, data): """HTML version""" data['restricted'] = restricted browser = BrowserModule(self.env) if chgset: # Changeset Mode (possibly restricted on a path) path, rev = data['new_path'], data['new_rev'] # -- getting the change summary from the Changeset.get_changes def get_changes(): for npath, kind, change, opath, orev in chgset.get_changes(): old_node = new_node = None if (restricted and not (npath == path or # same path npath.startswith(path + '/') or # npath is below path.startswith(npath + '/'))): # npath is above continue if change != Changeset.ADD: old_node = repos.get_node(opath, orev) if change != Changeset.DELETE: new_node = repos.get_node(npath, rev) yield old_node, new_node, kind, change def _changeset_title(rev): if restricted: return _('Changeset %(id)s for %(path)s', id=rev, path=path) else: return _('Changeset %(id)s', id=rev) data['changeset'] = chgset title = _changeset_title(rev) # Support for revision properties (#2545) context = Context.from_request(req, 'changeset', chgset.rev) revprops = chgset.get_properties() data['properties'] = browser.render_properties('revprop', context, revprops) oldest_rev = repos.oldest_rev if chgset.rev != oldest_rev: if restricted: prev = repos.get_node(path, rev).get_previous() if prev: prev_path, prev_rev = prev[:2] if prev_rev: prev_href = req.href.changeset(prev_rev, prev_path) else: prev_path = prev_rev = None else: add_link(req, 'first', req.href.changeset(oldest_rev), _('Changeset %(id)s', id=oldest_rev)) prev_path = data['old_path'] prev_rev = repos.previous_rev(chgset.rev) if prev_rev: prev_href = req.href.changeset(prev_rev) if prev_rev: add_link(req, 'prev', prev_href, _changeset_title(prev_rev)) youngest_rev = repos.youngest_rev if str(chgset.rev) != str(youngest_rev): if restricted: next_rev = repos.next_rev(chgset.rev, path) if next_rev: if repos.has_node(path, next_rev): next_href = req.href.changeset(next_rev, path) else: # must be a 'D'elete or 'R'ename, show full cset next_href = req.href.changeset(next_rev) else: add_link(req, 'last', req.href.changeset(youngest_rev), _('Changeset %(id)s', id=youngest_rev)) next_rev = repos.next_rev(chgset.rev) if next_rev: next_href = req.href.changeset(next_rev) if next_rev: add_link(req, 'next', next_href, _changeset_title(next_rev)) else: # Diff Mode # -- getting the change summary from the Repository.get_changes def get_changes(): for d in repos.get_changes( new_path=data['new_path'], new_rev=data['new_rev'], old_path=data['old_path'], old_rev=data['old_rev']): yield d title = self.title_for_diff(data) data['changeset'] = False data['title'] = title if 'BROWSER_VIEW' not in req.perm: return def node_info(node, annotated): return {'path': node.path, 'rev': node.rev, 'shortrev': repos.short_rev(node.rev), 'href': req.href.browser(node.created_path, rev=node.created_rev, annotate=annotated and 'blame' or \ None), 'title': (_('Show revision %(rev)s of this file in browser', rev=node.rev))} # Reminder: node.path may not exist at node.rev # as long as node.rev==node.created_rev # ... and data['old_rev'] may have nothing to do # with _that_ node specific history... options = data['diff']['options'] def _prop_changes(old_node, new_node): old_source = Resource('source', old_node.created_path, version=old_node.created_rev) new_source = Resource('source', new_node.created_path, version=new_node.created_rev) old_props = new_props = [] if 'FILE_VIEW' in req.perm(old_source): old_props = old_node.get_properties() if 'FILE_VIEW' in req.perm(new_source): new_props = new_node.get_properties() old_ctx = Context.from_request(req, old_source) new_ctx = Context.from_request(req, new_source) changed_properties = [] if old_props != new_props: for k, v in sorted(old_props.items()): new = old = diff = None if not k in new_props: old = v # won't be displayed, no need to render it elif v != new_props[k]: diff = self.render_property_diff( k, old_ctx, old_props, new_ctx, new_props, options) if not diff: old = browser.render_property(k, 'changeset', old_ctx, old_props) new = browser.render_property(k, 'changeset', new_ctx, new_props) if new or old or diff: changed_properties.append({'name': k, 'old': old, 'new': new, 'diff': diff}) for k, v in sorted(new_props.items()): if not k in old_props: new = browser.render_property(k, 'changeset', new_ctx, new_props) if new is not None: changed_properties.append({'name': k, 'new': new, 'old': None}) return changed_properties def _estimate_changes(old_node, new_node): old_size = old_node.get_content_length() new_size = new_node.get_content_length() return old_size + new_size def _content_changes(old_node, new_node): """Returns the list of differences. The list is empty when no differences between comparable files are detected, but the return value is None for non-comparable files. """ mview = Mimeview(self.env) if mview.is_binary(old_node.content_type, old_node.path): return None if mview.is_binary(new_node.content_type, new_node.path): return None old_content = old_node.get_content().read() if mview.is_binary(content=old_content): return None new_content = new_node.get_content().read() if mview.is_binary(content=new_content): return None old_content = mview.to_unicode(old_content, old_node.content_type) new_content = mview.to_unicode(new_content, new_node.content_type) if old_content != new_content: context = options.get('contextlines', 3) if context < 0: context = None tabwidth = self.config['diff'].getint('tab_width') or \ self.config['mimeviewer'].getint('tab_width', 8) ignore_blank_lines = options.get('ignoreblanklines') ignore_case = options.get('ignorecase') ignore_space = options.get('ignorewhitespace') return diff_blocks(old_content.splitlines(), new_content.splitlines(), context, tabwidth, ignore_blank_lines=ignore_blank_lines, ignore_case=ignore_case, ignore_space_changes=ignore_space) else: return [] if 'FILE_VIEW' in req.perm: diff_bytes = diff_files = 0 if self.max_diff_bytes or self.max_diff_files: for old_node, new_node, kind, change in get_changes(): if change in Changeset.DIFF_CHANGES and kind == Node.FILE: diff_files += 1 diff_bytes += _estimate_changes(old_node, new_node) show_diffs = (not self.max_diff_files or \ diff_files <= self.max_diff_files) and \ (not self.max_diff_bytes or \ diff_bytes <= self.max_diff_bytes or \ diff_files == 1) else: show_diffs = False # XHR is used for blame support: display the changeset view without # the navigation and with the changes concerning the annotated file annotated = False if xhr: show_diffs = False annotated = repos.normalize_path(req.args.get('annotate')) has_diffs = False filestats = self._prepare_filestats() changes = [] files = [] for old_node, new_node, kind, change in get_changes(): props = [] diffs = [] show_entry = change != Changeset.EDIT show_diff = show_diffs or (new_node and new_node.path == annotated) if change in Changeset.DIFF_CHANGES and 'FILE_VIEW' in req.perm: assert old_node and new_node props = _prop_changes(old_node, new_node) if props: show_entry = True if kind == Node.FILE and show_diff: diffs = _content_changes(old_node, new_node) if diffs != []: if diffs: has_diffs = True # elif None (means: manually compare to (previous)) show_entry = True if show_entry or not show_diff: info = {'change': change, 'old': old_node and node_info(old_node, annotated), 'new': new_node and node_info(new_node, annotated), 'props': props, 'diffs': diffs} files.append(new_node and new_node.path or \ old_node and old_node.path or '') filestats[change] += 1 if change in Changeset.DIFF_CHANGES: if chgset: href = req.href.changeset(new_node.rev, new_node.path) title = _('Show the changeset %(id)s restricted to ' '%(path)s', id=new_node.rev, path=new_node.path) else: href = req.href.changeset( new_node.created_rev, new_node.created_path, old=old_node.created_rev, old_path=old_node.created_path) title = _('Show the %(range)s differences restricted ' 'to %(path)s', range='r%s:%s' % (old_node.rev, new_node.rev), path=new_node.path) info['href'] = href info['title'] = old_node and title if change in Changeset.DIFF_CHANGES and not show_diff: info['hide_diff'] = True else: info = None changes.append(info) # the sequence should be immutable data.update({'has_diffs': has_diffs, 'changes': changes, 'xhr': xhr, 'filestats': filestats, 'annotated': annotated, 'files': files, 'location': self._get_parent_location(files), 'longcol': 'Revision', 'shortcol': 'r'}) if xhr: # render and return the content only stream = Chrome(self.env).render_template(req, 'changeset.html', data, fragment=True) content = stream.select('//div[@id="content"]') str_content = content.render('xhtml') req.send_header('Content-Length', len(str_content)) req.end_headers() req.write(str_content) raise RequestDone return data
def get_navigation_items(self, req): for category, name, text in \ BrowserModule.get_navigation_items(self, req): if str(text) == str(tag.a(_('Browse Source'), href=req.href.browser())): yield (category, name, tag.a('Source', href=req.href.browser()))
def __init__(self): BrowserModule.__init__(self, self.compmgr)