{"action":"created","comment":{"url":"https://api.github.com/repos/m-lab/collectd-mlab/pulls/comments/25572233","id":25572233,"diff_hunk":"@@ -0,0 +1,492 @@\n+# Copyright 2014 Google Inc. All rights reserved.\n+#\n+# Licensed under the Apache License, Version 2.0 (the \"License\");\n+# you may not use this file except in compliance with the License.\n+# You may obtain a copy of the License at\n+#\n+#     http://www.apache.org/licenses/LICENSE-2.0\n+#\n+# Unless required by applicable law or agreed to in writing, software\n+# distributed under the License is distributed on an \"AS IS\" BASIS,\n+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+# See the License for the specific language governing permissions and\n+# limitations under the License.\n+\"\"\"Unit tests for collectd-mlab.\"\"\"\n+\n+import errno\n+import os\n+import StringIO\n+import time\n+import unittest\n+\n+# Third-party modules.\n+import mock\n+\n+# Module under test.\n+import mlab_export\n+\n+# Too many public methods. Hard to avoid for unit tests.\n+# pylint: disable=R0904\n+\n+\n+def disable_show_options(options):\n+  \"\"\"Sets all show options to False.\"\"\"\n+  options.show_nagios = False\n+  options.show_rrdfile = False\n+  options.show_rawmetric = False\n+  options.show_metric = False\n+  options.show_skipped = False\n+  return options\n+\n+\n+def enable_show_options(options):\n+  \"\"\"Sets all show options to True.\"\"\"\n+  options.show_nagios = True\n+  options.show_rrdfile = True\n+  options.show_rawmetric = True\n+  options.show_metric = True\n+  options.show_skipped = True\n+  return options\n+\n+\n+class MlabExport_GlobalTests(unittest.TestCase):\n+\n+  def setUp(self):\n+    self._testdata_dir = os.path.join(\n+        os.path.dirname(mlab_export.__file__), 'testdata')\n+\n+  def testunit_LastTime(self):\n+    fake_tstamp = os.path.join(self._testdata_dir, 'fake.tstamp')\n+    # Artificially set fake timestamp to one hour ago.\n+    one_hour_ago = int(time.time() - 3600)\n+    os.utime(fake_tstamp, (one_hour_ago, one_hour_ago))\n+    \n+    with mlab_export.LastTime(fake_tstamp) as last_time:\n+      return_lock = last_time.lock()\n+      return_tstamp = last_time.get_mtime()\n+\n+    self.assertTrue(return_lock)\n+    self.assertEqual(one_hour_ago, return_tstamp)\n+\n+  def testunit_LastTime_first_creation_RETURNS_zero(self):\n+    fake_tstamp = os.path.join(self._testdata_dir, 'no_such_file.tstamp')\n+    # Make sure that fake timestamp file actually doesn't exist.\n+    if os.path.exists(fake_tstamp):\n+      os.remove(fake_tstamp)\n+    \n+    last_time = mlab_export.LastTime(fake_tstamp)\n+    last_time.open()\n+    return_lock = last_time.lock()\n+    return_tstamp = last_time.get_mtime()\n+    last_time.close()\n+\n+    self.assertTrue(return_lock)\n+    self.assertEqual(0, return_tstamp)\n+\n+  @mock.patch('mlab_export.os.utime')\n+  def testunit_LastTime_update_mtime(self, mock_utime):\n+    fake_tstamp = os.path.join(self._testdata_dir, 'fake.tstamp')\n+    fake_time = int(time.time())\n+\n+    last_time = mlab_export.LastTime(fake_tstamp)\n+    last_time.open()\n+    last_time.update_mtime(fake_time)\n+    last_time.close()\n+    \n+    mock_utime.assert_called_with(fake_tstamp, (fake_time, fake_time))\n+\n+  @mock.patch('mlab_export.fcntl')\n+  def testunit_LastTime_already_locked_RETURNS_False(self, mock_fcntl):\n+    mock_fcntl.flock.side_effect = IOError(errno.EAGAIN, 'fake ioerror')\n+    fake_tstamp = os.path.join(self._testdata_dir, 'fake.tstamp')\n+    \n+    last_time = mlab_export.LastTime(fake_tstamp)\n+    last_time.open()\n+    return_lock = last_time.lock()  # Should fail.\n+    last_time.close()\n+\n+    self.assertFalse(return_lock)\n+\n+  @mock.patch('mlab_export.logging.error')\n+  @mock.patch('mlab_export.LastTime.lock')\n+  def testcover_main_exit(self, mock_last_time_lock, mock_error):\n+    fake_tstamp = os.path.join(self._testdata_dir, 'fake.tstamp')\n+    mlab_export.LAST_EXPORT_FILENAME = fake_tstamp\n+    mock_last_time_lock.return_value = False\n+\n+    self.assertRaises(SystemExit, mlab_export.main)\n+\n+    self.assertTrue(mock_last_time_lock.called)\n+    self.assertTrue(mock_error.called)\n+\n+  @mock.patch('mlab_export.parse_args')\n+  @mock.patch('mlab_export.rrd_list')\n+  def testcover_main_list(self, mock_rrd_list, mock_parse_args):\n+    fake_tstamp = os.path.join(self._testdata_dir, 'fake.tstamp')\n+    mlab_export.LAST_EXPORT_FILENAME = fake_tstamp\n+    mock_options = disable_show_options(mock.Mock())\n+    mock_options.show_nagios = True  # Enable one show option.\n+    mock_parse_args.return_value = mock_options\n+\n+    mlab_export.main()\n+\n+    self.assertTrue(mock_parse_args.called)\n+    self.assertTrue(mock_rrd_list.called)\n+\n+  @mock.patch('mlab_export.parse_args')\n+  @mock.patch('mlab_export.rrd_export')\n+  def testcover_main_export(self, mock_rrd_export, mock_parse_args):\n+    fake_tstamp = os.path.join(self._testdata_dir, 'fake.tstamp')\n+    mlab_export.LAST_EXPORT_FILENAME = fake_tstamp\n+    mock_options = disable_show_options(mock.Mock())\n+    mock_options.update = True\n+    mock_options.rrddir_prefix = mlab_export.RRD_PREFIX\n+    mock_options.ts_start = time.time() - 60*60\n+    mock_options.ts_end = time.time()\n+    mock_parse_args.return_value = mock_options\n+\n+    mlab_export.main()\n+\n+    self.assertTrue(mock_parse_args.called)\n+    self.assertTrue(mock_rrd_export.called)\n+\n+  def testcover_init_global(self):\n+    mlab_export.init_global()\n+\n+  @mock.patch('mlab_export.logging.error')\n+  def testcover_parse_args(self, mock_error):\n+    mlab_export.sys.argv = [\n+        'mlab_export.py', '--bad-argument', '--causes-error']\n+\n+    self.assertRaises(SystemExit, mlab_export.parse_args, 0)\n+\n+    self.assertTrue(mock_error.called)\n+\n+  def testunit_align_timestamp(self):\n+    fake_ts = 1410341339\n+    expected_6 = fake_ts - 5\n+    expected_10 = fake_ts - 9\n+    expected_30 = fake_ts - 29\n+\n+    ts_6 = mlab_export.align_timestamp(fake_ts, 6)\n+    ts_10 = mlab_export.align_timestamp(fake_ts, 10)\n+    ts_30 = mlab_export.align_timestamp(fake_ts, 30)\n+\n+    self.assertEqual(ts_6, expected_6)\n+    self.assertEqual(ts_10, expected_10)\n+    self.assertEqual(ts_30, expected_30)\n+\n+  @mock.patch('mlab_export.time.time')\n+  def testunit_default_end_time(self, mock_time):\n+    mock_time.return_value = 1401401409\n+    mock_options = mock.Mock()\n+    mock_options.length = 3600\n+    mock_options.step = 10\n+    mock_options.update = False\n+    expected_ts_end = 1401400800\n+\n+    returned_ts_end = mlab_export.default_end_time(mock_options)\n+\n+    self.assertEqual(returned_ts_end, expected_ts_end)\n+\n+  @mock.patch('mlab_export.time.time')\n+  def testunit_default_end_time_WHEN_short_length_RAISES_TimeOptionError(\n+      self, mock_time):\n+    mock_time.return_value = 1401401409\n+    mock_options = mock.Mock()\n+    mock_options.length = 600\n+    mock_options.ts_offset = 100\n+    mock_options.step = 10\n+    mock_options.update = True\n+\n+    self.assertRaises(\n+        mlab_export.TimeOptionError, mlab_export.default_end_time, mock_options)\n+\n+  def testunit_default_start_time_WHEN_using_default_behavior(self):\n+    # Default behavior is --update=True and a non-zero ts_previous value\n+    # because the timestamp file aleady exists.\n+    mock_options = mock.Mock()\n+    mock_options.update = True\n+    mock_options.step = 10\n+    ts_previous = 1401401409\n+    expected_start = 1401401400\n+\n+    returned_start = mlab_export.default_start_time(mock_options, ts_previous)\n+\n+    self.assertEqual(returned_start, expected_start)\n+\n+  def testunit_default_start_time_WHEN_first_run(self):\n+    # On first run, ts_previous is zero b/c the timestamp file was just created.\n+    mock_options = mock.Mock()\n+    mock_options.update = True\n+    mock_options.step = 10\n+    mock_options.length = 3600\n+    mock_options.ts_end = 1401401409 + mock_options.length\n+    expected_start = 1401401400\n+    ts_previous = 0\n+\n+    returned_start = mlab_export.default_start_time(mock_options, ts_previous)\n+\n+    self.assertEqual(returned_start, expected_start)\n+\n+  @mock.patch('mlab_export.logging.debug')\n+  def testunit_assert_start_and_end_times(self, mock_logging):\n+    mock_options = mock.Mock()\n+    mock_options.length = 3600\n+    mock_options.ts_end = 1401401409 + mock_options.length\n+    mock_options.ts_start = 1401401409\n+\n+    try:\n+      mlab_export.assert_start_and_end_times(mock_options)\n+    except mlab_export.TimeOptionError as err:\n+      self.fail('Unexpected exception: %s' % err)\n+\n+    self.assertTrue(mock_logging.called)\n+\n+  def testunit_assert_times_WHEN_end_is_before_start_RAISES_TimeOptionError(\n+      self):\n+    mock_options = mock.Mock()\n+    mock_options.length = 3600\n+    mock_options.ts_end = 1401401408\n+    mock_options.ts_start = 1401401409\n+\n+    self.assertRaises(mlab_export.TimeOptionError,\n+                      mlab_export.assert_start_and_end_times, mock_options)\n+\n+  def testunit_assert_start_and_end_times_WHEN_too_short_RAISES_TimeOptionError(\n+      self):\n+    mock_options = mock.Mock()\n+    mock_options.length = 3600\n+    # Start & end should be at least 'length' apart. Subtract 1 to the\n+    # difference too short.\n+    mock_options.ts_end = 1401401409 + mock_options.length - 1\n+    mock_options.ts_start = 1401401409","path":"export/mlab_export_test.py","position":null,"original_position":263,"commit_id":"fc7be16fe64f5b901e72fc503c5c902407a551f1","original_commit_id":"54019e3a741f914547dc8c327867ccc22b16c9c3","user":{"login":"stephen-soltesz","id":1085316,"avatar_url":"https://avatars.githubusercontent.com/u/1085316?v=3","gravatar_id":"","url":"https://api.github.com/users/stephen-soltesz","html_url":"https://github.com/stephen-soltesz","followers_url":"https://api.github.com/users/stephen-soltesz/followers","following_url":"https://api.github.com/users/stephen-soltesz/following{/other_user}","gists_url":"https://api.github.com/users/stephen-soltesz/gists{/gist_id}","starred_url":"https://api.github.com/users/stephen-soltesz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stephen-soltesz/subscriptions","organizations_url":"https://api.github.com/users/stephen-soltesz/orgs","repos_url":"https://api.github.com/users/stephen-soltesz/repos","events_url":"https://api.github.com/users/stephen-soltesz/events{/privacy}","received_events_url":"https://api.github.com/users/stephen-soltesz/received_events","type":"User","site_admin":false},"body":"No problem. Also addressed in: 7eb66eba03850226e3eacab9eb8c7def00735bf4","created_at":"2015-03-01T22:10:15Z","updated_at":"2015-03-01T22:10:15Z","html_url":"https://github.com/m-lab/collectd-mlab/pull/8#discussion_r25572233","pull_request_url":"https://api.github.com/repos/m-lab/collectd-mlab/pulls/8","_links":{"self":{"href":"https://api.github.com/repos/m-lab/collectd-mlab/pulls/comments/25572233"},"html":{"href":"https://github.com/m-lab/collectd-mlab/pull/8#discussion_r25572233"},"pull_request":{"href":"https://api.github.com/repos/m-lab/collectd-mlab/pulls/8"}}},"pull_request":{"url":"https://api.github.com/repos/m-lab/collectd-mlab/pulls/8","id":27999502,"html_url":"https://github.com/m-lab/collectd-mlab/pull/8","diff_url":"https://github.com/m-lab/collectd-mlab/pull/8.diff","patch_url":"https://github.com/m-lab/collectd-mlab/pull/8.patch","issue_url":"https://api.github.com/repos/m-lab/collectd-mlab/issues/8","number":8,"state":"open","locked":false,"title":"Add mlab_export_test.py and testdata.","user":{"login":"stephen-soltesz","id":1085316,"avatar_url":"https://avatars.githubusercontent.com/u/1085316?v=3","gravatar_id":"","url":"https://api.github.com/users/stephen-soltesz","html_url":"https://github.com/stephen-soltesz","followers_url":"https://api.github.com/users/stephen-soltesz/followers","following_url":"https://api.github.com/users/stephen-soltesz/following{/other_user}","gists_url":"https://api.github.com/users/stephen-soltesz/gists{/gist_id}","starred_url":"https://api.github.com/users/stephen-soltesz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stephen-soltesz/subscriptions","organizations_url":"https://api.github.com/users/stephen-soltesz/orgs","repos_url":"https://api.github.com/users/stephen-soltesz/repos","events_url":"https://api.github.com/users/stephen-soltesz/events{/privacy}","received_events_url":"https://api.github.com/users/stephen-soltesz/received_events","type":"User","site_admin":false},"body":"This change includes tests for the mlab_export.py command line tool.","created_at":"2015-01-25T22:40:54Z","updated_at":"2015-03-01T22:10:15Z","closed_at":null,"merged_at":null,"merge_commit_sha":"f381206dd1e8232d8d0ce96c90cdbee4cd120cf7","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/m-lab/collectd-mlab/pulls/8/commits","review_comments_url":"https://api.github.com/repos/m-lab/collectd-mlab/pulls/8/comments","review_comment_url":"https://api.github.com/repos/m-lab/collectd-mlab/pulls/comments{/number}","comments_url":"https://api.github.com/repos/m-lab/collectd-mlab/issues/8/comments","statuses_url":"https://api.github.com/repos/m-lab/collectd-mlab/statuses/fc7be16fe64f5b901e72fc503c5c902407a551f1","head":{"label":"stephen-soltesz:review-export-scripts-test","ref":"review-export-scripts-test","sha":"fc7be16fe64f5b901e72fc503c5c902407a551f1","user":{"login":"stephen-soltesz","id":1085316,"avatar_url":"https://avatars.githubusercontent.com/u/1085316?v=3","gravatar_id":"","url":"https://api.github.com/users/stephen-soltesz","html_url":"https://github.com/stephen-soltesz","followers_url":"https://api.github.com/users/stephen-soltesz/followers","following_url":"https://api.github.com/users/stephen-soltesz/following{/other_user}","gists_url":"https://api.github.com/users/stephen-soltesz/gists{/gist_id}","starred_url":"https://api.github.com/users/stephen-soltesz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stephen-soltesz/subscriptions","organizations_url":"https://api.github.com/users/stephen-soltesz/orgs","repos_url":"https://api.github.com/users/stephen-soltesz/repos","events_url":"https://api.github.com/users/stephen-soltesz/events{/privacy}","received_events_url":"https://api.github.com/users/stephen-soltesz/received_events","type":"User","site_admin":false},"repo":{"id":26023666,"name":"collectd-mlab","full_name":"stephen-soltesz/collectd-mlab","owner":{"login":"stephen-soltesz","id":1085316,"avatar_url":"https://avatars.githubusercontent.com/u/1085316?v=3","gravatar_id":"","url":"https://api.github.com/users/stephen-soltesz","html_url":"https://github.com/stephen-soltesz","followers_url":"https://api.github.com/users/stephen-soltesz/followers","following_url":"https://api.github.com/users/stephen-soltesz/following{/other_user}","gists_url":"https://api.github.com/users/stephen-soltesz/gists{/gist_id}","starred_url":"https://api.github.com/users/stephen-soltesz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stephen-soltesz/subscriptions","organizations_url":"https://api.github.com/users/stephen-soltesz/orgs","repos_url":"https://api.github.com/users/stephen-soltesz/repos","events_url":"https://api.github.com/users/stephen-soltesz/events{/privacy}","received_events_url":"https://api.github.com/users/stephen-soltesz/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/stephen-soltesz/collectd-mlab","description":"Collectd plugin and configuration for Measurement Lab servers.","fork":true,"url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab","forks_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/forks","keys_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/keys{/key_id}","collaborators_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/teams","hooks_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/hooks","issue_events_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/issues/events{/number}","events_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/events","assignees_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/assignees{/user}","branches_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/branches{/branch}","tags_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/tags","blobs_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/git/refs{/sha}","trees_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/git/trees{/sha}","statuses_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/statuses/{sha}","languages_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/languages","stargazers_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/stargazers","contributors_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/contributors","subscribers_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/subscribers","subscription_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/subscription","commits_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/commits{/sha}","git_commits_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/git/commits{/sha}","comments_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/comments{/number}","issue_comment_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/issues/comments{/number}","contents_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/contents/{+path}","compare_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/compare/{base}...{head}","merges_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/merges","archive_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/downloads","issues_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/issues{/number}","pulls_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/pulls{/number}","milestones_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/milestones{/number}","notifications_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/labels{/name}","releases_url":"https://api.github.com/repos/stephen-soltesz/collectd-mlab/releases{/id}","created_at":"2014-10-31T16:22:44Z","updated_at":"2015-01-23T02:20:49Z","pushed_at":"2015-03-01T22:07:21Z","git_url":"git://github.com/stephen-soltesz/collectd-mlab.git","ssh_url":"git@github.com:stephen-soltesz/collectd-mlab.git","clone_url":"https://github.com/stephen-soltesz/collectd-mlab.git","svn_url":"https://github.com/stephen-soltesz/collectd-mlab","homepage":null,"size":370,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"m-lab:master","ref":"master","sha":"5a730295bbbfc097fa42d809ac8743756fad7801","user":{"login":"m-lab","id":3496264,"avatar_url":"https://avatars.githubusercontent.com/u/3496264?v=3","gravatar_id":"","url":"https://api.github.com/users/m-lab","html_url":"https://github.com/m-lab","followers_url":"https://api.github.com/users/m-lab/followers","following_url":"https://api.github.com/users/m-lab/following{/other_user}","gists_url":"https://api.github.com/users/m-lab/gists{/gist_id}","starred_url":"https://api.github.com/users/m-lab/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/m-lab/subscriptions","organizations_url":"https://api.github.com/users/m-lab/orgs","repos_url":"https://api.github.com/users/m-lab/repos","events_url":"https://api.github.com/users/m-lab/events{/privacy}","received_events_url":"https://api.github.com/users/m-lab/received_events","type":"Organization","site_admin":false},"repo":{"id":26023658,"name":"collectd-mlab","full_name":"m-lab/collectd-mlab","owner":{"login":"m-lab","id":3496264,"avatar_url":"https://avatars.githubusercontent.com/u/3496264?v=3","gravatar_id":"","url":"https://api.github.com/users/m-lab","html_url":"https://github.com/m-lab","followers_url":"https://api.github.com/users/m-lab/followers","following_url":"https://api.github.com/users/m-lab/following{/other_user}","gists_url":"https://api.github.com/users/m-lab/gists{/gist_id}","starred_url":"https://api.github.com/users/m-lab/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/m-lab/subscriptions","organizations_url":"https://api.github.com/users/m-lab/orgs","repos_url":"https://api.github.com/users/m-lab/repos","events_url":"https://api.github.com/users/m-lab/events{/privacy}","received_events_url":"https://api.github.com/users/m-lab/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/m-lab/collectd-mlab","description":"Collectd plugin and configuration for Measurement Lab servers.","fork":false,"url":"https://api.github.com/repos/m-lab/collectd-mlab","forks_url":"https://api.github.com/repos/m-lab/collectd-mlab/forks","keys_url":"https://api.github.com/repos/m-lab/collectd-mlab/keys{/key_id}","collaborators_url":"https://api.github.com/repos/m-lab/collectd-mlab/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/m-lab/collectd-mlab/teams","hooks_url":"https://api.github.com/repos/m-lab/collectd-mlab/hooks","issue_events_url":"https://api.github.com/repos/m-lab/collectd-mlab/issues/events{/number}","events_url":"https://api.github.com/repos/m-lab/collectd-mlab/events","assignees_url":"https://api.github.com/repos/m-lab/collectd-mlab/assignees{/user}","branches_url":"https://api.github.com/repos/m-lab/collectd-mlab/branches{/branch}","tags_url":"https://api.github.com/repos/m-lab/collectd-mlab/tags","blobs_url":"https://api.github.com/repos/m-lab/collectd-mlab/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/m-lab/collectd-mlab/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/m-lab/collectd-mlab/git/refs{/sha}","trees_url":"https://api.github.com/repos/m-lab/collectd-mlab/git/trees{/sha}","statuses_url":"https://api.github.com/repos/m-lab/collectd-mlab/statuses/{sha}","languages_url":"https://api.github.com/repos/m-lab/collectd-mlab/languages","stargazers_url":"https://api.github.com/repos/m-lab/collectd-mlab/stargazers","contributors_url":"https://api.github.com/repos/m-lab/collectd-mlab/contributors","subscribers_url":"https://api.github.com/repos/m-lab/collectd-mlab/subscribers","subscription_url":"https://api.github.com/repos/m-lab/collectd-mlab/subscription","commits_url":"https://api.github.com/repos/m-lab/collectd-mlab/commits{/sha}","git_commits_url":"https://api.github.com/repos/m-lab/collectd-mlab/git/commits{/sha}","comments_url":"https://api.github.com/repos/m-lab/collectd-mlab/comments{/number}","issue_comment_url":"https://api.github.com/repos/m-lab/collectd-mlab/issues/comments{/number}","contents_url":"https://api.github.com/repos/m-lab/collectd-mlab/contents/{+path}","compare_url":"https://api.github.com/repos/m-lab/collectd-mlab/compare/{base}...{head}","merges_url":"https://api.github.com/repos/m-lab/collectd-mlab/merges","archive_url":"https://api.github.com/repos/m-lab/collectd-mlab/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/m-lab/collectd-mlab/downloads","issues_url":"https://api.github.com/repos/m-lab/collectd-mlab/issues{/number}","pulls_url":"https://api.github.com/repos/m-lab/collectd-mlab/pulls{/number}","milestones_url":"https://api.github.com/repos/m-lab/collectd-mlab/milestones{/number}","notifications_url":"https://api.github.com/repos/m-lab/collectd-mlab/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/m-lab/collectd-mlab/labels{/name}","releases_url":"https://api.github.com/repos/m-lab/collectd-mlab/releases{/id}","created_at":"2014-10-31T16:22:28Z","updated_at":"2015-02-09T01:40:48Z","pushed_at":"2015-02-09T01:40:46Z","git_url":"git://github.com/m-lab/collectd-mlab.git","ssh_url":"git@github.com:m-lab/collectd-mlab.git","clone_url":"https://github.com/m-lab/collectd-mlab.git","svn_url":"https://github.com/m-lab/collectd-mlab","homepage":null,"size":376,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":4,"forks":1,"open_issues":4,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/m-lab/collectd-mlab/pulls/8"},"html":{"href":"https://github.com/m-lab/collectd-mlab/pull/8"},"issue":{"href":"https://api.github.com/repos/m-lab/collectd-mlab/issues/8"},"comments":{"href":"https://api.github.com/repos/m-lab/collectd-mlab/issues/8/comments"},"review_comments":{"href":"https://api.github.com/repos/m-lab/collectd-mlab/pulls/8/comments"},"review_comment":{"href":"https://api.github.com/repos/m-lab/collectd-mlab/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/m-lab/collectd-mlab/pulls/8/commits"},"statuses":{"href":"https://api.github.com/repos/m-lab/collectd-mlab/statuses/fc7be16fe64f5b901e72fc503c5c902407a551f1"}}}}