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) | ||