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 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
85 | 85 |
86 | 86 |
87 def ask_for_data(prompt): | 87 def ask_for_data(prompt): |
88 try: | 88 try: |
89 return raw_input(prompt) | 89 return raw_input(prompt) |
90 except KeyboardInterrupt: | 90 except KeyboardInterrupt: |
91 # Hide the exception. | 91 # Hide the exception. |
92 sys.exit(1) | 92 sys.exit(1) |
93 | 93 |
94 | 94 |
| 95 def add_git_similarity(parser): |
| 96 parser.add_option( |
| 97 '--similarity', metavar='SIM', type='int', action='store', default=None, |
| 98 help='Sets the percentage that a pair of files need to match in order to' |
| 99 ' be considered copies (default 50)') |
| 100 |
| 101 old_parser_args = parser.parse_args |
| 102 def Parse(args): |
| 103 options, args = old_parser_args(args) |
| 104 |
| 105 branch = Changelist().GetBranch() |
| 106 key = 'branch.%s.git-cl-similarity' % branch |
| 107 if options.similarity is None: |
| 108 if branch: |
| 109 (_, stdout) = RunGitWithCode(['config', '--int', '--get', key]) |
| 110 try: |
| 111 options.similarity = int(stdout.strip()) |
| 112 except ValueError: |
| 113 pass |
| 114 options.similarity = options.similarity or 50 |
| 115 else: |
| 116 if branch: |
| 117 print('Note: Saving similarity of %d%% in git config.' |
| 118 % options.similarity) |
| 119 RunGit(['config', '--int', key, str(options.similarity)]) |
| 120 |
| 121 options.similarity = max(1, min(options.similarity, 100)) |
| 122 |
| 123 print('Using %d%% similarity for rename/copy detection. ' |
| 124 'Override with --similarity.' % options.similarity) |
| 125 |
| 126 return options, args |
| 127 parser.parse_args = Parse |
| 128 |
| 129 |
95 def MatchSvnGlob(url, base_url, glob_spec, allow_wildcards): | 130 def MatchSvnGlob(url, base_url, glob_spec, allow_wildcards): |
96 """Return the corresponding git ref if |base_url| together with |glob_spec| | 131 """Return the corresponding git ref if |base_url| together with |glob_spec| |
97 matches the full |url|. | 132 matches the full |url|. |
98 | 133 |
99 If |allow_wildcards| is true, |glob_spec| can contain wildcards (see below). | 134 If |allow_wildcards| is true, |glob_spec| can contain wildcards (see below). |
100 """ | 135 """ |
101 fetch_suburl, as_ref = glob_spec.split(':') | 136 fetch_suburl, as_ref = glob_spec.split(':') |
102 if allow_wildcards: | 137 if allow_wildcards: |
103 glob_match = re.match('(.+/)?(\*|{[^/]*})(/.+)?', fetch_suburl) | 138 glob_match = re.match('(.+/)?(\*|{[^/]*})(/.+)?', fetch_suburl) |
104 if glob_match: | 139 if glob_match: |
(...skipping 22 matching lines...) Expand all Loading... |
127 # Parse specs like "trunk/src:refs/remotes/origin/trunk". | 162 # Parse specs like "trunk/src:refs/remotes/origin/trunk". |
128 if fetch_suburl: | 163 if fetch_suburl: |
129 full_url = base_url + '/' + fetch_suburl | 164 full_url = base_url + '/' + fetch_suburl |
130 else: | 165 else: |
131 full_url = base_url | 166 full_url = base_url |
132 if full_url == url: | 167 if full_url == url: |
133 return as_ref | 168 return as_ref |
134 return None | 169 return None |
135 | 170 |
136 | 171 |
137 def print_stats(args): | 172 def print_stats(similarity, args): |
138 """Prints statistics about the change to the user.""" | 173 """Prints statistics about the change to the user.""" |
139 # --no-ext-diff is broken in some versions of Git, so try to work around | 174 # --no-ext-diff is broken in some versions of Git, so try to work around |
140 # this by overriding the environment (but there is still a problem if the | 175 # this by overriding the environment (but there is still a problem if the |
141 # git config key "diff.external" is used). | 176 # git config key "diff.external" is used). |
142 env = os.environ.copy() | 177 env = os.environ.copy() |
143 if 'GIT_EXTERNAL_DIFF' in env: | 178 if 'GIT_EXTERNAL_DIFF' in env: |
144 del env['GIT_EXTERNAL_DIFF'] | 179 del env['GIT_EXTERNAL_DIFF'] |
145 return subprocess2.call( | 180 return subprocess2.call( |
146 ['git', 'diff', '--no-ext-diff', '--stat', '--find-copies-harder', | 181 ['git', 'diff', '--no-ext-diff', '--stat', '--find-copies-harder', |
147 '-l100000'] + args, env=env) | 182 '-C%s' % similarity, '-l100000'] + args, env=env) |
148 | 183 |
149 | 184 |
150 class Settings(object): | 185 class Settings(object): |
151 def __init__(self): | 186 def __init__(self): |
152 self.default_server = None | 187 self.default_server = None |
153 self.cc = None | 188 self.cc = None |
154 self.root = None | 189 self.root = None |
155 self.is_git_svn = None | 190 self.is_git_svn = None |
156 self.svn_branch = None | 191 self.svn_branch = None |
157 self.tree_status_url = None | 192 self.tree_status_url = None |
(...skipping 872 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1030 if change_desc.reviewers: | 1065 if change_desc.reviewers: |
1031 upload_args.extend(['--reviewers', change_desc.reviewers]) | 1066 upload_args.extend(['--reviewers', change_desc.reviewers]) |
1032 if options.send_mail: | 1067 if options.send_mail: |
1033 if not change_desc.reviewers: | 1068 if not change_desc.reviewers: |
1034 DieWithError("Must specify reviewers to send email.") | 1069 DieWithError("Must specify reviewers to send email.") |
1035 upload_args.append('--send_mail') | 1070 upload_args.append('--send_mail') |
1036 cc = ','.join(filter(None, (cl.GetCCList(), options.cc))) | 1071 cc = ','.join(filter(None, (cl.GetCCList(), options.cc))) |
1037 if cc: | 1072 if cc: |
1038 upload_args.extend(['--cc', cc]) | 1073 upload_args.extend(['--cc', cc]) |
1039 | 1074 |
| 1075 upload_args.extend(['--git_similarity', str(options.similarity)]) |
| 1076 |
1040 # Include the upstream repo's URL in the change -- this is useful for | 1077 # Include the upstream repo's URL in the change -- this is useful for |
1041 # projects that have their source spread across multiple repos. | 1078 # projects that have their source spread across multiple repos. |
1042 remote_url = cl.GetGitBaseUrlFromConfig() | 1079 remote_url = cl.GetGitBaseUrlFromConfig() |
1043 if not remote_url: | 1080 if not remote_url: |
1044 if settings.GetIsGitSvn(): | 1081 if settings.GetIsGitSvn(): |
1045 # URL is dependent on the current directory. | 1082 # URL is dependent on the current directory. |
1046 data = RunGit(['svn', 'info'], cwd=settings.GetRoot()) | 1083 data = RunGit(['svn', 'info'], cwd=settings.GetRoot()) |
1047 if data: | 1084 if data: |
1048 keys = dict(line.split(': ', 1) for line in data.splitlines() | 1085 keys = dict(line.split(': ', 1) for line in data.splitlines() |
1049 if ': ' in line) | 1086 if ': ' in line) |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1096 parser.add_option('--send-mail', action='store_true', | 1133 parser.add_option('--send-mail', action='store_true', |
1097 help='send email to reviewer immediately') | 1134 help='send email to reviewer immediately') |
1098 parser.add_option("--emulate_svn_auto_props", action="store_true", | 1135 parser.add_option("--emulate_svn_auto_props", action="store_true", |
1099 dest="emulate_svn_auto_props", | 1136 dest="emulate_svn_auto_props", |
1100 help="Emulate Subversion's auto properties feature.") | 1137 help="Emulate Subversion's auto properties feature.") |
1101 parser.add_option('-c', '--use-commit-queue', action='store_true', | 1138 parser.add_option('-c', '--use-commit-queue', action='store_true', |
1102 help='tell the commit queue to commit this patchset') | 1139 help='tell the commit queue to commit this patchset') |
1103 if settings.GetIsGerrit(): | 1140 if settings.GetIsGerrit(): |
1104 parser.add_option('--target_branch', dest='target_branch', default='master', | 1141 parser.add_option('--target_branch', dest='target_branch', default='master', |
1105 help='target branch to upload') | 1142 help='target branch to upload') |
| 1143 add_git_similarity(parser) |
1106 (options, args) = parser.parse_args(args) | 1144 (options, args) = parser.parse_args(args) |
1107 | 1145 |
1108 # Print warning if the user used the -m/--message argument. This will soon | 1146 # Print warning if the user used the -m/--message argument. This will soon |
1109 # change to -t/--title. | 1147 # change to -t/--title. |
1110 if options.message: | 1148 if options.message: |
1111 print >> sys.stderr, ( | 1149 print >> sys.stderr, ( |
1112 '\nWARNING: Use -t or --title to set the title of the patchset.\n' | 1150 '\nWARNING: Use -t or --title to set the title of the patchset.\n' |
1113 'In the near future, -m or --message will send a message instead.\n' | 1151 'In the near future, -m or --message will send a message instead.\n' |
1114 'See http://goo.gl/JGg0Z for details.\n') | 1152 'See http://goo.gl/JGg0Z for details.\n') |
1115 | 1153 |
(...skipping 15 matching lines...) Expand all Loading... |
1131 if not options.bypass_hooks: | 1169 if not options.bypass_hooks: |
1132 hook_results = cl.RunHook(committing=False, upstream_branch=base_branch, | 1170 hook_results = cl.RunHook(committing=False, upstream_branch=base_branch, |
1133 may_prompt=not options.force, | 1171 may_prompt=not options.force, |
1134 verbose=options.verbose, | 1172 verbose=options.verbose, |
1135 author=None) | 1173 author=None) |
1136 if not hook_results.should_continue(): | 1174 if not hook_results.should_continue(): |
1137 return 1 | 1175 return 1 |
1138 if not options.reviewers and hook_results.reviewers: | 1176 if not options.reviewers and hook_results.reviewers: |
1139 options.reviewers = hook_results.reviewers | 1177 options.reviewers = hook_results.reviewers |
1140 | 1178 |
1141 print_stats(args) | 1179 print_stats(options.similarity, args) |
1142 if settings.GetIsGerrit(): | 1180 if settings.GetIsGerrit(): |
1143 return GerritUpload(options, args, cl) | 1181 return GerritUpload(options, args, cl) |
1144 return RietveldUpload(options, args, cl) | 1182 return RietveldUpload(options, args, cl) |
1145 | 1183 |
1146 | 1184 |
1147 def IsSubmoduleMergeCommit(ref): | 1185 def IsSubmoduleMergeCommit(ref): |
1148 # When submodules are added to the repo, we expect there to be a single | 1186 # When submodules are added to the repo, we expect there to be a single |
1149 # non-git-svn merge commit at remote HEAD with a signature comment. | 1187 # non-git-svn merge commit at remote HEAD with a signature comment. |
1150 pattern = '^SVN changes up to revision [0-9]*$' | 1188 pattern = '^SVN changes up to revision [0-9]*$' |
1151 cmd = ['rev-list', '--merges', '--grep=%s' % pattern, '%s^!' % ref] | 1189 cmd = ['rev-list', '--merges', '--grep=%s' % pattern, '%s^!' % ref] |
(...skipping 11 matching lines...) Expand all Loading... |
1163 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', | 1201 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', |
1164 help='bypass upload presubmit hook') | 1202 help='bypass upload presubmit hook') |
1165 parser.add_option('-m', dest='message', | 1203 parser.add_option('-m', dest='message', |
1166 help="override review description") | 1204 help="override review description") |
1167 parser.add_option('-f', action='store_true', dest='force', | 1205 parser.add_option('-f', action='store_true', dest='force', |
1168 help="force yes to questions (don't prompt)") | 1206 help="force yes to questions (don't prompt)") |
1169 parser.add_option('-c', dest='contributor', | 1207 parser.add_option('-c', dest='contributor', |
1170 help="external contributor for patch (appended to " + | 1208 help="external contributor for patch (appended to " + |
1171 "description and used as author for git). Should be " + | 1209 "description and used as author for git). Should be " + |
1172 "formatted as 'First Last <email@example.com>'") | 1210 "formatted as 'First Last <email@example.com>'") |
| 1211 add_git_similarity(parser) |
1173 (options, args) = parser.parse_args(args) | 1212 (options, args) = parser.parse_args(args) |
1174 cl = Changelist() | 1213 cl = Changelist() |
1175 | 1214 |
1176 if not args or cmd == 'push': | 1215 if not args or cmd == 'push': |
1177 # Default to merging against our best guess of the upstream branch. | 1216 # Default to merging against our best guess of the upstream branch. |
1178 args = [cl.GetUpstreamBranch()] | 1217 args = [cl.GetUpstreamBranch()] |
1179 | 1218 |
1180 if options.contributor: | 1219 if options.contributor: |
1181 if not re.match('^.*\s<\S+@\S+>$', options.contributor): | 1220 if not re.match('^.*\s<\S+@\S+>$', options.contributor): |
1182 print "Please provide contibutor as 'First Last <email@example.com>'" | 1221 print "Please provide contibutor as 'First Last <email@example.com>'" |
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1264 | 1303 |
1265 if cl.GetIssue(): | 1304 if cl.GetIssue(): |
1266 description += "\n\nReview URL: %s" % cl.GetIssueURL() | 1305 description += "\n\nReview URL: %s" % cl.GetIssueURL() |
1267 | 1306 |
1268 if options.contributor: | 1307 if options.contributor: |
1269 description += "\nPatch from %s." % options.contributor | 1308 description += "\nPatch from %s." % options.contributor |
1270 print 'Description:', repr(description) | 1309 print 'Description:', repr(description) |
1271 | 1310 |
1272 branches = [base_branch, cl.GetBranchRef()] | 1311 branches = [base_branch, cl.GetBranchRef()] |
1273 if not options.force: | 1312 if not options.force: |
1274 print_stats(branches) | 1313 print_stats(options.similarity, branches) |
1275 ask_for_data('About to commit; enter to confirm.') | 1314 ask_for_data('About to commit; enter to confirm.') |
1276 | 1315 |
1277 # We want to squash all this branch's commits into one commit with the proper | 1316 # We want to squash all this branch's commits into one commit with the proper |
1278 # description. We do this by doing a "reset --soft" to the base branch (which | 1317 # description. We do this by doing a "reset --soft" to the base branch (which |
1279 # keeps the working copy the same), then dcommitting that. If origin/master | 1318 # keeps the working copy the same), then dcommitting that. If origin/master |
1280 # has a submodule merge commit, we'll also need to cherry-pick the squashed | 1319 # has a submodule merge commit, we'll also need to cherry-pick the squashed |
1281 # commit onto a branch based on the git-svn head. | 1320 # commit onto a branch based on the git-svn head. |
1282 MERGE_BRANCH = 'git-cl-commit' | 1321 MERGE_BRANCH = 'git-cl-commit' |
1283 CHERRY_PICK_BRANCH = 'git-cl-cherry-pick' | 1322 CHERRY_PICK_BRANCH = 'git-cl-cherry-pick' |
1284 # Delete the branches if they exist. | 1323 # Delete the branches if they exist. |
(...skipping 28 matching lines...) Expand all Loading... |
1313 RunGit(['cherry-pick', cherry_pick_commit]) | 1352 RunGit(['cherry-pick', cherry_pick_commit]) |
1314 if cmd == 'push': | 1353 if cmd == 'push': |
1315 # push the merge branch. | 1354 # push the merge branch. |
1316 remote, branch = cl.FetchUpstreamTuple() | 1355 remote, branch = cl.FetchUpstreamTuple() |
1317 retcode, output = RunGitWithCode( | 1356 retcode, output = RunGitWithCode( |
1318 ['push', '--porcelain', remote, 'HEAD:%s' % branch]) | 1357 ['push', '--porcelain', remote, 'HEAD:%s' % branch]) |
1319 logging.debug(output) | 1358 logging.debug(output) |
1320 else: | 1359 else: |
1321 # dcommit the merge branch. | 1360 # dcommit the merge branch. |
1322 retcode, output = RunGitWithCode(['svn', 'dcommit', | 1361 retcode, output = RunGitWithCode(['svn', 'dcommit', |
| 1362 '-C%s' % options.similarity, |
1323 '--no-rebase', '--rmdir']) | 1363 '--no-rebase', '--rmdir']) |
1324 finally: | 1364 finally: |
1325 # And then swap back to the original branch and clean up. | 1365 # And then swap back to the original branch and clean up. |
1326 RunGit(['checkout', '-q', cl.GetBranch()]) | 1366 RunGit(['checkout', '-q', cl.GetBranch()]) |
1327 RunGit(['branch', '-D', MERGE_BRANCH]) | 1367 RunGit(['branch', '-D', MERGE_BRANCH]) |
1328 if base_has_submodules: | 1368 if base_has_submodules: |
1329 RunGit(['branch', '-D', CHERRY_PICK_BRANCH]) | 1369 RunGit(['branch', '-D', CHERRY_PICK_BRANCH]) |
1330 | 1370 |
1331 if cl.GetIssue(): | 1371 if cl.GetIssue(): |
1332 if cmd == 'dcommit' and 'Committed r' in output: | 1372 if cmd == 'dcommit' and 'Committed r' in output: |
(...skipping 383 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1716 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) | 1756 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) |
1717 | 1757 |
1718 # Not a known command. Default to help. | 1758 # Not a known command. Default to help. |
1719 GenUsage(parser, 'help') | 1759 GenUsage(parser, 'help') |
1720 return CMDhelp(parser, argv) | 1760 return CMDhelp(parser, argv) |
1721 | 1761 |
1722 | 1762 |
1723 if __name__ == '__main__': | 1763 if __name__ == '__main__': |
1724 fix_encoding.fix_encoding() | 1764 fix_encoding.fix_encoding() |
1725 sys.exit(main(sys.argv[1:])) | 1765 sys.exit(main(sys.argv[1:])) |
OLD | NEW |