Merge commits up to 224a31a765eb943443640301a715d2d4eb005b79

This pulls in upstream changes:
224a31a765eb 2017-07-10 14:46:25 -0700 init: add missing submodule arg
b54343d9fd68 2017-07-10 10:31:24 +0200 Tell the user if it will upload a draft
8419ab22d69e 2017-06-16 12:09:06 +0200 sync: Continue job if some fetchs failed but force-broken is set
913327f10c73 2017-06-05 15:01:41 +0200 Add a newline after "Fetching projects" progress output
35d22217a5ed 2016-11-01 11:24:52 -0700 Ensure repo waits for child process to terminate
e0684addeeb9 2017-04-05 00:02:59 -0700 sync: Add support to dump a JSON event log of all sync events.
fef9f21b28d3 2016-11-01 18:28:01 -0700 Fix misplaced file separator string.replace call
6a470be220ab 2016-11-01 11:25:15 -0700 Use OS file separator
c79d3b8fd173 2017-01-24 21:47:50 +0900 init: allow relative path on --reference argument
aa90021fbc33 2017-04-05 13:50:52 -0700 Set result if sys.exit() is called by subcommand.
fddfa6fbac4f 2017-01-09 23:47:52 +0000 Adding include element into the top-level element
eec726c6d880 2016-10-07 10:52:08 +0200 Add option REPO_IGNORE_SSH_INFO to ignore ssh_info
666debc5180f 2017-05-26 21:53:34 +0900 gitc_delete: Remove unused imports
c354a9b92268 2017-05-26 21:52:12 +0900 abandon: fix usage of undefined variable
06848b24150c 2017-05-26 21:44:57 +0900 Update .mailmap
e4e94d26ae81 2017-03-21 16:05:12 -0700 init: add --submodules to sync manifest submodules
3d7bbc9edf2e 2017-04-12 19:51:47 +0800 project.py: fix performance issue with --reference when the mirrored repository has many refs

The submodules work conflicted slightly with our --cache-dir change
because of a change in the Sync_NetworkHalf function signature, but
resolving that was straight forward.

BUG=chromium:900461
TEST=`repo sync` locally

Change-Id: I13dd556fd2ac48e459e7268ec92d6b2e6b917138
diff --git a/error.py b/error.py
index f2a7c4e..1050b8d 100644
--- a/error.py
+++ b/error.py
@@ -111,3 +111,7 @@
 
   The common case is that the file wasn't present when we tried to run it.
   """
+
+class CacheApplyError(Exception):
+  """Thrown when errors happen in 'repo sync' with '--cache-dir' option.
+  """
diff --git a/project.py b/project.py
index 9a79527..d6e11d2 100644
--- a/project.py
+++ b/project.py
@@ -31,8 +31,9 @@
 from color import Coloring
 from git_command import GitCommand, git_require
 from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
-    ID_RE
+    ID_RE, RefSpec
 from error import GitError, HookError, UploadError, DownloadError
+from error import CacheApplyError
 from error import ManifestInvalidRevisionError
 from error import NoManifestException
 from trace import IsTrace, Trace
@@ -176,11 +177,17 @@
   def UploadForReview(self, people,
                       auto_topic=False,
                       draft=False,
+                      private=False,
+                      notify=None,
+                      wip=False,
                       dest_branch=None):
     self.project.UploadForReview(self.name,
                                  people,
                                  auto_topic=auto_topic,
                                  draft=draft,
+                                 private=private,
+                                 notify=notify,
+                                 wip=wip,
                                  dest_branch=dest_branch)
 
   def GetPublishedRefs(self):
@@ -1107,6 +1114,9 @@
                       people=([], []),
                       auto_topic=False,
                       draft=False,
+                      private=False,
+                      notify=None,
+                      wip=False,
                       dest_branch=None):
     """Uploads the named branch for code review.
     """
@@ -1138,12 +1148,7 @@
     cmd = ['push']
 
     if url.startswith('ssh://'):
-      rp = ['gerrit receive-pack']
-      for e in people[0]:
-        rp.append('--reviewer=%s' % sq(e))
-      for e in people[1]:
-        rp.append('--cc=%s' % sq(e))
-      cmd.append('--receive-pack=%s' % " ".join(rp))
+      cmd.append('--receive-pack=gerrit receive-pack')
 
     cmd.append(url)
 
@@ -1158,11 +1163,17 @@
                                   dest_branch)
     if auto_topic:
       ref_spec = ref_spec + '/' + branch.name
-    if not url.startswith('ssh://'):
-      rp = ['r=%s' % p for p in people[0]] + \
-           ['cc=%s' % p for p in people[1]]
-      if rp:
-        ref_spec = ref_spec + '%' + ','.join(rp)
+
+    opts = ['r=%s' % p for p in people[0]]
+    opts += ['cc=%s' % p for p in people[1]]
+    if notify:
+      opts += ['notify=' + notify]
+    if private:
+      opts += ['private']
+    if wip:
+      opts += ['wip']
+    if opts:
+      ref_spec = ref_spec + '%' + ','.join(opts)
     cmd.append(ref_spec)
 
     if GitCommand(self, cmd, bare=True).Wait() != 0:
@@ -1191,6 +1202,68 @@
       _error("Cannot extract archive %s: %s", tarpath, str(e))
     return False
 
+  def CachePopulate(self, cache_dir, url):
+    """Populate cache in the cache_dir.
+
+    Args:
+      cache_dir: Directory to cache git files from Google Storage.
+      url: Git url of current repository.
+
+    Raises:
+      CacheApplyError if it fails to populate the git cache.
+    """
+    cmd = ['cache', 'populate', '--ignore_locks', '-v',
+           '--cache-dir', cache_dir, url]
+
+    if GitCommand(self, cmd, cwd=cache_dir).Wait() != 0:
+      raise CacheApplyError('Failed to populate cache. cache_dir: %s '
+                            'url: %s' % (cache_dir, url))
+
+  def CacheExists(self, cache_dir, url):
+    """Check the existence of the cache files.
+
+    Args:
+      cache_dir: Directory to cache git files.
+      url: Git url of current repository.
+
+    Raises:
+      CacheApplyError if the cache files do not exist.
+    """
+    cmd = ['cache', 'exists', '--quiet', '--cache-dir', cache_dir, url]
+
+    exist = GitCommand(self, cmd, cwd=self.gitdir, capture_stdout=True)
+    if exist.Wait() != 0:
+      raise CacheApplyError('Failed to execute git cache exists cmd. '
+                            'cache_dir: %s url: %s' % (cache_dir, url))
+
+    if not exist.stdout or not exist.stdout.strip():
+      raise CacheApplyError('Failed to find cache. cache_dir: %s '
+                            'url: %s' % (cache_dir, url))
+    return exist.stdout.strip()
+
+  def CacheApply(self, cache_dir):
+    """Apply git cache files populated from Google Storage buckets.
+
+    Args:
+      cache_dir: Directory to cache git files.
+
+    Raises:
+      CacheApplyError if it fails to apply git caches.
+    """
+    remote = self.GetRemote(self.remote.name)
+
+    self.CachePopulate(cache_dir, remote.url)
+
+    mirror_dir = self.CacheExists(cache_dir, remote.url)
+
+    refspec = RefSpec(True, 'refs/heads/*',
+                      'refs/remotes/%s/*' % remote.name)
+
+    fetch_cache_cmd = ['fetch', mirror_dir, str(refspec)]
+    if GitCommand(self, fetch_cache_cmd, self.gitdir).Wait() != 0:
+      raise CacheApplyError('Failed to fetch refs %s from %s' %
+                            (mirror_dir, str(refspec)))
+
   def Sync_NetworkHalf(self,
                        quiet=False,
                        is_new=None,
@@ -1201,7 +1274,8 @@
                        archive=False,
                        optimized_fetch=False,
                        prune=False,
-                       submodules=False):
+                       submodules=False,
+                       cache_dir=None):
     """Perform only the network IO portion of the sync process.
        Local working directory/branch state is not affected.
     """
@@ -1253,7 +1327,22 @@
     else:
       alt_dir = None
 
+    applied_cache = False
+    # If cache_dir is provided, and it's a new repository without
+    # alternative_dir, bootstrap this project repo with the git
+    # cache files.
+    if cache_dir is not None and is_new and alt_dir is None:
+      try:
+        self.CacheApply(cache_dir)
+        applied_cache = True
+        is_new = False
+      except CacheApplyError as e:
+        _error('Could not apply git cache: %s', e)
+        _error('Please check if you have the right GS credentials.')
+        _error('Please check if the cache files exist in GS.')
+
     if clone_bundle \
+            and not applied_cache \
             and alt_dir is None \
             and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
       is_new = False
diff --git a/repo b/repo
index 13ccd2b..abd74ca 100755
--- a/repo
+++ b/repo
@@ -5,7 +5,7 @@
 import os
 REPO_URL = os.environ.get('REPO_URL', None)
 if not REPO_URL:
-  REPO_URL = 'https://gerrit.googlesource.com/git-repo'
+  REPO_URL = 'https://chromium.googlesource.com/external/repo'
 REPO_REV = 'stable'
 
 # Copyright (C) 2008 Google Inc.
@@ -26,7 +26,7 @@
 VERSION = (1, 24)
 
 # increment this if the MAINTAINER_KEYS block is modified
-KEYRING_VERSION = (1, 2)
+KEYRING_VERSION = (1, 5)
 
 # Each individual key entry is created by using:
 # gpg --armor --export keyid
@@ -105,6 +105,133 @@
 +H/g3HsL2LOB9uoIm8/5p2TTU5ttYCXMHhQZ81AY
 =AUp4
 -----END PGP PUBLIC KEY BLOCK-----
+
+     Stefan Zager <szager@chromium.org>
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.11 (GNU/Linux)
+
+mQINBFIJOcgBEADwZIq4GRGoO1RJFKlrtVK501cwT5H+Acbizc9N5RxTkFmqxDjb
+9ApUaPW6S1b8+nrzE9P1Ri5erfzipuStfaZ/Wl3mP1JjKulibddmgnPOEbAJ673k
+Vj85RUO4rt2oZAHnZN3D3gFJzVY8JVlZ47Enj9fTqzcW78FVsPCpIT9P2LpTLWeE
+jX9Cjxeimy6VvyJstIcDLYhlpUN5UWen79L4LFAkHf3luLuU4W3p9NriqUsy5UG2
+8vO6QdhKrCr5wsjDFFeVnpMtjlSeZJAWH+XhFFibMX1xP5R9BTuJfzw3kOVKvcE0
+e9ClxgoulepXPv2xnDkqO3pG2gQVzl8LA+Aol8/IXfa7KP5FBkxK/g1cDuDtXRk4
+YLpLaLYeeKEhhOHLpsKYkK2DXTIcN+56UnTLGolummpZnCM8UUSZxQgbkFgk4YJL
+Elip0hgLZzqEl5h9vjmnQp89AZIHKcgNmzn+szLTOR9x24joaLyQ534x8OSC8lmu
+tJv2tQjDOVGWVwvY4gOTpyxCWMwur6WOiMk/TPWdiVRFWAGrAHwf0/CTBEqNhosh
+sVXfPeMADBA0PorDbJ6kwcOkLUTGf8CT7OG1R9TuKPEmSjK7BYu/pT4DXitaRCiv
+uPVlwbVFpLFr0/jwaKJVMLUjL5MaYwzjJqI2c4RdROZhpMhkn4LvCMmFSQARAQAB
+tCJTdGVmYW4gWmFnZXIgPHN6YWdlckBjaHJvbWl1bS5vcmc+iQI4BBMBAgAiBQJS
+CTnIAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRDcuoHPGCdZNU0UD/9y
+0zwwOJH2UGPAzZ0YVzr7p0HtKedoxuFvPkdQxlBIaUOueMzFRmNQu3GI9irAu3MQ
+Jkip8/gi7dnLVmJyS/zWARBaRGwSVd1++87XDjw8n7l181p7394X0Agq/heri599
+YheHXkxXKVMPqByWNEPHu4eDbxeJTaDIjcKC2pzKQkm6HbWgW4wA9gCh1TRki8FP
+LMv1Fu/dr13STCR9P2evsTRZ+ZSJhTSboHNHeEAJGiGZQAsN94oht7647lYj+AyR
+ThzyHDMXXiDr8jPJIkyRilY+y82bCOatOfPoCkce3VI+LRUGJ19hJY01m4RRneIE
+55l7fXR3zggcsONjV5b+oLcGQPGgX9w64BJ7khT7Wb9+kuyrdJBIBzJsaACFEbri
+pPi02FS/HahYpLC3J66REAeNyofgVXau6WQsHrHMGsBTL9aAr0nrCrkF4Nyyc2Jd
+do6nYuljuUhORqbEECmmBM2eBtkL6Ac92D6WMBIwBOC5tCNHO2YFIvi8Y8EuE8sc
+1zB5U5Ai4SIu2icRAhzAhCRaUq02cMWuELKH6Vuh9nzgEefFWty6vPbKEyZLu19D
+B80aqP1cTN88FjtKQ/eTF29TUB6AefUeBS17e2e3WUMy4nc8tduuOFYfiHP40ScP
+wOoatwfzpiTIPGbocUEPL+pS0O/Xy8SINxFMCud3zA==
+=Vd2S
+-----END PGP PUBLIC KEY BLOCK-----
+
+     David James <davidjames@google.com>
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1
+
+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
+IM4=
+=6VNc
+-----END PGP PUBLIC KEY BLOCK-----
+
+     Mike Frysinger <vapier@chromium.org>
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1
+
+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
+=i88c
+-----END PGP PUBLIC KEY BLOCK-----
 """
 
 GIT = 'git'                      # our git command
diff --git a/subcmds/sync.py b/subcmds/sync.py
index d4432ce..f6469ce 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -14,6 +14,7 @@
 # limitations under the License.
 
 from __future__ import print_function
+import glob
 import json
 import netrc
 from optparse import SUPPRESS_HELP
@@ -65,7 +66,7 @@
   multiprocessing = None
 
 import event_log
-from git_command import GIT, git_require
+from git_command import GIT, git_require, GitCommand
 from git_config import GetUrlCookieFile
 from git_refs import R_HEADS, HEAD
 import gitc_utils
@@ -240,6 +241,11 @@
                  help='only fetch projects fixed to sha1 if revision does not exist locally')
     p.add_option('--prune', dest='prune', action='store_true',
                  help='delete refs that no longer exist on the remote')
+    p.add_option('--cache-dir', dest='cache_dir', action='store',
+                 help='Use git-cache to populate project cache into this '
+                      'directory. Bootstrap the local repository from this '
+                      'directory if the project cache exists. This applies '
+                      'to the projects on chromium and chrome-internal.')
     if show_smart:
       p.add_option('-s', '--smart-sync',
                    dest='smart_sync', action='store_true',
@@ -316,7 +322,8 @@
           clone_bundle=not opt.no_clone_bundle,
           no_tags=opt.no_tags, archive=self.manifest.IsArchive,
           optimized_fetch=opt.optimized_fetch,
-          prune=opt.prune)
+          prune=opt.prune,
+          cache_dir=opt.cache_dir)
         self._fetch_times.Set(project, time.time() - start)
 
         # Lock around all the rest of the code, since printing, updating a set
@@ -606,6 +613,30 @@
         print('error: both -u and -p must be given', file=sys.stderr)
         sys.exit(1)
 
+    cache_dir = opt.cache_dir
+    if cache_dir:
+      if self.manifest.IsMirror or self.manifest.IsArchive:
+        print('fatal: --cache-dir is not supported with mirror or archive '
+              'repository.')
+        sys.exit(1)
+
+      if os.path.exists(cache_dir):
+        if not os.path.isdir(cache_dir):
+          print('fatal: cache_dir must be a directory', file=sys.stderr)
+          sys.exit(1)
+        else:
+          # Unlock the locks in the cache_dir.
+          unlock_cmd = ['cache', 'unlock', '-vv', '--force', '--all',
+                        '--cache-dir', cache_dir]
+          if GitCommand(None, unlock_cmd).Wait() != 0:
+            raise Exception('Failed to unlock cache_dir %s' % cache_dir)
+
+          locks = glob.glob(os.path.join(cache_dir, '*.lock'))
+          if locks:
+            raise Exception('Found %s after cache unlock.' % locks)
+      else:
+        os.makedirs(opt.cache_dir)
+
     if opt.manifest_name:
       self.manifest.Override(opt.manifest_name)
 
@@ -731,7 +762,8 @@
                                     current_branch_only=opt.current_branch_only,
                                     no_tags=opt.no_tags,
                                     optimized_fetch=opt.optimized_fetch,
-                                    submodules=self.manifest.HasSubmodules)
+                                    submodules=self.manifest.HasSubmodules,
+                                    cache_dir=cache_dir)
       finish = time.time()
       self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK,
                              start, finish, success)
diff --git a/subcmds/upload.py b/subcmds/upload.py
index 38c061d..4feddf7 100644
--- a/subcmds/upload.py
+++ b/subcmds/upload.py
@@ -154,6 +154,15 @@
     p.add_option('-d', '--draft',
                  action='store_true', dest='draft', default=False,
                  help='If specified, upload as a draft.')
+    p.add_option('--ne', '--no-emails',
+                 action='store_false', dest='notify', default=True,
+                 help='If specified, do not send emails on upload.')
+    p.add_option('-p', '--private',
+                 action='store_true', dest='private', default=False,
+                 help='If specified, upload as a private change.')
+    p.add_option('-w', '--wip',
+                 action='store_true', dest='wip', default=False,
+                 help='If specified, upload as a work-in-progress change.')
     p.add_option('-D', '--destination', '--dest',
                  type='string', action='store', dest='dest_branch',
                  metavar='BRANCH',
@@ -378,7 +387,13 @@
             branch.uploaded = False
             continue
 
-        branch.UploadForReview(people, auto_topic=opt.auto_topic, draft=opt.draft, dest_branch=destination)
+        branch.UploadForReview(people,
+                               auto_topic=opt.auto_topic,
+                               draft=opt.draft,
+                               private=opt.private,
+                               notify=None if opt.notify else 'NONE',
+                               wip=opt.wip,
+                               dest_branch=destination)
         branch.uploaded = True
       except UploadError as e:
         branch.error = e