o k`@sddlmZddlZddlZddlmZddlmZddlmZm Z m Z m Z m Z ddl mZmZmZddlmZmZmZddlmZmZdd lmZmZmZdd lmZdd lmZm Z m!Z!m"Z"m#Z#m$Z$m%Z%m&Z&m'Z'dd l(m)Z)m*Z*m+Z+dd l,m-Z-ddl.m/Z/ddl0m1Z1ddl2m3Z3Gddde*Z4Gddde+Z5ddZ6ddZ7ddZ8dZ9ddZ:ddZ;Gd d!d!eZGd&d'd'e Z?Gd(d)d)eZ@Gd*d+d+eZAdS),)datetimeN)ISystemInfoProvider)cached) BoolOption IntOption ListOption PathOptionOption) Component TracError implements) FixedOffset to_timestampformat_datetime)Markuptag)exception_to_unicode shorten_line to_unicode_) ChangesetNode RepositoryIRepositoryConnectorInvalidRepositoryNoSuchChangeset NoSuchNodeRepositoryManagerIRepositoryProvider)CACHE_YOUNGEST_REVCachedRepositoryCachedChangeset)IPropertyRenderer)Chrome)IWikiSyntaxProvider)PyGITc@sReZdZdZddZddZddZdd Zd d Zd d Z ddZ dddZ dS)GitCachedRepositoryzGit-specific cached repository.cC ||SN short_revselfrevr/C/usr/lib/python3/dist-packages/tracopt/versioncontrol/git/git_fs.py display_rev+ zGitCachedRepository.display_revcC |j|Sr))reposr+r-pathr/r/r0r+. zGitCachedRepository.short_revcCs.|s|S|jj|}|durt||Sr))get_youngest_revr4git verifyrevrr-r.normrevr/r/r0 normalize_rev1s z!GitCachedRepository.normalize_revcCst|pdSr))r!r8r-r/r/r0r89sz$GitCachedRepository.get_youngest_revcCr3r))r4 child_revsr,r/r/r0r?=r7zGitCachedRepository.child_revsc#stjt||dddD]6\}}t|}t|dkr#|dVqdd|DrEfdd D}t|D]}|Vq:s,qdS) NcSs|jSr))date)csetr/r/r0Csz4GitCachedRepository.get_changesets..)keyrcSsi|]}|j|qSr/)r.).0rAr/r/r0 Hz6GitCachedRepository.get_changesets..cs.g|]}tfddj|Ds|qS)c3s|]}|vVqdSr)r/)rEr) rev_csetsr/r0 Ksz@GitCachedRepository.get_changesets...)anyr4r?rEr.rIr-r/r0 Js    z6GitCachedRepository.get_changesets..) itertoolsgroupbyr!get_changesetslistlensortedpop)r-startstoprCcsetsrevsr.r/rMr0rQ@s"    z"GitCachedRepository.get_changesetscCst||||jSr))GitCachedChangesetr=envr,r/r/r0 get_changesetPz!GitCachedRepository.get_changesetNFc s|rj}||td}jfddfdd}fddfdd }jj> |rF|rFq:j pJd}||krnjj }|d |j tf`Wdn1siwY WddS1szwYdS) Ncs*jdj|fD]\}|dkSdS)Nza SELECT COUNT(*) FROM revision WHERE repos=%s AND rev=%s rF)r[db_queryid)r.countr>r/r0 is_synced\s  z+GitCachedRepository.sync..is_syncedc sd}ttddjD}|d}tdt||D]5}||||}ddt|}jg}||d|}j ||D] \}|t|krPd SqCqd S) Nicss|]\}}|VqdSr)r/)rErefnamer.r/r/r0rJesz?GitCachedRepository.sync..needs_sync..rDr,)z%szXSELECT COUNT(*) FROM revision WHERE repos=%%s AND rev IN (%s)TF) rTsetr9get_refsrangerSjoinr`extendr[r_) max_holdersrYstepidxrevs_holdersargsqueryra)r4r-r/r0 needs_synccs"  z,GitCachedRepository.sync..needs_synccsg}g} ||vr n/|||rn%|||}|s!n|d}t|dkr8|t||ddfqt|D]\}}|D] }|||||<qCq=|S)NTrrD)addappend parent_revsrSreversed)r.seenrY merge_revsrtrl)rbr4traverser/r0rxss*     z*GitCachedRepository.sync..traversec sd}t}jD]O}|rq ||}|rY|}jd||}z ||d}Wnj j j yP}zjd||WYd}~qd}~wwrW||sq |S)NFzTrying to sync revision [%s]TzRevision %s already cached: %r) rer9all_revsr?rUloginfor\insert_changesetr[db_excIntegrityError)updatedrvr.rYrAe)feedbackr4r-rxr/r0 sync_revss.     z+GitCachedRepository.sync..sync_revsTz UPDATE repository SET value=%s WHERE id=%s AND name=%s ) remove_cachemetadata save_metadatagetr r4r[r_sync youngest_revdb_transactionr`) r-rcleanr meta_youngestrqrrepos_youngestdbr/)rrbr4r-rxr0rSs2        $zGitCachedRepository.sync)NF) __name__ __module__ __qualname____doc__r1r+r=r8r?rQr\rr/r/r/r0r'(sr'c@s eZdZdZddZddZdS)rZzGit-specific cached changeset.cs(|jfdd|jjjjddDS)Ncg|] \}}||kfqSr/r/rEkv_revr/r0rNsz3GitCachedChangeset.get_branches..Tresolver.r4r9get_branch_containsr>r/rr0 get_branchess zGitCachedChangeset.get_branchescCs|jjj|jSr)r4r9get_tagsr.r>r/r/r0rzGitCachedChangeset.get_tagsN)rrrrrrr/r/r/r0rZs rZccsRt|}zt|}Wn tyYdSw|D] }d|fV|}qd|fVdS)z/helper for detecting last iteration in for-loopNFT)iternext StopIteration)iterableirnextvr/r/r0_last_iterables   rccs(t|D] \}}|r|V|VqdS)zThe 'intersperse' generator takes an element and an iterable and intersperses that element between the elements of the iterable. inspired by Haskell's ``Data.List.intersperse`` N) enumerate)seprritemr/r/r0 intersperses  rcCs@|dd\}}}tt|dd|}tt||}||fS)zgParse author or committer attribute lines and return corresponding ``(user, timestamp)`` pair. N )rsplitr intr fromtimestampfloat)susertimetz_strtzr/r/r0_parse_user_timesricCs|durdS|t@dvS)NF)i@_file_type_maskmoder/r/r0_is_dir rcCs|durdS|t@dkS)NFrrrr/r/r0 _is_submodulerrc@seZdZeeeeddZddZddZ ddZ d d Z e d d d dZ e d dd dZed dddZed dddZe d dd dZe d dddZe d dddZed ddd Zed d!d d"Zd#d$Zd%d&Zd'S)( GitConnectorc Csd|_z tjj|jd|_Wntjy(}z |jd|WYd}~nd}~ww|jrK|jd|jd|jdsM|jd|jd|jddSdSdS)N)git_binz GitError: %szdetected GIT version %sv_str v_compatiblez3GIT version %s installed not compatible(need >= %s) v_min_str) _versionr&Storage git_versionrGitErrorrzerrorr{)r-rr/r/r0__init__s  zGitConnector.__init__ccs |jrd|jdfVdSdS)NGITr)rr>r/r/r0get_system_infoszGitConnector.get_system_infoc Csd}|j}|r|jjdvr|jjj}n|j}|sz-t|j|}|s)td|| |}| |}t j |dt |j|j||jdWStyb}zt j |dt|ddWYd}~Sd}~ww) Nr^)source changesetzRepository '%s' not foundrclass_titlehrefmissing changesetnofollowrrrel)contextresourcerealmparentr`rr[get_repository Exceptionr=r\rarmessagerrreponamer) r- formattershalabelrrr4rrr/r/r0_format_sha_link s0     zGitConnector._format_sha_linkc#sdjfddfVdS)Nz(?:\b|!)r?[0-9a-fA-F]{%d,40}\bcs$||dr|ddp||S)NrHrD)r startswith)fmtrmatchr>r/r0rB's  z.GitConnector.get_wiki_syntax..)wiki_shortrev_lenr>r/r>r0get_wiki_syntax%s  zGitConnector.get_wiki_syntaxc#sddfdd fVdS)Nrcs|||Sr))r)rrrrrr>r/r0rB,sz1GitConnector.get_link_resolvers..r)r/r>r/r>r0get_link_resolvers+szGitConnector.get_link_resolversr9persistent_cachefalsez)Enable persistent caching of commit tree.cached_repositoryz+Wrap `GitRepository` in `CachedRepository`. shortrev_lenzTThe length at which a sha1 is abbreviated (must be >= 4 and <= 40). wikishortrev_len(zThe minimum length at which a hex-string in wiki content is formatted as a changeset TracLink (must be >= 4 and <= 40). trac_user_rlookupaEnable reverse mapping of git email addresses to trac user ids. Performance will be reduced if there are many users and the `cached_repository` option is `disabled`. A repository resync is required after changing the value of this option. use_committer_idtruezbUse git-committer id instead of git-author id for the changeset ''Author'' field. use_committer_timezsUse git-committer timestamp instead of git-author timestamp for the changeset ''Timestamp'' field. git_fs_encodingutf-8z9Define charset encoding of paths within git repositories.rzPath to the git executable.ccs dVdS)N)r9r/r>r/r/r0get_supported_types[s z GitConnector.get_supported_typesc s|dksJdjkrdksnttddddjkr%dks.nttdddjs7ttdjd sKttd jd jd d jrUfdd}ndd}tj||jj j j j|j j d }jrtj|j}jd||Sjd||S)zGitRepository factory methodr9rz'%(option)s must be in the range [4..40]z[git] shortrev_len)optionz[git] wikishortrev_lenzGIT backend not availablerzFGIT version %(hasver)s installed not compatible (need >= %(needsver)s)rr)hasverneedsverc sz|dd\}}|dd\}}|}Wn ty YdSwjD]\}}}z ||kr7|WSWq&tyAYq&wdS)zReverse map 'real name ' addresses to trac user ids. :return: `None` if lookup failed N)rsplitlowerrr[get_known_users)emailr_uid_name_emailr>r/r0 rlookup_uidss      z0GitConnector.get_repository..rlookup_uidcSdSr)r/rr/r/r0rs)rrrrrrrz!enabled CachedRepository for '%s'z"disabled CachedRepository for '%s')rr rrrr GitRepositoryr[rzrrrrrrr'debug)r-typedirparamsrr4r/r>r0r^sD    zGitConnector.get_repositoryN)rrrr rrr%rrrrrrrrrrrrrrr rrrrr/r/r/r0rsH   rc@s$eZdZeeddZddZdS)CsetPropertyRenderercCs|dvr |dkr dp dS)N)ParentsChildrenBranches git-committer git-authorrevproprrr/)r-namerr/r/r0match_propertysz#CsetPropertyRenderer.match_propertyc s4dfdd |dkr||}ttdfdd|DS|dvrr||}|dkrht|d krhjjjjjtdfd d|D}tt|ttjt t d d d ttjt t dd d Sttdt |S|dvr||\}} dt j j|t| jjdf} t| Stt d)Nc sz.jjj}tj|}||}|dur||}tj |dt |j j ||jdWStyK}ztj |dt|ddWYd}~Sd}~ww)Nrrrrr)rrr`rr[rr\r1rrrrrrrrr)rrrr4rAr)rr-r/r0sha_links    z6CsetPropertyRenderer.render_property..sha_linkrz, c3s|] \}}||VqdSr)r/)rErr.)rr/r0rJsz7CsetPropertyRenderer.render_property..)rrrrDc 3sB|]}|dtjtdtdjj|dddfVqdS)z (diffzIDiff against this parent (show the changes merged from the other parents))old)rr)N)rrrrrrL)r current_sharrr/r0rJs   zmNote: this is a merge changeset, the changes displayed below correspond to the merge itself.hint)rzWUse the (diff) links above to see all the changes relative to each parent.)rrz%s (%s))tzinfoInternal errorr))rrrSrr`rrRbrspanrrmapr$r[ format_authorreqrrstrr ) r-rrrpropsbranchesrY parent_linksuser_time__strr/)rrrr-rr0render_propertysF     z$CsetPropertyRenderer.render_propertyN)rrrr r#rr*r/r/r/r0r s r c@seZdZdZddddddddfdd Zd d Zed d ZedddZ ddZ d@ddZ ddZ ddZ ddZddZddZdAd d!Zd@d"d#Zd$d%Zd&d'Zd(d)Zd*d+Zd,d-Z .dBd/d0ZdCd2d3ZdCd4d5Zd6d7Zd8d9Zd:d;ZdAdd?ZdS)DrzGit repositoryFr9rrcCrr)r/rr/r/r0rBszGitRepository.c  Cs||_||_||_||_||_tdt|d|_| |_| |_ | |_ zt j ||| ||d} | |_Wn!t jyT} z|t| ttd|dpLddd} ~ wwt|d||j|t|j|_dS) Nrr)rrz3"%(name)s" is not readable or not a Git repository.rz (default))rzgit:)r[loggergitrepor rmaxminrrrrr&StorageFactory getInstance_gitrrrrrrrrr#r`_cached_git_id)r-r[r6r rzrrrrrrrfactoryrr/r/r0rs4   zGitRepository.__init__cCs d|_dSr))r1r>r/r/r0closer2zGitRepository.closecCs|jr|jS|jSr))r _cached_gitr1r>r/r/r0r9szGitRepository.gitr2cCs|j|jSr))r1invalidate_rev_cacher>r/r/r0r5%s zGitRepository._cached_gitcC |jSr))r9rr>r/r/r0r8*r2zGitRepository.get_youngest_revNcCs ttd)Nz(Unsupported "Show only adds and deletes")r r)r-r6r.limitr/r/r0get_path_history-r7zGitRepository.get_path_historycCr7r))r9 oldest_revr>r/r/r0get_oldest_rev0r2zGitRepository.get_oldest_revcCs|r|dpdS)N/)stripr5r/r/r0normalize_path3rzGitRepository.normalize_pathcCs,|s|S|j|}|durt||Sr))r8r9r:rr;r/r/r0r=6s  zGitRepository.normalize_revcCr(r)r*r,r/r/r0r1>r2zGitRepository.display_revcCs|jj|||jdS)N)min_len)r9shortrevr=rr,r/r/r0r+AszGitRepository.short_revcCs |||Sr)) _get_noder-r6r.r/r/r0get_nodeEr7zGitRepository.get_nodecCst||||j||Sr))GitNoderz)r-r6r. ls_tree_info historianr/r/r0rAHr]zGitRepository._get_nodeccsF|jD] \}}d|d|fVq|jD] }d|d|fVqdS)Nr%r<tags)r9rr)r-r.bnamebshatr/r/r0get_quickjump_entriesKs z#GitRepository.get_quickjump_entriescCs |jdS)Nurl)r rrBr/r/r0 get_path_urlQr7zGitRepository.get_path_urlccs.|jt|t|D]}||Vq dSr))r9history_timerangerr\)r-rVrWr.r/r/r0rQTs  zGitRepository.get_changesetscCs t||S)zGitChangeset factory method) GitChangesetr,r/r/r0r\Ys zGitRepository.get_changesetcCr(r))r=r,r/r/r0get_changeset_uid]r2zGitRepository.get_changeset_uidrc #s|}|}||krttd|}|}||kr%dSfdd}|d||}||}j||} j|\} j||D]K} | \} } }}}}}t| sdt| rgt j nt j }t j |}|tjkr||||d| nd}|tjkr||||d| nd}||||fVqQWdn1swYWddSWddS1swYdS)NzNot supported in git_fscs jj|dd}dd|DS)NT) recursivecSsi|]}|d|qS)rr/)rEresultr/r/r0rFosz?GitRepository.get_changes..get_tree..)r9ls_tree)r.resultsr- target_pathr/r0get_treemsz+GitRepository.get_changes..get_treer<F)r>r rr=r=r9 get_historian diff_treerr DIRECTORYFILErO action_maprADDrArDELETE)r-old_pathold_revnew_pathnew_revignore_ancestryrWold_treenew_tree old_historian new_historianchgmode1mode2obj1obj2actionr6path2kindchangeold_nodenew_noder/rUr0 get_changes`sX           "zGitRepository.get_changesr^cCr3r))r9hist_next_revisionr-r.r6r/r/r0next_revr7zGitRepository.next_revcCr3r))r9hist_prev_revisionrur/r/r0 previous_revr7zGitRepository.previous_revcCr3r))r9parentsr,r/r/r0rtr7zGitRepository.parent_revscCr3r))r9childrenr,r/r/r0r?r7zGitRepository.child_revscCs|j||||Sr))r9rev_is_anchestor_ofr=)r-rev1rev2r/r/r0rev_older_thanszGitRepository.rev_older_thancCs |dSr))r)r-rr/r/r0clearr7zGitRepository.clearcCsZ|r t|j}|jr|`|jsdS|r)t|j|}|D]}||q"dSdSr))rer9ryrr5r)r- rev_callbackrrYr.r/r/r0rs  zGitRepository.syncNNr))r)r^) rrrrrr4propertyr9rr5r8r9r;r>r=r1r+rCrArKrMrQr\rPrsrvrxrtr?r~rrr/r/r/r0rsL "      )  rc@sdeZdZ  dddZddZddZdd Zd d Zd d ZddZ ddZ dddZ ddZ dS)rDNc Cs2||_||_d|_d|_d|_d|_|r|t|}n|j}|}t j }| d} | r|s2t |||durC|j || }|rC|d}|sJt |||\|_|_|_|_} |j || |}|jdkrft j }n"|jdkrot j}nt|jrxt j }n|jd|ttd|jd||_||_t |||||dS)Nr<rtreeblobzGot unexpected object %rz6Internal error (got unexpected object kind '%(kind)s'))ro)rzr4fs_shafs_permfs_typefs_sizer=rrrrZr=rr9rS last_changer[rwarningr r created_path created_revr) r-r4r6r.rzrErFrropfnamer/r/r0rsJ      zGitNode.__init__cCs<|jd}|jr|s J|S|jr|o|dSttd)z return path as expected by PyGITr<r)r6r=isfileisdirr r)r-rr/r/r0 __git_paths   zGitNode.__git_pathcCs|jsdS|jj|jSr))rr4r9get_filerr>r/r/r0 get_contentszGitNode.get_contentcCs4|jduriSdd|ji}t|jr|j|d<|S)Nrz%06ocommit)rrr)r-r$r/r/r0get_propertiess   zGitNode.get_propertiescCs*|jsdSdd|jj|j|DS)NcSsg|]\}}|qSr/r/)rEr.linenor/r/r0rNrGz+GitNode.get_annotations..)rr4r9blamer._GitNode__git_pathr>r/r/r0get_annotationss zGitNode.get_annotationsc cs|jsdS|js dSt|jrdS|jj|j|jd&}|jj |j| D]}t |j|d|j|j ||Vq+WddS1sHwYdS)Nr<) r.rrrr4r9rXr6r=rSrrDrz)r-rFentr/r/r0 get_entriess"   "zGitNode.get_entriescCs|jrdSdS)Nr^)rr>r/r/r0get_content_typeszGitNode.get_content_typecCs,|jsdS|jdur|jj|j|_|jSr))rrr4r9 get_obj_sizerr>r/r/r0get_content_lengths  zGitNode.get_content_lengthccsP|jsdSt|jj|j||D]\}}|j||s tjntj fVqdSr)) r.rr4r9historyrr6rEDITr])r-r8is_lastr.r/r/r0 get_history szGitNode.get_historycCsV|jsdSz|jj|j\}}t|dd\}}W|S|jd|jYdS)N committerrz9internal error (could not get timestamp from commit '%s'))rr4r9 read_commitr.rrzr)r-msgr$rtsr/r/r0get_last_modified)szGitNode.get_last_modifiedrr)) rrrrrrrrrrrrrr/r/r/r0rDs 1  rDc@s^eZdZdZejejejejejej dZ ddZ ddZ ddZ d d Zd d Zd dZdS)rOzRA Git changeset in the Git repository. Corresponds to a Git commit blob. )AMTDRCcCs|durt|z |j|\}}Wn tjyt|w||_d|vs(Jt|j|}|r6||d<|\}}d}} } } |rLt |\}} |rTt |\} } |j r\| pZ| } n| p_| } |j rh|pf| } n| pk|} | | pr| } t j||||| | ddS)Nrz)r.rauthorr@)rr9rr& GitErrorShar$rRrz_get_committer_and_authorrrrrrr)r-r4rrr$ _childrenrrc_usera_userc_timea_timerrr/r/r0rGs8       zGitChangeset.__init__cCs@d}}d|jvr|jdd}d|jvr|jdd}||fS)Nrrr)r$)r-rrr/r/r0ros   z&GitChangeset._get_committer_and_authorcCsi}d|jvr|jd|d<d|jvr|jd|d<|\}}||kr0t||d<t||d<t|jjj|jdd}|rB||d <|S) NrrrzrrrTrr)r$rrrRr4r9rr.)r- propertiesrrr%r/r/r0rws     zGitChangeset.get_propertiesccs|jd}|r |dnd}|jj||j}|D]6\}}}}}}} | p&|} ||} } t|s4t|r7tjntj } t j |}|t j krHd} } | | || | fVqdS)Nrr)r$rr4r9rsr.rrrZr[rOr\rr])r-rchangesrirjrkrlrmpath1rnr6p_pathp_revror/r/r0rss$    zGitChangeset.get_changescs&|jfdd|jjjddDS)Ncrr/r/rrr/r0rNsz-GitChangeset.get_branches..Trrr>r/rr0rs   zGitChangeset.get_branchescCs|jj|jSr)rr>r/r/r0rszGitChangeset.get_tagsN)rrrrrr]rr^MOVECOPYr\rrrrsrrr/r/r/r0rO8s ( rOc@sVeZdZeeeddddZeddddZeddddZ e dd d d dZ d d Z dS) GitwebProjectsRepositoryProviderzgitweb-repositories projects_listz(Path to a gitweb-formatted projects.list)doc projects_basez%Path to the base of your git projects projects_urlzKTemplate for project URLs. `%s` will be replaced with the repo namesync_per_requestr^z@Repositories to sync on every request (not recommended).c cs(|jsdStj|js|jd|jdSt|jdddk}|D]_}|}|r|d}| d}tj |j |||j vdd }tj |d d }tj|rrt|ddd}| |d <Wdn1smwY|jr||j||d <||fVq"WddS1swYdS) z:Retrieve repositories specified in a `projects_list` file.Nz2The [git] projects_list file was not found at '%s'rHr)encodingrz.gitr9)r rr r  descriptionrL)rosr6existsrzropenr=rrstriprhrrreadr) r-fplineentriesrrr{description_pathfdr/r/r0get_repositoriessB    "z1GitwebProjectsRepositoryProvider.get_repositoriesN) rrrr rrrrr rrrrr/r/r/r0rs r)BrrOrtrac.apir trac.cacher trac.configrrrrr trac.corer r r trac.util.datefmtr rrtrac.util.htmlrrtrac.util.textrrrtrac.util.translationrtrac.versioncontrol.apirrrrrrrrrtrac.versioncontrol.cacher r!r"trac.versioncontrol.web_uir#trac.web.chromer$ trac.wikir%tracopt.versioncontrol.gitr&r'rZrrrrrrrr rrDrOrr/r/r/r0sF    ,         1W<t