Exemple #1
0
    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
Exemple #2
0
    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
Exemple #3
0
    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
Exemple #4
0
    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)
Exemple #5
0
    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)
Exemple #7
0
 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)
Exemple #8
0
 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))
Exemple #10
0
 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
Exemple #11
0
    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'))
Exemple #12
0
    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
Exemple #13
0
    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"))
Exemple #14
0
 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
Exemple #16
0
 def format_hdr(self):
     return '#%s: %s' % (self.ticket.id, wrap(self.ticket['summary'],
                                              self.COLS, linesep='\n',
                                              ambiwidth=self.ambiwidth))
Exemple #17
0
    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
Exemple #18
0
 def format_hdr(self):
     return '#%s: %s' % (self.ticket.id, wrap(self.ticket['summary'],
                                              self.COLS, linesep=CRLF))
Exemple #19
0
 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
Exemple #20
0
    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)
Exemple #21
0
    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
Exemple #22
0
 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)
Exemple #23
0
    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(' &rarr;')))

        return 'revisionlog.html', data, None
Exemple #24
0
    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
Exemple #25
0
 def format_hdr(self):
     return '#%s: %s' % (self.ticket.id, wrap(self.ticket['summary'],
                                              self.COLS, linesep=CRLF))
Exemple #26
0
    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)
Exemple #27
0
 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
Exemple #28
0
    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
Exemple #29
0
    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(" &rarr;")))

        return "revisionlog.html", data, None
Exemple #30
0
    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')
Exemple #31
0
    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(' &rarr;')))

        return 'revisionlog.html', data, None
Exemple #32
0
 def _format_hdr(self, ticket):
     return '#%s: %s' % (ticket.id,
                         wrap(ticket['summary'],
                              self.COLS,
                              linesep='\n',
                              ambiwidth=self.ambiwidth))
Exemple #33
0
    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)
Exemple #34
0
    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
Exemple #35
0
 def format_hdr(self):
     return "#%s: %s" % (self.ticket.id, wrap(self.ticket["summary"], self.COLS, linesep=CRLF))
Exemple #36
0
    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(' &rarr;')))

        return 'revisionlog.html', data
Exemple #37
0
 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)
Exemple #38
0
    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