Gerrit git cl: cache change detail to save on RPC calls.

R=sergiyb@chromium.org
BUG=681704

Change-Id: I5c320f7e6442a92ae0574e2ca6481a082e82568a
Reviewed-on: https://chromium-review.googlesource.com/430795
Commit-Queue: Andrii Shyshkalov <tandrii@chromium.org>
Reviewed-by: Sergiy Byelozyorov <sergiyb@chromium.org>
diff --git a/git_cl.py b/git_cl.py
index d86902c..b6ec63f 100755
--- a/git_cl.py
+++ b/git_cl.py
@@ -2270,6 +2270,8 @@
     # Lazily cached values.
     self._gerrit_server = None  # e.g. https://chromium-review.googlesource.com
     self._gerrit_host = None    # e.g. chromium-review.googlesource.com
+    # Map from change number (issue) to its detail cache.
+    self._detail_cache = {}
 
   def _GetGerritHost(self):
     # Lazy load of configs.
@@ -2487,10 +2489,36 @@
     gerrit_util.SubmitChange(self._GetGerritHost(), self.GetIssue(),
                              wait_for_merge=wait_for_merge)
 
-  def _GetChangeDetail(self, options=None, issue=None):
+  def _GetChangeDetail(self, options=None, issue=None,
+                       no_cache=False):
+    """Returns details of the issue by querying Gerrit and caching results.
+
+    If fresh data is needed, set no_cache=True which will clear cache and
+    thus new data will be fetched from Gerrit.
+    """
     options = options or []
     issue = issue or self.GetIssue()
     assert issue, 'issue is required to query Gerrit'
+
+    # Normalize issue and options for consistent keys in cache.
+    issue = str(issue)
+    options = [o.upper() for o in options]
+
+    # Check in cache first unless no_cache is True.
+    if no_cache:
+      self._detail_cache.pop(issue, None)
+    else:
+      options_set = frozenset(options)
+      for cached_options_set, data in self._detail_cache.get(issue, []):
+        # Assumption: data fetched before with extra options is suitable
+        # for return for a smaller set of options.
+        # For example, if we cached data for
+        #     options=[CURRENT_REVISION, DETAILED_FOOTERS]
+        #   and request is for options=[CURRENT_REVISION],
+        # THEN we can return prior cached data.
+        if options_set.issubset(cached_options_set):
+          return data
+
     try:
       data = gerrit_util.GetChangeDetail(self._GetGerritHost(), str(issue),
                                          options, ignore_404=False)
@@ -2498,6 +2526,8 @@
       if e.http_status == 404:
         raise GerritChangeNotExists(issue, self.GetCodereviewServer())
       raise
+
+    self._detail_cache.setdefault(issue, []).append((frozenset(options), data))
     return data
 
   def _GetChangeCommit(self, issue=None):