diff options
author | Shawn O. Pearce <sop@google.com> | 2010-12-07 10:31:19 -0800 |
---|---|---|
committer | Shawn O. Pearce <sop@google.com> | 2010-12-07 11:13:29 -0800 |
commit | 13f3da50d40b89ee5b05f5f3de9542c20edac6d1 (patch) | |
tree | d085b6f6b498bde85a1969fce884dd24e88d03d5 /subcmds | |
parent | 3218c13205694434edb2375ab8a8515554eed366 (diff) | |
parent | 2b8db3ce3e7344b9f3b5216637c5af0d54be5656 (diff) | |
download | git-repo-13f3da50d40b89ee5b05f5f3de9542c20edac6d1.tar.gz |
Merge branch 'stable'
* stable: (33 commits)
Added feature to print a <notice> from manifest at the end of a sync.
sync: Use --force-broken to continue other projects
upload: Remove --replace option
sync --quiet: be more quiet
sync: Enable use of git clone --reference
Only delete corrupt pickle config files if they exist
Don't allow git fetch to start ControlMaster
Check for existing SSH ControlMaster
Fix for handling values of EDITOR which contain a space.
upload: Fix --replace flag
rebase: Pass through more options
upload: Allow review.HOST.username to override email
upload -t: Automatically include local branch name
Warn users before uploading if there are local changes
sync: Try fetching a tag as a last resort before giving up
rebase: Automatically rebase branch on upstrea
upload: Automatically --cc folks in review.URL.autocopy
Fix format string bugs in grep
Do not invoke ssh with -p argument when no port has been specified.
Allow files to be copied into new folders
...
Conflicts:
git_config.py
manifest_xml.py
subcmds/init.py
subcmds/sync.py
subcmds/upload.py
Change-Id: I4756a6908277e91505c35287a122a775b68f4df5
Diffstat (limited to 'subcmds')
-rw-r--r-- | subcmds/branches.py | 12 | ||||
-rw-r--r-- | subcmds/grep.py | 8 | ||||
-rw-r--r-- | subcmds/init.py | 29 | ||||
-rw-r--r-- | subcmds/rebase.py | 107 | ||||
-rw-r--r-- | subcmds/smartsync.py | 33 | ||||
-rw-r--r-- | subcmds/sync.py | 196 | ||||
-rw-r--r-- | subcmds/upload.py | 164 |
7 files changed, 417 insertions, 132 deletions
diff --git a/subcmds/branches.py b/subcmds/branches.py index 0e3ab3c2..a4f8d360 100644 --- a/subcmds/branches.py +++ b/subcmds/branches.py | |||
@@ -136,7 +136,7 @@ is shown, then the branch appears in all projects. | |||
136 | hdr('%c%c %-*s' % (current, published, width, name)) | 136 | hdr('%c%c %-*s' % (current, published, width, name)) |
137 | out.write(' |') | 137 | out.write(' |') |
138 | 138 | ||
139 | if in_cnt < project_cnt and (in_cnt == 1): | 139 | if in_cnt < project_cnt: |
140 | fmt = out.write | 140 | fmt = out.write |
141 | paths = [] | 141 | paths = [] |
142 | if in_cnt < project_cnt - in_cnt: | 142 | if in_cnt < project_cnt - in_cnt: |
@@ -150,15 +150,17 @@ is shown, then the branch appears in all projects. | |||
150 | for b in i.projects: | 150 | for b in i.projects: |
151 | have.add(b.project) | 151 | have.add(b.project) |
152 | for p in projects: | 152 | for p in projects: |
153 | paths.append(p.relpath) | 153 | if not p in have: |
154 | paths.append(p.relpath) | ||
154 | 155 | ||
155 | s = ' %s %s' % (type, ', '.join(paths)) | 156 | s = ' %s %s' % (type, ', '.join(paths)) |
156 | if width + 7 + len(s) < 80: | 157 | if width + 7 + len(s) < 80: |
157 | fmt(s) | 158 | fmt(s) |
158 | else: | 159 | else: |
159 | out.nl() | 160 | fmt(' %s:' % type) |
160 | fmt(' %s:' % type) | ||
161 | for p in paths: | 161 | for p in paths: |
162 | out.nl() | 162 | out.nl() |
163 | fmt(' %s' % p) | 163 | fmt(width*' ' + ' %s' % p) |
164 | else: | ||
165 | out.write(' in all projects') | ||
164 | out.nl() | 166 | out.nl() |
diff --git a/subcmds/grep.py b/subcmds/grep.py index 4f714271..1cb5650b 100644 --- a/subcmds/grep.py +++ b/subcmds/grep.py | |||
@@ -204,7 +204,7 @@ contain a line that matches both expressions: | |||
204 | else: | 204 | else: |
205 | out.project('--- project %s ---' % project.relpath) | 205 | out.project('--- project %s ---' % project.relpath) |
206 | out.nl() | 206 | out.nl() |
207 | out.write(p.stderr) | 207 | out.write("%s", p.stderr) |
208 | out.nl() | 208 | out.nl() |
209 | continue | 209 | continue |
210 | have_match = True | 210 | have_match = True |
@@ -217,17 +217,17 @@ contain a line that matches both expressions: | |||
217 | if have_rev and full_name: | 217 | if have_rev and full_name: |
218 | for line in r: | 218 | for line in r: |
219 | rev, line = line.split(':', 1) | 219 | rev, line = line.split(':', 1) |
220 | out.write(rev) | 220 | out.write("%s", rev) |
221 | out.write(':') | 221 | out.write(':') |
222 | out.project(project.relpath) | 222 | out.project(project.relpath) |
223 | out.write('/') | 223 | out.write('/') |
224 | out.write(line) | 224 | out.write("%s", line) |
225 | out.nl() | 225 | out.nl() |
226 | elif full_name: | 226 | elif full_name: |
227 | for line in r: | 227 | for line in r: |
228 | out.project(project.relpath) | 228 | out.project(project.relpath) |
229 | out.write('/') | 229 | out.write('/') |
230 | out.write(line) | 230 | out.write("%s", line) |
231 | out.nl() | 231 | out.nl() |
232 | else: | 232 | else: |
233 | for line in r: | 233 | for line in r: |
diff --git a/subcmds/init.py b/subcmds/init.py index cdbbfdf7..2ca4e163 100644 --- a/subcmds/init.py +++ b/subcmds/init.py | |||
@@ -40,6 +40,17 @@ current working directory. | |||
40 | The optional -b argument can be used to select the manifest branch | 40 | The optional -b argument can be used to select the manifest branch |
41 | to checkout and use. If no branch is specified, master is assumed. | 41 | to checkout and use. If no branch is specified, master is assumed. |
42 | 42 | ||
43 | The optional -m argument can be used to specify an alternate manifest | ||
44 | to be used. If no manifest is specified, the manifest default.xml | ||
45 | will be used. | ||
46 | |||
47 | The --reference option can be used to point to a directory that | ||
48 | has the content of a --mirror sync. This will make the working | ||
49 | directory use as much data as possible from the local reference | ||
50 | directory when fetching from the server. This will make the sync | ||
51 | go a lot faster by reducing data traffic on the network. | ||
52 | |||
53 | |||
43 | Switching Manifest Branches | 54 | Switching Manifest Branches |
44 | --------------------------- | 55 | --------------------------- |
45 | 56 | ||
@@ -76,7 +87,9 @@ to update the working directory files. | |||
76 | g.add_option('--mirror', | 87 | g.add_option('--mirror', |
77 | dest='mirror', action='store_true', | 88 | dest='mirror', action='store_true', |
78 | help='mirror the forrest') | 89 | help='mirror the forrest') |
79 | 90 | g.add_option('--reference', | |
91 | dest='reference', | ||
92 | help='location of mirror directory', metavar='DIR') | ||
80 | 93 | ||
81 | # Tool | 94 | # Tool |
82 | g = p.add_option_group('repo Version options') | 95 | g = p.add_option_group('repo Version options') |
@@ -132,6 +145,9 @@ to update the working directory files. | |||
132 | r.ResetFetch() | 145 | r.ResetFetch() |
133 | r.Save() | 146 | r.Save() |
134 | 147 | ||
148 | if opt.reference: | ||
149 | m.config.SetString('repo.reference', opt.reference) | ||
150 | |||
135 | if opt.mirror: | 151 | if opt.mirror: |
136 | if is_new: | 152 | if is_new: |
137 | m.config.SetString('repo.mirror', 'true') | 153 | m.config.SetString('repo.mirror', 'true') |
@@ -162,7 +178,11 @@ to update the working directory files. | |||
162 | syncbuf = SyncBuffer(m.config) | 178 | syncbuf = SyncBuffer(m.config) |
163 | m.Sync_LocalHalf(syncbuf) | 179 | m.Sync_LocalHalf(syncbuf) |
164 | syncbuf.Finish() | 180 | syncbuf.Finish() |
181 | |||
182 | if isinstance(self.manifest, XmlManifest): | ||
183 | self._LinkManifest(opt.manifest_name) | ||
165 | _ReloadManifest(self) | 184 | _ReloadManifest(self) |
185 | |||
166 | self._ApplyOptions(opt, is_new) | 186 | self._ApplyOptions(opt, is_new) |
167 | 187 | ||
168 | if not self.manifest.InitBranch(): | 188 | if not self.manifest.InitBranch(): |
@@ -200,8 +220,9 @@ to update the working directory files. | |||
200 | 220 | ||
201 | print '' | 221 | print '' |
202 | print 'Your identity is: %s <%s>' % (name, email) | 222 | print 'Your identity is: %s <%s>' % (name, email) |
203 | sys.stdout.write('is this correct [yes/no]? ') | 223 | sys.stdout.write('is this correct [y/n]? ') |
204 | if 'yes' == sys.stdin.readline().strip(): | 224 | a = sys.stdin.readline().strip() |
225 | if a in ('yes', 'y', 't', 'true'): | ||
205 | break | 226 | break |
206 | 227 | ||
207 | if name != mp.UserName: | 228 | if name != mp.UserName: |
@@ -249,8 +270,6 @@ to update the working directory files. | |||
249 | def Execute(self, opt, args): | 270 | def Execute(self, opt, args): |
250 | git_require(MIN_GIT_VERSION, fail=True) | 271 | git_require(MIN_GIT_VERSION, fail=True) |
251 | self._SyncManifest(opt) | 272 | self._SyncManifest(opt) |
252 | if isinstance(self.manifest, XmlManifest): | ||
253 | self._LinkManifest(opt.manifest_name) | ||
254 | 273 | ||
255 | if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror: | 274 | if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror: |
256 | self._ConfigureUser() | 275 | self._ConfigureUser() |
diff --git a/subcmds/rebase.py b/subcmds/rebase.py new file mode 100644 index 00000000..e341296d --- /dev/null +++ b/subcmds/rebase.py | |||
@@ -0,0 +1,107 @@ | |||
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 | |||
16 | import sys | ||
17 | |||
18 | from command import Command | ||
19 | from git_command import GitCommand | ||
20 | from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB | ||
21 | from error import GitError | ||
22 | |||
23 | class Rebase(Command): | ||
24 | common = True | ||
25 | helpSummary = "Rebase local branches on upstream branch" | ||
26 | helpUsage = """ | ||
27 | %prog {[<project>...] | -i <project>...} | ||
28 | """ | ||
29 | helpDescription = """ | ||
30 | '%prog' uses git rebase to move local changes in the current topic branch to | ||
31 | the HEAD of the upstream history, useful when you have made commits in a topic | ||
32 | branch but need to incorporate new upstream changes "underneath" them. | ||
33 | """ | ||
34 | |||
35 | def _Options(self, p): | ||
36 | p.add_option('-i', '--interactive', | ||
37 | dest="interactive", action="store_true", | ||
38 | help="interactive rebase (single project only)") | ||
39 | |||
40 | p.add_option('-f', '--force-rebase', | ||
41 | dest='force_rebase', action='store_true', | ||
42 | help='Pass --force-rebase to git rebase') | ||
43 | p.add_option('--no-ff', | ||
44 | dest='no_ff', action='store_true', | ||
45 | help='Pass --no-ff to git rebase') | ||
46 | p.add_option('-q', '--quiet', | ||
47 | dest='quiet', action='store_true', | ||
48 | help='Pass --quiet to git rebase') | ||
49 | p.add_option('--autosquash', | ||
50 | dest='autosquash', action='store_true', | ||
51 | help='Pass --autosquash to git rebase') | ||
52 | p.add_option('--whitespace', | ||
53 | dest='whitespace', action='store', metavar='WS', | ||
54 | help='Pass --whitespace to git rebase') | ||
55 | |||
56 | def Execute(self, opt, args): | ||
57 | all = self.GetProjects(args) | ||
58 | one_project = len(all) == 1 | ||
59 | |||
60 | if opt.interactive and not one_project: | ||
61 | print >>sys.stderr, 'error: interactive rebase not supported with multiple projects' | ||
62 | return -1 | ||
63 | |||
64 | for project in all: | ||
65 | cb = project.CurrentBranch | ||
66 | if not cb: | ||
67 | if one_project: | ||
68 | print >>sys.stderr, "error: project %s has a detatched HEAD" % project.relpath | ||
69 | return -1 | ||
70 | # ignore branches with detatched HEADs | ||
71 | continue | ||
72 | |||
73 | upbranch = project.GetBranch(cb) | ||
74 | if not upbranch.LocalMerge: | ||
75 | if one_project: | ||
76 | print >>sys.stderr, "error: project %s does not track any remote branches" % project.relpath | ||
77 | return -1 | ||
78 | # ignore branches without remotes | ||
79 | continue | ||
80 | |||
81 | args = ["rebase"] | ||
82 | |||
83 | if opt.whitespace: | ||
84 | args.append('--whitespace=%s' % opt.whitespace) | ||
85 | |||
86 | if opt.quiet: | ||
87 | args.append('--quiet') | ||
88 | |||
89 | if opt.force_rebase: | ||
90 | args.append('--force-rebase') | ||
91 | |||
92 | if opt.no_ff: | ||
93 | args.append('--no-ff') | ||
94 | |||
95 | if opt.autosquash: | ||
96 | args.append('--autosquash') | ||
97 | |||
98 | if opt.interactive: | ||
99 | args.append("-i") | ||
100 | |||
101 | args.append(upbranch.LocalMerge) | ||
102 | |||
103 | print >>sys.stderr, '# %s: rebasing %s -> %s' % \ | ||
104 | (project.relpath, cb, upbranch.LocalMerge) | ||
105 | |||
106 | if GitCommand(project, args).Wait() != 0: | ||
107 | return -1 | ||
diff --git a/subcmds/smartsync.py b/subcmds/smartsync.py new file mode 100644 index 00000000..1edbd35b --- /dev/null +++ b/subcmds/smartsync.py | |||
@@ -0,0 +1,33 @@ | |||
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 | |||
16 | from sync import Sync | ||
17 | |||
18 | class Smartsync(Sync): | ||
19 | common = True | ||
20 | helpSummary = "Update working tree to the latest known good revision" | ||
21 | helpUsage = """ | ||
22 | %prog [<project>...] | ||
23 | """ | ||
24 | helpDescription = """ | ||
25 | The '%prog' command is a shortcut for sync -s. | ||
26 | """ | ||
27 | |||
28 | def _Options(self, p): | ||
29 | Sync._Options(self, p, show_smart=False) | ||
30 | |||
31 | def Execute(self, opt, args): | ||
32 | opt.smart_sync = True | ||
33 | Sync.Execute(self, opt, args) | ||
diff --git a/subcmds/sync.py b/subcmds/sync.py index d89c2b8c..7b77388b 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py | |||
@@ -17,11 +17,19 @@ from optparse import SUPPRESS_HELP | |||
17 | import os | 17 | import os |
18 | import re | 18 | import re |
19 | import shutil | 19 | import shutil |
20 | import socket | ||
20 | import subprocess | 21 | import subprocess |
21 | import sys | 22 | import sys |
22 | import time | 23 | import time |
24 | import xmlrpclib | ||
25 | |||
26 | try: | ||
27 | import threading as _threading | ||
28 | except ImportError: | ||
29 | import dummy_threading as _threading | ||
23 | 30 | ||
24 | from git_command import GIT | 31 | from git_command import GIT |
32 | from git_refs import R_HEADS | ||
25 | from project import HEAD | 33 | from project import HEAD |
26 | from project import Project | 34 | from project import Project |
27 | from project import RemoteSpec | 35 | from project import RemoteSpec |
@@ -32,6 +40,7 @@ from project import SyncBuffer | |||
32 | from progress import Progress | 40 | from progress import Progress |
33 | 41 | ||
34 | class Sync(Command, MirrorSafeCommand): | 42 | class Sync(Command, MirrorSafeCommand): |
43 | jobs = 1 | ||
35 | common = True | 44 | common = True |
36 | helpSummary = "Update working tree to the latest revision" | 45 | helpSummary = "Update working tree to the latest revision" |
37 | helpUsage = """ | 46 | helpUsage = """ |
@@ -57,6 +66,13 @@ back to the manifest revision. This option is especially helpful | |||
57 | if the project is currently on a topic branch, but the manifest | 66 | if the project is currently on a topic branch, but the manifest |
58 | revision is temporarily needed. | 67 | revision is temporarily needed. |
59 | 68 | ||
69 | The -s/--smart-sync option can be used to sync to a known good | ||
70 | build as specified by the manifest-server element in the current | ||
71 | manifest. | ||
72 | |||
73 | The -f/--force-broken option can be used to proceed with syncing | ||
74 | other projects if a project sync fails. | ||
75 | |||
60 | SSH Connections | 76 | SSH Connections |
61 | --------------- | 77 | --------------- |
62 | 78 | ||
@@ -87,7 +103,10 @@ later is required to fix a server side protocol bug. | |||
87 | 103 | ||
88 | """ | 104 | """ |
89 | 105 | ||
90 | def _Options(self, p): | 106 | def _Options(self, p, show_smart=True): |
107 | p.add_option('-f', '--force-broken', | ||
108 | dest='force_broken', action='store_true', | ||
109 | help="continue sync even if a project fails to sync") | ||
91 | p.add_option('-l','--local-only', | 110 | p.add_option('-l','--local-only', |
92 | dest='local_only', action='store_true', | 111 | dest='local_only', action='store_true', |
93 | help="only update working tree, don't fetch") | 112 | help="only update working tree, don't fetch") |
@@ -97,6 +116,16 @@ later is required to fix a server side protocol bug. | |||
97 | p.add_option('-d','--detach', | 116 | p.add_option('-d','--detach', |
98 | dest='detach_head', action='store_true', | 117 | dest='detach_head', action='store_true', |
99 | help='detach projects back to manifest revision') | 118 | help='detach projects back to manifest revision') |
119 | p.add_option('-q','--quiet', | ||
120 | dest='quiet', action='store_true', | ||
121 | help='be more quiet') | ||
122 | p.add_option('-j','--jobs', | ||
123 | dest='jobs', action='store', type='int', | ||
124 | help="number of projects to fetch simultaneously") | ||
125 | if show_smart: | ||
126 | p.add_option('-s', '--smart-sync', | ||
127 | dest='smart_sync', action='store_true', | ||
128 | help='smart sync using manifest from a known good build') | ||
100 | 129 | ||
101 | g = p.add_option_group('repo Version options') | 130 | g = p.add_option_group('repo Version options') |
102 | g.add_option('--no-repo-verify', | 131 | g.add_option('--no-repo-verify', |
@@ -106,16 +135,55 @@ later is required to fix a server side protocol bug. | |||
106 | dest='repo_upgraded', action='store_true', | 135 | dest='repo_upgraded', action='store_true', |
107 | help=SUPPRESS_HELP) | 136 | help=SUPPRESS_HELP) |
108 | 137 | ||
109 | def _Fetch(self, projects): | 138 | def _FetchHelper(self, opt, project, lock, fetched, pm, sem): |
139 | if not project.Sync_NetworkHalf(quiet=opt.quiet): | ||
140 | print >>sys.stderr, 'error: Cannot fetch %s' % project.name | ||
141 | if opt.force_broken: | ||
142 | print >>sys.stderr, 'warn: --force-broken, continuing to sync' | ||
143 | else: | ||
144 | sem.release() | ||
145 | sys.exit(1) | ||
146 | |||
147 | lock.acquire() | ||
148 | fetched.add(project.gitdir) | ||
149 | pm.update() | ||
150 | lock.release() | ||
151 | sem.release() | ||
152 | |||
153 | def _Fetch(self, projects, opt): | ||
110 | fetched = set() | 154 | fetched = set() |
111 | pm = Progress('Fetching projects', len(projects)) | 155 | pm = Progress('Fetching projects', len(projects)) |
112 | for project in projects: | 156 | |
113 | pm.update() | 157 | if self.jobs == 1: |
114 | if project.Sync_NetworkHalf(): | 158 | for project in projects: |
115 | fetched.add(project.gitdir) | 159 | pm.update() |
116 | else: | 160 | if project.Sync_NetworkHalf(quiet=opt.quiet): |
117 | print >>sys.stderr, 'error: Cannot fetch %s' % project.name | 161 | fetched.add(project.gitdir) |
118 | sys.exit(1) | 162 | else: |
163 | print >>sys.stderr, 'error: Cannot fetch %s' % project.name | ||
164 | if opt.force_broken: | ||
165 | print >>sys.stderr, 'warn: --force-broken, continuing to sync' | ||
166 | else: | ||
167 | sys.exit(1) | ||
168 | else: | ||
169 | threads = set() | ||
170 | lock = _threading.Lock() | ||
171 | sem = _threading.Semaphore(self.jobs) | ||
172 | for project in projects: | ||
173 | sem.acquire() | ||
174 | t = _threading.Thread(target = self._FetchHelper, | ||
175 | args = (opt, | ||
176 | project, | ||
177 | lock, | ||
178 | fetched, | ||
179 | pm, | ||
180 | sem)) | ||
181 | threads.add(t) | ||
182 | t.start() | ||
183 | |||
184 | for t in threads: | ||
185 | t.join() | ||
186 | |||
119 | pm.end() | 187 | pm.end() |
120 | for project in projects: | 188 | for project in projects: |
121 | project.bare_git.gc('--auto') | 189 | project.bare_git.gc('--auto') |
@@ -140,32 +208,36 @@ later is required to fix a server side protocol bug. | |||
140 | if not path: | 208 | if not path: |
141 | continue | 209 | continue |
142 | if path not in new_project_paths: | 210 | if path not in new_project_paths: |
143 | project = Project( | 211 | """If the path has already been deleted, we don't need to do it |
144 | manifest = self.manifest, | 212 | """ |
145 | name = path, | 213 | if os.path.exists(self.manifest.topdir + '/' + path): |
146 | remote = RemoteSpec('origin'), | 214 | project = Project( |
147 | gitdir = os.path.join(self.manifest.topdir, | 215 | manifest = self.manifest, |
148 | path, '.git'), | 216 | name = path, |
149 | worktree = os.path.join(self.manifest.topdir, path), | 217 | remote = RemoteSpec('origin'), |
150 | relpath = path, | 218 | gitdir = os.path.join(self.manifest.topdir, |
151 | revisionExpr = 'HEAD', | 219 | path, '.git'), |
152 | revisionId = None) | 220 | worktree = os.path.join(self.manifest.topdir, path), |
153 | if project.IsDirty(): | 221 | relpath = path, |
154 | print >>sys.stderr, 'error: Cannot remove project "%s": \ | 222 | revisionExpr = 'HEAD', |
223 | revisionId = None) | ||
224 | |||
225 | if project.IsDirty(): | ||
226 | print >>sys.stderr, 'error: Cannot remove project "%s": \ | ||
155 | uncommitted changes are present' % project.relpath | 227 | uncommitted changes are present' % project.relpath |
156 | print >>sys.stderr, ' commit changes, then run sync again' | 228 | print >>sys.stderr, ' commit changes, then run sync again' |
157 | return -1 | 229 | return -1 |
158 | else: | 230 | else: |
159 | print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree | 231 | print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree |
160 | shutil.rmtree(project.worktree) | 232 | shutil.rmtree(project.worktree) |
161 | # Try deleting parent subdirs if they are empty | 233 | # Try deleting parent subdirs if they are empty |
162 | dir = os.path.dirname(project.worktree) | 234 | dir = os.path.dirname(project.worktree) |
163 | while dir != self.manifest.topdir: | 235 | while dir != self.manifest.topdir: |
164 | try: | 236 | try: |
165 | os.rmdir(dir) | 237 | os.rmdir(dir) |
166 | except OSError: | 238 | except OSError: |
167 | break | 239 | break |
168 | dir = os.path.dirname(dir) | 240 | dir = os.path.dirname(dir) |
169 | 241 | ||
170 | new_project_paths.sort() | 242 | new_project_paths.sort() |
171 | fd = open(file_path, 'w') | 243 | fd = open(file_path, 'w') |
@@ -177,6 +249,8 @@ uncommitted changes are present' % project.relpath | |||
177 | return 0 | 249 | return 0 |
178 | 250 | ||
179 | def Execute(self, opt, args): | 251 | def Execute(self, opt, args): |
252 | if opt.jobs: | ||
253 | self.jobs = opt.jobs | ||
180 | if opt.network_only and opt.detach_head: | 254 | if opt.network_only and opt.detach_head: |
181 | print >>sys.stderr, 'error: cannot combine -n and -d' | 255 | print >>sys.stderr, 'error: cannot combine -n and -d' |
182 | sys.exit(1) | 256 | sys.exit(1) |
@@ -184,6 +258,51 @@ uncommitted changes are present' % project.relpath | |||
184 | print >>sys.stderr, 'error: cannot combine -n and -l' | 258 | print >>sys.stderr, 'error: cannot combine -n and -l' |
185 | sys.exit(1) | 259 | sys.exit(1) |
186 | 260 | ||
261 | if opt.smart_sync: | ||
262 | if not self.manifest.manifest_server: | ||
263 | print >>sys.stderr, \ | ||
264 | 'error: cannot smart sync: no manifest server defined in manifest' | ||
265 | sys.exit(1) | ||
266 | try: | ||
267 | server = xmlrpclib.Server(self.manifest.manifest_server) | ||
268 | p = self.manifest.manifestProject | ||
269 | b = p.GetBranch(p.CurrentBranch) | ||
270 | branch = b.merge | ||
271 | if branch.startswith(R_HEADS): | ||
272 | branch = branch[len(R_HEADS):] | ||
273 | |||
274 | env = dict(os.environ) | ||
275 | if (env.has_key('TARGET_PRODUCT') and | ||
276 | env.has_key('TARGET_BUILD_VARIANT')): | ||
277 | target = '%s-%s' % (env['TARGET_PRODUCT'], | ||
278 | env['TARGET_BUILD_VARIANT']) | ||
279 | [success, manifest_str] = server.GetApprovedManifest(branch, target) | ||
280 | else: | ||
281 | [success, manifest_str] = server.GetApprovedManifest(branch) | ||
282 | |||
283 | if success: | ||
284 | manifest_name = "smart_sync_override.xml" | ||
285 | manifest_path = os.path.join(self.manifest.manifestProject.worktree, | ||
286 | manifest_name) | ||
287 | try: | ||
288 | f = open(manifest_path, 'w') | ||
289 | try: | ||
290 | f.write(manifest_str) | ||
291 | finally: | ||
292 | f.close() | ||
293 | except IOError: | ||
294 | print >>sys.stderr, 'error: cannot write manifest to %s' % \ | ||
295 | manifest_path | ||
296 | sys.exit(1) | ||
297 | self.manifest.Override(manifest_name) | ||
298 | else: | ||
299 | print >>sys.stderr, 'error: %s' % manifest_str | ||
300 | sys.exit(1) | ||
301 | except socket.error: | ||
302 | print >>sys.stderr, 'error: cannot connect to manifest server %s' % ( | ||
303 | self.manifest.manifest_server) | ||
304 | sys.exit(1) | ||
305 | |||
187 | rp = self.manifest.repoProject | 306 | rp = self.manifest.repoProject |
188 | rp.PreSync() | 307 | rp.PreSync() |
189 | 308 | ||
@@ -194,7 +313,7 @@ uncommitted changes are present' % project.relpath | |||
194 | _PostRepoUpgrade(self.manifest) | 313 | _PostRepoUpgrade(self.manifest) |
195 | 314 | ||
196 | if not opt.local_only: | 315 | if not opt.local_only: |
197 | mp.Sync_NetworkHalf() | 316 | mp.Sync_NetworkHalf(quiet=opt.quiet) |
198 | 317 | ||
199 | if mp.HasChanges: | 318 | if mp.HasChanges: |
200 | syncbuf = SyncBuffer(mp.config) | 319 | syncbuf = SyncBuffer(mp.config) |
@@ -211,7 +330,7 @@ uncommitted changes are present' % project.relpath | |||
211 | to_fetch.append(rp) | 330 | to_fetch.append(rp) |
212 | to_fetch.extend(all) | 331 | to_fetch.extend(all) |
213 | 332 | ||
214 | fetched = self._Fetch(to_fetch) | 333 | fetched = self._Fetch(to_fetch, opt) |
215 | _PostRepoFetch(rp, opt.no_repo_verify) | 334 | _PostRepoFetch(rp, opt.no_repo_verify) |
216 | if opt.network_only: | 335 | if opt.network_only: |
217 | # bail out now; the rest touches the working tree | 336 | # bail out now; the rest touches the working tree |
@@ -230,7 +349,7 @@ uncommitted changes are present' % project.relpath | |||
230 | for project in all: | 349 | for project in all: |
231 | if project.gitdir not in fetched: | 350 | if project.gitdir not in fetched: |
232 | missing.append(project) | 351 | missing.append(project) |
233 | self._Fetch(missing) | 352 | self._Fetch(missing, opt) |
234 | 353 | ||
235 | if self.manifest.IsMirror: | 354 | if self.manifest.IsMirror: |
236 | # bail out now, we have no working tree | 355 | # bail out now, we have no working tree |
@@ -258,6 +377,9 @@ def _ReloadManifest(cmd): | |||
258 | if old.__class__ != new.__class__: | 377 | if old.__class__ != new.__class__: |
259 | print >>sys.stderr, 'NOTICE: manifest format has changed ***' | 378 | print >>sys.stderr, 'NOTICE: manifest format has changed ***' |
260 | new.Upgrade_Local(old) | 379 | new.Upgrade_Local(old) |
380 | else: | ||
381 | if new.notice: | ||
382 | print new.notice | ||
261 | 383 | ||
262 | def _PostRepoUpgrade(manifest): | 384 | def _PostRepoUpgrade(manifest): |
263 | for project in manifest.projects.values(): | 385 | for project in manifest.projects.values(): |
diff --git a/subcmds/upload.py b/subcmds/upload.py index 2ab6a484..20822096 100644 --- a/subcmds/upload.py +++ b/subcmds/upload.py | |||
@@ -13,6 +13,7 @@ | |||
13 | # See the License for the specific language governing permissions and | 13 | # See the License for the specific language governing permissions and |
14 | # limitations under the License. | 14 | # limitations under the License. |
15 | 15 | ||
16 | import copy | ||
16 | import re | 17 | import re |
17 | import sys | 18 | import sys |
18 | 19 | ||
@@ -20,6 +21,17 @@ from command import InteractiveCommand | |||
20 | from editor import Editor | 21 | from editor import Editor |
21 | from error import UploadError | 22 | from error import UploadError |
22 | 23 | ||
24 | UNUSUAL_COMMIT_THRESHOLD = 5 | ||
25 | |||
26 | def _ConfirmManyUploads(multiple_branches=False): | ||
27 | if multiple_branches: | ||
28 | print "ATTENTION: One or more branches has an unusually high number of commits." | ||
29 | else: | ||
30 | print "ATTENTION: You are uploading an unusually high number of commits." | ||
31 | print "YOU PROBABLY DO NOT MEAN TO DO THIS. (Did you rebase across branches?)" | ||
32 | answer = raw_input("If you are sure you intend to do this, type 'yes': ").strip() | ||
33 | return answer == "yes" | ||
34 | |||
23 | def _die(fmt, *args): | 35 | def _die(fmt, *args): |
24 | msg = fmt % args | 36 | msg = fmt % args |
25 | print >>sys.stderr, 'error: %s' % msg | 37 | print >>sys.stderr, 'error: %s' % msg |
@@ -35,7 +47,7 @@ class Upload(InteractiveCommand): | |||
35 | common = True | 47 | common = True |
36 | helpSummary = "Upload changes for code review" | 48 | helpSummary = "Upload changes for code review" |
37 | helpUsage=""" | 49 | helpUsage=""" |
38 | %prog [--re --cc] {[<project>]... | --replace <project>} | 50 | %prog [--re --cc] [<project>]... |
39 | """ | 51 | """ |
40 | helpDescription = """ | 52 | helpDescription = """ |
41 | The '%prog' command is used to send changes to the Gerrit Code | 53 | The '%prog' command is used to send changes to the Gerrit Code |
@@ -55,12 +67,6 @@ added to the respective list of users, and emails are sent to any | |||
55 | new users. Users passed as --reviewers must already be registered | 67 | new users. Users passed as --reviewers must already be registered |
56 | with the code review system, or the upload will fail. | 68 | with the code review system, or the upload will fail. |
57 | 69 | ||
58 | If the --replace option (deprecated) is passed the user can designate | ||
59 | which existing change(s) in Gerrit match up to the commits in the | ||
60 | branch being uploaded. For each matched pair of change,commit the | ||
61 | commit will be added as a new patch set, completely replacing the | ||
62 | set of files and description associated with the change in Gerrit. | ||
63 | |||
64 | Configuration | 70 | Configuration |
65 | ------------- | 71 | ------------- |
66 | 72 | ||
@@ -72,6 +78,19 @@ to "true" then repo will assume you always answer "y" at the prompt, | |||
72 | and will not prompt you further. If it is set to "false" then repo | 78 | and will not prompt you further. If it is set to "false" then repo |
73 | will assume you always answer "n", and will abort. | 79 | will assume you always answer "n", and will abort. |
74 | 80 | ||
81 | review.URL.autocopy: | ||
82 | |||
83 | To automatically copy a user or mailing list to all uploaded reviews, | ||
84 | you can set a per-project or global Git option to do so. Specifically, | ||
85 | review.URL.autocopy can be set to a comma separated list of reviewers | ||
86 | who you always want copied on all uploads with a non-empty --re | ||
87 | argument. | ||
88 | |||
89 | review.URL.username: | ||
90 | |||
91 | Override the username used to connect to Gerrit Code Review. | ||
92 | By default the local part of the email address is used. | ||
93 | |||
75 | The URL must match the review URL listed in the manifest XML file, | 94 | The URL must match the review URL listed in the manifest XML file, |
76 | or in the .git/config within the project. For example: | 95 | or in the .git/config within the project. For example: |
77 | 96 | ||
@@ -81,6 +100,7 @@ or in the .git/config within the project. For example: | |||
81 | 100 | ||
82 | [review "http://review.example.com/"] | 101 | [review "http://review.example.com/"] |
83 | autoupload = true | 102 | autoupload = true |
103 | autocopy = johndoe@company.com,my-team-alias@company.com | ||
84 | 104 | ||
85 | References | 105 | References |
86 | ---------- | 106 | ---------- |
@@ -90,9 +110,9 @@ Gerrit Code Review: http://code.google.com/p/gerrit/ | |||
90 | """ | 110 | """ |
91 | 111 | ||
92 | def _Options(self, p): | 112 | def _Options(self, p): |
93 | p.add_option('--replace', | 113 | p.add_option('-t', |
94 | dest='replace', action='store_true', | 114 | dest='auto_topic', action='store_true', |
95 | help='Upload replacement patchsets from this branch (deprecated)') | 115 | help='Send local branch name to Gerrit Code Review') |
96 | p.add_option('--re', '--reviewers', | 116 | p.add_option('--re', '--reviewers', |
97 | type='string', action='append', dest='reviewers', | 117 | type='string', action='append', dest='reviewers', |
98 | help='Request reviews from these people.') | 118 | help='Request reviews from these people.') |
@@ -100,7 +120,7 @@ Gerrit Code Review: http://code.google.com/p/gerrit/ | |||
100 | type='string', action='append', dest='cc', | 120 | type='string', action='append', dest='cc', |
101 | help='Also send email to these email addresses.') | 121 | help='Also send email to these email addresses.') |
102 | 122 | ||
103 | def _SingleBranch(self, branch, people): | 123 | def _SingleBranch(self, opt, branch, people): |
104 | project = branch.project | 124 | project = branch.project |
105 | name = branch.name | 125 | name = branch.name |
106 | remote = project.GetBranch(name).remote | 126 | remote = project.GetBranch(name).remote |
@@ -129,11 +149,15 @@ Gerrit Code Review: http://code.google.com/p/gerrit/ | |||
129 | answer = answer in ('y', 'Y', 'yes', '1', 'true', 't') | 149 | answer = answer in ('y', 'Y', 'yes', '1', 'true', 't') |
130 | 150 | ||
131 | if answer: | 151 | if answer: |
132 | self._UploadAndReport([branch], people) | 152 | if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD: |
153 | answer = _ConfirmManyUploads() | ||
154 | |||
155 | if answer: | ||
156 | self._UploadAndReport(opt, [branch], people) | ||
133 | else: | 157 | else: |
134 | _die("upload aborted by user") | 158 | _die("upload aborted by user") |
135 | 159 | ||
136 | def _MultipleBranches(self, pending, people): | 160 | def _MultipleBranches(self, opt, pending, people): |
137 | projects = {} | 161 | projects = {} |
138 | branches = {} | 162 | branches = {} |
139 | 163 | ||
@@ -192,7 +216,30 @@ Gerrit Code Review: http://code.google.com/p/gerrit/ | |||
192 | todo.append(branch) | 216 | todo.append(branch) |
193 | if not todo: | 217 | if not todo: |
194 | _die("nothing uncommented for upload") | 218 | _die("nothing uncommented for upload") |
195 | self._UploadAndReport(todo, people) | 219 | |
220 | many_commits = False | ||
221 | for branch in todo: | ||
222 | if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD: | ||
223 | many_commits = True | ||
224 | break | ||
225 | if many_commits: | ||
226 | if not _ConfirmManyUploads(multiple_branches=True): | ||
227 | _die("upload aborted by user") | ||
228 | |||
229 | self._UploadAndReport(opt, todo, people) | ||
230 | |||
231 | def _AppendAutoCcList(self, branch, people): | ||
232 | """ | ||
233 | Appends the list of users in the CC list in the git project's config if a | ||
234 | non-empty reviewer list was found. | ||
235 | """ | ||
236 | |||
237 | name = branch.name | ||
238 | project = branch.project | ||
239 | key = 'review.%s.autocopy' % project.GetBranch(name).remote.review | ||
240 | raw_list = project.config.GetString(key) | ||
241 | if not raw_list is None and len(people[0]) > 0: | ||
242 | people[1].extend([entry.strip() for entry in raw_list.split(',')]) | ||
196 | 243 | ||
197 | def _FindGerritChange(self, branch): | 244 | def _FindGerritChange(self, branch): |
198 | last_pub = branch.project.WasPublished(branch.name) | 245 | last_pub = branch.project.WasPublished(branch.name) |
@@ -206,66 +253,29 @@ Gerrit Code Review: http://code.google.com/p/gerrit/ | |||
206 | except: | 253 | except: |
207 | return "" | 254 | return "" |
208 | 255 | ||
209 | def _ReplaceBranch(self, project, people): | 256 | def _UploadAndReport(self, opt, todo, original_people): |
210 | branch = project.CurrentBranch | ||
211 | if not branch: | ||
212 | print >>sys.stdout, "no branches ready for upload" | ||
213 | return | ||
214 | branch = project.GetUploadableBranch(branch) | ||
215 | if not branch: | ||
216 | print >>sys.stdout, "no branches ready for upload" | ||
217 | return | ||
218 | |||
219 | script = [] | ||
220 | script.append('# Replacing from branch %s' % branch.name) | ||
221 | |||
222 | if len(branch.commits) == 1: | ||
223 | change = self._FindGerritChange(branch) | ||
224 | script.append('[%-6s] %s' % (change, branch.commits[0])) | ||
225 | else: | ||
226 | for commit in branch.commits: | ||
227 | script.append('[ ] %s' % commit) | ||
228 | |||
229 | script.append('') | ||
230 | script.append('# Insert change numbers in the brackets to add a new patch set.') | ||
231 | script.append('# To create a new change record, leave the brackets empty.') | ||
232 | |||
233 | script = Editor.EditString("\n".join(script)).split("\n") | ||
234 | |||
235 | change_re = re.compile(r'^\[\s*(\d{1,})\s*\]\s*([0-9a-f]{1,}) .*$') | ||
236 | to_replace = dict() | ||
237 | full_hashes = branch.unabbrev_commits | ||
238 | |||
239 | for line in script: | ||
240 | m = change_re.match(line) | ||
241 | if m: | ||
242 | c = m.group(1) | ||
243 | f = m.group(2) | ||
244 | try: | ||
245 | f = full_hashes[f] | ||
246 | except KeyError: | ||
247 | print 'fh = %s' % full_hashes | ||
248 | print >>sys.stderr, "error: commit %s not found" % f | ||
249 | sys.exit(1) | ||
250 | if c in to_replace: | ||
251 | print >>sys.stderr,\ | ||
252 | "error: change %s cannot accept multiple commits" % c | ||
253 | sys.exit(1) | ||
254 | to_replace[c] = f | ||
255 | |||
256 | if not to_replace: | ||
257 | print >>sys.stderr, "error: no replacements specified" | ||
258 | print >>sys.stderr, " use 'repo upload' without --replace" | ||
259 | sys.exit(1) | ||
260 | |||
261 | branch.replace_changes = to_replace | ||
262 | self._UploadAndReport([branch], people) | ||
263 | |||
264 | def _UploadAndReport(self, todo, people): | ||
265 | have_errors = False | 257 | have_errors = False |
266 | for branch in todo: | 258 | for branch in todo: |
267 | try: | 259 | try: |
268 | branch.UploadForReview(people) | 260 | people = copy.deepcopy(original_people) |
261 | self._AppendAutoCcList(branch, people) | ||
262 | |||
263 | # Check if there are local changes that may have been forgotten | ||
264 | if branch.project.HasChanges(): | ||
265 | key = 'review.%s.autoupload' % branch.project.remote.review | ||
266 | answer = branch.project.config.GetBoolean(key) | ||
267 | |||
268 | # if they want to auto upload, let's not ask because it could be automated | ||
269 | if answer is None: | ||
270 | 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() | ||
272 | if a not in ('y', 'yes', 't', 'true', 'on'): | ||
273 | print >>sys.stderr, "skipping upload" | ||
274 | branch.uploaded = False | ||
275 | branch.error = 'User aborted' | ||
276 | continue | ||
277 | |||
278 | branch.UploadForReview(people, auto_topic=opt.auto_topic) | ||
269 | branch.uploaded = True | 279 | branch.uploaded = True |
270 | except UploadError, e: | 280 | except UploadError, e: |
271 | branch.error = e | 281 | branch.error = e |
@@ -309,14 +319,6 @@ Gerrit Code Review: http://code.google.com/p/gerrit/ | |||
309 | cc = _SplitEmails(opt.cc) | 319 | cc = _SplitEmails(opt.cc) |
310 | people = (reviewers,cc) | 320 | people = (reviewers,cc) |
311 | 321 | ||
312 | if opt.replace: | ||
313 | if len(project_list) != 1: | ||
314 | print >>sys.stderr, \ | ||
315 | 'error: --replace requires exactly one project' | ||
316 | sys.exit(1) | ||
317 | self._ReplaceBranch(project_list[0], people) | ||
318 | return | ||
319 | |||
320 | for project in project_list: | 322 | for project in project_list: |
321 | avail = project.GetUploadableBranches() | 323 | avail = project.GetUploadableBranches() |
322 | if avail: | 324 | if avail: |
@@ -325,6 +327,6 @@ Gerrit Code Review: http://code.google.com/p/gerrit/ | |||
325 | if not pending: | 327 | if not pending: |
326 | print >>sys.stdout, "no branches ready for upload" | 328 | print >>sys.stdout, "no branches ready for upload" |
327 | elif len(pending) == 1 and len(pending[0][1]) == 1: | 329 | elif len(pending) == 1 and len(pending[0][1]) == 1: |
328 | self._SingleBranch(pending[0][1][0], people) | 330 | self._SingleBranch(opt, pending[0][1][0], people) |
329 | else: | 331 | else: |
330 | self._MultipleBranches(pending, people) | 332 | self._MultipleBranches(opt, pending, people) |