summaryrefslogtreecommitdiffstats
path: root/subcmds
diff options
context:
space:
mode:
Diffstat (limited to 'subcmds')
-rw-r--r--subcmds/abandon.py27
-rw-r--r--subcmds/checkout.py24
-rw-r--r--subcmds/cherry_pick.py114
-rw-r--r--subcmds/diff.py15
-rw-r--r--subcmds/download.py21
-rw-r--r--subcmds/forall.py7
-rw-r--r--subcmds/help.py2
-rw-r--r--subcmds/init.py173
-rw-r--r--subcmds/list.py48
-rw-r--r--subcmds/manifest.py81
-rw-r--r--subcmds/overview.py80
-rw-r--r--subcmds/rebase.py23
-rw-r--r--subcmds/start.py5
-rw-r--r--subcmds/status.py86
-rw-r--r--subcmds/sync.py200
-rw-r--r--subcmds/upload.py96
-rw-r--r--subcmds/version.py8
17 files changed, 778 insertions, 232 deletions
diff --git a/subcmds/abandon.py b/subcmds/abandon.py
index 8af61327..42abb2ff 100644
--- a/subcmds/abandon.py
+++ b/subcmds/abandon.py
@@ -41,21 +41,30 @@ It is equivalent to "git branch -D <branchname>".
41 41
42 nb = args[0] 42 nb = args[0]
43 err = [] 43 err = []
44 success = []
44 all = self.GetProjects(args[1:]) 45 all = self.GetProjects(args[1:])
45 46
46 pm = Progress('Abandon %s' % nb, len(all)) 47 pm = Progress('Abandon %s' % nb, len(all))
47 for project in all: 48 for project in all:
48 pm.update() 49 pm.update()
49 if not project.AbandonBranch(nb): 50
50 err.append(project) 51 status = project.AbandonBranch(nb)
52 if status is not None:
53 if status:
54 success.append(project)
55 else:
56 err.append(project)
51 pm.end() 57 pm.end()
52 58
53 if err: 59 if err:
54 if len(err) == len(all): 60 for p in err:
55 print >>sys.stderr, 'error: no project has branch %s' % nb 61 print >>sys.stderr,\
56 else: 62 "error: %s/: cannot abandon %s" \
57 for p in err: 63 % (p.relpath, nb)
58 print >>sys.stderr,\ 64 sys.exit(1)
59 "error: %s/: cannot abandon %s" \ 65 elif not success:
60 % (p.relpath, nb) 66 print >>sys.stderr, 'error: no project has branch %s' % nb
61 sys.exit(1) 67 sys.exit(1)
68 else:
69 print >>sys.stderr, 'Abandoned in %d project(s):\n %s' % (
70 len(success), '\n '.join(p.relpath for p in success))
diff --git a/subcmds/checkout.py b/subcmds/checkout.py
index 4198acd1..533d20e1 100644
--- a/subcmds/checkout.py
+++ b/subcmds/checkout.py
@@ -38,21 +38,27 @@ The command is equivalent to:
38 38
39 nb = args[0] 39 nb = args[0]
40 err = [] 40 err = []
41 success = []
41 all = self.GetProjects(args[1:]) 42 all = self.GetProjects(args[1:])
42 43
43 pm = Progress('Checkout %s' % nb, len(all)) 44 pm = Progress('Checkout %s' % nb, len(all))
44 for project in all: 45 for project in all:
45 pm.update() 46 pm.update()
46 if not project.CheckoutBranch(nb): 47
47 err.append(project) 48 status = project.CheckoutBranch(nb)
49 if status is not None:
50 if status:
51 success.append(project)
52 else:
53 err.append(project)
48 pm.end() 54 pm.end()
49 55
50 if err: 56 if err:
51 if len(err) == len(all): 57 for p in err:
52 print >>sys.stderr, 'error: no project has branch %s' % nb 58 print >>sys.stderr,\
53 else: 59 "error: %s/: cannot checkout %s" \
54 for p in err: 60 % (p.relpath, nb)
55 print >>sys.stderr,\ 61 sys.exit(1)
56 "error: %s/: cannot checkout %s" \ 62 elif not success:
57 % (p.relpath, nb) 63 print >>sys.stderr, 'error: no project has branch %s' % nb
58 sys.exit(1) 64 sys.exit(1)
diff --git a/subcmds/cherry_pick.py b/subcmds/cherry_pick.py
new file mode 100644
index 00000000..8da3a750
--- /dev/null
+++ b/subcmds/cherry_pick.py
@@ -0,0 +1,114 @@
1#
2# Copyright (C) 2010 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import sys, re, string, random, os
17from command import Command
18from git_command import GitCommand
19
20CHANGE_ID_RE = re.compile(r'^\s*Change-Id: I([0-9a-f]{40})\s*$')
21
22class CherryPick(Command):
23 common = True
24 helpSummary = "Cherry-pick a change."
25 helpUsage = """
26%prog <sha1>
27"""
28 helpDescription = """
29'%prog' cherry-picks a change from one branch to another.
30The change id will be updated, and a reference to the old
31change id will be added.
32"""
33
34 def _Options(self, p):
35 pass
36
37 def Execute(self, opt, args):
38 if len(args) != 1:
39 self.Usage()
40
41 reference = args[0]
42
43 p = GitCommand(None,
44 ['rev-parse', '--verify', reference],
45 capture_stdout = True,
46 capture_stderr = True)
47 if p.Wait() != 0:
48 print >>sys.stderr, p.stderr
49 sys.exit(1)
50 sha1 = p.stdout.strip()
51
52 p = GitCommand(None, ['cat-file', 'commit', sha1], capture_stdout=True)
53 if p.Wait() != 0:
54 print >>sys.stderr, "error: Failed to retrieve old commit message"
55 sys.exit(1)
56 old_msg = self._StripHeader(p.stdout)
57
58 p = GitCommand(None,
59 ['cherry-pick', sha1],
60 capture_stdout = True,
61 capture_stderr = True)
62 status = p.Wait()
63
64 print >>sys.stdout, p.stdout
65 print >>sys.stderr, p.stderr
66
67 if status == 0:
68 # The cherry-pick was applied correctly. We just need to edit the
69 # commit message.
70 new_msg = self._Reformat(old_msg, sha1)
71
72 p = GitCommand(None, ['commit', '--amend', '-F', '-'],
73 provide_stdin = True,
74 capture_stdout = True,
75 capture_stderr = True)
76 p.stdin.write(new_msg)
77 if p.Wait() != 0:
78 print >>sys.stderr, "error: Failed to update commit message"
79 sys.exit(1)
80
81 else:
82 print >>sys.stderr, """\
83NOTE: When committing (please see above) and editing the commit message,
84please remove the old Change-Id-line and add:
85"""
86 print >>sys.stderr, self._GetReference(sha1)
87 print >>sys.stderr
88
89 def _IsChangeId(self, line):
90 return CHANGE_ID_RE.match(line)
91
92 def _GetReference(self, sha1):
93 return "(cherry picked from commit %s)" % sha1
94
95 def _StripHeader(self, commit_msg):
96 lines = commit_msg.splitlines()
97 return "\n".join(lines[lines.index("")+1:])
98
99 def _Reformat(self, old_msg, sha1):
100 new_msg = []
101
102 for line in old_msg.splitlines():
103 if not self._IsChangeId(line):
104 new_msg.append(line)
105
106 # Add a blank line between the message and the change id/reference
107 try:
108 if new_msg[-1].strip() != "":
109 new_msg.append("")
110 except IndexError:
111 pass
112
113 new_msg.append(self._GetReference(sha1))
114 return "\n".join(new_msg)
diff --git a/subcmds/diff.py b/subcmds/diff.py
index e0247140..f233f690 100644
--- a/subcmds/diff.py
+++ b/subcmds/diff.py
@@ -20,8 +20,21 @@ class Diff(PagedCommand):
20 helpSummary = "Show changes between commit and working tree" 20 helpSummary = "Show changes between commit and working tree"
21 helpUsage = """ 21 helpUsage = """
22%prog [<project>...] 22%prog [<project>...]
23
24The -u option causes '%prog' to generate diff output with file paths
25relative to the repository root, so the output can be applied
26to the Unix 'patch' command.
23""" 27"""
24 28
29 def _Options(self, p):
30 def cmd(option, opt_str, value, parser):
31 setattr(parser.values, option.dest, list(parser.rargs))
32 while parser.rargs:
33 del parser.rargs[0]
34 p.add_option('-u', '--absolute',
35 dest='absolute', action='store_true',
36 help='Paths are relative to the repository root')
37
25 def Execute(self, opt, args): 38 def Execute(self, opt, args):
26 for project in self.GetProjects(args): 39 for project in self.GetProjects(args):
27 project.PrintWorkTreeDiff() 40 project.PrintWorkTreeDiff(opt.absolute)
diff --git a/subcmds/download.py b/subcmds/download.py
index 61eadd54..0ea45c3f 100644
--- a/subcmds/download.py
+++ b/subcmds/download.py
@@ -33,7 +33,15 @@ makes it available in your project's local working directory.
33""" 33"""
34 34
35 def _Options(self, p): 35 def _Options(self, p):
36 pass 36 p.add_option('-c','--cherry-pick',
37 dest='cherrypick', action='store_true',
38 help="cherry-pick instead of checkout")
39 p.add_option('-r','--revert',
40 dest='revert', action='store_true',
41 help="revert instead of checkout")
42 p.add_option('-f','--ff-only',
43 dest='ffonly', action='store_true',
44 help="force fast-forward merge")
37 45
38 def _ParseChangeIds(self, args): 46 def _ParseChangeIds(self, args):
39 if not args: 47 if not args:
@@ -66,7 +74,7 @@ makes it available in your project's local working directory.
66 % (project.name, change_id, ps_id) 74 % (project.name, change_id, ps_id)
67 sys.exit(1) 75 sys.exit(1)
68 76
69 if not dl.commits: 77 if not opt.revert and not dl.commits:
70 print >>sys.stderr, \ 78 print >>sys.stderr, \
71 '[%s] change %d/%d has already been merged' \ 79 '[%s] change %d/%d has already been merged' \
72 % (project.name, change_id, ps_id) 80 % (project.name, change_id, ps_id)
@@ -78,4 +86,11 @@ makes it available in your project's local working directory.
78 % (project.name, change_id, ps_id, len(dl.commits)) 86 % (project.name, change_id, ps_id, len(dl.commits))
79 for c in dl.commits: 87 for c in dl.commits:
80 print >>sys.stderr, ' %s' % (c) 88 print >>sys.stderr, ' %s' % (c)
81 project._Checkout(dl.commit) 89 if opt.cherrypick:
90 project._CherryPick(dl.commit)
91 elif opt.revert:
92 project._Revert(dl.commit)
93 elif opt.ffonly:
94 project._FastForward(dl.commit, ffonly=True)
95 else:
96 project._Checkout(dl.commit)
diff --git a/subcmds/forall.py b/subcmds/forall.py
index d3e70ae1..9436f4e5 100644
--- a/subcmds/forall.py
+++ b/subcmds/forall.py
@@ -82,6 +82,11 @@ revision to a locally executed git command, use REPO_LREV.
82REPO_RREV is the name of the revision from the manifest, exactly 82REPO_RREV is the name of the revision from the manifest, exactly
83as written in the manifest. 83as written in the manifest.
84 84
85REPO__* are any extra environment variables, specified by the
86"annotation" element under any project element. This can be useful
87for differentiating trees based on user-specific criteria, or simply
88annotating tree details.
89
85shell positional arguments ($1, $2, .., $#) are set to any arguments 90shell positional arguments ($1, $2, .., $#) are set to any arguments
86following <command>. 91following <command>.
87 92
@@ -162,6 +167,8 @@ terminal and are not redirected.
162 setenv('REPO_REMOTE', project.remote.name) 167 setenv('REPO_REMOTE', project.remote.name)
163 setenv('REPO_LREV', project.GetRevisionId()) 168 setenv('REPO_LREV', project.GetRevisionId())
164 setenv('REPO_RREV', project.revisionExpr) 169 setenv('REPO_RREV', project.revisionExpr)
170 for a in project.annotations:
171 setenv("REPO__%s" % (a.name), a.value)
165 172
166 if mirror: 173 if mirror:
167 setenv('GIT_DIR', project.gitdir) 174 setenv('GIT_DIR', project.gitdir)
diff --git a/subcmds/help.py b/subcmds/help.py
index e2f3074c..0df3c14b 100644
--- a/subcmds/help.py
+++ b/subcmds/help.py
@@ -165,7 +165,7 @@ See 'repo help --all' for a complete list of recognized commands.
165 print >>sys.stderr, "repo: '%s' is not a repo command." % name 165 print >>sys.stderr, "repo: '%s' is not a repo command." % name
166 sys.exit(1) 166 sys.exit(1)
167 167
168 cmd.repodir = self.repodir 168 cmd.manifest = self.manifest
169 self._PrintCommandHelp(cmd) 169 self._PrintCommandHelp(cmd)
170 170
171 else: 171 else:
diff --git a/subcmds/init.py b/subcmds/init.py
index 2ca4e163..a758fbb1 100644
--- a/subcmds/init.py
+++ b/subcmds/init.py
@@ -14,16 +14,17 @@
14# limitations under the License. 14# limitations under the License.
15 15
16import os 16import os
17import platform
18import re
19import shutil
17import sys 20import sys
18 21
19from color import Coloring 22from color import Coloring
20from command import InteractiveCommand, MirrorSafeCommand 23from command import InteractiveCommand, MirrorSafeCommand
21from error import ManifestParseError 24from error import ManifestParseError
22from project import SyncBuffer 25from project import SyncBuffer
26from git_config import GitConfig
23from git_command import git_require, MIN_GIT_VERSION 27from git_command import git_require, MIN_GIT_VERSION
24from manifest_submodule import SubmoduleManifest
25from manifest_xml import XmlManifest
26from subcmds.sync import _ReloadManifest
27 28
28class Init(InteractiveCommand, MirrorSafeCommand): 29class Init(InteractiveCommand, MirrorSafeCommand):
29 common = True 30 common = True
@@ -75,21 +76,27 @@ to update the working directory files.
75 g.add_option('-b', '--manifest-branch', 76 g.add_option('-b', '--manifest-branch',
76 dest='manifest_branch', 77 dest='manifest_branch',
77 help='manifest branch or revision', metavar='REVISION') 78 help='manifest branch or revision', metavar='REVISION')
78 g.add_option('-o', '--origin', 79 g.add_option('-m', '--manifest-name',
79 dest='manifest_origin', 80 dest='manifest_name', default='default.xml',
80 help="use REMOTE instead of 'origin' to track upstream", 81 help='initial manifest file', metavar='NAME.xml')
81 metavar='REMOTE')
82 if isinstance(self.manifest, XmlManifest) \
83 or not self.manifest.manifestProject.Exists:
84 g.add_option('-m', '--manifest-name',
85 dest='manifest_name', default='default.xml',
86 help='initial manifest file', metavar='NAME.xml')
87 g.add_option('--mirror', 82 g.add_option('--mirror',
88 dest='mirror', action='store_true', 83 dest='mirror', action='store_true',
89 help='mirror the forrest') 84 help='mirror the forrest')
90 g.add_option('--reference', 85 g.add_option('--reference',
91 dest='reference', 86 dest='reference',
92 help='location of mirror directory', metavar='DIR') 87 help='location of mirror directory', metavar='DIR')
88 g.add_option('--depth', type='int', default=None,
89 dest='depth',
90 help='create a shallow clone with given depth; see git clone')
91 g.add_option('-g', '--groups',
92 dest='groups', default='default',
93 help='restrict manifest projects to ones with a specified group',
94 metavar='GROUP')
95 g.add_option('-p', '--platform',
96 dest='platform', default='auto',
97 help='restrict manifest projects to ones with a specified'
98 'platform group [auto|all|none|linux|darwin|...]',
99 metavar='PLATFORM')
93 100
94 # Tool 101 # Tool
95 g = p.add_option_group('repo Version options') 102 g = p.add_option_group('repo Version options')
@@ -103,91 +110,94 @@ to update the working directory files.
103 dest='no_repo_verify', action='store_true', 110 dest='no_repo_verify', action='store_true',
104 help='do not verify repo source code') 111 help='do not verify repo source code')
105 112
106 def _ApplyOptions(self, opt, is_new): 113 # Other
114 g = p.add_option_group('Other options')
115 g.add_option('--config-name',
116 dest='config_name', action="store_true", default=False,
117 help='Always prompt for name/e-mail')
118
119 def _SyncManifest(self, opt):
107 m = self.manifest.manifestProject 120 m = self.manifest.manifestProject
121 is_new = not m.Exists
108 122
109 if is_new: 123 if is_new:
110 if opt.manifest_origin: 124 if not opt.manifest_url:
111 m.remote.name = opt.manifest_origin 125 print >>sys.stderr, 'fatal: manifest url (-u) is required.'
126 sys.exit(1)
127
128 if not opt.quiet:
129 print >>sys.stderr, 'Get %s' \
130 % GitConfig.ForUser().UrlInsteadOf(opt.manifest_url)
131 m._InitGitDir()
112 132
113 if opt.manifest_branch: 133 if opt.manifest_branch:
114 m.revisionExpr = opt.manifest_branch 134 m.revisionExpr = opt.manifest_branch
115 else: 135 else:
116 m.revisionExpr = 'refs/heads/master' 136 m.revisionExpr = 'refs/heads/master'
117 else: 137 else:
118 if opt.manifest_origin:
119 print >>sys.stderr, 'fatal: cannot change origin name'
120 sys.exit(1)
121
122 if opt.manifest_branch: 138 if opt.manifest_branch:
123 m.revisionExpr = opt.manifest_branch 139 m.revisionExpr = opt.manifest_branch
124 else: 140 else:
125 m.PreSync() 141 m.PreSync()
126 142
127 def _SyncManifest(self, opt):
128 m = self.manifest.manifestProject
129 is_new = not m.Exists
130
131 if is_new:
132 if not opt.manifest_url:
133 print >>sys.stderr, 'fatal: manifest url (-u) is required.'
134 sys.exit(1)
135
136 if not opt.quiet:
137 print >>sys.stderr, 'Getting manifest ...'
138 print >>sys.stderr, ' from %s' % opt.manifest_url
139 m._InitGitDir()
140
141 self._ApplyOptions(opt, is_new)
142 if opt.manifest_url: 143 if opt.manifest_url:
143 r = m.GetRemote(m.remote.name) 144 r = m.GetRemote(m.remote.name)
144 r.url = opt.manifest_url 145 r.url = opt.manifest_url
145 r.ResetFetch() 146 r.ResetFetch()
146 r.Save() 147 r.Save()
147 148
149 groups = re.split('[,\s]+', opt.groups)
150 all_platforms = ['linux', 'darwin']
151 platformize = lambda x: 'platform-' + x
152 if opt.platform == 'auto':
153 if (not opt.mirror and
154 not m.config.GetString('repo.mirror') == 'true'):
155 groups.append(platformize(platform.system().lower()))
156 elif opt.platform == 'all':
157 groups.extend(map(platformize, all_platforms))
158 elif opt.platform in all_platforms:
159 groups.extend(platformize(opt.platform))
160 elif opt.platform != 'none':
161 print >>sys.stderr, 'fatal: invalid platform flag'
162 sys.exit(1)
163
164 groups = [x for x in groups if x]
165 groupstr = ','.join(groups)
166 if opt.platform == 'auto' and groupstr == 'default,platform-' + platform.system().lower():
167 groupstr = None
168 m.config.SetString('manifest.groups', groupstr)
169
148 if opt.reference: 170 if opt.reference:
149 m.config.SetString('repo.reference', opt.reference) 171 m.config.SetString('repo.reference', opt.reference)
150 172
151 if opt.mirror: 173 if opt.mirror:
152 if is_new: 174 if is_new:
153 m.config.SetString('repo.mirror', 'true') 175 m.config.SetString('repo.mirror', 'true')
154 m.config.ClearCache()
155 else: 176 else:
156 print >>sys.stderr, 'fatal: --mirror not supported on existing client' 177 print >>sys.stderr, 'fatal: --mirror not supported on existing client'
157 sys.exit(1) 178 sys.exit(1)
158 179
159 if not m.Sync_NetworkHalf(): 180 if not m.Sync_NetworkHalf(is_new=is_new):
160 r = m.GetRemote(m.remote.name) 181 r = m.GetRemote(m.remote.name)
161 print >>sys.stderr, 'fatal: cannot obtain manifest %s' % r.url 182 print >>sys.stderr, 'fatal: cannot obtain manifest %s' % r.url
162 sys.exit(1)
163 183
164 if is_new and SubmoduleManifest.IsBare(m): 184 # Better delete the manifest git dir if we created it; otherwise next
165 new = self.GetManifest(reparse=True, type=SubmoduleManifest) 185 # time (when user fixes problems) we won't go through the "is_new" logic.
166 if m.gitdir != new.manifestProject.gitdir: 186 if is_new:
167 os.rename(m.gitdir, new.manifestProject.gitdir) 187 shutil.rmtree(m.gitdir)
168 new = self.GetManifest(reparse=True, type=SubmoduleManifest) 188 sys.exit(1)
169 m = new.manifestProject
170 self._ApplyOptions(opt, is_new)
171 189
172 if not is_new: 190 if opt.manifest_branch:
173 # Force the manifest to load if it exists, the old graph 191 m.MetaBranchSwitch(opt.manifest_branch)
174 # may be needed inside of _ReloadManifest().
175 #
176 self.manifest.projects
177 192
178 syncbuf = SyncBuffer(m.config) 193 syncbuf = SyncBuffer(m.config)
179 m.Sync_LocalHalf(syncbuf) 194 m.Sync_LocalHalf(syncbuf)
180 syncbuf.Finish() 195 syncbuf.Finish()
181 196
182 if isinstance(self.manifest, XmlManifest): 197 if is_new or m.CurrentBranch is None:
183 self._LinkManifest(opt.manifest_name) 198 if not m.StartBranch('default'):
184 _ReloadManifest(self) 199 print >>sys.stderr, 'fatal: cannot create default in manifest'
185 200 sys.exit(1)
186 self._ApplyOptions(opt, is_new)
187
188 if not self.manifest.InitBranch():
189 print >>sys.stderr, 'fatal: cannot create branch in manifest'
190 sys.exit(1)
191 201
192 def _LinkManifest(self, name): 202 def _LinkManifest(self, name):
193 if not name: 203 if not name:
@@ -210,6 +220,24 @@ to update the working directory files.
210 return value 220 return value
211 return a 221 return a
212 222
223 def _ShouldConfigureUser(self):
224 gc = self.manifest.globalConfig
225 mp = self.manifest.manifestProject
226
227 # If we don't have local settings, get from global.
228 if not mp.config.Has('user.name') or not mp.config.Has('user.email'):
229 if not gc.Has('user.name') or not gc.Has('user.email'):
230 return True
231
232 mp.config.SetString('user.name', gc.GetString('user.name'))
233 mp.config.SetString('user.email', gc.GetString('user.email'))
234
235 print ''
236 print 'Your identity is: %s <%s>' % (mp.config.GetString('user.name'),
237 mp.config.GetString('user.email'))
238 print 'If you want to change this, please re-run \'repo init\' with --config-name'
239 return False
240
213 def _ConfigureUser(self): 241 def _ConfigureUser(self):
214 mp = self.manifest.manifestProject 242 mp = self.manifest.manifestProject
215 243
@@ -220,7 +248,7 @@ to update the working directory files.
220 248
221 print '' 249 print ''
222 print 'Your identity is: %s <%s>' % (name, email) 250 print 'Your identity is: %s <%s>' % (name, email)
223 sys.stdout.write('is this correct [y/n]? ') 251 sys.stdout.write('is this correct [y/N]? ')
224 a = sys.stdin.readline().strip() 252 a = sys.stdin.readline().strip()
225 if a in ('yes', 'y', 't', 'true'): 253 if a in ('yes', 'y', 't', 'true'):
226 break 254 break
@@ -262,19 +290,42 @@ to update the working directory files.
262 out.printer(fg='black', attr=c)(' %-6s ', c) 290 out.printer(fg='black', attr=c)(' %-6s ', c)
263 out.nl() 291 out.nl()
264 292
265 sys.stdout.write('Enable color display in this user account (y/n)? ') 293 sys.stdout.write('Enable color display in this user account (y/N)? ')
266 a = sys.stdin.readline().strip().lower() 294 a = sys.stdin.readline().strip().lower()
267 if a in ('y', 'yes', 't', 'true', 'on'): 295 if a in ('y', 'yes', 't', 'true', 'on'):
268 gc.SetString('color.ui', 'auto') 296 gc.SetString('color.ui', 'auto')
269 297
298 def _ConfigureDepth(self, opt):
299 """Configure the depth we'll sync down.
300
301 Args:
302 opt: Options from optparse. We care about opt.depth.
303 """
304 # Opt.depth will be non-None if user actually passed --depth to repo init.
305 if opt.depth is not None:
306 if opt.depth > 0:
307 # Positive values will set the depth.
308 depth = str(opt.depth)
309 else:
310 # Negative numbers will clear the depth; passing None to SetString
311 # will do that.
312 depth = None
313
314 # We store the depth in the main manifest project.
315 self.manifest.manifestProject.config.SetString('repo.depth', depth)
316
270 def Execute(self, opt, args): 317 def Execute(self, opt, args):
271 git_require(MIN_GIT_VERSION, fail=True) 318 git_require(MIN_GIT_VERSION, fail=True)
272 self._SyncManifest(opt) 319 self._SyncManifest(opt)
320 self._LinkManifest(opt.manifest_name)
273 321
274 if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror: 322 if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror:
275 self._ConfigureUser() 323 if opt.config_name or self._ShouldConfigureUser():
324 self._ConfigureUser()
276 self._ConfigureColor() 325 self._ConfigureColor()
277 326
327 self._ConfigureDepth(opt)
328
278 if self.manifest.IsMirror: 329 if self.manifest.IsMirror:
279 type = 'mirror ' 330 type = 'mirror '
280 else: 331 else:
diff --git a/subcmds/list.py b/subcmds/list.py
new file mode 100644
index 00000000..2be82570
--- /dev/null
+++ b/subcmds/list.py
@@ -0,0 +1,48 @@
1#
2# Copyright (C) 2011 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16from command import Command, MirrorSafeCommand
17
18class List(Command, MirrorSafeCommand):
19 common = True
20 helpSummary = "List projects and their associated directories"
21 helpUsage = """
22%prog [<project>...]
23"""
24 helpDescription = """
25List all projects; pass '.' to list the project for the cwd.
26
27This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'.
28"""
29
30 def Execute(self, opt, args):
31 """List all projects and the associated directories.
32
33 This may be possible to do with 'repo forall', but repo newbies have
34 trouble figuring that out. The idea here is that it should be more
35 discoverable.
36
37 Args:
38 opt: The options. We don't take any.
39 args: Positional args. Can be a list of projects to list, or empty.
40 """
41 projects = self.GetProjects(args)
42
43 lines = []
44 for project in projects:
45 lines.append("%s : %s" % (project.relpath, project.name))
46
47 lines.sort()
48 print '\n'.join(lines)
diff --git a/subcmds/manifest.py b/subcmds/manifest.py
index dcd3df17..4374a9d0 100644
--- a/subcmds/manifest.py
+++ b/subcmds/manifest.py
@@ -17,25 +17,14 @@ import os
17import sys 17import sys
18 18
19from command import PagedCommand 19from command import PagedCommand
20from manifest_submodule import SubmoduleManifest
21from manifest_xml import XmlManifest
22
23def _doc(name):
24 r = os.path.dirname(__file__)
25 r = os.path.dirname(r)
26 fd = open(os.path.join(r, 'docs', name))
27 try:
28 return fd.read()
29 finally:
30 fd.close()
31 20
32class Manifest(PagedCommand): 21class Manifest(PagedCommand):
33 common = False 22 common = False
34 helpSummary = "Manifest inspection utility" 23 helpSummary = "Manifest inspection utility"
35 helpUsage = """ 24 helpUsage = """
36%prog [options] 25%prog [-o {-|NAME.xml} [-r]]
37""" 26"""
38 _xmlHelp = """ 27 _helpDescription = """
39 28
40With the -o option, exports the current manifest for inspection. 29With the -o option, exports the current manifest for inspection.
41The manifest and (if present) local_manifest.xml are combined 30The manifest and (if present) local_manifest.xml are combined
@@ -46,30 +35,23 @@ in a Git repository for use during future 'repo init' invocations.
46 35
47 @property 36 @property
48 def helpDescription(self): 37 def helpDescription(self):
49 help = '' 38 help = self._helpDescription + '\n'
50 if isinstance(self.manifest, XmlManifest): 39 r = os.path.dirname(__file__)
51 help += self._xmlHelp + '\n' + _doc('manifest_xml.txt') 40 r = os.path.dirname(r)
52 if isinstance(self.manifest, SubmoduleManifest): 41 fd = open(os.path.join(r, 'docs', 'manifest-format.txt'))
53 help += _doc('manifest_submodule.txt') 42 for line in fd:
43 help += line
44 fd.close()
54 return help 45 return help
55 46
56 def _Options(self, p): 47 def _Options(self, p):
57 if isinstance(self.manifest, XmlManifest): 48 p.add_option('-r', '--revision-as-HEAD',
58 p.add_option('--upgrade', 49 dest='peg_rev', action='store_true',
59 dest='upgrade', action='store_true', 50 help='Save revisions as current HEAD')
60 help='Upgrade XML manifest to submodule') 51 p.add_option('-o', '--output-file',
61 p.add_option('-r', '--revision-as-HEAD', 52 dest='output_file',
62 dest='peg_rev', action='store_true', 53 help='File to save the manifest to',
63 help='Save revisions as current HEAD') 54 metavar='-|NAME.xml')
64 p.add_option('-o', '--output-file',
65 dest='output_file',
66 help='File to save the manifest to',
67 metavar='-|NAME.xml')
68
69 def WantPager(self, opt):
70 if isinstance(self.manifest, XmlManifest) and opt.upgrade:
71 return False
72 return True
73 55
74 def _Output(self, opt): 56 def _Output(self, opt):
75 if opt.output_file == '-': 57 if opt.output_file == '-':
@@ -82,38 +64,13 @@ in a Git repository for use during future 'repo init' invocations.
82 if opt.output_file != '-': 64 if opt.output_file != '-':
83 print >>sys.stderr, 'Saved manifest to %s' % opt.output_file 65 print >>sys.stderr, 'Saved manifest to %s' % opt.output_file
84 66
85 def _Upgrade(self):
86 old = self.manifest
87
88 if isinstance(old, SubmoduleManifest):
89 print >>sys.stderr, 'error: already upgraded'
90 sys.exit(1)
91
92 old._Load()
93 for p in old.projects.values():
94 if not os.path.exists(p.gitdir) \
95 or not os.path.exists(p.worktree):
96 print >>sys.stderr, 'fatal: project "%s" missing' % p.relpath
97 sys.exit(1)
98
99 new = SubmoduleManifest(old.repodir)
100 new.FromXml_Local_1(old, checkout=False)
101 new.FromXml_Definition(old)
102 new.FromXml_Local_2(old)
103 print >>sys.stderr, 'upgraded manifest; commit result manually'
104
105 def Execute(self, opt, args): 67 def Execute(self, opt, args):
106 if args: 68 if args:
107 self.Usage() 69 self.Usage()
108 70
109 if isinstance(self.manifest, XmlManifest): 71 if opt.output_file is not None:
110 if opt.upgrade: 72 self._Output(opt)
111 self._Upgrade() 73 return
112 return
113
114 if opt.output_file is not None:
115 self._Output(opt)
116 return
117 74
118 print >>sys.stderr, 'error: no operation to perform' 75 print >>sys.stderr, 'error: no operation to perform'
119 print >>sys.stderr, 'error: see repo help manifest' 76 print >>sys.stderr, 'error: see repo help manifest'
diff --git a/subcmds/overview.py b/subcmds/overview.py
new file mode 100644
index 00000000..96fa93d8
--- /dev/null
+++ b/subcmds/overview.py
@@ -0,0 +1,80 @@
1#
2# Copyright (C) 2012 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16from color import Coloring
17from command import PagedCommand
18
19
20class Overview(PagedCommand):
21 common = True
22 helpSummary = "Display overview of unmerged project branches"
23 helpUsage = """
24%prog [--current-branch] [<project>...]
25"""
26 helpDescription = """
27The '%prog' command is used to display an overview of the projects branches,
28and list any local commits that have not yet been merged into the project.
29
30The -b/--current-branch option can be used to restrict the output to only
31branches currently checked out in each project. By default, all branches
32are displayed.
33"""
34
35 def _Options(self, p):
36 p.add_option('-b', '--current-branch',
37 dest="current_branch", action="store_true",
38 help="Consider only checked out branches")
39
40 def Execute(self, opt, args):
41 all = []
42 for project in self.GetProjects(args):
43 br = [project.GetUploadableBranch(x)
44 for x in project.GetBranches().keys()]
45 br = [x for x in br if x]
46 if opt.current_branch:
47 br = [x for x in br if x.name == project.CurrentBranch]
48 all.extend(br)
49
50 if not all:
51 return
52
53 class Report(Coloring):
54 def __init__(self, config):
55 Coloring.__init__(self, config, 'status')
56 self.project = self.printer('header', attr='bold')
57
58 out = Report(all[0].project.config)
59 out.project('Projects Overview')
60 out.nl()
61
62 project = None
63
64 for branch in all:
65 if project != branch.project:
66 project = branch.project
67 out.nl()
68 out.project('project %s/' % project.relpath)
69 out.nl()
70
71 commits = branch.commits
72 date = branch.date
73 print '%s %-33s (%2d commit%s, %s)' % (
74 branch.name == project.CurrentBranch and '*' or ' ',
75 branch.name,
76 len(commits),
77 len(commits) != 1 and 's' or ' ',
78 date)
79 for commit in commits:
80 print '%-35s - %s' % ('', commit)
diff --git a/subcmds/rebase.py b/subcmds/rebase.py
index e341296d..20662b11 100644
--- a/subcmds/rebase.py
+++ b/subcmds/rebase.py
@@ -17,7 +17,7 @@ import sys
17 17
18from command import Command 18from command import Command
19from git_command import GitCommand 19from git_command import GitCommand
20from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB 20from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
21from error import GitError 21from error import GitError
22 22
23class Rebase(Command): 23class Rebase(Command):
@@ -52,6 +52,9 @@ branch but need to incorporate new upstream changes "underneath" them.
52 p.add_option('--whitespace', 52 p.add_option('--whitespace',
53 dest='whitespace', action='store', metavar='WS', 53 dest='whitespace', action='store', metavar='WS',
54 help='Pass --whitespace to git rebase') 54 help='Pass --whitespace to git rebase')
55 p.add_option('--auto-stash',
56 dest='auto_stash', action='store_true',
57 help='Stash local modifications before starting')
55 58
56 def Execute(self, opt, args): 59 def Execute(self, opt, args):
57 all = self.GetProjects(args) 60 all = self.GetProjects(args)
@@ -103,5 +106,23 @@ branch but need to incorporate new upstream changes "underneath" them.
103 print >>sys.stderr, '# %s: rebasing %s -> %s' % \ 106 print >>sys.stderr, '# %s: rebasing %s -> %s' % \
104 (project.relpath, cb, upbranch.LocalMerge) 107 (project.relpath, cb, upbranch.LocalMerge)
105 108
109 needs_stash = False
110 if opt.auto_stash:
111 stash_args = ["update-index", "--refresh", "-q"]
112
113 if GitCommand(project, stash_args).Wait() != 0:
114 needs_stash = True
115 # Dirty index, requires stash...
116 stash_args = ["stash"]
117
118 if GitCommand(project, stash_args).Wait() != 0:
119 return -1
120
106 if GitCommand(project, args).Wait() != 0: 121 if GitCommand(project, args).Wait() != 0:
107 return -1 122 return -1
123
124 if needs_stash:
125 stash_args.append('pop')
126 stash_args.append('--quiet')
127 if GitCommand(project, stash_args).Wait() != 0:
128 return -1
diff --git a/subcmds/start.py b/subcmds/start.py
index ae2985d2..00885076 100644
--- a/subcmds/start.py
+++ b/subcmds/start.py
@@ -15,6 +15,7 @@
15 15
16import sys 16import sys
17from command import Command 17from command import Command
18from git_config import IsId
18from git_command import git 19from git_command import git
19from progress import Progress 20from progress import Progress
20 21
@@ -56,6 +57,10 @@ revision specified in the manifest.
56 pm = Progress('Starting %s' % nb, len(all)) 57 pm = Progress('Starting %s' % nb, len(all))
57 for project in all: 58 for project in all:
58 pm.update() 59 pm.update()
60 # If the current revision is a specific SHA1 then we can't push back
61 # to it so substitute the manifest default revision instead.
62 if IsId(project.revisionExpr):
63 project.revisionExpr = self.manifest.default.revisionExpr
59 if not project.StartBranch(nb): 64 if not project.StartBranch(nb):
60 err.append(project) 65 err.append(project)
61 pm.end() 66 pm.end()
diff --git a/subcmds/status.py b/subcmds/status.py
index b0d419a7..69e2dbfc 100644
--- a/subcmds/status.py
+++ b/subcmds/status.py
@@ -15,6 +15,15 @@
15 15
16from command import PagedCommand 16from command import PagedCommand
17 17
18try:
19 import threading as _threading
20except ImportError:
21 import dummy_threading as _threading
22
23import itertools
24import sys
25import StringIO
26
18class Status(PagedCommand): 27class Status(PagedCommand):
19 common = True 28 common = True
20 helpSummary = "Show the working tree status" 29 helpSummary = "Show the working tree status"
@@ -27,6 +36,9 @@ and the most recent commit on this branch (HEAD), in each project
27specified. A summary is displayed, one line per file where there 36specified. A summary is displayed, one line per file where there
28is a difference between these three states. 37is a difference between these three states.
29 38
39The -j/--jobs option can be used to run multiple status queries
40in parallel.
41
30Status Display 42Status Display
31-------------- 43--------------
32 44
@@ -60,26 +72,60 @@ the following meanings:
60 72
61""" 73"""
62 74
75 def _Options(self, p):
76 p.add_option('-j', '--jobs',
77 dest='jobs', action='store', type='int', default=2,
78 help="number of projects to check simultaneously")
79
80 def _StatusHelper(self, project, clean_counter, sem, output):
81 """Obtains the status for a specific project.
82
83 Obtains the status for a project, redirecting the output to
84 the specified object. It will release the semaphore
85 when done.
86
87 Args:
88 project: Project to get status of.
89 clean_counter: Counter for clean projects.
90 sem: Semaphore, will call release() when complete.
91 output: Where to output the status.
92 """
93 try:
94 state = project.PrintWorkTreeStatus(output)
95 if state == 'CLEAN':
96 clean_counter.next()
97 finally:
98 sem.release()
99
63 def Execute(self, opt, args): 100 def Execute(self, opt, args):
64 all = self.GetProjects(args) 101 all = self.GetProjects(args)
65 clean = 0 102 counter = itertools.count()
66 103
67 on = {} 104 if opt.jobs == 1:
68 for project in all: 105 for project in all:
69 cb = project.CurrentBranch 106 state = project.PrintWorkTreeStatus()
70 if cb: 107 if state == 'CLEAN':
71 if cb not in on: 108 counter.next()
72 on[cb] = [] 109 else:
73 on[cb].append(project) 110 sem = _threading.Semaphore(opt.jobs)
74 111 threads_and_output = []
75 branch_names = list(on.keys()) 112 for project in all:
76 branch_names.sort() 113 sem.acquire()
77 for cb in branch_names: 114
78 print '# on branch %s' % cb 115 class BufList(StringIO.StringIO):
79 116 def dump(self, ostream):
80 for project in all: 117 for entry in self.buflist:
81 state = project.PrintWorkTreeStatus() 118 ostream.write(entry)
82 if state == 'CLEAN': 119
83 clean += 1 120 output = BufList()
84 if len(all) == clean: 121
122 t = _threading.Thread(target=self._StatusHelper,
123 args=(project, counter, sem, output))
124 threads_and_output.append((t, output))
125 t.start()
126 for (t, output) in threads_and_output:
127 t.join()
128 output.dump(sys.stdout)
129 output.close()
130 if len(all) == counter.next():
85 print 'nothing to commit (working directory clean)' 131 print 'nothing to commit (working directory clean)'
diff --git a/subcmds/sync.py b/subcmds/sync.py
index 16f1d189..bfe146b6 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -28,6 +28,14 @@ try:
28except ImportError: 28except ImportError:
29 import dummy_threading as _threading 29 import dummy_threading as _threading
30 30
31try:
32 import resource
33 def _rlimit_nofile():
34 return resource.getrlimit(resource.RLIMIT_NOFILE)
35except ImportError:
36 def _rlimit_nofile():
37 return (256, 256)
38
31from git_command import GIT 39from git_command import GIT
32from git_refs import R_HEADS 40from git_refs import R_HEADS
33from project import HEAD 41from project import HEAD
@@ -39,6 +47,10 @@ from project import R_HEADS
39from project import SyncBuffer 47from project import SyncBuffer
40from progress import Progress 48from progress import Progress
41 49
50class _FetchError(Exception):
51 """Internal error thrown in _FetchHelper() when we don't want stack trace."""
52 pass
53
42class Sync(Command, MirrorSafeCommand): 54class Sync(Command, MirrorSafeCommand):
43 jobs = 1 55 jobs = 1
44 common = True 56 common = True
@@ -68,11 +80,18 @@ revision is temporarily needed.
68 80
69The -s/--smart-sync option can be used to sync to a known good 81The -s/--smart-sync option can be used to sync to a known good
70build as specified by the manifest-server element in the current 82build as specified by the manifest-server element in the current
71manifest. 83manifest. The -t/--smart-tag option is similar and allows you to
84specify a custom tag/label.
72 85
73The -f/--force-broken option can be used to proceed with syncing 86The -f/--force-broken option can be used to proceed with syncing
74other projects if a project sync fails. 87other projects if a project sync fails.
75 88
89The --no-clone-bundle option disables any attempt to use
90$URL/clone.bundle to bootstrap a new Git repository from a
91resumeable bundle file on a content delivery network. This
92may be necessary if there are problems with the local Python
93HTTP client or proxy configuration, but the Git binary works.
94
76SSH Connections 95SSH Connections
77--------------- 96---------------
78 97
@@ -104,6 +123,8 @@ later is required to fix a server side protocol bug.
104""" 123"""
105 124
106 def _Options(self, p, show_smart=True): 125 def _Options(self, p, show_smart=True):
126 self.jobs = self.manifest.default.sync_j
127
107 p.add_option('-f', '--force-broken', 128 p.add_option('-f', '--force-broken',
108 dest='force_broken', action='store_true', 129 dest='force_broken', action='store_true',
109 help="continue sync even if a project fails to sync") 130 help="continue sync even if a project fails to sync")
@@ -116,16 +137,28 @@ later is required to fix a server side protocol bug.
116 p.add_option('-d','--detach', 137 p.add_option('-d','--detach',
117 dest='detach_head', action='store_true', 138 dest='detach_head', action='store_true',
118 help='detach projects back to manifest revision') 139 help='detach projects back to manifest revision')
140 p.add_option('-c','--current-branch',
141 dest='current_branch_only', action='store_true',
142 help='fetch only current branch from server')
119 p.add_option('-q','--quiet', 143 p.add_option('-q','--quiet',
120 dest='quiet', action='store_true', 144 dest='quiet', action='store_true',
121 help='be more quiet') 145 help='be more quiet')
122 p.add_option('-j','--jobs', 146 p.add_option('-j','--jobs',
123 dest='jobs', action='store', type='int', 147 dest='jobs', action='store', type='int',
124 help="number of projects to fetch simultaneously") 148 help="projects to fetch simultaneously (default %d)" % self.jobs)
149 p.add_option('-m', '--manifest-name',
150 dest='manifest_name',
151 help='temporary manifest to use for this sync', metavar='NAME.xml')
152 p.add_option('--no-clone-bundle',
153 dest='no_clone_bundle', action='store_true',
154 help='disable use of /clone.bundle on HTTP/HTTPS')
125 if show_smart: 155 if show_smart:
126 p.add_option('-s', '--smart-sync', 156 p.add_option('-s', '--smart-sync',
127 dest='smart_sync', action='store_true', 157 dest='smart_sync', action='store_true',
128 help='smart sync using manifest from a known good build') 158 help='smart sync using manifest from a known good build')
159 p.add_option('-t', '--smart-tag',
160 dest='smart_tag', action='store',
161 help='smart sync using manifest from a known tag')
129 162
130 g = p.add_option_group('repo Version options') 163 g = p.add_option_group('repo Version options')
131 g.add_option('--no-repo-verify', 164 g.add_option('--no-repo-verify',
@@ -135,20 +168,60 @@ later is required to fix a server side protocol bug.
135 dest='repo_upgraded', action='store_true', 168 dest='repo_upgraded', action='store_true',
136 help=SUPPRESS_HELP) 169 help=SUPPRESS_HELP)
137 170
138 def _FetchHelper(self, opt, project, lock, fetched, pm, sem): 171 def _FetchHelper(self, opt, project, lock, fetched, pm, sem, err_event):
139 if not project.Sync_NetworkHalf(quiet=opt.quiet): 172 """Main function of the fetch threads when jobs are > 1.
140 print >>sys.stderr, 'error: Cannot fetch %s' % project.name 173
141 if opt.force_broken: 174 Args:
142 print >>sys.stderr, 'warn: --force-broken, continuing to sync' 175 opt: Program options returned from optparse. See _Options().
143 else: 176 project: Project object for the project to fetch.
144 sem.release() 177 lock: Lock for accessing objects that are shared amongst multiple
145 sys.exit(1) 178 _FetchHelper() threads.
179 fetched: set object that we will add project.gitdir to when we're done
180 (with our lock held).
181 pm: Instance of a Project object. We will call pm.update() (with our
182 lock held).
183 sem: We'll release() this semaphore when we exit so that another thread
184 can be started up.
185 err_event: We'll set this event in the case of an error (after printing
186 out info about the error).
187 """
188 # We'll set to true once we've locked the lock.
189 did_lock = False
190
191 # Encapsulate everything in a try/except/finally so that:
192 # - We always set err_event in the case of an exception.
193 # - We always make sure we call sem.release().
194 # - We always make sure we unlock the lock if we locked it.
195 try:
196 try:
197 success = project.Sync_NetworkHalf(
198 quiet=opt.quiet,
199 current_branch_only=opt.current_branch_only,
200 clone_bundle=not opt.no_clone_bundle)
201
202 # Lock around all the rest of the code, since printing, updating a set
203 # and Progress.update() are not thread safe.
204 lock.acquire()
205 did_lock = True
206
207 if not success:
208 print >>sys.stderr, 'error: Cannot fetch %s' % project.name
209 if opt.force_broken:
210 print >>sys.stderr, 'warn: --force-broken, continuing to sync'
211 else:
212 raise _FetchError()
146 213
147 lock.acquire() 214 fetched.add(project.gitdir)
148 fetched.add(project.gitdir) 215 pm.update()
149 pm.update() 216 except _FetchError:
150 lock.release() 217 err_event.set()
151 sem.release() 218 except:
219 err_event.set()
220 raise
221 finally:
222 if did_lock:
223 lock.release()
224 sem.release()
152 225
153 def _Fetch(self, projects, opt): 226 def _Fetch(self, projects, opt):
154 fetched = set() 227 fetched = set()
@@ -157,7 +230,10 @@ later is required to fix a server side protocol bug.
157 if self.jobs == 1: 230 if self.jobs == 1:
158 for project in projects: 231 for project in projects:
159 pm.update() 232 pm.update()
160 if project.Sync_NetworkHalf(quiet=opt.quiet): 233 if project.Sync_NetworkHalf(
234 quiet=opt.quiet,
235 current_branch_only=opt.current_branch_only,
236 clone_bundle=not opt.no_clone_bundle):
161 fetched.add(project.gitdir) 237 fetched.add(project.gitdir)
162 else: 238 else:
163 print >>sys.stderr, 'error: Cannot fetch %s' % project.name 239 print >>sys.stderr, 'error: Cannot fetch %s' % project.name
@@ -169,7 +245,13 @@ later is required to fix a server side protocol bug.
169 threads = set() 245 threads = set()
170 lock = _threading.Lock() 246 lock = _threading.Lock()
171 sem = _threading.Semaphore(self.jobs) 247 sem = _threading.Semaphore(self.jobs)
248 err_event = _threading.Event()
172 for project in projects: 249 for project in projects:
250 # Check for any errors before starting any new threads.
251 # ...we'll let existing threads finish, though.
252 if err_event.isSet():
253 break
254
173 sem.acquire() 255 sem.acquire()
174 t = _threading.Thread(target = self._FetchHelper, 256 t = _threading.Thread(target = self._FetchHelper,
175 args = (opt, 257 args = (opt,
@@ -177,13 +259,19 @@ later is required to fix a server side protocol bug.
177 lock, 259 lock,
178 fetched, 260 fetched,
179 pm, 261 pm,
180 sem)) 262 sem,
263 err_event))
181 threads.add(t) 264 threads.add(t)
182 t.start() 265 t.start()
183 266
184 for t in threads: 267 for t in threads:
185 t.join() 268 t.join()
186 269
270 # If we saw an error, exit with code 1 so that other scripts can check.
271 if err_event.isSet():
272 print >>sys.stderr, '\nerror: Exited sync due to fetch errors'
273 sys.exit(1)
274
187 pm.end() 275 pm.end()
188 for project in projects: 276 for project in projects:
189 project.bare_git.gc('--auto') 277 project.bare_git.gc('--auto')
@@ -191,7 +279,7 @@ later is required to fix a server side protocol bug.
191 279
192 def UpdateProjectList(self): 280 def UpdateProjectList(self):
193 new_project_paths = [] 281 new_project_paths = []
194 for project in self.manifest.projects.values(): 282 for project in self.GetProjects(None, missing_ok=True):
195 if project.relpath: 283 if project.relpath:
196 new_project_paths.append(project.relpath) 284 new_project_paths.append(project.relpath)
197 file_name = 'project.list' 285 file_name = 'project.list'
@@ -220,7 +308,8 @@ later is required to fix a server side protocol bug.
220 worktree = os.path.join(self.manifest.topdir, path), 308 worktree = os.path.join(self.manifest.topdir, path),
221 relpath = path, 309 relpath = path,
222 revisionExpr = 'HEAD', 310 revisionExpr = 'HEAD',
223 revisionId = None) 311 revisionId = None,
312 groups = None)
224 313
225 if project.IsDirty(): 314 if project.IsDirty():
226 print >>sys.stderr, 'error: Cannot remove project "%s": \ 315 print >>sys.stderr, 'error: Cannot remove project "%s": \
@@ -251,34 +340,51 @@ uncommitted changes are present' % project.relpath
251 def Execute(self, opt, args): 340 def Execute(self, opt, args):
252 if opt.jobs: 341 if opt.jobs:
253 self.jobs = opt.jobs 342 self.jobs = opt.jobs
343 if self.jobs > 1:
344 soft_limit, _ = _rlimit_nofile()
345 self.jobs = min(self.jobs, (soft_limit - 5) / 3)
346
254 if opt.network_only and opt.detach_head: 347 if opt.network_only and opt.detach_head:
255 print >>sys.stderr, 'error: cannot combine -n and -d' 348 print >>sys.stderr, 'error: cannot combine -n and -d'
256 sys.exit(1) 349 sys.exit(1)
257 if opt.network_only and opt.local_only: 350 if opt.network_only and opt.local_only:
258 print >>sys.stderr, 'error: cannot combine -n and -l' 351 print >>sys.stderr, 'error: cannot combine -n and -l'
259 sys.exit(1) 352 sys.exit(1)
353 if opt.manifest_name and opt.smart_sync:
354 print >>sys.stderr, 'error: cannot combine -m and -s'
355 sys.exit(1)
356 if opt.manifest_name and opt.smart_tag:
357 print >>sys.stderr, 'error: cannot combine -m and -t'
358 sys.exit(1)
260 359
261 if opt.smart_sync: 360 if opt.manifest_name:
361 self.manifest.Override(opt.manifest_name)
362
363 if opt.smart_sync or opt.smart_tag:
262 if not self.manifest.manifest_server: 364 if not self.manifest.manifest_server:
263 print >>sys.stderr, \ 365 print >>sys.stderr, \
264 'error: cannot smart sync: no manifest server defined in manifest' 366 'error: cannot smart sync: no manifest server defined in manifest'
265 sys.exit(1) 367 sys.exit(1)
266 try: 368 try:
267 server = xmlrpclib.Server(self.manifest.manifest_server) 369 server = xmlrpclib.Server(self.manifest.manifest_server)
268 p = self.manifest.manifestProject 370 if opt.smart_sync:
269 b = p.GetBranch(p.CurrentBranch) 371 p = self.manifest.manifestProject
270 branch = b.merge 372 b = p.GetBranch(p.CurrentBranch)
271 if branch.startswith(R_HEADS): 373 branch = b.merge
272 branch = branch[len(R_HEADS):] 374 if branch.startswith(R_HEADS):
273 375 branch = branch[len(R_HEADS):]
274 env = os.environ.copy() 376
275 if (env.has_key('TARGET_PRODUCT') and 377 env = os.environ.copy()
276 env.has_key('TARGET_BUILD_VARIANT')): 378 if (env.has_key('TARGET_PRODUCT') and
277 target = '%s-%s' % (env['TARGET_PRODUCT'], 379 env.has_key('TARGET_BUILD_VARIANT')):
278 env['TARGET_BUILD_VARIANT']) 380 target = '%s-%s' % (env['TARGET_PRODUCT'],
279 [success, manifest_str] = server.GetApprovedManifest(branch, target) 381 env['TARGET_BUILD_VARIANT'])
382 [success, manifest_str] = server.GetApprovedManifest(branch, target)
383 else:
384 [success, manifest_str] = server.GetApprovedManifest(branch)
280 else: 385 else:
281 [success, manifest_str] = server.GetApprovedManifest(branch) 386 assert(opt.smart_tag)
387 [success, manifest_str] = server.GetManifest(opt.smart_tag)
282 388
283 if success: 389 if success:
284 manifest_name = "smart_sync_override.xml" 390 manifest_name = "smart_sync_override.xml"
@@ -313,7 +419,8 @@ uncommitted changes are present' % project.relpath
313 _PostRepoUpgrade(self.manifest) 419 _PostRepoUpgrade(self.manifest)
314 420
315 if not opt.local_only: 421 if not opt.local_only:
316 mp.Sync_NetworkHalf(quiet=opt.quiet) 422 mp.Sync_NetworkHalf(quiet=opt.quiet,
423 current_branch_only=opt.current_branch_only)
317 424
318 if mp.HasChanges: 425 if mp.HasChanges:
319 syncbuf = SyncBuffer(mp.config) 426 syncbuf = SyncBuffer(mp.config)
@@ -321,6 +428,8 @@ uncommitted changes are present' % project.relpath
321 if not syncbuf.Finish(): 428 if not syncbuf.Finish():
322 sys.exit(1) 429 sys.exit(1)
323 self.manifest._Unload() 430 self.manifest._Unload()
431 if opt.jobs is None:
432 self.jobs = self.manifest.default.sync_j
324 all = self.GetProjects(args, missing_ok=True) 433 all = self.GetProjects(args, missing_ok=True)
325 434
326 if not opt.local_only: 435 if not opt.local_only:
@@ -336,14 +445,7 @@ uncommitted changes are present' % project.relpath
336 # bail out now; the rest touches the working tree 445 # bail out now; the rest touches the working tree
337 return 446 return
338 447
339 if mp.HasChanges: 448 self.manifest._Unload()
340 syncbuf = SyncBuffer(mp.config)
341 mp.Sync_LocalHalf(syncbuf)
342 if not syncbuf.Finish():
343 sys.exit(1)
344 _ReloadManifest(self)
345 mp = self.manifest.manifestProject
346
347 all = self.GetProjects(args, missing_ok=True) 449 all = self.GetProjects(args, missing_ok=True)
348 missing = [] 450 missing = []
349 for project in all: 451 for project in all:
@@ -370,16 +472,10 @@ uncommitted changes are present' % project.relpath
370 if not syncbuf.Finish(): 472 if not syncbuf.Finish():
371 sys.exit(1) 473 sys.exit(1)
372 474
373def _ReloadManifest(cmd): 475 # If there's a notice that's supposed to print at the end of the sync, print
374 old = cmd.manifest 476 # it now...
375 new = cmd.GetManifest(reparse=True) 477 if self.manifest.notice:
376 478 print self.manifest.notice
377 if old.__class__ != new.__class__:
378 print >>sys.stderr, 'NOTICE: manifest format has changed ***'
379 new.Upgrade_Local(old)
380 else:
381 if new.notice:
382 print new.notice
383 479
384def _PostRepoUpgrade(manifest): 480def _PostRepoUpgrade(manifest):
385 for project in manifest.projects.values(): 481 for project in manifest.projects.values():
diff --git a/subcmds/upload.py b/subcmds/upload.py
index 20822096..c9312973 100644
--- a/subcmds/upload.py
+++ b/subcmds/upload.py
@@ -19,7 +19,8 @@ import sys
19 19
20from command import InteractiveCommand 20from command import InteractiveCommand
21from editor import Editor 21from editor import Editor
22from error import UploadError 22from error import HookError, UploadError
23from project import RepoHook
23 24
24UNUSUAL_COMMIT_THRESHOLD = 5 25UNUSUAL_COMMIT_THRESHOLD = 5
25 26
@@ -72,7 +73,7 @@ Configuration
72 73
73review.URL.autoupload: 74review.URL.autoupload:
74 75
75To disable the "Upload ... (y/n)?" prompt, you can set a per-project 76To disable the "Upload ... (y/N)?" prompt, you can set a per-project
76or global Git configuration option. If review.URL.autoupload is set 77or global Git configuration option. If review.URL.autoupload is set
77to "true" then repo will assume you always answer "y" at the prompt, 78to "true" then repo will assume you always answer "y" at the prompt,
78and will not prompt you further. If it is set to "false" then repo 79and will not prompt you further. If it is set to "false" then repo
@@ -102,6 +103,14 @@ or in the .git/config within the project. For example:
102 autoupload = true 103 autoupload = true
103 autocopy = johndoe@company.com,my-team-alias@company.com 104 autocopy = johndoe@company.com,my-team-alias@company.com
104 105
106review.URL.uploadtopic:
107
108To add a topic branch whenever uploading a commit, you can set a
109per-project or global Git option to do so. If review.URL.uploadtopic
110is set to "true" then repo will assume you always want the equivalent
111of the -t option to the repo command. If unset or set to "false" then
112repo will make use of only the command line option.
113
105References 114References
106---------- 115----------
107 116
@@ -119,6 +128,38 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
119 p.add_option('--cc', 128 p.add_option('--cc',
120 type='string', action='append', dest='cc', 129 type='string', action='append', dest='cc',
121 help='Also send email to these email addresses.') 130 help='Also send email to these email addresses.')
131 p.add_option('--br',
132 type='string', action='store', dest='branch',
133 help='Branch to upload.')
134 p.add_option('--cbr', '--current-branch',
135 dest='current_branch', action='store_true',
136 help='Upload current git branch.')
137 p.add_option('-d', '--draft',
138 action='store_true', dest='draft', default=False,
139 help='If specified, upload as a draft.')
140
141 # Options relating to upload hook. Note that verify and no-verify are NOT
142 # opposites of each other, which is why they store to different locations.
143 # We are using them to match 'git commit' syntax.
144 #
145 # Combinations:
146 # - no-verify=False, verify=False (DEFAULT):
147 # If stdout is a tty, can prompt about running upload hooks if needed.
148 # If user denies running hooks, the upload is cancelled. If stdout is
149 # not a tty and we would need to prompt about upload hooks, upload is
150 # cancelled.
151 # - no-verify=False, verify=True:
152 # Always run upload hooks with no prompt.
153 # - no-verify=True, verify=False:
154 # Never run upload hooks, but upload anyway (AKA bypass hooks).
155 # - no-verify=True, verify=True:
156 # Invalid
157 p.add_option('--no-verify',
158 dest='bypass_hooks', action='store_true',
159 help='Do not run the upload hook.')
160 p.add_option('--verify',
161 dest='allow_all_hooks', action='store_true',
162 help='Run the upload hook without prompting.')
122 163
123 def _SingleBranch(self, opt, branch, people): 164 def _SingleBranch(self, opt, branch, people):
124 project = branch.project 165 project = branch.project
@@ -135,7 +176,7 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
135 date = branch.date 176 date = branch.date
136 list = branch.commits 177 list = branch.commits
137 178
138 print 'Upload project %s/:' % project.relpath 179 print 'Upload project %s/ to remote branch %s:' % (project.relpath, project.revisionExpr)
139 print ' branch %s (%2d commit%s, %s):' % ( 180 print ' branch %s (%2d commit%s, %s):' % (
140 name, 181 name,
141 len(list), 182 len(list),
@@ -144,7 +185,7 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
144 for commit in list: 185 for commit in list:
145 print ' %s' % commit 186 print ' %s' % commit
146 187
147 sys.stdout.write('to %s (y/n)? ' % remote.review) 188 sys.stdout.write('to %s (y/N)? ' % remote.review)
148 answer = sys.stdin.readline().strip() 189 answer = sys.stdin.readline().strip()
149 answer = answer in ('y', 'Y', 'yes', '1', 'true', 't') 190 answer = answer in ('y', 'Y', 'yes', '1', 'true', 't')
150 191
@@ -175,11 +216,12 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
175 216
176 if b: 217 if b:
177 script.append('#') 218 script.append('#')
178 script.append('# branch %s (%2d commit%s, %s):' % ( 219 script.append('# branch %s (%2d commit%s, %s) to remote branch %s:' % (
179 name, 220 name,
180 len(list), 221 len(list),
181 len(list) != 1 and 's' or '', 222 len(list) != 1 and 's' or '',
182 date)) 223 date,
224 project.revisionExpr))
183 for commit in list: 225 for commit in list:
184 script.append('# %s' % commit) 226 script.append('# %s' % commit)
185 b[name] = branch 227 b[name] = branch
@@ -188,6 +230,11 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
188 branches[project.name] = b 230 branches[project.name] = b
189 script.append('') 231 script.append('')
190 232
233 script = [ x.encode('utf-8')
234 if issubclass(type(x), unicode)
235 else x
236 for x in script ]
237
191 script = Editor.EditString("\n".join(script)).split("\n") 238 script = Editor.EditString("\n".join(script)).split("\n")
192 239
193 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$') 240 project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
@@ -267,7 +314,7 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
267 314
268 # if they want to auto upload, let's not ask because it could be automated 315 # if they want to auto upload, let's not ask because it could be automated
269 if answer is None: 316 if answer is None:
270 sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/n) ') 317 sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/N) ')
271 a = sys.stdin.readline().strip().lower() 318 a = sys.stdin.readline().strip().lower()
272 if a not in ('y', 'yes', 't', 'true', 'on'): 319 if a not in ('y', 'yes', 't', 'true', 'on'):
273 print >>sys.stderr, "skipping upload" 320 print >>sys.stderr, "skipping upload"
@@ -275,7 +322,12 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
275 branch.error = 'User aborted' 322 branch.error = 'User aborted'
276 continue 323 continue
277 324
278 branch.UploadForReview(people, auto_topic=opt.auto_topic) 325 # Check if topic branches should be sent to the server during upload
326 if opt.auto_topic is not True:
327 key = 'review.%s.uploadtopic' % branch.project.remote.review
328 opt.auto_topic = branch.project.config.GetBoolean(key)
329
330 branch.UploadForReview(people, auto_topic=opt.auto_topic, draft=opt.draft)
279 branch.uploaded = True 331 branch.uploaded = True
280 except UploadError, e: 332 except UploadError, e:
281 branch.error = e 333 branch.error = e
@@ -312,6 +364,29 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
312 pending = [] 364 pending = []
313 reviewers = [] 365 reviewers = []
314 cc = [] 366 cc = []
367 branch = None
368
369 if opt.branch:
370 branch = opt.branch
371
372 for project in project_list:
373 if opt.current_branch:
374 cbr = project.CurrentBranch
375 avail = [project.GetUploadableBranch(cbr)] if cbr else None
376 else:
377 avail = project.GetUploadableBranches(branch)
378 if avail:
379 pending.append((project, avail))
380
381 if pending and (not opt.bypass_hooks):
382 hook = RepoHook('pre-upload', self.manifest.repo_hooks_project,
383 self.manifest.topdir, abort_if_user_denies=True)
384 pending_proj_names = [project.name for (project, avail) in pending]
385 try:
386 hook.Run(opt.allow_all_hooks, project_list=pending_proj_names)
387 except HookError, e:
388 print >>sys.stderr, "ERROR: %s" % str(e)
389 return
315 390
316 if opt.reviewers: 391 if opt.reviewers:
317 reviewers = _SplitEmails(opt.reviewers) 392 reviewers = _SplitEmails(opt.reviewers)
@@ -319,11 +394,6 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
319 cc = _SplitEmails(opt.cc) 394 cc = _SplitEmails(opt.cc)
320 people = (reviewers,cc) 395 people = (reviewers,cc)
321 396
322 for project in project_list:
323 avail = project.GetUploadableBranches()
324 if avail:
325 pending.append((project, avail))
326
327 if not pending: 397 if not pending:
328 print >>sys.stdout, "no branches ready for upload" 398 print >>sys.stdout, "no branches ready for upload"
329 elif len(pending) == 1 and len(pending[0][1]) == 1: 399 elif len(pending) == 1 and len(pending[0][1]) == 1:
diff --git a/subcmds/version.py b/subcmds/version.py
index 83e77d0b..03195f88 100644
--- a/subcmds/version.py
+++ b/subcmds/version.py
@@ -19,6 +19,9 @@ from git_command import git
19from project import HEAD 19from project import HEAD
20 20
21class Version(Command, MirrorSafeCommand): 21class Version(Command, MirrorSafeCommand):
22 wrapper_version = None
23 wrapper_path = None
24
22 common = False 25 common = False
23 helpSummary = "Display the version of repo" 26 helpSummary = "Display the version of repo"
24 helpUsage = """ 27 helpUsage = """
@@ -31,5 +34,10 @@ class Version(Command, MirrorSafeCommand):
31 34
32 print 'repo version %s' % rp.work_git.describe(HEAD) 35 print 'repo version %s' % rp.work_git.describe(HEAD)
33 print ' (from %s)' % rem.url 36 print ' (from %s)' % rem.url
37
38 if Version.wrapper_path is not None:
39 print 'repo launcher version %s' % Version.wrapper_version
40 print ' (from %s)' % Version.wrapper_path
41
34 print git.version().strip() 42 print git.version().strip()
35 print 'Python %s' % sys.version 43 print 'Python %s' % sys.version