OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 # Copyright (C) 2008 Evan Martin <martine@danga.com> | 6 # Copyright (C) 2008 Evan Martin <martine@danga.com> |
7 | 7 |
8 """A git-command for integrating reviews on Rietveld.""" | 8 """A git-command for integrating reviews on Rietveld.""" |
9 | 9 |
10 import json | 10 import json |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
62 return e.stdout | 62 return e.stdout |
63 | 63 |
64 | 64 |
65 def RunGit(args, **kwargs): | 65 def RunGit(args, **kwargs): |
66 """Returns stdout.""" | 66 """Returns stdout.""" |
67 return RunCommand(['git'] + args, **kwargs) | 67 return RunCommand(['git'] + args, **kwargs) |
68 | 68 |
69 | 69 |
70 def RunGitWithCode(args): | 70 def RunGitWithCode(args): |
71 """Returns return code and stdout.""" | 71 """Returns return code and stdout.""" |
72 out, code = subprocess2.communicate(['git'] + args, stdout=subprocess2.PIPE) | 72 try: |
73 return code, out[0] | 73 out, code = subprocess2.communicate(['git'] + args, stdout=subprocess2.PIPE) |
| 74 return code, out[0] |
| 75 except ValueError: |
| 76 # When the subprocess fails, it returns None. That triggers a ValueError |
| 77 # when trying to unpack the return value into (out, code). |
| 78 return 1, '' |
74 | 79 |
75 | 80 |
76 def usage(more): | 81 def usage(more): |
77 def hook(fn): | 82 def hook(fn): |
78 fn.usage_more = more | 83 fn.usage_more = more |
79 return fn | 84 return fn |
80 return hook | 85 return hook |
81 | 86 |
82 | 87 |
83 def ask_for_data(prompt): | 88 def ask_for_data(prompt): |
(...skipping 1044 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1128 if 'GIT_EXTERNAL_DIFF' in env: | 1133 if 'GIT_EXTERNAL_DIFF' in env: |
1129 del env['GIT_EXTERNAL_DIFF'] | 1134 del env['GIT_EXTERNAL_DIFF'] |
1130 subprocess2.call( | 1135 subprocess2.call( |
1131 ['git', 'diff', '--no-ext-diff', '--stat', '-M'] + args, env=env) | 1136 ['git', 'diff', '--no-ext-diff', '--stat', '-M'] + args, env=env) |
1132 | 1137 |
1133 if settings.GetIsGerrit(): | 1138 if settings.GetIsGerrit(): |
1134 return GerritUpload(options, args, cl) | 1139 return GerritUpload(options, args, cl) |
1135 return RietveldUpload(options, args, cl) | 1140 return RietveldUpload(options, args, cl) |
1136 | 1141 |
1137 | 1142 |
| 1143 def IsSubmoduleMergeCommit(ref): |
| 1144 # When submodules are added to the repo, we expect there to be a single |
| 1145 # non-git-svn merge commit at remote HEAD with a signature comment. |
| 1146 pattern = '^SVN changes up to revision [0-9]*$' |
| 1147 cmd = ['rev-list', '--merges', '--grep="%s"' % pattern, '%s^!' % ref] |
| 1148 return RunGit(cmd) != '' |
| 1149 |
| 1150 |
1138 def SendUpstream(parser, args, cmd): | 1151 def SendUpstream(parser, args, cmd): |
1139 """Common code for CmdPush and CmdDCommit | 1152 """Common code for CmdPush and CmdDCommit |
1140 | 1153 |
1141 Squashed commit into a single. | 1154 Squashed commit into a single. |
1142 Updates changelog with metadata (e.g. pointer to review). | 1155 Updates changelog with metadata (e.g. pointer to review). |
1143 Pushes/dcommits the code upstream. | 1156 Pushes/dcommits the code upstream. |
1144 Updates review and closes. | 1157 Updates review and closes. |
1145 """ | 1158 """ |
1146 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', | 1159 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', |
1147 help='bypass upload presubmit hook') | 1160 help='bypass upload presubmit hook') |
(...skipping 11 matching lines...) Expand all Loading... |
1159 if not args or cmd == 'push': | 1172 if not args or cmd == 'push': |
1160 # Default to merging against our best guess of the upstream branch. | 1173 # Default to merging against our best guess of the upstream branch. |
1161 args = [cl.GetUpstreamBranch()] | 1174 args = [cl.GetUpstreamBranch()] |
1162 | 1175 |
1163 if options.contributor: | 1176 if options.contributor: |
1164 if not re.match('^.*\s<\S+@\S+>$', options.contributor): | 1177 if not re.match('^.*\s<\S+@\S+>$', options.contributor): |
1165 print "Please provide contibutor as 'First Last <email@example.com>'" | 1178 print "Please provide contibutor as 'First Last <email@example.com>'" |
1166 return 1 | 1179 return 1 |
1167 | 1180 |
1168 base_branch = args[0] | 1181 base_branch = args[0] |
| 1182 base_has_submodules = IsSubmoduleMergeCommit(base_branch) |
1169 | 1183 |
1170 # Make sure index is up-to-date before running diff-index. | 1184 # Make sure index is up-to-date before running diff-index. |
1171 RunGit(['update-index', '--refresh', '-q'], error_ok=True) | 1185 RunGit(['update-index', '--refresh', '-q'], error_ok=True) |
1172 if RunGit(['diff-index', 'HEAD']): | 1186 if RunGit(['diff-index', 'HEAD']): |
1173 print 'Cannot %s with a dirty tree. You must commit locally first.' % cmd | 1187 print 'Cannot %s with a dirty tree. You must commit locally first.' % cmd |
1174 return 1 | 1188 return 1 |
1175 | 1189 |
1176 # This rev-list syntax means "show all commits not in my branch that | 1190 # This rev-list syntax means "show all commits not in my branch that |
1177 # are in base_branch". | 1191 # are in base_branch". |
1178 upstream_commits = RunGit(['rev-list', '^' + cl.GetBranchRef(), | 1192 upstream_commits = RunGit(['rev-list', '^' + cl.GetBranchRef(), |
1179 base_branch]).splitlines() | 1193 base_branch]).splitlines() |
1180 if upstream_commits: | 1194 if upstream_commits: |
1181 print ('Base branch "%s" has %d commits ' | 1195 print ('Base branch "%s" has %d commits ' |
1182 'not in this branch.' % (base_branch, len(upstream_commits))) | 1196 'not in this branch.' % (base_branch, len(upstream_commits))) |
1183 print 'Run "git merge %s" before attempting to %s.' % (base_branch, cmd) | 1197 print 'Run "git merge %s" before attempting to %s.' % (base_branch, cmd) |
1184 return 1 | 1198 return 1 |
1185 | 1199 |
| 1200 # This is the revision `svn dcommit` will commit on top of. |
| 1201 svn_head = RunGit(['log', '--grep=^git-svn-id:', '-1', |
| 1202 '--pretty=format:%H']) |
| 1203 |
1186 if cmd == 'dcommit': | 1204 if cmd == 'dcommit': |
1187 # This is the revision `svn dcommit` will commit on top of. | 1205 # If the base_head is a submodule merge commit, the first parent of the |
1188 svn_head = RunGit(['log', '--grep=^git-svn-id:', '-1', | 1206 # base_head should be a git-svn commit, which is what we're interested in. |
1189 '--pretty=format:%H']) | 1207 base_svn_head = base_branch |
1190 extra_commits = RunGit(['rev-list', '^' + svn_head, base_branch]) | 1208 if base_has_submodules: |
| 1209 base_svn_head += '^1' |
| 1210 |
| 1211 extra_commits = RunGit(['rev-list', '^' + svn_head, base_svn_head]) |
1191 if extra_commits: | 1212 if extra_commits: |
1192 print ('This branch has %d additional commits not upstreamed yet.' | 1213 print ('This branch has %d additional commits not upstreamed yet.' |
1193 % len(extra_commits.splitlines())) | 1214 % len(extra_commits.splitlines())) |
1194 print ('Upstream "%s" or rebase this branch on top of the upstream trunk ' | 1215 print ('Upstream "%s" or rebase this branch on top of the upstream trunk ' |
1195 'before attempting to %s.' % (base_branch, cmd)) | 1216 'before attempting to %s.' % (base_branch, cmd)) |
1196 return 1 | 1217 return 1 |
1197 | 1218 |
1198 if not options.bypass_hooks: | 1219 if not options.bypass_hooks: |
1199 author = None | 1220 author = None |
1200 if options.contributor: | 1221 if options.contributor: |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1239 | 1260 |
1240 if options.contributor: | 1261 if options.contributor: |
1241 description += "\nPatch from %s." % options.contributor | 1262 description += "\nPatch from %s." % options.contributor |
1242 print 'Description:', repr(description) | 1263 print 'Description:', repr(description) |
1243 | 1264 |
1244 branches = [base_branch, cl.GetBranchRef()] | 1265 branches = [base_branch, cl.GetBranchRef()] |
1245 if not options.force: | 1266 if not options.force: |
1246 subprocess2.call(['git', 'diff', '--stat'] + branches) | 1267 subprocess2.call(['git', 'diff', '--stat'] + branches) |
1247 ask_for_data('About to commit; enter to confirm.') | 1268 ask_for_data('About to commit; enter to confirm.') |
1248 | 1269 |
1249 # We want to squash all this branch's commits into one commit with the | 1270 # We want to squash all this branch's commits into one commit with the proper |
1250 # proper description. | 1271 # description. We do this by doing a "reset --soft" to the base branch (which |
1251 # We do this by doing a "reset --soft" to the base branch (which keeps | 1272 # keeps the working copy the same), then dcommitting that. If origin/master |
1252 # the working copy the same), then dcommitting that. | 1273 # has a submodule merge commit, we'll also need to cherry-pick the squashed |
| 1274 # commit onto a branch based on the git-svn head. |
1253 MERGE_BRANCH = 'git-cl-commit' | 1275 MERGE_BRANCH = 'git-cl-commit' |
1254 # Delete the merge branch if it already exists. | 1276 CHERRY_PICK_BRANCH = 'git-cl-cherry-pick' |
1255 if RunGitWithCode(['show-ref', '--quiet', '--verify', | 1277 # Delete the branches if they exist. |
1256 'refs/heads/' + MERGE_BRANCH])[0] == 0: | 1278 for branch in [MERGE_BRANCH, CHERRY_PICK_BRANCH]: |
1257 RunGit(['branch', '-D', MERGE_BRANCH]) | 1279 showref_cmd = ['show-ref', '--quiet', '--verify', 'refs/heads/%s' % branch] |
| 1280 result = RunGitWithCode(showref_cmd) |
| 1281 if result[0] == 0: |
| 1282 RunGit(['branch', '-D', branch]) |
1258 | 1283 |
1259 # We might be in a directory that's present in this branch but not in the | 1284 # We might be in a directory that's present in this branch but not in the |
1260 # trunk. Move up to the top of the tree so that git commands that expect a | 1285 # trunk. Move up to the top of the tree so that git commands that expect a |
1261 # valid CWD won't fail after we check out the merge branch. | 1286 # valid CWD won't fail after we check out the merge branch. |
1262 rel_base_path = RunGit(['rev-parse', '--show-cdup']).strip() | 1287 rel_base_path = RunGit(['rev-parse', '--show-cdup']).strip() |
1263 if rel_base_path: | 1288 if rel_base_path: |
1264 os.chdir(rel_base_path) | 1289 os.chdir(rel_base_path) |
1265 | 1290 |
1266 # Stuff our change into the merge branch. | 1291 # Stuff our change into the merge branch. |
1267 # We wrap in a try...finally block so if anything goes wrong, | 1292 # We wrap in a try...finally block so if anything goes wrong, |
1268 # we clean up the branches. | 1293 # we clean up the branches. |
1269 retcode = -1 | 1294 retcode = -1 |
1270 try: | 1295 try: |
1271 RunGit(['checkout', '-q', '-b', MERGE_BRANCH]) | 1296 RunGit(['checkout', '-q', '-b', MERGE_BRANCH]) |
1272 RunGit(['reset', '--soft', base_branch]) | 1297 RunGit(['reset', '--soft', base_branch]) |
1273 if options.contributor: | 1298 if options.contributor: |
1274 RunGit(['commit', '--author', options.contributor, '-m', description]) | 1299 RunGit(['commit', '--author', options.contributor, '-m', description]) |
1275 else: | 1300 else: |
1276 RunGit(['commit', '-m', description]) | 1301 RunGit(['commit', '-m', description]) |
| 1302 if base_has_submodules: |
| 1303 cherry_pick_commit = RunGit(['rev-list', 'HEAD^!']).rstrip() |
| 1304 RunGit(['branch', CHERRY_PICK_BRANCH, svn_head]) |
| 1305 RunGit(['checkout', CHERRY_PICK_BRANCH]) |
| 1306 RunGit(['cherry-pick', cherry_pick_commit]) |
1277 if cmd == 'push': | 1307 if cmd == 'push': |
1278 # push the merge branch. | 1308 # push the merge branch. |
1279 remote, branch = cl.FetchUpstreamTuple() | 1309 remote, branch = cl.FetchUpstreamTuple() |
1280 retcode, output = RunGitWithCode( | 1310 retcode, output = RunGitWithCode( |
1281 ['push', '--porcelain', remote, 'HEAD:%s' % branch]) | 1311 ['push', '--porcelain', remote, 'HEAD:%s' % branch]) |
1282 logging.debug(output) | 1312 logging.debug(output) |
1283 else: | 1313 else: |
1284 # dcommit the merge branch. | 1314 # dcommit the merge branch. |
1285 retcode, output = RunGitWithCode(['svn', 'dcommit', | 1315 retcode, output = RunGitWithCode(['svn', 'dcommit', |
1286 '--no-rebase', '--rmdir']) | 1316 '--no-rebase', '--rmdir']) |
1287 finally: | 1317 finally: |
1288 # And then swap back to the original branch and clean up. | 1318 # And then swap back to the original branch and clean up. |
1289 RunGit(['checkout', '-q', cl.GetBranch()]) | 1319 RunGit(['checkout', '-q', cl.GetBranch()]) |
1290 RunGit(['branch', '-D', MERGE_BRANCH]) | 1320 RunGit(['branch', '-D', MERGE_BRANCH]) |
| 1321 if base_has_submodules: |
| 1322 RunGit(['branch', '-D', CHERRY_PICK_BRANCH]) |
1291 | 1323 |
1292 if cl.GetIssue(): | 1324 if cl.GetIssue(): |
1293 if cmd == 'dcommit' and 'Committed r' in output: | 1325 if cmd == 'dcommit' and 'Committed r' in output: |
1294 revision = re.match('.*?\nCommitted r(\\d+)', output, re.DOTALL).group(1) | 1326 revision = re.match('.*?\nCommitted r(\\d+)', output, re.DOTALL).group(1) |
1295 elif cmd == 'push' and retcode == 0: | 1327 elif cmd == 'push' and retcode == 0: |
1296 match = (re.match(r'.*?([a-f0-9]{7})\.\.([a-f0-9]{7})$', l) | 1328 match = (re.match(r'.*?([a-f0-9]{7})\.\.([a-f0-9]{7})$', l) |
1297 for l in output.splitlines(False)) | 1329 for l in output.splitlines(False)) |
1298 match = filter(None, match) | 1330 match = filter(None, match) |
1299 if len(match) != 1: | 1331 if len(match) != 1: |
1300 DieWithError("Couldn't parse ouput to extract the committed hash:\n%s" % | 1332 DieWithError("Couldn't parse ouput to extract the committed hash:\n%s" % |
(...skipping 266 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1567 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) | 1599 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) |
1568 | 1600 |
1569 # Not a known command. Default to help. | 1601 # Not a known command. Default to help. |
1570 GenUsage(parser, 'help') | 1602 GenUsage(parser, 'help') |
1571 return CMDhelp(parser, argv) | 1603 return CMDhelp(parser, argv) |
1572 | 1604 |
1573 | 1605 |
1574 if __name__ == '__main__': | 1606 if __name__ == '__main__': |
1575 fix_encoding.fix_encoding() | 1607 fix_encoding.fix_encoding() |
1576 sys.exit(main(sys.argv[1:])) | 1608 sys.exit(main(sys.argv[1:])) |
OLD | NEW |