def test_node_get_history_with_empty_commit(self): # regression test for #11328 path = os.path.join(self.repos_path, '.git') DbRepositoryProvider(self.env).add_repository('gitrepos', path, 'git') repos = RepositoryManager(self.env).get_repository('gitrepos') parent_rev = repos.youngest_rev self._git_commit('-m', 'ticket:11328', '--allow-empty', date=datetime(2013, 10, 15, 9, 46, 27)) repos.sync() rev = repos.youngest_rev node = repos.get_node('', rev) self.assertEqual(rev, repos.git.last_change(rev, '')) history = list(node.get_history()) self.assertEqual(u'', history[0][0]) self.assertEqual(rev, history[0][1]) self.assertEqual(Changeset.EDIT, history[0][2]) self.assertEqual(u'', history[1][0]) self.assertEqual(parent_rev, history[1][1]) self.assertEqual(Changeset.ADD, history[1][2]) self.assertEqual(2, len(history))
def render_property(self, name, mode, context, props): """Parse svn:mergeinfo and svnmerge-* properties, converting branch names to links and providing links to the revision log for merged and eligible revisions. """ has_eligible = name in ('svnmerge-integrated', 'svn:mergeinfo') revs_label = (_('merged'), _('blocked'))[name.endswith('blocked')] revs_cols = has_eligible and 2 or None reponame = context.resource.parent.id target_path = context.resource.id repos = RepositoryManager(self.env).get_repository(reponame) target_rev = context.resource.version if has_eligible: node = repos.get_node(target_path, target_rev) branch_starts = {} for path, rev in node.get_copy_ancestry(): if path not in branch_starts: branch_starts[path] = rev + 1 rows = [] if name.startswith('svnmerge-'): sources = props[name].split() else: sources = props[name].splitlines() for line in sources: path, revs = line.split(':', 1) spath = _path_within_scope(repos.scope, path) if spath is None: continue revs = revs.strip() inheritable, non_inheritable = _partition_inheritable(revs) revs = ','.join(inheritable) deleted = False try: node = repos.get_node(spath, target_rev) resource = context.resource.parent.child('source', spath) if 'LOG_VIEW' in context.perm(resource): row = [_get_source_link(spath, context), _get_revs_link(revs_label, context, spath, revs)] if non_inheritable: non_inheritable = ','.join(non_inheritable) row.append(_get_revs_link(_('non-inheritable'), context, spath, non_inheritable, _('merged on the directory ' 'itself but not below'))) if has_eligible: first_rev = branch_starts.get(spath) if not first_rev: first_rev = node.get_branch_origin() eligible = set(xrange(first_rev or 1, target_rev + 1)) eligible -= set(Ranges(revs)) blocked = _get_blocked_revs(props, name, spath) if blocked: eligible -= set(Ranges(blocked)) if eligible: nrevs = repos._get_node_revs(spath, max(eligible), min(eligible)) eligible &= set(nrevs) eligible = to_ranges(eligible) row.append(_get_revs_link(_('eligible'), context, spath, eligible)) rows.append((False, spath, [tag.td(each) for each in row])) continue except NoSuchNode: deleted = True revs = revs.replace(',', u',\u200b') rows.append((deleted, spath, [tag.td('/' + spath), tag.td(revs, colspan=revs_cols)])) if not rows: return None rows.sort() has_deleted = rows and rows[-1][0] or None return tag(has_deleted and tag.a(_('(toggle deleted branches)'), class_='trac-toggledeleted', href='#'), tag.table(tag.tbody( [tag.tr(row, class_=deleted and 'trac-deleted' or None) for deleted, spath, row in rows]), class_='props'))
def render_property(self, name, mode, context, props): """Parse svn:mergeinfo and svnmerge-* properties, converting branch names to links and providing links to the revision log for merged and eligible revisions. """ has_eligible = name in ('svnmerge-integrated', 'svn:mergeinfo') revs_label = _('blocked') if name.endswith('blocked') else _('merged') revs_cols = 2 if has_eligible else None reponame = context.resource.parent.id target_path = context.resource.id repos = RepositoryManager(self.env).get_repository(reponame) target_rev = context.resource.version if has_eligible: node = repos.get_node(target_path, target_rev) branch_starts = {} for path, rev in node.get_copy_ancestry(): if path not in branch_starts: branch_starts[path] = rev + 1 rows = [] eligible_infos = [] if name.startswith('svnmerge-'): sources = props[name].split() else: sources = props[name].splitlines() for line in sources: path, revs = line.split(':', 1) spath = _path_within_scope(repos.scope, path) if spath is None: continue revs = revs.strip() inheritable, non_inheritable = _partition_inheritable(revs) revs = ','.join(inheritable) deleted = False try: node = repos.get_node(spath, target_rev) resource = context.resource.parent.child('source', spath) if 'LOG_VIEW' in context.perm(resource): row = [ _get_source_link(spath, context), _get_revs_link(revs_label, context, spath, revs) ] if non_inheritable: non_inheritable = ','.join(non_inheritable) row.append( _get_revs_link( _('non-inheritable'), context, spath, non_inheritable, _('merged on the directory ' 'itself but not below'))) if has_eligible: first_rev = branch_starts.get(spath) if not first_rev: first_rev = node.get_branch_origin() eligible = set(xrange(first_rev or 1, target_rev + 1)) eligible -= set(Ranges(revs)) blocked = _get_blocked_revs(props, name, spath) if blocked: eligible -= set(Ranges(blocked)) if eligible: node = repos.get_node(spath, max(eligible)) eligible_infos.append((spath, node, eligible, row)) continue eligible = to_ranges(eligible) row.append( _get_revs_link(_('eligible'), context, spath, eligible)) rows.append((False, spath, [tag.td(each) for each in row])) continue except NoSuchNode: deleted = True revs = revs.replace(',', u',\u200b') rows.append( (deleted, spath, [tag.td('/' + spath), tag.td(revs, colspan=revs_cols)])) # fetch eligible revisions for each path at a time changed_revs = {} changed_nodes = [(node, min(eligible)) for spath, node, eligible, row in eligible_infos] if changed_nodes: changed_revs = repos._get_changed_revs(changed_nodes) for spath, node, eligible, row in eligible_infos: if spath in changed_revs: eligible &= set(changed_revs[spath]) else: eligible.clear() row.append( _get_revs_link(_("eligible"), context, spath, to_ranges(eligible))) rows.append((False, spath, [tag.td(each) for each in row])) if not rows: return None rows.sort() if rows and rows[-1][0]: toggledeleted = tag.a(_("(toggle deleted branches)"), class_='trac-toggledeleted', href='#') else: toggledeleted = None return tag( toggledeleted, tag.table(tag.tbody([ tag.tr(row, class_='trac-deleted' if deleted else None) for deleted, spath, row in rows ]), class_='props'))
def send_as_email(self, req, sender, recipients, subject, text, mode, *resources): """ `authname` Trac username of sender `sender` Tuple of (real name, email address) `recipients` List of (real name, email address) recipient address tuples `subject` The e-mail subject `text` The text body of the e-mail `files` List of paths to the files to send """ assert len(resources) > 0, 'Nothing to send!' mailsys = self.distributor(self.env) from_addr = sender[1] root = MIMEMultipart('related') root.set_charset(self._make_charset()) root.preamble = 'This is a multi-part message in MIME format.' headers = {} recp = [r[1] for r in recipients] headers['Subject'] = subject headers['To'] = ', '.join(recp) headers['From'] = from_addr headers['Date'] = formatdate() authname = req.authname files = [] links = [] attachments = [] mimeview = Mimeview(self.env) for r in resources: repo = RepositoryManager(self.env).get_repository(r.parent.id) n = repo.get_node(r.id, rev=r.version) files.append(n.path) f = os.path.join(repo.repos.path, n.path) if mode in (self.LINKS_ONLY, self.LINKS_ATTACHMENTS): links.append((req.abs_href.browser(repo.reponame or None, n.path, format='raw'), os.path.basename(f))) if mode in (self.ATTACHMENTS_ONLY, self.LINKS_ATTACHMENTS): content = n.get_content().read() mtype = n.get_content_type() or mimeview.get_mimetype(f, content) if not mtype: mtype = 'application/octet-stream' if '; charset=' in mtype: # What to use encoding for? mtype, encoding = mtype.split('; charset=', 1) attachments.append(os.path.basename(f)) maintype, subtype = mtype.split('/', 1) part = MIMEBase(maintype, subtype) part.set_payload(content) part.add_header('content-disposition', 'attachment', filename=os.path.basename(f)) encode_base64(part) root.attach(part) body = self._format_email(authname, sender, recipients, subject, text, mode, links, attachments) msg = MIMEText(body, 'html', 'utf-8') root.attach(msg) del root['Content-Transfer-Encoding'] for k, v in headers.items(): set_header(root, k, v) email = (from_addr, recp, root.as_string()) # Write mail to /tmp #import logging #if self.log.isEnabledFor(logging.DEBUG): # (fd, tmpname) = mkstemp() # os.write(fd, email[2]) # os.close(fd) # self.log.debug('Wrote mail from %s to %s to %s', email[0], email[1], tmpname) self.log.info('Sending mail with items %s from %s to %s', resources, from_addr, recp) try: if using_announcer: if mailsys.use_threaded_delivery: mailsys.get_delivery_queue().put(email) else: mailsys.send(*email) else: mailsys.send_email(*email) except Exception, e: raise TracError(e.message)
recipients.append(address) for f in files: # TODO: add ?rev=xxx and select correct version try: file_res = self._get_file_resource(req, realm='source', parent=repo.resource, path=f) # req.perm(file_res).require('BROWSER_VIEW') for the perm check maybe? except Exception, e: failures.append(str(e)) continue if not hasattr(file_res, 'realm') or file_res.realm != 'source': failures.append("Resources must have the 'realm' attribute") continue repo = RepositoryManager(self.env).get_repository(file_res.parent.id) n = repo.get_node(file_res.id, rev=file_res.version) if not n.isfile: failures.append("Resources must be files") continue to_send.append(file_res) if not failures: try: files = self.send_as_email(req, sender, recipients, subject, message, mode, *to_send) except Exception, e: files = [] failures.append(str(e)) else: files = [] response = dict(files=files, recipients=[x[1] for x in recipients], failures=failures) if failures != []:
def render_property(self, name, mode, context, props): """Parse svn:mergeinfo and svnmerge-* properties, converting branch names to links and providing links to the revision log for merged and eligible revisions. """ has_eligible = name in ("svnmerge-integrated", "svn:mergeinfo") revs_label = _("blocked") if name.endswith("blocked") else _("merged") revs_cols = 2 if has_eligible else None reponame = context.resource.parent.id target_path = context.resource.id repos = RepositoryManager(self.env).get_repository(reponame) target_rev = context.resource.version if has_eligible: node = repos.get_node(target_path, target_rev) branch_starts = {} for path, rev in node.get_copy_ancestry(): if path not in branch_starts: branch_starts[path] = rev + 1 rows = [] if name.startswith("svnmerge-"): sources = props[name].split() else: sources = props[name].splitlines() for line in sources: path, revs = line.split(":", 1) spath = _path_within_scope(repos.scope, path) if spath is None: continue revs = revs.strip() inheritable, non_inheritable = _partition_inheritable(revs) revs = ",".join(inheritable) deleted = False try: node = repos.get_node(spath, target_rev) resource = context.resource.parent.child("source", spath) if "LOG_VIEW" in context.perm(resource): row = [_get_source_link(spath, context), _get_revs_link(revs_label, context, spath, revs)] if non_inheritable: non_inheritable = ",".join(non_inheritable) row.append( _get_revs_link( _("non-inheritable"), context, spath, non_inheritable, _("merged on the directory " "itself but not below"), ) ) if has_eligible: first_rev = branch_starts.get(spath) if not first_rev: first_rev = node.get_branch_origin() eligible = set(xrange(first_rev or 1, target_rev + 1)) eligible -= set(Ranges(revs)) blocked = _get_blocked_revs(props, name, spath) if blocked: eligible -= set(Ranges(blocked)) if eligible: nrevs = repos._get_node_revs(spath, max(eligible), min(eligible)) eligible &= set(nrevs) eligible = to_ranges(eligible) row.append(_get_revs_link(_("eligible"), context, spath, eligible)) rows.append((False, spath, [tag.td(each) for each in row])) continue except NoSuchNode: deleted = True revs = revs.replace(",", u",\u200b") rows.append((deleted, spath, [tag.td("/" + spath), tag.td(revs, colspan=revs_cols)])) if not rows: return None rows.sort() has_deleted = rows[-1][0] if rows else None return tag( has_deleted and tag.a(_("(toggle deleted branches)"), class_="trac-toggledeleted", href="#"), tag.table( tag.tbody([tag.tr(row, class_="trac-deleted" if deleted else None) for deleted, spath, row in rows]), class_="props", ), )