{"action":"created","comment":{"url":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/comments/40064079","id":40064079,"diff_hunk":"@@ -0,0 +1,309 @@\n+/* This Source Code Form is subject to the terms of the Mozilla Public\n+ * License, v. 2.0. If a copy of the MPL was not distributed with this\n+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n+\n+/**\n+  History Data Adapter\n+\n+    The feature of History adapter is to retrieve History records from FxSync\n+  Service and merge to Places DataStore which has the same functionality, and\n+  the adapter is based on SyncEngine's design to handle 'update' and\n+  'handleConflict' request. History Data Adapter provides Read-Only version\n+  currently, and the basic ideas are included:\n+  - Traverse all records from History Collection (HC).\n+  - Add a new or existed place to Places DataStore (PDS).\n+    - Convert the records from HC to PDS format.\n+    - Merge a new record and the existed one then write to PDS.\n+  - Remove an existed place when receiving a \"deleted: true\" record.\n+**/\n+\n+'use strict';\n+\n+/* global\n+  DataAdapters,\n+  LazyLoader\n+*/\n+\n+const HISTORY_COLLECTION_MTIME = 'LastSyncedStatus::HistoryCollection::mtime';\n+\n+var HistoryHelper = (() => {\n+  var placesStore;\n+  function _ensureStore() {\n+    if (placesStore) {\n+      return Promise.resolve(placesStore);\n+    }\n+    return new Promise(resolve => {\n+      navigator.getDataStores('places').then(stores => {\n+        placesStore = stores[0];\n+        resolve(placesStore);\n+      });\n+    });\n+  }\n+\n+  function setSyncedCollectionMtime(mtime) {\n+    return new Promise(resolve => {\n+      LazyLoader.load(['shared/js/async_storage.js'], () => {\n+        window.asyncStorage.setItem(HISTORY_COLLECTION_MTIME, mtime, resolve);\n+      });\n+    });\n+  }\n+\n+  function getSyncedCollectionMtime() {\n+    return new Promise(resolve => {\n+      LazyLoader.load(['shared/js/async_storage.js'], () => {\n+        window.asyncStorage.getItem(HISTORY_COLLECTION_MTIME, resolve);\n+      });\n+    });\n+  }\n+\n+  function mergeRecordsToDataStore(existed, newPlace) {\n+    if (existed.url !== newPlace.url) {\n+      // The existed record has different url(id) with the new record.\n+      throw new Error('Inconsistent records');\n+    }\n+    if (!existed.fxsyncId) {\n+      existed.fxsyncId = newPlace.fxsyncId;\n+    } else if(existed.fxsyncId !== newPlace.fxsyncId) {\n+      // Two records have different fxSyncId but have the same url(id).\n+      throw new Error('Inconsistent records');\n+    }\n+\n+    existed.visits = existed.visits || [];\n+    if (existed.visits.length === 0 && newPlace.title) {\n+      existed.title = newPlace.title;\n+    } else if (newPlace.visits[0] >= existed.visits[0]) {\n+      existed.title = newPlace.title;\n+    }\n+\n+    newPlace.visits.forEach(item => {\n+      if (existed.visits.indexOf(item) === -1) {\n+        existed.visits.push(item);\n+      }\n+    });\n+\n+    existed.visits.sort((a, b) => {\n+      // sort in descending order\n+      return b - a;\n+    });\n+\n+    return existed;\n+  }\n+\n+  function addPlace(place) {\n+    // 1. get place by url(id of DataStore)\n+    // 2.A merge the existed one and new one if it's an existed one,\n+    //     and update the places.\n+    // 2.B Add a new record with RevisionId.\n+\n+    var id = place.url;\n+    var revisionId;\n+    return _ensureStore().then(placesStore => {\n+      revisionId = placesStore.revisionId;\n+      return placesStore.get(id);\n+    }).then(existedPlace => {\n+      if (existedPlace) {\n+        var newPlace = mergeRecordsToDataStore(existedPlace, place);\n+        return placesStore.put(newPlace, id, revisionId);\n+      } else {\n+        return placesStore.add(place, id, revisionId);\n+      }\n+    }).catch(e => {\n+      console.error(e);\n+      return;\n+    });\n+  }\n+\n+  function updatePlaces(places) {\n+    return new Promise(resolve => {\n+      places.reduce((cur, next) => {\n+        return cur.then(() => {\n+          if (next.deleted) {\n+            return deletePlaceByFxSyncId(next.fxsyncId);\n+          } else {\n+            return addPlace(next);\n+          }\n+        });\n+      }, Promise.resolve()).then(resolve);\n+    });\n+  }\n+\n+  function traverseRecords(each, revisionId) {\n+    return _ensureStore().then(placesStore => {\n+      return new Promise(resolve => {\n+        var cursor = placesStore.sync(revisionId);\n+        runNextTask(cursor);\n+\n+        function runNextTask(cursor) {\n+          cursor.next().then(function(task) {\n+            manageTask(cursor, task);\n+          });\n+        }\n+\n+        function manageTask(cursor, task) {\n+          if (task.operation === 'done') {\n+            resolve(task);\n+            return;\n+          }\n+          each(task).then(() => {\n+            runNextTask(cursor);\n+          });\n+        }\n+      });\n+    });\n+  }\n+\n+  function deletePlace(id) {\n+    return _ensureStore().then(placesStore => {\n+      return placesStore.remove(id);\n+    });\n+  }\n+\n+  function deletePlaceByFxSyncId(fxsyncId) {\n+    var deletingTask;\n+\n+    function each(task) {\n+      if (task.data && task.data.fxsyncId === fxsyncId) {\n+        deletingTask = task;\n+      }\n+      return Promise.resolve();\n+    }\n+\n+    return traverseRecords(each).then(() => {\n+      if (deletingTask) {\n+        return deletePlace(deletingTask.id);\n+      }\n+      return Promise.resolve();\n+    });\n+  }\n+\n+  return {\n+    mergeRecordsToDataStore: mergeRecordsToDataStore,\n+    setSyncedCollectionMtime: setSyncedCollectionMtime,\n+    getSyncedCollectionMtime: getSyncedCollectionMtime,\n+    updatePlaces: updatePlaces,\n+    deletePlaceByFxSyncId: deletePlaceByFxSyncId\n+  };\n+})();\n+\n+DataAdapters.history = {\n+/**\n+    KintoCollection.list() provides a list contains all records sorted by\n+  \"last_modified\" property which indicates the data-modified time in\n+  descending order. The \"last_modified\" value can be recongized as which\n+  records have already synchronized before. It is used to stop early and avoid\n+  the redundant operations which had handled.\n+\n+    When the new adding/updating records are coming from History Collection in\n+  the format [2], it should be converted to the format [1] then stored into PDS.\n+  Besides, the ID from HC records should be written into PDS records as well\n+  for deleting record usage.\n+\n+    When the new deleting records are coming, the ID can help us to find which\n+  record has to be removed.\n+\n+  1. Records stored in Places DataStore (PDS): {\n+    \"url\": \"http://mozilla.org/\", // KEY in PDS\n+    \"title\": \"Mozilla\",\n+    \"icons\": {\n+      \"http://mozilla.org/favicon.ico\": {\n+        \"sizes\": []\n+      }\n+    },\n+    \"frecency\": 1,\n+    \"visits\": [\n+      // NOTICE: date/time without ms\n+      1442247252490, 1442247250001\n+    ],\n+    \"screenshot\": {},\n+    \"visited\": 1442247252490\n+  }\n+\n+  2. Add/Update Records from History Collection (HC): {\n+    \"id\": \"zMgfGkRinh92\",\n+    \"sortindex\": 2000,\n+    \"last_modified\": 1442247272150,\n+    \"payload\": {\n+      \"id\": \"zMgfGkRinh92\",\n+      \"histUri\": \"http://mozilla.org/\",\n+      \"title\": \"Mozilla\",\n+      \"visits\": [\n+         // NOTICE: date/time with ms\n+        { \"date\": 1442247252490018, \"type\": 2 },\n+        { \"date\": 1442247250001234, \"type\": 2 }\n+      ]\n+    },\n+    \"_status\": \"synced\"\n+  }\n+\n+  3. Delete Records from History Collection (HC): {\n+    \"id\": \"_Avscjx5srFy\",\n+    \"sortindex\": 100,\n+    \"last_modified\": 1441985077970,\n+    \"payload\": {\n+      \"id\": \"_Avscjx5srFy\",\n+      \"deleted\": true\n+    },\n+    \"_status\": \"synced\"\n+  }\n+**/\n+  _fullSync(kintoCollection, lastModifiedTime) {\n+    function updateHistoryCollection(list) {\n+      var historyRecords = list.data;\n+      var places = [];\n+      var i;\n+      for (i = 0; i < historyRecords.length; i++) {\n+        if (historyRecords[i].last_modified <= lastModifiedTime) {\n+          break;\n+        }\n+      }\n+      var partialRecords = historyRecords.slice(0, i);\n+      if (partialRecords.length === 0) {\n+        return Promise.resolve(false);\n+      }\n+      partialRecords.forEach(record => {\n+        var payload = record.payload;\n+        if (payload.deleted) {\n+          places.push({\n+            deleted: true,\n+            fxsyncId: payload.id\n+          });\n+          return;\n+        }\n+        if (!payload.histUri || !payload.visits || !payload.visits.length) {\n+          console.warn('Incorrect payload? ', JSON.stringify(payload));\n+          return;\n+        }\n+\n+        places.push({\n+          url: payload.histUri,\n+          title: payload.title,\n+          visits: payload.visits.map(elem => Math.floor(elem.date / 1000)),\n+          fxsyncId: payload.id\n+        });\n+      });\n+\n+      if (places.length === 0) {\n+        return Promise.resolve(false);\n+      }\n+\n+      return HistoryHelper.updatePlaces(places).then(() => {\n+        var latestMtime = partialRecords[0].last_modified;\n+        return HistoryHelper.setSyncedCollectionMtime(latestMtime).then(() => {\n+          Promise.resolve(false);\n+        });\n+      });\n+    }\n+\n+    return kintoCollection.list().then(updateHistoryCollection);\n+  },\n+  update(kintoCollection) {\n+    return HistoryHelper.getSyncedCollectionMtime().then(mtime => {\n+      return this._fullSync(kintoCollection, mtime);\n+    });","path":"apps/sync/js/adapters/history.js","position":302,"original_position":302,"commit_id":"d00362266d7399f6d43c64d7153e2df7970617e7","original_commit_id":"d00362266d7399f6d43c64d7153e2df7970617e7","user":{"login":"ferjm","id":480202,"avatar_url":"https://avatars.githubusercontent.com/u/480202?v=3","gravatar_id":"","url":"https://api.github.com/users/ferjm","html_url":"https://github.com/ferjm","followers_url":"https://api.github.com/users/ferjm/followers","following_url":"https://api.github.com/users/ferjm/following{/other_user}","gists_url":"https://api.github.com/users/ferjm/gists{/gist_id}","starred_url":"https://api.github.com/users/ferjm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ferjm/subscriptions","organizations_url":"https://api.github.com/users/ferjm/orgs","repos_url":"https://api.github.com/users/ferjm/repos","events_url":"https://api.github.com/users/ferjm/events{/privacy}","received_events_url":"https://api.github.com/users/ferjm/received_events","type":"User","site_admin":false},"body":"You can avoid creating the `updateHistoryCollection` helper within `_fullSync` (to be renamed to `_update`)\r\n\r\n    var mtime;\r\n    return HistoryHelper.getSyncedCollectionMtime().then(mtime => {\r\n      mtime = mtime;\r\n      return collection.list();\r\n    }).then(localRecords => {\r\n      return _update(localRecords, mtime);\r\n    });","created_at":"2015-09-22T08:48:21Z","updated_at":"2015-09-22T08:48:21Z","html_url":"https://github.com/mozilla-b2g/gaia/pull/31896#discussion_r40064079","pull_request_url":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/31896","_links":{"self":{"href":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/comments/40064079"},"html":{"href":"https://github.com/mozilla-b2g/gaia/pull/31896#discussion_r40064079"},"pull_request":{"href":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/31896"}}},"pull_request":{"url":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/31896","id":45351702,"html_url":"https://github.com/mozilla-b2g/gaia/pull/31896","diff_url":"https://github.com/mozilla-b2g/gaia/pull/31896.diff","patch_url":"https://github.com/mozilla-b2g/gaia/pull/31896.patch","issue_url":"https://api.github.com/repos/mozilla-b2g/gaia/issues/31896","number":31896,"state":"open","locked":false,"title":"Bug 1191773 - Implement History Adapter for FxSync feature.","user":{"login":"weilonge","id":872741,"avatar_url":"https://avatars.githubusercontent.com/u/872741?v=3","gravatar_id":"","url":"https://api.github.com/users/weilonge","html_url":"https://github.com/weilonge","followers_url":"https://api.github.com/users/weilonge/followers","following_url":"https://api.github.com/users/weilonge/following{/other_user}","gists_url":"https://api.github.com/users/weilonge/gists{/gist_id}","starred_url":"https://api.github.com/users/weilonge/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/weilonge/subscriptions","organizations_url":"https://api.github.com/users/weilonge/orgs","repos_url":"https://api.github.com/users/weilonge/repos","events_url":"https://api.github.com/users/weilonge/events{/privacy}","received_events_url":"https://api.github.com/users/weilonge/received_events","type":"User","site_admin":false},"body":"","created_at":"2015-09-17T22:50:30Z","updated_at":"2015-09-22T08:48:21Z","closed_at":null,"merged_at":null,"merge_commit_sha":"534665a71ef7390419d6787a65858cef5acbf57b","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/31896/commits","review_comments_url":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/31896/comments","review_comment_url":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/comments{/number}","comments_url":"https://api.github.com/repos/mozilla-b2g/gaia/issues/31896/comments","statuses_url":"https://api.github.com/repos/mozilla-b2g/gaia/statuses/d00362266d7399f6d43c64d7153e2df7970617e7","head":{"label":"weilonge:seanlee/DataSync/master/Bug1191773","ref":"seanlee/DataSync/master/Bug1191773","sha":"d00362266d7399f6d43c64d7153e2df7970617e7","user":{"login":"weilonge","id":872741,"avatar_url":"https://avatars.githubusercontent.com/u/872741?v=3","gravatar_id":"","url":"https://api.github.com/users/weilonge","html_url":"https://github.com/weilonge","followers_url":"https://api.github.com/users/weilonge/followers","following_url":"https://api.github.com/users/weilonge/following{/other_user}","gists_url":"https://api.github.com/users/weilonge/gists{/gist_id}","starred_url":"https://api.github.com/users/weilonge/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/weilonge/subscriptions","organizations_url":"https://api.github.com/users/weilonge/orgs","repos_url":"https://api.github.com/users/weilonge/repos","events_url":"https://api.github.com/users/weilonge/events{/privacy}","received_events_url":"https://api.github.com/users/weilonge/received_events","type":"User","site_admin":false},"repo":{"id":24669384,"name":"gaia","full_name":"weilonge/gaia","owner":{"login":"weilonge","id":872741,"avatar_url":"https://avatars.githubusercontent.com/u/872741?v=3","gravatar_id":"","url":"https://api.github.com/users/weilonge","html_url":"https://github.com/weilonge","followers_url":"https://api.github.com/users/weilonge/followers","following_url":"https://api.github.com/users/weilonge/following{/other_user}","gists_url":"https://api.github.com/users/weilonge/gists{/gist_id}","starred_url":"https://api.github.com/users/weilonge/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/weilonge/subscriptions","organizations_url":"https://api.github.com/users/weilonge/orgs","repos_url":"https://api.github.com/users/weilonge/repos","events_url":"https://api.github.com/users/weilonge/events{/privacy}","received_events_url":"https://api.github.com/users/weilonge/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/weilonge/gaia","description":"Gaia is a HTML5-based Phone UI for the Boot 2 Gecko Project. NOTE: For details of what branches are used for what releases, see","fork":true,"url":"https://api.github.com/repos/weilonge/gaia","forks_url":"https://api.github.com/repos/weilonge/gaia/forks","keys_url":"https://api.github.com/repos/weilonge/gaia/keys{/key_id}","collaborators_url":"https://api.github.com/repos/weilonge/gaia/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/weilonge/gaia/teams","hooks_url":"https://api.github.com/repos/weilonge/gaia/hooks","issue_events_url":"https://api.github.com/repos/weilonge/gaia/issues/events{/number}","events_url":"https://api.github.com/repos/weilonge/gaia/events","assignees_url":"https://api.github.com/repos/weilonge/gaia/assignees{/user}","branches_url":"https://api.github.com/repos/weilonge/gaia/branches{/branch}","tags_url":"https://api.github.com/repos/weilonge/gaia/tags","blobs_url":"https://api.github.com/repos/weilonge/gaia/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/weilonge/gaia/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/weilonge/gaia/git/refs{/sha}","trees_url":"https://api.github.com/repos/weilonge/gaia/git/trees{/sha}","statuses_url":"https://api.github.com/repos/weilonge/gaia/statuses/{sha}","languages_url":"https://api.github.com/repos/weilonge/gaia/languages","stargazers_url":"https://api.github.com/repos/weilonge/gaia/stargazers","contributors_url":"https://api.github.com/repos/weilonge/gaia/contributors","subscribers_url":"https://api.github.com/repos/weilonge/gaia/subscribers","subscription_url":"https://api.github.com/repos/weilonge/gaia/subscription","commits_url":"https://api.github.com/repos/weilonge/gaia/commits{/sha}","git_commits_url":"https://api.github.com/repos/weilonge/gaia/git/commits{/sha}","comments_url":"https://api.github.com/repos/weilonge/gaia/comments{/number}","issue_comment_url":"https://api.github.com/repos/weilonge/gaia/issues/comments{/number}","contents_url":"https://api.github.com/repos/weilonge/gaia/contents/{+path}","compare_url":"https://api.github.com/repos/weilonge/gaia/compare/{base}...{head}","merges_url":"https://api.github.com/repos/weilonge/gaia/merges","archive_url":"https://api.github.com/repos/weilonge/gaia/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/weilonge/gaia/downloads","issues_url":"https://api.github.com/repos/weilonge/gaia/issues{/number}","pulls_url":"https://api.github.com/repos/weilonge/gaia/pulls{/number}","milestones_url":"https://api.github.com/repos/weilonge/gaia/milestones{/number}","notifications_url":"https://api.github.com/repos/weilonge/gaia/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/weilonge/gaia/labels{/name}","releases_url":"https://api.github.com/repos/weilonge/gaia/releases{/id}","created_at":"2014-10-01T07:24:02Z","updated_at":"2015-05-04T03:40:54Z","pushed_at":"2015-09-22T03:28:08Z","git_url":"git://github.com/weilonge/gaia.git","ssh_url":"git@github.com:weilonge/gaia.git","clone_url":"https://github.com/weilonge/gaia.git","svn_url":"https://github.com/weilonge/gaia","homepage":"https://wiki.mozilla.org/B2G","size":957374,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":3,"forks":0,"open_issues":3,"watchers":0,"default_branch":"master"}},"base":{"label":"mozilla-b2g:master","ref":"master","sha":"4c530b0a51a319d8acffd4330e23f6c7f6806e4a","user":{"login":"mozilla-b2g","id":1261292,"avatar_url":"https://avatars.githubusercontent.com/u/1261292?v=3","gravatar_id":"","url":"https://api.github.com/users/mozilla-b2g","html_url":"https://github.com/mozilla-b2g","followers_url":"https://api.github.com/users/mozilla-b2g/followers","following_url":"https://api.github.com/users/mozilla-b2g/following{/other_user}","gists_url":"https://api.github.com/users/mozilla-b2g/gists{/gist_id}","starred_url":"https://api.github.com/users/mozilla-b2g/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mozilla-b2g/subscriptions","organizations_url":"https://api.github.com/users/mozilla-b2g/orgs","repos_url":"https://api.github.com/users/mozilla-b2g/repos","events_url":"https://api.github.com/users/mozilla-b2g/events{/privacy}","received_events_url":"https://api.github.com/users/mozilla-b2g/received_events","type":"Organization","site_admin":false},"repo":{"id":2317369,"name":"gaia","full_name":"mozilla-b2g/gaia","owner":{"login":"mozilla-b2g","id":1261292,"avatar_url":"https://avatars.githubusercontent.com/u/1261292?v=3","gravatar_id":"","url":"https://api.github.com/users/mozilla-b2g","html_url":"https://github.com/mozilla-b2g","followers_url":"https://api.github.com/users/mozilla-b2g/followers","following_url":"https://api.github.com/users/mozilla-b2g/following{/other_user}","gists_url":"https://api.github.com/users/mozilla-b2g/gists{/gist_id}","starred_url":"https://api.github.com/users/mozilla-b2g/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mozilla-b2g/subscriptions","organizations_url":"https://api.github.com/users/mozilla-b2g/orgs","repos_url":"https://api.github.com/users/mozilla-b2g/repos","events_url":"https://api.github.com/users/mozilla-b2g/events{/privacy}","received_events_url":"https://api.github.com/users/mozilla-b2g/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/mozilla-b2g/gaia","description":"Gaia is a HTML5-based Phone UI for the Boot 2 Gecko Project. NOTE: For details of what branches are used for what releases, see","fork":false,"url":"https://api.github.com/repos/mozilla-b2g/gaia","forks_url":"https://api.github.com/repos/mozilla-b2g/gaia/forks","keys_url":"https://api.github.com/repos/mozilla-b2g/gaia/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mozilla-b2g/gaia/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mozilla-b2g/gaia/teams","hooks_url":"https://api.github.com/repos/mozilla-b2g/gaia/hooks","issue_events_url":"https://api.github.com/repos/mozilla-b2g/gaia/issues/events{/number}","events_url":"https://api.github.com/repos/mozilla-b2g/gaia/events","assignees_url":"https://api.github.com/repos/mozilla-b2g/gaia/assignees{/user}","branches_url":"https://api.github.com/repos/mozilla-b2g/gaia/branches{/branch}","tags_url":"https://api.github.com/repos/mozilla-b2g/gaia/tags","blobs_url":"https://api.github.com/repos/mozilla-b2g/gaia/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mozilla-b2g/gaia/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mozilla-b2g/gaia/git/refs{/sha}","trees_url":"https://api.github.com/repos/mozilla-b2g/gaia/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mozilla-b2g/gaia/statuses/{sha}","languages_url":"https://api.github.com/repos/mozilla-b2g/gaia/languages","stargazers_url":"https://api.github.com/repos/mozilla-b2g/gaia/stargazers","contributors_url":"https://api.github.com/repos/mozilla-b2g/gaia/contributors","subscribers_url":"https://api.github.com/repos/mozilla-b2g/gaia/subscribers","subscription_url":"https://api.github.com/repos/mozilla-b2g/gaia/subscription","commits_url":"https://api.github.com/repos/mozilla-b2g/gaia/commits{/sha}","git_commits_url":"https://api.github.com/repos/mozilla-b2g/gaia/git/commits{/sha}","comments_url":"https://api.github.com/repos/mozilla-b2g/gaia/comments{/number}","issue_comment_url":"https://api.github.com/repos/mozilla-b2g/gaia/issues/comments{/number}","contents_url":"https://api.github.com/repos/mozilla-b2g/gaia/contents/{+path}","compare_url":"https://api.github.com/repos/mozilla-b2g/gaia/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mozilla-b2g/gaia/merges","archive_url":"https://api.github.com/repos/mozilla-b2g/gaia/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mozilla-b2g/gaia/downloads","issues_url":"https://api.github.com/repos/mozilla-b2g/gaia/issues{/number}","pulls_url":"https://api.github.com/repos/mozilla-b2g/gaia/pulls{/number}","milestones_url":"https://api.github.com/repos/mozilla-b2g/gaia/milestones{/number}","notifications_url":"https://api.github.com/repos/mozilla-b2g/gaia/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mozilla-b2g/gaia/labels{/name}","releases_url":"https://api.github.com/repos/mozilla-b2g/gaia/releases{/id}","created_at":"2011-09-03T01:38:46Z","updated_at":"2015-09-21T20:21:10Z","pushed_at":"2015-09-22T08:46:53Z","git_url":"git://github.com/mozilla-b2g/gaia.git","ssh_url":"git@github.com:mozilla-b2g/gaia.git","clone_url":"https://github.com/mozilla-b2g/gaia.git","svn_url":"https://github.com/mozilla-b2g/gaia","homepage":"https://wiki.mozilla.org/B2G","size":1635054,"stargazers_count":1981,"watchers_count":1981,"language":"JavaScript","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":2443,"mirror_url":null,"open_issues_count":1206,"forks":2443,"open_issues":1206,"watchers":1981,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/31896"},"html":{"href":"https://github.com/mozilla-b2g/gaia/pull/31896"},"issue":{"href":"https://api.github.com/repos/mozilla-b2g/gaia/issues/31896"},"comments":{"href":"https://api.github.com/repos/mozilla-b2g/gaia/issues/31896/comments"},"review_comments":{"href":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/31896/comments"},"review_comment":{"href":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/mozilla-b2g/gaia/pulls/31896/commits"},"statuses":{"href":"https://api.github.com/repos/mozilla-b2g/gaia/statuses/d00362266d7399f6d43c64d7153e2df7970617e7"}}}}