def _format_html(self, event): ticket = event.target short_changes = {} long_changes = {} chrome = Chrome(self.env) for field, old_value in event.changes.items(): new_value = ticket[field] if (new_value and '\n' in new_value) or \ (old_value and '\n' in old_value): long_changes[field.capitalize()] = HTML( "<pre>\n%s\n</pre>" % ( '\n'.join( diff_cleanup( difflib.unified_diff( wrap(old_value, cols=60).split('\n'), wrap(new_value, cols=60).split('\n'), lineterm='', n=3 ) ) ) ) ) else: short_changes[field.capitalize()] = (old_value, new_value) data = dict( ticket = ticket, author = event.author, header = self._header_fields(ticket), comment = event.comment, category = event.category, ticket_link = self.env.abs_href('ticket', ticket.id), project_name = self.env.project_name, project_desc = self.env.project_description, project_link = self.env.project_url or self.env.abs_href(), has_changes = short_changes or long_changes, long_changes = long_changes, short_changes = short_changes, attachment= event.attachment ) chrome = Chrome(self.env) dirs = [] for provider in chrome.template_providers: dirs += provider.get_templates_dirs() templates = TemplateLoader(dirs, variable_lookup='lenient') template = templates.load('ticket_email_mimic.html', cls=MarkupTemplate) if template: stream = template.generate(**data) output = stream.render() return output
def _format_html(self, event): ticket = event.target attachment = event.attachment short_changes = {} long_changes = {} chrome = Chrome(self.env) for field, old_value in event.changes.items(): new_value = ticket[field] if (new_value and '\n' in new_value) or \ (old_value and '\n' in old_value): long_changes[field.capitalize()] = HTML( "<pre>\n%s\n</pre>" % ( '\n'.join( diff_cleanup( difflib.unified_diff( wrap(old_value, cols=60).split('\n'), wrap(new_value, cols=60).split('\n'), lineterm='', n=3 ) ) ) ) ) else: short_changes[field.capitalize()] = (old_value, new_value) def wiki_to_html(event, wikitext): if wikitext is None: return "" try: req = Mock( href=Href(self.env.abs_href()), abs_href=self.env.abs_href, authname=event.author, perm=MockPerm(), chrome=dict( warnings=[], notices=[] ), args={} ) context = Context.from_request(req, event.realm, event.target.id) formatter = HtmlFormatter(self.env, context, wikitext) return formatter.generate(True) except Exception, e: raise self.log.error("Failed to render %s", repr(wikitext)) self.log.error(exception_to_unicode(e, traceback=True)) return wikitext
def _format_html(self, event): ticket = event.target short_changes = {} long_changes = {} chrome = Chrome(self.env) for field, old_value in event.changes.items(): new_value = ticket[field] if (new_value and '\n' in new_value) or \ (old_value and '\n' in old_value): long_changes[field.capitalize()] = HTML( "<pre>\n%s\n</pre>" % ( '\n'.join( diff_cleanup( difflib.unified_diff( wrap(old_value, cols=60).split('\n'), wrap(new_value, cols=60).split('\n'), lineterm='', n=3 ) ) ) ) ) else: short_changes[field.capitalize()] = (old_value, new_value) if event.comment: try: req = Mock( href=Href(self.env.abs_href()), abs_href=self.env.abs_href(), authname=event.author, perm=MockPerm(), chrome=dict( warnings=[], notices=[] ), args={} ) context = Context.from_request(req, event.realm, event.target.id) formatter = HtmlFormatter(self.env, context, event.comment) temp = formatter.generate(True) except Exception, e: self.log.error(exception_to_unicode(e, traceback=True)) temp = 'Comment in plain text: %s'%event.comment
def notify_attachment(self, ticket, attachment, added=True): """Send ticket attachment notification (untranslated)""" self.ticket = ticket self.modtime = attachment.date or datetime.now(utc) self.newticket = False self.reporter = '' self.owner = '' link = self.env.abs_href.ticket(ticket.id) summary = self.ticket['summary'] author = attachment.author # Note: no translation yet changes_body = wrap(" * Attachment \"%s\" %s." % (attachment.filename, "added" if added else "removed"), self.COLS, ' ', ' ', '\n', self.ambiwidth) + "\n" if attachment.description: changes_body += "\n" + wrap(attachment.description, self.COLS, ' ', ' ', '\n', self.ambiwidth) ticket_values = ticket.values.copy() ticket_values['id'] = ticket.id ticket_values['description'] = wrap( ticket_values.get('description', ''), self.COLS, initial_indent=' ', subsequent_indent=' ', linesep='\n', ambiwidth=self.ambiwidth) ticket_values['new'] = self.newticket ticket_values['link'] = link subject = self.format_subj(summary, False) with _translation_deactivated(ticket): self.data.update({ 'ticket_props': self.format_props(), 'ticket_body_hdr': self.format_hdr(), 'subject': subject, 'ticket': ticket_values, 'changes_body': changes_body, 'changes_descr': '', 'change': {'author': self.obfuscate_email(author)}, }) super(TicketNotifyEmail, self).notify(ticket.id, subject, author)
def test_wrap_ambiwidth_double(self): text = u'Trac は BSD ライセンスのもとで配布されて' + \ u'います。[1:]このライセンスの全文は、𠀋' + \ u'配布ファイルに含まれている[3:CОPYING]ファ' + \ u'イルと同じものが[2:オンライン]で参照でき' \ u'ます。' wrapped = u"""\ > Trac は BSD ライセンスのもとで配布されています。[1:]この | ライセンスの全文は、𠀋配布ファイルに含まれている | [3:CОPYING]ファイルと同じものが[2:オンライン]で参照でき | ます。""" self.assertEqual(wrapped, wrap(text, 59, '> ', '| ', '\n', ambiwidth=2))
def notify_attachment(self, ticket, author, filename, modtime, body): """Send ticket attachment notification (untranslated)""" t = deactivate() translated_fields = ticket.fields try: ticket.fields = TicketSystem(self.env).get_ticket_fields() self.ticket = ticket self.modtime = modtime self.newticket = False self.reporter = '' self.owner = '' link = self.env.abs_href.ticket(ticket.id) summary = self.ticket['summary'] ticket_values = ticket.values.copy() ticket_values['id'] = ticket.id wrap_kargs = { 'initial_indent': ' ', 'subsequent_indent': ' ', 'linesep': CRLF } if 'ambiwidth' in getargspec(wrap)[0]: wrap_kargs['ambiwidth'] = self.ambiwidth ticket_values['description'] = wrap( ticket_values.get('description', ''), self.COLS, **wrap_kargs ) ticket_values['new'] = self.newticket ticket_values['link'] = link subject = 'Re: ' + self.format_subj(summary) author = obfuscate_email_address(author) change = { 'author': author } self.data.update({ 'ticket_props': self.format_props(), 'ticket_body_hdr': self.format_hdr(), 'subject': subject, 'ticket': ticket_values, 'change': change, 'changes_body': body, }) NotifyEmail.notify(self, ticket.id, subject) finally: ticket.fields = translated_fields reactivate(t)
def expand_macro(self, formatter, name, args): text = '' if args: lines = args.split('\n') if lines[0].startswith('cols:'): try: width = int(lines[0][5:].strip()) lines.pop(0) except ValueError: width = 72 else: width = 72 text = wrap('\n'.join(lines), cols=width) return '<pre class="wiki">%s</pre>' % escape(text)
def render_macro(self, req, name, content): text = "" if content: lines = content.split("\n") if lines[0].startswith("cols:"): try: width = int(lines[0][5:].strip()) lines.pop(0) except ValueError: width = 72 else: width = 72 text = wrap("\n".join(lines), cols=width) return Markup("<pre class='wiki'>%s</pre>" % escape(text))
def test_wrap_ambiwidth_double(self): text = ( u"Trac は BSD ライセンスのもとで配布されて" + u"います。[1:]このライセンスの全文は、𠀋" + u"配布ファイルに含まれている[3:CОPYING]ファ" + u"イルと同じものが[2:オンライン]で参照でき" u"ます。" ) wrapped = u"""\ > Trac は BSD ライセンスのもとで配布されています。[1:]この | ライセンスの全文は、𠀋配布ファイルに含まれている | [3:CОPYING]ファイルと同じものが[2:オンライン]で参照でき | ます。""" self.assertEqual(wrapped, wrap(text, 59, "> ", "| ", "\n", ambiwidth=2))
def _do_list(self, user=None): permsys = PermissionSystem(self.env) if user: rows = [] perms = permsys.get_user_permissions(user) for action in perms: if perms[action]: rows.append((user, action)) else: rows = permsys.get_all_permissions() rows.sort() print_table(rows, [_('User'), _('Action')]) print printout(_("Available actions:")) actions = permsys.get_actions() actions.sort() text = ', '.join(actions) printout(wrap(text, initial_indent=' ', subsequent_indent=' ', linesep='\n')) print
def test_wrap_ambiwidth_single(self): text = u'Lorem ipsum dolor sit amet, consectetur adipisicing ' + \ u'elit, sed do eiusmod tempor incididunt ut labore et ' + \ u'dolore magna aliqua. Ut enim ad minim veniam, quis ' + \ u'nostrud exercitation ullamco laboris nisi ut aliquip ex ' + \ u'ea commodo consequat. Duis aute irure dolor in ' + \ u'reprehenderit in voluptate velit esse cillum dolore eu ' + \ u'fugiat nulla pariatur. Excepteur sint occaecat ' + \ u'cupidatat non proident, sunt in culpa qui officia ' + \ u'deserunt mollit anim id est laborum.' wrapped = u"""\ > Lorem ipsum dolor sit amet, consectetur adipisicing elit, | sed do eiusmod tempor incididunt ut labore et dolore | magna aliqua. Ut enim ad minim veniam, quis nostrud | exercitation ullamco laboris nisi ut aliquip ex ea | commodo consequat. Duis aute irure dolor in reprehenderit | in voluptate velit esse cillum dolore eu fugiat nulla | pariatur. Excepteur sint occaecat cupidatat non proident, | sunt in culpa qui officia deserunt mollit anim id est | laborum.""" self.assertEqual(wrapped, wrap(text, 59, '> ', '| ', '\n'))
def _format_plaintext(self, event): ticket = event.target short_changes = {} long_changes = {} changed_items = [(field, to_unicode(old_value)) for \ field, old_value in event.changes.items()] for field, old_value in changed_items: new_value = to_unicode(ticket[field]) if ('\n' in new_value) or ('\n' in old_value): long_changes[field.capitalize()] = '\n'.join( lineup(wrap(new_value, cols=67).split('\n'))) else: short_changes[field.capitalize()] = (old_value, new_value) data = dict( ticket = ticket, author = event.author, comment = event.comment, fields = self._header_fields(ticket), category = event.category, ticket_link = self._ticket_link(ticket), project_name = self.env.project_name, project_desc = self.env.project_description, project_link = self.env.project_url or self.env.abs_href(), has_changes = short_changes or long_changes, long_changes = long_changes, short_changes = short_changes, attachment= event.attachment ) chrome = Chrome(self.env) dirs = [] for provider in chrome.template_providers: dirs += provider.get_templates_dirs() templates = TemplateLoader(dirs, variable_lookup='lenient') template = templates.load('ticket_email_plaintext.txt', cls=NewTextTemplate) if template: stream = template.generate(**data) output = stream.render('text') return output
def test_wrap_ambiwidth_single(self): text = ( u"Lorem ipsum dolor sit amet, consectetur adipisicing " + u"elit, sed do eiusmod tempor incididunt ut labore et " + u"dolore magna aliqua. Ut enim ad minim veniam, quis " + u"nostrud exercitation ullamco laboris nisi ut aliquip ex " + u"ea commodo consequat. Duis aute irure dolor in " + u"reprehenderit in voluptate velit esse cillum dolore eu " + u"fugiat nulla pariatur. Excepteur sint occaecat " + u"cupidatat non proident, sunt in culpa qui officia " + u"deserunt mollit anim id est laborum." ) wrapped = u"""\ > Lorem ipsum dolor sit amet, consectetur adipisicing elit, | sed do eiusmod tempor incididunt ut labore et dolore | magna aliqua. Ut enim ad minim veniam, quis nostrud | exercitation ullamco laboris nisi ut aliquip ex ea | commodo consequat. Duis aute irure dolor in reprehenderit | in voluptate velit esse cillum dolore eu fugiat nulla | pariatur. Excepteur sint occaecat cupidatat non proident, | sunt in culpa qui officia deserunt mollit anim id est | laborum.""" self.assertEqual(wrapped, wrap(text, 59, "> ", "| ", "\n"))
def format_props(self): tkt = self.ticket fields = [f for f in tkt.fields if f['name'] not in ('summary', 'cc', 'time', 'changetime')] width = [0, 0, 0, 0] i = 0 for f in fields: if f['type'] == 'textarea': continue fname = f['name'] if not fname in tkt.values: continue fval = tkt[fname] or '' if fval.find('\n') != -1: continue if fname in ['owner', 'reporter']: fval = obfuscate_email_address(fval) idx = 2 * (i % 2) width[idx] = max(self.get_text_width(f['label']), width[idx]) width[idx + 1] = max(self.get_text_width(fval), width[idx + 1]) i += 1 width_l = width[0] + width[1] + 5 width_r = width[2] + width[3] + 5 half_cols = (self.COLS - 1) / 2 if width_l + width_r + 1 > self.COLS: if ((width_l > half_cols and width_r > half_cols) or (width[0] > half_cols / 2 or width[2] > half_cols / 2)): width_l = half_cols width_r = half_cols elif width_l > width_r: width_l = min((self.COLS - 1) * 2 / 3, width_l) width_r = self.COLS - width_l - 1 else: width_r = min((self.COLS - 1) * 2 / 3, width_r) width_l = self.COLS - width_r - 1 sep = width_l * '-' + '+' + width_r * '-' txt = sep + CRLF cell_tmp = [u'', u''] big = [] i = 0 width_lr = [width_l, width_r] for f in [f for f in fields if f['name'] != 'description']: fname = f['name'] if not tkt.values.has_key(fname): continue fval = tkt[fname] or '' if fname in ['owner', 'reporter']: fval = obfuscate_email_address(fval) if f['type'] == 'textarea' or '\n' in unicode(fval): big.append((f['label'], CRLF.join(fval.splitlines()))) else: # Note: f['label'] is a Babel's LazyObject, make sure its # __str__ method won't be called. str_tmp = u'%s: %s' % (f['label'], unicode(fval)) idx = i % 2 cell_tmp[idx] += wrap(str_tmp, width_lr[idx] - 2 + 2 * idx, (width[2 * idx] - self.get_text_width(f['label']) + 2 * idx) * ' ', 2 * ' ', CRLF) cell_tmp[idx] += CRLF i += 1 cell_l = cell_tmp[0].splitlines() cell_r = cell_tmp[1].splitlines() for i in range(max(len(cell_l), len(cell_r))): if i >= len(cell_l): cell_l.append(width_l * ' ') elif i >= len(cell_r): cell_r.append('') fmt_width = width_l - self.get_text_width(cell_l[i]) \ + len(cell_l[i]) txt += u'%-*s|%s%s' % (fmt_width, cell_l[i], cell_r[i], CRLF) if big: txt += sep for name, value in big: txt += CRLF.join(['', name + ':', value, '', '']) txt += sep return txt
def _prepare_body(self, ticket, newticket, modtime): self.ticket = ticket self.modtime = modtime self.newticket = newticket self.reporter = '' self.owner = '' link = self.env.abs_href.ticket(ticket.id) summary = self.ticket['summary'] author = None changes_body = '' changes_descr = '' change_data = {} if not self.newticket and modtime: # Ticket change from trac.ticket.web_ui import TicketModule for change in TicketModule(self.env) \ .grouped_changelog_entries(ticket, when=modtime): if not change['permanent']: # attachment with same time... continue author = change['author'] change_data.update({ 'author': self.obfuscate_email(author), 'comment': wrap(change['comment'], self.COLS, ' ', ' ', '\n', self.ambiwidth) }) link += '#comment:%s' % str(change.get('cnum', '')) for field, values in change['fields'].iteritems(): old = values['old'] new = values['new'] newv = '' if field == 'description': new_descr = wrap(new, self.COLS, ' ', ' ', '\n', self.ambiwidth) old_descr = wrap(old, self.COLS, '> ', '> ', '\n', self.ambiwidth) old_descr = old_descr.replace(2 * '\n', '\n' + '>' + '\n') cdescr = '\n' cdescr += 'Old description:' + 2 * '\n' + old_descr + \ 2 * '\n' cdescr += 'New description:' + 2 * '\n' + new_descr + \ '\n' changes_descr = cdescr elif field == 'summary': summary = "%s (was: %s)" % (new, old) elif field == 'cc': addcc, delcc = self.diff_cc(old, new) chgcc = '' if delcc: chgcc += wrap( " * cc: %s (removed)" % ', '.join(delcc), self.COLS, ' ', ' ', '\n', self.ambiwidth) + '\n' if addcc: chgcc += wrap( " * cc: %s (added)" % ', '.join(addcc), self.COLS, ' ', ' ', '\n', self.ambiwidth) + '\n' if chgcc: changes_body += chgcc else: if field in ['owner', 'reporter']: old = self.obfuscate_email(old) new = self.obfuscate_email(new) elif field in ticket.time_fields: format = ticket.fields.by_name(field).get('format') old = self.format_time_field(old, format) new = self.format_time_field(new, format) newv = new length = 7 + len(field) spacer_old, spacer_new = ' ', ' ' if len(old + new) + length > self.COLS: length = 5 if len(old) + length > self.COLS: spacer_old = '\n' if len(new) + length > self.COLS: spacer_new = '\n' chg = '* %s: %s%s%s=>%s%s' % (field, spacer_old, old, spacer_old, spacer_new, new) chg = chg.replace('\n', '\n' + length * ' ') chg = wrap(chg, self.COLS, '', length * ' ', '\n', self.ambiwidth) changes_body += ' %s%s' % (chg, '\n') if newv: change_data[field] = {'oldvalue': old, 'newvalue': new} if newticket: author = ticket['reporter'] ticket_values = ticket.values.copy() ticket_values['id'] = ticket.id ticket_values['description'] = wrap(ticket_values.get( 'description', ''), self.COLS, initial_indent=' ', subsequent_indent=' ', linesep='\n', ambiwidth=self.ambiwidth) ticket_values['new'] = self.newticket ticket_values['link'] = link subject = self.format_subj(summary, newticket) self.data.update({ 'ticket_props': self.format_props(), 'ticket_body_hdr': self.format_hdr(), 'subject': subject, 'ticket': ticket_values, 'changes_body': changes_body, 'changes_descr': changes_descr, 'change': change_data }) return author
def format_hdr(self): return '#%s: %s' % (self.ticket.id, wrap(self.ticket['summary'], self.COLS, linesep='\n', ambiwidth=self.ambiwidth))
def process_request(self, req): req.perm.assert_permission('LOG_VIEW') mode = req.args.get('mode', 'stop_on_copy') path = req.args.get('path', '/') rev = req.args.get('rev') stop_rev = req.args.get('stop_rev') format = req.args.get('format') verbose = req.args.get('verbose') limit = LOG_LIMIT repos = self.env.get_repository(req.authname) normpath = repos.normalize_path(path) rev = unicode(repos.normalize_rev(rev)) if stop_rev: stop_rev = unicode(repos.normalize_rev(stop_rev)) if repos.rev_older_than(rev, stop_rev): rev, stop_rev = stop_rev, rev req.hdf['title'] = path + ' (log)' req.hdf['log'] = { 'mode': mode, 'path': path, 'rev': rev, 'verbose': verbose, 'stop_rev': stop_rev, 'browser_href': req.href.browser(path), 'changeset_href': req.href.changeset(), 'log_href': req.href.log(path, rev=rev) } path_links = get_path_links(req.href, path, rev) req.hdf['log.path'] = path_links if path_links: add_link(req, 'up', path_links[-1]['href'], u'Répertoire parent') # The `history()` method depends on the mode: # * for ''stop on copy'' and ''follow copies'', it's `Node.history()` # * for ''show only add, delete'' it's`Repository.get_path_history()` if mode == 'path_history': def history(limit): for h in repos.get_path_history(path, rev, limit): yield h else: history = get_existing_node(req, repos, path, rev).get_history # -- retrieve history, asking for limit+1 results info = [] previous_path = repos.normalize_path(path) for old_path, old_rev, old_chg in history(limit + 1): if stop_rev and repos.rev_older_than(old_rev, stop_rev): break old_path = repos.normalize_path(old_path) item = { 'rev': str(old_rev), 'path': old_path, 'log_href': req.href.log(old_path, rev=old_rev), 'browser_href': req.href.browser(old_path, rev=old_rev), 'changeset_href': req.href.changeset(old_rev), 'restricted_href': req.href.changeset(old_rev, new_path=old_path), 'change': old_chg } if not (mode == 'path_history' and old_chg == Changeset.EDIT): info.append(item) if old_path and old_path != previous_path \ and not (mode == 'path_history' and old_path == normpath): item['copyfrom_path'] = old_path if mode == 'stop_on_copy': break if len(info) > limit: # we want limit+1 entries break previous_path = old_path if info == []: # FIXME: we should send a 404 error here raise TracError( u"Le fichier ou le répertoire '%s' n'existe pas " u"en révision %s ou pour toute révision précédente." % (path, rev), u'Chemin inexistant') def make_log_href(path, **args): link_rev = rev if rev == str(repos.youngest_rev): link_rev = None params = {'rev': link_rev, 'mode': mode, 'limit': limit} params.update(args) if verbose: params['verbose'] = verbose return req.href.log(path, **params) if len(info ) == limit + 1: # limit+1 reached, there _might_ be some more next_rev = info[-1]['rev'] next_path = info[-1]['path'] add_link( req, 'next', make_log_href(next_path, rev=next_rev), u'Journal des révisions (repartant de %s, rév. %s)' % (next_path, next_rev)) # now, only show 'limit' results del info[-1] req.hdf['log.items'] = info revs = [i['rev'] for i in info] changes = get_changes(self.env, repos, revs, verbose, req, format) if format == 'rss': # Get the email addresses of all known users email_map = {} for username, name, email in self.env.get_known_users(): if email: email_map[username] = email for cs in changes.values(): # For RSS, author must be an email address author = cs['author'] author_email = '' if '@' in author: author_email = author elif email_map.has_key(author): author_email = email_map[author] cs['author'] = author_email cs['date'] = http_date(cs['date_seconds']) elif format == 'changelog': for rev in revs: changeset = repos.get_changeset(rev) cs = changes[rev] cs['message'] = wrap(changeset.message, 70, initial_indent='\t', subsequent_indent='\t') files = [] actions = [] for path, kind, chg, bpath, brev in changeset.get_changes(): files.append(chg == Changeset.DELETE and bpath or path) actions.append(chg) cs['files'] = files cs['actions'] = actions req.hdf['log.changes'] = changes if req.args.get('format') == 'changelog': return 'log_changelog.cs', 'text/plain' elif req.args.get('format') == 'rss': return 'log_rss.cs', 'application/rss+xml' add_stylesheet(req, 'common/css/browser.css') add_stylesheet(req, 'common/css/diff.css') rss_href = make_log_href(path, format='rss', stop_rev=stop_rev) add_link(req, 'alternate', rss_href, 'RSS Feed', 'application/rss+xml', 'rss') changelog_href = make_log_href(path, format='changelog', stop_rev=stop_rev) add_link(req, 'alternate', changelog_href, 'ChangeLog', 'text/plain') return 'log.cs', None
def format_hdr(self): return '#%s: %s' % (self.ticket.id, wrap(self.ticket['summary'], self.COLS, linesep=CRLF))
def format_props(self): tkt = self.ticket fields = [f for f in tkt.fields if f["name"] not in ("summary", "cc", "time", "changetime")] width = [0, 0, 0, 0] i = 0 for f in fields: if f["type"] == "textarea": continue fname = f["name"] if not fname in tkt.values: continue fval = tkt[fname] or "" if fval.find("\n") != -1: continue if fname in ["owner", "reporter"]: fval = obfuscate_email_address(fval) idx = 2 * (i % 2) width[idx] = max(self.get_text_width(f["label"]), width[idx]) width[idx + 1] = max(self.get_text_width(fval), width[idx + 1]) i += 1 width_l = width[0] + width[1] + 5 width_r = width[2] + width[3] + 5 half_cols = (self.COLS - 1) / 2 if width_l + width_r + 1 > self.COLS: if (width_l > half_cols and width_r > half_cols) or (width[0] > half_cols / 2 or width[2] > half_cols / 2): width_l = half_cols width_r = half_cols elif width_l > width_r: width_l = min((self.COLS - 1) * 2 / 3, width_l) width_r = self.COLS - width_l - 1 else: width_r = min((self.COLS - 1) * 2 / 3, width_r) width_l = self.COLS - width_r - 1 sep = width_l * "-" + "+" + width_r * "-" txt = sep + CRLF cell_tmp = [u"", u""] big = [] i = 0 width_lr = [width_l, width_r] for f in [f for f in fields if f["name"] != "description"]: fname = f["name"] if not tkt.values.has_key(fname): continue fval = tkt[fname] or "" if fname in ["owner", "reporter"]: fval = obfuscate_email_address(fval) if f["type"] == "textarea" or "\n" in unicode(fval): big.append((f["label"], CRLF.join(fval.splitlines()))) else: # Note: f['label'] is a Babel's LazyObject, make sure its # __str__ method won't be called. str_tmp = u"%s: %s" % (f["label"], unicode(fval)) idx = i % 2 cell_tmp[idx] += wrap( str_tmp, width_lr[idx] - 2 + 2 * idx, (width[2 * idx] - self.get_text_width(f["label"]) + 2 * idx) * " ", 2 * " ", CRLF, ) cell_tmp[idx] += CRLF i += 1 cell_l = cell_tmp[0].splitlines() cell_r = cell_tmp[1].splitlines() for i in range(max(len(cell_l), len(cell_r))): if i >= len(cell_l): cell_l.append(width_l * " ") elif i >= len(cell_r): cell_r.append("") fmt_width = width_l - self.get_text_width(cell_l[i]) + len(cell_l[i]) txt += u"%-*s|%s%s" % (fmt_width, cell_l[i], cell_r[i], CRLF) if big: txt += sep for name, value in big: txt += CRLF.join(["", name + ":", value, "", ""]) txt += sep return txt
def _notify(self, ticket, newticket=True, modtime=None): self.ticket = ticket self.modtime = modtime self.newticket = newticket changes_body = '' self.reporter = '' self.owner = '' changes_descr = '' change_data = {} link = self.env.abs_href.ticket(ticket.id) summary = self.ticket['summary'] if not self.newticket and modtime: # Ticket change from trac.ticket.web_ui import TicketModule for change in TicketModule(self.env).grouped_changelog_entries( ticket, self.db, when=modtime): if not change['permanent']: # attachment with same time... continue change_data.update({ 'author': obfuscate_email_address(change['author']), 'comment': wrap(change['comment'], self.COLS, ' ', ' ', CRLF) }) link += '#comment:%s' % str(change.get('cnum', '')) for field, values in change['fields'].iteritems(): old = values['old'] new = values['new'] newv = '' if field == 'description': new_descr = wrap(new, self.COLS, ' ', ' ', CRLF) old_descr = wrap(old, self.COLS, '> ', '> ', CRLF) old_descr = old_descr.replace(2 * CRLF, CRLF + '>' + \ CRLF) cdescr = CRLF cdescr += 'Old description:' + 2 * CRLF + old_descr + \ 2 * CRLF cdescr += 'New description:' + 2 * CRLF + new_descr + \ CRLF changes_descr = cdescr elif field == 'summary': summary = "%s (was: %s)" % (new, old) elif field == 'cc': (addcc, delcc) = self.diff_cc(old, new) chgcc = '' if delcc: chgcc += wrap(" * cc: %s (removed)" % ', '.join(delcc), self.COLS, ' ', ' ', CRLF) + CRLF if addcc: chgcc += wrap(" * cc: %s (added)" % ', '.join(addcc), self.COLS, ' ', ' ', CRLF) + CRLF if chgcc: changes_body += chgcc self.prev_cc += old and self.parse_cc(old) or [] else: if field in ['owner', 'reporter']: old = obfuscate_email_address(old) new = obfuscate_email_address(new) newv = new length = 7 + len(field) spacer_old, spacer_new = ' ', ' ' if len(old + new) + length > self.COLS: length = 5 if len(old) + length > self.COLS: spacer_old = CRLF if len(new) + length > self.COLS: spacer_new = CRLF chg = '* %s: %s%s%s=>%s%s' % (field, spacer_old, old, spacer_old, spacer_new, new) chg = chg.replace(CRLF, CRLF + length * ' ') chg = wrap(chg, self.COLS, '', length * ' ', CRLF) changes_body += ' %s%s' % (chg, CRLF) if newv: change_data[field] = {'oldvalue': old, 'newvalue': new} ticket_values = ticket.values.copy() ticket_values['id'] = ticket.id ticket_values['description'] = wrap( ticket_values.get('description', ''), self.COLS, initial_indent=' ', subsequent_indent=' ', linesep=CRLF) ticket_values['new'] = self.newticket ticket_values['link'] = link subject = self.format_subj(summary) if not self.newticket: subject = 'Re: ' + subject self.data.update({ 'ticket_props': self.format_props(), 'ticket_body_hdr': self.format_hdr(), 'subject': subject, 'ticket': ticket_values, 'changes_body': changes_body, 'changes_descr': changes_descr, 'change': change_data }) NotifyEmail.notify(self, ticket.id, subject)
def _prepare_body(self, ticket, newticket, modtime): self.ticket = ticket self.modtime = modtime self.newticket = newticket self.reporter = '' self.owner = '' link = self.env.abs_href.ticket(ticket.id) summary = self.ticket['summary'] author = None changes_body = '' changes_descr = '' change_data = {} if not self.newticket and modtime: # Ticket change from trac.ticket.web_ui import TicketModule for change in TicketModule(self.env) \ .grouped_changelog_entries(ticket, when=modtime): if not change['permanent']: # attachment with same time... continue author = change['author'] change_data.update({ 'author': self.format_author(author), 'comment': wrap(change['comment'], self.COLS, ' ', ' ', '\n', self.ambiwidth) }) link += '#comment:%s' % str(change.get('cnum', '')) for field, values in change['fields'].iteritems(): old = values['old'] new = values['new'] newv = '' if field == 'description': new_descr = wrap(new, self.COLS, ' ', ' ', '\n', self.ambiwidth) old_descr = wrap(old, self.COLS, '> ', '> ', '\n', self.ambiwidth) old_descr = old_descr.replace(2 * '\n', '\n' + '>' + '\n') cdescr = '\n' cdescr += 'Old description:' + 2 * '\n' + old_descr + \ 2 * '\n' cdescr += 'New description:' + 2 * '\n' + new_descr + \ '\n' changes_descr = cdescr elif field == 'summary': summary = "%s (was: %s)" % (new, old) elif field == 'cc': addcc, delcc = self.diff_cc(old, new) chgcc = '' if delcc: chgcc += wrap(" * cc: %s (removed)" % ', '.join(delcc), self.COLS, ' ', ' ', '\n', self.ambiwidth) + '\n' if addcc: chgcc += wrap(" * cc: %s (added)" % ', '.join(addcc), self.COLS, ' ', ' ', '\n', self.ambiwidth) + '\n' if chgcc: changes_body += chgcc else: if field in ['owner', 'reporter']: old = self.format_author(old) new = self.format_author(new) elif field in ticket.time_fields: format = ticket.fields.by_name(field).get('format') old = self.format_time_field(old, format) new = self.format_time_field(new, format) newv = new length = 7 + len(field) spacer_old, spacer_new = ' ', ' ' if len(old + new) + length > self.COLS: length = 5 if len(old) + length > self.COLS: spacer_old = '\n' if len(new) + length > self.COLS: spacer_new = '\n' chg = '* %s: %s%s%s=>%s%s' % (field, spacer_old, old, spacer_old, spacer_new, new) chg = chg.replace('\n', '\n' + length * ' ') chg = wrap(chg, self.COLS, '', length * ' ', '\n', self.ambiwidth) changes_body += ' %s%s' % (chg, '\n') if newv: change_data[field] = {'oldvalue': old, 'newvalue': new} if newticket: author = ticket['reporter'] ticket_values = ticket.values.copy() ticket_values['id'] = ticket.id ticket_values['description'] = wrap( ticket_values.get('description', ''), self.COLS, initial_indent=' ', subsequent_indent=' ', linesep='\n', ambiwidth=self.ambiwidth) ticket_values['new'] = self.newticket ticket_values['link'] = link subject = self.format_subj(summary, newticket) self.data.update({ 'ticket_props': self.format_props(), 'ticket_body_hdr': self.format_hdr(), 'subject': subject, 'ticket': ticket_values, 'changes_body': changes_body, 'changes_descr': changes_descr, 'change': change_data }) return author
def notify(self, ticket, newticket=True, modtime=0): self.ticket = ticket self.modtime = modtime self.newticket = newticket self.ticket['description'] = wrap(self.ticket.values.get( 'description', ''), self.COLS, initial_indent=' ', subsequent_indent=' ', linesep=CRLF) self.reporter = '' self.owner = '' self.hdf.set_unescaped('email.ticket_props', self.format_props()) self.hdf.set_unescaped('email.ticket_body_hdr', self.format_hdr()) self.hdf['ticket.new'] = self.newticket subject = self.format_subj() link = self.env.abs_href.ticket(ticket.id) if not self.newticket: subject = 'Re: ' + subject self.hdf.set_unescaped('email.subject', subject) changes = '' if not self.newticket and modtime: # Ticket change from trac.ticket.web_ui import TicketModule for change in TicketModule(self.env).grouped_changelog_entries( ticket, self.db, when=modtime): if not change['permanent']: # attachment with same time... continue self.hdf.set_unescaped('ticket.change.author', change['author']) self.hdf.set_unescaped( 'ticket.change.comment', wrap(change['comment'], self.COLS, ' ', ' ', CRLF)) link += '#comment:%s' % str(change.get('cnum', '')) for field, values in change['fields'].iteritems(): old = values['old'] new = values['new'] pfx = 'ticket.change.%s' % field newv = '' if field == 'description': new_descr = wrap(new, self.COLS, ' ', ' ', CRLF) old_descr = wrap(old, self.COLS, '> ', '> ', CRLF) old_descr = old_descr.replace(2 * CRLF, CRLF + '>' + CRLF) cdescr = CRLF cdescr += 'Old description:' + 2 * CRLF + old_descr + 2 * CRLF cdescr += 'New description:' + 2 * CRLF + new_descr + CRLF self.hdf.set_unescaped('email.changes_descr', cdescr) elif field == 'cc': (addcc, delcc) = self.diff_cc(old, new) chgcc = '' if delcc: chgcc += wrap( " * cc: %s (removed)" % ', '.join(delcc), self.COLS, ' ', ' ', CRLF) chgcc += CRLF if addcc: chgcc += wrap( " * cc: %s (added)" % ', '.join(addcc), self.COLS, ' ', ' ', CRLF) chgcc += CRLF if chgcc: changes += chgcc self.prev_cc += old and self.parse_cc(old) or [] else: newv = new l = 7 + len(field) chg = wrap('%s => %s' % (old, new), self.COLS - l, '', l * ' ', CRLF) changes += ' * %s: %s%s' % (field, chg, CRLF) if newv: self.hdf.set_unescaped('%s.oldvalue' % pfx, old) self.hdf.set_unescaped('%s.newvalue' % pfx, newv) if changes: self.hdf.set_unescaped('email.changes_body', changes) self.ticket['link'] = link self.hdf.set_unescaped('ticket', self.ticket.values) NotifyEmail.notify(self, ticket.id, subject)
def process_request(self, req): req.perm.require('LOG_VIEW') mode = req.args.get('mode', 'stop_on_copy') path = req.args.get('path', '/') rev = req.args.get('rev') stop_rev = req.args.get('stop_rev') revs = req.args.get('revs') format = req.args.get('format') verbose = req.args.get('verbose') limit = int(req.args.get('limit') or self.default_log_limit) rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path(path) if not repos: raise ResourceNotFound( _("Repository '%(repo)s' not found", repo=reponame)) if reponame != repos.reponame: # Redirect alias qs = req.query_string req.redirect( req.href.log(repos.reponame or None, path) + (qs and '?' + qs or '')) normpath = repos.normalize_path(path) # if `revs` parameter is given, then we're restricted to the # corresponding revision ranges. # If not, then we're considering all revisions since `rev`, # on that path, in which case `revranges` will be None. revranges = None if revs: try: revranges = Ranges(revs) rev = revranges.b except ValueError: pass rev = unicode(repos.normalize_rev(rev)) display_rev = repos.display_rev # The `history()` method depends on the mode: # * for ''stop on copy'' and ''follow copies'', it's `Node.history()` # unless explicit ranges have been specified # * for ''show only add, delete'' we're using # `Repository.get_path_history()` cset_resource = repos.resource.child('changeset') if mode == 'path_history': def history(): for h in repos.get_path_history(path, rev): if 'CHANGESET_VIEW' in req.perm(cset_resource(id=h[1])): yield h elif revranges: def history(): prevpath = path expected_next_item = None ranges = list(revranges.pairs) ranges.reverse() for (a, b) in ranges: a = repos.normalize_rev(a) b = repos.normalize_rev(b) while not repos.rev_older_than(b, a): node = get_existing_node(req, repos, prevpath, b) node_history = list(node.get_history(2)) p, rev, chg = node_history[0] if repos.rev_older_than(rev, a): break # simply skip, no separator if 'CHANGESET_VIEW' in req.perm(cset_resource(id=rev)): if expected_next_item: # check whether we're continuing previous range np, nrev, nchg = expected_next_item if rev != nrev: # no, we need a separator yield (np, nrev, None) yield node_history[0] prevpath = node_history[-1][0] # follow copy b = repos.previous_rev(rev) if len(node_history) > 1: expected_next_item = node_history[-1] else: expected_next_item = None if expected_next_item: yield (expected_next_item[0], expected_next_item[1], None) else: def history(): node = get_existing_node(req, repos, path, rev) for h in node.get_history(): if 'CHANGESET_VIEW' in req.perm(cset_resource(id=h[1])): yield h # -- retrieve history, asking for limit+1 results info = [] depth = 1 previous_path = normpath count = 0 for old_path, old_rev, old_chg in history(): if stop_rev and repos.rev_older_than(old_rev, stop_rev): break old_path = repos.normalize_path(old_path) item = { 'path': old_path, 'rev': old_rev, 'existing_rev': old_rev, 'change': old_chg, 'depth': depth, } if old_chg == Changeset.DELETE: item['existing_rev'] = repos.previous_rev(old_rev, old_path) if not (mode == 'path_history' and old_chg == Changeset.EDIT): info.append(item) if old_path and old_path != previous_path and \ not (mode == 'path_history' and old_path == normpath): depth += 1 item['depth'] = depth item['copyfrom_path'] = old_path if mode == 'stop_on_copy': break elif mode == 'path_history': depth -= 1 if old_chg is None: # separator entry stop_limit = limit else: count += 1 stop_limit = limit + 1 if count >= stop_limit: break previous_path = old_path if info == []: node = get_existing_node(req, repos, path, rev) if repos.rev_older_than(stop_rev, node.created_rev): # FIXME: we should send a 404 error here raise TracError( _( "The file or directory '%(path)s' doesn't " "exist at revision %(rev)s or at any previous revision.", path=path, rev=display_rev(rev)), _('Nonexistent path')) def make_log_href(path, **args): link_rev = rev if rev == str(repos.youngest_rev): link_rev = None params = {'rev': link_rev, 'mode': mode, 'limit': limit} params.update(args) if verbose: params['verbose'] = verbose return req.href.log(repos.reponame or None, path, **params) if format in ('rss', 'changelog'): info = [i for i in info if i['change']] # drop separators if info and count > limit: del info[-1] elif info and count >= limit: # stop_limit reached, there _might_ be some more next_rev = info[-1]['rev'] next_path = info[-1]['path'] next_revranges = None if revranges: next_revranges = str(revranges.truncate(next_rev)) if next_revranges or not revranges: older_revisions_href = make_log_href(next_path, rev=next_rev, revs=next_revranges) add_link( req, 'next', older_revisions_href, _('Revision Log (restarting at %(path)s, rev. %(rev)s)', path=next_path, rev=display_rev(next_rev))) # only show fully 'limit' results, use `change == None` as a marker info[-1]['change'] = None revisions = [i['rev'] for i in info] changes = get_changes(repos, revisions, self.log) extra_changes = {} if format == 'changelog': for rev in revisions: changeset = changes[rev] cs = {} cs['message'] = wrap(changeset.message, 70, initial_indent='\t', subsequent_indent='\t') files = [] actions = [] for cpath, kind, chg, bpath, brev in changeset.get_changes(): files.append(chg == Changeset.DELETE and bpath or cpath) actions.append(chg) cs['files'] = files cs['actions'] = actions extra_changes[rev] = cs data = { 'context': Context.from_request(req, 'source', path, parent=repos.resource), 'reponame': repos.reponame or None, 'repos': repos, 'path': path, 'rev': rev, 'stop_rev': stop_rev, 'display_rev': display_rev, 'revranges': revranges, 'mode': mode, 'verbose': verbose, 'limit': limit, 'items': info, 'changes': changes, 'extra_changes': extra_changes, 'wiki_format_messages': self.config['changeset'].getbool('wiki_format_messages') } if format == 'changelog': return 'revisionlog.txt', data, 'text/plain' elif format == 'rss': data['email_map'] = Chrome(self.env).get_email_map() data['context'] = Context.from_request(req, 'source', path, parent=repos.resource, absurls=True) return 'revisionlog.rss', data, 'application/rss+xml' item_ranges = [] range = [] for item in info: if item['change'] is None: # separator if range: # start new range range.append(item) item_ranges.append(range) range = [] else: range.append(item) if range: item_ranges.append(range) data['item_ranges'] = item_ranges add_stylesheet(req, 'common/css/diff.css') add_stylesheet(req, 'common/css/browser.css') path_links = get_path_links(req.href, repos.reponame, path, rev) if path_links: data['path_links'] = path_links if path != '/': add_link(req, 'up', path_links[-2]['href'], _('Parent directory')) rss_href = make_log_href(path, format='rss', revs=revs, stop_rev=stop_rev) add_link(req, 'alternate', rss_href, _('RSS Feed'), 'application/rss+xml', 'rss') changelog_href = make_log_href(path, format='changelog', revs=revs, stop_rev=stop_rev) add_link(req, 'alternate', changelog_href, _('ChangeLog'), 'text/plain') add_ctxtnav(req, _('View Latest Revision'), href=req.href.browser(repos.reponame or None, path)) if 'next' in req.chrome['links']: next = req.chrome['links']['next'][0] add_ctxtnav( req, tag.span(tag.a(_('Older Revisions'), href=next['href']), Markup(' →'))) return 'revisionlog.html', data, None
def format_props(self): tkt = self.ticket fields = [f for f in tkt.fields if f['name'] not in ('summary', 'cc', 'time', 'changetime')] width = [0, 0, 0, 0] i = 0 for f in fields: if f['type'] == 'textarea': continue fname = f['name'] if fname not in tkt.values: continue fval = tkt[fname] or '' if fname in tkt.time_fields: format = tkt.fields.by_name(fname).get('format') fval = self.format_time_field(fval, format) if fval.find('\n') != -1: continue if fname in ['owner', 'reporter']: fval = self.format_author(fval) idx = 2 * (i % 2) width[idx] = max(self.get_text_width(f['label']), width[idx]) width[idx + 1] = max(self.get_text_width(fval), width[idx + 1]) i += 1 width_l = width[0] + width[1] + 5 width_r = width[2] + width[3] + 5 half_cols = (self.COLS - 1) / 2 if width_l + width_r + 1 > self.COLS: if ((width_l > half_cols and width_r > half_cols) or (width[0] > half_cols / 2 or width[2] > half_cols / 2)): width_l = half_cols width_r = half_cols elif width_l > width_r: width_l = min((self.COLS - 1) * 2 / 3, width_l) width_r = self.COLS - width_l - 1 else: width_r = min((self.COLS - 1) * 2 / 3, width_r) width_l = self.COLS - width_r - 1 sep = width_l * '-' + '+' + width_r * '-' txt = sep + '\n' vals_lr = ([], []) big = [] i = 0 width_lr = [width_l, width_r] for f in [f for f in fields if f['name'] != 'description']: fname = f['name'] if fname not in tkt.values: continue fval = tkt[fname] or '' if fname in tkt.time_fields: format = tkt.fields.by_name(fname).get('format') fval = self.format_time_field(fval, format) if fname in ['owner', 'reporter']: fval = self.format_author(fval) if f['type'] == 'textarea' or '\n' in unicode(fval): big.append((f['label'], '\n'.join(fval.splitlines()))) else: # Note: f['label'] is a Babel's LazyObject, make sure its # __str__ method won't be called. str_tmp = u'%s: %s' % (f['label'], unicode(fval)) idx = i % 2 initial_indent = ' ' * (width[2 * idx] - self.get_text_width(f['label']) + 2 * idx) wrapped = wrap(str_tmp, width_lr[idx] - 2 + 2 * idx, initial_indent, ' ', '\n', self.ambiwidth) vals_lr[idx].append(wrapped.splitlines()) i += 1 if len(vals_lr[0]) > len(vals_lr[1]): vals_lr[1].append([]) cell_l = [] cell_r = [] for i in xrange(len(vals_lr[0])): vals_l = vals_lr[0][i] vals_r = vals_lr[1][i] vals_diff = len(vals_l) - len(vals_r) diff = len(cell_l) - len(cell_r) if diff > 0: # add padding to right side if needed if vals_diff < 0: diff += vals_diff cell_r.extend([''] * max(diff, 0)) elif diff < 0: # add padding to left side if needed if vals_diff > 0: diff += vals_diff cell_l.extend([''] * max(-diff, 0)) cell_l.extend(vals_l) cell_r.extend(vals_r) for i in range(max(len(cell_l), len(cell_r))): if i >= len(cell_l): cell_l.append(width_l * ' ') elif i >= len(cell_r): cell_r.append('') fmt_width = width_l - self.get_text_width(cell_l[i]) \ + len(cell_l[i]) txt += u'%-*s|%s%s' % (fmt_width, cell_l[i], cell_r[i], '\n') if big: txt += sep for name, value in big: txt += '\n'.join(['', name + ':', value, '', '']) txt += sep return txt
def process_request(self, req): req.perm.assert_permission('LOG_VIEW') mode = req.args.get('mode', 'stop_on_copy') path = req.args.get('path', '/') rev = req.args.get('rev') stop_rev = req.args.get('stop_rev') format = req.args.get('format') verbose = req.args.get('verbose') limit = LOG_LIMIT repos = self.env.get_repository(req.authname) normpath = repos.normalize_path(path) rev = unicode(repos.normalize_rev(rev)) if stop_rev: stop_rev = unicode(repos.normalize_rev(stop_rev)) if repos.rev_older_than(rev, stop_rev): rev, stop_rev = stop_rev, rev req.hdf['title'] = path + ' (log)' req.hdf['log'] = { 'mode': mode, 'path': path, 'rev': rev, 'verbose': verbose, 'stop_rev': stop_rev, 'browser_href': req.href.browser(path), 'changeset_href': req.href.changeset(), 'log_href': req.href.log(path, rev=rev) } path_links = get_path_links(req.href, path, rev) req.hdf['log.path'] = path_links if path_links: add_link(req, 'up', path_links[-1]['href'], 'Parent directory') # The `history()` method depends on the mode: # * for ''stop on copy'' and ''follow copies'', it's `Node.history()` # * for ''show only add, delete'' it's`Repository.get_path_history()` if mode == 'path_history': def history(limit): for h in repos.get_path_history(path, rev, limit): yield h else: history = get_existing_node(req, repos, path, rev).get_history # -- retrieve history, asking for limit+1 results info = [] previous_path = repos.normalize_path(path) for old_path, old_rev, old_chg in history(limit+1): if stop_rev and repos.rev_older_than(old_rev, stop_rev): break old_path = repos.normalize_path(old_path) item = { 'rev': str(old_rev), 'path': old_path, 'log_href': req.href.log(old_path, rev=old_rev), 'browser_href': req.href.browser(old_path, rev=old_rev), 'changeset_href': req.href.changeset(old_rev), 'restricted_href': req.href.changeset(old_rev, new_path=old_path), 'change': old_chg } if not (mode == 'path_history' and old_chg == Changeset.EDIT): info.append(item) if old_path and old_path != previous_path \ and not (mode == 'path_history' and old_path == normpath): item['copyfrom_path'] = old_path if mode == 'stop_on_copy': break if len(info) > limit: # we want limit+1 entries break previous_path = old_path if info == []: # FIXME: we should send a 404 error here raise TracError("The file or directory '%s' doesn't exist " "at revision %s or at any previous revision." % (path, rev), 'Nonexistent path') def make_log_href(path, **args): link_rev = rev if rev == str(repos.youngest_rev): link_rev = None params = {'rev': link_rev, 'mode': mode, 'limit': limit} params.update(args) if verbose: params['verbose'] = verbose return req.href.log(path, **params) if len(info) == limit+1: # limit+1 reached, there _might_ be some more next_rev = info[-1]['rev'] next_path = info[-1]['path'] add_link(req, 'next', make_log_href(next_path, rev=next_rev), u'리비전 로그 ( %s, 리비전 %s 에서 다시 시작)' % (next_path, next_rev)) # now, only show 'limit' results del info[-1] req.hdf['log.items'] = info revs = [i['rev'] for i in info] changes = get_changes(self.env, repos, revs, verbose, req, format) if format == 'rss': # Get the email addresses of all known users email_map = {} for username,name,email in self.env.get_known_users(): if email: email_map[username] = email for cs in changes.values(): # For RSS, author must be an email address author = cs['author'] author_email = '' if '@' in author: author_email = author elif email_map.has_key(author): author_email = email_map[author] cs['author'] = author_email cs['date'] = http_date(cs['date_seconds']) elif format == 'changelog': for rev in revs: changeset = repos.get_changeset(rev) cs = changes[rev] cs['message'] = wrap(changeset.message, 70, initial_indent='\t', subsequent_indent='\t') files = [] actions = [] for path, kind, chg, bpath, brev in changeset.get_changes(): files.append(chg == Changeset.DELETE and bpath or path) actions.append(chg) cs['files'] = files cs['actions'] = actions req.hdf['log.changes'] = changes if req.args.get('format') == 'changelog': return 'log_changelog.cs', 'text/plain' elif req.args.get('format') == 'rss': return 'log_rss.cs', 'application/rss+xml' add_stylesheet(req, 'common/css/browser.css') add_stylesheet(req, 'common/css/diff.css') rss_href = make_log_href(path, format='rss', stop_rev=stop_rev) add_link(req, 'alternate', rss_href, 'RSS Feed', 'application/rss+xml', 'rss') changelog_href = make_log_href(path, format='changelog', stop_rev=stop_rev) add_link(req, 'alternate', changelog_href, 'ChangeLog', 'text/plain') return 'log.cs', None
def process_request(self, req): req.perm.require("LOG_VIEW") mode = req.args.get("mode", "stop_on_copy") path = req.args.get("path", "/") rev = req.args.get("rev") stop_rev = req.args.get("stop_rev") revs = req.args.get("revs") format = req.args.get("format") verbose = req.args.get("verbose") limit = int(req.args.get("limit") or self.default_log_limit) rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path(path) if not repos: if path == "/": raise TracError(_("No repository specified and no default" " repository configured.")) else: raise ResourceNotFound(_("Repository '%(repo)s' not found", repo=reponame or path.strip("/"))) if reponame != repos.reponame: # Redirect alias qs = req.query_string req.redirect(req.href.log(repos.reponame or None, path) + ("?" + qs if qs else "")) normpath = repos.normalize_path(path) # if `revs` parameter is given, then we're restricted to the # corresponding revision ranges. # If not, then we're considering all revisions since `rev`, # on that path, in which case `revranges` will be None. if revs: revranges = RevRanges(repos, revs, resolve=True) rev = revranges.b else: revranges = None rev = repos.normalize_rev(rev) # The `history()` method depends on the mode: # * for ''stop on copy'' and ''follow copies'', it's `Node.history()` # unless explicit ranges have been specified # * for ''show only add, delete'' we're using # `Repository.get_path_history()` cset_resource = repos.resource.child(self.realm) show_graph = False curr_revrange = [] if mode == "path_history": def history(): for h in repos.get_path_history(path, rev): if "CHANGESET_VIEW" in req.perm(cset_resource(id=h[1])): yield h elif revranges: show_graph = path == "/" and not verbose and not repos.has_linear_changesets and len(revranges) == 1 def history(): separator = False for a, b in reversed(revranges.pairs): curr_revrange[:] = (a, b) node = get_existing_node(req, repos, path, b) for p, rev, chg in node.get_history(): if repos.rev_older_than(rev, a): break if "CHANGESET_VIEW" in req.perm(cset_resource(id=rev)): separator = True yield p, rev, chg else: separator = False if separator: yield p, rev, None else: show_graph = path == "/" and not verbose and not repos.has_linear_changesets def history(): node = get_existing_node(req, repos, path, rev) for h in node.get_history(): if "CHANGESET_VIEW" in req.perm(cset_resource(id=h[1])): yield h # -- retrieve history, asking for limit+1 results info = [] depth = 1 previous_path = normpath count = 0 history_remaining = True for old_path, old_rev, old_chg in history(): if stop_rev and repos.rev_older_than(old_rev, stop_rev): break old_path = repos.normalize_path(old_path) item = {"path": old_path, "rev": old_rev, "existing_rev": old_rev, "change": old_chg, "depth": depth} if old_chg == Changeset.DELETE: item["existing_rev"] = repos.previous_rev(old_rev, old_path) if not (mode == "path_history" and old_chg == Changeset.EDIT): info.append(item) if old_path and old_path != previous_path and not (mode == "path_history" and old_path == normpath): depth += 1 item["depth"] = depth item["copyfrom_path"] = old_path if mode == "stop_on_copy": break elif mode == "path_history": depth -= 1 if old_chg is None: # separator entry stop_limit = limit else: count += 1 stop_limit = limit + 1 if count >= stop_limit: break previous_path = old_path else: history_remaining = False if not info: node = get_existing_node(req, repos, path, rev) if repos.rev_older_than(stop_rev, node.created_rev): # FIXME: we should send a 404 error here raise TracError( _( "The file or directory '%(path)s' doesn't " "exist at revision %(rev)s or at any " "previous revision.", path=path, rev=repos.display_rev(rev), ), _("Nonexistent path"), ) # Generate graph data graph = {} if show_graph: threads, vertices, columns = make_log_graph(repos, (item["rev"] for item in info)) graph.update( threads=threads, vertices=vertices, columns=columns, colors=self.graph_colors, line_width=0.04, dot_radius=0.1, ) add_script(req, "common/js/excanvas.js", ie_if="IE") add_script(req, "common/js/log_graph.js") add_script_data(req, graph=graph) def make_log_href(path, **args): link_rev = rev if rev == str(repos.youngest_rev): link_rev = None params = {"rev": link_rev, "mode": mode, "limit": limit} params.update(args) if verbose: params["verbose"] = verbose return req.href.log(repos.reponame or None, path, **params) if format in ("rss", "changelog"): info = [i for i in info if i["change"]] # drop separators if info and count > limit: del info[-1] elif info and history_remaining and count >= limit: # stop_limit reached, there _might_ be some more next_rev = info[-1]["rev"] next_path = info[-1]["path"] next_revranges = None if curr_revrange: new_revrange = (curr_revrange[0], next_rev) if info[-1]["change"] else None next_revranges = revranges.truncate(curr_revrange, new_revrange) next_revranges = unicode(next_revranges) or None if next_revranges or not revranges: older_revisions_href = make_log_href(next_path, rev=next_rev, revs=next_revranges) add_link( req, "next", older_revisions_href, _( "Revision Log (restarting at %(path)s, rev. " "%(rev)s)", path=next_path, rev=repos.display_rev(next_rev), ), ) # only show fully 'limit' results, use `change == None` as a marker info[-1]["change"] = None revisions = [i["rev"] for i in info] changes = get_changes(repos, revisions, self.log) extra_changes = {} if format == "changelog": for rev in revisions: changeset = changes[rev] cs = {} cs["message"] = wrap(changeset.message, 70, initial_indent="\t", subsequent_indent="\t") files = [] actions = [] for cpath, kind, chg, bpath, brev in changeset.get_changes(): files.append(bpath if chg == Changeset.DELETE else cpath) actions.append(chg) cs["files"] = files cs["actions"] = actions extra_changes[rev] = cs data = { "context": web_context(req, "source", path, parent=repos.resource), "reponame": repos.reponame or None, "repos": repos, "path": path, "rev": rev, "stop_rev": stop_rev, "display_rev": repos.display_rev, "revranges": revranges, "mode": mode, "verbose": verbose, "limit": limit, "items": info, "changes": changes, "extra_changes": extra_changes, "graph": graph, "wiki_format_messages": self.config["changeset"].getbool("wiki_format_messages"), } if format == "changelog": return "revisionlog.txt", data, "text/plain" elif format == "rss": data["context"] = web_context(req, "source", path, parent=repos.resource, absurls=True) return "revisionlog.rss", data, "application/rss+xml" item_ranges = [] range = [] for item in info: if item["change"] is None: # separator if range: # start new range range.append(item) item_ranges.append(range) range = [] else: range.append(item) if range: item_ranges.append(range) data["item_ranges"] = item_ranges add_stylesheet(req, "common/css/diff.css") add_stylesheet(req, "common/css/browser.css") path_links = get_path_links(req.href, repos.reponame, path, rev) if path_links: data["path_links"] = path_links if path != "/": add_link(req, "up", path_links[-2]["href"], _("Parent directory")) rss_href = make_log_href(path, format="rss", revs=revs, stop_rev=stop_rev) add_link(req, "alternate", auth_link(req, rss_href), _("RSS Feed"), "application/rss+xml", "rss") changelog_href = make_log_href(path, format="changelog", revs=revs, stop_rev=stop_rev) add_link(req, "alternate", changelog_href, _("ChangeLog"), "text/plain") add_ctxtnav(req, _("View Latest Revision"), href=req.href.browser(repos.reponame or None, path)) if "next" in req.chrome["links"]: next = req.chrome["links"]["next"][0] add_ctxtnav(req, tag.span(tag.a(_("Older Revisions"), href=next["href"]), Markup(" →"))) return "revisionlog.html", data, None
def _format_plaintext(self, event): """Format ticket change notification e-mail (untranslated)""" ticket = event.target newticket = event.category == 'created' with translation_deactivated(ticket): link = self.env.abs_href.ticket(ticket.id) changes_body = '' changes_descr = '' change_data = {} if not newticket and event.time: # Ticket change from trac.ticket.web_ui import TicketModule for change in TicketModule(self.env) \ .grouped_changelog_entries(ticket, when=event.time): if not change['permanent']: # attachment with same time... continue author = change['author'] change_data.update({ 'author': self._format_author(author), 'comment': wrap(change['comment'], self.COLS, ' ', ' ', '\n', self.ambiwidth) }) link += '#comment:%s' % str(change.get('cnum', '')) for field, values in change['fields'].iteritems(): old = values['old'] new = values['new'] newv = '' if field == 'description': new_descr = wrap(new, self.COLS, ' ', ' ', '\n', self.ambiwidth) old_descr = wrap(old, self.COLS, '> ', '> ', '\n', self.ambiwidth) old_descr = old_descr.replace( 2 * '\n', '\n' + '>' + '\n') cdescr = '\n' cdescr += 'Old description:' + 2 * '\n' + old_descr + \ 2 * '\n' cdescr += 'New description:' + 2 * '\n' + new_descr + \ '\n' changes_descr = cdescr elif field == 'cc': addcc, delcc = self._diff_cc(old, new) chgcc = '' if delcc: chgcc += wrap( " * cc: %s (removed)" % ', '.join(delcc), self.COLS, ' ', ' ', '\n', self.ambiwidth) + '\n' if addcc: chgcc += wrap( " * cc: %s (added)" % ', '.join(addcc), self.COLS, ' ', ' ', '\n', self.ambiwidth) + '\n' if chgcc: changes_body += chgcc else: if field in ['owner', 'reporter']: old = self._format_author(old) new = self._format_author(new) elif field in ticket.time_fields: format = ticket.fields.by_name(field) \ .get('format') old = self._format_time_field(old, format) new = self._format_time_field(new, format) newv = new length = 7 + len(field) spacer_old, spacer_new = ' ', ' ' if len(old + new) + length > self.COLS: length = 5 if len(old) + length > self.COLS: spacer_old = '\n' if len(new) + length > self.COLS: spacer_new = '\n' chg = '* %s: %s%s%s=>%s%s' \ % (field, spacer_old, old, spacer_old, spacer_new, new) chg = chg.replace('\n', '\n' + length * ' ') chg = wrap(chg, self.COLS, '', length * ' ', '\n', self.ambiwidth) changes_body += ' %s%s' % (chg, '\n') if newv: change_data[field] = { 'oldvalue': old, 'newvalue': new } ticket_values = ticket.values.copy() ticket_values['id'] = ticket.id ticket_values['description'] = wrap(ticket_values.get( 'description', ''), self.COLS, initial_indent=' ', subsequent_indent=' ', linesep='\n', ambiwidth=self.ambiwidth) ticket_values['new'] = newticket ticket_values['link'] = link data = Chrome(self.env).populate_data( None, { 'CRLF': CRLF, 'ticket_props': self._format_props(ticket), 'ticket_body_hdr': self._format_hdr(ticket), 'ticket': ticket_values, 'changes_body': changes_body, 'changes_descr': changes_descr, 'change': change_data }) return self._format_body(data, 'ticket_notify_email.txt')
def process_request(self, req): req.perm.require('LOG_VIEW') mode = req.args.get('mode', 'stop_on_copy') path = req.args.get('path', '/') rev = req.args.get('rev') stop_rev = req.args.get('stop_rev') revs = req.args.get('revs') format = req.args.get('format') verbose = req.args.get('verbose') limit = int(req.args.get('limit') or self.default_log_limit) rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path(path) if not repos: raise ResourceNotFound(_("Repository '%(repo)s' not found", repo=reponame)) if reponame != repos.reponame: # Redirect alias qs = req.query_string req.redirect(req.href.log(repos.reponame or None, path) + ('?' + qs if qs else '')) normpath = repos.normalize_path(path) # if `revs` parameter is given, then we're restricted to the # corresponding revision ranges. # If not, then we're considering all revisions since `rev`, # on that path, in which case `revranges` will be None. revranges = None if revs: try: revranges = Ranges(revs) rev = revranges.b except ValueError: pass rev = unicode(repos.normalize_rev(rev)) display_rev = repos.display_rev # The `history()` method depends on the mode: # * for ''stop on copy'' and ''follow copies'', it's `Node.history()` # unless explicit ranges have been specified # * for ''show only add, delete'' we're using # `Repository.get_path_history()` cset_resource = repos.resource.child('changeset') show_graph = False if mode == 'path_history': def history(): for h in repos.get_path_history(path, rev): if 'CHANGESET_VIEW' in req.perm(cset_resource(id=h[1])): yield h elif revranges: def history(): prevpath = path expected_next_item = None ranges = list(revranges.pairs) ranges.reverse() for (a, b) in ranges: a = repos.normalize_rev(a) b = repos.normalize_rev(b) while not repos.rev_older_than(b, a): node = get_existing_node(req, repos, prevpath, b) node_history = list(node.get_history(2)) p, rev, chg = node_history[0] if repos.rev_older_than(rev, a): break # simply skip, no separator if 'CHANGESET_VIEW' in req.perm(cset_resource(id=rev)): if expected_next_item: # check whether we're continuing previous range np, nrev, nchg = expected_next_item if rev != nrev: # no, we need a separator yield (np, nrev, None) yield node_history[0] prevpath = node_history[-1][0] # follow copy b = repos.previous_rev(rev) if len(node_history) > 1: expected_next_item = node_history[-1] else: expected_next_item = None if expected_next_item: yield (expected_next_item[0], expected_next_item[1], None) else: show_graph = path == '/' and not verbose \ and not repos.has_linear_changesets def history(): node = get_existing_node(req, repos, path, rev) for h in node.get_history(): if 'CHANGESET_VIEW' in req.perm(cset_resource(id=h[1])): yield h # -- retrieve history, asking for limit+1 results info = [] depth = 1 previous_path = normpath count = 0 for old_path, old_rev, old_chg in history(): if stop_rev and repos.rev_older_than(old_rev, stop_rev): break old_path = repos.normalize_path(old_path) item = { 'path': old_path, 'rev': old_rev, 'existing_rev': old_rev, 'change': old_chg, 'depth': depth, } if old_chg == Changeset.DELETE: item['existing_rev'] = repos.previous_rev(old_rev, old_path) if not (mode == 'path_history' and old_chg == Changeset.EDIT): info.append(item) if old_path and old_path != previous_path and \ not (mode == 'path_history' and old_path == normpath): depth += 1 item['depth'] = depth item['copyfrom_path'] = old_path if mode == 'stop_on_copy': break elif mode == 'path_history': depth -= 1 if old_chg is None: # separator entry stop_limit = limit else: count += 1 stop_limit = limit + 1 if count >= stop_limit: break previous_path = old_path if info == []: node = get_existing_node(req, repos, path, rev) if repos.rev_older_than(stop_rev, node.created_rev): # FIXME: we should send a 404 error here raise TracError(_("The file or directory '%(path)s' doesn't " "exist at revision %(rev)s or at any previous revision.", path=path, rev=display_rev(rev)), _('Nonexistent path')) # Generate graph data graph = {} if show_graph: threads, vertices, columns = \ make_log_graph(repos, (item['rev'] for item in info)) graph.update(threads=threads, vertices=vertices, columns=columns, colors=self.graph_colors, line_width=0.04, dot_radius=0.1) add_script(req, 'common/js/excanvas.js', ie_if='IE') add_script(req, 'common/js/log_graph.js') add_script_data(req, graph=graph) def make_log_href(path, **args): link_rev = rev if rev == str(repos.youngest_rev): link_rev = None params = {'rev': link_rev, 'mode': mode, 'limit': limit} params.update(args) if verbose: params['verbose'] = verbose return req.href.log(repos.reponame or None, path, **params) if format in ('rss', 'changelog'): info = [i for i in info if i['change']] # drop separators if info and count > limit: del info[-1] elif info and count >= limit: # stop_limit reached, there _might_ be some more next_rev = info[-1]['rev'] next_path = info[-1]['path'] next_revranges = None if revranges: next_revranges = str(revranges.truncate(next_rev)) if next_revranges or not revranges: older_revisions_href = make_log_href(next_path, rev=next_rev, revs=next_revranges) add_link(req, 'next', older_revisions_href, _('Revision Log (restarting at %(path)s, rev. %(rev)s)', path=next_path, rev=display_rev(next_rev))) # only show fully 'limit' results, use `change == None` as a marker info[-1]['change'] = None revisions = [i['rev'] for i in info] changes = get_changes(repos, revisions, self.log) extra_changes = {} if format == 'changelog': for rev in revisions: changeset = changes[rev] cs = {} cs['message'] = wrap(changeset.message, 70, initial_indent='\t', subsequent_indent='\t') files = [] actions = [] for cpath, kind, chg, bpath, brev in changeset.get_changes(): files.append(bpath if chg == Changeset.DELETE else cpath) actions.append(chg) cs['files'] = files cs['actions'] = actions extra_changes[rev] = cs data = { 'context': web_context(req, 'source', path, parent=repos.resource), 'reponame': repos.reponame or None, 'repos': repos, 'path': path, 'rev': rev, 'stop_rev': stop_rev, 'display_rev': display_rev, 'revranges': revranges, 'mode': mode, 'verbose': verbose, 'limit' : limit, 'items': info, 'changes': changes, 'extra_changes': extra_changes, 'graph': graph, 'wiki_format_messages': self.config['changeset'].getbool('wiki_format_messages') } if format == 'changelog': return 'revisionlog.txt', data, 'text/plain' elif format == 'rss': data['email_map'] = Chrome(self.env).get_email_map() data['context'] = web_context(req, 'source', path, parent=repos.resource, absurls=True) return 'revisionlog.rss', data, 'application/rss+xml' item_ranges = [] range = [] for item in info: if item['change'] is None: # separator if range: # start new range range.append(item) item_ranges.append(range) range = [] else: range.append(item) if range: item_ranges.append(range) data['item_ranges'] = item_ranges add_stylesheet(req, 'common/css/diff.css') add_stylesheet(req, 'common/css/browser.css') path_links = get_path_links(req.href, repos.reponame, path, rev) if path_links: data['path_links'] = path_links if path != '/': add_link(req, 'up', path_links[-2]['href'], _('Parent directory')) rss_href = make_log_href(path, format='rss', revs=revs, stop_rev=stop_rev) add_link(req, 'alternate', auth_link(req, rss_href), _('RSS Feed'), 'application/rss+xml', 'rss') changelog_href = make_log_href(path, format='changelog', revs=revs, stop_rev=stop_rev) add_link(req, 'alternate', changelog_href, _('ChangeLog'), 'text/plain') add_ctxtnav(req, _('View Latest Revision'), href=req.href.browser(repos.reponame or None, path)) if 'next' in req.chrome['links']: next = req.chrome['links']['next'][0] add_ctxtnav(req, tag.span(tag.a(_('Older Revisions'), href=next['href']), Markup(' →'))) return 'revisionlog.html', data, None
def _format_hdr(self, ticket): return '#%s: %s' % (ticket.id, wrap(ticket['summary'], self.COLS, linesep='\n', ambiwidth=self.ambiwidth))
def _notify(self, ticket, newticket=True, modtime=None): self.ticket = ticket self.modtime = modtime self.newticket = newticket changes_body = "" self.reporter = "" self.owner = "" changes_descr = "" change_data = {} link = self.env.abs_href.ticket(ticket.id) summary = self.ticket["summary"] if not self.newticket and modtime: # Ticket change from trac.ticket.web_ui import TicketModule for change in TicketModule(self.env).grouped_changelog_entries(ticket, self.db, when=modtime): if not change["permanent"]: # attachment with same time... continue change_data.update( { "author": obfuscate_email_address(change["author"]), "comment": wrap(change["comment"], self.COLS, " ", " ", CRLF), } ) link += "#comment:%s" % str(change.get("cnum", "")) for field, values in change["fields"].iteritems(): old = values["old"] new = values["new"] newv = "" if field == "description": new_descr = wrap(new, self.COLS, " ", " ", CRLF) old_descr = wrap(old, self.COLS, "> ", "> ", CRLF) old_descr = old_descr.replace(2 * CRLF, CRLF + ">" + CRLF) cdescr = CRLF cdescr += "Old description:" + 2 * CRLF + old_descr + 2 * CRLF cdescr += "New description:" + 2 * CRLF + new_descr + CRLF changes_descr = cdescr elif field == "summary": summary = "%s (was: %s)" % (new, old) elif field == "cc": (addcc, delcc) = self.diff_cc(old, new) chgcc = "" if delcc: chgcc += wrap(" * cc: %s (removed)" % ", ".join(delcc), self.COLS, " ", " ", CRLF) + CRLF if addcc: chgcc += wrap(" * cc: %s (added)" % ", ".join(addcc), self.COLS, " ", " ", CRLF) + CRLF if chgcc: changes_body += chgcc self.prev_cc += old and self.parse_cc(old) or [] else: if field in ["owner", "reporter"]: old = obfuscate_email_address(old) new = obfuscate_email_address(new) newv = new length = 7 + len(field) spacer_old, spacer_new = " ", " " if len(old + new) + length > self.COLS: length = 5 if len(old) + length > self.COLS: spacer_old = CRLF if len(new) + length > self.COLS: spacer_new = CRLF chg = "* %s: %s%s%s=>%s%s" % (field, spacer_old, old, spacer_old, spacer_new, new) chg = chg.replace(CRLF, CRLF + length * " ") chg = wrap(chg, self.COLS, "", length * " ", CRLF) changes_body += " %s%s" % (chg, CRLF) if newv: change_data[field] = {"oldvalue": old, "newvalue": new} ticket_values = ticket.values.copy() ticket_values["id"] = ticket.id ticket_values["description"] = wrap( ticket_values.get("description", ""), self.COLS, initial_indent=" ", subsequent_indent=" ", linesep=CRLF ) ticket_values["new"] = self.newticket ticket_values["link"] = link subject = self.format_subj(summary) if not self.newticket: subject = "Re: " + subject self.data.update( { "ticket_props": self.format_props(), "ticket_body_hdr": self.format_hdr(), "subject": subject, "ticket": ticket_values, "changes_body": changes_body, "changes_descr": changes_descr, "change": change_data, } ) NotifyEmail.notify(self, ticket.id, subject)
def _format_props(self, ticket): fields = [ f for f in ticket.fields if f['name'] not in ('summary', 'cc', 'time', 'changetime') ] width = [0, 0, 0, 0] i = 0 for f in fields: if f['type'] == 'textarea': continue fname = f['name'] if fname not in ticket.values: continue fval = ticket[fname] or '' if fname in ticket.time_fields: format = ticket.fields.by_name(fname).get('format') fval = self._format_time_field(fval, format) if fval.find('\n') != -1: continue if fname in ['owner', 'reporter']: fval = self._format_author(fval) idx = 2 * (i % 2) width[idx] = max(self._get_text_width(f['label']), width[idx]) width[idx + 1] = max(self._get_text_width(fval), width[idx + 1]) i += 1 width_l = width[0] + width[1] + 5 width_r = width[2] + width[3] + 5 half_cols = (self.COLS - 1) / 2 if width_l + width_r + 1 > self.COLS: if ((width_l > half_cols and width_r > half_cols) or (width[0] > half_cols / 2 or width[2] > half_cols / 2)): width_l = half_cols width_r = half_cols elif width_l > width_r: width_l = min((self.COLS - 1) * 2 / 3, width_l) width_r = self.COLS - width_l - 1 else: width_r = min((self.COLS - 1) * 2 / 3, width_r) width_l = self.COLS - width_r - 1 sep = width_l * '-' + '+' + width_r * '-' txt = sep + '\n' vals_lr = ([], []) big = [] i = 0 width_lr = [width_l, width_r] for f in [f for f in fields if f['name'] != 'description']: fname = f['name'] if fname not in ticket.values: continue fval = ticket[fname] or '' if fname in ticket.time_fields: format = ticket.fields.by_name(fname).get('format') fval = self._format_time_field(fval, format) if fname in ['owner', 'reporter']: fval = self._format_author(fval) if f['type'] == 'textarea' or '\n' in unicode(fval): big.append((f['label'], '\n'.join(fval.splitlines()))) else: # Note: f['label'] is a Babel's LazyObject, make sure its # __str__ method won't be called. str_tmp = u'%s: %s' % (f['label'], unicode(fval)) idx = i % 2 initial_indent = ' ' * (width[2 * idx] - self._get_text_width( f['label']) + 2 * idx) wrapped = wrap(str_tmp, width_lr[idx] - 2 + 2 * idx, initial_indent, ' ', '\n', self.ambiwidth) vals_lr[idx].append(wrapped.splitlines()) i += 1 if len(vals_lr[0]) > len(vals_lr[1]): vals_lr[1].append([]) cell_l = [] cell_r = [] for i in xrange(len(vals_lr[0])): vals_l = vals_lr[0][i] vals_r = vals_lr[1][i] vals_diff = len(vals_l) - len(vals_r) diff = len(cell_l) - len(cell_r) if diff > 0: # add padding to right side if needed if vals_diff < 0: diff += vals_diff cell_r.extend([''] * max(diff, 0)) elif diff < 0: # add padding to left side if needed if vals_diff > 0: diff += vals_diff cell_l.extend([''] * max(-diff, 0)) cell_l.extend(vals_l) cell_r.extend(vals_r) for i in xrange(max(len(cell_l), len(cell_r))): if i >= len(cell_l): cell_l.append(width_l * ' ') elif i >= len(cell_r): cell_r.append('') fmt_width = width_l - self._get_text_width(cell_l[i]) \ + len(cell_l[i]) txt += u'%-*s|%s%s' % (fmt_width, cell_l[i], cell_r[i], '\n') if big: txt += sep for name, value in big: txt += '\n'.join(['', name + ':', value, '', '']) txt += sep return txt
def format_hdr(self): return "#%s: %s" % (self.ticket.id, wrap(self.ticket["summary"], self.COLS, linesep=CRLF))
def process_request(self, req): req.perm.require('LOG_VIEW') mode = req.args.get('mode', 'stop_on_copy') path = req.args.get('path', '/') rev = req.args.get('rev') stop_rev = req.args.get('stop_rev') revs = req.args.get('revs') format = req.args.get('format') verbose = req.args.get('verbose') limit = req.args.getint('limit', self.default_log_limit) rm = RepositoryManager(self.env) reponame, repos, path = rm.get_repository_by_path(path) if not repos: if path == '/': raise TracError( _("No repository specified and no default" " repository configured.")) else: raise ResourceNotFound( _("Repository '%(repo)s' not found", repo=reponame or path.strip('/'))) if reponame != repos.reponame: # Redirect alias qs = req.query_string req.redirect( req.href.log(repos.reponame or None, path) + ('?' + qs if qs else '')) normpath = repos.normalize_path(path) # if `revs` parameter is given, then we're restricted to the # corresponding revision ranges. # If not, then we're considering all revisions since `rev`, # on that path, in which case `revranges` will be None. if revs: revranges = RevRanges(repos, revs, resolve=True) rev = revranges.b else: revranges = None rev = repos.normalize_rev(rev) # The `history()` method depends on the mode: # * for ''stop on copy'' and ''follow copies'', it's `Node.history()` # unless explicit ranges have been specified # * for ''show only add, delete'' we're using # `Repository.get_path_history()` cset_resource = repos.resource.child(self.realm) show_graph = False curr_revrange = [] if mode == 'path_history': def history(): for h in repos.get_path_history(path, rev): if 'CHANGESET_VIEW' in req.perm(cset_resource(id=h[1])): yield h elif revranges: show_graph = path == '/' and not verbose \ and not repos.has_linear_changesets \ and len(revranges) == 1 def history(): separator = False for a, b in reversed(revranges.pairs): curr_revrange[:] = (a, b) node = get_existing_node(req, repos, path, b) for p, rev, chg in node.get_history(): if repos.rev_older_than(rev, a): break if 'CHANGESET_VIEW' in req.perm(cset_resource(id=rev)): separator = True yield p, rev, chg else: separator = False if separator: yield p, rev, None else: show_graph = path == '/' and not verbose \ and not repos.has_linear_changesets def history(): node = get_existing_node(req, repos, path, rev) for h in node.get_history(): if 'CHANGESET_VIEW' in req.perm(cset_resource(id=h[1])): yield h # -- retrieve history, asking for limit+1 results info = [] depth = 1 previous_path = normpath count = 0 history_remaining = True for old_path, old_rev, old_chg in history(): if stop_rev and repos.rev_older_than(old_rev, stop_rev): break old_path = repos.normalize_path(old_path) item = { 'path': old_path, 'rev': old_rev, 'existing_rev': old_rev, 'change': old_chg, 'depth': depth, } if old_chg == Changeset.DELETE: item['existing_rev'] = repos.previous_rev(old_rev, old_path) if not (mode == 'path_history' and old_chg == Changeset.EDIT): info.append(item) if old_path and old_path != previous_path and \ not (mode == 'path_history' and old_path == normpath): depth += 1 item['depth'] = depth item['copyfrom_path'] = old_path if mode == 'stop_on_copy': break elif mode == 'path_history': depth -= 1 if old_chg is None: # separator entry stop_limit = limit else: count += 1 stop_limit = limit + 1 if count >= stop_limit: break previous_path = old_path else: history_remaining = False if not info: node = get_existing_node(req, repos, path, rev) if repos.rev_older_than(stop_rev, node.created_rev): # FIXME: we should send a 404 error here raise TracError( _( "The file or directory '%(path)s' doesn't " "exist at revision %(rev)s or at any " "previous revision.", path=path, rev=repos.display_rev(rev)), _('Nonexistent path')) # Generate graph data graph = {} if show_graph: threads, vertices, columns = \ make_log_graph(repos, (item['rev'] for item in info)) graph.update(threads=threads, vertices=vertices, columns=columns, colors=self.graph_colors, line_width=0.04, dot_radius=0.1) add_script(req, 'common/js/excanvas.js', ie_if='IE') add_script(req, 'common/js/log_graph.js') add_script_data(req, graph=graph) def make_log_href(path, **args): link_rev = rev if rev == str(repos.youngest_rev): link_rev = None params = {'rev': link_rev, 'mode': mode, 'limit': limit} params.update(args) if verbose: params['verbose'] = verbose return req.href.log(repos.reponame or None, path, **params) if format in ('rss', 'changelog'): info = [i for i in info if i['change']] # drop separators if info and count > limit: del info[-1] elif info and history_remaining and count >= limit: # stop_limit reached, there _might_ be some more next_rev = info[-1]['rev'] next_path = info[-1]['path'] next_revranges = None if curr_revrange: new_revrange = (curr_revrange[0], next_rev) \ if info[-1]['change'] else None next_revranges = revranges.truncate(curr_revrange, new_revrange) next_revranges = unicode(next_revranges) or None if next_revranges or not revranges: older_revisions_href = make_log_href(next_path, rev=next_rev, revs=next_revranges) add_link( req, 'next', older_revisions_href, _('Revision Log (restarting at %(path)s, rev. ' '%(rev)s)', path=next_path, rev=repos.display_rev(next_rev))) # only show fully 'limit' results, use `change == None` as a marker info[-1]['change'] = None revisions = [i['rev'] for i in info] changes = get_changes(repos, revisions, self.log) extra_changes = {} if format == 'changelog': for rev in revisions: changeset = changes[rev] cs = {} cs['message'] = wrap(changeset.message, 70, initial_indent='\t', subsequent_indent='\t') files = [] actions = [] for cpath, kind, chg, bpath, brev in changeset.get_changes(): files.append(bpath if chg == Changeset.DELETE else cpath) actions.append(chg) cs['files'] = files cs['actions'] = actions extra_changes[rev] = cs data = { 'context': web_context(req, 'source', path, parent=repos.resource), 'reponame': repos.reponame or None, 'repos': repos, 'path': path, 'rev': rev, 'stop_rev': stop_rev, 'display_rev': repos.display_rev, 'revranges': revranges, 'mode': mode, 'verbose': verbose, 'limit': limit, 'items': info, 'changes': changes, 'extra_changes': extra_changes, 'graph': graph, 'wiki_format_messages': self.config['changeset'].getbool('wiki_format_messages') } if format == 'changelog': return 'revisionlog.txt', data, {'content_type': 'text/plain'} elif format == 'rss': data['context'] = web_context(req, 'source', path, parent=repos.resource, absurls=True) return ('revisionlog.rss', data, { 'content_type': 'application/rss+xml' }) item_ranges = [] range = [] for item in info: if item['change'] is None: # separator if range: # start new range range.append(item) item_ranges.append(range) range = [] else: range.append(item) if range: item_ranges.append(range) data['item_ranges'] = item_ranges add_stylesheet(req, 'common/css/diff.css') add_stylesheet(req, 'common/css/browser.css') path_links = get_path_links(req.href, repos.reponame, path, rev) if path_links: data['path_links'] = path_links if path != '/': add_link(req, 'up', path_links[-2]['href'], _('Parent directory')) rss_href = make_log_href(path, format='rss', revs=revs, stop_rev=stop_rev) add_link(req, 'alternate', auth_link(req, rss_href), _('RSS Feed'), 'application/rss+xml', 'rss') changelog_href = make_log_href(path, format='changelog', revs=revs, stop_rev=stop_rev) add_link(req, 'alternate', changelog_href, _('ChangeLog'), 'text/plain') add_ctxtnav(req, _('View Latest Revision'), href=req.href.browser(repos.reponame or None, path)) if 'next' in req.chrome['links']: next = req.chrome['links']['next'][0] add_ctxtnav( req, tag.span(tag.a(_('Older Revisions'), href=next['href']), Markup(' →'))) return 'revisionlog.html', data
def notify(self, ticket, newticket=True, modtime=0): self.ticket = ticket self.modtime = modtime self.newticket = newticket self.ticket['description'] = wrap(self.ticket.values.get('description', ''), self.COLS, initial_indent=' ', subsequent_indent=' ', linesep=CRLF) self.reporter = '' self.owner = '' self.hdf.set_unescaped('email.ticket_props', self.format_props()) self.hdf.set_unescaped('email.ticket_body_hdr', self.format_hdr()) self.hdf['ticket.new'] = self.newticket subject = self.format_subj() link = self.env.abs_href.ticket(ticket.id) if not self.newticket: subject = 'Re: ' + subject self.hdf.set_unescaped('email.subject', subject) changes = '' if not self.newticket and modtime: # Ticket change from trac.ticket.web_ui import TicketModule for change in TicketModule(self.env).grouped_changelog_entries( ticket, self.db, when=modtime): if not change['permanent']: # attachment with same time... continue self.hdf.set_unescaped('ticket.change.author', change['author']) self.hdf.set_unescaped('ticket.change.comment', wrap(change['comment'], self.COLS, ' ', ' ', CRLF)) link += '#comment:%s' % str(change.get('cnum', '')) for field, values in change['fields'].iteritems(): old = values['old'] new = values['new'] pfx = 'ticket.change.%s' % field newv = '' if field == 'description': new_descr = wrap(new, self.COLS, ' ', ' ', CRLF) old_descr = wrap(old, self.COLS, '> ', '> ', CRLF) old_descr = old_descr.replace(2*CRLF, CRLF + '>' + CRLF) cdescr = CRLF cdescr += 'Old description:' + 2*CRLF + old_descr + 2*CRLF cdescr += 'New description:' + 2*CRLF + new_descr + CRLF self.hdf.set_unescaped('email.changes_descr', cdescr) elif field == 'cc': (addcc, delcc) = self.diff_cc(old, new) chgcc = '' if delcc: chgcc += wrap(" * cc: %s (removed)" % ', '.join(delcc), self.COLS, ' ', ' ', CRLF) chgcc += CRLF if addcc: chgcc += wrap(" * cc: %s (added)" % ', '.join(addcc), self.COLS, ' ', ' ', CRLF) chgcc += CRLF if chgcc: changes += chgcc self.prev_cc += old and self.parse_cc(old) or [] else: newv = new l = 7 + len(field) chg = wrap('%s => %s' % (old, new), self.COLS - l, '', l * ' ', CRLF) changes += ' * %s: %s%s' % (field, chg, CRLF) if newv: self.hdf.set_unescaped('%s.oldvalue' % pfx, old) self.hdf.set_unescaped('%s.newvalue' % pfx, newv) if changes: self.hdf.set_unescaped('email.changes_body', changes) self.ticket['link'] = link self.hdf.set_unescaped('ticket', self.ticket.values) NotifyEmail.notify(self, ticket.id, subject)
def process_request(self, req): req.perm.assert_permission("LOG_VIEW") mode = req.args.get("mode", "stop_on_copy") path = req.args.get("path", "/") rev = req.args.get("rev") stop_rev = req.args.get("stop_rev") format = req.args.get("format") verbose = req.args.get("verbose") limit = LOG_LIMIT repos = self.env.get_repository(req.authname) normpath = repos.normalize_path(path) rev = unicode(repos.normalize_rev(rev)) if stop_rev: stop_rev = unicode(repos.normalize_rev(stop_rev)) if repos.rev_older_than(rev, stop_rev): rev, stop_rev = stop_rev, rev req.hdf["title"] = path + " (log)" req.hdf["log"] = { "mode": mode, "path": path, "rev": rev, "verbose": verbose, "stop_rev": stop_rev, "browser_href": req.href.browser(path), "changeset_href": req.href.changeset(), "log_href": req.href.log(path, rev=rev), } path_links = get_path_links(req.href, path, rev) req.hdf["log.path"] = path_links if path_links: add_link(req, "up", path_links[-1]["href"], u"Répertoire parent") # The `history()` method depends on the mode: # * for ''stop on copy'' and ''follow copies'', it's `Node.history()` # * for ''show only add, delete'' it's`Repository.get_path_history()` if mode == "path_history": def history(limit): for h in repos.get_path_history(path, rev, limit): yield h else: history = get_existing_node(req, repos, path, rev).get_history # -- retrieve history, asking for limit+1 results info = [] previous_path = repos.normalize_path(path) for old_path, old_rev, old_chg in history(limit + 1): if stop_rev and repos.rev_older_than(old_rev, stop_rev): break old_path = repos.normalize_path(old_path) item = { "rev": str(old_rev), "path": old_path, "log_href": req.href.log(old_path, rev=old_rev), "browser_href": req.href.browser(old_path, rev=old_rev), "changeset_href": req.href.changeset(old_rev), "restricted_href": req.href.changeset(old_rev, new_path=old_path), "change": old_chg, } if not (mode == "path_history" and old_chg == Changeset.EDIT): info.append(item) if old_path and old_path != previous_path and not (mode == "path_history" and old_path == normpath): item["copyfrom_path"] = old_path if mode == "stop_on_copy": break if len(info) > limit: # we want limit+1 entries break previous_path = old_path if info == []: # FIXME: we should send a 404 error here raise TracError( u"Le fichier ou le répertoire '%s' n'existe pas " u"en révision %s ou pour toute révision précédente." % (path, rev), u"Chemin inexistant", ) def make_log_href(path, **args): link_rev = rev if rev == str(repos.youngest_rev): link_rev = None params = {"rev": link_rev, "mode": mode, "limit": limit} params.update(args) if verbose: params["verbose"] = verbose return req.href.log(path, **params) if len(info) == limit + 1: # limit+1 reached, there _might_ be some more next_rev = info[-1]["rev"] next_path = info[-1]["path"] add_link( req, "next", make_log_href(next_path, rev=next_rev), u"Journal des révisions (repartant de %s, rév. %s)" % (next_path, next_rev), ) # now, only show 'limit' results del info[-1] req.hdf["log.items"] = info revs = [i["rev"] for i in info] changes = get_changes(self.env, repos, revs, verbose, req, format) if format == "rss": # Get the email addresses of all known users email_map = {} for username, name, email in self.env.get_known_users(): if email: email_map[username] = email for cs in changes.values(): # For RSS, author must be an email address author = cs["author"] author_email = "" if "@" in author: author_email = author elif email_map.has_key(author): author_email = email_map[author] cs["author"] = author_email cs["date"] = http_date(cs["date_seconds"]) elif format == "changelog": for rev in revs: changeset = repos.get_changeset(rev) cs = changes[rev] cs["message"] = wrap(changeset.message, 70, initial_indent="\t", subsequent_indent="\t") files = [] actions = [] for path, kind, chg, bpath, brev in changeset.get_changes(): files.append(chg == Changeset.DELETE and bpath or path) actions.append(chg) cs["files"] = files cs["actions"] = actions req.hdf["log.changes"] = changes if req.args.get("format") == "changelog": return "log_changelog.cs", "text/plain" elif req.args.get("format") == "rss": return "log_rss.cs", "application/rss+xml" add_stylesheet(req, "common/css/browser.css") add_stylesheet(req, "common/css/diff.css") rss_href = make_log_href(path, format="rss", stop_rev=stop_rev) add_link(req, "alternate", rss_href, "RSS Feed", "application/rss+xml", "rss") changelog_href = make_log_href(path, format="changelog", stop_rev=stop_rev) add_link(req, "alternate", changelog_href, "ChangeLog", "text/plain") return "log.cs", None