# # add_dir "" # # add_dir "tracvc" # # add_dir "tracvc/mtn" # # add_file ".mtn-ignore" # content [0f95f77b12d4c8c672d445a1d0fe1f1adddbd390] # # add_file "COPYING" # content [b47456e2c1f38c40346ff00db976a2badf36b5e3] # # add_file "README" # content [edf0d00b38304aec3850810fe01b4e4a422fe748] # # add_file "setup.py" # content [06fc7bdc71ab6ffccbb8f4a994047861c8e5b42f] # # add_file "tracvc/__init__.py" # content [c782af2d3d8a2b84f982b211d971115434985b12] # # add_file "tracvc/mtn/__init__.py" # content [da39a3ee5e6b4b0d3255bfef95601890afd80709] # # add_file "tracvc/mtn/automate.py" # content [b27017b76c97e788f6db82703337588ee919a413] # # add_file "tracvc/mtn/backend.py" # content [c1db9df4e08c6538d73e986e5f1ca8e694149e5b] # # add_file "tracvc/mtn/basic_io.py" # content [645fd874583a084b3a062cfd1a61531424c06289] # # set "setup.py" # attr "mtn:execute" # value "true" --- .mtn-ignore +++ .mtn-ignore @@ -0,0 +1,3 @@ +^build +^dist +^TracMonotone.egg-info --- COPYING +++ COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. --- README +++ README @@ -0,0 +1,61 @@ +TracMonotone: Monotone Plugin for Trac +====================================================================== + +Warning + + The plugin is currently neither stable nor optimized for high + performance. Use at your own risk! Some things work, others don't. + + +Prerequisites + + * A Monotone 0.26 binary. Currently, it is expected in + /usr/bin/mtn. In the future this will be configurable. + + * An installation of Trac. A very recent (development) version is + needed. Trunk revision 3239 should work. + + +Installation + + * Create a Python egg: run 'python ./setup.py bdist_egg' in the + plugin's toplevel dir. + + * Put the generated egg into the plugins directory of your project. + + * Add 'repository_type=mtn' and 'repository_dir=path_to_monotone_db' + options to the [trac] section of the conf/trac.init file of your + project. + + * Enable the plugin by adding the option 'tracvc.mtn.* = enabled' to + the [components] section of the conf/trac.ini file. + + * That's it. + + +Known Problems/Missing Features + + * Revisions are always printed as complete 40-char string. + + * Wiki-Link support for tags/branches/... is missing. + + * Changeset displays don't show attr changes. + + * Log on per-file basis is missing. + + * Some operations are very slow. + + * Revision and Cert data is cached forever. This will be adjustable + in the future. + + * Support for trac-admin is missing. (?) + + +Author + + Thomas Moschny + + +Licence + + GPL, see COPYING. --- setup.py +++ setup.py @@ -0,0 +1,16 @@ +#!/usr/bin/python + +from setuptools import setup + +setup( + name = 'TracMonotone', + keywords = 'trac monotone scm plugin mtn', + version = '0.1', + author = 'Thomas Moschny', + author_email = 'address@hidden', + packages = ['tracvc', 'tracvc.mtn'], + data_files = ['README', 'COPYING'], + description = 'Monotone Plugin for Trac', + entry_points={'trac.plugins': 'mtn = tracvc.mtn.backend'}, + license = 'GPL', +) --- tracvc/__init__.py +++ tracvc/__init__.py @@ -0,0 +1,1 @@ +__import__('pkg_resources').declare_namespace(__name__) --- tracvc/mtn/__init__.py +++ tracvc/mtn/__init__.py @@ -0,0 +1,0 @@ --- tracvc/mtn/automate.py +++ tracvc/mtn/automate.py @@ -0,0 +1,239 @@ +""" +Trac Plugin for Monotone + +Copyright 2006, Thomas Moschny (address@hidden) + +{{{ +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or (at +your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA +}}} + +""" + +import os, basic_io, time, calendar + +MT_BINARY = "/usr/bin/mtn" # fixme: default, make this configurable + +class Connection: + """Starts monotone and communicates with it through a pipe.""" + + def __init__(self, db, cmd, binary): + (self.to_child, self.from_child) = os.popen2( + "%s --db=%s %s" % (binary, db, cmd), "b") + + def read(self, maxlen): + return self.from_child.read(maxlen) + + def write(self, data): + return self.to_child.write(data) + + def flush(self): + return self.to_child.flush() + + +class Automate: + """General interface to the 'automate stdio' command.""" + + def __init__(self, db, binary): + self.conn = Connection(db, "automate stdio", binary) + + def _read_until_colon(self): + result = '' + while True: + char = self.conn.read(1) + if char == ':': break + result += char + return result + + # we must not block, so be careful to read only what's expected + def _read_packet(self): + cmd_nr = self._read_until_colon() + status = int(self._read_until_colon()) + cont = self._read_until_colon() + size = int(self._read_until_colon()) + val = self.conn.read(size) + return status, cont, val + + def _get_result(self): + result, cont = ('', '') + while cont != 'l': + status, cont, val = self._read_packet() + result += val + return status, result + + def _write_cmd(self, cmd, args): + def lstring(str): + return "%d:%s" % (len(str), str) + cmdstring = "l" + lstring(cmd) + for arg in args: + cmdstring += lstring(arg) + cmdstring += "e" + self.conn.write(cmdstring) + self.conn.flush() + + def command(self, cmd, *args): + """Send a command to mtn. Returns a tuple (status, result).""" + self._write_cmd(cmd, args) + return self._get_result() + + +class MTN: + + def __init__(self, db, binary = MT_BINARY): + self.automate = Automate(db, binary) + self.manifest_cache = {} + self.certs_cache = {} + self.roots_cache = [] + + def leaves(self): + """Returns a list containing the current leaves.""" + status, result = self.automate.command("leaves") + if status == 0: return result.splitlines() + else: return [] + + def children(self, rev): + """Returns a list of the children of rev.""" + status, result = self.automate.command("children", rev) + if status == 0: return result.splitlines() + else: return [] + + def parents(self, rev): + """Returns a list of the parents of rev.""" + status, result = self.automate.command("parents", rev) + if status == 0: return result.splitlines() + else: return [] + + def all_revs(self): + """Returns a list of all revs in the repository.""" + status, result = self.automate.command("select", '') + if status == 0: return result.splitlines() + else: return [] + + def roots(self): + """Returns a list of all root revisions.""" + if self.roots_cache: return self.roots_cache + status, result = self.automate.command("graph") + if status != 0: return [] + roots = [] + for line in result.splitlines(): + rev_and_parents = line.split(' ') + if len(rev_and_parents) == 1: + roots.append(rev_and_parents[0]) + self.roots_cache = roots + return roots + + def manifest(self, rev): + """ + Returns a processed manifest for rev. + + The manifest is a dictionary: path -> ( kind, file_id, attrs), + with kind being 'file' or 'dir', and attrs being a dictionary + attr_name -> attr_value. + """ + + #print "MANIFEST of %s" % rev + if self.manifest_cache.has_key(rev): + # print "cached" + return self.manifest_cache[rev] + status, result = self.automate.command("get_manifest_of", rev) + manifest = {} + if status != 0: return manifest + for stanza in basic_io.get_stanzas(result): + path, kind, content, attrs = ('', '', '', {}) + for (key, value) in basic_io.get_pairs(stanza): + if key == 'content': content = value + elif key == 'attr': + attrs[value[0]] = value[1] + elif key == 'file' or key == 'dir': + path = '/' + (value or '') + kind = key + if path: manifest[path] = (kind, content, attrs) + self.manifest_cache[rev] = manifest + return manifest + + def certs(self, rev): + """ + Returns a dictionary of certs for rev. There might be more + than one cert of the same name, so their values are collected + in a list. + """ + #print "asking for cert for rev", revb + if self.certs_cache.has_key(rev): + return self.certs_cache[rev] + status, result = self.automate.command("certs", rev) + certs = {} + if status != 0: return certs + for stanza in basic_io.get_stanzas(result): + cert = basic_io.get_hash_from_stanza(stanza) + (name, value) = (cert['name'], cert['value']) + if not certs.has_key(name): + certs[name] = [value] + else: + certs[name].append(value) + self.certs_cache[rev] = certs + return certs + + def dates(self, rev): + """ + Return a sorted list of commit datetimes for this rev. The + datetimes are converted to floats for use by Trac. + """ + dates = [] + for date in self.certs(rev)['date']: + dt = time.strptime(date + " UTC","%Y-%m-%dT%H:%M:%S %Z") + dates.append(calendar.timegm(dt)) + dates.sort() + return dates + + def file(self, id): + """Returns the file contents for a given file id.""" + status, result = self.automate.command("get_file", id) + if status == 0: return result + + def changeset(self, rev): + """Returns changeset dictionary processed to be consumed by Trac.""" + status, result = self.automate.command("get_revision", rev) + changeset = [] + if status != 0: return changeset + renames = {} + for stanza in basic_io.get_stanzas(result): + entry = basic_io.get_hash_from_stanza(stanza) + if entry.has_key('old_revision'): + # oldrev is in effect until overwritten by a later stanza + oldrev = entry['old_revision'] + elif entry.has_key('add_dir'): + path = '/' + entry['add_dir'] + changeset.append((path, 'dir', 'add', None, None)) + elif entry.has_key('add_file'): + path = '/' + entry['add_file'] + changeset.append((path, 'file', 'add', None, None)) + elif entry.has_key('delete'): + path = '/' + entry['delete'] + changeset.append((None, None, 'delete', path, oldrev)) + elif entry.has_key('rename'): + oldpath = '/' + entry['rename'] + newpath = '/' + entry['to'] + # remember renames for edits + renames[newpath] = (oldpath, oldrev) + changeset.append((newpath, None, 'move', oldpath, oldrev)) + elif entry.has_key('patch'): + path = '/' + entry['patch'] + if renames.has_key(path): + (oldpath, oldrev_for_path) = renames[path] + changeset.append((path, 'file', 'edit', oldpath, oldrev_for_path)) + else: + changeset.append((path, 'file', 'edit', path, oldrev)) + # fixme: add clear and set + return changeset --- tracvc/mtn/backend.py +++ tracvc/mtn/backend.py @@ -0,0 +1,319 @@ +""" +Trac Plugin for Monotone + +Copyright 2006, Thomas Moschny (address@hidden) + +{{{ +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or (at +your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA +}}} + +""" + +from trac.versioncontrol.api import Node, Repository, Changeset, IRepositoryConnector, NoSuchNode +from trac.wiki import IWikiSyntaxProvider +from trac.util import shorten_line, escape +from trac.core import * +from cStringIO import StringIO +from automate import MTN + + +class MonotoneConnector(Component): + + implements(IRepositoryConnector, IWikiSyntaxProvider) + + # IRepositoryConnector methods + def __init__(self): + self.repos = {} + + def get_supported_types(self): + """Support for repository type 'mtn'.""" + yield ("mtn", 0) + + def get_repository(self, type, path, authname): + """Return a monotone repository.""" + # note: we don't use type or authname, therefore we can always + # return the same Repository object for the same database path + if not self.repos.has_key(path): + self.repos[path] = MonotoneRepository(path, self.log) + return self.repos[path] + + # IWikiSyntaxProvider methods + def get_wiki_syntax(self): + """We don't add wiki syntax elements.""" + return [] + + def get_link_resolvers(self): + """Add the cset namespace.""" + yield('cset', self._format_cset) + + def _format_cset(self, formatter, ns, rev, label): + """Format a changeset link.""" + # fixme: add support for selectors + repos = self.env.get_repository() + try: + changeset = repos.get_changeset(rev) + return '%s' \ + % (escape(shorten_line(changeset.message)), + formatter.href.changeset(rev), label) + except TracError, e: + return '%s' \ + % (str(e), formatter.href.changeset(rev), label) + + +class MonotoneRepository(Repository): + + def __init__(self, db_path, log): + self.mtn = MTN(db_path) + Repository.__init__(self, 'mtn:%s' % db_path, None, log) + + def get_changeset(self, rev): + """ + Retrieve a Changeset object that describes the changes made in + revision 'rev'. + """ + return MonotoneChangeset(self.mtn, rev) + + def get_changesets(self, start, stop): + """ + Generate Changesets belonging to the given time period (start, stop). + """ + nodes = self.mtn.leaves() + seen = {} + while nodes: + current = nodes.pop(0) + time = self.mtn.dates(current)[0] + if time < start: + continue # assume none of the parents is younger + elif time < stop: + yield MonotoneChangeset(self.mtn, current) + for parent in self.mtn.parents(current): + if parent not in seen: + seen[parent] = 1 + nodes.append(parent) + + def get_node(self, path, rev=None): + """ + Retrieve a Node (directory or file) from the repository at the + given path. If the rev parameter is specified, the version of the + node at that revision is returned, otherwise the latest version + of the node is returned. + """ + return MonotoneNode(self.mtn, rev, path) + + def get_oldest_rev(self): + """ + Return the oldest revision stored in the repository. + Here: Return the oldest root. + """ + revs = dict([(self.mtn.dates(rev)[0], rev) for rev in self.mtn.roots()]) + return revs[sorted(revs.keys())[0]] + + def get_youngest_rev(self): + """ + Return the youngest revision in the repository. + Here: Return the youngest leave. + """ + leaves = dict([(self.mtn.dates(rev)[-1], rev) for rev in self.mtn.leaves()]) + return leaves[sorted(leaves.keys())[-1]] + + def previous_rev(self, rev): + """ + Return the revision immediately preceding the specified revision. + """ + # note: returning only one parent + parents = self.mtn.parents(rev); + parents.sort() + return parents and parents[0] or None + + def next_rev(self, rev, path=''): + """ + Return the revision immediately following the specified revision. + """ + # note: ignoring path for now + # note: returning only one child + children = self.mtn.children(rev); + children.sort() + return children and children[0] or None + + def rev_older_than(self, rev1, rev2): + """ + Return True if rev1 is older than rev2, i.e. if rev1 comes before rev2 + in the revision sequence. + """ + return self.mtn.dates(rev1)[0] < self.mtn.dates(rev2)[-1] + + def get_path_history(self, path, rev=None, limit=None): + """ + Retrieve all the revisions containing this path (no newer than 'rev'). + The result format should be the same as the one of Node.get_history() + """ + raise NotImplementedError + + def normalize_path(self, path): + """ + Return a canonical representation of path in the repos. + We strip trailing slashes except for the root. + """ + return path and path.rstrip('/') or '/' + + def normalize_rev(self, rev): + """ + Return a canonical representation of a revision in the repos. + 'None' is a valid revision value and represents the youngest revision. + """ + return rev or self.get_youngest_rev() + + def short_rev(self, rev): + """ + Return a compact representation of a revision in the repos. + """ + return self.normalize_rev(rev) + + def get_changes(self, old_path, old_rev, new_path, new_rev, + ignore_ancestry=1): + """ + Generator that yields change tuples (old_node, new_node, kind, change) + for each node change between the two arbitrary (path,rev) pairs. + + The old_node is assumed to be None when the change is an ADD, + the new_node is assumed to be None when the change is a DELETE. + """ + raise NotImplementedError + + +class MonotoneNode(Node): + + def __init__(self, mtn, rev, path): + self.mtn = mtn + self.manifest = self.mtn.manifest(rev) + if not self.manifest.has_key(path): + raise NoSuchNode(path, rev) + kind = self.manifest[path][0] # 'file' or 'dir' + Node.__init__(self, path, rev, kind) + self.created_path = path + self.created_rev = rev + self.id = self.manifest[path][1] + + def get_content(self): + """ + Return a stream for reading the content of the node. This method + will return None for directories. The returned object should provide + a read([len]) function. + """ + if self.isdir: + return None + return StringIO(self.mtn.file(self.id)) + + def get_entries(self): + """ + Generator that yields the immediate child entries of a directory, in no + particular order. If the node is a file, this method returns None. + """ + if self.isfile: + return + + def ischild(path): + if path.startswith(self.path) and \ + path.rfind('/') <= len(self.path) and \ + path != self.path: + return True + else: + return False + + for path in filter(ischild, self.manifest.keys()): + yield MonotoneNode(self.mtn, self.rev, path) + + def get_history(self, limit=None): + """ + Generator that yields (path, rev, chg) tuples, one for each revision in which + the node was changed. This generator will follow copies and moves of a + node (if the underlying version control system supports that), which + will be indicated by the first element of the tuple (i.e. the path) + changing. + Starts with an entry for the current revision. + """ + # fixme!! this is only a stub + yield (self.path, self.rev, None) + + def get_properties(self): + """ + Returns a dictionary containing the properties (meta-data) of the node. + The set of properties depends on the version control system. + """ + return self.manifest[self.path][2] + + def get_content_length(self): + if self.isdir: + return None + return len(self.mtn.file(self.id)) + + def get_content_type(self): + if self.isdir: + return None + return '' + + def get_last_modified(self): + # fixme: might be to pessimistic + return self.mtn.dates(self.rev)[-1] + + +class MonotoneChangeset(Changeset): + + def __init__(self, mtn, rev): + certs = mtn.certs(rev) + + # always pick the first + self.messages = certs['changelog'] + message = self.messages[0] + self.dates = mtn.dates(rev) + date = self.dates[0] + self.authors = certs['author'] + author = self.authors[0] + Changeset.__init__(self, rev, message, author, date) + self.mtn = mtn + + def get_changes(self): + """ + Generator that produces a (path, kind, change, base_path, base_rev) + tuple for every change in the changeset, where change can be one of + Changeset.ADD, Changeset.COPY, Changeset.DELETE, Changeset.EDIT or + Changeset.MOVE, and kind is one of Node.FILE or Node.DIRECTORY. + """ + return self.mtn.changeset(self.rev) + + def get_properties(self): + """Generator that provides additional metadata for this changeset. + + Each additional property is a 4 element tuple: + * `name` is the name of the property, + * `text` its value + * `wikiflag` indicates whether the `text` should be interpreted as + wiki text or not + * `htmlclass` enables to attach special formatting to the displayed + property, e.g. `'author'`, `'time'`, `'message'` or `'changeset'`. + """ + for author in self.authors[1:]: + yield('+Author', author, True, 'author') + # fixme: print additional dates? + for parent in self.mtn.parents(self.rev): + yield('Parent', '[cset:%s]' % parent, True, 'changeset') + for child in self.mtn.children(self.rev): + yield('Child', '[cset:%s]' % child, True, 'changeset') + for branch in self.mtn.certs(self.rev)['branch']: + yield('Branch', branch, False, '') + for message in self.messages[1:]: + yield('+Message', message, True, 'message') --- tracvc/mtn/basic_io.py +++ tracvc/mtn/basic_io.py @@ -0,0 +1,74 @@ +""" +Trac Plugin for Monotone + +Copyright 2006, Thomas Moschny (address@hidden) + +{{{ +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or (at +your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA +}}} + +""" +import re + +STR = r'"(?P(\\\\|\\"|[^"])*)" *' +ID = r'\[(?P[a-f0-9]{40})\] *' +VAL = STR + r'|' + ID +LINE = r' *(?P\w+) *(?P(' + VAL + r')+)' + '\n' +STANZA = r'(?P(' + LINE + r')+)(\n|$)' + +STANZA_RULE = re.compile(STANZA, re.DOTALL) +LINE_RULE = re.compile(LINE, re.DOTALL) +VAL_RULE = re.compile(VAL, re.DOTALL) + + +def get_stanzas(raw): + """Generator, splits a basic_io blob into stanzas.""" + for match in STANZA_RULE.finditer(raw): + yield match.group('stanza') + +def get_pairs(stanza): + """Generator, returns (key, value) pairs in one stanza.""" + for match in LINE_RULE.finditer(stanza): + key = match.group('key') + + values = match.group('values') + valarray = [] + for match in VAL_RULE.finditer(values): + value = match.group('strval') or match.group('idval') + if value: + # dequote: replace \" with " + value = re.sub(r'\\"', '"', value) + # dequote: replace \\ with \ + value = re.sub(r'\\\\', r'\\', value) + valarray.append(value) + + if len(valarray) == 1: + yield key, valarray[0] + elif not valarray: + yield key, '' + else: + yield key, valarray + +def get_hash_from_stanza(stanza): + """Return a dictionary of the key, value elements in one stanza""" + return dict([(k,v) for k,v in get_pairs(stanza)]) + +def process(raw): + """Parse any basic io output.""" + result = [] + for stanza in get_stanzas(raw): + result.append(get_hash_from_stanza(stanza)) + return result