diff options
| author | Shawn O. Pearce <sop@google.com> | 2010-12-07 10:31:19 -0800 |
|---|---|---|
| committer | Shawn O. Pearce <sop@google.com> | 2010-12-07 11:13:29 -0800 |
| commit | 13f3da50d40b89ee5b05f5f3de9542c20edac6d1 (patch) | |
| tree | d085b6f6b498bde85a1969fce884dd24e88d03d5 /subcmds/sync.py | |
| parent | 3218c13205694434edb2375ab8a8515554eed366 (diff) | |
| parent | 2b8db3ce3e7344b9f3b5216637c5af0d54be5656 (diff) | |
| download | git-repo-13f3da50d40b89ee5b05f5f3de9542c20edac6d1.tar.gz | |
Merge branch 'stable'
* stable: (33 commits)
Added feature to print a <notice> from manifest at the end of a sync.
sync: Use --force-broken to continue other projects
upload: Remove --replace option
sync --quiet: be more quiet
sync: Enable use of git clone --reference
Only delete corrupt pickle config files if they exist
Don't allow git fetch to start ControlMaster
Check for existing SSH ControlMaster
Fix for handling values of EDITOR which contain a space.
upload: Fix --replace flag
rebase: Pass through more options
upload: Allow review.HOST.username to override email
upload -t: Automatically include local branch name
Warn users before uploading if there are local changes
sync: Try fetching a tag as a last resort before giving up
rebase: Automatically rebase branch on upstrea
upload: Automatically --cc folks in review.URL.autocopy
Fix format string bugs in grep
Do not invoke ssh with -p argument when no port has been specified.
Allow files to be copied into new folders
...
Conflicts:
git_config.py
manifest_xml.py
subcmds/init.py
subcmds/sync.py
subcmds/upload.py
Change-Id: I4756a6908277e91505c35287a122a775b68f4df5
Diffstat (limited to 'subcmds/sync.py')
| -rw-r--r-- | subcmds/sync.py | 196 |
1 files changed, 159 insertions, 37 deletions
diff --git a/subcmds/sync.py b/subcmds/sync.py index d89c2b8c..7b77388b 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py | |||
| @@ -17,11 +17,19 @@ from optparse import SUPPRESS_HELP | |||
| 17 | import os | 17 | import os |
| 18 | import re | 18 | import re |
| 19 | import shutil | 19 | import shutil |
| 20 | import socket | ||
| 20 | import subprocess | 21 | import subprocess |
| 21 | import sys | 22 | import sys |
| 22 | import time | 23 | import time |
| 24 | import xmlrpclib | ||
| 25 | |||
| 26 | try: | ||
| 27 | import threading as _threading | ||
| 28 | except ImportError: | ||
| 29 | import dummy_threading as _threading | ||
| 23 | 30 | ||
| 24 | from git_command import GIT | 31 | from git_command import GIT |
| 32 | from git_refs import R_HEADS | ||
| 25 | from project import HEAD | 33 | from project import HEAD |
| 26 | from project import Project | 34 | from project import Project |
| 27 | from project import RemoteSpec | 35 | from project import RemoteSpec |
| @@ -32,6 +40,7 @@ from project import SyncBuffer | |||
| 32 | from progress import Progress | 40 | from progress import Progress |
| 33 | 41 | ||
| 34 | class Sync(Command, MirrorSafeCommand): | 42 | class Sync(Command, MirrorSafeCommand): |
| 43 | jobs = 1 | ||
| 35 | common = True | 44 | common = True |
| 36 | helpSummary = "Update working tree to the latest revision" | 45 | helpSummary = "Update working tree to the latest revision" |
| 37 | helpUsage = """ | 46 | helpUsage = """ |
| @@ -57,6 +66,13 @@ back to the manifest revision. This option is especially helpful | |||
| 57 | if the project is currently on a topic branch, but the manifest | 66 | if the project is currently on a topic branch, but the manifest |
| 58 | revision is temporarily needed. | 67 | revision is temporarily needed. |
| 59 | 68 | ||
| 69 | The -s/--smart-sync option can be used to sync to a known good | ||
| 70 | build as specified by the manifest-server element in the current | ||
| 71 | manifest. | ||
| 72 | |||
| 73 | The -f/--force-broken option can be used to proceed with syncing | ||
| 74 | other projects if a project sync fails. | ||
| 75 | |||
| 60 | SSH Connections | 76 | SSH Connections |
| 61 | --------------- | 77 | --------------- |
| 62 | 78 | ||
| @@ -87,7 +103,10 @@ later is required to fix a server side protocol bug. | |||
| 87 | 103 | ||
| 88 | """ | 104 | """ |
| 89 | 105 | ||
| 90 | def _Options(self, p): | 106 | def _Options(self, p, show_smart=True): |
| 107 | p.add_option('-f', '--force-broken', | ||
| 108 | dest='force_broken', action='store_true', | ||
| 109 | help="continue sync even if a project fails to sync") | ||
| 91 | p.add_option('-l','--local-only', | 110 | p.add_option('-l','--local-only', |
| 92 | dest='local_only', action='store_true', | 111 | dest='local_only', action='store_true', |
| 93 | help="only update working tree, don't fetch") | 112 | help="only update working tree, don't fetch") |
| @@ -97,6 +116,16 @@ later is required to fix a server side protocol bug. | |||
| 97 | p.add_option('-d','--detach', | 116 | p.add_option('-d','--detach', |
| 98 | dest='detach_head', action='store_true', | 117 | dest='detach_head', action='store_true', |
| 99 | help='detach projects back to manifest revision') | 118 | help='detach projects back to manifest revision') |
| 119 | p.add_option('-q','--quiet', | ||
| 120 | dest='quiet', action='store_true', | ||
| 121 | help='be more quiet') | ||
| 122 | p.add_option('-j','--jobs', | ||
| 123 | dest='jobs', action='store', type='int', | ||
| 124 | help="number of projects to fetch simultaneously") | ||
| 125 | if show_smart: | ||
| 126 | p.add_option('-s', '--smart-sync', | ||
| 127 | dest='smart_sync', action='store_true', | ||
| 128 | help='smart sync using manifest from a known good build') | ||
| 100 | 129 | ||
| 101 | g = p.add_option_group('repo Version options') | 130 | g = p.add_option_group('repo Version options') |
| 102 | g.add_option('--no-repo-verify', | 131 | g.add_option('--no-repo-verify', |
| @@ -106,16 +135,55 @@ later is required to fix a server side protocol bug. | |||
| 106 | dest='repo_upgraded', action='store_true', | 135 | dest='repo_upgraded', action='store_true', |
| 107 | help=SUPPRESS_HELP) | 136 | help=SUPPRESS_HELP) |
| 108 | 137 | ||
| 109 | def _Fetch(self, projects): | 138 | def _FetchHelper(self, opt, project, lock, fetched, pm, sem): |
| 139 | if not project.Sync_NetworkHalf(quiet=opt.quiet): | ||
| 140 | print >>sys.stderr, 'error: Cannot fetch %s' % project.name | ||
| 141 | if opt.force_broken: | ||
| 142 | print >>sys.stderr, 'warn: --force-broken, continuing to sync' | ||
| 143 | else: | ||
| 144 | sem.release() | ||
| 145 | sys.exit(1) | ||
| 146 | |||
| 147 | lock.acquire() | ||
| 148 | fetched.add(project.gitdir) | ||
| 149 | pm.update() | ||
| 150 | lock.release() | ||
| 151 | sem.release() | ||
| 152 | |||
| 153 | def _Fetch(self, projects, opt): | ||
| 110 | fetched = set() | 154 | fetched = set() |
| 111 | pm = Progress('Fetching projects', len(projects)) | 155 | pm = Progress('Fetching projects', len(projects)) |
| 112 | for project in projects: | 156 | |
| 113 | pm.update() | 157 | if self.jobs == 1: |
| 114 | if project.Sync_NetworkHalf(): | 158 | for project in projects: |
| 115 | fetched.add(project.gitdir) | 159 | pm.update() |
| 116 | else: | 160 | if project.Sync_NetworkHalf(quiet=opt.quiet): |
| 117 | print >>sys.stderr, 'error: Cannot fetch %s' % project.name | 161 | fetched.add(project.gitdir) |
| 118 | sys.exit(1) | 162 | else: |
| 163 | print >>sys.stderr, 'error: Cannot fetch %s' % project.name | ||
| 164 | if opt.force_broken: | ||
| 165 | print >>sys.stderr, 'warn: --force-broken, continuing to sync' | ||
| 166 | else: | ||
| 167 | sys.exit(1) | ||
| 168 | else: | ||
| 169 | threads = set() | ||
| 170 | lock = _threading.Lock() | ||
| 171 | sem = _threading.Semaphore(self.jobs) | ||
| 172 | for project in projects: | ||
| 173 | sem.acquire() | ||
| 174 | t = _threading.Thread(target = self._FetchHelper, | ||
| 175 | args = (opt, | ||
| 176 | project, | ||
| 177 | lock, | ||
| 178 | fetched, | ||
| 179 | pm, | ||
| 180 | sem)) | ||
| 181 | threads.add(t) | ||
| 182 | t.start() | ||
| 183 | |||
| 184 | for t in threads: | ||
| 185 | t.join() | ||
| 186 | |||
| 119 | pm.end() | 187 | pm.end() |
| 120 | for project in projects: | 188 | for project in projects: |
| 121 | project.bare_git.gc('--auto') | 189 | project.bare_git.gc('--auto') |
| @@ -140,32 +208,36 @@ later is required to fix a server side protocol bug. | |||
| 140 | if not path: | 208 | if not path: |
| 141 | continue | 209 | continue |
| 142 | if path not in new_project_paths: | 210 | if path not in new_project_paths: |
| 143 | project = Project( | 211 | """If the path has already been deleted, we don't need to do it |
| 144 | manifest = self.manifest, | 212 | """ |
| 145 | name = path, | 213 | if os.path.exists(self.manifest.topdir + '/' + path): |
| 146 | remote = RemoteSpec('origin'), | 214 | project = Project( |
| 147 | gitdir = os.path.join(self.manifest.topdir, | 215 | manifest = self.manifest, |
| 148 | path, '.git'), | 216 | name = path, |
| 149 | worktree = os.path.join(self.manifest.topdir, path), | 217 | remote = RemoteSpec('origin'), |
| 150 | relpath = path, | 218 | gitdir = os.path.join(self.manifest.topdir, |
| 151 | revisionExpr = 'HEAD', | 219 | path, '.git'), |
| 152 | revisionId = None) | 220 | worktree = os.path.join(self.manifest.topdir, path), |
| 153 | if project.IsDirty(): | 221 | relpath = path, |
| 154 | print >>sys.stderr, 'error: Cannot remove project "%s": \ | 222 | revisionExpr = 'HEAD', |
| 223 | revisionId = None) | ||
| 224 | |||
| 225 | if project.IsDirty(): | ||
| 226 | print >>sys.stderr, 'error: Cannot remove project "%s": \ | ||
| 155 | uncommitted changes are present' % project.relpath | 227 | uncommitted changes are present' % project.relpath |
| 156 | print >>sys.stderr, ' commit changes, then run sync again' | 228 | print >>sys.stderr, ' commit changes, then run sync again' |
| 157 | return -1 | 229 | return -1 |
| 158 | else: | 230 | else: |
| 159 | print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree | 231 | print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree |
| 160 | shutil.rmtree(project.worktree) | 232 | shutil.rmtree(project.worktree) |
| 161 | # Try deleting parent subdirs if they are empty | 233 | # Try deleting parent subdirs if they are empty |
| 162 | dir = os.path.dirname(project.worktree) | 234 | dir = os.path.dirname(project.worktree) |
| 163 | while dir != self.manifest.topdir: | 235 | while dir != self.manifest.topdir: |
| 164 | try: | 236 | try: |
| 165 | os.rmdir(dir) | 237 | os.rmdir(dir) |
| 166 | except OSError: | 238 | except OSError: |
| 167 | break | 239 | break |
| 168 | dir = os.path.dirname(dir) | 240 | dir = os.path.dirname(dir) |
| 169 | 241 | ||
| 170 | new_project_paths.sort() | 242 | new_project_paths.sort() |
| 171 | fd = open(file_path, 'w') | 243 | fd = open(file_path, 'w') |
| @@ -177,6 +249,8 @@ uncommitted changes are present' % project.relpath | |||
| 177 | return 0 | 249 | return 0 |
| 178 | 250 | ||
| 179 | def Execute(self, opt, args): | 251 | def Execute(self, opt, args): |
| 252 | if opt.jobs: | ||
| 253 | self.jobs = opt.jobs | ||
| 180 | if opt.network_only and opt.detach_head: | 254 | if opt.network_only and opt.detach_head: |
| 181 | print >>sys.stderr, 'error: cannot combine -n and -d' | 255 | print >>sys.stderr, 'error: cannot combine -n and -d' |
| 182 | sys.exit(1) | 256 | sys.exit(1) |
| @@ -184,6 +258,51 @@ uncommitted changes are present' % project.relpath | |||
| 184 | print >>sys.stderr, 'error: cannot combine -n and -l' | 258 | print >>sys.stderr, 'error: cannot combine -n and -l' |
| 185 | sys.exit(1) | 259 | sys.exit(1) |
| 186 | 260 | ||
| 261 | if opt.smart_sync: | ||
| 262 | if not self.manifest.manifest_server: | ||
| 263 | print >>sys.stderr, \ | ||
| 264 | 'error: cannot smart sync: no manifest server defined in manifest' | ||
| 265 | sys.exit(1) | ||
| 266 | try: | ||
| 267 | server = xmlrpclib.Server(self.manifest.manifest_server) | ||
| 268 | p = self.manifest.manifestProject | ||
| 269 | b = p.GetBranch(p.CurrentBranch) | ||
| 270 | branch = b.merge | ||
| 271 | if branch.startswith(R_HEADS): | ||
| 272 | branch = branch[len(R_HEADS):] | ||
| 273 | |||
| 274 | env = dict(os.environ) | ||
| 275 | if (env.has_key('TARGET_PRODUCT') and | ||
| 276 | env.has_key('TARGET_BUILD_VARIANT')): | ||
| 277 | target = '%s-%s' % (env['TARGET_PRODUCT'], | ||
| 278 | env['TARGET_BUILD_VARIANT']) | ||
| 279 | [success, manifest_str] = server.GetApprovedManifest(branch, target) | ||
| 280 | else: | ||
| 281 | [success, manifest_str] = server.GetApprovedManifest(branch) | ||
| 282 | |||
| 283 | if success: | ||
| 284 | manifest_name = "smart_sync_override.xml" | ||
| 285 | manifest_path = os.path.join(self.manifest.manifestProject.worktree, | ||
| 286 | manifest_name) | ||
| 287 | try: | ||
| 288 | f = open(manifest_path, 'w') | ||
| 289 | try: | ||
| 290 | f.write(manifest_str) | ||
| 291 | finally: | ||
| 292 | f.close() | ||
| 293 | except IOError: | ||
| 294 | print >>sys.stderr, 'error: cannot write manifest to %s' % \ | ||
| 295 | manifest_path | ||
| 296 | sys.exit(1) | ||
| 297 | self.manifest.Override(manifest_name) | ||
| 298 | else: | ||
| 299 | print >>sys.stderr, 'error: %s' % manifest_str | ||
| 300 | sys.exit(1) | ||
| 301 | except socket.error: | ||
| 302 | print >>sys.stderr, 'error: cannot connect to manifest server %s' % ( | ||
| 303 | self.manifest.manifest_server) | ||
| 304 | sys.exit(1) | ||
| 305 | |||
| 187 | rp = self.manifest.repoProject | 306 | rp = self.manifest.repoProject |
| 188 | rp.PreSync() | 307 | rp.PreSync() |
| 189 | 308 | ||
| @@ -194,7 +313,7 @@ uncommitted changes are present' % project.relpath | |||
| 194 | _PostRepoUpgrade(self.manifest) | 313 | _PostRepoUpgrade(self.manifest) |
| 195 | 314 | ||
| 196 | if not opt.local_only: | 315 | if not opt.local_only: |
| 197 | mp.Sync_NetworkHalf() | 316 | mp.Sync_NetworkHalf(quiet=opt.quiet) |
| 198 | 317 | ||
| 199 | if mp.HasChanges: | 318 | if mp.HasChanges: |
| 200 | syncbuf = SyncBuffer(mp.config) | 319 | syncbuf = SyncBuffer(mp.config) |
| @@ -211,7 +330,7 @@ uncommitted changes are present' % project.relpath | |||
| 211 | to_fetch.append(rp) | 330 | to_fetch.append(rp) |
| 212 | to_fetch.extend(all) | 331 | to_fetch.extend(all) |
| 213 | 332 | ||
| 214 | fetched = self._Fetch(to_fetch) | 333 | fetched = self._Fetch(to_fetch, opt) |
| 215 | _PostRepoFetch(rp, opt.no_repo_verify) | 334 | _PostRepoFetch(rp, opt.no_repo_verify) |
| 216 | if opt.network_only: | 335 | if opt.network_only: |
| 217 | # bail out now; the rest touches the working tree | 336 | # bail out now; the rest touches the working tree |
| @@ -230,7 +349,7 @@ uncommitted changes are present' % project.relpath | |||
| 230 | for project in all: | 349 | for project in all: |
| 231 | if project.gitdir not in fetched: | 350 | if project.gitdir not in fetched: |
| 232 | missing.append(project) | 351 | missing.append(project) |
| 233 | self._Fetch(missing) | 352 | self._Fetch(missing, opt) |
| 234 | 353 | ||
| 235 | if self.manifest.IsMirror: | 354 | if self.manifest.IsMirror: |
| 236 | # bail out now, we have no working tree | 355 | # bail out now, we have no working tree |
| @@ -258,6 +377,9 @@ def _ReloadManifest(cmd): | |||
| 258 | if old.__class__ != new.__class__: | 377 | if old.__class__ != new.__class__: |
| 259 | print >>sys.stderr, 'NOTICE: manifest format has changed ***' | 378 | print >>sys.stderr, 'NOTICE: manifest format has changed ***' |
| 260 | new.Upgrade_Local(old) | 379 | new.Upgrade_Local(old) |
| 380 | else: | ||
| 381 | if new.notice: | ||
| 382 | print new.notice | ||
| 261 | 383 | ||
| 262 | def _PostRepoUpgrade(manifest): | 384 | def _PostRepoUpgrade(manifest): |
| 263 | for project in manifest.projects.values(): | 385 | for project in manifest.projects.values(): |
