diff options
Diffstat (limited to 'subcmds')
| -rw-r--r-- | subcmds/__init__.py | 11 | ||||
| -rw-r--r-- | subcmds/abandon.py | 75 | ||||
| -rw-r--r-- | subcmds/branches.py | 49 | ||||
| -rw-r--r-- | subcmds/checkout.py | 39 | ||||
| -rw-r--r-- | subcmds/cherry_pick.py | 27 | ||||
| -rw-r--r-- | subcmds/diff.py | 53 | ||||
| -rw-r--r-- | subcmds/diffmanifests.py | 32 | ||||
| -rw-r--r-- | subcmds/download.py | 80 | ||||
| -rw-r--r-- | subcmds/forall.py | 289 | ||||
| -rw-r--r-- | subcmds/gitc_delete.py | 10 | ||||
| -rw-r--r-- | subcmds/gitc_init.py | 19 | ||||
| -rw-r--r-- | subcmds/grep.py | 202 | ||||
| -rw-r--r-- | subcmds/help.py | 61 | ||||
| -rw-r--r-- | subcmds/info.py | 55 | ||||
| -rw-r--r-- | subcmds/init.py | 362 | ||||
| -rw-r--r-- | subcmds/list.py | 49 | ||||
| -rw-r--r-- | subcmds/manifest.py | 70 | ||||
| -rw-r--r-- | subcmds/overview.py | 20 | ||||
| -rw-r--r-- | subcmds/prune.py | 31 | ||||
| -rw-r--r-- | subcmds/rebase.py | 39 | ||||
| -rw-r--r-- | subcmds/selfupdate.py | 12 | ||||
| -rw-r--r-- | subcmds/smartsync.py | 5 | ||||
| -rw-r--r-- | subcmds/stage.py | 11 | ||||
| -rw-r--r-- | subcmds/start.py | 68 | ||||
| -rw-r--r-- | subcmds/status.py | 96 | ||||
| -rw-r--r-- | subcmds/sync.py | 1054 | ||||
| -rw-r--r-- | subcmds/upload.py | 315 | ||||
| -rw-r--r-- | subcmds/version.py | 27 |
28 files changed, 1766 insertions, 1395 deletions
diff --git a/subcmds/__init__.py b/subcmds/__init__.py index 27341038..051dda06 100644 --- a/subcmds/__init__.py +++ b/subcmds/__init__.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2008 The Android Open Source Project | 1 | # Copyright (C) 2008 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -16,6 +14,7 @@ | |||
| 16 | 14 | ||
| 17 | import os | 15 | import os |
| 18 | 16 | ||
| 17 | # A mapping of the subcommand name to the class that implements it. | ||
| 19 | all_commands = {} | 18 | all_commands = {} |
| 20 | 19 | ||
| 21 | my_dir = os.path.dirname(__file__) | 20 | my_dir = os.path.dirname(__file__) |
| @@ -37,14 +36,14 @@ for py in os.listdir(my_dir): | |||
| 37 | ['%s' % name]) | 36 | ['%s' % name]) |
| 38 | mod = getattr(mod, name) | 37 | mod = getattr(mod, name) |
| 39 | try: | 38 | try: |
| 40 | cmd = getattr(mod, clsn)() | 39 | cmd = getattr(mod, clsn) |
| 41 | except AttributeError: | 40 | except AttributeError: |
| 42 | raise SyntaxError('%s/%s does not define class %s' % ( | 41 | raise SyntaxError('%s/%s does not define class %s' % ( |
| 43 | __name__, py, clsn)) | 42 | __name__, py, clsn)) |
| 44 | 43 | ||
| 45 | name = name.replace('_', '-') | 44 | name = name.replace('_', '-') |
| 46 | cmd.NAME = name | 45 | cmd.NAME = name |
| 47 | all_commands[name] = cmd | 46 | all_commands[name] = cmd |
| 48 | 47 | ||
| 49 | if 'help' in all_commands: | 48 | # Add 'branch' as an alias for 'branches'. |
| 50 | all_commands['help'].commands = all_commands | 49 | all_commands['branch'] = all_commands['branches'] |
diff --git a/subcmds/abandon.py b/subcmds/abandon.py index cd1d0c40..85d85f5a 100644 --- a/subcmds/abandon.py +++ b/subcmds/abandon.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2008 The Android Open Source Project | 1 | # Copyright (C) 2008 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,15 +12,18 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from __future__ import print_function | ||
| 18 | import sys | ||
| 19 | from command import Command | ||
| 20 | from collections import defaultdict | 15 | from collections import defaultdict |
| 16 | import functools | ||
| 17 | import itertools | ||
| 18 | import sys | ||
| 19 | |||
| 20 | from command import Command, DEFAULT_LOCAL_JOBS | ||
| 21 | from git_command import git | 21 | from git_command import git |
| 22 | from progress import Progress | 22 | from progress import Progress |
| 23 | 23 | ||
| 24 | |||
| 24 | class Abandon(Command): | 25 | class Abandon(Command): |
| 25 | common = True | 26 | COMMON = True |
| 26 | helpSummary = "Permanently abandon a development branch" | 27 | helpSummary = "Permanently abandon a development branch" |
| 27 | helpUsage = """ | 28 | helpUsage = """ |
| 28 | %prog [--all | <branchname>] [<project>...] | 29 | %prog [--all | <branchname>] [<project>...] |
| @@ -32,6 +33,8 @@ deleting it (and all its history) from your local repository. | |||
| 32 | 33 | ||
| 33 | It is equivalent to "git branch -D <branchname>". | 34 | It is equivalent to "git branch -D <branchname>". |
| 34 | """ | 35 | """ |
| 36 | PARALLEL_JOBS = DEFAULT_LOCAL_JOBS | ||
| 37 | |||
| 35 | def _Options(self, p): | 38 | def _Options(self, p): |
| 36 | p.add_option('--all', | 39 | p.add_option('--all', |
| 37 | dest='all', action='store_true', | 40 | dest='all', action='store_true', |
| @@ -48,52 +51,64 @@ It is equivalent to "git branch -D <branchname>". | |||
| 48 | else: | 51 | else: |
| 49 | args.insert(0, "'All local branches'") | 52 | args.insert(0, "'All local branches'") |
| 50 | 53 | ||
| 54 | def _ExecuteOne(self, all_branches, nb, project): | ||
| 55 | """Abandon one project.""" | ||
| 56 | if all_branches: | ||
| 57 | branches = project.GetBranches() | ||
| 58 | else: | ||
| 59 | branches = [nb] | ||
| 60 | |||
| 61 | ret = {} | ||
| 62 | for name in branches: | ||
| 63 | status = project.AbandonBranch(name) | ||
| 64 | if status is not None: | ||
| 65 | ret[name] = status | ||
| 66 | return (ret, project) | ||
| 67 | |||
| 51 | def Execute(self, opt, args): | 68 | def Execute(self, opt, args): |
| 52 | nb = args[0] | 69 | nb = args[0] |
| 53 | err = defaultdict(list) | 70 | err = defaultdict(list) |
| 54 | success = defaultdict(list) | 71 | success = defaultdict(list) |
| 55 | all_projects = self.GetProjects(args[1:]) | 72 | all_projects = self.GetProjects(args[1:]) |
| 56 | 73 | ||
| 57 | pm = Progress('Abandon %s' % nb, len(all_projects)) | 74 | def _ProcessResults(_pool, pm, states): |
| 58 | for project in all_projects: | 75 | for (results, project) in states: |
| 59 | pm.update() | 76 | for branch, status in results.items(): |
| 60 | |||
| 61 | if opt.all: | ||
| 62 | branches = list(project.GetBranches().keys()) | ||
| 63 | else: | ||
| 64 | branches = [nb] | ||
| 65 | |||
| 66 | for name in branches: | ||
| 67 | status = project.AbandonBranch(name) | ||
| 68 | if status is not None: | ||
| 69 | if status: | 77 | if status: |
| 70 | success[name].append(project) | 78 | success[branch].append(project) |
| 71 | else: | 79 | else: |
| 72 | err[name].append(project) | 80 | err[branch].append(project) |
| 73 | pm.end() | 81 | pm.update() |
| 74 | 82 | ||
| 75 | width = 25 | 83 | self.ExecuteInParallel( |
| 76 | for name in branches: | 84 | opt.jobs, |
| 77 | if width < len(name): | 85 | functools.partial(self._ExecuteOne, opt.all, nb), |
| 78 | width = len(name) | 86 | all_projects, |
| 87 | callback=_ProcessResults, | ||
| 88 | output=Progress('Abandon %s' % (nb,), len(all_projects), quiet=opt.quiet)) | ||
| 79 | 89 | ||
| 90 | width = max(itertools.chain( | ||
| 91 | [25], (len(x) for x in itertools.chain(success, err)))) | ||
| 80 | if err: | 92 | if err: |
| 81 | for br in err.keys(): | 93 | for br in err.keys(): |
| 82 | err_msg = "error: cannot abandon %s" %br | 94 | err_msg = "error: cannot abandon %s" % br |
| 83 | print(err_msg, file=sys.stderr) | 95 | print(err_msg, file=sys.stderr) |
| 84 | for proj in err[br]: | 96 | for proj in err[br]: |
| 85 | print(' '*len(err_msg) + " | %s" % proj.relpath, file=sys.stderr) | 97 | print(' ' * len(err_msg) + " | %s" % proj.relpath, file=sys.stderr) |
| 86 | sys.exit(1) | 98 | sys.exit(1) |
| 87 | elif not success: | 99 | elif not success: |
| 88 | print('error: no project has local branch(es) : %s' % nb, | 100 | print('error: no project has local branch(es) : %s' % nb, |
| 89 | file=sys.stderr) | 101 | file=sys.stderr) |
| 90 | sys.exit(1) | 102 | sys.exit(1) |
| 91 | else: | 103 | else: |
| 92 | print('Abandoned branches:', file=sys.stderr) | 104 | # Everything below here is displaying status. |
| 105 | if opt.quiet: | ||
| 106 | return | ||
| 107 | print('Abandoned branches:') | ||
| 93 | for br in success.keys(): | 108 | for br in success.keys(): |
| 94 | if len(all_projects) > 1 and len(all_projects) == len(success[br]): | 109 | if len(all_projects) > 1 and len(all_projects) == len(success[br]): |
| 95 | result = "all project" | 110 | result = "all project" |
| 96 | else: | 111 | else: |
| 97 | result = "%s" % ( | 112 | result = "%s" % ( |
| 98 | ('\n'+' '*width + '| ').join(p.relpath for p in success[br])) | 113 | ('\n' + ' ' * width + '| ').join(p.relpath for p in success[br])) |
| 99 | print("%s%s| %s\n" % (br,' '*(width-len(br)), result),file=sys.stderr) | 114 | print("%s%s| %s\n" % (br, ' ' * (width - len(br)), result)) |
diff --git a/subcmds/branches.py b/subcmds/branches.py index fb60d7de..6d975ed4 100644 --- a/subcmds/branches.py +++ b/subcmds/branches.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2009 The Android Open Source Project | 1 | # Copyright (C) 2009 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,18 +12,21 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from __future__ import print_function | 15 | import itertools |
| 18 | import sys | 16 | import sys |
| 17 | |||
| 19 | from color import Coloring | 18 | from color import Coloring |
| 20 | from command import Command | 19 | from command import Command, DEFAULT_LOCAL_JOBS |
| 20 | |||
| 21 | 21 | ||
| 22 | class BranchColoring(Coloring): | 22 | class BranchColoring(Coloring): |
| 23 | def __init__(self, config): | 23 | def __init__(self, config): |
| 24 | Coloring.__init__(self, config, 'branch') | 24 | Coloring.__init__(self, config, 'branch') |
| 25 | self.current = self.printer('current', fg='green') | 25 | self.current = self.printer('current', fg='green') |
| 26 | self.local = self.printer('local') | 26 | self.local = self.printer('local') |
| 27 | self.notinproject = self.printer('notinproject', fg='red') | 27 | self.notinproject = self.printer('notinproject', fg='red') |
| 28 | 28 | ||
| 29 | |||
| 29 | class BranchInfo(object): | 30 | class BranchInfo(object): |
| 30 | def __init__(self, name): | 31 | def __init__(self, name): |
| 31 | self.name = name | 32 | self.name = name |
| @@ -61,7 +62,7 @@ class BranchInfo(object): | |||
| 61 | 62 | ||
| 62 | 63 | ||
| 63 | class Branches(Command): | 64 | class Branches(Command): |
| 64 | common = True | 65 | COMMON = True |
| 65 | helpSummary = "View current topic branches" | 66 | helpSummary = "View current topic branches" |
| 66 | helpUsage = """ | 67 | helpUsage = """ |
| 67 | %prog [<project>...] | 68 | %prog [<project>...] |
| @@ -94,6 +95,7 @@ the branch appears in, or does not appear in. If no project list | |||
| 94 | is shown, then the branch appears in all projects. | 95 | is shown, then the branch appears in all projects. |
| 95 | 96 | ||
| 96 | """ | 97 | """ |
| 98 | PARALLEL_JOBS = DEFAULT_LOCAL_JOBS | ||
| 97 | 99 | ||
| 98 | def Execute(self, opt, args): | 100 | def Execute(self, opt, args): |
| 99 | projects = self.GetProjects(args) | 101 | projects = self.GetProjects(args) |
| @@ -101,14 +103,19 @@ is shown, then the branch appears in all projects. | |||
| 101 | all_branches = {} | 103 | all_branches = {} |
| 102 | project_cnt = len(projects) | 104 | project_cnt = len(projects) |
| 103 | 105 | ||
| 104 | for project in projects: | 106 | def _ProcessResults(_pool, _output, results): |
| 105 | for name, b in project.GetBranches().items(): | 107 | for name, b in itertools.chain.from_iterable(results): |
| 106 | b.project = project | ||
| 107 | if name not in all_branches: | 108 | if name not in all_branches: |
| 108 | all_branches[name] = BranchInfo(name) | 109 | all_branches[name] = BranchInfo(name) |
| 109 | all_branches[name].add(b) | 110 | all_branches[name].add(b) |
| 110 | 111 | ||
| 111 | names = list(sorted(all_branches)) | 112 | self.ExecuteInParallel( |
| 113 | opt.jobs, | ||
| 114 | expand_project_to_branches, | ||
| 115 | projects, | ||
| 116 | callback=_ProcessResults) | ||
| 117 | |||
| 118 | names = sorted(all_branches) | ||
| 112 | 119 | ||
| 113 | if not names: | 120 | if not names: |
| 114 | print(' (no branches)', file=sys.stderr) | 121 | print(' (no branches)', file=sys.stderr) |
| @@ -158,7 +165,7 @@ is shown, then the branch appears in all projects. | |||
| 158 | for b in i.projects: | 165 | for b in i.projects: |
| 159 | have.add(b.project) | 166 | have.add(b.project) |
| 160 | for p in projects: | 167 | for p in projects: |
| 161 | if not p in have: | 168 | if p not in have: |
| 162 | paths.append(p.relpath) | 169 | paths.append(p.relpath) |
| 163 | 170 | ||
| 164 | s = ' %s %s' % (in_type, ', '.join(paths)) | 171 | s = ' %s %s' % (in_type, ', '.join(paths)) |
| @@ -170,11 +177,27 @@ is shown, then the branch appears in all projects. | |||
| 170 | fmt = out.current if i.IsCurrent else out.write | 177 | fmt = out.current if i.IsCurrent else out.write |
| 171 | for p in paths: | 178 | for p in paths: |
| 172 | out.nl() | 179 | out.nl() |
| 173 | fmt(width*' ' + ' %s' % p) | 180 | fmt(width * ' ' + ' %s' % p) |
| 174 | fmt = out.write | 181 | fmt = out.write |
| 175 | for p in non_cur_paths: | 182 | for p in non_cur_paths: |
| 176 | out.nl() | 183 | out.nl() |
| 177 | fmt(width*' ' + ' %s' % p) | 184 | fmt(width * ' ' + ' %s' % p) |
| 178 | else: | 185 | else: |
| 179 | out.write(' in all projects') | 186 | out.write(' in all projects') |
| 180 | out.nl() | 187 | out.nl() |
| 188 | |||
| 189 | |||
| 190 | def expand_project_to_branches(project): | ||
| 191 | """Expands a project into a list of branch names & associated information. | ||
| 192 | |||
| 193 | Args: | ||
| 194 | project: project.Project | ||
| 195 | |||
| 196 | Returns: | ||
| 197 | List[Tuple[str, git_config.Branch]] | ||
| 198 | """ | ||
| 199 | branches = [] | ||
| 200 | for name, b in project.GetBranches().items(): | ||
| 201 | b.project = project | ||
| 202 | branches.append((name, b)) | ||
| 203 | return branches | ||
diff --git a/subcmds/checkout.py b/subcmds/checkout.py index c8a09a8e..9b429489 100644 --- a/subcmds/checkout.py +++ b/subcmds/checkout.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2009 The Android Open Source Project | 1 | # Copyright (C) 2009 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,13 +12,15 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from __future__ import print_function | 15 | import functools |
| 18 | import sys | 16 | import sys |
| 19 | from command import Command | 17 | |
| 18 | from command import Command, DEFAULT_LOCAL_JOBS | ||
| 20 | from progress import Progress | 19 | from progress import Progress |
| 21 | 20 | ||
| 21 | |||
| 22 | class Checkout(Command): | 22 | class Checkout(Command): |
| 23 | common = True | 23 | COMMON = True |
| 24 | helpSummary = "Checkout a branch for development" | 24 | helpSummary = "Checkout a branch for development" |
| 25 | helpUsage = """ | 25 | helpUsage = """ |
| 26 | %prog <branchname> [<project>...] | 26 | %prog <branchname> [<project>...] |
| @@ -33,28 +33,37 @@ The command is equivalent to: | |||
| 33 | 33 | ||
| 34 | repo forall [<project>...] -c git checkout <branchname> | 34 | repo forall [<project>...] -c git checkout <branchname> |
| 35 | """ | 35 | """ |
| 36 | PARALLEL_JOBS = DEFAULT_LOCAL_JOBS | ||
| 36 | 37 | ||
| 37 | def ValidateOptions(self, opt, args): | 38 | def ValidateOptions(self, opt, args): |
| 38 | if not args: | 39 | if not args: |
| 39 | self.Usage() | 40 | self.Usage() |
| 40 | 41 | ||
| 42 | def _ExecuteOne(self, nb, project): | ||
| 43 | """Checkout one project.""" | ||
| 44 | return (project.CheckoutBranch(nb), project) | ||
| 45 | |||
| 41 | def Execute(self, opt, args): | 46 | def Execute(self, opt, args): |
| 42 | nb = args[0] | 47 | nb = args[0] |
| 43 | err = [] | 48 | err = [] |
| 44 | success = [] | 49 | success = [] |
| 45 | all_projects = self.GetProjects(args[1:]) | 50 | all_projects = self.GetProjects(args[1:]) |
| 46 | 51 | ||
| 47 | pm = Progress('Checkout %s' % nb, len(all_projects)) | 52 | def _ProcessResults(_pool, pm, results): |
| 48 | for project in all_projects: | 53 | for status, project in results: |
| 49 | pm.update() | 54 | if status is not None: |
| 55 | if status: | ||
| 56 | success.append(project) | ||
| 57 | else: | ||
| 58 | err.append(project) | ||
| 59 | pm.update() | ||
| 50 | 60 | ||
| 51 | status = project.CheckoutBranch(nb) | 61 | self.ExecuteInParallel( |
| 52 | if status is not None: | 62 | opt.jobs, |
| 53 | if status: | 63 | functools.partial(self._ExecuteOne, nb), |
| 54 | success.append(project) | 64 | all_projects, |
| 55 | else: | 65 | callback=_ProcessResults, |
| 56 | err.append(project) | 66 | output=Progress('Checkout %s' % (nb,), len(all_projects), quiet=opt.quiet)) |
| 57 | pm.end() | ||
| 58 | 67 | ||
| 59 | if err: | 68 | if err: |
| 60 | for p in err: | 69 | for p in err: |
diff --git a/subcmds/cherry_pick.py b/subcmds/cherry_pick.py index a541a040..7bd858bf 100644 --- a/subcmds/cherry_pick.py +++ b/subcmds/cherry_pick.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2010 The Android Open Source Project | 1 | # Copyright (C) 2010 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,7 +12,6 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from __future__ import print_function | ||
| 18 | import re | 15 | import re |
| 19 | import sys | 16 | import sys |
| 20 | from command import Command | 17 | from command import Command |
| @@ -22,8 +19,9 @@ from git_command import GitCommand | |||
| 22 | 19 | ||
| 23 | CHANGE_ID_RE = re.compile(r'^\s*Change-Id: I([0-9a-f]{40})\s*$') | 20 | CHANGE_ID_RE = re.compile(r'^\s*Change-Id: I([0-9a-f]{40})\s*$') |
| 24 | 21 | ||
| 22 | |||
| 25 | class CherryPick(Command): | 23 | class CherryPick(Command): |
| 26 | common = True | 24 | COMMON = True |
| 27 | helpSummary = "Cherry-pick a change." | 25 | helpSummary = "Cherry-pick a change." |
| 28 | helpUsage = """ | 26 | helpUsage = """ |
| 29 | %prog <sha1> | 27 | %prog <sha1> |
| @@ -34,9 +32,6 @@ The change id will be updated, and a reference to the old | |||
| 34 | change id will be added. | 32 | change id will be added. |
| 35 | """ | 33 | """ |
| 36 | 34 | ||
| 37 | def _Options(self, p): | ||
| 38 | pass | ||
| 39 | |||
| 40 | def ValidateOptions(self, opt, args): | 35 | def ValidateOptions(self, opt, args): |
| 41 | if len(args) != 1: | 36 | if len(args) != 1: |
| 42 | self.Usage() | 37 | self.Usage() |
| @@ -46,8 +41,8 @@ change id will be added. | |||
| 46 | 41 | ||
| 47 | p = GitCommand(None, | 42 | p = GitCommand(None, |
| 48 | ['rev-parse', '--verify', reference], | 43 | ['rev-parse', '--verify', reference], |
| 49 | capture_stdout = True, | 44 | capture_stdout=True, |
| 50 | capture_stderr = True) | 45 | capture_stderr=True) |
| 51 | if p.Wait() != 0: | 46 | if p.Wait() != 0: |
| 52 | print(p.stderr, file=sys.stderr) | 47 | print(p.stderr, file=sys.stderr) |
| 53 | sys.exit(1) | 48 | sys.exit(1) |
| @@ -61,8 +56,8 @@ change id will be added. | |||
| 61 | 56 | ||
| 62 | p = GitCommand(None, | 57 | p = GitCommand(None, |
| 63 | ['cherry-pick', sha1], | 58 | ['cherry-pick', sha1], |
| 64 | capture_stdout = True, | 59 | capture_stdout=True, |
| 65 | capture_stderr = True) | 60 | capture_stderr=True) |
| 66 | status = p.Wait() | 61 | status = p.Wait() |
| 67 | 62 | ||
| 68 | print(p.stdout, file=sys.stdout) | 63 | print(p.stdout, file=sys.stdout) |
| @@ -74,11 +69,9 @@ change id will be added. | |||
| 74 | new_msg = self._Reformat(old_msg, sha1) | 69 | new_msg = self._Reformat(old_msg, sha1) |
| 75 | 70 | ||
| 76 | p = GitCommand(None, ['commit', '--amend', '-F', '-'], | 71 | p = GitCommand(None, ['commit', '--amend', '-F', '-'], |
| 77 | provide_stdin = True, | 72 | input=new_msg, |
| 78 | capture_stdout = True, | 73 | capture_stdout=True, |
| 79 | capture_stderr = True) | 74 | capture_stderr=True) |
| 80 | p.stdin.write(new_msg) | ||
| 81 | p.stdin.close() | ||
| 82 | if p.Wait() != 0: | 75 | if p.Wait() != 0: |
| 83 | print("error: Failed to update commit message", file=sys.stderr) | 76 | print("error: Failed to update commit message", file=sys.stderr) |
| 84 | sys.exit(1) | 77 | sys.exit(1) |
| @@ -97,7 +90,7 @@ change id will be added. | |||
| 97 | 90 | ||
| 98 | def _StripHeader(self, commit_msg): | 91 | def _StripHeader(self, commit_msg): |
| 99 | lines = commit_msg.splitlines() | 92 | lines = commit_msg.splitlines() |
| 100 | return "\n".join(lines[lines.index("")+1:]) | 93 | return "\n".join(lines[lines.index("") + 1:]) |
| 101 | 94 | ||
| 102 | def _Reformat(self, old_msg, sha1): | 95 | def _Reformat(self, old_msg, sha1): |
| 103 | new_msg = [] | 96 | new_msg = [] |
diff --git a/subcmds/diff.py b/subcmds/diff.py index fa41e70e..00a7ec29 100644 --- a/subcmds/diff.py +++ b/subcmds/diff.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2008 The Android Open Source Project | 1 | # Copyright (C) 2008 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,10 +12,14 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from command import PagedCommand | 15 | import functools |
| 16 | import io | ||
| 17 | |||
| 18 | from command import DEFAULT_LOCAL_JOBS, PagedCommand | ||
| 19 | |||
| 18 | 20 | ||
| 19 | class Diff(PagedCommand): | 21 | class Diff(PagedCommand): |
| 20 | common = True | 22 | COMMON = True |
| 21 | helpSummary = "Show changes between commit and working tree" | 23 | helpSummary = "Show changes between commit and working tree" |
| 22 | helpUsage = """ | 24 | helpUsage = """ |
| 23 | %prog [<project>...] | 25 | %prog [<project>...] |
| @@ -26,19 +28,42 @@ The -u option causes '%prog' to generate diff output with file paths | |||
| 26 | relative to the repository root, so the output can be applied | 28 | relative to the repository root, so the output can be applied |
| 27 | to the Unix 'patch' command. | 29 | to the Unix 'patch' command. |
| 28 | """ | 30 | """ |
| 31 | PARALLEL_JOBS = DEFAULT_LOCAL_JOBS | ||
| 29 | 32 | ||
| 30 | def _Options(self, p): | 33 | def _Options(self, p): |
| 31 | def cmd(option, opt_str, value, parser): | ||
| 32 | setattr(parser.values, option.dest, list(parser.rargs)) | ||
| 33 | while parser.rargs: | ||
| 34 | del parser.rargs[0] | ||
| 35 | p.add_option('-u', '--absolute', | 34 | p.add_option('-u', '--absolute', |
| 36 | dest='absolute', action='store_true', | 35 | dest='absolute', action='store_true', |
| 37 | help='Paths are relative to the repository root') | 36 | help='paths are relative to the repository root') |
| 37 | |||
| 38 | def _ExecuteOne(self, absolute, project): | ||
| 39 | """Obtains the diff for a specific project. | ||
| 40 | |||
| 41 | Args: | ||
| 42 | absolute: Paths are relative to the root. | ||
| 43 | project: Project to get status of. | ||
| 44 | |||
| 45 | Returns: | ||
| 46 | The status of the project. | ||
| 47 | """ | ||
| 48 | buf = io.StringIO() | ||
| 49 | ret = project.PrintWorkTreeDiff(absolute, output_redir=buf) | ||
| 50 | return (ret, buf.getvalue()) | ||
| 38 | 51 | ||
| 39 | def Execute(self, opt, args): | 52 | def Execute(self, opt, args): |
| 40 | ret = 0 | 53 | all_projects = self.GetProjects(args) |
| 41 | for project in self.GetProjects(args): | 54 | |
| 42 | if not project.PrintWorkTreeDiff(opt.absolute): | 55 | def _ProcessResults(_pool, _output, results): |
| 43 | ret = 1 | 56 | ret = 0 |
| 44 | return ret | 57 | for (state, output) in results: |
| 58 | if output: | ||
| 59 | print(output, end='') | ||
| 60 | if not state: | ||
| 61 | ret = 1 | ||
| 62 | return ret | ||
| 63 | |||
| 64 | return self.ExecuteInParallel( | ||
| 65 | opt.jobs, | ||
| 66 | functools.partial(self._ExecuteOne, opt.absolute), | ||
| 67 | all_projects, | ||
| 68 | callback=_ProcessResults, | ||
| 69 | ordered=True) | ||
diff --git a/subcmds/diffmanifests.py b/subcmds/diffmanifests.py index b999699e..f6cc30a2 100644 --- a/subcmds/diffmanifests.py +++ b/subcmds/diffmanifests.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2014 The Android Open Source Project | 1 | # Copyright (C) 2014 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -16,12 +14,14 @@ | |||
| 16 | 14 | ||
| 17 | from color import Coloring | 15 | from color import Coloring |
| 18 | from command import PagedCommand | 16 | from command import PagedCommand |
| 19 | from manifest_xml import XmlManifest | 17 | from manifest_xml import RepoClient |
| 18 | |||
| 20 | 19 | ||
| 21 | class _Coloring(Coloring): | 20 | class _Coloring(Coloring): |
| 22 | def __init__(self, config): | 21 | def __init__(self, config): |
| 23 | Coloring.__init__(self, config, "status") | 22 | Coloring.__init__(self, config, "status") |
| 24 | 23 | ||
| 24 | |||
| 25 | class Diffmanifests(PagedCommand): | 25 | class Diffmanifests(PagedCommand): |
| 26 | """ A command to see logs in projects represented by manifests | 26 | """ A command to see logs in projects represented by manifests |
| 27 | 27 | ||
| @@ -31,7 +31,7 @@ class Diffmanifests(PagedCommand): | |||
| 31 | deeper level. | 31 | deeper level. |
| 32 | """ | 32 | """ |
| 33 | 33 | ||
| 34 | common = True | 34 | COMMON = True |
| 35 | helpSummary = "Manifest diff utility" | 35 | helpSummary = "Manifest diff utility" |
| 36 | helpUsage = """%prog manifest1.xml [manifest2.xml] [options]""" | 36 | helpUsage = """%prog manifest1.xml [manifest2.xml] [options]""" |
| 37 | 37 | ||
| @@ -68,16 +68,16 @@ synced and their revisions won't be found. | |||
| 68 | def _Options(self, p): | 68 | def _Options(self, p): |
| 69 | p.add_option('--raw', | 69 | p.add_option('--raw', |
| 70 | dest='raw', action='store_true', | 70 | dest='raw', action='store_true', |
| 71 | help='Display raw diff.') | 71 | help='display raw diff') |
| 72 | p.add_option('--no-color', | 72 | p.add_option('--no-color', |
| 73 | dest='color', action='store_false', default=True, | 73 | dest='color', action='store_false', default=True, |
| 74 | help='does not display the diff in color.') | 74 | help='does not display the diff in color') |
| 75 | p.add_option('--pretty-format', | 75 | p.add_option('--pretty-format', |
| 76 | dest='pretty_format', action='store', | 76 | dest='pretty_format', action='store', |
| 77 | metavar='<FORMAT>', | 77 | metavar='<FORMAT>', |
| 78 | help='print the log using a custom git pretty format string') | 78 | help='print the log using a custom git pretty format string') |
| 79 | 79 | ||
| 80 | def _printRawDiff(self, diff): | 80 | def _printRawDiff(self, diff, pretty_format=None): |
| 81 | for project in diff['added']: | 81 | for project in diff['added']: |
| 82 | self.printText("A %s %s" % (project.relpath, project.revisionExpr)) | 82 | self.printText("A %s %s" % (project.relpath, project.revisionExpr)) |
| 83 | self.out.nl() | 83 | self.out.nl() |
| @@ -90,7 +90,7 @@ synced and their revisions won't be found. | |||
| 90 | self.printText("C %s %s %s" % (project.relpath, project.revisionExpr, | 90 | self.printText("C %s %s %s" % (project.relpath, project.revisionExpr, |
| 91 | otherProject.revisionExpr)) | 91 | otherProject.revisionExpr)) |
| 92 | self.out.nl() | 92 | self.out.nl() |
| 93 | self._printLogs(project, otherProject, raw=True, color=False) | 93 | self._printLogs(project, otherProject, raw=True, color=False, pretty_format=pretty_format) |
| 94 | 94 | ||
| 95 | for project, otherProject in diff['unreachable']: | 95 | for project, otherProject in diff['unreachable']: |
| 96 | self.printText("U %s %s %s" % (project.relpath, project.revisionExpr, | 96 | self.printText("U %s %s %s" % (project.relpath, project.revisionExpr, |
| @@ -181,26 +181,26 @@ synced and their revisions won't be found. | |||
| 181 | self.OptionParser.error('missing manifests to diff') | 181 | self.OptionParser.error('missing manifests to diff') |
| 182 | 182 | ||
| 183 | def Execute(self, opt, args): | 183 | def Execute(self, opt, args): |
| 184 | self.out = _Coloring(self.manifest.globalConfig) | 184 | self.out = _Coloring(self.client.globalConfig) |
| 185 | self.printText = self.out.nofmt_printer('text') | 185 | self.printText = self.out.nofmt_printer('text') |
| 186 | if opt.color: | 186 | if opt.color: |
| 187 | self.printProject = self.out.nofmt_printer('project', attr = 'bold') | 187 | self.printProject = self.out.nofmt_printer('project', attr='bold') |
| 188 | self.printAdded = self.out.nofmt_printer('green', fg = 'green', attr = 'bold') | 188 | self.printAdded = self.out.nofmt_printer('green', fg='green', attr='bold') |
| 189 | self.printRemoved = self.out.nofmt_printer('red', fg = 'red', attr = 'bold') | 189 | self.printRemoved = self.out.nofmt_printer('red', fg='red', attr='bold') |
| 190 | self.printRevision = self.out.nofmt_printer('revision', fg = 'yellow') | 190 | self.printRevision = self.out.nofmt_printer('revision', fg='yellow') |
| 191 | else: | 191 | else: |
| 192 | self.printProject = self.printAdded = self.printRemoved = self.printRevision = self.printText | 192 | self.printProject = self.printAdded = self.printRemoved = self.printRevision = self.printText |
| 193 | 193 | ||
| 194 | manifest1 = XmlManifest(self.manifest.repodir) | 194 | manifest1 = RepoClient(self.repodir) |
| 195 | manifest1.Override(args[0], load_local_manifests=False) | 195 | manifest1.Override(args[0], load_local_manifests=False) |
| 196 | if len(args) == 1: | 196 | if len(args) == 1: |
| 197 | manifest2 = self.manifest | 197 | manifest2 = self.manifest |
| 198 | else: | 198 | else: |
| 199 | manifest2 = XmlManifest(self.manifest.repodir) | 199 | manifest2 = RepoClient(self.repodir) |
| 200 | manifest2.Override(args[1], load_local_manifests=False) | 200 | manifest2.Override(args[1], load_local_manifests=False) |
| 201 | 201 | ||
| 202 | diff = manifest1.projectsDiff(manifest2) | 202 | diff = manifest1.projectsDiff(manifest2) |
| 203 | if opt.raw: | 203 | if opt.raw: |
| 204 | self._printRawDiff(diff) | 204 | self._printRawDiff(diff, pretty_format=opt.pretty_format) |
| 205 | else: | 205 | else: |
| 206 | self._printDiff(diff, color=opt.color, pretty_format=opt.pretty_format) | 206 | self._printDiff(diff, color=opt.color, pretty_format=opt.pretty_format) |
diff --git a/subcmds/download.py b/subcmds/download.py index f746bc23..523f25e0 100644 --- a/subcmds/download.py +++ b/subcmds/download.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2008 The Android Open Source Project | 1 | # Copyright (C) 2008 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,17 +12,17 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from __future__ import print_function | ||
| 18 | import re | 15 | import re |
| 19 | import sys | 16 | import sys |
| 20 | 17 | ||
| 21 | from command import Command | 18 | from command import Command |
| 22 | from error import GitError | 19 | from error import GitError, NoSuchProjectError |
| 23 | 20 | ||
| 24 | CHANGE_RE = re.compile(r'^([1-9][0-9]*)(?:[/\.-]([1-9][0-9]*))?$') | 21 | CHANGE_RE = re.compile(r'^([1-9][0-9]*)(?:[/\.-]([1-9][0-9]*))?$') |
| 25 | 22 | ||
| 23 | |||
| 26 | class Download(Command): | 24 | class Download(Command): |
| 27 | common = True | 25 | COMMON = True |
| 28 | helpSummary = "Download and checkout a change" | 26 | helpSummary = "Download and checkout a change" |
| 29 | helpUsage = """ | 27 | helpUsage = """ |
| 30 | %prog {[project] change[/patchset]}... | 28 | %prog {[project] change[/patchset]}... |
| @@ -36,9 +34,13 @@ If no project is specified try to use current directory as a project. | |||
| 36 | """ | 34 | """ |
| 37 | 35 | ||
| 38 | def _Options(self, p): | 36 | def _Options(self, p): |
| 37 | p.add_option('-b', '--branch', | ||
| 38 | help='create a new branch first') | ||
| 39 | p.add_option('-c', '--cherry-pick', | 39 | p.add_option('-c', '--cherry-pick', |
| 40 | dest='cherrypick', action='store_true', | 40 | dest='cherrypick', action='store_true', |
| 41 | help="cherry-pick instead of checkout") | 41 | help="cherry-pick instead of checkout") |
| 42 | p.add_option('-x', '--record-origin', action='store_true', | ||
| 43 | help='pass -x when cherry-picking') | ||
| 42 | p.add_option('-r', '--revert', | 44 | p.add_option('-r', '--revert', |
| 43 | dest='revert', action='store_true', | 45 | dest='revert', action='store_true', |
| 44 | help="revert instead of checkout") | 46 | help="revert instead of checkout") |
| @@ -58,6 +60,7 @@ If no project is specified try to use current directory as a project. | |||
| 58 | if m: | 60 | if m: |
| 59 | if not project: | 61 | if not project: |
| 60 | project = self.GetProjects(".")[0] | 62 | project = self.GetProjects(".")[0] |
| 63 | print('Defaulting to cwd project', project.name) | ||
| 61 | chg_id = int(m.group(1)) | 64 | chg_id = int(m.group(1)) |
| 62 | if m.group(2): | 65 | if m.group(2): |
| 63 | ps_id = int(m.group(2)) | 66 | ps_id = int(m.group(2)) |
| @@ -74,9 +77,33 @@ If no project is specified try to use current directory as a project. | |||
| 74 | ps_id = max(int(match.group(1)), ps_id) | 77 | ps_id = max(int(match.group(1)), ps_id) |
| 75 | to_get.append((project, chg_id, ps_id)) | 78 | to_get.append((project, chg_id, ps_id)) |
| 76 | else: | 79 | else: |
| 77 | project = self.GetProjects([a])[0] | 80 | projects = self.GetProjects([a]) |
| 81 | if len(projects) > 1: | ||
| 82 | # If the cwd is one of the projects, assume they want that. | ||
| 83 | try: | ||
| 84 | project = self.GetProjects('.')[0] | ||
| 85 | except NoSuchProjectError: | ||
| 86 | project = None | ||
| 87 | if project not in projects: | ||
| 88 | print('error: %s matches too many projects; please re-run inside ' | ||
| 89 | 'the project checkout.' % (a,), file=sys.stderr) | ||
| 90 | for project in projects: | ||
| 91 | print(' %s/ @ %s' % (project.relpath, project.revisionExpr), | ||
| 92 | file=sys.stderr) | ||
| 93 | sys.exit(1) | ||
| 94 | else: | ||
| 95 | project = projects[0] | ||
| 96 | print('Defaulting to cwd project', project.name) | ||
| 78 | return to_get | 97 | return to_get |
| 79 | 98 | ||
| 99 | def ValidateOptions(self, opt, args): | ||
| 100 | if opt.record_origin: | ||
| 101 | if not opt.cherrypick: | ||
| 102 | self.OptionParser.error('-x only makes sense with --cherry-pick') | ||
| 103 | |||
| 104 | if opt.ffonly: | ||
| 105 | self.OptionParser.error('-x and --ff are mutually exclusive options') | ||
| 106 | |||
| 80 | def Execute(self, opt, args): | 107 | def Execute(self, opt, args): |
| 81 | for project, change_id, ps_id in self._ParseChangeIds(args): | 108 | for project, change_id, ps_id in self._ParseChangeIds(args): |
| 82 | dl = project.DownloadPatchSet(change_id, ps_id) | 109 | dl = project.DownloadPatchSet(change_id, ps_id) |
| @@ -93,22 +120,41 @@ If no project is specified try to use current directory as a project. | |||
| 93 | continue | 120 | continue |
| 94 | 121 | ||
| 95 | if len(dl.commits) > 1: | 122 | if len(dl.commits) > 1: |
| 96 | print('[%s] %d/%d depends on %d unmerged changes:' \ | 123 | print('[%s] %d/%d depends on %d unmerged changes:' |
| 97 | % (project.name, change_id, ps_id, len(dl.commits)), | 124 | % (project.name, change_id, ps_id, len(dl.commits)), |
| 98 | file=sys.stderr) | 125 | file=sys.stderr) |
| 99 | for c in dl.commits: | 126 | for c in dl.commits: |
| 100 | print(' %s' % (c), file=sys.stderr) | 127 | print(' %s' % (c), file=sys.stderr) |
| 101 | if opt.cherrypick: | ||
| 102 | try: | ||
| 103 | project._CherryPick(dl.commit) | ||
| 104 | except GitError: | ||
| 105 | print('[%s] Could not complete the cherry-pick of %s' \ | ||
| 106 | % (project.name, dl.commit), file=sys.stderr) | ||
| 107 | sys.exit(1) | ||
| 108 | 128 | ||
| 129 | if opt.cherrypick: | ||
| 130 | mode = 'cherry-pick' | ||
| 109 | elif opt.revert: | 131 | elif opt.revert: |
| 110 | project._Revert(dl.commit) | 132 | mode = 'revert' |
| 111 | elif opt.ffonly: | 133 | elif opt.ffonly: |
| 112 | project._FastForward(dl.commit, ffonly=True) | 134 | mode = 'fast-forward merge' |
| 113 | else: | 135 | else: |
| 114 | project._Checkout(dl.commit) | 136 | mode = 'checkout' |
| 137 | |||
| 138 | # We'll combine the branch+checkout operation, but all the rest need a | ||
| 139 | # dedicated branch start. | ||
| 140 | if opt.branch and mode != 'checkout': | ||
| 141 | project.StartBranch(opt.branch) | ||
| 142 | |||
| 143 | try: | ||
| 144 | if opt.cherrypick: | ||
| 145 | project._CherryPick(dl.commit, ffonly=opt.ffonly, | ||
| 146 | record_origin=opt.record_origin) | ||
| 147 | elif opt.revert: | ||
| 148 | project._Revert(dl.commit) | ||
| 149 | elif opt.ffonly: | ||
| 150 | project._FastForward(dl.commit, ffonly=True) | ||
| 151 | else: | ||
| 152 | if opt.branch: | ||
| 153 | project.StartBranch(opt.branch, revision=dl.commit) | ||
| 154 | else: | ||
| 155 | project._Checkout(dl.commit) | ||
| 156 | |||
| 157 | except GitError: | ||
| 158 | print('[%s] Could not complete the %s of %s' | ||
| 159 | % (project.name, mode, dl.commit), file=sys.stderr) | ||
| 160 | sys.exit(1) | ||
diff --git a/subcmds/forall.py b/subcmds/forall.py index 131ba676..7c1dea9e 100644 --- a/subcmds/forall.py +++ b/subcmds/forall.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2008 The Android Open Source Project | 1 | # Copyright (C) 2008 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,8 +12,9 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from __future__ import print_function | ||
| 18 | import errno | 15 | import errno |
| 16 | import functools | ||
| 17 | import io | ||
| 19 | import multiprocessing | 18 | import multiprocessing |
| 20 | import re | 19 | import re |
| 21 | import os | 20 | import os |
| @@ -24,14 +23,14 @@ import sys | |||
| 24 | import subprocess | 23 | import subprocess |
| 25 | 24 | ||
| 26 | from color import Coloring | 25 | from color import Coloring |
| 27 | from command import Command, MirrorSafeCommand | 26 | from command import DEFAULT_LOCAL_JOBS, Command, MirrorSafeCommand, WORKER_BATCH_SIZE |
| 28 | import platform_utils | 27 | from error import ManifestInvalidRevisionError |
| 29 | 28 | ||
| 30 | _CAN_COLOR = [ | 29 | _CAN_COLOR = [ |
| 31 | 'branch', | 30 | 'branch', |
| 32 | 'diff', | 31 | 'diff', |
| 33 | 'grep', | 32 | 'grep', |
| 34 | 'log', | 33 | 'log', |
| 35 | ] | 34 | ] |
| 36 | 35 | ||
| 37 | 36 | ||
| @@ -42,11 +41,11 @@ class ForallColoring(Coloring): | |||
| 42 | 41 | ||
| 43 | 42 | ||
| 44 | class Forall(Command, MirrorSafeCommand): | 43 | class Forall(Command, MirrorSafeCommand): |
| 45 | common = False | 44 | COMMON = False |
| 46 | helpSummary = "Run a shell command in each project" | 45 | helpSummary = "Run a shell command in each project" |
| 47 | helpUsage = """ | 46 | helpUsage = """ |
| 48 | %prog [<project>...] -c <command> [<arg>...] | 47 | %prog [<project>...] -c <command> [<arg>...] |
| 49 | %prog -r str1 [str2] ... -c <command> [<arg>...]" | 48 | %prog -r str1 [str2] ... -c <command> [<arg>...] |
| 50 | """ | 49 | """ |
| 51 | helpDescription = """ | 50 | helpDescription = """ |
| 52 | Executes the same shell command in each project. | 51 | Executes the same shell command in each project. |
| @@ -54,6 +53,11 @@ Executes the same shell command in each project. | |||
| 54 | The -r option allows running the command only on projects matching | 53 | The -r option allows running the command only on projects matching |
| 55 | regex or wildcard expression. | 54 | regex or wildcard expression. |
| 56 | 55 | ||
| 56 | By default, projects are processed non-interactively in parallel. If you want | ||
| 57 | to run interactive commands, make sure to pass --interactive to force --jobs 1. | ||
| 58 | While the processing order of projects is not guaranteed, the order of project | ||
| 59 | output is stable. | ||
| 60 | |||
| 57 | # Output Formatting | 61 | # Output Formatting |
| 58 | 62 | ||
| 59 | The -p option causes '%prog' to bind pipes to the command's stdin, | 63 | The -p option causes '%prog' to bind pipes to the command's stdin, |
| @@ -116,70 +120,48 @@ terminal and are not redirected. | |||
| 116 | If -e is used, when a command exits unsuccessfully, '%prog' will abort | 120 | If -e is used, when a command exits unsuccessfully, '%prog' will abort |
| 117 | without iterating through the remaining projects. | 121 | without iterating through the remaining projects. |
| 118 | """ | 122 | """ |
| 123 | PARALLEL_JOBS = DEFAULT_LOCAL_JOBS | ||
| 124 | |||
| 125 | @staticmethod | ||
| 126 | def _cmd_option(option, _opt_str, _value, parser): | ||
| 127 | setattr(parser.values, option.dest, list(parser.rargs)) | ||
| 128 | while parser.rargs: | ||
| 129 | del parser.rargs[0] | ||
| 119 | 130 | ||
| 120 | def _Options(self, p): | 131 | def _Options(self, p): |
| 121 | def cmd(option, opt_str, value, parser): | ||
| 122 | setattr(parser.values, option.dest, list(parser.rargs)) | ||
| 123 | while parser.rargs: | ||
| 124 | del parser.rargs[0] | ||
| 125 | p.add_option('-r', '--regex', | 132 | p.add_option('-r', '--regex', |
| 126 | dest='regex', action='store_true', | 133 | dest='regex', action='store_true', |
| 127 | help="Execute the command only on projects matching regex or wildcard expression") | 134 | help='execute the command only on projects matching regex or wildcard expression') |
| 128 | p.add_option('-i', '--inverse-regex', | 135 | p.add_option('-i', '--inverse-regex', |
| 129 | dest='inverse_regex', action='store_true', | 136 | dest='inverse_regex', action='store_true', |
| 130 | help="Execute the command only on projects not matching regex or wildcard expression") | 137 | help='execute the command only on projects not matching regex or ' |
| 138 | 'wildcard expression') | ||
| 131 | p.add_option('-g', '--groups', | 139 | p.add_option('-g', '--groups', |
| 132 | dest='groups', | 140 | dest='groups', |
| 133 | help="Execute the command only on projects matching the specified groups") | 141 | help='execute the command only on projects matching the specified groups') |
| 134 | p.add_option('-c', '--command', | 142 | p.add_option('-c', '--command', |
| 135 | help='Command (and arguments) to execute', | 143 | help='command (and arguments) to execute', |
| 136 | dest='command', | 144 | dest='command', |
| 137 | action='callback', | 145 | action='callback', |
| 138 | callback=cmd) | 146 | callback=self._cmd_option) |
| 139 | p.add_option('-e', '--abort-on-errors', | 147 | p.add_option('-e', '--abort-on-errors', |
| 140 | dest='abort_on_errors', action='store_true', | 148 | dest='abort_on_errors', action='store_true', |
| 141 | help='Abort if a command exits unsuccessfully') | 149 | help='abort if a command exits unsuccessfully') |
| 142 | p.add_option('--ignore-missing', action='store_true', | 150 | p.add_option('--ignore-missing', action='store_true', |
| 143 | help='Silently skip & do not exit non-zero due missing ' | 151 | help='silently skip & do not exit non-zero due missing ' |
| 144 | 'checkouts') | 152 | 'checkouts') |
| 145 | 153 | ||
| 146 | g = p.add_option_group('Output') | 154 | g = p.get_option_group('--quiet') |
| 147 | g.add_option('-p', | 155 | g.add_option('-p', |
| 148 | dest='project_header', action='store_true', | 156 | dest='project_header', action='store_true', |
| 149 | help='Show project headers before output') | 157 | help='show project headers before output') |
| 150 | g.add_option('-v', '--verbose', | 158 | p.add_option('--interactive', |
| 151 | dest='verbose', action='store_true', | 159 | action='store_true', |
| 152 | help='Show command error messages') | 160 | help='force interactive usage') |
| 153 | g.add_option('-j', '--jobs', | ||
| 154 | dest='jobs', action='store', type='int', default=1, | ||
| 155 | help='number of commands to execute simultaneously') | ||
| 156 | 161 | ||
| 157 | def WantPager(self, opt): | 162 | def WantPager(self, opt): |
| 158 | return opt.project_header and opt.jobs == 1 | 163 | return opt.project_header and opt.jobs == 1 |
| 159 | 164 | ||
| 160 | def _SerializeProject(self, project): | ||
| 161 | """ Serialize a project._GitGetByExec instance. | ||
| 162 | |||
| 163 | project._GitGetByExec is not pickle-able. Instead of trying to pass it | ||
| 164 | around between processes, make a dict ourselves containing only the | ||
| 165 | attributes that we need. | ||
| 166 | |||
| 167 | """ | ||
| 168 | if not self.manifest.IsMirror: | ||
| 169 | lrev = project.GetRevisionId() | ||
| 170 | else: | ||
| 171 | lrev = None | ||
| 172 | return { | ||
| 173 | 'name': project.name, | ||
| 174 | 'relpath': project.relpath, | ||
| 175 | 'remote_name': project.remote.name, | ||
| 176 | 'lrev': lrev, | ||
| 177 | 'rrev': project.revisionExpr, | ||
| 178 | 'annotations': dict((a.name, a.value) for a in project.annotations), | ||
| 179 | 'gitdir': project.gitdir, | ||
| 180 | 'worktree': project.worktree, | ||
| 181 | } | ||
| 182 | |||
| 183 | def ValidateOptions(self, opt, args): | 165 | def ValidateOptions(self, opt, args): |
| 184 | if not opt.command: | 166 | if not opt.command: |
| 185 | self.Usage() | 167 | self.Usage() |
| @@ -195,9 +177,14 @@ without iterating through the remaining projects. | |||
| 195 | cmd.append(cmd[0]) | 177 | cmd.append(cmd[0]) |
| 196 | cmd.extend(opt.command[1:]) | 178 | cmd.extend(opt.command[1:]) |
| 197 | 179 | ||
| 198 | if opt.project_header \ | 180 | # Historically, forall operated interactively, and in serial. If the user |
| 199 | and not shell \ | 181 | # has selected 1 job, then default to interacive mode. |
| 200 | and cmd[0] == 'git': | 182 | if opt.jobs == 1: |
| 183 | opt.interactive = True | ||
| 184 | |||
| 185 | if opt.project_header \ | ||
| 186 | and not shell \ | ||
| 187 | and cmd[0] == 'git': | ||
| 201 | # If this is a direct git command that can enable colorized | 188 | # If this is a direct git command that can enable colorized |
| 202 | # output and the user prefers coloring, add --color into the | 189 | # output and the user prefers coloring, add --color into the |
| 203 | # command line because we are going to wrap the command into | 190 | # command line because we are going to wrap the command into |
| @@ -220,7 +207,7 @@ without iterating through the remaining projects. | |||
| 220 | 207 | ||
| 221 | smart_sync_manifest_name = "smart_sync_override.xml" | 208 | smart_sync_manifest_name = "smart_sync_override.xml" |
| 222 | smart_sync_manifest_path = os.path.join( | 209 | smart_sync_manifest_path = os.path.join( |
| 223 | self.manifest.manifestProject.worktree, smart_sync_manifest_name) | 210 | self.manifest.manifestProject.worktree, smart_sync_manifest_name) |
| 224 | 211 | ||
| 225 | if os.path.isfile(smart_sync_manifest_path): | 212 | if os.path.isfile(smart_sync_manifest_path): |
| 226 | self.manifest.Override(smart_sync_manifest_path) | 213 | self.manifest.Override(smart_sync_manifest_path) |
| @@ -234,58 +221,50 @@ without iterating through the remaining projects. | |||
| 234 | 221 | ||
| 235 | os.environ['REPO_COUNT'] = str(len(projects)) | 222 | os.environ['REPO_COUNT'] = str(len(projects)) |
| 236 | 223 | ||
| 237 | pool = multiprocessing.Pool(opt.jobs, InitWorker) | ||
| 238 | try: | 224 | try: |
| 239 | config = self.manifest.manifestProject.config | 225 | config = self.manifest.manifestProject.config |
| 240 | results_it = pool.imap( | 226 | with multiprocessing.Pool(opt.jobs, InitWorker) as pool: |
| 241 | DoWorkWrapper, | 227 | results_it = pool.imap( |
| 242 | self.ProjectArgs(projects, mirror, opt, cmd, shell, config)) | 228 | functools.partial(DoWorkWrapper, mirror, opt, cmd, shell, config), |
| 243 | pool.close() | 229 | enumerate(projects), |
| 244 | for r in results_it: | 230 | chunksize=WORKER_BATCH_SIZE) |
| 245 | rc = rc or r | 231 | first = True |
| 246 | if r != 0 and opt.abort_on_errors: | 232 | for (r, output) in results_it: |
| 247 | raise Exception('Aborting due to previous error') | 233 | if output: |
| 234 | if first: | ||
| 235 | first = False | ||
| 236 | elif opt.project_header: | ||
| 237 | print() | ||
| 238 | # To simplify the DoWorkWrapper, take care of automatic newlines. | ||
| 239 | end = '\n' | ||
| 240 | if output[-1] == '\n': | ||
| 241 | end = '' | ||
| 242 | print(output, end=end) | ||
| 243 | rc = rc or r | ||
| 244 | if r != 0 and opt.abort_on_errors: | ||
| 245 | raise Exception('Aborting due to previous error') | ||
| 248 | except (KeyboardInterrupt, WorkerKeyboardInterrupt): | 246 | except (KeyboardInterrupt, WorkerKeyboardInterrupt): |
| 249 | # Catch KeyboardInterrupt raised inside and outside of workers | 247 | # Catch KeyboardInterrupt raised inside and outside of workers |
| 250 | print('Interrupted - terminating the pool') | ||
| 251 | pool.terminate() | ||
| 252 | rc = rc or errno.EINTR | 248 | rc = rc or errno.EINTR |
| 253 | except Exception as e: | 249 | except Exception as e: |
| 254 | # Catch any other exceptions raised | 250 | # Catch any other exceptions raised |
| 255 | print('Got an error, terminating the pool: %s: %s' % | 251 | print('forall: unhandled error, terminating the pool: %s: %s' % |
| 256 | (type(e).__name__, e), | 252 | (type(e).__name__, e), |
| 257 | file=sys.stderr) | 253 | file=sys.stderr) |
| 258 | pool.terminate() | ||
| 259 | rc = rc or getattr(e, 'errno', 1) | 254 | rc = rc or getattr(e, 'errno', 1) |
| 260 | finally: | ||
| 261 | pool.join() | ||
| 262 | if rc != 0: | 255 | if rc != 0: |
| 263 | sys.exit(rc) | 256 | sys.exit(rc) |
| 264 | 257 | ||
| 265 | def ProjectArgs(self, projects, mirror, opt, cmd, shell, config): | ||
| 266 | for cnt, p in enumerate(projects): | ||
| 267 | try: | ||
| 268 | project = self._SerializeProject(p) | ||
| 269 | except Exception as e: | ||
| 270 | print('Project list error on project %s: %s: %s' % | ||
| 271 | (p.name, type(e).__name__, e), | ||
| 272 | file=sys.stderr) | ||
| 273 | return | ||
| 274 | except KeyboardInterrupt: | ||
| 275 | print('Project list interrupted', | ||
| 276 | file=sys.stderr) | ||
| 277 | return | ||
| 278 | yield [mirror, opt, cmd, shell, cnt, config, project] | ||
| 279 | 258 | ||
| 280 | class WorkerKeyboardInterrupt(Exception): | 259 | class WorkerKeyboardInterrupt(Exception): |
| 281 | """ Keyboard interrupt exception for worker processes. """ | 260 | """ Keyboard interrupt exception for worker processes. """ |
| 282 | pass | ||
| 283 | 261 | ||
| 284 | 262 | ||
| 285 | def InitWorker(): | 263 | def InitWorker(): |
| 286 | signal.signal(signal.SIGINT, signal.SIG_IGN) | 264 | signal.signal(signal.SIGINT, signal.SIG_IGN) |
| 287 | 265 | ||
| 288 | def DoWorkWrapper(args): | 266 | |
| 267 | def DoWorkWrapper(mirror, opt, cmd, shell, config, args): | ||
| 289 | """ A wrapper around the DoWork() method. | 268 | """ A wrapper around the DoWork() method. |
| 290 | 269 | ||
| 291 | Catch the KeyboardInterrupt exceptions here and re-raise them as a different, | 270 | Catch the KeyboardInterrupt exceptions here and re-raise them as a different, |
| @@ -293,109 +272,81 @@ def DoWorkWrapper(args): | |||
| 293 | and making the parent hang indefinitely. | 272 | and making the parent hang indefinitely. |
| 294 | 273 | ||
| 295 | """ | 274 | """ |
| 296 | project = args.pop() | 275 | cnt, project = args |
| 297 | try: | 276 | try: |
| 298 | return DoWork(project, *args) | 277 | return DoWork(project, mirror, opt, cmd, shell, cnt, config) |
| 299 | except KeyboardInterrupt: | 278 | except KeyboardInterrupt: |
| 300 | print('%s: Worker interrupted' % project['name']) | 279 | print('%s: Worker interrupted' % project.name) |
| 301 | raise WorkerKeyboardInterrupt() | 280 | raise WorkerKeyboardInterrupt() |
| 302 | 281 | ||
| 303 | 282 | ||
| 304 | def DoWork(project, mirror, opt, cmd, shell, cnt, config): | 283 | def DoWork(project, mirror, opt, cmd, shell, cnt, config): |
| 305 | env = os.environ.copy() | 284 | env = os.environ.copy() |
| 285 | |||
| 306 | def setenv(name, val): | 286 | def setenv(name, val): |
| 307 | if val is None: | 287 | if val is None: |
| 308 | val = '' | 288 | val = '' |
| 309 | if hasattr(val, 'encode'): | ||
| 310 | val = val.encode() | ||
| 311 | env[name] = val | 289 | env[name] = val |
| 312 | 290 | ||
| 313 | setenv('REPO_PROJECT', project['name']) | 291 | setenv('REPO_PROJECT', project.name) |
| 314 | setenv('REPO_PATH', project['relpath']) | 292 | setenv('REPO_PATH', project.relpath) |
| 315 | setenv('REPO_REMOTE', project['remote_name']) | 293 | setenv('REPO_REMOTE', project.remote.name) |
| 316 | setenv('REPO_LREV', project['lrev']) | 294 | try: |
| 317 | setenv('REPO_RREV', project['rrev']) | 295 | # If we aren't in a fully synced state and we don't have the ref the manifest |
| 296 | # wants, then this will fail. Ignore it for the purposes of this code. | ||
| 297 | lrev = '' if mirror else project.GetRevisionId() | ||
| 298 | except ManifestInvalidRevisionError: | ||
| 299 | lrev = '' | ||
| 300 | setenv('REPO_LREV', lrev) | ||
| 301 | setenv('REPO_RREV', project.revisionExpr) | ||
| 302 | setenv('REPO_UPSTREAM', project.upstream) | ||
| 303 | setenv('REPO_DEST_BRANCH', project.dest_branch) | ||
| 318 | setenv('REPO_I', str(cnt + 1)) | 304 | setenv('REPO_I', str(cnt + 1)) |
| 319 | for name in project['annotations']: | 305 | for annotation in project.annotations: |
| 320 | setenv("REPO__%s" % (name), project['annotations'][name]) | 306 | setenv("REPO__%s" % (annotation.name), annotation.value) |
| 321 | 307 | ||
| 322 | if mirror: | 308 | if mirror: |
| 323 | setenv('GIT_DIR', project['gitdir']) | 309 | setenv('GIT_DIR', project.gitdir) |
| 324 | cwd = project['gitdir'] | 310 | cwd = project.gitdir |
| 325 | else: | 311 | else: |
| 326 | cwd = project['worktree'] | 312 | cwd = project.worktree |
| 327 | 313 | ||
| 328 | if not os.path.exists(cwd): | 314 | if not os.path.exists(cwd): |
| 329 | # Allow the user to silently ignore missing checkouts so they can run on | 315 | # Allow the user to silently ignore missing checkouts so they can run on |
| 330 | # partial checkouts (good for infra recovery tools). | 316 | # partial checkouts (good for infra recovery tools). |
| 331 | if opt.ignore_missing: | 317 | if opt.ignore_missing: |
| 332 | return 0 | 318 | return (0, '') |
| 319 | |||
| 320 | output = '' | ||
| 333 | if ((opt.project_header and opt.verbose) | 321 | if ((opt.project_header and opt.verbose) |
| 334 | or not opt.project_header): | 322 | or not opt.project_header): |
| 335 | print('skipping %s/' % project['relpath'], file=sys.stderr) | 323 | output = 'skipping %s/' % project.relpath |
| 336 | return 1 | 324 | return (1, output) |
| 337 | 325 | ||
| 338 | if opt.project_header: | 326 | if opt.verbose: |
| 339 | stdin = subprocess.PIPE | 327 | stderr = subprocess.STDOUT |
| 340 | stdout = subprocess.PIPE | ||
| 341 | stderr = subprocess.PIPE | ||
| 342 | else: | 328 | else: |
| 343 | stdin = None | 329 | stderr = subprocess.DEVNULL |
| 344 | stdout = None | 330 | |
| 345 | stderr = None | 331 | stdin = None if opt.interactive else subprocess.DEVNULL |
| 346 | |||
| 347 | p = subprocess.Popen(cmd, | ||
| 348 | cwd=cwd, | ||
| 349 | shell=shell, | ||
| 350 | env=env, | ||
| 351 | stdin=stdin, | ||
| 352 | stdout=stdout, | ||
| 353 | stderr=stderr) | ||
| 354 | 332 | ||
| 333 | result = subprocess.run( | ||
| 334 | cmd, cwd=cwd, shell=shell, env=env, check=False, | ||
| 335 | encoding='utf-8', errors='replace', | ||
| 336 | stdin=stdin, stdout=subprocess.PIPE, stderr=stderr) | ||
| 337 | |||
| 338 | output = result.stdout | ||
| 355 | if opt.project_header: | 339 | if opt.project_header: |
| 356 | out = ForallColoring(config) | 340 | if output: |
| 357 | out.redirect(sys.stdout) | 341 | buf = io.StringIO() |
| 358 | empty = True | 342 | out = ForallColoring(config) |
| 359 | errbuf = '' | 343 | out.redirect(buf) |
| 360 | 344 | if mirror: | |
| 361 | p.stdin.close() | 345 | project_header_path = project.name |
| 362 | s_in = platform_utils.FileDescriptorStreams.create() | 346 | else: |
| 363 | s_in.add(p.stdout, sys.stdout, 'stdout') | 347 | project_header_path = project.relpath |
| 364 | s_in.add(p.stderr, sys.stderr, 'stderr') | 348 | out.project('project %s/' % project_header_path) |
| 365 | 349 | out.nl() | |
| 366 | while not s_in.is_done: | 350 | buf.write(output) |
| 367 | in_ready = s_in.select() | 351 | output = buf.getvalue() |
| 368 | for s in in_ready: | 352 | return (result.returncode, output) |
| 369 | buf = s.read().decode() | ||
| 370 | if not buf: | ||
| 371 | s.close() | ||
| 372 | s_in.remove(s) | ||
| 373 | continue | ||
| 374 | |||
| 375 | if not opt.verbose: | ||
| 376 | if s.std_name == 'stderr': | ||
| 377 | errbuf += buf | ||
| 378 | continue | ||
| 379 | |||
| 380 | if empty and out: | ||
| 381 | if not cnt == 0: | ||
| 382 | out.nl() | ||
| 383 | |||
| 384 | if mirror: | ||
| 385 | project_header_path = project['name'] | ||
| 386 | else: | ||
| 387 | project_header_path = project['relpath'] | ||
| 388 | out.project('project %s/', project_header_path) | ||
| 389 | out.nl() | ||
| 390 | out.flush() | ||
| 391 | if errbuf: | ||
| 392 | sys.stderr.write(errbuf) | ||
| 393 | sys.stderr.flush() | ||
| 394 | errbuf = '' | ||
| 395 | empty = False | ||
| 396 | |||
| 397 | s.dest.write(buf) | ||
| 398 | s.dest.flush() | ||
| 399 | |||
| 400 | r = p.wait() | ||
| 401 | return r | ||
diff --git a/subcmds/gitc_delete.py b/subcmds/gitc_delete.py index e5214b8e..df749469 100644 --- a/subcmds/gitc_delete.py +++ b/subcmds/gitc_delete.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2015 The Android Open Source Project | 1 | # Copyright (C) 2015 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,18 +12,14 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from __future__ import print_function | ||
| 18 | import sys | 15 | import sys |
| 19 | 16 | ||
| 20 | from command import Command, GitcClientCommand | 17 | from command import Command, GitcClientCommand |
| 21 | import platform_utils | 18 | import platform_utils |
| 22 | 19 | ||
| 23 | from pyversion import is_python3 | ||
| 24 | if not is_python3(): | ||
| 25 | input = raw_input | ||
| 26 | 20 | ||
| 27 | class GitcDelete(Command, GitcClientCommand): | 21 | class GitcDelete(Command, GitcClientCommand): |
| 28 | common = True | 22 | COMMON = True |
| 29 | visible_everywhere = False | 23 | visible_everywhere = False |
| 30 | helpSummary = "Delete a GITC Client." | 24 | helpSummary = "Delete a GITC Client." |
| 31 | helpUsage = """ | 25 | helpUsage = """ |
| @@ -39,7 +33,7 @@ and all locally downloaded sources. | |||
| 39 | def _Options(self, p): | 33 | def _Options(self, p): |
| 40 | p.add_option('-f', '--force', | 34 | p.add_option('-f', '--force', |
| 41 | dest='force', action='store_true', | 35 | dest='force', action='store_true', |
| 42 | help='Force the deletion (no prompt).') | 36 | help='force the deletion (no prompt)') |
| 43 | 37 | ||
| 44 | def Execute(self, opt, args): | 38 | def Execute(self, opt, args): |
| 45 | if not opt.force: | 39 | if not opt.force: |
diff --git a/subcmds/gitc_init.py b/subcmds/gitc_init.py index 378f9236..e705b613 100644 --- a/subcmds/gitc_init.py +++ b/subcmds/gitc_init.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2015 The Android Open Source Project | 1 | # Copyright (C) 2015 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,7 +12,6 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from __future__ import print_function | ||
| 18 | import os | 15 | import os |
| 19 | import sys | 16 | import sys |
| 20 | 17 | ||
| @@ -26,7 +23,7 @@ import wrapper | |||
| 26 | 23 | ||
| 27 | 24 | ||
| 28 | class GitcInit(init.Init, GitcAvailableCommand): | 25 | class GitcInit(init.Init, GitcAvailableCommand): |
| 29 | common = True | 26 | COMMON = True |
| 30 | helpSummary = "Initialize a GITC Client." | 27 | helpSummary = "Initialize a GITC Client." |
| 31 | helpUsage = """ | 28 | helpUsage = """ |
| 32 | %prog [options] [client name] | 29 | %prog [options] [client name] |
| @@ -50,23 +47,17 @@ use for this GITC client. | |||
| 50 | """ | 47 | """ |
| 51 | 48 | ||
| 52 | def _Options(self, p): | 49 | def _Options(self, p): |
| 53 | super(GitcInit, self)._Options(p, gitc_init=True) | 50 | super()._Options(p, gitc_init=True) |
| 54 | g = p.add_option_group('GITC options') | ||
| 55 | g.add_option('-f', '--manifest-file', | ||
| 56 | dest='manifest_file', | ||
| 57 | help='Optional manifest file to use for this GITC client.') | ||
| 58 | g.add_option('-c', '--gitc-client', | ||
| 59 | dest='gitc_client', | ||
| 60 | help='The name of the gitc_client instance to create or modify.') | ||
| 61 | 51 | ||
| 62 | def Execute(self, opt, args): | 52 | def Execute(self, opt, args): |
| 63 | gitc_client = gitc_utils.parse_clientdir(os.getcwd()) | 53 | gitc_client = gitc_utils.parse_clientdir(os.getcwd()) |
| 64 | if not gitc_client or (opt.gitc_client and gitc_client != opt.gitc_client): | 54 | if not gitc_client or (opt.gitc_client and gitc_client != opt.gitc_client): |
| 65 | print('fatal: Please update your repo command. See go/gitc for instructions.', file=sys.stderr) | 55 | print('fatal: Please update your repo command. See go/gitc for instructions.', |
| 56 | file=sys.stderr) | ||
| 66 | sys.exit(1) | 57 | sys.exit(1) |
| 67 | self.client_dir = os.path.join(gitc_utils.get_gitc_manifest_dir(), | 58 | self.client_dir = os.path.join(gitc_utils.get_gitc_manifest_dir(), |
| 68 | gitc_client) | 59 | gitc_client) |
| 69 | super(GitcInit, self).Execute(opt, args) | 60 | super().Execute(opt, args) |
| 70 | 61 | ||
| 71 | manifest_file = self.manifest.manifestFile | 62 | manifest_file = self.manifest.manifestFile |
| 72 | if opt.manifest_file: | 63 | if opt.manifest_file: |
diff --git a/subcmds/grep.py b/subcmds/grep.py index 4dd85d57..8ac4ba14 100644 --- a/subcmds/grep.py +++ b/subcmds/grep.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2009 The Android Open Source Project | 1 | # Copyright (C) 2009 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,14 +12,14 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from __future__ import print_function | 15 | import functools |
| 18 | |||
| 19 | import sys | 16 | import sys |
| 20 | 17 | ||
| 21 | from color import Coloring | 18 | from color import Coloring |
| 22 | from command import PagedCommand | 19 | from command import DEFAULT_LOCAL_JOBS, PagedCommand |
| 23 | from error import GitError | 20 | from error import GitError |
| 24 | from git_command import git_require, GitCommand | 21 | from git_command import GitCommand |
| 22 | |||
| 25 | 23 | ||
| 26 | class GrepColoring(Coloring): | 24 | class GrepColoring(Coloring): |
| 27 | def __init__(self, config): | 25 | def __init__(self, config): |
| @@ -29,8 +27,9 @@ class GrepColoring(Coloring): | |||
| 29 | self.project = self.printer('project', attr='bold') | 27 | self.project = self.printer('project', attr='bold') |
| 30 | self.fail = self.printer('fail', fg='red') | 28 | self.fail = self.printer('fail', fg='red') |
| 31 | 29 | ||
| 30 | |||
| 32 | class Grep(PagedCommand): | 31 | class Grep(PagedCommand): |
| 33 | common = True | 32 | COMMON = True |
| 34 | helpSummary = "Print lines matching a pattern" | 33 | helpSummary = "Print lines matching a pattern" |
| 35 | helpUsage = """ | 34 | helpUsage = """ |
| 36 | %prog {pattern | -e pattern} [<project>...] | 35 | %prog {pattern | -e pattern} [<project>...] |
| @@ -63,30 +62,33 @@ contain a line that matches both expressions: | |||
| 63 | repo grep --all-match -e NODE -e Unexpected | 62 | repo grep --all-match -e NODE -e Unexpected |
| 64 | 63 | ||
| 65 | """ | 64 | """ |
| 65 | PARALLEL_JOBS = DEFAULT_LOCAL_JOBS | ||
| 66 | |||
| 67 | @staticmethod | ||
| 68 | def _carry_option(_option, opt_str, value, parser): | ||
| 69 | pt = getattr(parser.values, 'cmd_argv', None) | ||
| 70 | if pt is None: | ||
| 71 | pt = [] | ||
| 72 | setattr(parser.values, 'cmd_argv', pt) | ||
| 73 | |||
| 74 | if opt_str == '-(': | ||
| 75 | pt.append('(') | ||
| 76 | elif opt_str == '-)': | ||
| 77 | pt.append(')') | ||
| 78 | else: | ||
| 79 | pt.append(opt_str) | ||
| 66 | 80 | ||
| 67 | def _Options(self, p): | 81 | if value is not None: |
| 68 | def carry(option, | 82 | pt.append(value) |
| 69 | opt_str, | ||
| 70 | value, | ||
| 71 | parser): | ||
| 72 | pt = getattr(parser.values, 'cmd_argv', None) | ||
| 73 | if pt is None: | ||
| 74 | pt = [] | ||
| 75 | setattr(parser.values, 'cmd_argv', pt) | ||
| 76 | |||
| 77 | if opt_str == '-(': | ||
| 78 | pt.append('(') | ||
| 79 | elif opt_str == '-)': | ||
| 80 | pt.append(')') | ||
| 81 | else: | ||
| 82 | pt.append(opt_str) | ||
| 83 | 83 | ||
| 84 | if value is not None: | 84 | def _CommonOptions(self, p): |
| 85 | pt.append(value) | 85 | """Override common options slightly.""" |
| 86 | super()._CommonOptions(p, opt_v=False) | ||
| 86 | 87 | ||
| 88 | def _Options(self, p): | ||
| 87 | g = p.add_option_group('Sources') | 89 | g = p.add_option_group('Sources') |
| 88 | g.add_option('--cached', | 90 | g.add_option('--cached', |
| 89 | action='callback', callback=carry, | 91 | action='callback', callback=self._carry_option, |
| 90 | help='Search the index, instead of the work tree') | 92 | help='Search the index, instead of the work tree') |
| 91 | g.add_option('-r', '--revision', | 93 | g.add_option('-r', '--revision', |
| 92 | dest='revision', action='append', metavar='TREEish', | 94 | dest='revision', action='append', metavar='TREEish', |
| @@ -94,136 +96,111 @@ contain a line that matches both expressions: | |||
| 94 | 96 | ||
| 95 | g = p.add_option_group('Pattern') | 97 | g = p.add_option_group('Pattern') |
| 96 | g.add_option('-e', | 98 | g.add_option('-e', |
| 97 | action='callback', callback=carry, | 99 | action='callback', callback=self._carry_option, |
| 98 | metavar='PATTERN', type='str', | 100 | metavar='PATTERN', type='str', |
| 99 | help='Pattern to search for') | 101 | help='Pattern to search for') |
| 100 | g.add_option('-i', '--ignore-case', | 102 | g.add_option('-i', '--ignore-case', |
| 101 | action='callback', callback=carry, | 103 | action='callback', callback=self._carry_option, |
| 102 | help='Ignore case differences') | 104 | help='Ignore case differences') |
| 103 | g.add_option('-a', '--text', | 105 | g.add_option('-a', '--text', |
| 104 | action='callback', callback=carry, | 106 | action='callback', callback=self._carry_option, |
| 105 | help="Process binary files as if they were text") | 107 | help="Process binary files as if they were text") |
| 106 | g.add_option('-I', | 108 | g.add_option('-I', |
| 107 | action='callback', callback=carry, | 109 | action='callback', callback=self._carry_option, |
| 108 | help="Don't match the pattern in binary files") | 110 | help="Don't match the pattern in binary files") |
| 109 | g.add_option('-w', '--word-regexp', | 111 | g.add_option('-w', '--word-regexp', |
| 110 | action='callback', callback=carry, | 112 | action='callback', callback=self._carry_option, |
| 111 | help='Match the pattern only at word boundaries') | 113 | help='Match the pattern only at word boundaries') |
| 112 | g.add_option('-v', '--invert-match', | 114 | g.add_option('-v', '--invert-match', |
| 113 | action='callback', callback=carry, | 115 | action='callback', callback=self._carry_option, |
| 114 | help='Select non-matching lines') | 116 | help='Select non-matching lines') |
| 115 | g.add_option('-G', '--basic-regexp', | 117 | g.add_option('-G', '--basic-regexp', |
| 116 | action='callback', callback=carry, | 118 | action='callback', callback=self._carry_option, |
| 117 | help='Use POSIX basic regexp for patterns (default)') | 119 | help='Use POSIX basic regexp for patterns (default)') |
| 118 | g.add_option('-E', '--extended-regexp', | 120 | g.add_option('-E', '--extended-regexp', |
| 119 | action='callback', callback=carry, | 121 | action='callback', callback=self._carry_option, |
| 120 | help='Use POSIX extended regexp for patterns') | 122 | help='Use POSIX extended regexp for patterns') |
| 121 | g.add_option('-F', '--fixed-strings', | 123 | g.add_option('-F', '--fixed-strings', |
| 122 | action='callback', callback=carry, | 124 | action='callback', callback=self._carry_option, |
| 123 | help='Use fixed strings (not regexp) for pattern') | 125 | help='Use fixed strings (not regexp) for pattern') |
| 124 | 126 | ||
| 125 | g = p.add_option_group('Pattern Grouping') | 127 | g = p.add_option_group('Pattern Grouping') |
| 126 | g.add_option('--all-match', | 128 | g.add_option('--all-match', |
| 127 | action='callback', callback=carry, | 129 | action='callback', callback=self._carry_option, |
| 128 | help='Limit match to lines that have all patterns') | 130 | help='Limit match to lines that have all patterns') |
| 129 | g.add_option('--and', '--or', '--not', | 131 | g.add_option('--and', '--or', '--not', |
| 130 | action='callback', callback=carry, | 132 | action='callback', callback=self._carry_option, |
| 131 | help='Boolean operators to combine patterns') | 133 | help='Boolean operators to combine patterns') |
| 132 | g.add_option('-(', '-)', | 134 | g.add_option('-(', '-)', |
| 133 | action='callback', callback=carry, | 135 | action='callback', callback=self._carry_option, |
| 134 | help='Boolean operator grouping') | 136 | help='Boolean operator grouping') |
| 135 | 137 | ||
| 136 | g = p.add_option_group('Output') | 138 | g = p.add_option_group('Output') |
| 137 | g.add_option('-n', | 139 | g.add_option('-n', |
| 138 | action='callback', callback=carry, | 140 | action='callback', callback=self._carry_option, |
| 139 | help='Prefix the line number to matching lines') | 141 | help='Prefix the line number to matching lines') |
| 140 | g.add_option('-C', | 142 | g.add_option('-C', |
| 141 | action='callback', callback=carry, | 143 | action='callback', callback=self._carry_option, |
| 142 | metavar='CONTEXT', type='str', | 144 | metavar='CONTEXT', type='str', |
| 143 | help='Show CONTEXT lines around match') | 145 | help='Show CONTEXT lines around match') |
| 144 | g.add_option('-B', | 146 | g.add_option('-B', |
| 145 | action='callback', callback=carry, | 147 | action='callback', callback=self._carry_option, |
| 146 | metavar='CONTEXT', type='str', | 148 | metavar='CONTEXT', type='str', |
| 147 | help='Show CONTEXT lines before match') | 149 | help='Show CONTEXT lines before match') |
| 148 | g.add_option('-A', | 150 | g.add_option('-A', |
| 149 | action='callback', callback=carry, | 151 | action='callback', callback=self._carry_option, |
| 150 | metavar='CONTEXT', type='str', | 152 | metavar='CONTEXT', type='str', |
| 151 | help='Show CONTEXT lines after match') | 153 | help='Show CONTEXT lines after match') |
| 152 | g.add_option('-l', '--name-only', '--files-with-matches', | 154 | g.add_option('-l', '--name-only', '--files-with-matches', |
| 153 | action='callback', callback=carry, | 155 | action='callback', callback=self._carry_option, |
| 154 | help='Show only file names containing matching lines') | 156 | help='Show only file names containing matching lines') |
| 155 | g.add_option('-L', '--files-without-match', | 157 | g.add_option('-L', '--files-without-match', |
| 156 | action='callback', callback=carry, | 158 | action='callback', callback=self._carry_option, |
| 157 | help='Show only file names not containing matching lines') | 159 | help='Show only file names not containing matching lines') |
| 158 | 160 | ||
| 159 | 161 | def _ExecuteOne(self, cmd_argv, project): | |
| 160 | def Execute(self, opt, args): | 162 | """Process one project.""" |
| 161 | out = GrepColoring(self.manifest.manifestProject.config) | 163 | try: |
| 162 | 164 | p = GitCommand(project, | |
| 163 | cmd_argv = ['grep'] | 165 | cmd_argv, |
| 164 | if out.is_on and git_require((1, 6, 3)): | 166 | bare=False, |
| 165 | cmd_argv.append('--color') | 167 | capture_stdout=True, |
| 166 | cmd_argv.extend(getattr(opt, 'cmd_argv', [])) | 168 | capture_stderr=True) |
| 167 | 169 | except GitError as e: | |
| 168 | if '-e' not in cmd_argv: | 170 | return (project, -1, None, str(e)) |
| 169 | if not args: | 171 | |
| 170 | self.Usage() | 172 | return (project, p.Wait(), p.stdout, p.stderr) |
| 171 | cmd_argv.append('-e') | 173 | |
| 172 | cmd_argv.append(args[0]) | 174 | @staticmethod |
| 173 | args = args[1:] | 175 | def _ProcessResults(full_name, have_rev, _pool, out, results): |
| 174 | |||
| 175 | projects = self.GetProjects(args) | ||
| 176 | |||
| 177 | full_name = False | ||
| 178 | if len(projects) > 1: | ||
| 179 | cmd_argv.append('--full-name') | ||
| 180 | full_name = True | ||
| 181 | |||
| 182 | have_rev = False | ||
| 183 | if opt.revision: | ||
| 184 | if '--cached' in cmd_argv: | ||
| 185 | print('fatal: cannot combine --cached and --revision', file=sys.stderr) | ||
| 186 | sys.exit(1) | ||
| 187 | have_rev = True | ||
| 188 | cmd_argv.extend(opt.revision) | ||
| 189 | cmd_argv.append('--') | ||
| 190 | |||
| 191 | git_failed = False | 176 | git_failed = False |
| 192 | bad_rev = False | 177 | bad_rev = False |
| 193 | have_match = False | 178 | have_match = False |
| 194 | 179 | ||
| 195 | for project in projects: | 180 | for project, rc, stdout, stderr in results: |
| 196 | try: | 181 | if rc < 0: |
| 197 | p = GitCommand(project, | ||
| 198 | cmd_argv, | ||
| 199 | bare=False, | ||
| 200 | capture_stdout=True, | ||
| 201 | capture_stderr=True) | ||
| 202 | except GitError as e: | ||
| 203 | git_failed = True | 182 | git_failed = True |
| 204 | out.project('--- project %s ---' % project.relpath) | 183 | out.project('--- project %s ---' % project.relpath) |
| 205 | out.nl() | 184 | out.nl() |
| 206 | out.fail('%s', str(e)) | 185 | out.fail('%s', stderr) |
| 207 | out.nl() | 186 | out.nl() |
| 208 | continue | 187 | continue |
| 209 | 188 | ||
| 210 | if p.Wait() != 0: | 189 | if rc: |
| 211 | # no results | 190 | # no results |
| 212 | # | 191 | if stderr: |
| 213 | if p.stderr: | 192 | if have_rev and 'fatal: ambiguous argument' in stderr: |
| 214 | if have_rev and 'fatal: ambiguous argument' in p.stderr: | ||
| 215 | bad_rev = True | 193 | bad_rev = True |
| 216 | else: | 194 | else: |
| 217 | out.project('--- project %s ---' % project.relpath) | 195 | out.project('--- project %s ---' % project.relpath) |
| 218 | out.nl() | 196 | out.nl() |
| 219 | out.fail('%s', p.stderr.strip()) | 197 | out.fail('%s', stderr.strip()) |
| 220 | out.nl() | 198 | out.nl() |
| 221 | continue | 199 | continue |
| 222 | have_match = True | 200 | have_match = True |
| 223 | 201 | ||
| 224 | # We cut the last element, to avoid a blank line. | 202 | # We cut the last element, to avoid a blank line. |
| 225 | # | 203 | r = stdout.split('\n') |
| 226 | r = p.stdout.split('\n') | ||
| 227 | r = r[0:-1] | 204 | r = r[0:-1] |
| 228 | 205 | ||
| 229 | if have_rev and full_name: | 206 | if have_rev and full_name: |
| @@ -245,6 +222,47 @@ contain a line that matches both expressions: | |||
| 245 | for line in r: | 222 | for line in r: |
| 246 | print(line) | 223 | print(line) |
| 247 | 224 | ||
| 225 | return (git_failed, bad_rev, have_match) | ||
| 226 | |||
| 227 | def Execute(self, opt, args): | ||
| 228 | out = GrepColoring(self.manifest.manifestProject.config) | ||
| 229 | |||
| 230 | cmd_argv = ['grep'] | ||
| 231 | if out.is_on: | ||
| 232 | cmd_argv.append('--color') | ||
| 233 | cmd_argv.extend(getattr(opt, 'cmd_argv', [])) | ||
| 234 | |||
| 235 | if '-e' not in cmd_argv: | ||
| 236 | if not args: | ||
| 237 | self.Usage() | ||
| 238 | cmd_argv.append('-e') | ||
| 239 | cmd_argv.append(args[0]) | ||
| 240 | args = args[1:] | ||
| 241 | |||
| 242 | projects = self.GetProjects(args) | ||
| 243 | |||
| 244 | full_name = False | ||
| 245 | if len(projects) > 1: | ||
| 246 | cmd_argv.append('--full-name') | ||
| 247 | full_name = True | ||
| 248 | |||
| 249 | have_rev = False | ||
| 250 | if opt.revision: | ||
| 251 | if '--cached' in cmd_argv: | ||
| 252 | print('fatal: cannot combine --cached and --revision', file=sys.stderr) | ||
| 253 | sys.exit(1) | ||
| 254 | have_rev = True | ||
| 255 | cmd_argv.extend(opt.revision) | ||
| 256 | cmd_argv.append('--') | ||
| 257 | |||
| 258 | git_failed, bad_rev, have_match = self.ExecuteInParallel( | ||
| 259 | opt.jobs, | ||
| 260 | functools.partial(self._ExecuteOne, cmd_argv), | ||
| 261 | projects, | ||
| 262 | callback=functools.partial(self._ProcessResults, full_name, have_rev), | ||
| 263 | output=out, | ||
| 264 | ordered=True) | ||
| 265 | |||
| 248 | if git_failed: | 266 | if git_failed: |
| 249 | sys.exit(1) | 267 | sys.exit(1) |
| 250 | elif have_match: | 268 | elif have_match: |
diff --git a/subcmds/help.py b/subcmds/help.py index 78930502..1a60ef45 100644 --- a/subcmds/help.py +++ b/subcmds/help.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2008 The Android Open Source Project | 1 | # Copyright (C) 2008 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,17 +12,19 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from __future__ import print_function | ||
| 18 | import re | 15 | import re |
| 19 | import sys | 16 | import sys |
| 20 | from formatter import AbstractFormatter, DumbWriter | 17 | import textwrap |
| 21 | 18 | ||
| 19 | from subcmds import all_commands | ||
| 22 | from color import Coloring | 20 | from color import Coloring |
| 23 | from command import PagedCommand, MirrorSafeCommand, GitcAvailableCommand, GitcClientCommand | 21 | from command import PagedCommand, MirrorSafeCommand, GitcAvailableCommand, GitcClientCommand |
| 24 | import gitc_utils | 22 | import gitc_utils |
| 23 | from wrapper import Wrapper | ||
| 24 | |||
| 25 | 25 | ||
| 26 | class Help(PagedCommand, MirrorSafeCommand): | 26 | class Help(PagedCommand, MirrorSafeCommand): |
| 27 | common = False | 27 | COMMON = False |
| 28 | helpSummary = "Display detailed help on a command" | 28 | helpSummary = "Display detailed help on a command" |
| 29 | helpUsage = """ | 29 | helpUsage = """ |
| 30 | %prog [--all|command] | 30 | %prog [--all|command] |
| @@ -41,7 +41,7 @@ Displays detailed usage information about a command. | |||
| 41 | fmt = ' %%-%ds %%s' % maxlen | 41 | fmt = ' %%-%ds %%s' % maxlen |
| 42 | 42 | ||
| 43 | for name in commandNames: | 43 | for name in commandNames: |
| 44 | command = self.commands[name] | 44 | command = all_commands[name]() |
| 45 | try: | 45 | try: |
| 46 | summary = command.helpSummary.strip() | 46 | summary = command.helpSummary.strip() |
| 47 | except AttributeError: | 47 | except AttributeError: |
| @@ -50,20 +50,27 @@ Displays detailed usage information about a command. | |||
| 50 | 50 | ||
| 51 | def _PrintAllCommands(self): | 51 | def _PrintAllCommands(self): |
| 52 | print('usage: repo COMMAND [ARGS]') | 52 | print('usage: repo COMMAND [ARGS]') |
| 53 | self.PrintAllCommandsBody() | ||
| 54 | |||
| 55 | def PrintAllCommandsBody(self): | ||
| 53 | print('The complete list of recognized repo commands are:') | 56 | print('The complete list of recognized repo commands are:') |
| 54 | commandNames = list(sorted(self.commands)) | 57 | commandNames = list(sorted(all_commands)) |
| 55 | self._PrintCommands(commandNames) | 58 | self._PrintCommands(commandNames) |
| 56 | print("See 'repo help <command>' for more information on a " | 59 | print("See 'repo help <command>' for more information on a " |
| 57 | 'specific command.') | 60 | 'specific command.') |
| 61 | print('Bug reports:', Wrapper().BUG_URL) | ||
| 58 | 62 | ||
| 59 | def _PrintCommonCommands(self): | 63 | def _PrintCommonCommands(self): |
| 60 | print('usage: repo COMMAND [ARGS]') | 64 | print('usage: repo COMMAND [ARGS]') |
| 65 | self.PrintCommonCommandsBody() | ||
| 66 | |||
| 67 | def PrintCommonCommandsBody(self): | ||
| 61 | print('The most commonly used repo commands are:') | 68 | print('The most commonly used repo commands are:') |
| 62 | 69 | ||
| 63 | def gitc_supported(cmd): | 70 | def gitc_supported(cmd): |
| 64 | if not isinstance(cmd, GitcAvailableCommand) and not isinstance(cmd, GitcClientCommand): | 71 | if not isinstance(cmd, GitcAvailableCommand) and not isinstance(cmd, GitcClientCommand): |
| 65 | return True | 72 | return True |
| 66 | if self.manifest.isGitcClient: | 73 | if self.client.isGitcClient: |
| 67 | return True | 74 | return True |
| 68 | if isinstance(cmd, GitcClientCommand): | 75 | if isinstance(cmd, GitcClientCommand): |
| 69 | return False | 76 | return False |
| @@ -72,21 +79,21 @@ Displays detailed usage information about a command. | |||
| 72 | return False | 79 | return False |
| 73 | 80 | ||
| 74 | commandNames = list(sorted([name | 81 | commandNames = list(sorted([name |
| 75 | for name, command in self.commands.items() | 82 | for name, command in all_commands.items() |
| 76 | if command.common and gitc_supported(command)])) | 83 | if command.COMMON and gitc_supported(command)])) |
| 77 | self._PrintCommands(commandNames) | 84 | self._PrintCommands(commandNames) |
| 78 | 85 | ||
| 79 | print( | 86 | print( |
| 80 | "See 'repo help <command>' for more information on a specific command.\n" | 87 | "See 'repo help <command>' for more information on a specific command.\n" |
| 81 | "See 'repo help --all' for a complete list of recognized commands.") | 88 | "See 'repo help --all' for a complete list of recognized commands.") |
| 89 | print('Bug reports:', Wrapper().BUG_URL) | ||
| 82 | 90 | ||
| 83 | def _PrintCommandHelp(self, cmd, header_prefix=''): | 91 | def _PrintCommandHelp(self, cmd, header_prefix=''): |
| 84 | class _Out(Coloring): | 92 | class _Out(Coloring): |
| 85 | def __init__(self, gc): | 93 | def __init__(self, gc): |
| 86 | Coloring.__init__(self, gc, 'help') | 94 | Coloring.__init__(self, gc, 'help') |
| 87 | self.heading = self.printer('heading', attr='bold') | 95 | self.heading = self.printer('heading', attr='bold') |
| 88 | 96 | self._first = True | |
| 89 | self.wrap = AbstractFormatter(DumbWriter()) | ||
| 90 | 97 | ||
| 91 | def _PrintSection(self, heading, bodyAttr): | 98 | def _PrintSection(self, heading, bodyAttr): |
| 92 | try: | 99 | try: |
| @@ -96,7 +103,9 @@ Displays detailed usage information about a command. | |||
| 96 | if body == '' or body is None: | 103 | if body == '' or body is None: |
| 97 | return | 104 | return |
| 98 | 105 | ||
| 99 | self.nl() | 106 | if not self._first: |
| 107 | self.nl() | ||
| 108 | self._first = False | ||
| 100 | 109 | ||
| 101 | self.heading('%s%s', header_prefix, heading) | 110 | self.heading('%s%s', header_prefix, heading) |
| 102 | self.nl() | 111 | self.nl() |
| @@ -106,7 +115,8 @@ Displays detailed usage information about a command. | |||
| 106 | body = body.strip() | 115 | body = body.strip() |
| 107 | body = body.replace('%prog', me) | 116 | body = body.replace('%prog', me) |
| 108 | 117 | ||
| 109 | asciidoc_hdr = re.compile(r'^\n?#+ (.+)$') | 118 | # Extract the title, but skip any trailing {#anchors}. |
| 119 | asciidoc_hdr = re.compile(r'^\n?#+ ([^{]+)(\{#.+\})?$') | ||
| 110 | for para in body.split("\n\n"): | 120 | for para in body.split("\n\n"): |
| 111 | if para.startswith(' '): | 121 | if para.startswith(' '): |
| 112 | self.write('%s', para) | 122 | self.write('%s', para) |
| @@ -121,19 +131,21 @@ Displays detailed usage information about a command. | |||
| 121 | self.nl() | 131 | self.nl() |
| 122 | continue | 132 | continue |
| 123 | 133 | ||
| 124 | self.wrap.add_flowing_data(para) | 134 | lines = textwrap.wrap(para.replace(' ', ' '), width=80, |
| 125 | self.wrap.end_paragraph(1) | 135 | break_long_words=False, break_on_hyphens=False) |
| 126 | self.wrap.end_paragraph(0) | 136 | for line in lines: |
| 137 | self.write('%s', line) | ||
| 138 | self.nl() | ||
| 139 | self.nl() | ||
| 127 | 140 | ||
| 128 | out = _Out(self.manifest.globalConfig) | 141 | out = _Out(self.client.globalConfig) |
| 129 | out._PrintSection('Summary', 'helpSummary') | 142 | out._PrintSection('Summary', 'helpSummary') |
| 130 | cmd.OptionParser.print_help() | 143 | cmd.OptionParser.print_help() |
| 131 | out._PrintSection('Description', 'helpDescription') | 144 | out._PrintSection('Description', 'helpDescription') |
| 132 | 145 | ||
| 133 | def _PrintAllCommandHelp(self): | 146 | def _PrintAllCommandHelp(self): |
| 134 | for name in sorted(self.commands): | 147 | for name in sorted(all_commands): |
| 135 | cmd = self.commands[name] | 148 | cmd = all_commands[name](manifest=self.manifest) |
| 136 | cmd.manifest = self.manifest | ||
| 137 | self._PrintCommandHelp(cmd, header_prefix='[%s] ' % (name,)) | 149 | self._PrintCommandHelp(cmd, header_prefix='[%s] ' % (name,)) |
| 138 | 150 | ||
| 139 | def _Options(self, p): | 151 | def _Options(self, p): |
| @@ -157,12 +169,11 @@ Displays detailed usage information about a command. | |||
| 157 | name = args[0] | 169 | name = args[0] |
| 158 | 170 | ||
| 159 | try: | 171 | try: |
| 160 | cmd = self.commands[name] | 172 | cmd = all_commands[name](manifest=self.manifest) |
| 161 | except KeyError: | 173 | except KeyError: |
| 162 | print("repo: '%s' is not a repo command." % name, file=sys.stderr) | 174 | print("repo: '%s' is not a repo command." % name, file=sys.stderr) |
| 163 | sys.exit(1) | 175 | sys.exit(1) |
| 164 | 176 | ||
| 165 | cmd.manifest = self.manifest | ||
| 166 | self._PrintCommandHelp(cmd) | 177 | self._PrintCommandHelp(cmd) |
| 167 | 178 | ||
| 168 | else: | 179 | else: |
diff --git a/subcmds/info.py b/subcmds/info.py index d62e1e64..6c1246ef 100644 --- a/subcmds/info.py +++ b/subcmds/info.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2012 The Android Open Source Project | 1 | # Copyright (C) 2012 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,18 +12,22 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 15 | import optparse | ||
| 16 | |||
| 17 | from command import PagedCommand | 17 | from command import PagedCommand |
| 18 | from color import Coloring | 18 | from color import Coloring |
| 19 | from git_refs import R_M | 19 | from git_refs import R_M, R_HEADS |
| 20 | |||
| 20 | 21 | ||
| 21 | class _Coloring(Coloring): | 22 | class _Coloring(Coloring): |
| 22 | def __init__(self, config): | 23 | def __init__(self, config): |
| 23 | Coloring.__init__(self, config, "status") | 24 | Coloring.__init__(self, config, "status") |
| 24 | 25 | ||
| 26 | |||
| 25 | class Info(PagedCommand): | 27 | class Info(PagedCommand): |
| 26 | common = True | 28 | COMMON = True |
| 27 | helpSummary = "Get info on the manifest branch, current branch or unmerged branches" | 29 | helpSummary = "Get info on the manifest branch, current branch or unmerged branches" |
| 28 | helpUsage = "%prog [-dl] [-o [-b]] [<project>...]" | 30 | helpUsage = "%prog [-dl] [-o [-c]] [<project>...]" |
| 29 | 31 | ||
| 30 | def _Options(self, p): | 32 | def _Options(self, p): |
| 31 | p.add_option('-d', '--diff', | 33 | p.add_option('-d', '--diff', |
| @@ -34,22 +36,28 @@ class Info(PagedCommand): | |||
| 34 | p.add_option('-o', '--overview', | 36 | p.add_option('-o', '--overview', |
| 35 | dest='overview', action='store_true', | 37 | dest='overview', action='store_true', |
| 36 | help='show overview of all local commits') | 38 | help='show overview of all local commits') |
| 37 | p.add_option('-b', '--current-branch', | 39 | p.add_option('-c', '--current-branch', |
| 38 | dest="current_branch", action="store_true", | 40 | dest="current_branch", action="store_true", |
| 39 | help="consider only checked out branches") | 41 | help="consider only checked out branches") |
| 42 | p.add_option('--no-current-branch', | ||
| 43 | dest='current_branch', action='store_false', | ||
| 44 | help='consider all local branches') | ||
| 45 | # Turn this into a warning & remove this someday. | ||
| 46 | p.add_option('-b', | ||
| 47 | dest='current_branch', action='store_true', | ||
| 48 | help=optparse.SUPPRESS_HELP) | ||
| 40 | p.add_option('-l', '--local-only', | 49 | p.add_option('-l', '--local-only', |
| 41 | dest="local", action="store_true", | 50 | dest="local", action="store_true", |
| 42 | help="Disable all remote operations") | 51 | help="disable all remote operations") |
| 43 | |||
| 44 | 52 | ||
| 45 | def Execute(self, opt, args): | 53 | def Execute(self, opt, args): |
| 46 | self.out = _Coloring(self.manifest.globalConfig) | 54 | self.out = _Coloring(self.client.globalConfig) |
| 47 | self.heading = self.out.printer('heading', attr = 'bold') | 55 | self.heading = self.out.printer('heading', attr='bold') |
| 48 | self.headtext = self.out.nofmt_printer('headtext', fg = 'yellow') | 56 | self.headtext = self.out.nofmt_printer('headtext', fg='yellow') |
| 49 | self.redtext = self.out.printer('redtext', fg = 'red') | 57 | self.redtext = self.out.printer('redtext', fg='red') |
| 50 | self.sha = self.out.printer("sha", fg = 'yellow') | 58 | self.sha = self.out.printer("sha", fg='yellow') |
| 51 | self.text = self.out.nofmt_printer('text') | 59 | self.text = self.out.nofmt_printer('text') |
| 52 | self.dimtext = self.out.printer('dimtext', attr = 'dim') | 60 | self.dimtext = self.out.printer('dimtext', attr='dim') |
| 53 | 61 | ||
| 54 | self.opt = opt | 62 | self.opt = opt |
| 55 | 63 | ||
| @@ -122,11 +130,14 @@ class Info(PagedCommand): | |||
| 122 | self.printSeparator() | 130 | self.printSeparator() |
| 123 | 131 | ||
| 124 | def findRemoteLocalDiff(self, project): | 132 | def findRemoteLocalDiff(self, project): |
| 125 | #Fetch all the latest commits | 133 | # Fetch all the latest commits. |
| 126 | if not self.opt.local: | 134 | if not self.opt.local: |
| 127 | project.Sync_NetworkHalf(quiet=True, current_branch_only=True) | 135 | project.Sync_NetworkHalf(quiet=True, current_branch_only=True) |
| 128 | 136 | ||
| 129 | logTarget = R_M + self.manifest.manifestProject.config.GetBranch("default").merge | 137 | branch = self.manifest.manifestProject.config.GetBranch('default').merge |
| 138 | if branch.startswith(R_HEADS): | ||
| 139 | branch = branch[len(R_HEADS):] | ||
| 140 | logTarget = R_M + branch | ||
| 130 | 141 | ||
| 131 | bareTmp = project.bare_git._bare | 142 | bareTmp = project.bare_git._bare |
| 132 | project.bare_git._bare = False | 143 | project.bare_git._bare = False |
| @@ -195,16 +206,16 @@ class Info(PagedCommand): | |||
| 195 | commits = branch.commits | 206 | commits = branch.commits |
| 196 | date = branch.date | 207 | date = branch.date |
| 197 | self.text('%s %-33s (%2d commit%s, %s)' % ( | 208 | self.text('%s %-33s (%2d commit%s, %s)' % ( |
| 198 | branch.name == project.CurrentBranch and '*' or ' ', | 209 | branch.name == project.CurrentBranch and '*' or ' ', |
| 199 | branch.name, | 210 | branch.name, |
| 200 | len(commits), | 211 | len(commits), |
| 201 | len(commits) != 1 and 's' or '', | 212 | len(commits) != 1 and 's' or '', |
| 202 | date)) | 213 | date)) |
| 203 | self.out.nl() | 214 | self.out.nl() |
| 204 | 215 | ||
| 205 | for commit in commits: | 216 | for commit in commits: |
| 206 | split = commit.split() | 217 | split = commit.split() |
| 207 | self.text('{0:38}{1} '.format('','-')) | 218 | self.text('{0:38}{1} '.format('', '-')) |
| 208 | self.sha(split[0] + " ") | 219 | self.sha(split[0] + " ") |
| 209 | self.text(" ".join(split[1:])) | 220 | self.text(" ".join(split[1:])) |
| 210 | self.out.nl() | 221 | self.out.nl() |
diff --git a/subcmds/init.py b/subcmds/init.py index 6594a602..9c6b2ad9 100644 --- a/subcmds/init.py +++ b/subcmds/init.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2008 The Android Open Source Project | 1 | # Copyright (C) 2008 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,34 +12,30 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from __future__ import print_function | ||
| 18 | import os | 15 | import os |
| 19 | import platform | 16 | import platform |
| 20 | import re | 17 | import re |
| 18 | import subprocess | ||
| 21 | import sys | 19 | import sys |
| 22 | 20 | import urllib.parse | |
| 23 | from pyversion import is_python3 | ||
| 24 | if is_python3(): | ||
| 25 | import urllib.parse | ||
| 26 | else: | ||
| 27 | import imp | ||
| 28 | import urlparse | ||
| 29 | urllib = imp.new_module('urllib') | ||
| 30 | urllib.parse = urlparse | ||
| 31 | 21 | ||
| 32 | from color import Coloring | 22 | from color import Coloring |
| 33 | from command import InteractiveCommand, MirrorSafeCommand | 23 | from command import InteractiveCommand, MirrorSafeCommand |
| 34 | from error import ManifestParseError | 24 | from error import ManifestParseError |
| 35 | from project import SyncBuffer | 25 | from project import SyncBuffer |
| 36 | from git_config import GitConfig | 26 | from git_config import GitConfig |
| 37 | from git_command import git_require, MIN_GIT_VERSION | 27 | from git_command import git_require, MIN_GIT_VERSION_SOFT, MIN_GIT_VERSION_HARD |
| 28 | import fetch | ||
| 29 | import git_superproject | ||
| 38 | import platform_utils | 30 | import platform_utils |
| 31 | from wrapper import Wrapper | ||
| 32 | |||
| 39 | 33 | ||
| 40 | class Init(InteractiveCommand, MirrorSafeCommand): | 34 | class Init(InteractiveCommand, MirrorSafeCommand): |
| 41 | common = True | 35 | COMMON = True |
| 42 | helpSummary = "Initialize repo in the current directory" | 36 | helpSummary = "Initialize a repo client checkout in the current directory" |
| 43 | helpUsage = """ | 37 | helpUsage = """ |
| 44 | %prog [options] | 38 | %prog [options] [manifest url] |
| 45 | """ | 39 | """ |
| 46 | helpDescription = """ | 40 | helpDescription = """ |
| 47 | The '%prog' command is run once to install and initialize repo. | 41 | The '%prog' command is run once to install and initialize repo. |
| @@ -49,13 +43,24 @@ The latest repo source code and manifest collection is downloaded | |||
| 49 | from the server and is installed in the .repo/ directory in the | 43 | from the server and is installed in the .repo/ directory in the |
| 50 | current working directory. | 44 | current working directory. |
| 51 | 45 | ||
| 46 | When creating a new checkout, the manifest URL is the only required setting. | ||
| 47 | It may be specified using the --manifest-url option, or as the first optional | ||
| 48 | argument. | ||
| 49 | |||
| 52 | The optional -b argument can be used to select the manifest branch | 50 | The optional -b argument can be used to select the manifest branch |
| 53 | to checkout and use. If no branch is specified, master is assumed. | 51 | to checkout and use. If no branch is specified, the remote's default |
| 52 | branch is used. This is equivalent to using -b HEAD. | ||
| 54 | 53 | ||
| 55 | The optional -m argument can be used to specify an alternate manifest | 54 | The optional -m argument can be used to specify an alternate manifest |
| 56 | to be used. If no manifest is specified, the manifest default.xml | 55 | to be used. If no manifest is specified, the manifest default.xml |
| 57 | will be used. | 56 | will be used. |
| 58 | 57 | ||
| 58 | If the --standalone-manifest argument is set, the manifest will be downloaded | ||
| 59 | directly from the specified --manifest-url as a static file (rather than | ||
| 60 | setting up a manifest git checkout). With --standalone-manifest, the manifest | ||
| 61 | will be fully static and will not be re-downloaded during subsesquent | ||
| 62 | `repo init` and `repo sync` calls. | ||
| 63 | |||
| 59 | The --reference option can be used to point to a directory that | 64 | The --reference option can be used to point to a directory that |
| 60 | has the content of a --mirror sync. This will make the working | 65 | has the content of a --mirror sync. This will make the working |
| 61 | directory use as much data as possible from the local reference | 66 | directory use as much data as possible from the local reference |
| @@ -81,109 +86,64 @@ manifest, a subsequent `repo sync` (or `repo sync -d`) is necessary | |||
| 81 | to update the working directory files. | 86 | to update the working directory files. |
| 82 | """ | 87 | """ |
| 83 | 88 | ||
| 89 | def _CommonOptions(self, p): | ||
| 90 | """Disable due to re-use of Wrapper().""" | ||
| 91 | |||
| 84 | def _Options(self, p, gitc_init=False): | 92 | def _Options(self, p, gitc_init=False): |
| 85 | # Logging | 93 | Wrapper().InitParser(p, gitc_init=gitc_init) |
| 86 | g = p.add_option_group('Logging options') | ||
| 87 | g.add_option('-q', '--quiet', | ||
| 88 | dest="quiet", action="store_true", default=False, | ||
| 89 | help="be quiet") | ||
| 90 | |||
| 91 | # Manifest | ||
| 92 | g = p.add_option_group('Manifest options') | ||
| 93 | g.add_option('-u', '--manifest-url', | ||
| 94 | dest='manifest_url', | ||
| 95 | help='manifest repository location', metavar='URL') | ||
| 96 | g.add_option('-b', '--manifest-branch', | ||
| 97 | dest='manifest_branch', | ||
| 98 | help='manifest branch or revision', metavar='REVISION') | ||
| 99 | cbr_opts = ['--current-branch'] | ||
| 100 | # The gitc-init subcommand allocates -c itself, but a lot of init users | ||
| 101 | # want -c, so try to satisfy both as best we can. | ||
| 102 | if not gitc_init: | ||
| 103 | cbr_opts += ['-c'] | ||
| 104 | g.add_option(*cbr_opts, | ||
| 105 | dest='current_branch_only', action='store_true', | ||
| 106 | help='fetch only current manifest branch from server') | ||
| 107 | g.add_option('-m', '--manifest-name', | ||
| 108 | dest='manifest_name', default='default.xml', | ||
| 109 | help='initial manifest file', metavar='NAME.xml') | ||
| 110 | g.add_option('--mirror', | ||
| 111 | dest='mirror', action='store_true', | ||
| 112 | help='create a replica of the remote repositories ' | ||
| 113 | 'rather than a client working directory') | ||
| 114 | g.add_option('--reference', | ||
| 115 | dest='reference', | ||
| 116 | help='location of mirror directory', metavar='DIR') | ||
| 117 | g.add_option('--dissociate', | ||
| 118 | dest='dissociate', action='store_true', | ||
| 119 | help='dissociate from reference mirrors after clone') | ||
| 120 | g.add_option('--depth', type='int', default=None, | ||
| 121 | dest='depth', | ||
| 122 | help='create a shallow clone with given depth; see git clone') | ||
| 123 | g.add_option('--partial-clone', action='store_true', | ||
| 124 | dest='partial_clone', | ||
| 125 | help='perform partial clone (https://git-scm.com/' | ||
| 126 | 'docs/gitrepository-layout#_code_partialclone_code)') | ||
| 127 | g.add_option('--clone-filter', action='store', default='blob:none', | ||
| 128 | dest='clone_filter', | ||
| 129 | help='filter for use with --partial-clone [default: %default]') | ||
| 130 | g.add_option('--archive', | ||
| 131 | dest='archive', action='store_true', | ||
| 132 | help='checkout an archive instead of a git repository for ' | ||
| 133 | 'each project. See git archive.') | ||
| 134 | g.add_option('--submodules', | ||
| 135 | dest='submodules', action='store_true', | ||
| 136 | help='sync any submodules associated with the manifest repo') | ||
| 137 | g.add_option('-g', '--groups', | ||
| 138 | dest='groups', default='default', | ||
| 139 | help='restrict manifest projects to ones with specified ' | ||
| 140 | 'group(s) [default|all|G1,G2,G3|G4,-G5,-G6]', | ||
| 141 | metavar='GROUP') | ||
| 142 | g.add_option('-p', '--platform', | ||
| 143 | dest='platform', default='auto', | ||
| 144 | help='restrict manifest projects to ones with a specified ' | ||
| 145 | 'platform group [auto|all|none|linux|darwin|...]', | ||
| 146 | metavar='PLATFORM') | ||
| 147 | g.add_option('--no-clone-bundle', | ||
| 148 | dest='no_clone_bundle', action='store_true', | ||
| 149 | help='disable use of /clone.bundle on HTTP/HTTPS') | ||
| 150 | g.add_option('--no-tags', | ||
| 151 | dest='no_tags', action='store_true', | ||
| 152 | help="don't fetch tags in the manifest") | ||
| 153 | |||
| 154 | # Tool | ||
| 155 | g = p.add_option_group('repo Version options') | ||
| 156 | g.add_option('--repo-url', | ||
| 157 | dest='repo_url', | ||
| 158 | help='repo repository location', metavar='URL') | ||
| 159 | g.add_option('--repo-branch', | ||
| 160 | dest='repo_branch', | ||
| 161 | help='repo branch or revision', metavar='REVISION') | ||
| 162 | g.add_option('--no-repo-verify', | ||
| 163 | dest='no_repo_verify', action='store_true', | ||
| 164 | help='do not verify repo source code') | ||
| 165 | |||
| 166 | # Other | ||
| 167 | g = p.add_option_group('Other options') | ||
| 168 | g.add_option('--config-name', | ||
| 169 | dest='config_name', action="store_true", default=False, | ||
| 170 | help='Always prompt for name/e-mail') | ||
| 171 | 94 | ||
| 172 | def _RegisteredEnvironmentOptions(self): | 95 | def _RegisteredEnvironmentOptions(self): |
| 173 | return {'REPO_MANIFEST_URL': 'manifest_url', | 96 | return {'REPO_MANIFEST_URL': 'manifest_url', |
| 174 | 'REPO_MIRROR_LOCATION': 'reference'} | 97 | 'REPO_MIRROR_LOCATION': 'reference'} |
| 175 | 98 | ||
| 99 | def _CloneSuperproject(self, opt): | ||
| 100 | """Clone the superproject based on the superproject's url and branch. | ||
| 101 | |||
| 102 | Args: | ||
| 103 | opt: Program options returned from optparse. See _Options(). | ||
| 104 | """ | ||
| 105 | superproject = git_superproject.Superproject(self.manifest, | ||
| 106 | self.repodir, | ||
| 107 | self.git_event_log, | ||
| 108 | quiet=opt.quiet) | ||
| 109 | sync_result = superproject.Sync() | ||
| 110 | if not sync_result.success: | ||
| 111 | print('warning: git update of superproject failed, repo sync will not ' | ||
| 112 | 'use superproject to fetch source; while this error is not fatal, ' | ||
| 113 | 'and you can continue to run repo sync, please run repo init with ' | ||
| 114 | 'the --no-use-superproject option to stop seeing this warning', | ||
| 115 | file=sys.stderr) | ||
| 116 | if sync_result.fatal and opt.use_superproject is not None: | ||
| 117 | sys.exit(1) | ||
| 118 | |||
| 176 | def _SyncManifest(self, opt): | 119 | def _SyncManifest(self, opt): |
| 177 | m = self.manifest.manifestProject | 120 | m = self.manifest.manifestProject |
| 178 | is_new = not m.Exists | 121 | is_new = not m.Exists |
| 179 | 122 | ||
| 123 | # If repo has already been initialized, we take -u with the absence of | ||
| 124 | # --standalone-manifest to mean "transition to a standard repo set up", | ||
| 125 | # which necessitates starting fresh. | ||
| 126 | # If --standalone-manifest is set, we always tear everything down and start | ||
| 127 | # anew. | ||
| 128 | if not is_new: | ||
| 129 | was_standalone_manifest = m.config.GetString('manifest.standalone') | ||
| 130 | if opt.standalone_manifest or ( | ||
| 131 | was_standalone_manifest and opt.manifest_url): | ||
| 132 | m.config.ClearCache() | ||
| 133 | if m.gitdir and os.path.exists(m.gitdir): | ||
| 134 | platform_utils.rmtree(m.gitdir) | ||
| 135 | if m.worktree and os.path.exists(m.worktree): | ||
| 136 | platform_utils.rmtree(m.worktree) | ||
| 137 | |||
| 138 | is_new = not m.Exists | ||
| 180 | if is_new: | 139 | if is_new: |
| 181 | if not opt.manifest_url: | 140 | if not opt.manifest_url: |
| 182 | print('fatal: manifest url (-u) is required.', file=sys.stderr) | 141 | print('fatal: manifest url is required.', file=sys.stderr) |
| 183 | sys.exit(1) | 142 | sys.exit(1) |
| 184 | 143 | ||
| 185 | if not opt.quiet: | 144 | if not opt.quiet: |
| 186 | print('Get %s' % GitConfig.ForUser().UrlInsteadOf(opt.manifest_url), | 145 | print('Downloading manifest from %s' % |
| 146 | (GitConfig.ForUser().UrlInsteadOf(opt.manifest_url),), | ||
| 187 | file=sys.stderr) | 147 | file=sys.stderr) |
| 188 | 148 | ||
| 189 | # The manifest project object doesn't keep track of the path on the | 149 | # The manifest project object doesn't keep track of the path on the |
| @@ -200,30 +160,52 @@ to update the working directory files. | |||
| 200 | 160 | ||
| 201 | m._InitGitDir(mirror_git=mirrored_manifest_git) | 161 | m._InitGitDir(mirror_git=mirrored_manifest_git) |
| 202 | 162 | ||
| 203 | if opt.manifest_branch: | 163 | # If standalone_manifest is set, mark the project as "standalone" -- we'll |
| 204 | m.revisionExpr = opt.manifest_branch | 164 | # still do much of the manifests.git set up, but will avoid actual syncs to |
| 205 | else: | 165 | # a remote. |
| 206 | m.revisionExpr = 'refs/heads/master' | 166 | standalone_manifest = False |
| 207 | else: | 167 | if opt.standalone_manifest: |
| 208 | if opt.manifest_branch: | 168 | standalone_manifest = True |
| 209 | m.revisionExpr = opt.manifest_branch | 169 | elif not opt.manifest_url: |
| 210 | else: | 170 | # If -u is set and --standalone-manifest is not, then we're not in |
| 211 | m.PreSync() | 171 | # standalone mode. Otherwise, use config to infer what we were in the last |
| 172 | # init. | ||
| 173 | standalone_manifest = bool(m.config.GetString('manifest.standalone')) | ||
| 174 | m.config.SetString('manifest.standalone', opt.manifest_url) | ||
| 212 | 175 | ||
| 213 | self._ConfigureDepth(opt) | 176 | self._ConfigureDepth(opt) |
| 214 | 177 | ||
| 178 | # Set the remote URL before the remote branch as we might need it below. | ||
| 215 | if opt.manifest_url: | 179 | if opt.manifest_url: |
| 216 | r = m.GetRemote(m.remote.name) | 180 | r = m.GetRemote(m.remote.name) |
| 217 | r.url = opt.manifest_url | 181 | r.url = opt.manifest_url |
| 218 | r.ResetFetch() | 182 | r.ResetFetch() |
| 219 | r.Save() | 183 | r.Save() |
| 220 | 184 | ||
| 185 | if not standalone_manifest: | ||
| 186 | if opt.manifest_branch: | ||
| 187 | if opt.manifest_branch == 'HEAD': | ||
| 188 | opt.manifest_branch = m.ResolveRemoteHead() | ||
| 189 | if opt.manifest_branch is None: | ||
| 190 | print('fatal: unable to resolve HEAD', file=sys.stderr) | ||
| 191 | sys.exit(1) | ||
| 192 | m.revisionExpr = opt.manifest_branch | ||
| 193 | else: | ||
| 194 | if is_new: | ||
| 195 | default_branch = m.ResolveRemoteHead() | ||
| 196 | if default_branch is None: | ||
| 197 | # If the remote doesn't have HEAD configured, default to master. | ||
| 198 | default_branch = 'refs/heads/master' | ||
| 199 | m.revisionExpr = default_branch | ||
| 200 | else: | ||
| 201 | m.PreSync() | ||
| 202 | |||
| 221 | groups = re.split(r'[,\s]+', opt.groups) | 203 | groups = re.split(r'[,\s]+', opt.groups) |
| 222 | all_platforms = ['linux', 'darwin', 'windows'] | 204 | all_platforms = ['linux', 'darwin', 'windows'] |
| 223 | platformize = lambda x: 'platform-' + x | 205 | platformize = lambda x: 'platform-' + x |
| 224 | if opt.platform == 'auto': | 206 | if opt.platform == 'auto': |
| 225 | if (not opt.mirror and | 207 | if (not opt.mirror and |
| 226 | not m.config.GetString('repo.mirror') == 'true'): | 208 | not m.config.GetString('repo.mirror') == 'true'): |
| 227 | groups.append(platformize(platform.system().lower())) | 209 | groups.append(platformize(platform.system().lower())) |
| 228 | elif opt.platform == 'all': | 210 | elif opt.platform == 'all': |
| 229 | groups.extend(map(platformize, all_platforms)) | 211 | groups.extend(map(platformize, all_platforms)) |
| @@ -235,7 +217,7 @@ to update the working directory files. | |||
| 235 | 217 | ||
| 236 | groups = [x for x in groups if x] | 218 | groups = [x for x in groups if x] |
| 237 | groupstr = ','.join(groups) | 219 | groupstr = ','.join(groups) |
| 238 | if opt.platform == 'auto' and groupstr == 'default,platform-' + platform.system().lower(): | 220 | if opt.platform == 'auto' and groupstr == self.manifest.GetDefaultGroupsStr(): |
| 239 | groupstr = None | 221 | groupstr = None |
| 240 | m.config.SetString('manifest.groups', groupstr) | 222 | m.config.SetString('manifest.groups', groupstr) |
| 241 | 223 | ||
| @@ -243,11 +225,25 @@ to update the working directory files. | |||
| 243 | m.config.SetString('repo.reference', opt.reference) | 225 | m.config.SetString('repo.reference', opt.reference) |
| 244 | 226 | ||
| 245 | if opt.dissociate: | 227 | if opt.dissociate: |
| 246 | m.config.SetString('repo.dissociate', 'true') | 228 | m.config.SetBoolean('repo.dissociate', opt.dissociate) |
| 229 | |||
| 230 | if opt.worktree: | ||
| 231 | if opt.mirror: | ||
| 232 | print('fatal: --mirror and --worktree are incompatible', | ||
| 233 | file=sys.stderr) | ||
| 234 | sys.exit(1) | ||
| 235 | if opt.submodules: | ||
| 236 | print('fatal: --submodules and --worktree are incompatible', | ||
| 237 | file=sys.stderr) | ||
| 238 | sys.exit(1) | ||
| 239 | m.config.SetBoolean('repo.worktree', opt.worktree) | ||
| 240 | if is_new: | ||
| 241 | m.use_git_worktrees = True | ||
| 242 | print('warning: --worktree is experimental!', file=sys.stderr) | ||
| 247 | 243 | ||
| 248 | if opt.archive: | 244 | if opt.archive: |
| 249 | if is_new: | 245 | if is_new: |
| 250 | m.config.SetString('repo.archive', 'true') | 246 | m.config.SetBoolean('repo.archive', opt.archive) |
| 251 | else: | 247 | else: |
| 252 | print('fatal: --archive is only supported when initializing a new ' | 248 | print('fatal: --archive is only supported when initializing a new ' |
| 253 | 'workspace.', file=sys.stderr) | 249 | 'workspace.', file=sys.stderr) |
| @@ -257,7 +253,7 @@ to update the working directory files. | |||
| 257 | 253 | ||
| 258 | if opt.mirror: | 254 | if opt.mirror: |
| 259 | if is_new: | 255 | if is_new: |
| 260 | m.config.SetString('repo.mirror', 'true') | 256 | m.config.SetBoolean('repo.mirror', opt.mirror) |
| 261 | else: | 257 | else: |
| 262 | print('fatal: --mirror is only supported when initializing a new ' | 258 | print('fatal: --mirror is only supported when initializing a new ' |
| 263 | 'workspace.', file=sys.stderr) | 259 | 'workspace.', file=sys.stderr) |
| @@ -265,25 +261,49 @@ to update the working directory files. | |||
| 265 | 'in another location.', file=sys.stderr) | 261 | 'in another location.', file=sys.stderr) |
| 266 | sys.exit(1) | 262 | sys.exit(1) |
| 267 | 263 | ||
| 268 | if opt.partial_clone: | 264 | if opt.partial_clone is not None: |
| 269 | if opt.mirror: | 265 | if opt.mirror: |
| 270 | print('fatal: --mirror and --partial-clone are mutually exclusive', | 266 | print('fatal: --mirror and --partial-clone are mutually exclusive', |
| 271 | file=sys.stderr) | 267 | file=sys.stderr) |
| 272 | sys.exit(1) | 268 | sys.exit(1) |
| 273 | m.config.SetString('repo.partialclone', 'true') | 269 | m.config.SetBoolean('repo.partialclone', opt.partial_clone) |
| 274 | if opt.clone_filter: | 270 | if opt.clone_filter: |
| 275 | m.config.SetString('repo.clonefilter', opt.clone_filter) | 271 | m.config.SetString('repo.clonefilter', opt.clone_filter) |
| 272 | elif m.config.GetBoolean('repo.partialclone'): | ||
| 273 | opt.clone_filter = m.config.GetString('repo.clonefilter') | ||
| 276 | else: | 274 | else: |
| 277 | opt.clone_filter = None | 275 | opt.clone_filter = None |
| 278 | 276 | ||
| 277 | if opt.partial_clone_exclude is not None: | ||
| 278 | m.config.SetString('repo.partialcloneexclude', opt.partial_clone_exclude) | ||
| 279 | |||
| 280 | if opt.clone_bundle is None: | ||
| 281 | opt.clone_bundle = False if opt.partial_clone else True | ||
| 282 | else: | ||
| 283 | m.config.SetBoolean('repo.clonebundle', opt.clone_bundle) | ||
| 284 | |||
| 279 | if opt.submodules: | 285 | if opt.submodules: |
| 280 | m.config.SetString('repo.submodules', 'true') | 286 | m.config.SetBoolean('repo.submodules', opt.submodules) |
| 287 | |||
| 288 | if opt.use_superproject is not None: | ||
| 289 | m.config.SetBoolean('repo.superproject', opt.use_superproject) | ||
| 290 | |||
| 291 | if standalone_manifest: | ||
| 292 | if is_new: | ||
| 293 | manifest_name = 'default.xml' | ||
| 294 | manifest_data = fetch.fetch_file(opt.manifest_url) | ||
| 295 | dest = os.path.join(m.worktree, manifest_name) | ||
| 296 | os.makedirs(os.path.dirname(dest), exist_ok=True) | ||
| 297 | with open(dest, 'wb') as f: | ||
| 298 | f.write(manifest_data) | ||
| 299 | return | ||
| 281 | 300 | ||
| 282 | if not m.Sync_NetworkHalf(is_new=is_new, quiet=opt.quiet, | 301 | if not m.Sync_NetworkHalf(is_new=is_new, quiet=opt.quiet, verbose=opt.verbose, |
| 283 | clone_bundle=not opt.no_clone_bundle, | 302 | clone_bundle=opt.clone_bundle, |
| 284 | current_branch_only=opt.current_branch_only, | 303 | current_branch_only=opt.current_branch_only, |
| 285 | no_tags=opt.no_tags, submodules=opt.submodules, | 304 | tags=opt.tags, submodules=opt.submodules, |
| 286 | clone_filter=opt.clone_filter): | 305 | clone_filter=opt.clone_filter, |
| 306 | partial_clone_exclude=self.manifest.PartialCloneExclude): | ||
| 287 | r = m.GetRemote(m.remote.name) | 307 | r = m.GetRemote(m.remote.name) |
| 288 | print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr) | 308 | print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr) |
| 289 | 309 | ||
| @@ -326,8 +346,8 @@ to update the working directory files. | |||
| 326 | return value | 346 | return value |
| 327 | return a | 347 | return a |
| 328 | 348 | ||
| 329 | def _ShouldConfigureUser(self): | 349 | def _ShouldConfigureUser(self, opt): |
| 330 | gc = self.manifest.globalConfig | 350 | gc = self.client.globalConfig |
| 331 | mp = self.manifest.manifestProject | 351 | mp = self.manifest.manifestProject |
| 332 | 352 | ||
| 333 | # If we don't have local settings, get from global. | 353 | # If we don't have local settings, get from global. |
| @@ -338,21 +358,24 @@ to update the working directory files. | |||
| 338 | mp.config.SetString('user.name', gc.GetString('user.name')) | 358 | mp.config.SetString('user.name', gc.GetString('user.name')) |
| 339 | mp.config.SetString('user.email', gc.GetString('user.email')) | 359 | mp.config.SetString('user.email', gc.GetString('user.email')) |
| 340 | 360 | ||
| 341 | print() | 361 | if not opt.quiet: |
| 342 | print('Your identity is: %s <%s>' % (mp.config.GetString('user.name'), | 362 | print() |
| 343 | mp.config.GetString('user.email'))) | 363 | print('Your identity is: %s <%s>' % (mp.config.GetString('user.name'), |
| 344 | print('If you want to change this, please re-run \'repo init\' with --config-name') | 364 | mp.config.GetString('user.email'))) |
| 365 | print("If you want to change this, please re-run 'repo init' with --config-name") | ||
| 345 | return False | 366 | return False |
| 346 | 367 | ||
| 347 | def _ConfigureUser(self): | 368 | def _ConfigureUser(self, opt): |
| 348 | mp = self.manifest.manifestProject | 369 | mp = self.manifest.manifestProject |
| 349 | 370 | ||
| 350 | while True: | 371 | while True: |
| 351 | print() | 372 | if not opt.quiet: |
| 352 | name = self._Prompt('Your Name', mp.UserName) | 373 | print() |
| 374 | name = self._Prompt('Your Name', mp.UserName) | ||
| 353 | email = self._Prompt('Your Email', mp.UserEmail) | 375 | email = self._Prompt('Your Email', mp.UserEmail) |
| 354 | 376 | ||
| 355 | print() | 377 | if not opt.quiet: |
| 378 | print() | ||
| 356 | print('Your identity is: %s <%s>' % (name, email)) | 379 | print('Your identity is: %s <%s>' % (name, email)) |
| 357 | print('is this correct [y/N]? ', end='') | 380 | print('is this correct [y/N]? ', end='') |
| 358 | # TODO: When we require Python 3, use flush=True w/print above. | 381 | # TODO: When we require Python 3, use flush=True w/print above. |
| @@ -373,7 +396,7 @@ to update the working directory files. | |||
| 373 | return False | 396 | return False |
| 374 | 397 | ||
| 375 | def _ConfigureColor(self): | 398 | def _ConfigureColor(self): |
| 376 | gc = self.manifest.globalConfig | 399 | gc = self.client.globalConfig |
| 377 | if self._HasColorSet(gc): | 400 | if self._HasColorSet(gc): |
| 378 | return | 401 | return |
| 379 | 402 | ||
| @@ -424,15 +447,16 @@ to update the working directory files. | |||
| 424 | # We store the depth in the main manifest project. | 447 | # We store the depth in the main manifest project. |
| 425 | self.manifest.manifestProject.config.SetString('repo.depth', depth) | 448 | self.manifest.manifestProject.config.SetString('repo.depth', depth) |
| 426 | 449 | ||
| 427 | def _DisplayResult(self): | 450 | def _DisplayResult(self, opt): |
| 428 | if self.manifest.IsMirror: | 451 | if self.manifest.IsMirror: |
| 429 | init_type = 'mirror ' | 452 | init_type = 'mirror ' |
| 430 | else: | 453 | else: |
| 431 | init_type = '' | 454 | init_type = '' |
| 432 | 455 | ||
| 433 | print() | 456 | if not opt.quiet: |
| 434 | print('repo %shas been initialized in %s' | 457 | print() |
| 435 | % (init_type, self.manifest.topdir)) | 458 | print('repo %shas been initialized in %s' % |
| 459 | (init_type, self.manifest.topdir)) | ||
| 436 | 460 | ||
| 437 | current_dir = os.getcwd() | 461 | current_dir = os.getcwd() |
| 438 | if current_dir != self.manifest.topdir: | 462 | if current_dir != self.manifest.topdir: |
| @@ -450,15 +474,61 @@ to update the working directory files. | |||
| 450 | if opt.archive and opt.mirror: | 474 | if opt.archive and opt.mirror: |
| 451 | self.OptionParser.error('--mirror and --archive cannot be used together.') | 475 | self.OptionParser.error('--mirror and --archive cannot be used together.') |
| 452 | 476 | ||
| 477 | if opt.standalone_manifest and ( | ||
| 478 | opt.manifest_branch or opt.manifest_name != 'default.xml'): | ||
| 479 | self.OptionParser.error('--manifest-branch and --manifest-name cannot' | ||
| 480 | ' be used with --standalone-manifest.') | ||
| 481 | |||
| 482 | if args: | ||
| 483 | if opt.manifest_url: | ||
| 484 | self.OptionParser.error( | ||
| 485 | '--manifest-url option and URL argument both specified: only use ' | ||
| 486 | 'one to select the manifest URL.') | ||
| 487 | |||
| 488 | opt.manifest_url = args.pop(0) | ||
| 489 | |||
| 490 | if args: | ||
| 491 | self.OptionParser.error('too many arguments to init') | ||
| 492 | |||
| 453 | def Execute(self, opt, args): | 493 | def Execute(self, opt, args): |
| 454 | git_require(MIN_GIT_VERSION, fail=True) | 494 | git_require(MIN_GIT_VERSION_HARD, fail=True) |
| 495 | if not git_require(MIN_GIT_VERSION_SOFT): | ||
| 496 | print('repo: warning: git-%s+ will soon be required; please upgrade your ' | ||
| 497 | 'version of git to maintain support.' | ||
| 498 | % ('.'.join(str(x) for x in MIN_GIT_VERSION_SOFT),), | ||
| 499 | file=sys.stderr) | ||
| 500 | |||
| 501 | rp = self.manifest.repoProject | ||
| 502 | |||
| 503 | # Handle new --repo-url requests. | ||
| 504 | if opt.repo_url: | ||
| 505 | remote = rp.GetRemote('origin') | ||
| 506 | remote.url = opt.repo_url | ||
| 507 | remote.Save() | ||
| 508 | |||
| 509 | # Handle new --repo-rev requests. | ||
| 510 | if opt.repo_rev: | ||
| 511 | wrapper = Wrapper() | ||
| 512 | remote_ref, rev = wrapper.check_repo_rev( | ||
| 513 | rp.gitdir, opt.repo_rev, repo_verify=opt.repo_verify, quiet=opt.quiet) | ||
| 514 | branch = rp.GetBranch('default') | ||
| 515 | branch.merge = remote_ref | ||
| 516 | rp.work_git.reset('--hard', rev) | ||
| 517 | branch.Save() | ||
| 518 | |||
| 519 | if opt.worktree: | ||
| 520 | # Older versions of git supported worktree, but had dangerous gc bugs. | ||
| 521 | git_require((2, 15, 0), fail=True, msg='git gc worktree corruption') | ||
| 455 | 522 | ||
| 456 | self._SyncManifest(opt) | 523 | self._SyncManifest(opt) |
| 457 | self._LinkManifest(opt.manifest_name) | 524 | self._LinkManifest(opt.manifest_name) |
| 458 | 525 | ||
| 526 | if self.manifest.manifestProject.config.GetBoolean('repo.superproject'): | ||
| 527 | self._CloneSuperproject(opt) | ||
| 528 | |||
| 459 | if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror: | 529 | if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror: |
| 460 | if opt.config_name or self._ShouldConfigureUser(): | 530 | if opt.config_name or self._ShouldConfigureUser(opt): |
| 461 | self._ConfigureUser() | 531 | self._ConfigureUser(opt) |
| 462 | self._ConfigureColor() | 532 | self._ConfigureColor() |
| 463 | 533 | ||
| 464 | self._DisplayResult() | 534 | self._DisplayResult(opt) |
diff --git a/subcmds/list.py b/subcmds/list.py index 00172f0e..6adf85b7 100644 --- a/subcmds/list.py +++ b/subcmds/list.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2011 The Android Open Source Project | 1 | # Copyright (C) 2011 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,45 +12,59 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from __future__ import print_function | 15 | import os |
| 18 | import sys | ||
| 19 | 16 | ||
| 20 | from command import Command, MirrorSafeCommand | 17 | from command import Command, MirrorSafeCommand |
| 21 | 18 | ||
| 19 | |||
| 22 | class List(Command, MirrorSafeCommand): | 20 | class List(Command, MirrorSafeCommand): |
| 23 | common = True | 21 | COMMON = True |
| 24 | helpSummary = "List projects and their associated directories" | 22 | helpSummary = "List projects and their associated directories" |
| 25 | helpUsage = """ | 23 | helpUsage = """ |
| 26 | %prog [-f] [<project>...] | 24 | %prog [-f] [<project>...] |
| 27 | %prog [-f] -r str1 [str2]..." | 25 | %prog [-f] -r str1 [str2]... |
| 28 | """ | 26 | """ |
| 29 | helpDescription = """ | 27 | helpDescription = """ |
| 30 | List all projects; pass '.' to list the project for the cwd. | 28 | List all projects; pass '.' to list the project for the cwd. |
| 31 | 29 | ||
| 30 | By default, only projects that currently exist in the checkout are shown. If | ||
| 31 | you want to list all projects (using the specified filter settings), use the | ||
| 32 | --all option. If you want to show all projects regardless of the manifest | ||
| 33 | groups, then also pass --groups all. | ||
| 34 | |||
| 32 | This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'. | 35 | This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'. |
| 33 | """ | 36 | """ |
| 34 | 37 | ||
| 35 | def _Options(self, p): | 38 | def _Options(self, p): |
| 36 | p.add_option('-r', '--regex', | 39 | p.add_option('-r', '--regex', |
| 37 | dest='regex', action='store_true', | 40 | dest='regex', action='store_true', |
| 38 | help="Filter the project list based on regex or wildcard matching of strings") | 41 | help='filter the project list based on regex or wildcard matching of strings') |
| 39 | p.add_option('-g', '--groups', | 42 | p.add_option('-g', '--groups', |
| 40 | dest='groups', | 43 | dest='groups', |
| 41 | help="Filter the project list based on the groups the project is in") | 44 | help='filter the project list based on the groups the project is in') |
| 42 | p.add_option('-f', '--fullpath', | 45 | p.add_option('-a', '--all', |
| 43 | dest='fullpath', action='store_true', | 46 | action='store_true', |
| 44 | help="Display the full work tree path instead of the relative path") | 47 | help='show projects regardless of checkout state') |
| 45 | p.add_option('-n', '--name-only', | 48 | p.add_option('-n', '--name-only', |
| 46 | dest='name_only', action='store_true', | 49 | dest='name_only', action='store_true', |
| 47 | help="Display only the name of the repository") | 50 | help='display only the name of the repository') |
| 48 | p.add_option('-p', '--path-only', | 51 | p.add_option('-p', '--path-only', |
| 49 | dest='path_only', action='store_true', | 52 | dest='path_only', action='store_true', |
| 50 | help="Display only the path of the repository") | 53 | help='display only the path of the repository') |
| 54 | p.add_option('-f', '--fullpath', | ||
| 55 | dest='fullpath', action='store_true', | ||
| 56 | help='display the full work tree path instead of the relative path') | ||
| 57 | p.add_option('--relative-to', metavar='PATH', | ||
| 58 | help='display paths relative to this one (default: top of repo client checkout)') | ||
| 51 | 59 | ||
| 52 | def ValidateOptions(self, opt, args): | 60 | def ValidateOptions(self, opt, args): |
| 53 | if opt.fullpath and opt.name_only: | 61 | if opt.fullpath and opt.name_only: |
| 54 | self.OptionParser.error('cannot combine -f and -n') | 62 | self.OptionParser.error('cannot combine -f and -n') |
| 55 | 63 | ||
| 64 | # Resolve any symlinks so the output is stable. | ||
| 65 | if opt.relative_to: | ||
| 66 | opt.relative_to = os.path.realpath(opt.relative_to) | ||
| 67 | |||
| 56 | def Execute(self, opt, args): | 68 | def Execute(self, opt, args): |
| 57 | """List all projects and the associated directories. | 69 | """List all projects and the associated directories. |
| 58 | 70 | ||
| @@ -65,23 +77,26 @@ This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'. | |||
| 65 | args: Positional args. Can be a list of projects to list, or empty. | 77 | args: Positional args. Can be a list of projects to list, or empty. |
| 66 | """ | 78 | """ |
| 67 | if not opt.regex: | 79 | if not opt.regex: |
| 68 | projects = self.GetProjects(args, groups=opt.groups) | 80 | projects = self.GetProjects(args, groups=opt.groups, missing_ok=opt.all) |
| 69 | else: | 81 | else: |
| 70 | projects = self.FindProjects(args) | 82 | projects = self.FindProjects(args) |
| 71 | 83 | ||
| 72 | def _getpath(x): | 84 | def _getpath(x): |
| 73 | if opt.fullpath: | 85 | if opt.fullpath: |
| 74 | return x.worktree | 86 | return x.worktree |
| 87 | if opt.relative_to: | ||
| 88 | return os.path.relpath(x.worktree, opt.relative_to) | ||
| 75 | return x.relpath | 89 | return x.relpath |
| 76 | 90 | ||
| 77 | lines = [] | 91 | lines = [] |
| 78 | for project in projects: | 92 | for project in projects: |
| 79 | if opt.name_only and not opt.path_only: | 93 | if opt.name_only and not opt.path_only: |
| 80 | lines.append("%s" % ( project.name)) | 94 | lines.append("%s" % (project.name)) |
| 81 | elif opt.path_only and not opt.name_only: | 95 | elif opt.path_only and not opt.name_only: |
| 82 | lines.append("%s" % (_getpath(project))) | 96 | lines.append("%s" % (_getpath(project))) |
| 83 | else: | 97 | else: |
| 84 | lines.append("%s : %s" % (_getpath(project), project.name)) | 98 | lines.append("%s : %s" % (_getpath(project), project.name)) |
| 85 | 99 | ||
| 86 | lines.sort() | 100 | if lines: |
| 87 | print('\n'.join(lines)) | 101 | lines.sort() |
| 102 | print('\n'.join(lines)) | ||
diff --git a/subcmds/manifest.py b/subcmds/manifest.py index 9c1b3f0c..0fbdeac0 100644 --- a/subcmds/manifest.py +++ b/subcmds/manifest.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2009 The Android Open Source Project | 1 | # Copyright (C) 2009 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,25 +12,32 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from __future__ import print_function | 15 | import json |
| 18 | import os | 16 | import os |
| 19 | import sys | 17 | import sys |
| 20 | 18 | ||
| 21 | from command import PagedCommand | 19 | from command import PagedCommand |
| 22 | 20 | ||
| 21 | |||
| 23 | class Manifest(PagedCommand): | 22 | class Manifest(PagedCommand): |
| 24 | common = False | 23 | COMMON = False |
| 25 | helpSummary = "Manifest inspection utility" | 24 | helpSummary = "Manifest inspection utility" |
| 26 | helpUsage = """ | 25 | helpUsage = """ |
| 27 | %prog [-o {-|NAME.xml} [-r]] | 26 | %prog [-o {-|NAME.xml}] [-m MANIFEST.xml] [-r] |
| 28 | """ | 27 | """ |
| 29 | _helpDescription = """ | 28 | _helpDescription = """ |
| 30 | 29 | ||
| 31 | With the -o option, exports the current manifest for inspection. | 30 | With the -o option, exports the current manifest for inspection. |
| 32 | The manifest and (if present) local_manifest.xml are combined | 31 | The manifest and (if present) local_manifests/ are combined |
| 33 | together to produce a single manifest file. This file can be stored | 32 | together to produce a single manifest file. This file can be stored |
| 34 | in a Git repository for use during future 'repo init' invocations. | 33 | in a Git repository for use during future 'repo init' invocations. |
| 35 | 34 | ||
| 35 | The -r option can be used to generate a manifest file with project | ||
| 36 | revisions set to the current commit hash. These are known as | ||
| 37 | "revision locked manifests", as they don't follow a particular branch. | ||
| 38 | In this case, the 'upstream' attribute is set to the ref we were on | ||
| 39 | when the manifest was generated. The 'dest-branch' attribute is set | ||
| 40 | to indicate the remote ref to push changes to via 'repo upload'. | ||
| 36 | """ | 41 | """ |
| 37 | 42 | ||
| 38 | @property | 43 | @property |
| @@ -48,26 +53,63 @@ in a Git repository for use during future 'repo init' invocations. | |||
| 48 | def _Options(self, p): | 53 | def _Options(self, p): |
| 49 | p.add_option('-r', '--revision-as-HEAD', | 54 | p.add_option('-r', '--revision-as-HEAD', |
| 50 | dest='peg_rev', action='store_true', | 55 | dest='peg_rev', action='store_true', |
| 51 | help='Save revisions as current HEAD') | 56 | help='save revisions as current HEAD') |
| 57 | p.add_option('-m', '--manifest-name', | ||
| 58 | help='temporary manifest to use for this sync', metavar='NAME.xml') | ||
| 52 | p.add_option('--suppress-upstream-revision', dest='peg_rev_upstream', | 59 | p.add_option('--suppress-upstream-revision', dest='peg_rev_upstream', |
| 53 | default=True, action='store_false', | 60 | default=True, action='store_false', |
| 54 | help='If in -r mode, do not write the upstream field. ' | 61 | help='if in -r mode, do not write the upstream field ' |
| 55 | 'Only of use if the branch names for a sha1 manifest are ' | 62 | '(only of use if the branch names for a sha1 manifest are ' |
| 56 | 'sensitive.') | 63 | 'sensitive)') |
| 64 | p.add_option('--suppress-dest-branch', dest='peg_rev_dest_branch', | ||
| 65 | default=True, action='store_false', | ||
| 66 | help='if in -r mode, do not write the dest-branch field ' | ||
| 67 | '(only of use if the branch names for a sha1 manifest are ' | ||
| 68 | 'sensitive)') | ||
| 69 | p.add_option('--json', default=False, action='store_true', | ||
| 70 | help='output manifest in JSON format (experimental)') | ||
| 71 | p.add_option('--pretty', default=False, action='store_true', | ||
| 72 | help='format output for humans to read') | ||
| 73 | p.add_option('--no-local-manifests', default=False, action='store_true', | ||
| 74 | dest='ignore_local_manifests', help='ignore local manifests') | ||
| 57 | p.add_option('-o', '--output-file', | 75 | p.add_option('-o', '--output-file', |
| 58 | dest='output_file', | 76 | dest='output_file', |
| 59 | default='-', | 77 | default='-', |
| 60 | help='File to save the manifest to', | 78 | help='file to save the manifest to', |
| 61 | metavar='-|NAME.xml') | 79 | metavar='-|NAME.xml') |
| 62 | 80 | ||
| 63 | def _Output(self, opt): | 81 | def _Output(self, opt): |
| 82 | # If alternate manifest is specified, override the manifest file that we're using. | ||
| 83 | if opt.manifest_name: | ||
| 84 | self.manifest.Override(opt.manifest_name, False) | ||
| 85 | |||
| 64 | if opt.output_file == '-': | 86 | if opt.output_file == '-': |
| 65 | fd = sys.stdout | 87 | fd = sys.stdout |
| 66 | else: | 88 | else: |
| 67 | fd = open(opt.output_file, 'w') | 89 | fd = open(opt.output_file, 'w') |
| 68 | self.manifest.Save(fd, | 90 | |
| 69 | peg_rev = opt.peg_rev, | 91 | self.manifest.SetUseLocalManifests(not opt.ignore_local_manifests) |
| 70 | peg_rev_upstream = opt.peg_rev_upstream) | 92 | |
| 93 | if opt.json: | ||
| 94 | print('warning: --json is experimental!', file=sys.stderr) | ||
| 95 | doc = self.manifest.ToDict(peg_rev=opt.peg_rev, | ||
| 96 | peg_rev_upstream=opt.peg_rev_upstream, | ||
| 97 | peg_rev_dest_branch=opt.peg_rev_dest_branch) | ||
| 98 | |||
| 99 | json_settings = { | ||
| 100 | # JSON style guide says Uunicode characters are fully allowed. | ||
| 101 | 'ensure_ascii': False, | ||
| 102 | # We use 2 space indent to match JSON style guide. | ||
| 103 | 'indent': 2 if opt.pretty else None, | ||
| 104 | 'separators': (',', ': ') if opt.pretty else (',', ':'), | ||
| 105 | 'sort_keys': True, | ||
| 106 | } | ||
| 107 | fd.write(json.dumps(doc, **json_settings)) | ||
| 108 | else: | ||
| 109 | self.manifest.Save(fd, | ||
| 110 | peg_rev=opt.peg_rev, | ||
| 111 | peg_rev_upstream=opt.peg_rev_upstream, | ||
| 112 | peg_rev_dest_branch=opt.peg_rev_dest_branch) | ||
| 71 | fd.close() | 113 | fd.close() |
| 72 | if opt.output_file != '-': | 114 | if opt.output_file != '-': |
| 73 | print('Saved manifest to %s' % opt.output_file, file=sys.stderr) | 115 | print('Saved manifest to %s' % opt.output_file, file=sys.stderr) |
diff --git a/subcmds/overview.py b/subcmds/overview.py index 08b58a6c..63f5a79e 100644 --- a/subcmds/overview.py +++ b/subcmds/overview.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2012 The Android Open Source Project | 1 | # Copyright (C) 2012 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,13 +12,14 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from __future__ import print_function | 15 | import optparse |
| 16 | |||
| 18 | from color import Coloring | 17 | from color import Coloring |
| 19 | from command import PagedCommand | 18 | from command import PagedCommand |
| 20 | 19 | ||
| 21 | 20 | ||
| 22 | class Overview(PagedCommand): | 21 | class Overview(PagedCommand): |
| 23 | common = True | 22 | COMMON = True |
| 24 | helpSummary = "Display overview of unmerged project branches" | 23 | helpSummary = "Display overview of unmerged project branches" |
| 25 | helpUsage = """ | 24 | helpUsage = """ |
| 26 | %prog [--current-branch] [<project>...] | 25 | %prog [--current-branch] [<project>...] |
| @@ -29,15 +28,22 @@ class Overview(PagedCommand): | |||
| 29 | The '%prog' command is used to display an overview of the projects branches, | 28 | The '%prog' command is used to display an overview of the projects branches, |
| 30 | and list any local commits that have not yet been merged into the project. | 29 | and list any local commits that have not yet been merged into the project. |
| 31 | 30 | ||
| 32 | The -b/--current-branch option can be used to restrict the output to only | 31 | The -c/--current-branch option can be used to restrict the output to only |
| 33 | branches currently checked out in each project. By default, all branches | 32 | branches currently checked out in each project. By default, all branches |
| 34 | are displayed. | 33 | are displayed. |
| 35 | """ | 34 | """ |
| 36 | 35 | ||
| 37 | def _Options(self, p): | 36 | def _Options(self, p): |
| 38 | p.add_option('-b', '--current-branch', | 37 | p.add_option('-c', '--current-branch', |
| 39 | dest="current_branch", action="store_true", | 38 | dest="current_branch", action="store_true", |
| 40 | help="Consider only checked out branches") | 39 | help="consider only checked out branches") |
| 40 | p.add_option('--no-current-branch', | ||
| 41 | dest='current_branch', action='store_false', | ||
| 42 | help='consider all local branches') | ||
| 43 | # Turn this into a warning & remove this someday. | ||
| 44 | p.add_option('-b', | ||
| 45 | dest='current_branch', action='store_true', | ||
| 46 | help=optparse.SUPPRESS_HELP) | ||
| 41 | 47 | ||
| 42 | def Execute(self, opt, args): | 48 | def Execute(self, opt, args): |
| 43 | all_branches = [] | 49 | all_branches = [] |
diff --git a/subcmds/prune.py b/subcmds/prune.py index ff2fba1d..584ee7ed 100644 --- a/subcmds/prune.py +++ b/subcmds/prune.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2008 The Android Open Source Project | 1 | # Copyright (C) 2008 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,21 +12,38 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from __future__ import print_function | 15 | import itertools |
| 16 | |||
| 18 | from color import Coloring | 17 | from color import Coloring |
| 19 | from command import PagedCommand | 18 | from command import DEFAULT_LOCAL_JOBS, PagedCommand |
| 19 | |||
| 20 | 20 | ||
| 21 | class Prune(PagedCommand): | 21 | class Prune(PagedCommand): |
| 22 | common = True | 22 | COMMON = True |
| 23 | helpSummary = "Prune (delete) already merged topics" | 23 | helpSummary = "Prune (delete) already merged topics" |
| 24 | helpUsage = """ | 24 | helpUsage = """ |
| 25 | %prog [<project>...] | 25 | %prog [<project>...] |
| 26 | """ | 26 | """ |
| 27 | PARALLEL_JOBS = DEFAULT_LOCAL_JOBS | ||
| 28 | |||
| 29 | def _ExecuteOne(self, project): | ||
| 30 | """Process one project.""" | ||
| 31 | return project.PruneHeads() | ||
| 27 | 32 | ||
| 28 | def Execute(self, opt, args): | 33 | def Execute(self, opt, args): |
| 29 | all_branches = [] | 34 | projects = self.GetProjects(args) |
| 30 | for project in self.GetProjects(args): | 35 | |
| 31 | all_branches.extend(project.PruneHeads()) | 36 | # NB: Should be able to refactor this module to display summary as results |
| 37 | # come back from children. | ||
| 38 | def _ProcessResults(_pool, _output, results): | ||
| 39 | return list(itertools.chain.from_iterable(results)) | ||
| 40 | |||
| 41 | all_branches = self.ExecuteInParallel( | ||
| 42 | opt.jobs, | ||
| 43 | self._ExecuteOne, | ||
| 44 | projects, | ||
| 45 | callback=_ProcessResults, | ||
| 46 | ordered=True) | ||
| 32 | 47 | ||
| 33 | if not all_branches: | 48 | if not all_branches: |
| 34 | return | 49 | return |
diff --git a/subcmds/rebase.py b/subcmds/rebase.py index dcb8b2a3..7c53eb7a 100644 --- a/subcmds/rebase.py +++ b/subcmds/rebase.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2010 The Android Open Source Project | 1 | # Copyright (C) 2010 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,7 +12,6 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from __future__ import print_function | ||
| 18 | import sys | 15 | import sys |
| 19 | 16 | ||
| 20 | from color import Coloring | 17 | from color import Coloring |
| @@ -30,7 +27,7 @@ class RebaseColoring(Coloring): | |||
| 30 | 27 | ||
| 31 | 28 | ||
| 32 | class Rebase(Command): | 29 | class Rebase(Command): |
| 33 | common = True | 30 | COMMON = True |
| 34 | helpSummary = "Rebase local branches on upstream branch" | 31 | helpSummary = "Rebase local branches on upstream branch" |
| 35 | helpUsage = """ | 32 | helpUsage = """ |
| 36 | %prog {[<project>...] | -i <project>...} | 33 | %prog {[<project>...] | -i <project>...} |
| @@ -42,36 +39,34 @@ branch but need to incorporate new upstream changes "underneath" them. | |||
| 42 | """ | 39 | """ |
| 43 | 40 | ||
| 44 | def _Options(self, p): | 41 | def _Options(self, p): |
| 45 | p.add_option('-i', '--interactive', | 42 | g = p.get_option_group('--quiet') |
| 46 | dest="interactive", action="store_true", | 43 | g.add_option('-i', '--interactive', |
| 47 | help="interactive rebase (single project only)") | 44 | dest="interactive", action="store_true", |
| 45 | help="interactive rebase (single project only)") | ||
| 48 | 46 | ||
| 49 | p.add_option('--fail-fast', | 47 | p.add_option('--fail-fast', |
| 50 | dest='fail_fast', action='store_true', | 48 | dest='fail_fast', action='store_true', |
| 51 | help='Stop rebasing after first error is hit') | 49 | help='stop rebasing after first error is hit') |
| 52 | p.add_option('-f', '--force-rebase', | 50 | p.add_option('-f', '--force-rebase', |
| 53 | dest='force_rebase', action='store_true', | 51 | dest='force_rebase', action='store_true', |
| 54 | help='Pass --force-rebase to git rebase') | 52 | help='pass --force-rebase to git rebase') |
| 55 | p.add_option('--no-ff', | 53 | p.add_option('--no-ff', |
| 56 | dest='no_ff', action='store_true', | 54 | dest='ff', default=True, action='store_false', |
| 57 | help='Pass --no-ff to git rebase') | 55 | help='pass --no-ff to git rebase') |
| 58 | p.add_option('-q', '--quiet', | ||
| 59 | dest='quiet', action='store_true', | ||
| 60 | help='Pass --quiet to git rebase') | ||
| 61 | p.add_option('--autosquash', | 56 | p.add_option('--autosquash', |
| 62 | dest='autosquash', action='store_true', | 57 | dest='autosquash', action='store_true', |
| 63 | help='Pass --autosquash to git rebase') | 58 | help='pass --autosquash to git rebase') |
| 64 | p.add_option('--whitespace', | 59 | p.add_option('--whitespace', |
| 65 | dest='whitespace', action='store', metavar='WS', | 60 | dest='whitespace', action='store', metavar='WS', |
| 66 | help='Pass --whitespace to git rebase') | 61 | help='pass --whitespace to git rebase') |
| 67 | p.add_option('--auto-stash', | 62 | p.add_option('--auto-stash', |
| 68 | dest='auto_stash', action='store_true', | 63 | dest='auto_stash', action='store_true', |
| 69 | help='Stash local modifications before starting') | 64 | help='stash local modifications before starting') |
| 70 | p.add_option('-m', '--onto-manifest', | 65 | p.add_option('-m', '--onto-manifest', |
| 71 | dest='onto_manifest', action='store_true', | 66 | dest='onto_manifest', action='store_true', |
| 72 | help='Rebase onto the manifest version instead of upstream ' | 67 | help='rebase onto the manifest version instead of upstream ' |
| 73 | 'HEAD. This helps to make sure the local tree stays ' | 68 | 'HEAD (this helps to make sure the local tree stays ' |
| 74 | 'consistent if you previously synced to a manifest.') | 69 | 'consistent if you previously synced to a manifest)') |
| 75 | 70 | ||
| 76 | def Execute(self, opt, args): | 71 | def Execute(self, opt, args): |
| 77 | all_projects = self.GetProjects(args) | 72 | all_projects = self.GetProjects(args) |
| @@ -82,7 +77,7 @@ branch but need to incorporate new upstream changes "underneath" them. | |||
| 82 | file=sys.stderr) | 77 | file=sys.stderr) |
| 83 | if len(args) == 1: | 78 | if len(args) == 1: |
| 84 | print('note: project %s is mapped to more than one path' % (args[0],), | 79 | print('note: project %s is mapped to more than one path' % (args[0],), |
| 85 | file=sys.stderr) | 80 | file=sys.stderr) |
| 86 | return 1 | 81 | return 1 |
| 87 | 82 | ||
| 88 | # Setup the common git rebase args that we use for all projects. | 83 | # Setup the common git rebase args that we use for all projects. |
| @@ -93,7 +88,7 @@ branch but need to incorporate new upstream changes "underneath" them. | |||
| 93 | common_args.append('--quiet') | 88 | common_args.append('--quiet') |
| 94 | if opt.force_rebase: | 89 | if opt.force_rebase: |
| 95 | common_args.append('--force-rebase') | 90 | common_args.append('--force-rebase') |
| 96 | if opt.no_ff: | 91 | if not opt.ff: |
| 97 | common_args.append('--no-ff') | 92 | common_args.append('--no-ff') |
| 98 | if opt.autosquash: | 93 | if opt.autosquash: |
| 99 | common_args.append('--autosquash') | 94 | common_args.append('--autosquash') |
diff --git a/subcmds/selfupdate.py b/subcmds/selfupdate.py index a8a09b64..282f518e 100644 --- a/subcmds/selfupdate.py +++ b/subcmds/selfupdate.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2009 The Android Open Source Project | 1 | # Copyright (C) 2009 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,7 +12,6 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from __future__ import print_function | ||
| 18 | from optparse import SUPPRESS_HELP | 15 | from optparse import SUPPRESS_HELP |
| 19 | import sys | 16 | import sys |
| 20 | 17 | ||
| @@ -22,8 +19,9 @@ from command import Command, MirrorSafeCommand | |||
| 22 | from subcmds.sync import _PostRepoUpgrade | 19 | from subcmds.sync import _PostRepoUpgrade |
| 23 | from subcmds.sync import _PostRepoFetch | 20 | from subcmds.sync import _PostRepoFetch |
| 24 | 21 | ||
| 22 | |||
| 25 | class Selfupdate(Command, MirrorSafeCommand): | 23 | class Selfupdate(Command, MirrorSafeCommand): |
| 26 | common = False | 24 | COMMON = False |
| 27 | helpSummary = "Update repo to the latest version" | 25 | helpSummary = "Update repo to the latest version" |
| 28 | helpUsage = """ | 26 | helpUsage = """ |
| 29 | %prog | 27 | %prog |
| @@ -39,7 +37,7 @@ need to be performed by an end-user. | |||
| 39 | def _Options(self, p): | 37 | def _Options(self, p): |
| 40 | g = p.add_option_group('repo Version options') | 38 | g = p.add_option_group('repo Version options') |
| 41 | g.add_option('--no-repo-verify', | 39 | g.add_option('--no-repo-verify', |
| 42 | dest='no_repo_verify', action='store_true', | 40 | dest='repo_verify', default=True, action='store_false', |
| 43 | help='do not verify repo source code') | 41 | help='do not verify repo source code') |
| 44 | g.add_option('--repo-upgraded', | 42 | g.add_option('--repo-upgraded', |
| 45 | dest='repo_upgraded', action='store_true', | 43 | dest='repo_upgraded', action='store_true', |
| @@ -59,5 +57,5 @@ need to be performed by an end-user. | |||
| 59 | 57 | ||
| 60 | rp.bare_git.gc('--auto') | 58 | rp.bare_git.gc('--auto') |
| 61 | _PostRepoFetch(rp, | 59 | _PostRepoFetch(rp, |
| 62 | no_repo_verify = opt.no_repo_verify, | 60 | repo_verify=opt.repo_verify, |
| 63 | verbose = True) | 61 | verbose=True) |
diff --git a/subcmds/smartsync.py b/subcmds/smartsync.py index 675b9834..d91d59c6 100644 --- a/subcmds/smartsync.py +++ b/subcmds/smartsync.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2010 The Android Open Source Project | 1 | # Copyright (C) 2010 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -16,8 +14,9 @@ | |||
| 16 | 14 | ||
| 17 | from subcmds.sync import Sync | 15 | from subcmds.sync import Sync |
| 18 | 16 | ||
| 17 | |||
| 19 | class Smartsync(Sync): | 18 | class Smartsync(Sync): |
| 20 | common = True | 19 | COMMON = True |
| 21 | helpSummary = "Update working tree to the latest known good revision" | 20 | helpSummary = "Update working tree to the latest known good revision" |
| 22 | helpUsage = """ | 21 | helpUsage = """ |
| 23 | %prog [<project>...] | 22 | %prog [<project>...] |
diff --git a/subcmds/stage.py b/subcmds/stage.py index aeb49513..0389a4ff 100644 --- a/subcmds/stage.py +++ b/subcmds/stage.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2008 The Android Open Source Project | 1 | # Copyright (C) 2008 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,13 +12,13 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from __future__ import print_function | ||
| 18 | import sys | 15 | import sys |
| 19 | 16 | ||
| 20 | from color import Coloring | 17 | from color import Coloring |
| 21 | from command import InteractiveCommand | 18 | from command import InteractiveCommand |
| 22 | from git_command import GitCommand | 19 | from git_command import GitCommand |
| 23 | 20 | ||
| 21 | |||
| 24 | class _ProjectList(Coloring): | 22 | class _ProjectList(Coloring): |
| 25 | def __init__(self, gc): | 23 | def __init__(self, gc): |
| 26 | Coloring.__init__(self, gc, 'interactive') | 24 | Coloring.__init__(self, gc, 'interactive') |
| @@ -28,8 +26,9 @@ class _ProjectList(Coloring): | |||
| 28 | self.header = self.printer('header', attr='bold') | 26 | self.header = self.printer('header', attr='bold') |
| 29 | self.help = self.printer('help', fg='red', attr='bold') | 27 | self.help = self.printer('help', fg='red', attr='bold') |
| 30 | 28 | ||
| 29 | |||
| 31 | class Stage(InteractiveCommand): | 30 | class Stage(InteractiveCommand): |
| 32 | common = True | 31 | COMMON = True |
| 33 | helpSummary = "Stage file(s) for commit" | 32 | helpSummary = "Stage file(s) for commit" |
| 34 | helpUsage = """ | 33 | helpUsage = """ |
| 35 | %prog -i [<project>...] | 34 | %prog -i [<project>...] |
| @@ -39,7 +38,8 @@ The '%prog' command stages files to prepare the next commit. | |||
| 39 | """ | 38 | """ |
| 40 | 39 | ||
| 41 | def _Options(self, p): | 40 | def _Options(self, p): |
| 42 | p.add_option('-i', '--interactive', | 41 | g = p.get_option_group('--quiet') |
| 42 | g.add_option('-i', '--interactive', | ||
| 43 | dest='interactive', action='store_true', | 43 | dest='interactive', action='store_true', |
| 44 | help='use interactive staging') | 44 | help='use interactive staging') |
| 45 | 45 | ||
| @@ -105,6 +105,7 @@ The '%prog' command stages files to prepare the next commit. | |||
| 105 | continue | 105 | continue |
| 106 | print('Bye.') | 106 | print('Bye.') |
| 107 | 107 | ||
| 108 | |||
| 108 | def _AddI(project): | 109 | def _AddI(project): |
| 109 | p = GitCommand(project, ['add', '--interactive'], bare=False) | 110 | p = GitCommand(project, ['add', '--interactive'], bare=False) |
| 110 | p.Wait() | 111 | p.Wait() |
diff --git a/subcmds/start.py b/subcmds/start.py index 6ec0b2ce..2addaf2e 100644 --- a/subcmds/start.py +++ b/subcmds/start.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2008 The Android Open Source Project | 1 | # Copyright (C) 2008 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,19 +12,20 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from __future__ import print_function | 15 | import functools |
| 18 | import os | 16 | import os |
| 19 | import sys | 17 | import sys |
| 20 | 18 | ||
| 21 | from command import Command | 19 | from command import Command, DEFAULT_LOCAL_JOBS |
| 22 | from git_config import IsImmutable | 20 | from git_config import IsImmutable |
| 23 | from git_command import git | 21 | from git_command import git |
| 24 | import gitc_utils | 22 | import gitc_utils |
| 25 | from progress import Progress | 23 | from progress import Progress |
| 26 | from project import SyncBuffer | 24 | from project import SyncBuffer |
| 27 | 25 | ||
| 26 | |||
| 28 | class Start(Command): | 27 | class Start(Command): |
| 29 | common = True | 28 | COMMON = True |
| 30 | helpSummary = "Start a new branch for development" | 29 | helpSummary = "Start a new branch for development" |
| 31 | helpUsage = """ | 30 | helpUsage = """ |
| 32 | %prog <newbranchname> [--all | <project>...] | 31 | %prog <newbranchname> [--all | <project>...] |
| @@ -35,6 +34,7 @@ class Start(Command): | |||
| 35 | '%prog' begins a new branch of development, starting from the | 34 | '%prog' begins a new branch of development, starting from the |
| 36 | revision specified in the manifest. | 35 | revision specified in the manifest. |
| 37 | """ | 36 | """ |
| 37 | PARALLEL_JOBS = DEFAULT_LOCAL_JOBS | ||
| 38 | 38 | ||
| 39 | def _Options(self, p): | 39 | def _Options(self, p): |
| 40 | p.add_option('--all', | 40 | p.add_option('--all', |
| @@ -42,7 +42,8 @@ revision specified in the manifest. | |||
| 42 | help='begin branch in all projects') | 42 | help='begin branch in all projects') |
| 43 | p.add_option('-r', '--rev', '--revision', dest='revision', | 43 | p.add_option('-r', '--rev', '--revision', dest='revision', |
| 44 | help='point branch at this revision instead of upstream') | 44 | help='point branch at this revision instead of upstream') |
| 45 | p.add_option('--head', dest='revision', action='store_const', const='HEAD', | 45 | p.add_option('--head', '--HEAD', |
| 46 | dest='revision', action='store_const', const='HEAD', | ||
| 46 | help='abbreviation for --rev HEAD') | 47 | help='abbreviation for --rev HEAD') |
| 47 | 48 | ||
| 48 | def ValidateOptions(self, opt, args): | 49 | def ValidateOptions(self, opt, args): |
| @@ -53,6 +54,26 @@ revision specified in the manifest. | |||
| 53 | if not git.check_ref_format('heads/%s' % nb): | 54 | if not git.check_ref_format('heads/%s' % nb): |
| 54 | self.OptionParser.error("'%s' is not a valid name" % nb) | 55 | self.OptionParser.error("'%s' is not a valid name" % nb) |
| 55 | 56 | ||
| 57 | def _ExecuteOne(self, revision, nb, project): | ||
| 58 | """Start one project.""" | ||
| 59 | # If the current revision is immutable, such as a SHA1, a tag or | ||
| 60 | # a change, then we can't push back to it. Substitute with | ||
| 61 | # dest_branch, if defined; or with manifest default revision instead. | ||
| 62 | branch_merge = '' | ||
| 63 | if IsImmutable(project.revisionExpr): | ||
| 64 | if project.dest_branch: | ||
| 65 | branch_merge = project.dest_branch | ||
| 66 | else: | ||
| 67 | branch_merge = self.manifest.default.revisionExpr | ||
| 68 | |||
| 69 | try: | ||
| 70 | ret = project.StartBranch( | ||
| 71 | nb, branch_merge=branch_merge, revision=revision) | ||
| 72 | except Exception as e: | ||
| 73 | print('error: unable to checkout %s: %s' % (project.name, e), file=sys.stderr) | ||
| 74 | ret = False | ||
| 75 | return (ret, project) | ||
| 76 | |||
| 56 | def Execute(self, opt, args): | 77 | def Execute(self, opt, args): |
| 57 | nb = args[0] | 78 | nb = args[0] |
| 58 | err = [] | 79 | err = [] |
| @@ -60,7 +81,7 @@ revision specified in the manifest. | |||
| 60 | if not opt.all: | 81 | if not opt.all: |
| 61 | projects = args[1:] | 82 | projects = args[1:] |
| 62 | if len(projects) < 1: | 83 | if len(projects) < 1: |
| 63 | projects = ['.',] # start it in the local project by default | 84 | projects = ['.'] # start it in the local project by default |
| 64 | 85 | ||
| 65 | all_projects = self.GetProjects(projects, | 86 | all_projects = self.GetProjects(projects, |
| 66 | missing_ok=bool(self.gitc_manifest)) | 87 | missing_ok=bool(self.gitc_manifest)) |
| @@ -84,11 +105,8 @@ revision specified in the manifest. | |||
| 84 | if not os.path.exists(os.getcwd()): | 105 | if not os.path.exists(os.getcwd()): |
| 85 | os.chdir(self.manifest.topdir) | 106 | os.chdir(self.manifest.topdir) |
| 86 | 107 | ||
| 87 | pm = Progress('Starting %s' % nb, len(all_projects)) | 108 | pm = Progress('Syncing %s' % nb, len(all_projects), quiet=opt.quiet) |
| 88 | for project in all_projects: | 109 | for project in all_projects: |
| 89 | pm.update() | ||
| 90 | |||
| 91 | if self.gitc_manifest: | ||
| 92 | gitc_project = self.gitc_manifest.paths[project.relpath] | 110 | gitc_project = self.gitc_manifest.paths[project.relpath] |
| 93 | # Sync projects that have not been opened. | 111 | # Sync projects that have not been opened. |
| 94 | if not gitc_project.already_synced: | 112 | if not gitc_project.already_synced: |
| @@ -101,21 +119,21 @@ revision specified in the manifest. | |||
| 101 | sync_buf = SyncBuffer(self.manifest.manifestProject.config) | 119 | sync_buf = SyncBuffer(self.manifest.manifestProject.config) |
| 102 | project.Sync_LocalHalf(sync_buf) | 120 | project.Sync_LocalHalf(sync_buf) |
| 103 | project.revisionId = gitc_project.old_revision | 121 | project.revisionId = gitc_project.old_revision |
| 122 | pm.update() | ||
| 123 | pm.end() | ||
| 104 | 124 | ||
| 105 | # If the current revision is immutable, such as a SHA1, a tag or | 125 | def _ProcessResults(_pool, pm, results): |
| 106 | # a change, then we can't push back to it. Substitute with | 126 | for (result, project) in results: |
| 107 | # dest_branch, if defined; or with manifest default revision instead. | 127 | if not result: |
| 108 | branch_merge = '' | 128 | err.append(project) |
| 109 | if IsImmutable(project.revisionExpr): | 129 | pm.update() |
| 110 | if project.dest_branch: | ||
| 111 | branch_merge = project.dest_branch | ||
| 112 | else: | ||
| 113 | branch_merge = self.manifest.default.revisionExpr | ||
| 114 | 130 | ||
| 115 | if not project.StartBranch( | 131 | self.ExecuteInParallel( |
| 116 | nb, branch_merge=branch_merge, revision=opt.revision): | 132 | opt.jobs, |
| 117 | err.append(project) | 133 | functools.partial(self._ExecuteOne, opt.revision, nb), |
| 118 | pm.end() | 134 | all_projects, |
| 135 | callback=_ProcessResults, | ||
| 136 | output=Progress('Starting %s' % (nb,), len(all_projects), quiet=opt.quiet)) | ||
| 119 | 137 | ||
| 120 | if err: | 138 | if err: |
| 121 | for p in err: | 139 | for p in err: |
diff --git a/subcmds/status.py b/subcmds/status.py index 63972d72..5b669547 100644 --- a/subcmds/status.py +++ b/subcmds/status.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2008 The Android Open Source Project | 1 | # Copyright (C) 2008 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,25 +12,19 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from __future__ import print_function | 15 | import functools |
| 18 | |||
| 19 | from command import PagedCommand | ||
| 20 | |||
| 21 | try: | ||
| 22 | import threading as _threading | ||
| 23 | except ImportError: | ||
| 24 | import dummy_threading as _threading | ||
| 25 | |||
| 26 | import glob | 16 | import glob |
| 27 | 17 | import io | |
| 28 | import itertools | ||
| 29 | import os | 18 | import os |
| 30 | 19 | ||
| 20 | from command import DEFAULT_LOCAL_JOBS, PagedCommand | ||
| 21 | |||
| 31 | from color import Coloring | 22 | from color import Coloring |
| 32 | import platform_utils | 23 | import platform_utils |
| 33 | 24 | ||
| 25 | |||
| 34 | class Status(PagedCommand): | 26 | class Status(PagedCommand): |
| 35 | common = True | 27 | COMMON = True |
| 36 | helpSummary = "Show the working tree status" | 28 | helpSummary = "Show the working tree status" |
| 37 | helpUsage = """ | 29 | helpUsage = """ |
| 38 | %prog [<project>...] | 30 | %prog [<project>...] |
| @@ -84,36 +76,29 @@ the following meanings: | |||
| 84 | d: deleted ( in index, not in work tree ) | 76 | d: deleted ( in index, not in work tree ) |
| 85 | 77 | ||
| 86 | """ | 78 | """ |
| 79 | PARALLEL_JOBS = DEFAULT_LOCAL_JOBS | ||
| 87 | 80 | ||
| 88 | def _Options(self, p): | 81 | def _Options(self, p): |
| 89 | p.add_option('-j', '--jobs', | ||
| 90 | dest='jobs', action='store', type='int', default=2, | ||
| 91 | help="number of projects to check simultaneously") | ||
| 92 | p.add_option('-o', '--orphans', | 82 | p.add_option('-o', '--orphans', |
| 93 | dest='orphans', action='store_true', | 83 | dest='orphans', action='store_true', |
| 94 | help="include objects in working directory outside of repo projects") | 84 | help="include objects in working directory outside of repo projects") |
| 95 | p.add_option('-q', '--quiet', action='store_true', | ||
| 96 | help="only print the name of modified projects") | ||
| 97 | 85 | ||
| 98 | def _StatusHelper(self, project, clean_counter, sem, quiet): | 86 | def _StatusHelper(self, quiet, project): |
| 99 | """Obtains the status for a specific project. | 87 | """Obtains the status for a specific project. |
| 100 | 88 | ||
| 101 | Obtains the status for a project, redirecting the output to | 89 | Obtains the status for a project, redirecting the output to |
| 102 | the specified object. It will release the semaphore | 90 | the specified object. |
| 103 | when done. | ||
| 104 | 91 | ||
| 105 | Args: | 92 | Args: |
| 93 | quiet: Where to output the status. | ||
| 106 | project: Project to get status of. | 94 | project: Project to get status of. |
| 107 | clean_counter: Counter for clean projects. | 95 | |
| 108 | sem: Semaphore, will call release() when complete. | 96 | Returns: |
| 109 | output: Where to output the status. | 97 | The status of the project. |
| 110 | """ | 98 | """ |
| 111 | try: | 99 | buf = io.StringIO() |
| 112 | state = project.PrintWorkTreeStatus(quiet=quiet) | 100 | ret = project.PrintWorkTreeStatus(quiet=quiet, output_redir=buf) |
| 113 | if state == 'CLEAN': | 101 | return (ret, buf.getvalue()) |
| 114 | next(clean_counter) | ||
| 115 | finally: | ||
| 116 | sem.release() | ||
| 117 | 102 | ||
| 118 | def _FindOrphans(self, dirs, proj_dirs, proj_dirs_parents, outstring): | 103 | def _FindOrphans(self, dirs, proj_dirs, proj_dirs_parents, outstring): |
| 119 | """find 'dirs' that are present in 'proj_dirs_parents' but not in 'proj_dirs'""" | 104 | """find 'dirs' that are present in 'proj_dirs_parents' but not in 'proj_dirs'""" |
| @@ -126,34 +111,31 @@ the following meanings: | |||
| 126 | continue | 111 | continue |
| 127 | if item in proj_dirs_parents: | 112 | if item in proj_dirs_parents: |
| 128 | self._FindOrphans(glob.glob('%s/.*' % item) + | 113 | self._FindOrphans(glob.glob('%s/.*' % item) + |
| 129 | glob.glob('%s/*' % item), | 114 | glob.glob('%s/*' % item), |
| 130 | proj_dirs, proj_dirs_parents, outstring) | 115 | proj_dirs, proj_dirs_parents, outstring) |
| 131 | continue | 116 | continue |
| 132 | outstring.append(''.join([status_header, item, '/'])) | 117 | outstring.append(''.join([status_header, item, '/'])) |
| 133 | 118 | ||
| 134 | def Execute(self, opt, args): | 119 | def Execute(self, opt, args): |
| 135 | all_projects = self.GetProjects(args) | 120 | all_projects = self.GetProjects(args) |
| 136 | counter = itertools.count() | ||
| 137 | 121 | ||
| 138 | if opt.jobs == 1: | 122 | def _ProcessResults(_pool, _output, results): |
| 139 | for project in all_projects: | 123 | ret = 0 |
| 140 | state = project.PrintWorkTreeStatus(quiet=opt.quiet) | 124 | for (state, output) in results: |
| 125 | if output: | ||
| 126 | print(output, end='') | ||
| 141 | if state == 'CLEAN': | 127 | if state == 'CLEAN': |
| 142 | next(counter) | 128 | ret += 1 |
| 143 | else: | 129 | return ret |
| 144 | sem = _threading.Semaphore(opt.jobs) | 130 | |
| 145 | threads = [] | 131 | counter = self.ExecuteInParallel( |
| 146 | for project in all_projects: | 132 | opt.jobs, |
| 147 | sem.acquire() | 133 | functools.partial(self._StatusHelper, opt.quiet), |
| 148 | 134 | all_projects, | |
| 149 | t = _threading.Thread(target=self._StatusHelper, | 135 | callback=_ProcessResults, |
| 150 | args=(project, counter, sem, opt.quiet)) | 136 | ordered=True) |
| 151 | threads.append(t) | 137 | |
| 152 | t.daemon = True | 138 | if not opt.quiet and len(all_projects) == counter: |
| 153 | t.start() | ||
| 154 | for t in threads: | ||
| 155 | t.join() | ||
| 156 | if not opt.quiet and len(all_projects) == next(counter): | ||
| 157 | print('nothing to commit (working directory clean)') | 139 | print('nothing to commit (working directory clean)') |
| 158 | 140 | ||
| 159 | if opt.orphans: | 141 | if opt.orphans: |
| @@ -170,8 +152,8 @@ the following meanings: | |||
| 170 | class StatusColoring(Coloring): | 152 | class StatusColoring(Coloring): |
| 171 | def __init__(self, config): | 153 | def __init__(self, config): |
| 172 | Coloring.__init__(self, config, 'status') | 154 | Coloring.__init__(self, config, 'status') |
| 173 | self.project = self.printer('header', attr = 'bold') | 155 | self.project = self.printer('header', attr='bold') |
| 174 | self.untracked = self.printer('untracked', fg = 'red') | 156 | self.untracked = self.printer('untracked', fg='red') |
| 175 | 157 | ||
| 176 | orig_path = os.getcwd() | 158 | orig_path = os.getcwd() |
| 177 | try: | 159 | try: |
| @@ -179,11 +161,11 @@ the following meanings: | |||
| 179 | 161 | ||
| 180 | outstring = [] | 162 | outstring = [] |
| 181 | self._FindOrphans(glob.glob('.*') + | 163 | self._FindOrphans(glob.glob('.*') + |
| 182 | glob.glob('*'), | 164 | glob.glob('*'), |
| 183 | proj_dirs, proj_dirs_parents, outstring) | 165 | proj_dirs, proj_dirs_parents, outstring) |
| 184 | 166 | ||
| 185 | if outstring: | 167 | if outstring: |
| 186 | output = StatusColoring(self.manifest.globalConfig) | 168 | output = StatusColoring(self.client.globalConfig) |
| 187 | output.project('Objects not within a project (orphans)') | 169 | output.project('Objects not within a project (orphans)') |
| 188 | output.nl() | 170 | output.nl() |
| 189 | for entry in outstring: | 171 | for entry in outstring: |
diff --git a/subcmds/sync.py b/subcmds/sync.py index 2973a16e..3211cbb1 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2008 The Android Open Source Project | 1 | # Copyright (C) 2008 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,37 +12,23 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from __future__ import print_function | 15 | import errno |
| 16 | import functools | ||
| 17 | import http.cookiejar as cookielib | ||
| 18 | import io | ||
| 18 | import json | 19 | import json |
| 20 | import multiprocessing | ||
| 19 | import netrc | 21 | import netrc |
| 20 | from optparse import SUPPRESS_HELP | 22 | from optparse import SUPPRESS_HELP |
| 21 | import os | 23 | import os |
| 22 | import re | ||
| 23 | import socket | 24 | import socket |
| 24 | import subprocess | ||
| 25 | import sys | 25 | import sys |
| 26 | import tempfile | 26 | import tempfile |
| 27 | import time | 27 | import time |
| 28 | 28 | import urllib.error | |
| 29 | from pyversion import is_python3 | 29 | import urllib.parse |
| 30 | if is_python3(): | 30 | import urllib.request |
| 31 | import http.cookiejar as cookielib | 31 | import xmlrpc.client |
| 32 | import urllib.error | ||
| 33 | import urllib.parse | ||
| 34 | import urllib.request | ||
| 35 | import xmlrpc.client | ||
| 36 | else: | ||
| 37 | import cookielib | ||
| 38 | import imp | ||
| 39 | import urllib2 | ||
| 40 | import urlparse | ||
| 41 | import xmlrpclib | ||
| 42 | urllib = imp.new_module('urllib') | ||
| 43 | urllib.error = urllib2 | ||
| 44 | urllib.parse = urlparse | ||
| 45 | urllib.request = urllib2 | ||
| 46 | xmlrpc = imp.new_module('xmlrpc') | ||
| 47 | xmlrpc.client = xmlrpclib | ||
| 48 | 32 | ||
| 49 | try: | 33 | try: |
| 50 | import threading as _threading | 34 | import threading as _threading |
| @@ -53,44 +37,36 @@ except ImportError: | |||
| 53 | 37 | ||
| 54 | try: | 38 | try: |
| 55 | import resource | 39 | import resource |
| 40 | |||
| 56 | def _rlimit_nofile(): | 41 | def _rlimit_nofile(): |
| 57 | return resource.getrlimit(resource.RLIMIT_NOFILE) | 42 | return resource.getrlimit(resource.RLIMIT_NOFILE) |
| 58 | except ImportError: | 43 | except ImportError: |
| 59 | def _rlimit_nofile(): | 44 | def _rlimit_nofile(): |
| 60 | return (256, 256) | 45 | return (256, 256) |
| 61 | 46 | ||
| 62 | try: | ||
| 63 | import multiprocessing | ||
| 64 | except ImportError: | ||
| 65 | multiprocessing = None | ||
| 66 | |||
| 67 | import event_log | 47 | import event_log |
| 68 | from git_command import GIT, git_require | 48 | from git_command import git_require |
| 69 | from git_config import GetUrlCookieFile | 49 | from git_config import GetUrlCookieFile |
| 70 | from git_refs import R_HEADS, HEAD | 50 | from git_refs import R_HEADS, HEAD |
| 51 | import git_superproject | ||
| 71 | import gitc_utils | 52 | import gitc_utils |
| 72 | from project import Project | 53 | from project import Project |
| 73 | from project import RemoteSpec | 54 | from project import RemoteSpec |
| 74 | from command import Command, MirrorSafeCommand | 55 | from command import Command, MirrorSafeCommand, WORKER_BATCH_SIZE |
| 75 | from error import RepoChangedException, GitError, ManifestParseError | 56 | from error import RepoChangedException, GitError, ManifestParseError |
| 76 | import platform_utils | 57 | import platform_utils |
| 77 | from project import SyncBuffer | 58 | from project import SyncBuffer |
| 78 | from progress import Progress | 59 | from progress import Progress |
| 60 | import ssh | ||
| 79 | from wrapper import Wrapper | 61 | from wrapper import Wrapper |
| 80 | from manifest_xml import GitcManifest | 62 | from manifest_xml import GitcManifest |
| 81 | 63 | ||
| 82 | _ONE_DAY_S = 24 * 60 * 60 | 64 | _ONE_DAY_S = 24 * 60 * 60 |
| 83 | 65 | ||
| 84 | class _FetchError(Exception): | ||
| 85 | """Internal error thrown in _FetchHelper() when we don't want stack trace.""" | ||
| 86 | pass | ||
| 87 | |||
| 88 | class _CheckoutError(Exception): | ||
| 89 | """Internal error thrown in _CheckoutOne() when we don't want stack trace.""" | ||
| 90 | 66 | ||
| 91 | class Sync(Command, MirrorSafeCommand): | 67 | class Sync(Command, MirrorSafeCommand): |
| 92 | jobs = 1 | 68 | jobs = 1 |
| 93 | common = True | 69 | COMMON = True |
| 94 | helpSummary = "Update working tree to the latest revision" | 70 | helpSummary = "Update working tree to the latest revision" |
| 95 | helpUsage = """ | 71 | helpUsage = """ |
| 96 | %prog [<project>...] | 72 | %prog [<project>...] |
| @@ -133,11 +109,11 @@ if the manifest server specified in the manifest file already includes | |||
| 133 | credentials. | 109 | credentials. |
| 134 | 110 | ||
| 135 | By default, all projects will be synced. The --fail-fast option can be used | 111 | By default, all projects will be synced. The --fail-fast option can be used |
| 136 | to halt syncing as soon as possible when the the first project fails to sync. | 112 | to halt syncing as soon as possible when the first project fails to sync. |
| 137 | 113 | ||
| 138 | The --force-sync option can be used to overwrite existing git | 114 | The --force-sync option can be used to overwrite existing git |
| 139 | directories if they have previously been linked to a different | 115 | directories if they have previously been linked to a different |
| 140 | object direcotry. WARNING: This may cause data to be lost since | 116 | object directory. WARNING: This may cause data to be lost since |
| 141 | refs may be removed when overwriting. | 117 | refs may be removed when overwriting. |
| 142 | 118 | ||
| 143 | The --force-remove-dirty option can be used to remove previously used | 119 | The --force-remove-dirty option can be used to remove previously used |
| @@ -191,12 +167,21 @@ If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or | |||
| 191 | later is required to fix a server side protocol bug. | 167 | later is required to fix a server side protocol bug. |
| 192 | 168 | ||
| 193 | """ | 169 | """ |
| 170 | PARALLEL_JOBS = 1 | ||
| 171 | |||
| 172 | def _CommonOptions(self, p): | ||
| 173 | if self.manifest: | ||
| 174 | try: | ||
| 175 | self.PARALLEL_JOBS = self.manifest.default.sync_j | ||
| 176 | except ManifestParseError: | ||
| 177 | pass | ||
| 178 | super()._CommonOptions(p) | ||
| 194 | 179 | ||
| 195 | def _Options(self, p, show_smart=True): | 180 | def _Options(self, p, show_smart=True): |
| 196 | try: | 181 | p.add_option('--jobs-network', default=None, type=int, metavar='JOBS', |
| 197 | self.jobs = self.manifest.default.sync_j | 182 | help='number of network jobs to run in parallel (defaults to --jobs)') |
| 198 | except ManifestParseError: | 183 | p.add_option('--jobs-checkout', default=None, type=int, metavar='JOBS', |
| 199 | self.jobs = 1 | 184 | help='number of local checkout jobs to run in parallel (defaults to --jobs)') |
| 200 | 185 | ||
| 201 | p.add_option('-f', '--force-broken', | 186 | p.add_option('-f', '--force-broken', |
| 202 | dest='force_broken', action='store_true', | 187 | dest='force_broken', action='store_true', |
| @@ -217,6 +202,10 @@ later is required to fix a server side protocol bug. | |||
| 217 | p.add_option('-l', '--local-only', | 202 | p.add_option('-l', '--local-only', |
| 218 | dest='local_only', action='store_true', | 203 | dest='local_only', action='store_true', |
| 219 | help="only update working tree, don't fetch") | 204 | help="only update working tree, don't fetch") |
| 205 | p.add_option('--no-manifest-update', '--nmu', | ||
| 206 | dest='mp_update', action='store_false', default='true', | ||
| 207 | help='use the existing manifest checkout as-is. ' | ||
| 208 | '(do not update to the latest revision)') | ||
| 220 | p.add_option('-n', '--network-only', | 209 | p.add_option('-n', '--network-only', |
| 221 | dest='network_only', action='store_true', | 210 | dest='network_only', action='store_true', |
| 222 | help="fetch only, don't update working tree") | 211 | help="fetch only, don't update working tree") |
| @@ -226,17 +215,15 @@ later is required to fix a server side protocol bug. | |||
| 226 | p.add_option('-c', '--current-branch', | 215 | p.add_option('-c', '--current-branch', |
| 227 | dest='current_branch_only', action='store_true', | 216 | dest='current_branch_only', action='store_true', |
| 228 | help='fetch only current branch from server') | 217 | help='fetch only current branch from server') |
| 229 | p.add_option('-q', '--quiet', | 218 | p.add_option('--no-current-branch', |
| 230 | dest='quiet', action='store_true', | 219 | dest='current_branch_only', action='store_false', |
| 231 | help='be more quiet') | 220 | help='fetch all branches from server') |
| 232 | p.add_option('-j', '--jobs', | ||
| 233 | dest='jobs', action='store', type='int', | ||
| 234 | help="projects to fetch simultaneously (default %d)" % self.jobs) | ||
| 235 | p.add_option('-m', '--manifest-name', | 221 | p.add_option('-m', '--manifest-name', |
| 236 | dest='manifest_name', | 222 | dest='manifest_name', |
| 237 | help='temporary manifest to use for this sync', metavar='NAME.xml') | 223 | help='temporary manifest to use for this sync', metavar='NAME.xml') |
| 238 | p.add_option('--no-clone-bundle', | 224 | p.add_option('--clone-bundle', action='store_true', |
| 239 | dest='no_clone_bundle', action='store_true', | 225 | help='enable use of /clone.bundle on HTTP/HTTPS') |
| 226 | p.add_option('--no-clone-bundle', dest='clone_bundle', action='store_false', | ||
| 240 | help='disable use of /clone.bundle on HTTP/HTTPS') | 227 | help='disable use of /clone.bundle on HTTP/HTTPS') |
| 241 | p.add_option('-u', '--manifest-server-username', action='store', | 228 | p.add_option('-u', '--manifest-server-username', action='store', |
| 242 | dest='manifest_server_username', | 229 | dest='manifest_server_username', |
| @@ -247,12 +234,23 @@ later is required to fix a server side protocol bug. | |||
| 247 | p.add_option('--fetch-submodules', | 234 | p.add_option('--fetch-submodules', |
| 248 | dest='fetch_submodules', action='store_true', | 235 | dest='fetch_submodules', action='store_true', |
| 249 | help='fetch submodules from server') | 236 | help='fetch submodules from server') |
| 237 | p.add_option('--use-superproject', action='store_true', | ||
| 238 | help='use the manifest superproject to sync projects') | ||
| 239 | p.add_option('--no-use-superproject', action='store_false', | ||
| 240 | dest='use_superproject', | ||
| 241 | help='disable use of manifest superprojects') | ||
| 242 | p.add_option('--tags', | ||
| 243 | action='store_false', | ||
| 244 | help='fetch tags') | ||
| 250 | p.add_option('--no-tags', | 245 | p.add_option('--no-tags', |
| 251 | dest='no_tags', action='store_true', | 246 | dest='tags', action='store_false', |
| 252 | help="don't fetch tags") | 247 | help="don't fetch tags") |
| 253 | p.add_option('--optimized-fetch', | 248 | p.add_option('--optimized-fetch', |
| 254 | dest='optimized_fetch', action='store_true', | 249 | dest='optimized_fetch', action='store_true', |
| 255 | help='only fetch projects fixed to sha1 if revision does not exist locally') | 250 | help='only fetch projects fixed to sha1 if revision does not exist locally') |
| 251 | p.add_option('--retry-fetches', | ||
| 252 | default=0, action='store', type='int', | ||
| 253 | help='number of times to retry fetches on transient errors') | ||
| 256 | p.add_option('--prune', dest='prune', action='store_true', | 254 | p.add_option('--prune', dest='prune', action='store_true', |
| 257 | help='delete refs that no longer exist on the remote') | 255 | help='delete refs that no longer exist on the remote') |
| 258 | if show_smart: | 256 | if show_smart: |
| @@ -265,345 +263,400 @@ later is required to fix a server side protocol bug. | |||
| 265 | 263 | ||
| 266 | g = p.add_option_group('repo Version options') | 264 | g = p.add_option_group('repo Version options') |
| 267 | g.add_option('--no-repo-verify', | 265 | g.add_option('--no-repo-verify', |
| 268 | dest='no_repo_verify', action='store_true', | 266 | dest='repo_verify', default=True, action='store_false', |
| 269 | help='do not verify repo source code') | 267 | help='do not verify repo source code') |
| 270 | g.add_option('--repo-upgraded', | 268 | g.add_option('--repo-upgraded', |
| 271 | dest='repo_upgraded', action='store_true', | 269 | dest='repo_upgraded', action='store_true', |
| 272 | help=SUPPRESS_HELP) | 270 | help=SUPPRESS_HELP) |
| 273 | 271 | ||
| 274 | def _FetchProjectList(self, opt, projects, sem, *args, **kwargs): | 272 | def _GetBranch(self): |
| 275 | """Main function of the fetch threads. | 273 | """Returns the branch name for getting the approved manifest.""" |
| 274 | p = self.manifest.manifestProject | ||
| 275 | b = p.GetBranch(p.CurrentBranch) | ||
| 276 | branch = b.merge | ||
| 277 | if branch.startswith(R_HEADS): | ||
| 278 | branch = branch[len(R_HEADS):] | ||
| 279 | return branch | ||
| 280 | |||
| 281 | def _GetCurrentBranchOnly(self, opt): | ||
| 282 | """Returns True if current-branch or use-superproject options are enabled.""" | ||
| 283 | return opt.current_branch_only or git_superproject.UseSuperproject(opt, self.manifest) | ||
| 284 | |||
| 285 | def _UpdateProjectsRevisionId(self, opt, args, load_local_manifests, superproject_logging_data): | ||
| 286 | """Update revisionId of every project with the SHA from superproject. | ||
| 287 | |||
| 288 | This function updates each project's revisionId with SHA from superproject. | ||
| 289 | It writes the updated manifest into a file and reloads the manifest from it. | ||
| 290 | |||
| 291 | Args: | ||
| 292 | opt: Program options returned from optparse. See _Options(). | ||
| 293 | args: Arguments to pass to GetProjects. See the GetProjects | ||
| 294 | docstring for details. | ||
| 295 | load_local_manifests: Whether to load local manifests. | ||
| 296 | superproject_logging_data: A dictionary of superproject data that is to be logged. | ||
| 297 | |||
| 298 | Returns: | ||
| 299 | Returns path to the overriding manifest file instead of None. | ||
| 300 | """ | ||
| 301 | print_messages = git_superproject.PrintMessages(opt, self.manifest) | ||
| 302 | superproject = git_superproject.Superproject(self.manifest, | ||
| 303 | self.repodir, | ||
| 304 | self.git_event_log, | ||
| 305 | quiet=opt.quiet, | ||
| 306 | print_messages=print_messages) | ||
| 307 | if opt.local_only: | ||
| 308 | manifest_path = superproject.manifest_path | ||
| 309 | if manifest_path: | ||
| 310 | self._ReloadManifest(manifest_path, load_local_manifests) | ||
| 311 | return manifest_path | ||
| 312 | |||
| 313 | all_projects = self.GetProjects(args, | ||
| 314 | missing_ok=True, | ||
| 315 | submodules_ok=opt.fetch_submodules) | ||
| 316 | update_result = superproject.UpdateProjectsRevisionId(all_projects) | ||
| 317 | manifest_path = update_result.manifest_path | ||
| 318 | superproject_logging_data['updatedrevisionid'] = bool(manifest_path) | ||
| 319 | if manifest_path: | ||
| 320 | self._ReloadManifest(manifest_path, load_local_manifests) | ||
| 321 | else: | ||
| 322 | if print_messages: | ||
| 323 | print('warning: Update of revisionId from superproject has failed, ' | ||
| 324 | 'repo sync will not use superproject to fetch the source. ', | ||
| 325 | 'Please resync with the --no-use-superproject option to avoid this repo warning.', | ||
| 326 | file=sys.stderr) | ||
| 327 | if update_result.fatal and opt.use_superproject is not None: | ||
| 328 | sys.exit(1) | ||
| 329 | return manifest_path | ||
| 330 | |||
| 331 | def _FetchProjectList(self, opt, projects): | ||
| 332 | """Main function of the fetch worker. | ||
| 333 | |||
| 334 | The projects we're given share the same underlying git object store, so we | ||
| 335 | have to fetch them in serial. | ||
| 276 | 336 | ||
| 277 | Delegates most of the work to _FetchHelper. | 337 | Delegates most of the work to _FetchHelper. |
| 278 | 338 | ||
| 279 | Args: | 339 | Args: |
| 280 | opt: Program options returned from optparse. See _Options(). | 340 | opt: Program options returned from optparse. See _Options(). |
| 281 | projects: Projects to fetch. | 341 | projects: Projects to fetch. |
| 282 | sem: We'll release() this semaphore when we exit so that another thread | ||
| 283 | can be started up. | ||
| 284 | *args, **kwargs: Remaining arguments to pass to _FetchHelper. See the | ||
| 285 | _FetchHelper docstring for details. | ||
| 286 | """ | 342 | """ |
| 287 | try: | 343 | return [self._FetchOne(opt, x) for x in projects] |
| 288 | for project in projects: | ||
| 289 | success = self._FetchHelper(opt, project, *args, **kwargs) | ||
| 290 | if not success and opt.fail_fast: | ||
| 291 | break | ||
| 292 | finally: | ||
| 293 | sem.release() | ||
| 294 | 344 | ||
| 295 | def _FetchHelper(self, opt, project, lock, fetched, pm, err_event, | 345 | def _FetchOne(self, opt, project): |
| 296 | clone_filter): | ||
| 297 | """Fetch git objects for a single project. | 346 | """Fetch git objects for a single project. |
| 298 | 347 | ||
| 299 | Args: | 348 | Args: |
| 300 | opt: Program options returned from optparse. See _Options(). | 349 | opt: Program options returned from optparse. See _Options(). |
| 301 | project: Project object for the project to fetch. | 350 | project: Project object for the project to fetch. |
| 302 | lock: Lock for accessing objects that are shared amongst multiple | ||
| 303 | _FetchHelper() threads. | ||
| 304 | fetched: set object that we will add project.gitdir to when we're done | ||
| 305 | (with our lock held). | ||
| 306 | pm: Instance of a Project object. We will call pm.update() (with our | ||
| 307 | lock held). | ||
| 308 | err_event: We'll set this event in the case of an error (after printing | ||
| 309 | out info about the error). | ||
| 310 | clone_filter: Filter for use in a partial clone. | ||
| 311 | 351 | ||
| 312 | Returns: | 352 | Returns: |
| 313 | Whether the fetch was successful. | 353 | Whether the fetch was successful. |
| 314 | """ | 354 | """ |
| 315 | # We'll set to true once we've locked the lock. | ||
| 316 | did_lock = False | ||
| 317 | |||
| 318 | # Encapsulate everything in a try/except/finally so that: | ||
| 319 | # - We always set err_event in the case of an exception. | ||
| 320 | # - We always make sure we unlock the lock if we locked it. | ||
| 321 | start = time.time() | 355 | start = time.time() |
| 322 | success = False | 356 | success = False |
| 357 | buf = io.StringIO() | ||
| 323 | try: | 358 | try: |
| 324 | try: | 359 | success = project.Sync_NetworkHalf( |
| 325 | success = project.Sync_NetworkHalf( | ||
| 326 | quiet=opt.quiet, | 360 | quiet=opt.quiet, |
| 327 | current_branch_only=opt.current_branch_only, | 361 | verbose=opt.verbose, |
| 362 | output_redir=buf, | ||
| 363 | current_branch_only=self._GetCurrentBranchOnly(opt), | ||
| 328 | force_sync=opt.force_sync, | 364 | force_sync=opt.force_sync, |
| 329 | clone_bundle=not opt.no_clone_bundle, | 365 | clone_bundle=opt.clone_bundle, |
| 330 | no_tags=opt.no_tags, archive=self.manifest.IsArchive, | 366 | tags=opt.tags, archive=self.manifest.IsArchive, |
| 331 | optimized_fetch=opt.optimized_fetch, | 367 | optimized_fetch=opt.optimized_fetch, |
| 368 | retry_fetches=opt.retry_fetches, | ||
| 332 | prune=opt.prune, | 369 | prune=opt.prune, |
| 333 | clone_filter=clone_filter) | 370 | ssh_proxy=self.ssh_proxy, |
| 334 | self._fetch_times.Set(project, time.time() - start) | 371 | clone_filter=self.manifest.CloneFilter, |
| 372 | partial_clone_exclude=self.manifest.PartialCloneExclude) | ||
| 335 | 373 | ||
| 336 | # Lock around all the rest of the code, since printing, updating a set | 374 | output = buf.getvalue() |
| 337 | # and Progress.update() are not thread safe. | 375 | if (opt.verbose or not success) and output: |
| 338 | lock.acquire() | 376 | print('\n' + output.rstrip()) |
| 339 | did_lock = True | ||
| 340 | 377 | ||
| 341 | if not success: | 378 | if not success: |
| 342 | err_event.set() | 379 | print('error: Cannot fetch %s from %s' |
| 343 | print('error: Cannot fetch %s from %s' | 380 | % (project.name, project.remote.url), |
| 344 | % (project.name, project.remote.url), | 381 | file=sys.stderr) |
| 345 | file=sys.stderr) | 382 | except GitError as e: |
| 346 | if opt.fail_fast: | 383 | print('error.GitError: Cannot fetch %s' % str(e), file=sys.stderr) |
| 347 | raise _FetchError() | 384 | except Exception as e: |
| 348 | 385 | print('error: Cannot fetch %s (%s: %s)' | |
| 349 | fetched.add(project.gitdir) | ||
| 350 | pm.update(msg=project.name) | ||
| 351 | except _FetchError: | ||
| 352 | pass | ||
| 353 | except Exception as e: | ||
| 354 | print('error: Cannot fetch %s (%s: %s)' \ | ||
| 355 | % (project.name, type(e).__name__, str(e)), file=sys.stderr) | 386 | % (project.name, type(e).__name__, str(e)), file=sys.stderr) |
| 356 | err_event.set() | 387 | raise |
| 357 | raise | 388 | |
| 358 | finally: | 389 | finish = time.time() |
| 359 | if did_lock: | 390 | return (success, project, start, finish) |
| 360 | lock.release() | ||
| 361 | finish = time.time() | ||
| 362 | self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK, | ||
| 363 | start, finish, success) | ||
| 364 | 391 | ||
| 365 | return success | 392 | @classmethod |
| 393 | def _FetchInitChild(cls, ssh_proxy): | ||
| 394 | cls.ssh_proxy = ssh_proxy | ||
| 366 | 395 | ||
| 367 | def _Fetch(self, projects, opt): | 396 | def _Fetch(self, projects, opt, err_event, ssh_proxy): |
| 397 | ret = True | ||
| 398 | |||
| 399 | jobs = opt.jobs_network if opt.jobs_network else self.jobs | ||
| 368 | fetched = set() | 400 | fetched = set() |
| 369 | lock = _threading.Lock() | 401 | pm = Progress('Fetching', len(projects), delay=False, quiet=opt.quiet) |
| 370 | pm = Progress('Fetching projects', len(projects), | ||
| 371 | always_print_percentage=opt.quiet) | ||
| 372 | 402 | ||
| 373 | objdir_project_map = dict() | 403 | objdir_project_map = dict() |
| 374 | for project in projects: | 404 | for project in projects: |
| 375 | objdir_project_map.setdefault(project.objdir, []).append(project) | 405 | objdir_project_map.setdefault(project.objdir, []).append(project) |
| 376 | 406 | projects_list = list(objdir_project_map.values()) | |
| 377 | threads = set() | 407 | |
| 378 | sem = _threading.Semaphore(self.jobs) | 408 | def _ProcessResults(results_sets): |
| 379 | err_event = _threading.Event() | 409 | ret = True |
| 380 | for project_list in objdir_project_map.values(): | 410 | for results in results_sets: |
| 381 | # Check for any errors before running any more tasks. | 411 | for (success, project, start, finish) in results: |
| 382 | # ...we'll let existing threads finish, though. | 412 | self._fetch_times.Set(project, finish - start) |
| 383 | if err_event.isSet() and opt.fail_fast: | 413 | self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK, |
| 384 | break | 414 | start, finish, success) |
| 385 | 415 | # Check for any errors before running any more tasks. | |
| 386 | sem.acquire() | 416 | # ...we'll let existing jobs finish, though. |
| 387 | kwargs = dict(opt=opt, | 417 | if not success: |
| 388 | projects=project_list, | 418 | ret = False |
| 389 | sem=sem, | 419 | else: |
| 390 | lock=lock, | 420 | fetched.add(project.gitdir) |
| 391 | fetched=fetched, | 421 | pm.update(msg=project.name) |
| 392 | pm=pm, | 422 | if not ret and opt.fail_fast: |
| 393 | err_event=err_event, | 423 | break |
| 394 | clone_filter=self.manifest.CloneFilter) | 424 | return ret |
| 395 | if self.jobs > 1: | 425 | |
| 396 | t = _threading.Thread(target = self._FetchProjectList, | 426 | # We pass the ssh proxy settings via the class. This allows multiprocessing |
| 397 | kwargs = kwargs) | 427 | # to pickle it up when spawning children. We can't pass it as an argument |
| 398 | # Ensure that Ctrl-C will not freeze the repo process. | 428 | # to _FetchProjectList below as multiprocessing is unable to pickle those. |
| 399 | t.daemon = True | 429 | Sync.ssh_proxy = None |
| 400 | threads.add(t) | 430 | |
| 401 | t.start() | 431 | # NB: Multiprocessing is heavy, so don't spin it up for one job. |
| 432 | if len(projects_list) == 1 or jobs == 1: | ||
| 433 | self._FetchInitChild(ssh_proxy) | ||
| 434 | if not _ProcessResults(self._FetchProjectList(opt, x) for x in projects_list): | ||
| 435 | ret = False | ||
| 436 | else: | ||
| 437 | # Favor throughput over responsiveness when quiet. It seems that imap() | ||
| 438 | # will yield results in batches relative to chunksize, so even as the | ||
| 439 | # children finish a sync, we won't see the result until one child finishes | ||
| 440 | # ~chunksize jobs. When using a large --jobs with large chunksize, this | ||
| 441 | # can be jarring as there will be a large initial delay where repo looks | ||
| 442 | # like it isn't doing anything and sits at 0%, but then suddenly completes | ||
| 443 | # a lot of jobs all at once. Since this code is more network bound, we | ||
| 444 | # can accept a bit more CPU overhead with a smaller chunksize so that the | ||
| 445 | # user sees more immediate & continuous feedback. | ||
| 446 | if opt.quiet: | ||
| 447 | chunksize = WORKER_BATCH_SIZE | ||
| 402 | else: | 448 | else: |
| 403 | self._FetchProjectList(**kwargs) | 449 | pm.update(inc=0, msg='warming up') |
| 404 | 450 | chunksize = 4 | |
| 405 | for t in threads: | 451 | with multiprocessing.Pool( |
| 406 | t.join() | 452 | jobs, initializer=self._FetchInitChild, initargs=(ssh_proxy,)) as pool: |
| 407 | 453 | results = pool.imap_unordered( | |
| 408 | # If we saw an error, exit with code 1 so that other scripts can check. | 454 | functools.partial(self._FetchProjectList, opt), |
| 409 | if err_event.isSet() and opt.fail_fast: | 455 | projects_list, |
| 410 | print('\nerror: Exited sync due to fetch errors', file=sys.stderr) | 456 | chunksize=chunksize) |
| 411 | sys.exit(1) | 457 | if not _ProcessResults(results): |
| 458 | ret = False | ||
| 459 | pool.close() | ||
| 460 | |||
| 461 | # Cleanup the reference now that we're done with it, and we're going to | ||
| 462 | # release any resources it points to. If we don't, later multiprocessing | ||
| 463 | # usage (e.g. checkouts) will try to pickle and then crash. | ||
| 464 | del Sync.ssh_proxy | ||
| 412 | 465 | ||
| 413 | pm.end() | 466 | pm.end() |
| 414 | self._fetch_times.Save() | 467 | self._fetch_times.Save() |
| 415 | 468 | ||
| 416 | if not self.manifest.IsArchive: | 469 | if not self.manifest.IsArchive: |
| 417 | self._GCProjects(projects) | 470 | self._GCProjects(projects, opt, err_event) |
| 418 | 471 | ||
| 419 | return fetched | 472 | return (ret, fetched) |
| 420 | 473 | ||
| 421 | def _CheckoutWorker(self, opt, sem, project, *args, **kwargs): | 474 | def _FetchMain(self, opt, args, all_projects, err_event, manifest_name, |
| 422 | """Main function of the fetch threads. | 475 | load_local_manifests, ssh_proxy): |
| 423 | 476 | """The main network fetch loop. | |
| 424 | Delegates most of the work to _CheckoutOne. | ||
| 425 | 477 | ||
| 426 | Args: | 478 | Args: |
| 427 | opt: Program options returned from optparse. See _Options(). | 479 | opt: Program options returned from optparse. See _Options(). |
| 428 | projects: Projects to fetch. | 480 | args: Command line args used to filter out projects. |
| 429 | sem: We'll release() this semaphore when we exit so that another thread | 481 | all_projects: List of all projects that should be fetched. |
| 430 | can be started up. | 482 | err_event: Whether an error was hit while processing. |
| 431 | *args, **kwargs: Remaining arguments to pass to _CheckoutOne. See the | 483 | manifest_name: Manifest file to be reloaded. |
| 432 | _CheckoutOne docstring for details. | 484 | load_local_manifests: Whether to load local manifests. |
| 485 | ssh_proxy: SSH manager for clients & masters. | ||
| 486 | |||
| 487 | Returns: | ||
| 488 | List of all projects that should be checked out. | ||
| 433 | """ | 489 | """ |
| 434 | try: | 490 | rp = self.manifest.repoProject |
| 435 | return self._CheckoutOne(opt, project, *args, **kwargs) | ||
| 436 | finally: | ||
| 437 | sem.release() | ||
| 438 | 491 | ||
| 439 | def _CheckoutOne(self, opt, project, lock, pm, err_event, err_results): | 492 | to_fetch = [] |
| 493 | now = time.time() | ||
| 494 | if _ONE_DAY_S <= (now - rp.LastFetch): | ||
| 495 | to_fetch.append(rp) | ||
| 496 | to_fetch.extend(all_projects) | ||
| 497 | to_fetch.sort(key=self._fetch_times.Get, reverse=True) | ||
| 498 | |||
| 499 | success, fetched = self._Fetch(to_fetch, opt, err_event, ssh_proxy) | ||
| 500 | if not success: | ||
| 501 | err_event.set() | ||
| 502 | |||
| 503 | _PostRepoFetch(rp, opt.repo_verify) | ||
| 504 | if opt.network_only: | ||
| 505 | # bail out now; the rest touches the working tree | ||
| 506 | if err_event.is_set(): | ||
| 507 | print('\nerror: Exited sync due to fetch errors.\n', file=sys.stderr) | ||
| 508 | sys.exit(1) | ||
| 509 | return | ||
| 510 | |||
| 511 | # Iteratively fetch missing and/or nested unregistered submodules | ||
| 512 | previously_missing_set = set() | ||
| 513 | while True: | ||
| 514 | self._ReloadManifest(manifest_name, load_local_manifests) | ||
| 515 | all_projects = self.GetProjects(args, | ||
| 516 | missing_ok=True, | ||
| 517 | submodules_ok=opt.fetch_submodules) | ||
| 518 | missing = [] | ||
| 519 | for project in all_projects: | ||
| 520 | if project.gitdir not in fetched: | ||
| 521 | missing.append(project) | ||
| 522 | if not missing: | ||
| 523 | break | ||
| 524 | # Stop us from non-stopped fetching actually-missing repos: If set of | ||
| 525 | # missing repos has not been changed from last fetch, we break. | ||
| 526 | missing_set = set(p.name for p in missing) | ||
| 527 | if previously_missing_set == missing_set: | ||
| 528 | break | ||
| 529 | previously_missing_set = missing_set | ||
| 530 | success, new_fetched = self._Fetch(missing, opt, err_event, ssh_proxy) | ||
| 531 | if not success: | ||
| 532 | err_event.set() | ||
| 533 | fetched.update(new_fetched) | ||
| 534 | |||
| 535 | return all_projects | ||
| 536 | |||
| 537 | def _CheckoutOne(self, detach_head, force_sync, project): | ||
| 440 | """Checkout work tree for one project | 538 | """Checkout work tree for one project |
| 441 | 539 | ||
| 442 | Args: | 540 | Args: |
| 443 | opt: Program options returned from optparse. See _Options(). | 541 | detach_head: Whether to leave a detached HEAD. |
| 542 | force_sync: Force checking out of the repo. | ||
| 444 | project: Project object for the project to checkout. | 543 | project: Project object for the project to checkout. |
| 445 | lock: Lock for accessing objects that are shared amongst multiple | ||
| 446 | _CheckoutWorker() threads. | ||
| 447 | pm: Instance of a Project object. We will call pm.update() (with our | ||
| 448 | lock held). | ||
| 449 | err_event: We'll set this event in the case of an error (after printing | ||
| 450 | out info about the error). | ||
| 451 | err_results: A list of strings, paths to git repos where checkout | ||
| 452 | failed. | ||
| 453 | 544 | ||
| 454 | Returns: | 545 | Returns: |
| 455 | Whether the fetch was successful. | 546 | Whether the fetch was successful. |
| 456 | """ | 547 | """ |
| 457 | # We'll set to true once we've locked the lock. | ||
| 458 | did_lock = False | ||
| 459 | |||
| 460 | # Encapsulate everything in a try/except/finally so that: | ||
| 461 | # - We always set err_event in the case of an exception. | ||
| 462 | # - We always make sure we unlock the lock if we locked it. | ||
| 463 | start = time.time() | 548 | start = time.time() |
| 464 | syncbuf = SyncBuffer(self.manifest.manifestProject.config, | 549 | syncbuf = SyncBuffer(self.manifest.manifestProject.config, |
| 465 | detach_head=opt.detach_head) | 550 | detach_head=detach_head) |
| 466 | success = False | 551 | success = False |
| 467 | try: | 552 | try: |
| 468 | try: | 553 | project.Sync_LocalHalf(syncbuf, force_sync=force_sync) |
| 469 | project.Sync_LocalHalf(syncbuf, force_sync=opt.force_sync) | 554 | success = syncbuf.Finish() |
| 470 | 555 | except GitError as e: | |
| 471 | # Lock around all the rest of the code, since printing, updating a set | 556 | print('error.GitError: Cannot checkout %s: %s' % |
| 472 | # and Progress.update() are not thread safe. | 557 | (project.name, str(e)), file=sys.stderr) |
| 473 | lock.acquire() | 558 | except Exception as e: |
| 474 | success = syncbuf.Finish() | 559 | print('error: Cannot checkout %s: %s: %s' % |
| 475 | did_lock = True | 560 | (project.name, type(e).__name__, str(e)), |
| 476 | 561 | file=sys.stderr) | |
| 477 | if not success: | 562 | raise |
| 478 | err_event.set() | ||
| 479 | print('error: Cannot checkout %s' % (project.name), | ||
| 480 | file=sys.stderr) | ||
| 481 | raise _CheckoutError() | ||
| 482 | |||
| 483 | pm.update(msg=project.name) | ||
| 484 | except _CheckoutError: | ||
| 485 | pass | ||
| 486 | except Exception as e: | ||
| 487 | print('error: Cannot checkout %s: %s: %s' % | ||
| 488 | (project.name, type(e).__name__, str(e)), | ||
| 489 | file=sys.stderr) | ||
| 490 | err_event.set() | ||
| 491 | raise | ||
| 492 | finally: | ||
| 493 | if did_lock: | ||
| 494 | if not success: | ||
| 495 | err_results.append(project.relpath) | ||
| 496 | lock.release() | ||
| 497 | finish = time.time() | ||
| 498 | self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL, | ||
| 499 | start, finish, success) | ||
| 500 | 563 | ||
| 501 | return success | 564 | if not success: |
| 565 | print('error: Cannot checkout %s' % (project.name), file=sys.stderr) | ||
| 566 | finish = time.time() | ||
| 567 | return (success, project, start, finish) | ||
| 502 | 568 | ||
| 503 | def _Checkout(self, all_projects, opt): | 569 | def _Checkout(self, all_projects, opt, err_results): |
| 504 | """Checkout projects listed in all_projects | 570 | """Checkout projects listed in all_projects |
| 505 | 571 | ||
| 506 | Args: | 572 | Args: |
| 507 | all_projects: List of all projects that should be checked out. | 573 | all_projects: List of all projects that should be checked out. |
| 508 | opt: Program options returned from optparse. See _Options(). | 574 | opt: Program options returned from optparse. See _Options(). |
| 575 | err_results: A list of strings, paths to git repos where checkout failed. | ||
| 509 | """ | 576 | """ |
| 577 | # Only checkout projects with worktrees. | ||
| 578 | all_projects = [x for x in all_projects if x.worktree] | ||
| 579 | |||
| 580 | def _ProcessResults(pool, pm, results): | ||
| 581 | ret = True | ||
| 582 | for (success, project, start, finish) in results: | ||
| 583 | self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL, | ||
| 584 | start, finish, success) | ||
| 585 | # Check for any errors before running any more tasks. | ||
| 586 | # ...we'll let existing jobs finish, though. | ||
| 587 | if not success: | ||
| 588 | ret = False | ||
| 589 | err_results.append(project.relpath) | ||
| 590 | if opt.fail_fast: | ||
| 591 | if pool: | ||
| 592 | pool.close() | ||
| 593 | return ret | ||
| 594 | pm.update(msg=project.name) | ||
| 595 | return ret | ||
| 510 | 596 | ||
| 511 | # Perform checkouts in multiple threads when we are using partial clone. | 597 | return self.ExecuteInParallel( |
| 512 | # Without partial clone, all needed git objects are already downloaded, | 598 | opt.jobs_checkout if opt.jobs_checkout else self.jobs, |
| 513 | # in this situation it's better to use only one process because the checkout | 599 | functools.partial(self._CheckoutOne, opt.detach_head, opt.force_sync), |
| 514 | # would be mostly disk I/O; with partial clone, the objects are only | 600 | all_projects, |
| 515 | # downloaded when demanded (at checkout time), which is similar to the | 601 | callback=_ProcessResults, |
| 516 | # Sync_NetworkHalf case and parallelism would be helpful. | 602 | output=Progress('Checking out', len(all_projects), quiet=opt.quiet)) and not err_results |
| 517 | if self.manifest.CloneFilter: | ||
| 518 | syncjobs = self.jobs | ||
| 519 | else: | ||
| 520 | syncjobs = 1 | ||
| 521 | |||
| 522 | lock = _threading.Lock() | ||
| 523 | pm = Progress('Checking out projects', len(all_projects)) | ||
| 524 | |||
| 525 | threads = set() | ||
| 526 | sem = _threading.Semaphore(syncjobs) | ||
| 527 | err_event = _threading.Event() | ||
| 528 | err_results = [] | ||
| 529 | |||
| 530 | for project in all_projects: | ||
| 531 | # Check for any errors before running any more tasks. | ||
| 532 | # ...we'll let existing threads finish, though. | ||
| 533 | if err_event.isSet() and opt.fail_fast: | ||
| 534 | break | ||
| 535 | |||
| 536 | sem.acquire() | ||
| 537 | if project.worktree: | ||
| 538 | kwargs = dict(opt=opt, | ||
| 539 | sem=sem, | ||
| 540 | project=project, | ||
| 541 | lock=lock, | ||
| 542 | pm=pm, | ||
| 543 | err_event=err_event, | ||
| 544 | err_results=err_results) | ||
| 545 | if syncjobs > 1: | ||
| 546 | t = _threading.Thread(target=self._CheckoutWorker, | ||
| 547 | kwargs=kwargs) | ||
| 548 | # Ensure that Ctrl-C will not freeze the repo process. | ||
| 549 | t.daemon = True | ||
| 550 | threads.add(t) | ||
| 551 | t.start() | ||
| 552 | else: | ||
| 553 | self._CheckoutWorker(**kwargs) | ||
| 554 | |||
| 555 | for t in threads: | ||
| 556 | t.join() | ||
| 557 | 603 | ||
| 558 | pm.end() | 604 | def _GCProjects(self, projects, opt, err_event): |
| 559 | # If we saw an error, exit with code 1 so that other scripts can check. | 605 | pm = Progress('Garbage collecting', len(projects), delay=False, quiet=opt.quiet) |
| 560 | if err_event.isSet(): | 606 | pm.update(inc=0, msg='prescan') |
| 561 | print('\nerror: Exited sync due to checkout errors', file=sys.stderr) | ||
| 562 | if err_results: | ||
| 563 | print('Failing repos:\n%s' % '\n'.join(err_results), | ||
| 564 | file=sys.stderr) | ||
| 565 | sys.exit(1) | ||
| 566 | 607 | ||
| 567 | def _GCProjects(self, projects): | ||
| 568 | gc_gitdirs = {} | 608 | gc_gitdirs = {} |
| 569 | for project in projects: | 609 | for project in projects: |
| 570 | if len(project.manifest.GetProjectsWithName(project.name)) > 1: | 610 | # Make sure pruning never kicks in with shared projects. |
| 571 | print('Shared project %s found, disabling pruning.' % project.name) | 611 | if (not project.use_git_worktrees and |
| 572 | project.bare_git.config('--replace-all', 'gc.pruneExpire', 'never') | 612 | len(project.manifest.GetProjectsWithName(project.name)) > 1): |
| 613 | if not opt.quiet: | ||
| 614 | print('\r%s: Shared project %s found, disabling pruning.' % | ||
| 615 | (project.relpath, project.name)) | ||
| 616 | if git_require((2, 7, 0)): | ||
| 617 | project.EnableRepositoryExtension('preciousObjects') | ||
| 618 | else: | ||
| 619 | # This isn't perfect, but it's the best we can do with old git. | ||
| 620 | print('\r%s: WARNING: shared projects are unreliable when using old ' | ||
| 621 | 'versions of git; please upgrade to git-2.7.0+.' | ||
| 622 | % (project.relpath,), | ||
| 623 | file=sys.stderr) | ||
| 624 | project.config.SetString('gc.pruneExpire', 'never') | ||
| 573 | gc_gitdirs[project.gitdir] = project.bare_git | 625 | gc_gitdirs[project.gitdir] = project.bare_git |
| 574 | 626 | ||
| 575 | has_dash_c = git_require((1, 7, 2)) | 627 | pm.update(inc=len(projects) - len(gc_gitdirs), msg='warming up') |
| 576 | if multiprocessing and has_dash_c: | 628 | |
| 577 | cpu_count = multiprocessing.cpu_count() | 629 | cpu_count = os.cpu_count() |
| 578 | else: | ||
| 579 | cpu_count = 1 | ||
| 580 | jobs = min(self.jobs, cpu_count) | 630 | jobs = min(self.jobs, cpu_count) |
| 581 | 631 | ||
| 582 | if jobs < 2: | 632 | if jobs < 2: |
| 583 | for bare_git in gc_gitdirs.values(): | 633 | for bare_git in gc_gitdirs.values(): |
| 634 | pm.update(msg=bare_git._project.name) | ||
| 584 | bare_git.gc('--auto') | 635 | bare_git.gc('--auto') |
| 636 | pm.end() | ||
| 585 | return | 637 | return |
| 586 | 638 | ||
| 587 | config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1} | 639 | config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1} |
| 588 | 640 | ||
| 589 | threads = set() | 641 | threads = set() |
| 590 | sem = _threading.Semaphore(jobs) | 642 | sem = _threading.Semaphore(jobs) |
| 591 | err_event = _threading.Event() | ||
| 592 | 643 | ||
| 593 | def GC(bare_git): | 644 | def GC(bare_git): |
| 645 | pm.start(bare_git._project.name) | ||
| 594 | try: | 646 | try: |
| 595 | try: | 647 | try: |
| 596 | bare_git.gc('--auto', config=config) | 648 | bare_git.gc('--auto', config=config) |
| 597 | except GitError: | 649 | except GitError: |
| 598 | err_event.set() | 650 | err_event.set() |
| 599 | except: | 651 | except Exception: |
| 600 | err_event.set() | 652 | err_event.set() |
| 601 | raise | 653 | raise |
| 602 | finally: | 654 | finally: |
| 655 | pm.finish(bare_git._project.name) | ||
| 603 | sem.release() | 656 | sem.release() |
| 604 | 657 | ||
| 605 | for bare_git in gc_gitdirs.values(): | 658 | for bare_git in gc_gitdirs.values(): |
| 606 | if err_event.isSet(): | 659 | if err_event.is_set() and opt.fail_fast: |
| 607 | break | 660 | break |
| 608 | sem.acquire() | 661 | sem.acquire() |
| 609 | t = _threading.Thread(target=GC, args=(bare_git,)) | 662 | t = _threading.Thread(target=GC, args=(bare_git,)) |
| @@ -613,84 +666,30 @@ later is required to fix a server side protocol bug. | |||
| 613 | 666 | ||
| 614 | for t in threads: | 667 | for t in threads: |
| 615 | t.join() | 668 | t.join() |
| 669 | pm.end() | ||
| 616 | 670 | ||
| 617 | if err_event.isSet(): | 671 | def _ReloadManifest(self, manifest_name=None, load_local_manifests=True): |
| 618 | print('\nerror: Exited sync due to gc errors', file=sys.stderr) | 672 | """Reload the manfiest from the file specified by the |manifest_name|. |
| 619 | sys.exit(1) | 673 | |
| 674 | It unloads the manifest if |manifest_name| is None. | ||
| 620 | 675 | ||
| 621 | def _ReloadManifest(self, manifest_name=None): | 676 | Args: |
| 677 | manifest_name: Manifest file to be reloaded. | ||
| 678 | load_local_manifests: Whether to load local manifests. | ||
| 679 | """ | ||
| 622 | if manifest_name: | 680 | if manifest_name: |
| 623 | # Override calls _Unload already | 681 | # Override calls _Unload already |
| 624 | self.manifest.Override(manifest_name) | 682 | self.manifest.Override(manifest_name, load_local_manifests=load_local_manifests) |
| 625 | else: | 683 | else: |
| 626 | self.manifest._Unload() | 684 | self.manifest._Unload() |
| 627 | 685 | ||
| 628 | def _DeleteProject(self, path): | ||
| 629 | print('Deleting obsolete path %s' % path, file=sys.stderr) | ||
| 630 | |||
| 631 | # Delete the .git directory first, so we're less likely to have a partially | ||
| 632 | # working git repository around. There shouldn't be any git projects here, | ||
| 633 | # so rmtree works. | ||
| 634 | try: | ||
| 635 | platform_utils.rmtree(os.path.join(path, '.git')) | ||
| 636 | except OSError as e: | ||
| 637 | print('Failed to remove %s (%s)' % (os.path.join(path, '.git'), str(e)), file=sys.stderr) | ||
| 638 | print('error: Failed to delete obsolete path %s' % path, file=sys.stderr) | ||
| 639 | print(' remove manually, then run sync again', file=sys.stderr) | ||
| 640 | return 1 | ||
| 641 | |||
| 642 | # Delete everything under the worktree, except for directories that contain | ||
| 643 | # another git project | ||
| 644 | dirs_to_remove = [] | ||
| 645 | failed = False | ||
| 646 | for root, dirs, files in platform_utils.walk(path): | ||
| 647 | for f in files: | ||
| 648 | try: | ||
| 649 | platform_utils.remove(os.path.join(root, f)) | ||
| 650 | except OSError as e: | ||
| 651 | print('Failed to remove %s (%s)' % (os.path.join(root, f), str(e)), file=sys.stderr) | ||
| 652 | failed = True | ||
| 653 | dirs[:] = [d for d in dirs | ||
| 654 | if not os.path.lexists(os.path.join(root, d, '.git'))] | ||
| 655 | dirs_to_remove += [os.path.join(root, d) for d in dirs | ||
| 656 | if os.path.join(root, d) not in dirs_to_remove] | ||
| 657 | for d in reversed(dirs_to_remove): | ||
| 658 | if platform_utils.islink(d): | ||
| 659 | try: | ||
| 660 | platform_utils.remove(d) | ||
| 661 | except OSError as e: | ||
| 662 | print('Failed to remove %s (%s)' % (os.path.join(root, d), str(e)), file=sys.stderr) | ||
| 663 | failed = True | ||
| 664 | elif len(platform_utils.listdir(d)) == 0: | ||
| 665 | try: | ||
| 666 | platform_utils.rmdir(d) | ||
| 667 | except OSError as e: | ||
| 668 | print('Failed to remove %s (%s)' % (os.path.join(root, d), str(e)), file=sys.stderr) | ||
| 669 | failed = True | ||
| 670 | continue | ||
| 671 | if failed: | ||
| 672 | print('error: Failed to delete obsolete path %s' % path, file=sys.stderr) | ||
| 673 | print(' remove manually, then run sync again', file=sys.stderr) | ||
| 674 | return 1 | ||
| 675 | |||
| 676 | # Try deleting parent dirs if they are empty | ||
| 677 | project_dir = path | ||
| 678 | while project_dir != self.manifest.topdir: | ||
| 679 | if len(platform_utils.listdir(project_dir)) == 0: | ||
| 680 | platform_utils.rmdir(project_dir) | ||
| 681 | else: | ||
| 682 | break | ||
| 683 | project_dir = os.path.dirname(project_dir) | ||
| 684 | |||
| 685 | return 0 | ||
| 686 | |||
| 687 | def UpdateProjectList(self, opt): | 686 | def UpdateProjectList(self, opt): |
| 688 | new_project_paths = [] | 687 | new_project_paths = [] |
| 689 | for project in self.GetProjects(None, missing_ok=True): | 688 | for project in self.GetProjects(None, missing_ok=True): |
| 690 | if project.relpath: | 689 | if project.relpath: |
| 691 | new_project_paths.append(project.relpath) | 690 | new_project_paths.append(project.relpath) |
| 692 | file_name = 'project.list' | 691 | file_name = 'project.list' |
| 693 | file_path = os.path.join(self.manifest.repodir, file_name) | 692 | file_path = os.path.join(self.repodir, file_name) |
| 694 | old_project_paths = [] | 693 | old_project_paths = [] |
| 695 | 694 | ||
| 696 | if os.path.exists(file_path): | 695 | if os.path.exists(file_path): |
| @@ -705,28 +704,20 @@ later is required to fix a server side protocol bug. | |||
| 705 | gitdir = os.path.join(self.manifest.topdir, path, '.git') | 704 | gitdir = os.path.join(self.manifest.topdir, path, '.git') |
| 706 | if os.path.exists(gitdir): | 705 | if os.path.exists(gitdir): |
| 707 | project = Project( | 706 | project = Project( |
| 708 | manifest = self.manifest, | 707 | manifest=self.manifest, |
| 709 | name = path, | 708 | name=path, |
| 710 | remote = RemoteSpec('origin'), | 709 | remote=RemoteSpec('origin'), |
| 711 | gitdir = gitdir, | 710 | gitdir=gitdir, |
| 712 | objdir = gitdir, | 711 | objdir=gitdir, |
| 713 | worktree = os.path.join(self.manifest.topdir, path), | 712 | use_git_worktrees=os.path.isfile(gitdir), |
| 714 | relpath = path, | 713 | worktree=os.path.join(self.manifest.topdir, path), |
| 715 | revisionExpr = 'HEAD', | 714 | relpath=path, |
| 716 | revisionId = None, | 715 | revisionExpr='HEAD', |
| 717 | groups = None) | 716 | revisionId=None, |
| 718 | 717 | groups=None) | |
| 719 | if project.IsDirty() and opt.force_remove_dirty: | 718 | if not project.DeleteWorktree( |
| 720 | print('WARNING: Removing dirty project "%s": uncommitted changes ' | 719 | quiet=opt.quiet, |
| 721 | 'erased' % project.relpath, file=sys.stderr) | 720 | force=opt.force_remove_dirty): |
| 722 | self._DeleteProject(project.worktree) | ||
| 723 | elif project.IsDirty(): | ||
| 724 | print('error: Cannot remove project "%s": uncommitted changes ' | ||
| 725 | 'are present' % project.relpath, file=sys.stderr) | ||
| 726 | print(' commit changes, then run sync again', | ||
| 727 | file=sys.stderr) | ||
| 728 | return 1 | ||
| 729 | elif self._DeleteProject(project.worktree): | ||
| 730 | return 1 | 721 | return 1 |
| 731 | 722 | ||
| 732 | new_project_paths.sort() | 723 | new_project_paths.sort() |
| @@ -735,6 +726,56 @@ later is required to fix a server side protocol bug. | |||
| 735 | fd.write('\n') | 726 | fd.write('\n') |
| 736 | return 0 | 727 | return 0 |
| 737 | 728 | ||
| 729 | def UpdateCopyLinkfileList(self): | ||
| 730 | """Save all dests of copyfile and linkfile, and update them if needed. | ||
| 731 | |||
| 732 | Returns: | ||
| 733 | Whether update was successful. | ||
| 734 | """ | ||
| 735 | new_paths = {} | ||
| 736 | new_linkfile_paths = [] | ||
| 737 | new_copyfile_paths = [] | ||
| 738 | for project in self.GetProjects(None, missing_ok=True): | ||
| 739 | new_linkfile_paths.extend(x.dest for x in project.linkfiles) | ||
| 740 | new_copyfile_paths.extend(x.dest for x in project.copyfiles) | ||
| 741 | |||
| 742 | new_paths = { | ||
| 743 | 'linkfile': new_linkfile_paths, | ||
| 744 | 'copyfile': new_copyfile_paths, | ||
| 745 | } | ||
| 746 | |||
| 747 | copylinkfile_name = 'copy-link-files.json' | ||
| 748 | copylinkfile_path = os.path.join(self.manifest.repodir, copylinkfile_name) | ||
| 749 | old_copylinkfile_paths = {} | ||
| 750 | |||
| 751 | if os.path.exists(copylinkfile_path): | ||
| 752 | with open(copylinkfile_path, 'rb') as fp: | ||
| 753 | try: | ||
| 754 | old_copylinkfile_paths = json.load(fp) | ||
| 755 | except: | ||
| 756 | print('error: %s is not a json formatted file.' % | ||
| 757 | copylinkfile_path, file=sys.stderr) | ||
| 758 | platform_utils.remove(copylinkfile_path) | ||
| 759 | return False | ||
| 760 | |||
| 761 | need_remove_files = [] | ||
| 762 | need_remove_files.extend( | ||
| 763 | set(old_copylinkfile_paths.get('linkfile', [])) - | ||
| 764 | set(new_linkfile_paths)) | ||
| 765 | need_remove_files.extend( | ||
| 766 | set(old_copylinkfile_paths.get('copyfile', [])) - | ||
| 767 | set(new_copyfile_paths)) | ||
| 768 | |||
| 769 | for need_remove_file in need_remove_files: | ||
| 770 | # Try to remove the updated copyfile or linkfile. | ||
| 771 | # So, if the file is not exist, nothing need to do. | ||
| 772 | platform_utils.remove(need_remove_file, missing_ok=True) | ||
| 773 | |||
| 774 | # Create copy-link-files.json, save dest path of "copyfile" and "linkfile". | ||
| 775 | with open(copylinkfile_path, 'w', encoding='utf-8') as fp: | ||
| 776 | json.dump(new_paths, fp) | ||
| 777 | return True | ||
| 778 | |||
| 738 | def _SmartSyncSetup(self, opt, smart_sync_manifest_path): | 779 | def _SmartSyncSetup(self, opt, smart_sync_manifest_path): |
| 739 | if not self.manifest.manifest_server: | 780 | if not self.manifest.manifest_server: |
| 740 | print('error: cannot smart sync: no manifest server defined in ' | 781 | print('error: cannot smart sync: no manifest server defined in ' |
| @@ -745,7 +786,7 @@ later is required to fix a server side protocol bug. | |||
| 745 | if not opt.quiet: | 786 | if not opt.quiet: |
| 746 | print('Using manifest server %s' % manifest_server) | 787 | print('Using manifest server %s' % manifest_server) |
| 747 | 788 | ||
| 748 | if not '@' in manifest_server: | 789 | if '@' not in manifest_server: |
| 749 | username = None | 790 | username = None |
| 750 | password = None | 791 | password = None |
| 751 | if opt.manifest_server_username and opt.manifest_server_password: | 792 | if opt.manifest_server_username and opt.manifest_server_password: |
| @@ -782,19 +823,15 @@ later is required to fix a server side protocol bug. | |||
| 782 | try: | 823 | try: |
| 783 | server = xmlrpc.client.Server(manifest_server, transport=transport) | 824 | server = xmlrpc.client.Server(manifest_server, transport=transport) |
| 784 | if opt.smart_sync: | 825 | if opt.smart_sync: |
| 785 | p = self.manifest.manifestProject | 826 | branch = self._GetBranch() |
| 786 | b = p.GetBranch(p.CurrentBranch) | 827 | |
| 787 | branch = b.merge | 828 | if 'SYNC_TARGET' in os.environ: |
| 788 | if branch.startswith(R_HEADS): | 829 | target = os.environ['SYNC_TARGET'] |
| 789 | branch = branch[len(R_HEADS):] | ||
| 790 | |||
| 791 | env = os.environ.copy() | ||
| 792 | if 'SYNC_TARGET' in env: | ||
| 793 | target = env['SYNC_TARGET'] | ||
| 794 | [success, manifest_str] = server.GetApprovedManifest(branch, target) | 830 | [success, manifest_str] = server.GetApprovedManifest(branch, target) |
| 795 | elif 'TARGET_PRODUCT' in env and 'TARGET_BUILD_VARIANT' in env: | 831 | elif ('TARGET_PRODUCT' in os.environ and |
| 796 | target = '%s-%s' % (env['TARGET_PRODUCT'], | 832 | 'TARGET_BUILD_VARIANT' in os.environ): |
| 797 | env['TARGET_BUILD_VARIANT']) | 833 | target = '%s-%s' % (os.environ['TARGET_PRODUCT'], |
| 834 | os.environ['TARGET_BUILD_VARIANT']) | ||
| 798 | [success, manifest_str] = server.GetApprovedManifest(branch, target) | 835 | [success, manifest_str] = server.GetApprovedManifest(branch, target) |
| 799 | else: | 836 | else: |
| 800 | [success, manifest_str] = server.GetApprovedManifest(branch) | 837 | [success, manifest_str] = server.GetApprovedManifest(branch) |
| @@ -833,12 +870,15 @@ later is required to fix a server side protocol bug. | |||
| 833 | """Fetch & update the local manifest project.""" | 870 | """Fetch & update the local manifest project.""" |
| 834 | if not opt.local_only: | 871 | if not opt.local_only: |
| 835 | start = time.time() | 872 | start = time.time() |
| 836 | success = mp.Sync_NetworkHalf(quiet=opt.quiet, | 873 | success = mp.Sync_NetworkHalf(quiet=opt.quiet, verbose=opt.verbose, |
| 837 | current_branch_only=opt.current_branch_only, | 874 | current_branch_only=self._GetCurrentBranchOnly(opt), |
| 838 | no_tags=opt.no_tags, | 875 | force_sync=opt.force_sync, |
| 876 | tags=opt.tags, | ||
| 839 | optimized_fetch=opt.optimized_fetch, | 877 | optimized_fetch=opt.optimized_fetch, |
| 878 | retry_fetches=opt.retry_fetches, | ||
| 840 | submodules=self.manifest.HasSubmodules, | 879 | submodules=self.manifest.HasSubmodules, |
| 841 | clone_filter=self.manifest.CloneFilter) | 880 | clone_filter=self.manifest.CloneFilter, |
| 881 | partial_clone_exclude=self.manifest.PartialCloneExclude) | ||
| 842 | finish = time.time() | 882 | finish = time.time() |
| 843 | self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK, | 883 | self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK, |
| 844 | start, finish, success) | 884 | start, finish, success) |
| @@ -852,7 +892,7 @@ later is required to fix a server side protocol bug. | |||
| 852 | start, time.time(), clean) | 892 | start, time.time(), clean) |
| 853 | if not clean: | 893 | if not clean: |
| 854 | sys.exit(1) | 894 | sys.exit(1) |
| 855 | self._ReloadManifest(opt.manifest_name) | 895 | self._ReloadManifest(manifest_name) |
| 856 | if opt.jobs is None: | 896 | if opt.jobs is None: |
| 857 | self.jobs = self.manifest.default.sync_j | 897 | self.jobs = self.manifest.default.sync_j |
| 858 | 898 | ||
| @@ -886,7 +926,10 @@ later is required to fix a server side protocol bug. | |||
| 886 | 926 | ||
| 887 | manifest_name = opt.manifest_name | 927 | manifest_name = opt.manifest_name |
| 888 | smart_sync_manifest_path = os.path.join( | 928 | smart_sync_manifest_path = os.path.join( |
| 889 | self.manifest.manifestProject.worktree, 'smart_sync_override.xml') | 929 | self.manifest.manifestProject.worktree, 'smart_sync_override.xml') |
| 930 | |||
| 931 | if opt.clone_bundle is None: | ||
| 932 | opt.clone_bundle = self.manifest.CloneBundle | ||
| 890 | 933 | ||
| 891 | if opt.smart_sync or opt.smart_tag: | 934 | if opt.smart_sync or opt.smart_tag: |
| 892 | manifest_name = self._SmartSyncSetup(opt, smart_sync_manifest_path) | 935 | manifest_name = self._SmartSyncSetup(opt, smart_sync_manifest_path) |
| @@ -898,8 +941,17 @@ later is required to fix a server side protocol bug. | |||
| 898 | print('error: failed to remove existing smart sync override manifest: %s' % | 941 | print('error: failed to remove existing smart sync override manifest: %s' % |
| 899 | e, file=sys.stderr) | 942 | e, file=sys.stderr) |
| 900 | 943 | ||
| 944 | err_event = multiprocessing.Event() | ||
| 945 | |||
| 901 | rp = self.manifest.repoProject | 946 | rp = self.manifest.repoProject |
| 902 | rp.PreSync() | 947 | rp.PreSync() |
| 948 | cb = rp.CurrentBranch | ||
| 949 | if cb: | ||
| 950 | base = rp.GetBranch(cb).merge | ||
| 951 | if not base or not base.startswith('refs/heads/'): | ||
| 952 | print('warning: repo is not tracking a remote branch, so it will not ' | ||
| 953 | 'receive updates; run `repo init --repo-rev=stable` to fix.', | ||
| 954 | file=sys.stderr) | ||
| 903 | 955 | ||
| 904 | mp = self.manifest.manifestProject | 956 | mp = self.manifest.manifestProject |
| 905 | mp.PreSync() | 957 | mp.PreSync() |
| @@ -907,7 +959,21 @@ later is required to fix a server side protocol bug. | |||
| 907 | if opt.repo_upgraded: | 959 | if opt.repo_upgraded: |
| 908 | _PostRepoUpgrade(self.manifest, quiet=opt.quiet) | 960 | _PostRepoUpgrade(self.manifest, quiet=opt.quiet) |
| 909 | 961 | ||
| 910 | self._UpdateManifestProject(opt, mp, manifest_name) | 962 | if not opt.mp_update: |
| 963 | print('Skipping update of local manifest project.') | ||
| 964 | else: | ||
| 965 | self._UpdateManifestProject(opt, mp, manifest_name) | ||
| 966 | |||
| 967 | load_local_manifests = not self.manifest.HasLocalManifests | ||
| 968 | use_superproject = git_superproject.UseSuperproject(opt, self.manifest) | ||
| 969 | superproject_logging_data = { | ||
| 970 | 'superproject': use_superproject, | ||
| 971 | 'haslocalmanifests': bool(self.manifest.HasLocalManifests), | ||
| 972 | 'hassuperprojecttag': bool(self.manifest.superproject), | ||
| 973 | } | ||
| 974 | if use_superproject: | ||
| 975 | manifest_name = self._UpdateProjectsRevisionId( | ||
| 976 | opt, args, load_local_manifests, superproject_logging_data) or opt.manifest_name | ||
| 911 | 977 | ||
| 912 | if self.gitc_manifest: | 978 | if self.gitc_manifest: |
| 913 | gitc_manifest_projects = self.GetProjects(args, | 979 | gitc_manifest_projects = self.GetProjects(args, |
| @@ -948,56 +1014,92 @@ later is required to fix a server side protocol bug. | |||
| 948 | missing_ok=True, | 1014 | missing_ok=True, |
| 949 | submodules_ok=opt.fetch_submodules) | 1015 | submodules_ok=opt.fetch_submodules) |
| 950 | 1016 | ||
| 1017 | err_network_sync = False | ||
| 1018 | err_update_projects = False | ||
| 1019 | |||
| 951 | self._fetch_times = _FetchTimes(self.manifest) | 1020 | self._fetch_times = _FetchTimes(self.manifest) |
| 952 | if not opt.local_only: | 1021 | if not opt.local_only: |
| 953 | to_fetch = [] | 1022 | with multiprocessing.Manager() as manager: |
| 954 | now = time.time() | 1023 | with ssh.ProxyManager(manager) as ssh_proxy: |
| 955 | if _ONE_DAY_S <= (now - rp.LastFetch): | 1024 | # Initialize the socket dir once in the parent. |
| 956 | to_fetch.append(rp) | 1025 | ssh_proxy.sock() |
| 957 | to_fetch.extend(all_projects) | 1026 | all_projects = self._FetchMain(opt, args, all_projects, err_event, |
| 958 | to_fetch.sort(key=self._fetch_times.Get, reverse=True) | 1027 | manifest_name, load_local_manifests, |
| 959 | 1028 | ssh_proxy) | |
| 960 | fetched = self._Fetch(to_fetch, opt) | 1029 | |
| 961 | _PostRepoFetch(rp, opt.no_repo_verify) | ||
| 962 | if opt.network_only: | 1030 | if opt.network_only: |
| 963 | # bail out now; the rest touches the working tree | ||
| 964 | return | 1031 | return |
| 965 | 1032 | ||
| 966 | # Iteratively fetch missing and/or nested unregistered submodules | 1033 | # If we saw an error, exit with code 1 so that other scripts can check. |
| 967 | previously_missing_set = set() | 1034 | if err_event.is_set(): |
| 968 | while True: | 1035 | err_network_sync = True |
| 969 | self._ReloadManifest(manifest_name) | 1036 | if opt.fail_fast: |
| 970 | all_projects = self.GetProjects(args, | 1037 | print('\nerror: Exited sync due to fetch errors.\n' |
| 971 | missing_ok=True, | 1038 | 'Local checkouts *not* updated. Resolve network issues & ' |
| 972 | submodules_ok=opt.fetch_submodules) | 1039 | 'retry.\n' |
| 973 | missing = [] | 1040 | '`repo sync -l` will update some local checkouts.', |
| 974 | for project in all_projects: | 1041 | file=sys.stderr) |
| 975 | if project.gitdir not in fetched: | 1042 | sys.exit(1) |
| 976 | missing.append(project) | ||
| 977 | if not missing: | ||
| 978 | break | ||
| 979 | # Stop us from non-stopped fetching actually-missing repos: If set of | ||
| 980 | # missing repos has not been changed from last fetch, we break. | ||
| 981 | missing_set = set(p.name for p in missing) | ||
| 982 | if previously_missing_set == missing_set: | ||
| 983 | break | ||
| 984 | previously_missing_set = missing_set | ||
| 985 | fetched.update(self._Fetch(missing, opt)) | ||
| 986 | 1043 | ||
| 987 | if self.manifest.IsMirror or self.manifest.IsArchive: | 1044 | if self.manifest.IsMirror or self.manifest.IsArchive: |
| 988 | # bail out now, we have no working tree | 1045 | # bail out now, we have no working tree |
| 989 | return | 1046 | return |
| 990 | 1047 | ||
| 991 | if self.UpdateProjectList(opt): | 1048 | if self.UpdateProjectList(opt): |
| 992 | sys.exit(1) | 1049 | err_event.set() |
| 1050 | err_update_projects = True | ||
| 1051 | if opt.fail_fast: | ||
| 1052 | print('\nerror: Local checkouts *not* updated.', file=sys.stderr) | ||
| 1053 | sys.exit(1) | ||
| 993 | 1054 | ||
| 994 | self._Checkout(all_projects, opt) | 1055 | err_update_linkfiles = not self.UpdateCopyLinkfileList() |
| 1056 | if err_update_linkfiles: | ||
| 1057 | err_event.set() | ||
| 1058 | if opt.fail_fast: | ||
| 1059 | print('\nerror: Local update copyfile or linkfile failed.', file=sys.stderr) | ||
| 1060 | sys.exit(1) | ||
| 1061 | |||
| 1062 | err_results = [] | ||
| 1063 | # NB: We don't exit here because this is the last step. | ||
| 1064 | err_checkout = not self._Checkout(all_projects, opt, err_results) | ||
| 1065 | if err_checkout: | ||
| 1066 | err_event.set() | ||
| 995 | 1067 | ||
| 996 | # If there's a notice that's supposed to print at the end of the sync, print | 1068 | # If there's a notice that's supposed to print at the end of the sync, print |
| 997 | # it now... | 1069 | # it now... |
| 998 | if self.manifest.notice: | 1070 | if self.manifest.notice: |
| 999 | print(self.manifest.notice) | 1071 | print(self.manifest.notice) |
| 1000 | 1072 | ||
| 1073 | # If we saw an error, exit with code 1 so that other scripts can check. | ||
| 1074 | if err_event.is_set(): | ||
| 1075 | print('\nerror: Unable to fully sync the tree.', file=sys.stderr) | ||
| 1076 | if err_network_sync: | ||
| 1077 | print('error: Downloading network changes failed.', file=sys.stderr) | ||
| 1078 | if err_update_projects: | ||
| 1079 | print('error: Updating local project lists failed.', file=sys.stderr) | ||
| 1080 | if err_update_linkfiles: | ||
| 1081 | print('error: Updating copyfiles or linkfiles failed.', file=sys.stderr) | ||
| 1082 | if err_checkout: | ||
| 1083 | print('error: Checking out local projects failed.', file=sys.stderr) | ||
| 1084 | if err_results: | ||
| 1085 | print('Failing repos:\n%s' % '\n'.join(err_results), file=sys.stderr) | ||
| 1086 | print('Try re-running with "-j1 --fail-fast" to exit at the first error.', | ||
| 1087 | file=sys.stderr) | ||
| 1088 | sys.exit(1) | ||
| 1089 | |||
| 1090 | # Log the previous sync analysis state from the config. | ||
| 1091 | self.git_event_log.LogDataConfigEvents(mp.config.GetSyncAnalysisStateData(), | ||
| 1092 | 'previous_sync_state') | ||
| 1093 | |||
| 1094 | # Update and log with the new sync analysis state. | ||
| 1095 | mp.config.UpdateSyncAnalysisState(opt, superproject_logging_data) | ||
| 1096 | self.git_event_log.LogDataConfigEvents(mp.config.GetSyncAnalysisStateData(), | ||
| 1097 | 'current_sync_state') | ||
| 1098 | |||
| 1099 | if not opt.quiet: | ||
| 1100 | print('repo sync has finished successfully.') | ||
| 1101 | |||
| 1102 | |||
| 1001 | def _PostRepoUpgrade(manifest, quiet=False): | 1103 | def _PostRepoUpgrade(manifest, quiet=False): |
| 1002 | wrapper = Wrapper() | 1104 | wrapper = Wrapper() |
| 1003 | if wrapper.NeedSetupGnuPG(): | 1105 | if wrapper.NeedSetupGnuPG(): |
| @@ -1006,15 +1108,29 @@ def _PostRepoUpgrade(manifest, quiet=False): | |||
| 1006 | if project.Exists: | 1108 | if project.Exists: |
| 1007 | project.PostRepoUpgrade() | 1109 | project.PostRepoUpgrade() |
| 1008 | 1110 | ||
| 1009 | def _PostRepoFetch(rp, no_repo_verify=False, verbose=False): | 1111 | |
| 1112 | def _PostRepoFetch(rp, repo_verify=True, verbose=False): | ||
| 1010 | if rp.HasChanges: | 1113 | if rp.HasChanges: |
| 1011 | print('info: A new version of repo is available', file=sys.stderr) | 1114 | print('info: A new version of repo is available', file=sys.stderr) |
| 1012 | print(file=sys.stderr) | 1115 | wrapper = Wrapper() |
| 1013 | if no_repo_verify or _VerifyTag(rp): | 1116 | try: |
| 1014 | syncbuf = SyncBuffer(rp.config) | 1117 | rev = rp.bare_git.describe(rp.GetRevisionId()) |
| 1015 | rp.Sync_LocalHalf(syncbuf) | 1118 | except GitError: |
| 1016 | if not syncbuf.Finish(): | 1119 | rev = None |
| 1017 | sys.exit(1) | 1120 | _, new_rev = wrapper.check_repo_rev(rp.gitdir, rev, repo_verify=repo_verify) |
| 1121 | # See if we're held back due to missing signed tag. | ||
| 1122 | current_revid = rp.bare_git.rev_parse('HEAD') | ||
| 1123 | new_revid = rp.bare_git.rev_parse('--verify', new_rev) | ||
| 1124 | if current_revid != new_revid: | ||
| 1125 | # We want to switch to the new rev, but also not trash any uncommitted | ||
| 1126 | # changes. This helps with local testing/hacking. | ||
| 1127 | # If a local change has been made, we will throw that away. | ||
| 1128 | # We also have to make sure this will switch to an older commit if that's | ||
| 1129 | # the latest tag in order to support release rollback. | ||
| 1130 | try: | ||
| 1131 | rp.work_git.reset('--keep', new_rev) | ||
| 1132 | except GitError as e: | ||
| 1133 | sys.exit(str(e)) | ||
| 1018 | print('info: Restarting repo with latest version', file=sys.stderr) | 1134 | print('info: Restarting repo with latest version', file=sys.stderr) |
| 1019 | raise RepoChangedException(['--repo-upgraded']) | 1135 | raise RepoChangedException(['--repo-upgraded']) |
| 1020 | else: | 1136 | else: |
| @@ -1024,53 +1140,6 @@ def _PostRepoFetch(rp, no_repo_verify=False, verbose=False): | |||
| 1024 | print('repo version %s is current' % rp.work_git.describe(HEAD), | 1140 | print('repo version %s is current' % rp.work_git.describe(HEAD), |
| 1025 | file=sys.stderr) | 1141 | file=sys.stderr) |
| 1026 | 1142 | ||
| 1027 | def _VerifyTag(project): | ||
| 1028 | gpg_dir = os.path.expanduser('~/.repoconfig/gnupg') | ||
| 1029 | if not os.path.exists(gpg_dir): | ||
| 1030 | print('warning: GnuPG was not available during last "repo init"\n' | ||
| 1031 | 'warning: Cannot automatically authenticate repo."""', | ||
| 1032 | file=sys.stderr) | ||
| 1033 | return True | ||
| 1034 | |||
| 1035 | try: | ||
| 1036 | cur = project.bare_git.describe(project.GetRevisionId()) | ||
| 1037 | except GitError: | ||
| 1038 | cur = None | ||
| 1039 | |||
| 1040 | if not cur \ | ||
| 1041 | or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur): | ||
| 1042 | rev = project.revisionExpr | ||
| 1043 | if rev.startswith(R_HEADS): | ||
| 1044 | rev = rev[len(R_HEADS):] | ||
| 1045 | |||
| 1046 | print(file=sys.stderr) | ||
| 1047 | print("warning: project '%s' branch '%s' is not signed" | ||
| 1048 | % (project.name, rev), file=sys.stderr) | ||
| 1049 | return False | ||
| 1050 | |||
| 1051 | env = os.environ.copy() | ||
| 1052 | env['GIT_DIR'] = project.gitdir.encode() | ||
| 1053 | env['GNUPGHOME'] = gpg_dir.encode() | ||
| 1054 | |||
| 1055 | cmd = [GIT, 'tag', '-v', cur] | ||
| 1056 | proc = subprocess.Popen(cmd, | ||
| 1057 | stdout = subprocess.PIPE, | ||
| 1058 | stderr = subprocess.PIPE, | ||
| 1059 | env = env) | ||
| 1060 | out = proc.stdout.read() | ||
| 1061 | proc.stdout.close() | ||
| 1062 | |||
| 1063 | err = proc.stderr.read() | ||
| 1064 | proc.stderr.close() | ||
| 1065 | |||
| 1066 | if proc.wait() != 0: | ||
| 1067 | print(file=sys.stderr) | ||
| 1068 | print(out, file=sys.stderr) | ||
| 1069 | print(err, file=sys.stderr) | ||
| 1070 | print(file=sys.stderr) | ||
| 1071 | return False | ||
| 1072 | return True | ||
| 1073 | |||
| 1074 | 1143 | ||
| 1075 | class _FetchTimes(object): | 1144 | class _FetchTimes(object): |
| 1076 | _ALPHA = 0.5 | 1145 | _ALPHA = 0.5 |
| @@ -1090,7 +1159,7 @@ class _FetchTimes(object): | |||
| 1090 | old = self._times.get(name, t) | 1159 | old = self._times.get(name, t) |
| 1091 | self._seen.add(name) | 1160 | self._seen.add(name) |
| 1092 | a = self._ALPHA | 1161 | a = self._ALPHA |
| 1093 | self._times[name] = (a*t) + ((1-a) * old) | 1162 | self._times[name] = (a * t) + ((1 - a) * old) |
| 1094 | 1163 | ||
| 1095 | def _Load(self): | 1164 | def _Load(self): |
| 1096 | if self._times is None: | 1165 | if self._times is None: |
| @@ -1098,10 +1167,7 @@ class _FetchTimes(object): | |||
| 1098 | with open(self._path) as f: | 1167 | with open(self._path) as f: |
| 1099 | self._times = json.load(f) | 1168 | self._times = json.load(f) |
| 1100 | except (IOError, ValueError): | 1169 | except (IOError, ValueError): |
| 1101 | try: | 1170 | platform_utils.remove(self._path, missing_ok=True) |
| 1102 | platform_utils.remove(self._path) | ||
| 1103 | except OSError: | ||
| 1104 | pass | ||
| 1105 | self._times = {} | 1171 | self._times = {} |
| 1106 | 1172 | ||
| 1107 | def Save(self): | 1173 | def Save(self): |
| @@ -1119,15 +1185,14 @@ class _FetchTimes(object): | |||
| 1119 | with open(self._path, 'w') as f: | 1185 | with open(self._path, 'w') as f: |
| 1120 | json.dump(self._times, f, indent=2) | 1186 | json.dump(self._times, f, indent=2) |
| 1121 | except (IOError, TypeError): | 1187 | except (IOError, TypeError): |
| 1122 | try: | 1188 | platform_utils.remove(self._path, missing_ok=True) |
| 1123 | platform_utils.remove(self._path) | ||
| 1124 | except OSError: | ||
| 1125 | pass | ||
| 1126 | 1189 | ||
| 1127 | # This is a replacement for xmlrpc.client.Transport using urllib2 | 1190 | # This is a replacement for xmlrpc.client.Transport using urllib2 |
| 1128 | # and supporting persistent-http[s]. It cannot change hosts from | 1191 | # and supporting persistent-http[s]. It cannot change hosts from |
| 1129 | # request to request like the normal transport, the real url | 1192 | # request to request like the normal transport, the real url |
| 1130 | # is passed during initialization. | 1193 | # is passed during initialization. |
| 1194 | |||
| 1195 | |||
| 1131 | class PersistentTransport(xmlrpc.client.Transport): | 1196 | class PersistentTransport(xmlrpc.client.Transport): |
| 1132 | def __init__(self, orig_host): | 1197 | def __init__(self, orig_host): |
| 1133 | self.orig_host = orig_host | 1198 | self.orig_host = orig_host |
| @@ -1138,7 +1203,7 @@ class PersistentTransport(xmlrpc.client.Transport): | |||
| 1138 | # Since we're only using them for HTTP, copy the file temporarily, | 1203 | # Since we're only using them for HTTP, copy the file temporarily, |
| 1139 | # stripping those prefixes away. | 1204 | # stripping those prefixes away. |
| 1140 | if cookiefile: | 1205 | if cookiefile: |
| 1141 | tmpcookiefile = tempfile.NamedTemporaryFile() | 1206 | tmpcookiefile = tempfile.NamedTemporaryFile(mode='w') |
| 1142 | tmpcookiefile.write("# HTTP Cookie File") | 1207 | tmpcookiefile.write("# HTTP Cookie File") |
| 1143 | try: | 1208 | try: |
| 1144 | with open(cookiefile) as f: | 1209 | with open(cookiefile) as f: |
| @@ -1162,7 +1227,7 @@ class PersistentTransport(xmlrpc.client.Transport): | |||
| 1162 | if proxy: | 1227 | if proxy: |
| 1163 | proxyhandler = urllib.request.ProxyHandler({ | 1228 | proxyhandler = urllib.request.ProxyHandler({ |
| 1164 | "http": proxy, | 1229 | "http": proxy, |
| 1165 | "https": proxy }) | 1230 | "https": proxy}) |
| 1166 | 1231 | ||
| 1167 | opener = urllib.request.build_opener( | 1232 | opener = urllib.request.build_opener( |
| 1168 | urllib.request.HTTPCookieProcessor(cookiejar), | 1233 | urllib.request.HTTPCookieProcessor(cookiejar), |
| @@ -1219,4 +1284,3 @@ class PersistentTransport(xmlrpc.client.Transport): | |||
| 1219 | 1284 | ||
| 1220 | def close(self): | 1285 | def close(self): |
| 1221 | pass | 1286 | pass |
| 1222 | |||
diff --git a/subcmds/upload.py b/subcmds/upload.py index 5c12aaee..c48deab6 100644 --- a/subcmds/upload.py +++ b/subcmds/upload.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2008 The Android Open Source Project | 1 | # Copyright (C) 2008 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,25 +12,23 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from __future__ import print_function | ||
| 18 | import copy | 15 | import copy |
| 16 | import functools | ||
| 17 | import optparse | ||
| 19 | import re | 18 | import re |
| 20 | import sys | 19 | import sys |
| 21 | 20 | ||
| 22 | from command import InteractiveCommand | 21 | from command import DEFAULT_LOCAL_JOBS, InteractiveCommand |
| 23 | from editor import Editor | 22 | from editor import Editor |
| 24 | from error import HookError, UploadError | 23 | from error import UploadError |
| 25 | from git_command import GitCommand | 24 | from git_command import GitCommand |
| 26 | from project import RepoHook | 25 | from git_refs import R_HEADS |
| 26 | from hooks import RepoHook | ||
| 27 | 27 | ||
| 28 | from pyversion import is_python3 | ||
| 29 | if not is_python3(): | ||
| 30 | input = raw_input | ||
| 31 | else: | ||
| 32 | unicode = str | ||
| 33 | 28 | ||
| 34 | UNUSUAL_COMMIT_THRESHOLD = 5 | 29 | UNUSUAL_COMMIT_THRESHOLD = 5 |
| 35 | 30 | ||
| 31 | |||
| 36 | def _ConfirmManyUploads(multiple_branches=False): | 32 | def _ConfirmManyUploads(multiple_branches=False): |
| 37 | if multiple_branches: | 33 | if multiple_branches: |
| 38 | print('ATTENTION: One or more branches has an unusually high number ' | 34 | print('ATTENTION: One or more branches has an unusually high number ' |
| @@ -44,19 +40,22 @@ def _ConfirmManyUploads(multiple_branches=False): | |||
| 44 | answer = input("If you are sure you intend to do this, type 'yes': ").strip() | 40 | answer = input("If you are sure you intend to do this, type 'yes': ").strip() |
| 45 | return answer == "yes" | 41 | return answer == "yes" |
| 46 | 42 | ||
| 43 | |||
| 47 | def _die(fmt, *args): | 44 | def _die(fmt, *args): |
| 48 | msg = fmt % args | 45 | msg = fmt % args |
| 49 | print('error: %s' % msg, file=sys.stderr) | 46 | print('error: %s' % msg, file=sys.stderr) |
| 50 | sys.exit(1) | 47 | sys.exit(1) |
| 51 | 48 | ||
| 49 | |||
| 52 | def _SplitEmails(values): | 50 | def _SplitEmails(values): |
| 53 | result = [] | 51 | result = [] |
| 54 | for value in values: | 52 | for value in values: |
| 55 | result.extend([s.strip() for s in value.split(',')]) | 53 | result.extend([s.strip() for s in value.split(',')]) |
| 56 | return result | 54 | return result |
| 57 | 55 | ||
| 56 | |||
| 58 | class Upload(InteractiveCommand): | 57 | class Upload(InteractiveCommand): |
| 59 | common = True | 58 | COMMON = True |
| 60 | helpSummary = "Upload changes for code review" | 59 | helpSummary = "Upload changes for code review" |
| 61 | helpUsage = """ | 60 | helpUsage = """ |
| 62 | %prog [--re --cc] [<project>]... | 61 | %prog [--re --cc] [<project>]... |
| @@ -126,74 +125,89 @@ is set to "true" then repo will assume you always want the equivalent | |||
| 126 | of the -t option to the repo command. If unset or set to "false" then | 125 | of the -t option to the repo command. If unset or set to "false" then |
| 127 | repo will make use of only the command line option. | 126 | repo will make use of only the command line option. |
| 128 | 127 | ||
| 128 | review.URL.uploadhashtags: | ||
| 129 | |||
| 130 | To add hashtags whenever uploading a commit, you can set a per-project | ||
| 131 | or global Git option to do so. The value of review.URL.uploadhashtags | ||
| 132 | will be used as comma delimited hashtags like the --hashtag option. | ||
| 133 | |||
| 134 | review.URL.uploadlabels: | ||
| 135 | |||
| 136 | To add labels whenever uploading a commit, you can set a per-project | ||
| 137 | or global Git option to do so. The value of review.URL.uploadlabels | ||
| 138 | will be used as comma delimited labels like the --label option. | ||
| 139 | |||
| 140 | review.URL.uploadnotify: | ||
| 141 | |||
| 142 | Control e-mail notifications when uploading. | ||
| 143 | https://gerrit-review.googlesource.com/Documentation/user-upload.html#notify | ||
| 144 | |||
| 129 | # References | 145 | # References |
| 130 | 146 | ||
| 131 | Gerrit Code Review: https://www.gerritcodereview.com/ | 147 | Gerrit Code Review: https://www.gerritcodereview.com/ |
| 132 | 148 | ||
| 133 | """ | 149 | """ |
| 150 | PARALLEL_JOBS = DEFAULT_LOCAL_JOBS | ||
| 134 | 151 | ||
| 135 | def _Options(self, p): | 152 | def _Options(self, p): |
| 136 | p.add_option('-t', | 153 | p.add_option('-t', |
| 137 | dest='auto_topic', action='store_true', | 154 | dest='auto_topic', action='store_true', |
| 138 | help='Send local branch name to Gerrit Code Review') | 155 | help='send local branch name to Gerrit Code Review') |
| 156 | p.add_option('--hashtag', '--ht', | ||
| 157 | dest='hashtags', action='append', default=[], | ||
| 158 | help='add hashtags (comma delimited) to the review') | ||
| 159 | p.add_option('--hashtag-branch', '--htb', | ||
| 160 | action='store_true', | ||
| 161 | help='add local branch name as a hashtag') | ||
| 162 | p.add_option('-l', '--label', | ||
| 163 | dest='labels', action='append', default=[], | ||
| 164 | help='add a label when uploading') | ||
| 139 | p.add_option('--re', '--reviewers', | 165 | p.add_option('--re', '--reviewers', |
| 140 | type='string', action='append', dest='reviewers', | 166 | type='string', action='append', dest='reviewers', |
| 141 | help='Request reviews from these people.') | 167 | help='request reviews from these people') |
| 142 | p.add_option('--cc', | 168 | p.add_option('--cc', |
| 143 | type='string', action='append', dest='cc', | 169 | type='string', action='append', dest='cc', |
| 144 | help='Also send email to these email addresses.') | 170 | help='also send email to these email addresses') |
| 145 | p.add_option('--br', | 171 | p.add_option('--br', '--branch', |
| 146 | type='string', action='store', dest='branch', | 172 | type='string', action='store', dest='branch', |
| 147 | help='Branch to upload.') | 173 | help='(local) branch to upload') |
| 148 | p.add_option('--cbr', '--current-branch', | 174 | p.add_option('-c', '--current-branch', |
| 175 | dest='current_branch', action='store_true', | ||
| 176 | help='upload current git branch') | ||
| 177 | p.add_option('--no-current-branch', | ||
| 178 | dest='current_branch', action='store_false', | ||
| 179 | help='upload all git branches') | ||
| 180 | # Turn this into a warning & remove this someday. | ||
| 181 | p.add_option('--cbr', | ||
| 149 | dest='current_branch', action='store_true', | 182 | dest='current_branch', action='store_true', |
| 150 | help='Upload current git branch.') | 183 | help=optparse.SUPPRESS_HELP) |
| 151 | p.add_option('-d', '--draft', | ||
| 152 | action='store_true', dest='draft', default=False, | ||
| 153 | help='If specified, upload as a draft.') | ||
| 154 | p.add_option('--ne', '--no-emails', | 184 | p.add_option('--ne', '--no-emails', |
| 155 | action='store_false', dest='notify', default=True, | 185 | action='store_false', dest='notify', default=True, |
| 156 | help='If specified, do not send emails on upload.') | 186 | help='do not send e-mails on upload') |
| 157 | p.add_option('-p', '--private', | 187 | p.add_option('-p', '--private', |
| 158 | action='store_true', dest='private', default=False, | 188 | action='store_true', dest='private', default=False, |
| 159 | help='If specified, upload as a private change.') | 189 | help='upload as a private change (deprecated; use --wip)') |
| 160 | p.add_option('-w', '--wip', | 190 | p.add_option('-w', '--wip', |
| 161 | action='store_true', dest='wip', default=False, | 191 | action='store_true', dest='wip', default=False, |
| 162 | help='If specified, upload as a work-in-progress change.') | 192 | help='upload as a work-in-progress change') |
| 163 | p.add_option('-o', '--push-option', | 193 | p.add_option('-o', '--push-option', |
| 164 | type='string', action='append', dest='push_options', | 194 | type='string', action='append', dest='push_options', |
| 165 | default=[], | 195 | default=[], |
| 166 | help='Additional push options to transmit') | 196 | help='additional push options to transmit') |
| 167 | p.add_option('-D', '--destination', '--dest', | 197 | p.add_option('-D', '--destination', '--dest', |
| 168 | type='string', action='store', dest='dest_branch', | 198 | type='string', action='store', dest='dest_branch', |
| 169 | metavar='BRANCH', | 199 | metavar='BRANCH', |
| 170 | help='Submit for review on this target branch.') | 200 | help='submit for review on this target branch') |
| 171 | 201 | p.add_option('-n', '--dry-run', | |
| 172 | # Options relating to upload hook. Note that verify and no-verify are NOT | 202 | dest='dryrun', default=False, action='store_true', |
| 173 | # opposites of each other, which is why they store to different locations. | 203 | help='do everything except actually upload the CL') |
| 174 | # We are using them to match 'git commit' syntax. | 204 | p.add_option('-y', '--yes', |
| 175 | # | 205 | default=False, action='store_true', |
| 176 | # Combinations: | 206 | help='answer yes to all safe prompts') |
| 177 | # - no-verify=False, verify=False (DEFAULT): | ||
| 178 | # If stdout is a tty, can prompt about running upload hooks if needed. | ||
| 179 | # If user denies running hooks, the upload is cancelled. If stdout is | ||
| 180 | # not a tty and we would need to prompt about upload hooks, upload is | ||
| 181 | # cancelled. | ||
| 182 | # - no-verify=False, verify=True: | ||
| 183 | # Always run upload hooks with no prompt. | ||
| 184 | # - no-verify=True, verify=False: | ||
| 185 | # Never run upload hooks, but upload anyway (AKA bypass hooks). | ||
| 186 | # - no-verify=True, verify=True: | ||
| 187 | # Invalid | ||
| 188 | p.add_option('--no-cert-checks', | 207 | p.add_option('--no-cert-checks', |
| 189 | dest='validate_certs', action='store_false', default=True, | 208 | dest='validate_certs', action='store_false', default=True, |
| 190 | help='Disable verifying ssl certs (unsafe).') | 209 | help='disable verifying ssl certs (unsafe)') |
| 191 | p.add_option('--no-verify', | 210 | RepoHook.AddOptionGroup(p, 'pre-upload') |
| 192 | dest='bypass_hooks', action='store_true', | ||
| 193 | help='Do not run the upload hook.') | ||
| 194 | p.add_option('--verify', | ||
| 195 | dest='allow_all_hooks', action='store_true', | ||
| 196 | help='Run the upload hook without prompting.') | ||
| 197 | 211 | ||
| 198 | def _SingleBranch(self, opt, branch, people): | 212 | def _SingleBranch(self, opt, branch, people): |
| 199 | project = branch.project | 213 | project = branch.project |
| @@ -212,20 +226,24 @@ Gerrit Code Review: https://www.gerritcodereview.com/ | |||
| 212 | 226 | ||
| 213 | destination = opt.dest_branch or project.dest_branch or project.revisionExpr | 227 | destination = opt.dest_branch or project.dest_branch or project.revisionExpr |
| 214 | print('Upload project %s/ to remote branch %s%s:' % | 228 | print('Upload project %s/ to remote branch %s%s:' % |
| 215 | (project.relpath, destination, ' (draft)' if opt.draft else '')) | 229 | (project.relpath, destination, ' (private)' if opt.private else '')) |
| 216 | print(' branch %s (%2d commit%s, %s):' % ( | 230 | print(' branch %s (%2d commit%s, %s):' % ( |
| 217 | name, | 231 | name, |
| 218 | len(commit_list), | 232 | len(commit_list), |
| 219 | len(commit_list) != 1 and 's' or '', | 233 | len(commit_list) != 1 and 's' or '', |
| 220 | date)) | 234 | date)) |
| 221 | for commit in commit_list: | 235 | for commit in commit_list: |
| 222 | print(' %s' % commit) | 236 | print(' %s' % commit) |
| 223 | 237 | ||
| 224 | print('to %s (y/N)? ' % remote.review, end='') | 238 | print('to %s (y/N)? ' % remote.review, end='') |
| 225 | # TODO: When we require Python 3, use flush=True w/print above. | 239 | # TODO: When we require Python 3, use flush=True w/print above. |
| 226 | sys.stdout.flush() | 240 | sys.stdout.flush() |
| 227 | answer = sys.stdin.readline().strip().lower() | 241 | if opt.yes: |
| 228 | answer = answer in ('y', 'yes', '1', 'true', 't') | 242 | print('<--yes>') |
| 243 | answer = True | ||
| 244 | else: | ||
| 245 | answer = sys.stdin.readline().strip().lower() | ||
| 246 | answer = answer in ('y', 'yes', '1', 'true', 't') | ||
| 229 | 247 | ||
| 230 | if answer: | 248 | if answer: |
| 231 | if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD: | 249 | if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD: |
| @@ -322,12 +340,12 @@ Gerrit Code Review: https://www.gerritcodereview.com/ | |||
| 322 | 340 | ||
| 323 | key = 'review.%s.autoreviewer' % project.GetBranch(name).remote.review | 341 | key = 'review.%s.autoreviewer' % project.GetBranch(name).remote.review |
| 324 | raw_list = project.config.GetString(key) | 342 | raw_list = project.config.GetString(key) |
| 325 | if not raw_list is None: | 343 | if raw_list is not None: |
| 326 | people[0].extend([entry.strip() for entry in raw_list.split(',')]) | 344 | people[0].extend([entry.strip() for entry in raw_list.split(',')]) |
| 327 | 345 | ||
| 328 | key = 'review.%s.autocopy' % project.GetBranch(name).remote.review | 346 | key = 'review.%s.autocopy' % project.GetBranch(name).remote.review |
| 329 | raw_list = project.config.GetString(key) | 347 | raw_list = project.config.GetString(key) |
| 330 | if not raw_list is None and len(people[0]) > 0: | 348 | if raw_list is not None and len(people[0]) > 0: |
| 331 | people[1].extend([entry.strip() for entry in raw_list.split(',')]) | 349 | people[1].extend([entry.strip() for entry in raw_list.split(',')]) |
| 332 | 350 | ||
| 333 | def _FindGerritChange(self, branch): | 351 | def _FindGerritChange(self, branch): |
| @@ -364,7 +382,11 @@ Gerrit Code Review: https://www.gerritcodereview.com/ | |||
| 364 | print('Continue uploading? (y/N) ', end='') | 382 | print('Continue uploading? (y/N) ', end='') |
| 365 | # TODO: When we require Python 3, use flush=True w/print above. | 383 | # TODO: When we require Python 3, use flush=True w/print above. |
| 366 | sys.stdout.flush() | 384 | sys.stdout.flush() |
| 367 | a = sys.stdin.readline().strip().lower() | 385 | if opt.yes: |
| 386 | print('<--yes>') | ||
| 387 | a = 'yes' | ||
| 388 | else: | ||
| 389 | a = sys.stdin.readline().strip().lower() | ||
| 368 | if a not in ('y', 'yes', 't', 'true', 'on'): | 390 | if a not in ('y', 'yes', 't', 'true', 'on'): |
| 369 | print("skipping upload", file=sys.stderr) | 391 | print("skipping upload", file=sys.stderr) |
| 370 | branch.uploaded = False | 392 | branch.uploaded = False |
| @@ -376,12 +398,51 @@ Gerrit Code Review: https://www.gerritcodereview.com/ | |||
| 376 | key = 'review.%s.uploadtopic' % branch.project.remote.review | 398 | key = 'review.%s.uploadtopic' % branch.project.remote.review |
| 377 | opt.auto_topic = branch.project.config.GetBoolean(key) | 399 | opt.auto_topic = branch.project.config.GetBoolean(key) |
| 378 | 400 | ||
| 401 | def _ExpandCommaList(value): | ||
| 402 | """Split |value| up into comma delimited entries.""" | ||
| 403 | if not value: | ||
| 404 | return | ||
| 405 | for ret in value.split(','): | ||
| 406 | ret = ret.strip() | ||
| 407 | if ret: | ||
| 408 | yield ret | ||
| 409 | |||
| 410 | # Check if hashtags should be included. | ||
| 411 | key = 'review.%s.uploadhashtags' % branch.project.remote.review | ||
| 412 | hashtags = set(_ExpandCommaList(branch.project.config.GetString(key))) | ||
| 413 | for tag in opt.hashtags: | ||
| 414 | hashtags.update(_ExpandCommaList(tag)) | ||
| 415 | if opt.hashtag_branch: | ||
| 416 | hashtags.add(branch.name) | ||
| 417 | |||
| 418 | # Check if labels should be included. | ||
| 419 | key = 'review.%s.uploadlabels' % branch.project.remote.review | ||
| 420 | labels = set(_ExpandCommaList(branch.project.config.GetString(key))) | ||
| 421 | for label in opt.labels: | ||
| 422 | labels.update(_ExpandCommaList(label)) | ||
| 423 | # Basic sanity check on label syntax. | ||
| 424 | for label in labels: | ||
| 425 | if not re.match(r'^.+[+-][0-9]+$', label): | ||
| 426 | print('repo: error: invalid label syntax "%s": labels use forms ' | ||
| 427 | 'like CodeReview+1 or Verified-1' % (label,), file=sys.stderr) | ||
| 428 | sys.exit(1) | ||
| 429 | |||
| 430 | # Handle e-mail notifications. | ||
| 431 | if opt.notify is False: | ||
| 432 | notify = 'NONE' | ||
| 433 | else: | ||
| 434 | key = 'review.%s.uploadnotify' % branch.project.remote.review | ||
| 435 | notify = branch.project.config.GetString(key) | ||
| 436 | |||
| 379 | destination = opt.dest_branch or branch.project.dest_branch | 437 | destination = opt.dest_branch or branch.project.dest_branch |
| 380 | 438 | ||
| 381 | # Make sure our local branch is not setup to track a different remote branch | 439 | # Make sure our local branch is not setup to track a different remote branch |
| 382 | merge_branch = self._GetMergeBranch(branch.project) | 440 | merge_branch = self._GetMergeBranch(branch.project) |
| 383 | if destination: | 441 | if destination: |
| 384 | full_dest = 'refs/heads/%s' % destination | 442 | full_dest = destination |
| 443 | if not full_dest.startswith(R_HEADS): | ||
| 444 | full_dest = R_HEADS + full_dest | ||
| 445 | |||
| 385 | if not opt.dest_branch and merge_branch and merge_branch != full_dest: | 446 | if not opt.dest_branch and merge_branch and merge_branch != full_dest: |
| 386 | print('merge branch %s does not match destination branch %s' | 447 | print('merge branch %s does not match destination branch %s' |
| 387 | % (merge_branch, full_dest)) | 448 | % (merge_branch, full_dest)) |
| @@ -392,10 +453,12 @@ Gerrit Code Review: https://www.gerritcodereview.com/ | |||
| 392 | continue | 453 | continue |
| 393 | 454 | ||
| 394 | branch.UploadForReview(people, | 455 | branch.UploadForReview(people, |
| 456 | dryrun=opt.dryrun, | ||
| 395 | auto_topic=opt.auto_topic, | 457 | auto_topic=opt.auto_topic, |
| 396 | draft=opt.draft, | 458 | hashtags=hashtags, |
| 459 | labels=labels, | ||
| 397 | private=opt.private, | 460 | private=opt.private, |
| 398 | notify=None if opt.notify else 'NONE', | 461 | notify=notify, |
| 399 | wip=opt.wip, | 462 | wip=opt.wip, |
| 400 | dest_branch=destination, | 463 | dest_branch=destination, |
| 401 | validate_certs=opt.validate_certs, | 464 | validate_certs=opt.validate_certs, |
| @@ -418,18 +481,18 @@ Gerrit Code Review: https://www.gerritcodereview.com/ | |||
| 418 | else: | 481 | else: |
| 419 | fmt = '\n (%s)' | 482 | fmt = '\n (%s)' |
| 420 | print(('[FAILED] %-15s %-15s' + fmt) % ( | 483 | print(('[FAILED] %-15s %-15s' + fmt) % ( |
| 421 | branch.project.relpath + '/', \ | 484 | branch.project.relpath + '/', |
| 422 | branch.name, \ | 485 | branch.name, |
| 423 | str(branch.error)), | 486 | str(branch.error)), |
| 424 | file=sys.stderr) | 487 | file=sys.stderr) |
| 425 | print() | 488 | print() |
| 426 | 489 | ||
| 427 | for branch in todo: | 490 | for branch in todo: |
| 428 | if branch.uploaded: | 491 | if branch.uploaded: |
| 429 | print('[OK ] %-15s %s' % ( | 492 | print('[OK ] %-15s %s' % ( |
| 430 | branch.project.relpath + '/', | 493 | branch.project.relpath + '/', |
| 431 | branch.name), | 494 | branch.name), |
| 432 | file=sys.stderr) | 495 | file=sys.stderr) |
| 433 | 496 | ||
| 434 | if have_errors: | 497 | if have_errors: |
| 435 | sys.exit(1) | 498 | sys.exit(1) |
| @@ -437,68 +500,72 @@ Gerrit Code Review: https://www.gerritcodereview.com/ | |||
| 437 | def _GetMergeBranch(self, project): | 500 | def _GetMergeBranch(self, project): |
| 438 | p = GitCommand(project, | 501 | p = GitCommand(project, |
| 439 | ['rev-parse', '--abbrev-ref', 'HEAD'], | 502 | ['rev-parse', '--abbrev-ref', 'HEAD'], |
| 440 | capture_stdout = True, | 503 | capture_stdout=True, |
| 441 | capture_stderr = True) | 504 | capture_stderr=True) |
| 442 | p.Wait() | 505 | p.Wait() |
| 443 | local_branch = p.stdout.strip() | 506 | local_branch = p.stdout.strip() |
| 444 | p = GitCommand(project, | 507 | p = GitCommand(project, |
| 445 | ['config', '--get', 'branch.%s.merge' % local_branch], | 508 | ['config', '--get', 'branch.%s.merge' % local_branch], |
| 446 | capture_stdout = True, | 509 | capture_stdout=True, |
| 447 | capture_stderr = True) | 510 | capture_stderr=True) |
| 448 | p.Wait() | 511 | p.Wait() |
| 449 | merge_branch = p.stdout.strip() | 512 | merge_branch = p.stdout.strip() |
| 450 | return merge_branch | 513 | return merge_branch |
| 451 | 514 | ||
| 515 | @staticmethod | ||
| 516 | def _GatherOne(opt, project): | ||
| 517 | """Figure out the upload status for |project|.""" | ||
| 518 | if opt.current_branch: | ||
| 519 | cbr = project.CurrentBranch | ||
| 520 | up_branch = project.GetUploadableBranch(cbr) | ||
| 521 | avail = [up_branch] if up_branch else None | ||
| 522 | else: | ||
| 523 | avail = project.GetUploadableBranches(opt.branch) | ||
| 524 | return (project, avail) | ||
| 525 | |||
| 452 | def Execute(self, opt, args): | 526 | def Execute(self, opt, args): |
| 453 | project_list = self.GetProjects(args) | 527 | projects = self.GetProjects(args) |
| 454 | pending = [] | 528 | |
| 455 | reviewers = [] | 529 | def _ProcessResults(_pool, _out, results): |
| 456 | cc = [] | 530 | pending = [] |
| 457 | branch = None | 531 | for result in results: |
| 458 | 532 | project, avail = result | |
| 459 | if opt.branch: | 533 | if avail is None: |
| 460 | branch = opt.branch | 534 | print('repo: error: %s: Unable to upload branch "%s". ' |
| 461 | 535 | 'You might be able to fix the branch by running:\n' | |
| 462 | for project in project_list: | 536 | ' git branch --set-upstream-to m/%s' % |
| 463 | if opt.current_branch: | 537 | (project.relpath, project.CurrentBranch, self.manifest.branch), |
| 464 | cbr = project.CurrentBranch | ||
| 465 | up_branch = project.GetUploadableBranch(cbr) | ||
| 466 | if up_branch: | ||
| 467 | avail = [up_branch] | ||
| 468 | else: | ||
| 469 | avail = None | ||
| 470 | print('ERROR: Current branch (%s) not uploadable. ' | ||
| 471 | 'You may be able to type ' | ||
| 472 | '"git branch --set-upstream-to m/master" to fix ' | ||
| 473 | 'your branch.' % str(cbr), | ||
| 474 | file=sys.stderr) | 538 | file=sys.stderr) |
| 475 | else: | 539 | elif avail: |
| 476 | avail = project.GetUploadableBranches(branch) | 540 | pending.append(result) |
| 477 | if avail: | 541 | return pending |
| 478 | pending.append((project, avail)) | 542 | |
| 543 | pending = self.ExecuteInParallel( | ||
| 544 | opt.jobs, | ||
| 545 | functools.partial(self._GatherOne, opt), | ||
| 546 | projects, | ||
| 547 | callback=_ProcessResults) | ||
| 479 | 548 | ||
| 480 | if not pending: | 549 | if not pending: |
| 481 | print("no branches ready for upload", file=sys.stderr) | 550 | if opt.branch is None: |
| 482 | return | 551 | print('repo: error: no branches ready for upload', file=sys.stderr) |
| 483 | 552 | else: | |
| 484 | if not opt.bypass_hooks: | 553 | print('repo: error: no branches named "%s" ready for upload' % |
| 485 | hook = RepoHook('pre-upload', self.manifest.repo_hooks_project, | 554 | (opt.branch,), file=sys.stderr) |
| 486 | self.manifest.topdir, | 555 | return 1 |
| 487 | self.manifest.manifestProject.GetRemote('origin').url, | 556 | |
| 488 | abort_if_user_denies=True) | 557 | pending_proj_names = [project.name for (project, available) in pending] |
| 489 | pending_proj_names = [project.name for (project, available) in pending] | 558 | pending_worktrees = [project.worktree for (project, available) in pending] |
| 490 | pending_worktrees = [project.worktree for (project, available) in pending] | 559 | hook = RepoHook.FromSubcmd( |
| 491 | try: | 560 | hook_type='pre-upload', manifest=self.manifest, |
| 492 | hook.Run(opt.allow_all_hooks, project_list=pending_proj_names, | 561 | opt=opt, abort_if_user_denies=True) |
| 493 | worktree_list=pending_worktrees) | 562 | if not hook.Run( |
| 494 | except HookError as e: | 563 | project_list=pending_proj_names, |
| 495 | print("ERROR: %s" % str(e), file=sys.stderr) | 564 | worktree_list=pending_worktrees): |
| 496 | return | 565 | return 1 |
| 497 | 566 | ||
| 498 | if opt.reviewers: | 567 | reviewers = _SplitEmails(opt.reviewers) if opt.reviewers else [] |
| 499 | reviewers = _SplitEmails(opt.reviewers) | 568 | cc = _SplitEmails(opt.cc) if opt.cc else [] |
| 500 | if opt.cc: | ||
| 501 | cc = _SplitEmails(opt.cc) | ||
| 502 | people = (reviewers, cc) | 569 | people = (reviewers, cc) |
| 503 | 570 | ||
| 504 | if len(pending) == 1 and len(pending[0][1]) == 1: | 571 | if len(pending) == 1 and len(pending[0][1]) == 1: |
diff --git a/subcmds/version.py b/subcmds/version.py index 761172b7..09b053ea 100644 --- a/subcmds/version.py +++ b/subcmds/version.py | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | # -*- coding:utf-8 -*- | ||
| 2 | # | ||
| 3 | # Copyright (C) 2009 The Android Open Source Project | 1 | # Copyright (C) 2009 The Android Open Source Project |
| 4 | # | 2 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| @@ -14,17 +12,20 @@ | |||
| 14 | # See the License for the specific language governing permissions and | 12 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. | 13 | # limitations under the License. |
| 16 | 14 | ||
| 17 | from __future__ import print_function | 15 | import platform |
| 18 | import sys | 16 | import sys |
| 17 | |||
| 19 | from command import Command, MirrorSafeCommand | 18 | from command import Command, MirrorSafeCommand |
| 20 | from git_command import git, RepoSourceVersion, user_agent | 19 | from git_command import git, RepoSourceVersion, user_agent |
| 21 | from git_refs import HEAD | 20 | from git_refs import HEAD |
| 21 | from wrapper import Wrapper | ||
| 22 | |||
| 22 | 23 | ||
| 23 | class Version(Command, MirrorSafeCommand): | 24 | class Version(Command, MirrorSafeCommand): |
| 24 | wrapper_version = None | 25 | wrapper_version = None |
| 25 | wrapper_path = None | 26 | wrapper_path = None |
| 26 | 27 | ||
| 27 | common = False | 28 | COMMON = False |
| 28 | helpSummary = "Display the version of repo" | 29 | helpSummary = "Display the version of repo" |
| 29 | helpUsage = """ | 30 | helpUsage = """ |
| 30 | %prog | 31 | %prog |
| @@ -33,16 +34,19 @@ class Version(Command, MirrorSafeCommand): | |||
| 33 | def Execute(self, opt, args): | 34 | def Execute(self, opt, args): |
| 34 | rp = self.manifest.repoProject | 35 | rp = self.manifest.repoProject |
| 35 | rem = rp.GetRemote(rp.remote.name) | 36 | rem = rp.GetRemote(rp.remote.name) |
| 37 | branch = rp.GetBranch('default') | ||
| 36 | 38 | ||
| 37 | # These might not be the same. Report them both. | 39 | # These might not be the same. Report them both. |
| 38 | src_ver = RepoSourceVersion() | 40 | src_ver = RepoSourceVersion() |
| 39 | rp_ver = rp.bare_git.describe(HEAD) | 41 | rp_ver = rp.bare_git.describe(HEAD) |
| 40 | print('repo version %s' % rp_ver) | 42 | print('repo version %s' % rp_ver) |
| 41 | print(' (from %s)' % rem.url) | 43 | print(' (from %s)' % rem.url) |
| 44 | print(' (tracking %s)' % branch.merge) | ||
| 45 | print(' (%s)' % rp.bare_git.log('-1', '--format=%cD', HEAD)) | ||
| 42 | 46 | ||
| 43 | if Version.wrapper_path is not None: | 47 | if self.wrapper_path is not None: |
| 44 | print('repo launcher version %s' % Version.wrapper_version) | 48 | print('repo launcher version %s' % self.wrapper_version) |
| 45 | print(' (from %s)' % Version.wrapper_path) | 49 | print(' (from %s)' % self.wrapper_path) |
| 46 | 50 | ||
| 47 | if src_ver != rp_ver: | 51 | if src_ver != rp_ver: |
| 48 | print(' (currently at %s)' % src_ver) | 52 | print(' (currently at %s)' % src_ver) |
| @@ -51,3 +55,12 @@ class Version(Command, MirrorSafeCommand): | |||
| 51 | print('git %s' % git.version_tuple().full) | 55 | print('git %s' % git.version_tuple().full) |
| 52 | print('git User-Agent %s' % user_agent.git) | 56 | print('git User-Agent %s' % user_agent.git) |
| 53 | print('Python %s' % sys.version) | 57 | print('Python %s' % sys.version) |
| 58 | uname = platform.uname() | ||
| 59 | if sys.version_info.major < 3: | ||
| 60 | # Python 3 returns a named tuple, but Python 2 is simpler. | ||
| 61 | print(uname) | ||
| 62 | else: | ||
| 63 | print('OS %s %s (%s)' % (uname.system, uname.release, uname.version)) | ||
| 64 | print('CPU %s (%s)' % | ||
| 65 | (uname.machine, uname.processor if uname.processor else 'unknown')) | ||
| 66 | print('Bug reports:', Wrapper().BUG_URL) | ||
