o k`4@sddlZddlZddlmZmZddlmZmZddlm Z ddl m Z ddl m Z ddlmZddlmZdd lmZdd lmZmZdd lmZdd lmZdd lmZmZddlmZm Z ddl!m"Z"ddl#m$Z$ddl%m&Z&GdddeZ'Gddde&Z(dS)N) BoolOptionOption) Component implements)NotificationSystem)PermissionCache)Resource)Ticket)TicketChangeEvent)as_int) datetime_nowutc)tag)exception_to_unicode)_ cleandoc_)IRepositoryChangeListenerRepositoryManager)ChangesetModule)format_to_html) WikiMacroBasec@seZdZdZeeeddddZeddddZedd d d Z e dd d dZ e ddd dZ dZ e dZdeefZeddZee dZdZddZddZddZddZd d!Zd"d#Zd$d%Zd&d'Zd(d)Zd*d+Zd,d-Z dS).CommitTicketUpdateraUpdate tickets based on commit messages. This component hooks into changeset notifications and searches commit messages for text in the form of: {{{ command #1 command #1, #2 command #1 & #2 command #1 and #2 }}} Instead of the short-hand syntax "#1", "ticket:1" can be used as well, e.g.: {{{ command ticket:1 command ticket:1, ticket:2 command ticket:1 & ticket:2 command ticket:1 and ticket:2 }}} Using the long-form syntax allows a comment to be included in the reference, e.g.: {{{ command ticket:1#comment:1 command ticket:1#comment:description }}} In addition, the ':' character can be omitted and issue or bug can be used instead of ticket. You can have more than one command in a message. The following commands are supported. There is more than one spelling for each command, to make this as user-friendly as possible. close, closed, closes, fix, fixed, fixes:: The specified tickets are closed, and the commit message is added to them as a comment. references, refs, addresses, re, see:: The specified tickets are left in their current status, and the commit message is added to them as a comment. A fairly complicated example of what you can do is with a commit message of: Changed blah and foo to do this or that. Fixes #10 and #12, and refs #12. This will close #10 and #12, and add a note to #12. ticketcommit_ticket_update_envelopezRequire commands to be enclosed in an envelope. Must be empty or contain two characters. For example, if set to `[]`, then commands must be in the form of `[closes #4]`.z#commit_ticket_update_commands.closez#close closed closes fix fixed fixesz7Commands that close tickets, as a space-separated list.z"commit_ticket_update_commands.refsz addresses re references refs seezCommands that add a reference, as a space-separated list. If set to the special value ``, all tickets referenced by the message will get a reference to the changeset. commit_ticket_update_check_permstruezCheck that the committer has permission to perform the requested operations on the referenced tickets. This requires that the user names be the same for Trac and repository operations.commit_ticket_update_notifyz7Send ticket change notification when updating a ticket.z(?:#|(?:ticket|issue|bug)[: ]?)z([0-9]+(?:#comment:([0-9]+|description))?zH(?P[A-Za-z]*)\s*.?\s*(?P%s(?:(?:[, &]*|[ ]?and[ ]?)%s)*)cCs>t|jddt|jdd}}t||j|S)Nr)reescapeenvelopecompileticket_command)selfbeginendr(?/usr/lib/python3/dist-packages/tracopt/ticket/commit_updater.py command_reszCommitTicketUpdater.command_rez([0-9]+)NcCs>||rdS||j}|||}||||ttdSN) _is_duplicate_parse_messagemessagemake_ticket_comment_update_ticketsr r )r%repos changesetticketscommentr(r(r)changeset_addeds   z#CommitTicketUpdater.changeset_addedcsp||rdS||j}i|dur||jtfdd|D}|||}||||ttdS)Nc3s |] }|dvr|VqdS)rNr().0each old_ticketsr(r) s  z9CommitTicketUpdater.changeset_modified..) r,r-r.dictitemsr/r0r r )r%r1r2 old_changesetr3r4r(r8r)changeset_modifieds    z&CommitTicketUpdater.changeset_modifiedcCs,|j|j|j|jf}||jkr||_dSdS)NFT)revr.authordate _last_cset_id)r%r2cset_idr(r(r)r,s  z!CommitTicketUpdater._is_duplicatec Cs|j|}|}i}|D]3}|dd\}}||}|s+|jdkr+|j}|rA|j |D] } | t | g |q3q|S)z:Parse the commit message and return the ticket references.actionrz)r*finditer_get_functionsgroupgetlower commands_refsstripcmd_refs ticket_refindall setdefaultintappend) r%r. cmd_groups functionsr3mcmdtktsfunctkt_idr(r(r)r-s z"CommitTicketUpdater._parse_messagecCs^|j}t|}t||}|jr|d|j7}|d|j7}td|||j||jfS)z2Create the ticket comment from the changeset data./z In [changeset:"%s" %s]: {{{#!CommitTicketReference repository="%s" revision="%s" %s }}})r?str display_revreponametextwrapdedentr.rK)r%r1r2r? revstringdrevr(r(r)r/s z'CommitTicketUpdater.make_ticket_commentc Cs||}t|j|}|D]m\}}|jd|d} zD|jj+t|j|} || j} |D] } | | || dur;d} q/| rE| |||Wdn1sOwY| r_| | ||j |Wqt y|} z|j d|t| WYd} ~ qd} ~ wwdS)z*Update the tickets with the given comment.zUpdating ticket #%dFTNz0Unexpected error while processing ticket #%s: %s) _authnamerenvr<logdebugdb_transactionr resource save_changes_notifyr@ Exceptionerrorr)r%r3r2r4rAauthnamepermrXcmdssaver ticket_permrUer(r(r)r0s6     z#CommitTicketUpdater._update_ticketsc Csn|jsdStd||||}z t|j|WdSty6}z|jd|jt|WYd}~dSd}~ww)z"Send a ticket update notification.Nchangedz8Failure sending notification on change to ticket #%s: %s) notifyr rrbrircrjidr)r%rrAr@r4eventrpr(r(r)rhszCommitTicketUpdater._notifycCsVi}t|D]"}|dsqt||}t|d|dddD]}|||<q!q|S)z4Create a mapping from commands to command functions.cmd_ commands_Nr)dir startswithgetattrsplit)r%rSr7rWrUr(r(r)rFs     z"CommitTicketUpdater._get_functionscCs |jjddr |jS|jS)ziReturns the author of the changeset, normalizing the casing if [trac] ignore_author_case is true.tracignore_auth_case)rbconfiggetboolr@rI)r%r2r(r(r)ras zCommitTicketUpdater._authnamecCsV||}|jrd|vr|jd||jdSd|d<d|d<|ds)||d<dSdS) N TICKET_MODIFYz0%s doesn't have TICKET_MODIFY permission for #%dFclosedstatusfixed resolutionowner)ra check_permsrcinfors)r%rr2rlrkr(r(r) cmd_closes  zCommitTicketUpdater.cmd_closecCs2|jrd|vr|jd|||jdSdSdS)N TICKET_APPENDz0%s doesn't have TICKET_APPEND permission for #%dF)rrcrrars)r%rr2rlr(r(r)rLs  zCommitTicketUpdater.cmd_refs)!__name__ __module__ __qualname____doc__rrrr"commands_closerJrrrr ticket_prefixticket_referencer$propertyr*r r#rMrBr5r>r,r-r/r0rhrFrarrLr(r(r(r)r;sV3      rc@s(eZdZdZedZejZdddZdS)CommitTicketReferenceMacromessagesayInsert a changeset message into the output. This macro must be called using wiki processor syntax as follows: {{{ {{{ #!CommitTicketReference repository="reponame" revision="rev" }}} }}} where the arguments are the following: - `repository`: the repository containing the changeset - `revision`: the revision of the desired changeset Nc s|pi}|dp d}|d}t|j|}z||}Wnty/|} td|} Yn w|j} |j}|j } |j j j dkrat |j j j rXtfdd|j| Dsatjtddd St|jjr|tjt|j|j jd || d | d d dd Stj| dd S)N repositoryrrevisionrc3s|] }t|kVqdSr+)rP)r6rX resource_idr(r)r:Esz:CommitTicketReferenceMacro.expand_macro..z5(The changeset message doesn't reference this ticket)hint)class_r2)parentT)escape_newlinesr.)rHrrbget_repository get_changesetrirr.r?rfcontextrealmr rsanyrMrNrprrwiki_format_messagesdivrchildpre) r% formatternamecontentargsr\r?r1r2r.rfr(rr) expand_macro5s:      z'CommitTicketReferenceMacro.expand_macror+) rrr_domainr _descriptionrrMrr(r(r(r)r#sr))r r] trac.configrr trac.corerrtrac.notification.apir trac.permr trac.resourcer trac.ticketr trac.ticket.notificationr trac.utilr trac.util.datefmtr r trac.util.htmlrtrac.util.textrtrac.util.translationrrtrac.versioncontrolrr$trac.versioncontrol.web_ui.changesetrtrac.wiki.formatterrtrac.wiki.macrosrrrr(r(r(r)s*%           i