summaryrefslogtreecommitdiffstats
path: root/subcmds
diff options
context:
space:
mode:
Diffstat (limited to 'subcmds')
-rw-r--r--subcmds/abandon.py71
-rw-r--r--subcmds/init.py11
-rw-r--r--subcmds/start.py3
-rw-r--r--subcmds/status.py12
-rw-r--r--subcmds/sync.py110
-rw-r--r--subcmds/upload.py14
6 files changed, 158 insertions, 63 deletions
diff --git a/subcmds/abandon.py b/subcmds/abandon.py
index b94ccdd3..6f78da74 100644
--- a/subcmds/abandon.py
+++ b/subcmds/abandon.py
@@ -16,6 +16,7 @@
16from __future__ import print_function 16from __future__ import print_function
17import sys 17import sys
18from command import Command 18from command import Command
19from collections import defaultdict
19from git_command import git 20from git_command import git
20from progress import Progress 21from progress import Progress
21 22
@@ -23,49 +24,75 @@ class Abandon(Command):
23 common = True 24 common = True
24 helpSummary = "Permanently abandon a development branch" 25 helpSummary = "Permanently abandon a development branch"
25 helpUsage = """ 26 helpUsage = """
26%prog <branchname> [<project>...] 27%prog [--all | <branchname>] [<project>...]
27 28
28This subcommand permanently abandons a development branch by 29This subcommand permanently abandons a development branch by
29deleting it (and all its history) from your local repository. 30deleting it (and all its history) from your local repository.
30 31
31It is equivalent to "git branch -D <branchname>". 32It is equivalent to "git branch -D <branchname>".
32""" 33"""
34 def _Options(self, p):
35 p.add_option('--all',
36 dest='all', action='store_true',
37 help='delete all branches in all projects')
33 38
34 def Execute(self, opt, args): 39 def Execute(self, opt, args):
35 if not args: 40 if not opt.all and not args:
36 self.Usage() 41 self.Usage()
37 42
38 nb = args[0] 43 if not opt.all:
39 if not git.check_ref_format('heads/%s' % nb): 44 nb = args[0]
40 print("error: '%s' is not a valid name" % nb, file=sys.stderr) 45 if not git.check_ref_format('heads/%s' % nb):
41 sys.exit(1) 46 print("error: '%s' is not a valid name" % nb, file=sys.stderr)
47 sys.exit(1)
48 else:
49 args.insert(0,None)
50 nb = "'All local branches'"
42 51
43 nb = args[0] 52 err = defaultdict(list)
44 err = [] 53 success = defaultdict(list)
45 success = []
46 all_projects = self.GetProjects(args[1:]) 54 all_projects = self.GetProjects(args[1:])
47 55
48 pm = Progress('Abandon %s' % nb, len(all_projects)) 56 pm = Progress('Abandon %s' % nb, len(all_projects))
49 for project in all_projects: 57 for project in all_projects:
50 pm.update() 58 pm.update()
51 59
52 status = project.AbandonBranch(nb) 60 if opt.all:
53 if status is not None: 61 branches = project.GetBranches().keys()
54 if status: 62 else:
55 success.append(project) 63 branches = [nb]
56 else: 64
57 err.append(project) 65 for name in branches:
66 status = project.AbandonBranch(name)
67 if status is not None:
68 if status:
69 success[name].append(project)
70 else:
71 err[name].append(project)
58 pm.end() 72 pm.end()
59 73
74 width = 25
75 for name in branches:
76 if width < len(name):
77 width = len(name)
78
60 if err: 79 if err:
61 for p in err: 80 for br in err.keys():
62 print("error: %s/: cannot abandon %s" % (p.relpath, nb), 81 err_msg = "error: cannot abandon %s" %br
63 file=sys.stderr) 82 print(err_msg, file=sys.stderr)
83 for proj in err[br]:
84 print(' '*len(err_msg) + " | %s" % p.relpath, file=sys.stderr)
64 sys.exit(1) 85 sys.exit(1)
65 elif not success: 86 elif not success:
66 print('error: no project has branch %s' % nb, file=sys.stderr) 87 print('error: no project has local branch(es) : %s' % nb,
88 file=sys.stderr)
67 sys.exit(1) 89 sys.exit(1)
68 else: 90 else:
69 print('Abandoned in %d project(s):\n %s' 91 print('Abandoned branches:', file=sys.stderr)
70 % (len(success), '\n '.join(p.relpath for p in success)), 92 for br in success.keys():
71 file=sys.stderr) 93 if len(all_projects) > 1 and len(all_projects) == len(success[br]):
94 result = "all project"
95 else:
96 result = "%s" % (
97 ('\n'+' '*width + '| ').join(p.relpath for p in success[br]))
98 print("%s%s| %s\n" % (br,' '*(width-len(br)), result),file=sys.stderr)
diff --git a/subcmds/init.py b/subcmds/init.py
index b8e3de5a..45d69b79 100644
--- a/subcmds/init.py
+++ b/subcmds/init.py
@@ -61,6 +61,11 @@ directory use as much data as possible from the local reference
61directory when fetching from the server. This will make the sync 61directory when fetching from the server. This will make the sync
62go a lot faster by reducing data traffic on the network. 62go a lot faster by reducing data traffic on the network.
63 63
64The --no-clone-bundle option disables any attempt to use
65$URL/clone.bundle to bootstrap a new Git repository from a
66resumeable bundle file on a content delivery network. This
67may be necessary if there are problems with the local Python
68HTTP client or proxy configuration, but the Git binary works.
64 69
65Switching Manifest Branches 70Switching Manifest Branches
66--------------------------- 71---------------------------
@@ -113,6 +118,9 @@ to update the working directory files.
113 help='restrict manifest projects to ones with a specified ' 118 help='restrict manifest projects to ones with a specified '
114 'platform group [auto|all|none|linux|darwin|...]', 119 'platform group [auto|all|none|linux|darwin|...]',
115 metavar='PLATFORM') 120 metavar='PLATFORM')
121 g.add_option('--no-clone-bundle',
122 dest='no_clone_bundle', action='store_true',
123 help='disable use of /clone.bundle on HTTP/HTTPS')
116 124
117 # Tool 125 # Tool
118 g = p.add_option_group('repo Version options') 126 g = p.add_option_group('repo Version options')
@@ -222,7 +230,8 @@ to update the working directory files.
222 'in another location.', file=sys.stderr) 230 'in another location.', file=sys.stderr)
223 sys.exit(1) 231 sys.exit(1)
224 232
225 if not m.Sync_NetworkHalf(is_new=is_new): 233 if not m.Sync_NetworkHalf(is_new=is_new, quiet=opt.quiet,
234 clone_bundle=not opt.no_clone_bundle):
226 r = m.GetRemote(m.remote.name) 235 r = m.GetRemote(m.remote.name)
227 print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr) 236 print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
228 237
diff --git a/subcmds/start.py b/subcmds/start.py
index d1430a9d..290b6897 100644
--- a/subcmds/start.py
+++ b/subcmds/start.py
@@ -54,8 +54,7 @@ revision specified in the manifest.
54 if not opt.all: 54 if not opt.all:
55 projects = args[1:] 55 projects = args[1:]
56 if len(projects) < 1: 56 if len(projects) < 1:
57 print("error: at least one project must be specified", file=sys.stderr) 57 projects = ['.',] # start it in the local project by default
58 sys.exit(1)
59 58
60 all_projects = self.GetProjects(projects, 59 all_projects = self.GetProjects(projects,
61 missing_ok=bool(self.gitc_manifest)) 60 missing_ok=bool(self.gitc_manifest))
diff --git a/subcmds/status.py b/subcmds/status.py
index 38c229b1..60e26ff4 100644
--- a/subcmds/status.py
+++ b/subcmds/status.py
@@ -89,8 +89,10 @@ the following meanings:
89 p.add_option('-o', '--orphans', 89 p.add_option('-o', '--orphans',
90 dest='orphans', action='store_true', 90 dest='orphans', action='store_true',
91 help="include objects in working directory outside of repo projects") 91 help="include objects in working directory outside of repo projects")
92 p.add_option('-q', '--quiet', action='store_true',
93 help="only print the name of modified projects")
92 94
93 def _StatusHelper(self, project, clean_counter, sem): 95 def _StatusHelper(self, project, clean_counter, sem, quiet):
94 """Obtains the status for a specific project. 96 """Obtains the status for a specific project.
95 97
96 Obtains the status for a project, redirecting the output to 98 Obtains the status for a project, redirecting the output to
@@ -104,7 +106,7 @@ the following meanings:
104 output: Where to output the status. 106 output: Where to output the status.
105 """ 107 """
106 try: 108 try:
107 state = project.PrintWorkTreeStatus() 109 state = project.PrintWorkTreeStatus(quiet=quiet)
108 if state == 'CLEAN': 110 if state == 'CLEAN':
109 next(clean_counter) 111 next(clean_counter)
110 finally: 112 finally:
@@ -132,7 +134,7 @@ the following meanings:
132 134
133 if opt.jobs == 1: 135 if opt.jobs == 1:
134 for project in all_projects: 136 for project in all_projects:
135 state = project.PrintWorkTreeStatus() 137 state = project.PrintWorkTreeStatus(quiet=opt.quiet)
136 if state == 'CLEAN': 138 if state == 'CLEAN':
137 next(counter) 139 next(counter)
138 else: 140 else:
@@ -142,13 +144,13 @@ the following meanings:
142 sem.acquire() 144 sem.acquire()
143 145
144 t = _threading.Thread(target=self._StatusHelper, 146 t = _threading.Thread(target=self._StatusHelper,
145 args=(project, counter, sem)) 147 args=(project, counter, sem, opt.quiet))
146 threads.append(t) 148 threads.append(t)
147 t.daemon = True 149 t.daemon = True
148 t.start() 150 t.start()
149 for t in threads: 151 for t in threads:
150 t.join() 152 t.join()
151 if len(all_projects) == next(counter): 153 if not opt.quiet and len(all_projects) == next(counter):
152 print('nothing to commit (working directory clean)') 154 print('nothing to commit (working directory clean)')
153 155
154 if opt.orphans: 156 if opt.orphans:
diff --git a/subcmds/sync.py b/subcmds/sync.py
index 9124a653..bbb166c0 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -255,7 +255,7 @@ later is required to fix a server side protocol bug.
255 dest='repo_upgraded', action='store_true', 255 dest='repo_upgraded', action='store_true',
256 help=SUPPRESS_HELP) 256 help=SUPPRESS_HELP)
257 257
258 def _FetchProjectList(self, opt, projects, *args, **kwargs): 258 def _FetchProjectList(self, opt, projects, sem, *args, **kwargs):
259 """Main function of the fetch threads when jobs are > 1. 259 """Main function of the fetch threads when jobs are > 1.
260 260
261 Delegates most of the work to _FetchHelper. 261 Delegates most of the work to _FetchHelper.
@@ -263,15 +263,20 @@ later is required to fix a server side protocol bug.
263 Args: 263 Args:
264 opt: Program options returned from optparse. See _Options(). 264 opt: Program options returned from optparse. See _Options().
265 projects: Projects to fetch. 265 projects: Projects to fetch.
266 sem: We'll release() this semaphore when we exit so that another thread
267 can be started up.
266 *args, **kwargs: Remaining arguments to pass to _FetchHelper. See the 268 *args, **kwargs: Remaining arguments to pass to _FetchHelper. See the
267 _FetchHelper docstring for details. 269 _FetchHelper docstring for details.
268 """ 270 """
269 for project in projects: 271 try:
270 success = self._FetchHelper(opt, project, *args, **kwargs) 272 for project in projects:
271 if not success and not opt.force_broken: 273 success = self._FetchHelper(opt, project, *args, **kwargs)
272 break 274 if not success and not opt.force_broken:
275 break
276 finally:
277 sem.release()
273 278
274 def _FetchHelper(self, opt, project, lock, fetched, pm, sem, err_event): 279 def _FetchHelper(self, opt, project, lock, fetched, pm, err_event):
275 """Fetch git objects for a single project. 280 """Fetch git objects for a single project.
276 281
277 Args: 282 Args:
@@ -283,8 +288,6 @@ later is required to fix a server side protocol bug.
283 (with our lock held). 288 (with our lock held).
284 pm: Instance of a Project object. We will call pm.update() (with our 289 pm: Instance of a Project object. We will call pm.update() (with our
285 lock held). 290 lock held).
286 sem: We'll release() this semaphore when we exit so that another thread
287 can be started up.
288 err_event: We'll set this event in the case of an error (after printing 291 err_event: We'll set this event in the case of an error (after printing
289 out info about the error). 292 out info about the error).
290 293
@@ -340,7 +343,6 @@ later is required to fix a server side protocol bug.
340 finally: 343 finally:
341 if did_lock: 344 if did_lock:
342 lock.release() 345 lock.release()
343 sem.release()
344 346
345 return success 347 return success
346 348
@@ -365,10 +367,10 @@ later is required to fix a server side protocol bug.
365 sem.acquire() 367 sem.acquire()
366 kwargs = dict(opt=opt, 368 kwargs = dict(opt=opt,
367 projects=project_list, 369 projects=project_list,
370 sem=sem,
368 lock=lock, 371 lock=lock,
369 fetched=fetched, 372 fetched=fetched,
370 pm=pm, 373 pm=pm,
371 sem=sem,
372 err_event=err_event) 374 err_event=err_event)
373 if self.jobs > 1: 375 if self.jobs > 1:
374 t = _threading.Thread(target = self._FetchProjectList, 376 t = _threading.Thread(target = self._FetchProjectList,
@@ -397,9 +399,12 @@ later is required to fix a server side protocol bug.
397 return fetched 399 return fetched
398 400
399 def _GCProjects(self, projects): 401 def _GCProjects(self, projects):
400 gitdirs = {} 402 gc_gitdirs = {}
401 for project in projects: 403 for project in projects:
402 gitdirs[project.gitdir] = project.bare_git 404 if len(project.manifest.GetProjectsWithName(project.name)) > 1:
405 print('Shared project %s found, disabling pruning.' % project.name)
406 project.bare_git.config('--replace-all', 'gc.pruneExpire', 'never')
407 gc_gitdirs[project.gitdir] = project.bare_git
403 408
404 has_dash_c = git_require((1, 7, 2)) 409 has_dash_c = git_require((1, 7, 2))
405 if multiprocessing and has_dash_c: 410 if multiprocessing and has_dash_c:
@@ -409,7 +414,7 @@ later is required to fix a server side protocol bug.
409 jobs = min(self.jobs, cpu_count) 414 jobs = min(self.jobs, cpu_count)
410 415
411 if jobs < 2: 416 if jobs < 2:
412 for bare_git in gitdirs.values(): 417 for bare_git in gc_gitdirs.values():
413 bare_git.gc('--auto') 418 bare_git.gc('--auto')
414 return 419 return
415 420
@@ -431,7 +436,7 @@ later is required to fix a server side protocol bug.
431 finally: 436 finally:
432 sem.release() 437 sem.release()
433 438
434 for bare_git in gitdirs.values(): 439 for bare_git in gc_gitdirs.values():
435 if err_event.isSet(): 440 if err_event.isSet():
436 break 441 break
437 sem.acquire() 442 sem.acquire()
@@ -454,6 +459,65 @@ later is required to fix a server side protocol bug.
454 else: 459 else:
455 self.manifest._Unload() 460 self.manifest._Unload()
456 461
462 def _DeleteProject(self, path):
463 print('Deleting obsolete path %s' % path, file=sys.stderr)
464
465 # Delete the .git directory first, so we're less likely to have a partially
466 # working git repository around. There shouldn't be any git projects here,
467 # so rmtree works.
468 try:
469 shutil.rmtree(os.path.join(path, '.git'))
470 except OSError:
471 print('Failed to remove %s' % os.path.join(path, '.git'), file=sys.stderr)
472 print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
473 print(' remove manually, then run sync again', file=sys.stderr)
474 return -1
475
476 # Delete everything under the worktree, except for directories that contain
477 # another git project
478 dirs_to_remove = []
479 failed = False
480 for root, dirs, files in os.walk(path):
481 for f in files:
482 try:
483 os.remove(os.path.join(root, f))
484 except OSError:
485 print('Failed to remove %s' % os.path.join(root, f), file=sys.stderr)
486 failed = True
487 dirs[:] = [d for d in dirs
488 if not os.path.lexists(os.path.join(root, d, '.git'))]
489 dirs_to_remove += [os.path.join(root, d) for d in dirs
490 if os.path.join(root, d) not in dirs_to_remove]
491 for d in reversed(dirs_to_remove):
492 if os.path.islink(d):
493 try:
494 os.remove(d)
495 except OSError:
496 print('Failed to remove %s' % os.path.join(root, d), file=sys.stderr)
497 failed = True
498 elif len(os.listdir(d)) == 0:
499 try:
500 os.rmdir(d)
501 except OSError:
502 print('Failed to remove %s' % os.path.join(root, d), file=sys.stderr)
503 failed = True
504 continue
505 if failed:
506 print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
507 print(' remove manually, then run sync again', file=sys.stderr)
508 return -1
509
510 # Try deleting parent dirs if they are empty
511 project_dir = path
512 while project_dir != self.manifest.topdir:
513 if len(os.listdir(project_dir)) == 0:
514 os.rmdir(project_dir)
515 else:
516 break
517 project_dir = os.path.dirname(project_dir)
518
519 return 0
520
457 def UpdateProjectList(self): 521 def UpdateProjectList(self):
458 new_project_paths = [] 522 new_project_paths = []
459 for project in self.GetProjects(None, missing_ok=True): 523 for project in self.GetProjects(None, missing_ok=True):
@@ -474,8 +538,8 @@ later is required to fix a server side protocol bug.
474 continue 538 continue
475 if path not in new_project_paths: 539 if path not in new_project_paths:
476 # If the path has already been deleted, we don't need to do it 540 # If the path has already been deleted, we don't need to do it
477 if os.path.exists(self.manifest.topdir + '/' + path): 541 gitdir = os.path.join(self.manifest.topdir, path, '.git')
478 gitdir = os.path.join(self.manifest.topdir, path, '.git') 542 if os.path.exists(gitdir):
479 project = Project( 543 project = Project(
480 manifest = self.manifest, 544 manifest = self.manifest,
481 name = path, 545 name = path,
@@ -494,18 +558,8 @@ later is required to fix a server side protocol bug.
494 print(' commit changes, then run sync again', 558 print(' commit changes, then run sync again',
495 file=sys.stderr) 559 file=sys.stderr)
496 return -1 560 return -1
497 else: 561 elif self._DeleteProject(project.worktree):
498 print('Deleting obsolete path %s' % project.worktree, 562 return -1
499 file=sys.stderr)
500 shutil.rmtree(project.worktree)
501 # Try deleting parent subdirs if they are empty
502 project_dir = os.path.dirname(project.worktree)
503 while project_dir != self.manifest.topdir:
504 try:
505 os.rmdir(project_dir)
506 except OSError:
507 break
508 project_dir = os.path.dirname(project_dir)
509 563
510 new_project_paths.sort() 564 new_project_paths.sort()
511 fd = open(file_path, 'w') 565 fd = open(file_path, 'w')
diff --git a/subcmds/upload.py b/subcmds/upload.py
index 674fc17d..1172dadc 100644
--- a/subcmds/upload.py
+++ b/subcmds/upload.py
@@ -454,9 +454,15 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
454 if avail: 454 if avail:
455 pending.append((project, avail)) 455 pending.append((project, avail))
456 456
457 if pending and (not opt.bypass_hooks): 457 if not pending:
458 print("no branches ready for upload", file=sys.stderr)
459 return
460
461 if not opt.bypass_hooks:
458 hook = RepoHook('pre-upload', self.manifest.repo_hooks_project, 462 hook = RepoHook('pre-upload', self.manifest.repo_hooks_project,
459 self.manifest.topdir, abort_if_user_denies=True) 463 self.manifest.topdir,
464 self.manifest.manifestProject.GetRemote('origin').url,
465 abort_if_user_denies=True)
460 pending_proj_names = [project.name for (project, avail) in pending] 466 pending_proj_names = [project.name for (project, avail) in pending]
461 pending_worktrees = [project.worktree for (project, avail) in pending] 467 pending_worktrees = [project.worktree for (project, avail) in pending]
462 try: 468 try:
@@ -472,9 +478,7 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
472 cc = _SplitEmails(opt.cc) 478 cc = _SplitEmails(opt.cc)
473 people = (reviewers, cc) 479 people = (reviewers, cc)
474 480
475 if not pending: 481 if len(pending) == 1 and len(pending[0][1]) == 1:
476 print("no branches ready for upload", file=sys.stderr)
477 elif len(pending) == 1 and len(pending[0][1]) == 1:
478 self._SingleBranch(opt, pending[0][1][0], people) 482 self._SingleBranch(opt, pending[0][1][0], people)
479 else: 483 else:
480 self._MultipleBranches(opt, pending, people) 484 self._MultipleBranches(opt, pending, people)