Rev 1: Create the BranchFeed plugin, which creates ATOM feeds during commit push in file:///home/robertc/source/baz/plugins/branchrss/trunk/

Robert Collins robertc at robertcollins.net
Mon Jan 29 17:50:01 GMT 2007


------------------------------------------------------------
revno: 1
revision-id: robertc at robertcollins.net-20070129175001-422ce4405f446d40
committer: Robert Collins <robertc at robertcollins.net>
branch nick: trunk
timestamp: Tue 2007-01-30 04:50:01 +1100
message:
  Create the BranchFeed plugin, which creates ATOM feeds during commit push
  and pull for bzr 0.15 or greater.
added:
  README                         readme-20070121145705-sx0o8hd51l0r67r1-1
  TODO                           todo-20070121145705-sx0o8hd51l0r67r1-2
  __init__.py                    __init__.py-20070121145705-sx0o8hd51l0r67r1-3
  branch_feed.py                 branch_feed.py-20070121145705-sx0o8hd51l0r67r1-4
  tests/                         tests-20070121145705-sx0o8hd51l0r67r1-5
  tests/__init__.py              __init__.py-20070121145705-sx0o8hd51l0r67r1-6
  tests/test_branch_feed.py      test_branch_feed.py-20070121145705-sx0o8hd51l0r67r1-7
  tests/test_hook.py             test_hook.py-20070129172729-withaw4ffooledsr-1
=== added file 'README'
--- a/README	1970-01-01 00:00:00 +0000
+++ b/README	2007-01-29 17:50:01 +0000
@@ -0,0 +1,9 @@
+Branch Feed generator
+_____________________
+
+This plugin creates a branch.atom file after every commit, push and pull. The
+amount of detail contained in the branch.atom file currently is always 20
+items, but it is planned to be made configurable.
+
+There is a bugtracker, specification tracker etc at
+https://launchpad.net/bzr-branchfeed/.

=== added file 'TODO'
--- a/TODO	1970-01-01 00:00:00 +0000
+++ b/TODO	2007-01-29 17:50:01 +0000
@@ -0,0 +1,3 @@
+ * proper escaping of the commit message
+ * Allow the filename and number of items to be configured via
+   branch.conf/branches.conf etc.

=== added file '__init__.py'
--- a/__init__.py	1970-01-01 00:00:00 +0000
+++ b/__init__.py	2007-01-29 17:50:01 +0000
@@ -0,0 +1,35 @@
+# BranchFeed: A plugin for bzr to generate rss feeds for branches.
+# Copyright (C) 2007 Canonical Limited.
+#   Author: Robert Collins.
+# 
+# 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
+# 
+
+"""BranchFeed is a plugin for bzr to generate rss feeds for branches.
+
+This plugin creates a branch.atom file after every commit, push and pull. The
+amount of detail contained in the branch.atom file currently is always 20
+items, but it is planned to be made configurable.
+"""
+
+from branch_feed import install_hooks
+
+
+def test_suite():
+    import bzrlib.plugins.branchfeed.tests
+    return bzrlib.plugins.branchfeed.tests.test_suite()
+
+
+install_hooks()

=== added file 'branch_feed.py'
--- a/branch_feed.py	1970-01-01 00:00:00 +0000
+++ b/branch_feed.py	2007-01-29 17:50:01 +0000
@@ -0,0 +1,123 @@
+# BranchFeed: A plugin for bzr to generate rss feeds for branches.
+# Copyright (C) 2007 Canonical Limited.
+#   Author: Robert Collins.
+# 
+# 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
+# 
+
+"""Branch feed generator."""
+
+import itertools
+from string import Template
+import time
+
+from bzrlib.branch import Branch
+from bzrlib.osutils import format_date
+
+
+def install_hooks():
+    """Install the BranchFeed hooks into bzrlib."""
+    Branch.hooks['set_rh'].append(set_rh_hook)
+
+
+def set_rh_hook(branch, rev_history):
+    """Update branch's atom feed from the set_rh hook."""
+    BranchFeed(branch).update()
+
+
+class BranchFeed(object):
+    """A Branch Feed.
+
+    BranchFeeds can create RSS content from a branch.
+
+    Public attributes:
+    now: a time tuple for the time that the feed is executing.
+    item_limit: The maximum number of commit items to include. Set to -1 to
+        disable.
+    """
+
+    ATOM_HEAD_TEMPLATE = u"""<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+  <title>$title</title> 
+  <id>$id</id>
+  <updated>$updated</updated>
+  <generator uri="http://atonie.org/code/atomlog" />
+"""
+    ATOM_ITEM_TEMPLATE = u"""
+<entry>
+  <title>$title</title>
+  <id>$id</id>
+  <author>
+    <name>$author</name>
+  </author>
+  <updated>$updated</updated>
+  <content type="xhtml">
+    <div xmlns="http://www.w3.org/1999/xhtml">
+     <p>$content</p>
+    </div>
+  </content>
+</entry>
+"""
+
+    def __init__(self, branch, now=None):
+        """Create a BranchFeed on branch.
+
+        :param branch: The branch to generate a feed for.
+        :param now: The current time since epochs in seconds.
+            If not supplied, time.time is used.
+        """
+        self.branch = branch
+        self.item_limit = 20
+        self.now = now or time.time()
+
+    def iter_revisions(self):
+        """Walk the revisions to be included."""
+        history = self.branch.revision_history()
+        tip = len(history)
+        if self.item_limit != -1:
+            history = history[-self.item_limit:]
+        return itertools.izip(xrange(tip, 0, -1),
+            reversed(self.branch.repository.get_revisions(history)))
+
+    def generate(self):
+        """Generate the feed content.
+
+        The title of the feed is pulled from the branch nick, and the
+        id from the branch URL.
+
+        :return: a bytestring containing the feed.
+        """
+        template = Template(self.ATOM_HEAD_TEMPLATE)
+        feed = template.substitute({
+            'title':self.branch.nick,
+            'id':str(self.branch.base),
+            'updated':format_date(self.now, 0, 'utc', '%Y-%m-%dT%H:%M:%SZ', show_offset=False)
+            })
+        template = Template(self.ATOM_ITEM_TEMPLATE)
+        for revno, revision in self.iter_revisions():
+            feed += template.substitute({
+                'title':'revision %s' % revno,
+                'id':revision.revision_id,
+                'author':revision.committer,
+                'updated':format_date(revision.timestamp, revision.timezone or 0, 'utc', '%Y-%m-%dT%H:%M:%SZ', show_offset=False),
+                'content':revision.message,
+                })
+        return feed.encode('utf8')
+
+    def update(self):
+        """Update the feed in the branch directory."""
+        # This is a little ugly: poking under the hood.
+        self.branch.control_files._transport.put_bytes(
+            'branch.atom', self.generate())

=== added directory 'tests'
=== added file 'tests/__init__.py'
--- a/tests/__init__.py	1970-01-01 00:00:00 +0000
+++ b/tests/__init__.py	2007-01-29 17:50:01 +0000
@@ -0,0 +1,33 @@
+# BranchFeed: A plugin for bzr to generate rss feeds for branches.
+# Copyright (C) 2007 Canonical Limited.
+#   Author: Robert Collins.
+# 
+# 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
+# 
+
+
+"""Tests for the branchfeed plugin."""
+
+
+from bzrlib.tests.TestUtil import TestLoader, TestSuite
+
+
+def test_suite():
+    module_names = [
+        'bzrlib.plugins.branchfeed.tests.test_branch_feed',
+        'bzrlib.plugins.branchfeed.tests.test_hook',
+        ]
+    loader = TestLoader()
+    return loader.loadTestsFromModuleNames(module_names)

=== added file 'tests/test_branch_feed.py'
--- a/tests/test_branch_feed.py	1970-01-01 00:00:00 +0000
+++ b/tests/test_branch_feed.py	2007-01-29 17:50:01 +0000
@@ -0,0 +1,156 @@
+# BranchFeed: A plugin for bzr to generate rss feeds for branches.
+# Copyright (C) 2007 Canonical Limited.
+#   Author: Robert Collins.
+# 
+# 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
+# 
+
+"""Tests for the creation of the atom content."""
+
+import time
+
+from bzrlib.osutils import format_date
+from bzrlib.tests import TestCaseWithMemoryTransport
+
+from bzrlib.plugins.branchfeed.branch_feed import BranchFeed
+
+class TestGenerateAtom(TestCaseWithMemoryTransport):
+
+    def test_construct(self):
+        branch = self.make_branch(".")
+        branch.nick = "a nick"
+        feed = BranchFeed(branch)
+        self.assertNotEqual(feed.now, None)
+        feed = BranchFeed(branch, "now")
+        self.assertEqual(feed.now, "now")
+        self.assertEqual(feed.item_limit, 20)
+
+    def test_empty_branch(self):
+        branch = self.make_branch(".")
+        branch.nick = "a nick"
+        currenttime = time.time()
+        feed = BranchFeed(branch, now=currenttime)
+        content = feed.generate()
+        self.assertEqual("""<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+  <title>a nick</title> 
+  <id>%s</id>
+  <updated>%s</updated>
+  <generator uri="http://atonie.org/code/atomlog" />
+""" % (branch.base, format_date(currenttime, 0, 'utc', '%Y-%m-%dT%H:%M:%SZ', False)),
+            content)
+
+    def test_generate(self):
+        """Generate a feed."""
+        commitnow = time.time()
+        rev1time = commitnow - 60*60*24 - 1
+        rev2time = commitnow - 60*60*24 + 1
+        tree = self.make_branch_and_memory_tree('.')
+        tree.branch.nick = 'a nick'
+        tree.lock_write()
+        tree.add('')
+        # two commits to check outputting two records
+        tree.commit('1', rev_id="revid1", committer="author1 at localhost",
+            timestamp=rev1time, timezone = 0)
+        tree.commit('2', rev_id="revid2", committer="author2 at localhost",
+            timestamp=rev2time, timezone = 0)
+        tree.unlock()
+        branch = tree.branch
+        feed = BranchFeed(branch, now=commitnow)
+        content = feed.generate()
+        self.assertEqualDiff("""<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+  <title>a nick</title> 
+  <id>%s</id>
+  <updated>%s</updated>
+  <generator uri="http://atonie.org/code/atomlog" />
+
+<entry>
+  <title>revision 2</title>
+  <id>revid2</id>
+  <author>
+    <name>author2 at localhost</name>
+  </author>
+  <updated>%s</updated>
+  <content type="xhtml">
+    <div xmlns="http://www.w3.org/1999/xhtml">
+     <p>2</p>
+    </div>
+  </content>
+</entry>
+
+<entry>
+  <title>revision 1</title>
+  <id>revid1</id>
+  <author>
+    <name>author1 at localhost</name>
+  </author>
+  <updated>%s</updated>
+  <content type="xhtml">
+    <div xmlns="http://www.w3.org/1999/xhtml">
+     <p>1</p>
+    </div>
+  </content>
+</entry>
+"""  % (branch.base,
+        format_date(commitnow, 0, 'utc', '%Y-%m-%dT%H:%M:%SZ', False),
+        format_date(rev2time, 0, 'utc', "%Y-%m-%dT%H:%M:%SZ", False),
+        format_date(rev1time, 0, 'utc', "%Y-%m-%dT%H:%M:%SZ", False),
+        ),
+        content)
+
+    def test_number_limited_branch(self):
+        commitnow = time.time()
+        tree = self.make_branch_and_memory_tree('.')
+        tree.lock_write()
+        tree.add('')
+        # two commits at the same time, which we then limit to select only
+        # the topologically latest by the item_limit.
+        tree.commit('rev one', rev_id="revid1", committer="author1 at localhost",
+            timestamp=commitnow, timezone = 0)
+        tree.commit('rev two', rev_id="revid2", committer="author2 at localhost",
+            timestamp=commitnow, timezone = 0)
+        tree.unlock()
+        branch = tree.branch
+        currenttime = time.time()
+        feed = BranchFeed(branch, now=currenttime)
+        feed.item_limit = 1
+        self.assertEqual(
+            zip([2], list(branch.repository.get_revisions(['revid2']))),
+            list(feed.iter_revisions()))
+
+    def test_ordering(self):
+        """iter_revisions should return the newest first."""
+        tree = self.make_branch_and_memory_tree('.')
+        tree.lock_write()
+        tree.add('')
+        tree.commit('1', rev_id="1")
+        tree.commit('2', rev_id="2")
+        tree.unlock()
+        branch = tree.branch
+        feed = BranchFeed(branch)
+        self.assertEqual(
+            zip([2, 1], list(branch.repository.get_revisions(['2', '1']))),
+            list(feed.iter_revisions()))
+
+    def test_update_branch(self):
+        """update() writes the generated content to the branch."""
+        branch = self.make_branch(".")
+        branch.nick = "a nick"
+        feed = BranchFeed(branch)
+        feed.update()
+        content = feed.generate()
+        self.assertEqualDiff(content,
+            branch.control_files._transport.get('branch.atom').read())

=== added file 'tests/test_hook.py'
--- a/tests/test_hook.py	1970-01-01 00:00:00 +0000
+++ b/tests/test_hook.py	2007-01-29 17:50:01 +0000
@@ -0,0 +1,59 @@
+# BranchFeed: A plugin for bzr to generate rss feeds for branches.
+# Copyright (C) 2007 Canonical Limited.
+#   Author: Robert Collins.
+# 
+# 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
+# 
+
+"""Tests for the hook installation."""
+
+from bzrlib.branch import Branch
+from bzrlib.tests import TestCaseWithMemoryTransport
+
+from bzrlib.plugins.branchfeed import branch_feed
+
+
+class TestHooksAreMade(TestCaseWithMemoryTransport):
+
+    def test_set_rh_installed(self):
+        """The _preserved hooks should have set_rh in them."""
+        self.assertTrue(branch_feed.set_rh_hook in
+            self._preserved_hooks['set_rh'])
+
+    def test_install_hooks(self):
+        """branch_feed.set_rh_hook should be in Branch.hooks['set_rh']."""
+        branch_feed.install_hooks()
+        self.assertTrue(branch_feed.set_rh_hook in Branch.hooks['set_rh'])
+
+    def test_set_rh_hook(self):
+        """The set_rh_hook should create a BranchFeed and call update."""
+        # change the BranchFeed object in the branch_feed module to test
+        # the api use.
+        calls = []
+        class SampleFeed(object):
+
+            def __init__(self, branch):
+                calls.append(('init', branch))
+
+            def update(self):
+                calls.append(('update', ))
+
+        original_class = branch_feed.BranchFeed
+        try:
+            branch_feed.BranchFeed = SampleFeed
+            branch_feed.set_rh_hook('branch', 'history')
+        finally:
+            branch_feed.BranchFeed = original_class
+        self.assertEqual([('init', 'branch'), ('update', )], calls)



More information about the bazaar-commits mailing list