summaryrefslogtreecommitdiffstats
path: root/subcmds
diff options
context:
space:
mode:
authorLaMont Jones <lamontjones@google.com>2021-11-18 22:40:18 +0000
committerLaMont Jones <lamontjones@google.com>2022-02-17 21:57:55 +0000
commitcc879a97c3e2614d19b15b4661c3cab4d33139c9 (patch)
tree69d225e9f0e9d79fec8f423d9c40c275f0bf3b8c /subcmds
parent87cce68b28c34fa86895baa8d7f48307382e6c75 (diff)
downloadgit-repo-c4d9261b7e87a03fa3b0403d4811d05bbbddc0a4.tar.gz
Add multi-manifest support with <submanifest> elementv2.22
To be addressed in another change: - a partial `repo sync` (with a list of projects/paths to sync) requires `--this-tree-only`. Change-Id: I6c7400bf001540e9d7694fa70934f8f204cb5f57 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/322657 Tested-by: LaMont Jones <lamontjones@google.com> Reviewed-by: Mike Frysinger <vapier@google.com>
Diffstat (limited to 'subcmds')
-rw-r--r--subcmds/abandon.py7
-rw-r--r--subcmds/branches.py14
-rw-r--r--subcmds/checkout.py2
-rw-r--r--subcmds/diff.py2
-rw-r--r--subcmds/diffmanifests.py3
-rw-r--r--subcmds/download.py10
-rw-r--r--subcmds/forall.py12
-rw-r--r--subcmds/gitc_init.py1
-rw-r--r--subcmds/grep.py15
-rw-r--r--subcmds/info.py16
-rw-r--r--subcmds/init.py12
-rw-r--r--subcmds/list.py7
-rw-r--r--subcmds/manifest.py72
-rw-r--r--subcmds/overview.py4
-rw-r--r--subcmds/prune.py4
-rw-r--r--subcmds/rebase.py9
-rw-r--r--subcmds/stage.py11
-rw-r--r--subcmds/start.py5
-rw-r--r--subcmds/status.py9
-rw-r--r--subcmds/sync.py8
-rw-r--r--subcmds/upload.py44
21 files changed, 162 insertions, 105 deletions
diff --git a/subcmds/abandon.py b/subcmds/abandon.py
index 85d85f5a..c3d2d5b7 100644
--- a/subcmds/abandon.py
+++ b/subcmds/abandon.py
@@ -69,7 +69,8 @@ It is equivalent to "git branch -D <branchname>".
69 nb = args[0] 69 nb = args[0]
70 err = defaultdict(list) 70 err = defaultdict(list)
71 success = defaultdict(list) 71 success = defaultdict(list)
72 all_projects = self.GetProjects(args[1:]) 72 all_projects = self.GetProjects(args[1:], all_manifests=not opt.this_manifest_only)
73 _RelPath = lambda p: p.RelPath(local=opt.this_manifest_only)
73 74
74 def _ProcessResults(_pool, pm, states): 75 def _ProcessResults(_pool, pm, states):
75 for (results, project) in states: 76 for (results, project) in states:
@@ -94,7 +95,7 @@ It is equivalent to "git branch -D <branchname>".
94 err_msg = "error: cannot abandon %s" % br 95 err_msg = "error: cannot abandon %s" % br
95 print(err_msg, file=sys.stderr) 96 print(err_msg, file=sys.stderr)
96 for proj in err[br]: 97 for proj in err[br]:
97 print(' ' * len(err_msg) + " | %s" % proj.relpath, file=sys.stderr) 98 print(' ' * len(err_msg) + " | %s" % _RelPath(proj), file=sys.stderr)
98 sys.exit(1) 99 sys.exit(1)
99 elif not success: 100 elif not success:
100 print('error: no project has local branch(es) : %s' % nb, 101 print('error: no project has local branch(es) : %s' % nb,
@@ -110,5 +111,5 @@ It is equivalent to "git branch -D <branchname>".
110 result = "all project" 111 result = "all project"
111 else: 112 else:
112 result = "%s" % ( 113 result = "%s" % (
113 ('\n' + ' ' * width + '| ').join(p.relpath for p in success[br])) 114 ('\n' + ' ' * width + '| ').join(_RelPath(p) for p in success[br]))
114 print("%s%s| %s\n" % (br, ' ' * (width - len(br)), result)) 115 print("%s%s| %s\n" % (br, ' ' * (width - len(br)), result))
diff --git a/subcmds/branches.py b/subcmds/branches.py
index 7b5decc6..b89cc2f8 100644
--- a/subcmds/branches.py
+++ b/subcmds/branches.py
@@ -98,7 +98,7 @@ is shown, then the branch appears in all projects.
98 PARALLEL_JOBS = DEFAULT_LOCAL_JOBS 98 PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
99 99
100 def Execute(self, opt, args): 100 def Execute(self, opt, args):
101 projects = self.GetProjects(args) 101 projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
102 out = BranchColoring(self.manifest.manifestProject.config) 102 out = BranchColoring(self.manifest.manifestProject.config)
103 all_branches = {} 103 all_branches = {}
104 project_cnt = len(projects) 104 project_cnt = len(projects)
@@ -147,6 +147,7 @@ is shown, then the branch appears in all projects.
147 hdr('%c%c %-*s' % (current, published, width, name)) 147 hdr('%c%c %-*s' % (current, published, width, name))
148 out.write(' |') 148 out.write(' |')
149 149
150 _RelPath = lambda p: p.RelPath(local=opt.this_manifest_only)
150 if in_cnt < project_cnt: 151 if in_cnt < project_cnt:
151 fmt = out.write 152 fmt = out.write
152 paths = [] 153 paths = []
@@ -154,19 +155,20 @@ is shown, then the branch appears in all projects.
154 if i.IsSplitCurrent or (in_cnt <= project_cnt - in_cnt): 155 if i.IsSplitCurrent or (in_cnt <= project_cnt - in_cnt):
155 in_type = 'in' 156 in_type = 'in'
156 for b in i.projects: 157 for b in i.projects:
158 relpath = b.project.relpath
157 if not i.IsSplitCurrent or b.current: 159 if not i.IsSplitCurrent or b.current:
158 paths.append(b.project.relpath) 160 paths.append(_RelPath(b.project))
159 else: 161 else:
160 non_cur_paths.append(b.project.relpath) 162 non_cur_paths.append(_RelPath(b.project))
161 else: 163 else:
162 fmt = out.notinproject 164 fmt = out.notinproject
163 in_type = 'not in' 165 in_type = 'not in'
164 have = set() 166 have = set()
165 for b in i.projects: 167 for b in i.projects:
166 have.add(b.project.relpath) 168 have.add(_RelPath(b.project))
167 for p in projects: 169 for p in projects:
168 if p.relpath not in have: 170 if _RelPath(p) not in have:
169 paths.append(p.relpath) 171 paths.append(_RelPath(p))
170 172
171 s = ' %s %s' % (in_type, ', '.join(paths)) 173 s = ' %s %s' % (in_type, ', '.join(paths))
172 if not i.IsSplitCurrent and (width + 7 + len(s) < 80): 174 if not i.IsSplitCurrent and (width + 7 + len(s) < 80):
diff --git a/subcmds/checkout.py b/subcmds/checkout.py
index 9b429489..768b6027 100644
--- a/subcmds/checkout.py
+++ b/subcmds/checkout.py
@@ -47,7 +47,7 @@ The command is equivalent to:
47 nb = args[0] 47 nb = args[0]
48 err = [] 48 err = []
49 success = [] 49 success = []
50 all_projects = self.GetProjects(args[1:]) 50 all_projects = self.GetProjects(args[1:], all_manifests=not opt.this_manifest_only)
51 51
52 def _ProcessResults(_pool, pm, results): 52 def _ProcessResults(_pool, pm, results):
53 for status, project in results: 53 for status, project in results:
diff --git a/subcmds/diff.py b/subcmds/diff.py
index 00a7ec29..a1f4ba88 100644
--- a/subcmds/diff.py
+++ b/subcmds/diff.py
@@ -50,7 +50,7 @@ to the Unix 'patch' command.
50 return (ret, buf.getvalue()) 50 return (ret, buf.getvalue())
51 51
52 def Execute(self, opt, args): 52 def Execute(self, opt, args):
53 all_projects = self.GetProjects(args) 53 all_projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
54 54
55 def _ProcessResults(_pool, _output, results): 55 def _ProcessResults(_pool, _output, results):
56 ret = 0 56 ret = 0
diff --git a/subcmds/diffmanifests.py b/subcmds/diffmanifests.py
index f6cc30a2..0e5f4108 100644
--- a/subcmds/diffmanifests.py
+++ b/subcmds/diffmanifests.py
@@ -179,6 +179,9 @@ synced and their revisions won't be found.
179 def ValidateOptions(self, opt, args): 179 def ValidateOptions(self, opt, args):
180 if not args or len(args) > 2: 180 if not args or len(args) > 2:
181 self.OptionParser.error('missing manifests to diff') 181 self.OptionParser.error('missing manifests to diff')
182 if opt.this_manifest_only is False:
183 raise self.OptionParser.error(
184 '`diffmanifest` only supports the current tree')
182 185
183 def Execute(self, opt, args): 186 def Execute(self, opt, args):
184 self.out = _Coloring(self.client.globalConfig) 187 self.out = _Coloring(self.client.globalConfig)
diff --git a/subcmds/download.py b/subcmds/download.py
index 523f25e0..15824843 100644
--- a/subcmds/download.py
+++ b/subcmds/download.py
@@ -48,7 +48,7 @@ If no project is specified try to use current directory as a project.
48 dest='ffonly', action='store_true', 48 dest='ffonly', action='store_true',
49 help="force fast-forward merge") 49 help="force fast-forward merge")
50 50
51 def _ParseChangeIds(self, args): 51 def _ParseChangeIds(self, opt, args):
52 if not args: 52 if not args:
53 self.Usage() 53 self.Usage()
54 54
@@ -77,7 +77,7 @@ If no project is specified try to use current directory as a project.
77 ps_id = max(int(match.group(1)), ps_id) 77 ps_id = max(int(match.group(1)), ps_id)
78 to_get.append((project, chg_id, ps_id)) 78 to_get.append((project, chg_id, ps_id))
79 else: 79 else:
80 projects = self.GetProjects([a]) 80 projects = self.GetProjects([a], all_manifests=not opt.this_manifest_only)
81 if len(projects) > 1: 81 if len(projects) > 1:
82 # If the cwd is one of the projects, assume they want that. 82 # If the cwd is one of the projects, assume they want that.
83 try: 83 try:
@@ -88,8 +88,8 @@ If no project is specified try to use current directory as a project.
88 print('error: %s matches too many projects; please re-run inside ' 88 print('error: %s matches too many projects; please re-run inside '
89 'the project checkout.' % (a,), file=sys.stderr) 89 'the project checkout.' % (a,), file=sys.stderr)
90 for project in projects: 90 for project in projects:
91 print(' %s/ @ %s' % (project.relpath, project.revisionExpr), 91 print(' %s/ @ %s' % (project.RelPath(local=opt.this_manifest_only),
92 file=sys.stderr) 92 project.revisionExpr), file=sys.stderr)
93 sys.exit(1) 93 sys.exit(1)
94 else: 94 else:
95 project = projects[0] 95 project = projects[0]
@@ -105,7 +105,7 @@ If no project is specified try to use current directory as a project.
105 self.OptionParser.error('-x and --ff are mutually exclusive options') 105 self.OptionParser.error('-x and --ff are mutually exclusive options')
106 106
107 def Execute(self, opt, args): 107 def Execute(self, opt, args):
108 for project, change_id, ps_id in self._ParseChangeIds(args): 108 for project, change_id, ps_id in self._ParseChangeIds(opt, args):
109 dl = project.DownloadPatchSet(change_id, ps_id) 109 dl = project.DownloadPatchSet(change_id, ps_id)
110 if not dl: 110 if not dl:
111 print('[%s] change %d/%d not found' 111 print('[%s] change %d/%d not found'
diff --git a/subcmds/forall.py b/subcmds/forall.py
index 7c1dea9e..cc578b52 100644
--- a/subcmds/forall.py
+++ b/subcmds/forall.py
@@ -168,6 +168,7 @@ without iterating through the remaining projects.
168 168
169 def Execute(self, opt, args): 169 def Execute(self, opt, args):
170 cmd = [opt.command[0]] 170 cmd = [opt.command[0]]
171 all_trees = not opt.this_manifest_only
171 172
172 shell = True 173 shell = True
173 if re.compile(r'^[a-z0-9A-Z_/\.-]+$').match(cmd[0]): 174 if re.compile(r'^[a-z0-9A-Z_/\.-]+$').match(cmd[0]):
@@ -213,11 +214,11 @@ without iterating through the remaining projects.
213 self.manifest.Override(smart_sync_manifest_path) 214 self.manifest.Override(smart_sync_manifest_path)
214 215
215 if opt.regex: 216 if opt.regex:
216 projects = self.FindProjects(args) 217 projects = self.FindProjects(args, all_manifests=all_trees)
217 elif opt.inverse_regex: 218 elif opt.inverse_regex:
218 projects = self.FindProjects(args, inverse=True) 219 projects = self.FindProjects(args, inverse=True, all_manifests=all_trees)
219 else: 220 else:
220 projects = self.GetProjects(args, groups=opt.groups) 221 projects = self.GetProjects(args, groups=opt.groups, all_manifests=all_trees)
221 222
222 os.environ['REPO_COUNT'] = str(len(projects)) 223 os.environ['REPO_COUNT'] = str(len(projects))
223 224
@@ -290,6 +291,7 @@ def DoWork(project, mirror, opt, cmd, shell, cnt, config):
290 291
291 setenv('REPO_PROJECT', project.name) 292 setenv('REPO_PROJECT', project.name)
292 setenv('REPO_PATH', project.relpath) 293 setenv('REPO_PATH', project.relpath)
294 setenv('REPO_OUTERPATH', project.RelPath(local=opt.this_manifest_only))
293 setenv('REPO_REMOTE', project.remote.name) 295 setenv('REPO_REMOTE', project.remote.name)
294 try: 296 try:
295 # If we aren't in a fully synced state and we don't have the ref the manifest 297 # If we aren't in a fully synced state and we don't have the ref the manifest
@@ -320,7 +322,7 @@ def DoWork(project, mirror, opt, cmd, shell, cnt, config):
320 output = '' 322 output = ''
321 if ((opt.project_header and opt.verbose) 323 if ((opt.project_header and opt.verbose)
322 or not opt.project_header): 324 or not opt.project_header):
323 output = 'skipping %s/' % project.relpath 325 output = 'skipping %s/' % project.RelPath(local=opt.this_manifest_only)
324 return (1, output) 326 return (1, output)
325 327
326 if opt.verbose: 328 if opt.verbose:
@@ -344,7 +346,7 @@ def DoWork(project, mirror, opt, cmd, shell, cnt, config):
344 if mirror: 346 if mirror:
345 project_header_path = project.name 347 project_header_path = project.name
346 else: 348 else:
347 project_header_path = project.relpath 349 project_header_path = project.RelPath(local=opt.this_manifest_only)
348 out.project('project %s/' % project_header_path) 350 out.project('project %s/' % project_header_path)
349 out.nl() 351 out.nl()
350 buf.write(output) 352 buf.write(output)
diff --git a/subcmds/gitc_init.py b/subcmds/gitc_init.py
index e705b613..1d81baf5 100644
--- a/subcmds/gitc_init.py
+++ b/subcmds/gitc_init.py
@@ -24,6 +24,7 @@ import wrapper
24 24
25class GitcInit(init.Init, GitcAvailableCommand): 25class GitcInit(init.Init, GitcAvailableCommand):
26 COMMON = True 26 COMMON = True
27 MULTI_MANIFEST_SUPPORT = False
27 helpSummary = "Initialize a GITC Client." 28 helpSummary = "Initialize a GITC Client."
28 helpUsage = """ 29 helpUsage = """
29%prog [options] [client name] 30%prog [options] [client name]
diff --git a/subcmds/grep.py b/subcmds/grep.py
index 8ac4ba14..93c9ae51 100644
--- a/subcmds/grep.py
+++ b/subcmds/grep.py
@@ -172,15 +172,16 @@ contain a line that matches both expressions:
172 return (project, p.Wait(), p.stdout, p.stderr) 172 return (project, p.Wait(), p.stdout, p.stderr)
173 173
174 @staticmethod 174 @staticmethod
175 def _ProcessResults(full_name, have_rev, _pool, out, results): 175 def _ProcessResults(full_name, have_rev, opt, _pool, out, results):
176 git_failed = False 176 git_failed = False
177 bad_rev = False 177 bad_rev = False
178 have_match = False 178 have_match = False
179 _RelPath = lambda p: p.RelPath(local=opt.this_manifest_only)
179 180
180 for project, rc, stdout, stderr in results: 181 for project, rc, stdout, stderr in results:
181 if rc < 0: 182 if rc < 0:
182 git_failed = True 183 git_failed = True
183 out.project('--- project %s ---' % project.relpath) 184 out.project('--- project %s ---' % _RelPath(project))
184 out.nl() 185 out.nl()
185 out.fail('%s', stderr) 186 out.fail('%s', stderr)
186 out.nl() 187 out.nl()
@@ -192,7 +193,7 @@ contain a line that matches both expressions:
192 if have_rev and 'fatal: ambiguous argument' in stderr: 193 if have_rev and 'fatal: ambiguous argument' in stderr:
193 bad_rev = True 194 bad_rev = True
194 else: 195 else:
195 out.project('--- project %s ---' % project.relpath) 196 out.project('--- project %s ---' % _RelPath(project))
196 out.nl() 197 out.nl()
197 out.fail('%s', stderr.strip()) 198 out.fail('%s', stderr.strip())
198 out.nl() 199 out.nl()
@@ -208,13 +209,13 @@ contain a line that matches both expressions:
208 rev, line = line.split(':', 1) 209 rev, line = line.split(':', 1)
209 out.write("%s", rev) 210 out.write("%s", rev)
210 out.write(':') 211 out.write(':')
211 out.project(project.relpath) 212 out.project(_RelPath(project))
212 out.write('/') 213 out.write('/')
213 out.write("%s", line) 214 out.write("%s", line)
214 out.nl() 215 out.nl()
215 elif full_name: 216 elif full_name:
216 for line in r: 217 for line in r:
217 out.project(project.relpath) 218 out.project(_RelPath(project))
218 out.write('/') 219 out.write('/')
219 out.write("%s", line) 220 out.write("%s", line)
220 out.nl() 221 out.nl()
@@ -239,7 +240,7 @@ contain a line that matches both expressions:
239 cmd_argv.append(args[0]) 240 cmd_argv.append(args[0])
240 args = args[1:] 241 args = args[1:]
241 242
242 projects = self.GetProjects(args) 243 projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
243 244
244 full_name = False 245 full_name = False
245 if len(projects) > 1: 246 if len(projects) > 1:
@@ -259,7 +260,7 @@ contain a line that matches both expressions:
259 opt.jobs, 260 opt.jobs,
260 functools.partial(self._ExecuteOne, cmd_argv), 261 functools.partial(self._ExecuteOne, cmd_argv),
261 projects, 262 projects,
262 callback=functools.partial(self._ProcessResults, full_name, have_rev), 263 callback=functools.partial(self._ProcessResults, full_name, have_rev, opt),
263 output=out, 264 output=out,
264 ordered=True) 265 ordered=True)
265 266
diff --git a/subcmds/info.py b/subcmds/info.py
index 6c1246ef..4bedf9d5 100644
--- a/subcmds/info.py
+++ b/subcmds/info.py
@@ -61,6 +61,8 @@ class Info(PagedCommand):
61 61
62 self.opt = opt 62 self.opt = opt
63 63
64 if not opt.this_manifest_only:
65 self.manifest = self.manifest.outer_client
64 manifestConfig = self.manifest.manifestProject.config 66 manifestConfig = self.manifest.manifestProject.config
65 mergeBranch = manifestConfig.GetBranch("default").merge 67 mergeBranch = manifestConfig.GetBranch("default").merge
66 manifestGroups = (manifestConfig.GetString('manifest.groups') 68 manifestGroups = (manifestConfig.GetString('manifest.groups')
@@ -80,17 +82,17 @@ class Info(PagedCommand):
80 self.printSeparator() 82 self.printSeparator()
81 83
82 if not opt.overview: 84 if not opt.overview:
83 self.printDiffInfo(args) 85 self._printDiffInfo(opt, args)
84 else: 86 else:
85 self.printCommitOverview(args) 87 self._printCommitOverview(opt, args)
86 88
87 def printSeparator(self): 89 def printSeparator(self):
88 self.text("----------------------------") 90 self.text("----------------------------")
89 self.out.nl() 91 self.out.nl()
90 92
91 def printDiffInfo(self, args): 93 def _printDiffInfo(self, opt, args):
92 # We let exceptions bubble up to main as they'll be well structured. 94 # We let exceptions bubble up to main as they'll be well structured.
93 projs = self.GetProjects(args) 95 projs = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
94 96
95 for p in projs: 97 for p in projs:
96 self.heading("Project: ") 98 self.heading("Project: ")
@@ -179,9 +181,9 @@ class Info(PagedCommand):
179 self.text(" ".join(split[1:])) 181 self.text(" ".join(split[1:]))
180 self.out.nl() 182 self.out.nl()
181 183
182 def printCommitOverview(self, args): 184 def _printCommitOverview(self, opt, args):
183 all_branches = [] 185 all_branches = []
184 for project in self.GetProjects(args): 186 for project in self.GetProjects(args, all_manifests=not opt.this_manifest_only):
185 br = [project.GetUploadableBranch(x) 187 br = [project.GetUploadableBranch(x)
186 for x in project.GetBranches()] 188 for x in project.GetBranches()]
187 br = [x for x in br if x] 189 br = [x for x in br if x]
@@ -200,7 +202,7 @@ class Info(PagedCommand):
200 if project != branch.project: 202 if project != branch.project:
201 project = branch.project 203 project = branch.project
202 self.out.nl() 204 self.out.nl()
203 self.headtext(project.relpath) 205 self.headtext(project.RelPath(local=opt.this_manifest_only))
204 self.out.nl() 206 self.out.nl()
205 207
206 commits = branch.commits 208 commits = branch.commits
diff --git a/subcmds/init.py b/subcmds/init.py
index 32c85f79..b9775a34 100644
--- a/subcmds/init.py
+++ b/subcmds/init.py
@@ -32,6 +32,7 @@ from wrapper import Wrapper
32 32
33class Init(InteractiveCommand, MirrorSafeCommand): 33class Init(InteractiveCommand, MirrorSafeCommand):
34 COMMON = True 34 COMMON = True
35 MULTI_MANIFEST_SUPPORT = False
35 helpSummary = "Initialize a repo client checkout in the current directory" 36 helpSummary = "Initialize a repo client checkout in the current directory"
36 helpUsage = """ 37 helpUsage = """
37%prog [options] [manifest url] 38%prog [options] [manifest url]
@@ -90,6 +91,17 @@ to update the working directory files.
90 91
91 def _Options(self, p, gitc_init=False): 92 def _Options(self, p, gitc_init=False):
92 Wrapper().InitParser(p, gitc_init=gitc_init) 93 Wrapper().InitParser(p, gitc_init=gitc_init)
94 m = p.add_option_group('Multi-manifest')
95 m.add_option('--outer-manifest', action='store_true',
96 help='operate starting at the outermost manifest')
97 m.add_option('--no-outer-manifest', dest='outer_manifest',
98 action='store_false', default=None,
99 help='do not operate on outer manifests')
100 m.add_option('--this-manifest-only', action='store_true', default=None,
101 help='only operate on this (sub)manifest')
102 m.add_option('--no-this-manifest-only', '--all-manifests',
103 dest='this_manifest_only', action='store_false',
104 help='operate on this manifest and its submanifests')
93 105
94 def _RegisteredEnvironmentOptions(self): 106 def _RegisteredEnvironmentOptions(self):
95 return {'REPO_MANIFEST_URL': 'manifest_url', 107 return {'REPO_MANIFEST_URL': 'manifest_url',
diff --git a/subcmds/list.py b/subcmds/list.py
index 6adf85b7..ad8036ee 100644
--- a/subcmds/list.py
+++ b/subcmds/list.py
@@ -77,16 +77,17 @@ This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'.
77 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.
78 """ 78 """
79 if not opt.regex: 79 if not opt.regex:
80 projects = self.GetProjects(args, groups=opt.groups, missing_ok=opt.all) 80 projects = self.GetProjects(args, groups=opt.groups, missing_ok=opt.all,
81 all_manifests=not opt.this_manifest_only)
81 else: 82 else:
82 projects = self.FindProjects(args) 83 projects = self.FindProjects(args, all_manifests=not opt.this_manifest_only)
83 84
84 def _getpath(x): 85 def _getpath(x):
85 if opt.fullpath: 86 if opt.fullpath:
86 return x.worktree 87 return x.worktree
87 if opt.relative_to: 88 if opt.relative_to:
88 return os.path.relpath(x.worktree, opt.relative_to) 89 return os.path.relpath(x.worktree, opt.relative_to)
89 return x.relpath 90 return x.RelPath(local=opt.this_manifest_only)
90 91
91 lines = [] 92 lines = []
92 for project in projects: 93 for project in projects:
diff --git a/subcmds/manifest.py b/subcmds/manifest.py
index 0fbdeac0..08905cb4 100644
--- a/subcmds/manifest.py
+++ b/subcmds/manifest.py
@@ -15,6 +15,7 @@
15import json 15import json
16import os 16import os
17import sys 17import sys
18import optparse
18 19
19from command import PagedCommand 20from command import PagedCommand
20 21
@@ -75,7 +76,7 @@ to indicate the remote ref to push changes to via 'repo upload'.
75 p.add_option('-o', '--output-file', 76 p.add_option('-o', '--output-file',
76 dest='output_file', 77 dest='output_file',
77 default='-', 78 default='-',
78 help='file to save the manifest to', 79 help='file to save the manifest to. (Filename prefix for multi-tree.)',
79 metavar='-|NAME.xml') 80 metavar='-|NAME.xml')
80 81
81 def _Output(self, opt): 82 def _Output(self, opt):
@@ -83,36 +84,45 @@ to indicate the remote ref to push changes to via 'repo upload'.
83 if opt.manifest_name: 84 if opt.manifest_name:
84 self.manifest.Override(opt.manifest_name, False) 85 self.manifest.Override(opt.manifest_name, False)
85 86
86 if opt.output_file == '-': 87 for manifest in self.ManifestList(opt):
87 fd = sys.stdout 88 output_file = opt.output_file
88 else: 89 if output_file == '-':
89 fd = open(opt.output_file, 'w') 90 fd = sys.stdout
90 91 else:
91 self.manifest.SetUseLocalManifests(not opt.ignore_local_manifests) 92 if manifest.path_prefix:
92 93 output_file = f'{opt.output_file}:{manifest.path_prefix.replace("/", "%2f")}'
93 if opt.json: 94 fd = open(output_file, 'w')
94 print('warning: --json is experimental!', file=sys.stderr) 95
95 doc = self.manifest.ToDict(peg_rev=opt.peg_rev, 96 manifest.SetUseLocalManifests(not opt.ignore_local_manifests)
96 peg_rev_upstream=opt.peg_rev_upstream, 97
97 peg_rev_dest_branch=opt.peg_rev_dest_branch) 98 if opt.json:
98 99 print('warning: --json is experimental!', file=sys.stderr)
99 json_settings = { 100 doc = manifest.ToDict(peg_rev=opt.peg_rev,
100 # JSON style guide says Uunicode characters are fully allowed. 101 peg_rev_upstream=opt.peg_rev_upstream,
101 'ensure_ascii': False, 102 peg_rev_dest_branch=opt.peg_rev_dest_branch)
102 # We use 2 space indent to match JSON style guide. 103
103 'indent': 2 if opt.pretty else None, 104 json_settings = {
104 'separators': (',', ': ') if opt.pretty else (',', ':'), 105 # JSON style guide says Uunicode characters are fully allowed.
105 'sort_keys': True, 106 'ensure_ascii': False,
106 } 107 # We use 2 space indent to match JSON style guide.
107 fd.write(json.dumps(doc, **json_settings)) 108 'indent': 2 if opt.pretty else None,
108 else: 109 'separators': (',', ': ') if opt.pretty else (',', ':'),
109 self.manifest.Save(fd, 110 'sort_keys': True,
110 peg_rev=opt.peg_rev, 111 }
111 peg_rev_upstream=opt.peg_rev_upstream, 112 fd.write(json.dumps(doc, **json_settings))
112 peg_rev_dest_branch=opt.peg_rev_dest_branch) 113 else:
113 fd.close() 114 manifest.Save(fd,
114 if opt.output_file != '-': 115 peg_rev=opt.peg_rev,
115 print('Saved manifest to %s' % opt.output_file, file=sys.stderr) 116 peg_rev_upstream=opt.peg_rev_upstream,
117 peg_rev_dest_branch=opt.peg_rev_dest_branch)
118 if output_file != '-':
119 fd.close()
120 if manifest.path_prefix:
121 print(f'Saved {manifest.path_prefix} submanifest to {output_file}',
122 file=sys.stderr)
123 else:
124 print(f'Saved manifest to {output_file}', file=sys.stderr)
125
116 126
117 def ValidateOptions(self, opt, args): 127 def ValidateOptions(self, opt, args):
118 if args: 128 if args:
diff --git a/subcmds/overview.py b/subcmds/overview.py
index 63f5a79e..11dba95f 100644
--- a/subcmds/overview.py
+++ b/subcmds/overview.py
@@ -47,7 +47,7 @@ are displayed.
47 47
48 def Execute(self, opt, args): 48 def Execute(self, opt, args):
49 all_branches = [] 49 all_branches = []
50 for project in self.GetProjects(args): 50 for project in self.GetProjects(args, all_manifests=not opt.this_manifest_only):
51 br = [project.GetUploadableBranch(x) 51 br = [project.GetUploadableBranch(x)
52 for x in project.GetBranches()] 52 for x in project.GetBranches()]
53 br = [x for x in br if x] 53 br = [x for x in br if x]
@@ -76,7 +76,7 @@ are displayed.
76 if project != branch.project: 76 if project != branch.project:
77 project = branch.project 77 project = branch.project
78 out.nl() 78 out.nl()
79 out.project('project %s/' % project.relpath) 79 out.project('project %s/' % project.RelPath(local=opt.this_manifest_only))
80 out.nl() 80 out.nl()
81 81
82 commits = branch.commits 82 commits = branch.commits
diff --git a/subcmds/prune.py b/subcmds/prune.py
index 584ee7ed..251accaa 100644
--- a/subcmds/prune.py
+++ b/subcmds/prune.py
@@ -31,7 +31,7 @@ class Prune(PagedCommand):
31 return project.PruneHeads() 31 return project.PruneHeads()
32 32
33 def Execute(self, opt, args): 33 def Execute(self, opt, args):
34 projects = self.GetProjects(args) 34 projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
35 35
36 # NB: Should be able to refactor this module to display summary as results 36 # NB: Should be able to refactor this module to display summary as results
37 # come back from children. 37 # come back from children.
@@ -63,7 +63,7 @@ class Prune(PagedCommand):
63 if project != branch.project: 63 if project != branch.project:
64 project = branch.project 64 project = branch.project
65 out.nl() 65 out.nl()
66 out.project('project %s/' % project.relpath) 66 out.project('project %s/' % project.RelPath(local=opt.this_manifest_only))
67 out.nl() 67 out.nl()
68 68
69 print('%s %-33s ' % ( 69 print('%s %-33s ' % (
diff --git a/subcmds/rebase.py b/subcmds/rebase.py
index 7c53eb7a..3d1a63e4 100644
--- a/subcmds/rebase.py
+++ b/subcmds/rebase.py
@@ -69,7 +69,7 @@ branch but need to incorporate new upstream changes "underneath" them.
69 'consistent if you previously synced to a manifest)') 69 'consistent if you previously synced to a manifest)')
70 70
71 def Execute(self, opt, args): 71 def Execute(self, opt, args):
72 all_projects = self.GetProjects(args) 72 all_projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
73 one_project = len(all_projects) == 1 73 one_project = len(all_projects) == 1
74 74
75 if opt.interactive and not one_project: 75 if opt.interactive and not one_project:
@@ -98,6 +98,7 @@ branch but need to incorporate new upstream changes "underneath" them.
98 config = self.manifest.manifestProject.config 98 config = self.manifest.manifestProject.config
99 out = RebaseColoring(config) 99 out = RebaseColoring(config)
100 out.redirect(sys.stdout) 100 out.redirect(sys.stdout)
101 _RelPath = lambda p: p.RelPath(local=opt.this_manifest_only)
101 102
102 ret = 0 103 ret = 0
103 for project in all_projects: 104 for project in all_projects:
@@ -107,7 +108,7 @@ branch but need to incorporate new upstream changes "underneath" them.
107 cb = project.CurrentBranch 108 cb = project.CurrentBranch
108 if not cb: 109 if not cb:
109 if one_project: 110 if one_project:
110 print("error: project %s has a detached HEAD" % project.relpath, 111 print("error: project %s has a detached HEAD" % _RelPath(project),
111 file=sys.stderr) 112 file=sys.stderr)
112 return 1 113 return 1
113 # ignore branches with detatched HEADs 114 # ignore branches with detatched HEADs
@@ -117,7 +118,7 @@ branch but need to incorporate new upstream changes "underneath" them.
117 if not upbranch.LocalMerge: 118 if not upbranch.LocalMerge:
118 if one_project: 119 if one_project:
119 print("error: project %s does not track any remote branches" 120 print("error: project %s does not track any remote branches"
120 % project.relpath, file=sys.stderr) 121 % _RelPath(project), file=sys.stderr)
121 return 1 122 return 1
122 # ignore branches without remotes 123 # ignore branches without remotes
123 continue 124 continue
@@ -130,7 +131,7 @@ branch but need to incorporate new upstream changes "underneath" them.
130 args.append(upbranch.LocalMerge) 131 args.append(upbranch.LocalMerge)
131 132
132 out.project('project %s: rebasing %s -> %s', 133 out.project('project %s: rebasing %s -> %s',
133 project.relpath, cb, upbranch.LocalMerge) 134 _RelPath(project), cb, upbranch.LocalMerge)
134 out.nl() 135 out.nl()
135 out.flush() 136 out.flush()
136 137
diff --git a/subcmds/stage.py b/subcmds/stage.py
index 0389a4ff..5f17cb64 100644
--- a/subcmds/stage.py
+++ b/subcmds/stage.py
@@ -50,7 +50,9 @@ The '%prog' command stages files to prepare the next commit.
50 self.Usage() 50 self.Usage()
51 51
52 def _Interactive(self, opt, args): 52 def _Interactive(self, opt, args):
53 all_projects = [p for p in self.GetProjects(args) if p.IsDirty()] 53 all_projects = [
54 p for p in self.GetProjects(args, all_manifests=not opt.this_manifest_only)
55 if p.IsDirty()]
54 if not all_projects: 56 if not all_projects:
55 print('no projects have uncommitted modifications', file=sys.stderr) 57 print('no projects have uncommitted modifications', file=sys.stderr)
56 return 58 return
@@ -62,7 +64,8 @@ The '%prog' command stages files to prepare the next commit.
62 64
63 for i in range(len(all_projects)): 65 for i in range(len(all_projects)):
64 project = all_projects[i] 66 project = all_projects[i]
65 out.write('%3d: %s', i + 1, project.relpath + '/') 67 out.write('%3d: %s', i + 1,
68 project.RelPath(local=opt.this_manifest_only) + '/')
66 out.nl() 69 out.nl()
67 out.nl() 70 out.nl()
68 71
@@ -99,7 +102,9 @@ The '%prog' command stages files to prepare the next commit.
99 _AddI(all_projects[a_index - 1]) 102 _AddI(all_projects[a_index - 1])
100 continue 103 continue
101 104
102 projects = [p for p in all_projects if a in [p.name, p.relpath]] 105 projects = [
106 p for p in all_projects
107 if a in [p.name, p.RelPath(local=opt.this_manifest_only)]]
103 if len(projects) == 1: 108 if len(projects) == 1:
104 _AddI(projects[0]) 109 _AddI(projects[0])
105 continue 110 continue
diff --git a/subcmds/start.py b/subcmds/start.py
index 2addaf2e..809df963 100644
--- a/subcmds/start.py
+++ b/subcmds/start.py
@@ -84,7 +84,8 @@ revision specified in the manifest.
84 projects = ['.'] # start it in the local project by default 84 projects = ['.'] # start it in the local project by default
85 85
86 all_projects = self.GetProjects(projects, 86 all_projects = self.GetProjects(projects,
87 missing_ok=bool(self.gitc_manifest)) 87 missing_ok=bool(self.gitc_manifest),
88 all_manifests=not opt.this_manifest_only)
88 89
89 # This must happen after we find all_projects, since GetProjects may need 90 # This must happen after we find all_projects, since GetProjects may need
90 # the local directory, which will disappear once we save the GITC manifest. 91 # the local directory, which will disappear once we save the GITC manifest.
@@ -137,6 +138,6 @@ revision specified in the manifest.
137 138
138 if err: 139 if err:
139 for p in err: 140 for p in err:
140 print("error: %s/: cannot start %s" % (p.relpath, nb), 141 print("error: %s/: cannot start %s" % (p.RelPath(local=opt.this_manifest_only), nb),
141 file=sys.stderr) 142 file=sys.stderr)
142 sys.exit(1) 143 sys.exit(1)
diff --git a/subcmds/status.py b/subcmds/status.py
index 5b669547..0aa4200f 100644
--- a/subcmds/status.py
+++ b/subcmds/status.py
@@ -117,7 +117,7 @@ the following meanings:
117 outstring.append(''.join([status_header, item, '/'])) 117 outstring.append(''.join([status_header, item, '/']))
118 118
119 def Execute(self, opt, args): 119 def Execute(self, opt, args):
120 all_projects = self.GetProjects(args) 120 all_projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
121 121
122 def _ProcessResults(_pool, _output, results): 122 def _ProcessResults(_pool, _output, results):
123 ret = 0 123 ret = 0
@@ -141,9 +141,10 @@ the following meanings:
141 if opt.orphans: 141 if opt.orphans:
142 proj_dirs = set() 142 proj_dirs = set()
143 proj_dirs_parents = set() 143 proj_dirs_parents = set()
144 for project in self.GetProjects(None, missing_ok=True): 144 for project in self.GetProjects(None, missing_ok=True, all_manifests=not opt.this_manifest_only):
145 proj_dirs.add(project.relpath) 145 relpath = project.RelPath(local=opt.this_manifest_only)
146 (head, _tail) = os.path.split(project.relpath) 146 proj_dirs.add(relpath)
147 (head, _tail) = os.path.split(relpath)
147 while head != "": 148 while head != "":
148 proj_dirs_parents.add(head) 149 proj_dirs_parents.add(head)
149 (head, _tail) = os.path.split(head) 150 (head, _tail) = os.path.split(head)
diff --git a/subcmds/sync.py b/subcmds/sync.py
index 707c5bbd..f5584dc8 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -66,6 +66,7 @@ _ONE_DAY_S = 24 * 60 * 60
66class Sync(Command, MirrorSafeCommand): 66class Sync(Command, MirrorSafeCommand):
67 jobs = 1 67 jobs = 1
68 COMMON = True 68 COMMON = True
69 MULTI_MANIFEST_SUPPORT = False
69 helpSummary = "Update working tree to the latest revision" 70 helpSummary = "Update working tree to the latest revision"
70 helpUsage = """ 71 helpUsage = """
71%prog [<project>...] 72%prog [<project>...]
@@ -704,7 +705,7 @@ later is required to fix a server side protocol bug.
704 if project.relpath: 705 if project.relpath:
705 new_project_paths.append(project.relpath) 706 new_project_paths.append(project.relpath)
706 file_name = 'project.list' 707 file_name = 'project.list'
707 file_path = os.path.join(self.repodir, file_name) 708 file_path = os.path.join(self.manifest.subdir, file_name)
708 old_project_paths = [] 709 old_project_paths = []
709 710
710 if os.path.exists(file_path): 711 if os.path.exists(file_path):
@@ -760,7 +761,7 @@ later is required to fix a server side protocol bug.
760 } 761 }
761 762
762 copylinkfile_name = 'copy-link-files.json' 763 copylinkfile_name = 'copy-link-files.json'
763 copylinkfile_path = os.path.join(self.manifest.repodir, copylinkfile_name) 764 copylinkfile_path = os.path.join(self.manifest.subdir, copylinkfile_name)
764 old_copylinkfile_paths = {} 765 old_copylinkfile_paths = {}
765 766
766 if os.path.exists(copylinkfile_path): 767 if os.path.exists(copylinkfile_path):
@@ -932,6 +933,9 @@ later is required to fix a server side protocol bug.
932 if opt.prune is None: 933 if opt.prune is None:
933 opt.prune = True 934 opt.prune = True
934 935
936 if self.manifest.is_multimanifest and not opt.this_manifest_only and args:
937 self.OptionParser.error('partial syncs must use --this-manifest-only')
938
935 def Execute(self, opt, args): 939 def Execute(self, opt, args):
936 if opt.jobs: 940 if opt.jobs:
937 self.jobs = opt.jobs 941 self.jobs = opt.jobs
diff --git a/subcmds/upload.py b/subcmds/upload.py
index c48deab6..ef3d8e9d 100644
--- a/subcmds/upload.py
+++ b/subcmds/upload.py
@@ -226,7 +226,8 @@ Gerrit Code Review: https://www.gerritcodereview.com/
226 226
227 destination = opt.dest_branch or project.dest_branch or project.revisionExpr 227 destination = opt.dest_branch or project.dest_branch or project.revisionExpr
228 print('Upload project %s/ to remote branch %s%s:' % 228 print('Upload project %s/ to remote branch %s%s:' %
229 (project.relpath, destination, ' (private)' if opt.private else '')) 229 (project.RelPath(local=opt.this_manifest_only), destination,
230 ' (private)' if opt.private else ''))
230 print(' branch %s (%2d commit%s, %s):' % ( 231 print(' branch %s (%2d commit%s, %s):' % (
231 name, 232 name,
232 len(commit_list), 233 len(commit_list),
@@ -262,7 +263,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
262 script.append('# Uncomment the branches to upload:') 263 script.append('# Uncomment the branches to upload:')
263 for project, avail in pending: 264 for project, avail in pending:
264 script.append('#') 265 script.append('#')
265 script.append('# project %s/:' % project.relpath) 266 script.append('# project %s/:' % project.RelPath(local=opt.this_manifest_only))
266 267
267 b = {} 268 b = {}
268 for branch in avail: 269 for branch in avail:
@@ -285,7 +286,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
285 script.append('# %s' % commit) 286 script.append('# %s' % commit)
286 b[name] = branch 287 b[name] = branch
287 288
288 projects[project.relpath] = project 289 projects[project.RelPath(local=opt.this_manifest_only)] = project
289 branches[project.name] = b 290 branches[project.name] = b
290 script.append('') 291 script.append('')
291 292
@@ -313,7 +314,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
313 _die('project for branch %s not in script', name) 314 _die('project for branch %s not in script', name)
314 branch = branches[project.name].get(name) 315 branch = branches[project.name].get(name)
315 if not branch: 316 if not branch:
316 _die('branch %s not in %s', name, project.relpath) 317 _die('branch %s not in %s', name, project.RelPath(local=opt.this_manifest_only))
317 todo.append(branch) 318 todo.append(branch)
318 if not todo: 319 if not todo:
319 _die("nothing uncommented for upload") 320 _die("nothing uncommented for upload")
@@ -481,7 +482,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
481 else: 482 else:
482 fmt = '\n (%s)' 483 fmt = '\n (%s)'
483 print(('[FAILED] %-15s %-15s' + fmt) % ( 484 print(('[FAILED] %-15s %-15s' + fmt) % (
484 branch.project.relpath + '/', 485 branch.project.RelPath(local=opt.this_manifest_only) + '/',
485 branch.name, 486 branch.name,
486 str(branch.error)), 487 str(branch.error)),
487 file=sys.stderr) 488 file=sys.stderr)
@@ -490,7 +491,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
490 for branch in todo: 491 for branch in todo:
491 if branch.uploaded: 492 if branch.uploaded:
492 print('[OK ] %-15s %s' % ( 493 print('[OK ] %-15s %s' % (
493 branch.project.relpath + '/', 494 branch.project.RelPath(local=opt.this_manifest_only) + '/',
494 branch.name), 495 branch.name),
495 file=sys.stderr) 496 file=sys.stderr)
496 497
@@ -524,7 +525,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
524 return (project, avail) 525 return (project, avail)
525 526
526 def Execute(self, opt, args): 527 def Execute(self, opt, args):
527 projects = self.GetProjects(args) 528 projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
528 529
529 def _ProcessResults(_pool, _out, results): 530 def _ProcessResults(_pool, _out, results):
530 pending = [] 531 pending = []
@@ -534,7 +535,8 @@ Gerrit Code Review: https://www.gerritcodereview.com/
534 print('repo: error: %s: Unable to upload branch "%s". ' 535 print('repo: error: %s: Unable to upload branch "%s". '
535 'You might be able to fix the branch by running:\n' 536 'You might be able to fix the branch by running:\n'
536 ' git branch --set-upstream-to m/%s' % 537 ' git branch --set-upstream-to m/%s' %
537 (project.relpath, project.CurrentBranch, self.manifest.branch), 538 (project.RelPath(local=opt.this_manifest_only), project.CurrentBranch,
539 project.manifest.branch),
538 file=sys.stderr) 540 file=sys.stderr)
539 elif avail: 541 elif avail:
540 pending.append(result) 542 pending.append(result)
@@ -554,15 +556,23 @@ Gerrit Code Review: https://www.gerritcodereview.com/
554 (opt.branch,), file=sys.stderr) 556 (opt.branch,), file=sys.stderr)
555 return 1 557 return 1
556 558
557 pending_proj_names = [project.name for (project, available) in pending] 559 manifests = {project.manifest.topdir: project.manifest
558 pending_worktrees = [project.worktree for (project, available) in pending] 560 for (project, available) in pending}
559 hook = RepoHook.FromSubcmd( 561 ret = 0
560 hook_type='pre-upload', manifest=self.manifest, 562 for manifest in manifests.values():
561 opt=opt, abort_if_user_denies=True) 563 pending_proj_names = [project.name for (project, available) in pending
562 if not hook.Run( 564 if project.manifest.topdir == manifest.topdir]
563 project_list=pending_proj_names, 565 pending_worktrees = [project.worktree for (project, available) in pending
564 worktree_list=pending_worktrees): 566 if project.manifest.topdir == manifest.topdir]
565 return 1 567 hook = RepoHook.FromSubcmd(
568 hook_type='pre-upload', manifest=manifest,
569 opt=opt, abort_if_user_denies=True)
570 if not hook.Run(
571 project_list=pending_proj_names,
572 worktree_list=pending_worktrees):
573 ret = 1
574 if ret:
575 return ret
566 576
567 reviewers = _SplitEmails(opt.reviewers) if opt.reviewers else [] 577 reviewers = _SplitEmails(opt.reviewers) if opt.reviewers else []
568 cc = _SplitEmails(opt.cc) if opt.cc else [] 578 cc = _SplitEmails(opt.cc) if opt.cc else []