Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

TracMercurialChangesetPlugin v0.1 first commit

  • Loading branch information...
commit c08d7c2c5c05ccebb52c3fd8b1d5065d045b04a8 0 parents
@maraujop authored
156 0.12/README.txt
@@ -0,0 +1,156 @@
+# Copyright (c) 2010
+# Miguel Araujo Perez <miguel.araujo.perez@gmail.com>
+# J. Javier Maestro de la Calle <jjmaestro@ieee.org>
+#
+# GNU General Public Licence (GPL)
+#
+# This work 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 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.
+#
+# GNU GPL v2: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+# GNU GPL v3: http://www.gnu.org/copyleft/gpl-3.0.html
+
+++++++++++++++++++++++++++++
+MercurialChangesetPlugin 0.1
+++++++++++++++++++++++++++++
+
++++++++++++++++++++++
+ About this software
++++++++++++++++++++++
+Latest branch at http://github.com/maraujop/TracMercurialChangesetPlugin
+
+MercurialChangesetPlugin is a Trac plugin that inserts Mercurial repository
+information into Trac's database, thus integrating Mercurial and Trac to a
+fuller extend that the default Trac support.
+
+++++++++++++++
+ Installation
+++++++++++++++
+You can install this software as a normal Trac plugin.
+
+ 1. First uninstall former MercurialChangesetPlugin version, if you have
+ installed it before.
+
+ 2. Change directory to Trac's plugin's directory containing setup.py.
+
+ 3. If you want to install this plugin globally, you will have to install this
+ plugin to the python path:
+
+ $ python setup.py install
+
+ 4. If you want to install this plugin to your trac instance only, create the
+ egg from the repo running:
+
+ $ python setup.py bdist_egg
+
+ Then, copy the generated egg file to the trac instance's plugin directory
+
+ $ cp dist/*.egg /srv/trac/env/plugins
+
+ 5. Enable plugin in config trac.ini:
+
+ [components]
+ mercurialchangeset.* = enabled
+
+ 6. Then sync your repository into Trac by running:
+
+ trac-admin /srv/trac/env/ mercurial sync <repository name>
+
+ <repository name> can be default or a repository defined in Trac's
+ repository table
+
+ 6. Now you can keep your repository permanently synchronized in Trac by
+ configuring a post-commit hook. It's highly recommended that you read
+ http://www.selenic.com/mercurial/hgrc.5.html#hooks
+
+ In the server running Trac and the Mercurial repository, you should add the
+ following to the hooks section of your global or personal hgrc:
+
+ [hooks]
+ changegroup.TracMercurialChangeset = /path/to/hook_script.sh
+
+ Your hook_script.sh should at least have something like this
+ (example uses default repository):
+
+ trac-admin /srv/trac/env/ mercurial afterRevision $HG_NODE `pwd`
+
+
+ If you rather have a commit hook, you can set it like this:
+
+ [hooks]
+ commit.TracMercurialChangeset = /path/to/hook_script.sh
+
+ Your hook_script.sh should then look like this:
+
+ trac-admin /srv/trac/env/ mercurial revision $HG_NODE `pwd`
+
+ This will keep synced with Trac every Mercurial's repository that you have
+ configured within Trac. Thanks to the way the path is checked, if the
+ repository is NOT controlled by Trac, it will let you commit or push
+ withouth failing. This way you can keep all your Trac's repositories
+ synced with only one hook_script
+
++++++++++++++++
+ Prerequisites
++++++++++++++++
+ * Mercurial
+ * Trac (tested on v0.12, might work on others)
+
++++++++++++++++
+ How-To use it
++++++++++++++++
+ This plugin adds four trac-admin commands that will let you synchronize your
+ Mercurial repository with Trac.
+
+ These are: lastRevision, sync, afterRevision and revision.
+
+ You can access the help for each command by doing:
+
+ $ trac-admin /srv/trac/env/ help mercurial <command> (e.g. lastRevision)
+
+ 1. trac-admin /srv/trac/env/ mercurial lastRevision <repository>
+ Synchronize the last changeset in Mercurial repository into Trac's DB
+ revision table.
+
+ 2. trac-admin /srv/trac/env/ mercurial sync <repository>
+ Synchronize the whole Mercurial repository changelog into Trac's DB
+ revision table.
+
+ 3. trac-admin /srv/trac/env/ mercurial afterRevision <revision> <repository>
+ Synchronize all changesets in Mercurial repository greater than the given
+ revision into Trac's DB revision table.
+
+ 4. trac-admin /srv/trac/env/ mercurial revision <revision> <repository>
+ Synchronize the given revision from Mercurial's repository into Trac's DB
+ revision table.
+
+ <revision> can be a revision number or a revision hash
+ <repository> can be a repository name within Trac or the path to the root
+ of the repository controlled by Trac
+
++++++++++++++++++
+ Troubleshooting
++++++++++++++++++
+
+ 1. If the hook is giving you an "Error: Command not found" that's because you
+ don't have the right privileges. The user or role that executes the script
+ should have read and write privileges in you Trac ENV.
+
+ If this is not the case, you can fix it running trac-admin inside the hook
+ script as sudo:
+
+ echo "yourpassword" | sudo -S trac-admin /srv/trac/env/ mercurial afterRevision $HG_NODE default
+
+ Of course this is neither an elegant fix, nor a very secure, but it
+ certainly is a very easy one :)
+
+ 2. You can report bugs you find at the Project's Github, filling an issue.
+ Please activate logging in Trac, so you can send as much information as
+ possible. Please paste the traceback you will find at log/trac.log within
+ your issue. This way it will be easier to fix any error.
17 0.12/mercurialchangeset/__init__.py
@@ -0,0 +1,17 @@
+# Copyright (c) 2010
+# Miguel Araujo Perez <miguel.araujo.perez@gmail.com>
+# J. Javier Maestro de la Calle <jjmaestro@ieee.org>
+#
+# GNU General Public Licence (GPL)
+#
+# This work 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 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.
+#
+# GNU GPL v2: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+# GNU GPL v3: http://www.gnu.org/copyleft/gpl-3.0.html
261 0.12/mercurialchangeset/admin.py
@@ -0,0 +1,261 @@
+# Copyright (c) 2010
+# Miguel Araujo Perez <miguel.araujo.perez@gmail.com>
+# J. Javier Maestro de la Calle <jjmaestro@ieee.org>
+#
+# GNU General Public Licence (GPL)
+#
+# This work 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 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.
+#
+# GNU GPL v2: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+# GNU GPL v3: http://www.gnu.org/copyleft/gpl-3.0.html
+
+from trac.admin import IAdminCommandProvider
+from trac.core import *
+from trac.util.text import printout
+from mercurial import ui, hg, context
+from mercurial.node import short
+import sys, re, os
+
+class MercurialChangesetAdmin(Component):
+ """trac-admin command provider for mercurialchangesets plugin"""
+
+ implements(IAdminCommandProvider)
+
+ def __init__ (self):
+ # Trac's Database connection
+ self.db = self.env.get_db_cnx()
+ self.cursor = self.db.cursor()
+
+ # IAdminCommandProvider methods
+ # ---------------------------------------
+
+ def get_admin_commands(self):
+ yield ('mercurial lastRevision', '<repository>',
+ """Insert Mercurial's last changeset into Trac's DB revision table.\n
+ Example: mercurial lastRevision default\n
+ Example: mercurial lastRevision Important-project\n
+ Example: mercurial lastRevision /path/to/repo
+ """,
+ None, self.sync_last_revision)
+
+ yield ('mercurial sync', '<repository>',
+ """Synchronize the whole Mercurial's repository changelog into Trac's DB revision table.\n
+ Example: mercurial sync default\n
+ Example: mercurial sync Important-project\n
+ Example: mercurial sync /path/to/repo
+ """,
+ None, self.sync_repository)
+
+ yield ('mercurial afterRevision', '<revision> <repository>',
+ """Synchronize all changesets from Mercurial's repository greater than the given revision into Trac's DB revision table.\n
+ Example: mercurial afterRevision 43 default\n
+ Example: mercurial afterRevision 3ece62c442a6f3d5dc20b2b9a9fc779 default\n
+ Example: mercurial afterRevision 3ece62c442a6f3d5dc20b2b9a9fc779 /path/to/repo
+ """,
+ None, self.sync_after_revision)
+
+ yield ('mercurial revision', '<revision> <repository>',
+ """Synchronize the given revision from Mercurial's repository into Trac's DB revision table.\n
+ Example: mercurial revision 33 default\n
+ Example: mercurial revision 3ece62c442a6f3d5dc20b2b9a9fc779 Important-project\n
+ Example: mercurial revision 3ece62c442a6f3d5dc20b2b9a9fc779 /path/to/repo
+ """,
+ None, self.sync_specific_revision)
+
+ # Internal methods
+ # ---------------------------------------
+ def get_repository_id(self, repository):
+ """
+ Returns the repository id inside Trac. This is needed to support
+ multi-repository since v0.12. However, in lower versions of Trac, by simply
+ using 'default' as repository name it should work just fine :)
+ """
+ if (repository == "default"):
+ return 1
+
+ # The repository can be a path to the root of the repository
+ if os.path.isdir(repository):
+ # The default repository is not in the repository table
+ # We need to check it apart, against the Trac's config file
+ default_repository_path = self.config.get("trac", "repository_dir", False)
+ contains = re.compile(repository)
+ if contains.match(default_repository_path):
+ return 1
+
+ sql_string = """
+ SELECT id
+ FROM repository
+ WHERE name = 'dir' AND value LIKE %s
+ """
+ self.cursor.execute(sql_string, ("%%%s%%" % repository,))
+ row = self.cursor.fetchone()
+
+ # If the repository is not controlled by Trac
+ # We exit with successful execution. This way we can make a generic
+ # Mercurial hook that will work with all repositories in the machine
+ if not row:
+ printout("[E] Path",repository,"was not found in repository table, neither in Trac's config. Sync will not be executed")
+ sys.exit(0)
+
+ # Otherwise it's the name of the repository
+ else:
+ sql_string = """
+ SELECT id
+ FROM repository
+ WHERE name = 'name' AND value LIKE %s
+ """
+ self.cursor.execute(sql_string, (repository,))
+ row = self.cursor.fetchone()
+
+ if not row:
+ printout("[E] Sorry, repository name or path not found")
+ sys.exit(1)
+
+ return row[0]
+
+ def get_mercurial_repository(self, repository, repository_id):
+ """
+ Returns a Mercurial repository API object pointing at the repository
+ given by the trac-admin parameter
+ """
+ try:
+ if (repository == "default"):
+ repository_dir = self.config.get("trac", "repository_dir", False)
+ else:
+ if os.path.isdir(repository):
+ repository_dir = repository
+ else:
+ sql_string = """
+ SELECT value
+ FROM repository
+ WHERE name = 'dir' AND id = %s
+ """
+ self.cursor.execute(sql_string, (repository_id,))
+ row = self.cursor.fetchone()
+ if not row:
+ printout("[E] Sorry, but repository name is not defined in repository table")
+ sys.exit(1)
+
+ repository_dir = row[0]
+
+ return hg.repository(ui.ui(), repository_dir)
+
+ except repo.RepoError:
+ printout("[E] Impossible to connect to Mercurial repository at", repository_dir)
+ sys.exit(1)
+
+ def initialize_repository(self, repository):
+ """
+ As the repository name is not known till a trac-admin command is called,
+ we cannot initialize the repository in the constructor. Thus, we must
+ call this method before performing any action on the repository.
+ """
+ self.repository_id = self.get_repository_id(repository)
+ self.repository = self.get_mercurial_repository(repository, self.repository_id)
+
+ def check_revision(self, rev, rev_hash):
+ """
+ Checks if the revision is already in Trac's revision table. If it exists
+ returns True, False otherwise
+ """
+ sql_string = """
+ SELECT rev, author, time, message
+ FROM revision
+ WHERE rev LIKE %s
+ """
+ self.cursor.execute(sql_string, (rev + ":" + rev_hash,))
+ rows = self.cursor.fetchall()
+
+ return len(rows) == 1
+
+ def insert_revision(self, repository_id, rev, rev_hash, time, author, description):
+ """
+ Inserts the changeset information into Trac's revision table.
+ """
+ sql_string = """
+ INSERT INTO revision (repos, rev, time, author, message)
+ VALUES (%s, %s, %s, %s, %s)
+ """
+ # We insert only the first line of the commit description (the summary)
+ # TODO: think of having a config flag to choose from saving summary or the
+ # whole commit message
+ eol = re.compile(r'\r?\n')
+ first_line = eol.split(description.strip())[0]
+
+ self.cursor.execute(sql_string, (repository_id, rev + ":" + rev_hash, time, author, first_line))
+ self.db.commit()
+
+ def sync_revision(self, revision):
+ """
+ Receives a revision number (or revision hash) and an already-initialized
+ repository environment (that's why you cannot call this method directly
+ from a yield command). Then, it gets the revision information from
+ Mercurial repository and inserts it into Trac's revision table if it's
+ not already present.
+ """
+ # Let's get Mercurial commit information
+ node = self.repository.lookup(revision)
+ # Let's get its change context object from the repository
+ ctx = self.repository.changectx(node)
+ rev = str(ctx.rev())
+ rev_hash = short(ctx.node())
+
+ # If revision is not already in Trac's revision table, insert it
+ if not(self.check_revision(rev, rev_hash)):
+ description = ctx.description()
+ time = ctx.date()[0]
+ author = ctx.user()
+ self.log.debug("Inserting revision %s" % rev + ":" + rev_hash)
+ self.insert_revision(self.repository_id, rev, rev_hash, time, author, description)
+ else:
+ self.log.debug("Revision %s already present in Trac" % rev + ":" + rev_hash)
+
+ def sync_last_revision(self, repository):
+ """
+ Synchronize the last changeset in Mercurial repository into Trac's DB
+ revision table.
+ """
+ self.initialize_repository(repository)
+ self.sync_revision(self.repository['tip'].rev())
+
+ def sync_repository(self, repository):
+ """
+ Synchronize the whole Mercurial repository changelog into Trac's DB
+ revision table.
+ """
+ self.initialize_repository(repository)
+
+ for change in self.repository.changelog:
+ self.sync_revision(change.real)
+
+ def sync_after_revision(self, revision, repository):
+ """
+ Synchronize all changesets in Mercurial's repository greater than the
+ given revision into Trac's DB revision table.
+ """
+ self.initialize_repository(repository)
+
+ # We don't know if revision is a number or a hash. Therefore, we have
+ # to get the related change context to get the number of the revision
+ node = self.repository.lookup(revision)
+ ctx = self.repository.changectx(node)
+ initial_revision = ctx.rev()
+ until = len(self.repository.changelog)
+
+ for rev in range(initial_revision, until):
+ self.sync_revision(rev)
+
+ def sync_specific_revision(self, revision, repository):
+ """
+ Synchronize the given revision from Mercurial's repository into Trac's
+ DB revision table.
+ """
+ self.initialize_repository(repository)
+ self.sync_revision(revision)
0  0.12/setup.cfg
No changes.
56 0.12/setup.py
@@ -0,0 +1,56 @@
+# Copyright (c) 2010
+# Miguel Araujo Perez <miguel.araujo.perez@gmail.com>
+# J. Javier Maestro de la Calle <jjmaestro@ieee.org>
+#
+# GNU General Public Licence (GPL)
+#
+# This work 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 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.
+#
+# GNU GPL v2: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+# GNU GPL v3: http://www.gnu.org/copyleft/gpl-3.0.html
+
+from setuptools import find_packages, setup
+
+extra = {}
+
+setup(
+ name = 'TracMercurialChangesetPlugin',
+ version = "0.1",
+ description = "Insert Mercurial's changesets into Trac's DB revision table",
+ author = "Miguel Araujo Perez, J. Javier Maestro de la Calle",
+ author_email = "miguel.araujo.perez@gmail.com",
+ url = "http://github.com/maraujop/TracMercurialChangesetPlugin",
+ license = "GPL",
+ packages = find_packages(exclude=['tests*']),
+ include_package_data = True,
+
+ package_data = { 'mercurialchangeset': [
+ '*.txt'
+ ],
+ },
+
+ zip_safe = True,
+
+ keywords = "trac ticket plugin mercurial hg changeset integration",
+
+ classifiers = [
+ 'Framework :: Trac',
+ ],
+
+ install_requires = [],
+
+ entry_points = """
+ [trac.plugins]
+ mercurialchangeset.admin = mercurialchangeset.admin
+ """,
+
+ **extra
+
+ )
Please sign in to comment.
Something went wrong with that request. Please try again.