libv8 5.3.332.38.5 → 5.6.326.50.0beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (352) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -1
  3. data/CHANGELOG.md +2 -0
  4. data/ext/libv8/builder.rb +2 -2
  5. data/lib/libv8/version.rb +1 -1
  6. data/patches/0001-Build-a-standalone-static-library.patch +4 -4
  7. data/patches/0002-Don-t-compile-unnecessary-stuff.patch +16 -11
  8. data/patches/0003-Use-the-fPIC-flag-for-the-static-library.patch +4 -4
  9. data/patches/{0005-Do-not-embed-debug-symbols-in-macOS-libraries.patch → 0004-Do-not-embed-debug-symbols-in-macOS-libraries.patch} +4 -4
  10. data/vendor/depot_tools/.gitignore +4 -0
  11. data/vendor/depot_tools/OWNERS +0 -2
  12. data/vendor/depot_tools/PRESUBMIT.py +20 -23
  13. data/vendor/depot_tools/README.gclient.md +3 -3
  14. data/vendor/depot_tools/README.git-cl.md +13 -12
  15. data/vendor/depot_tools/README.md +2 -3
  16. data/vendor/depot_tools/WATCHLISTS +0 -1
  17. data/vendor/depot_tools/appengine_mapper.py +23 -0
  18. data/vendor/depot_tools/apply_issue.py +2 -8
  19. data/vendor/depot_tools/bootstrap/win/README.md +1 -8
  20. data/vendor/depot_tools/bootstrap/win/git_bootstrap.py +6 -16
  21. data/vendor/depot_tools/bootstrap/win/git_version.txt +1 -1
  22. data/vendor/depot_tools/bootstrap/win/git_version_bleeding_edge.txt +1 -1
  23. data/vendor/depot_tools/checkout.py +20 -433
  24. data/vendor/depot_tools/cipd +73 -0
  25. data/vendor/depot_tools/cipd.bat +12 -0
  26. data/vendor/depot_tools/cipd.ps1 +57 -0
  27. data/vendor/depot_tools/cipd_client_version +1 -0
  28. data/vendor/depot_tools/clang_format.py +9 -6
  29. data/vendor/depot_tools/clang_format_merge_driver +8 -0
  30. data/vendor/depot_tools/clang_format_merge_driver.bat +11 -0
  31. data/vendor/depot_tools/clang_format_merge_driver.py +67 -0
  32. data/vendor/depot_tools/codereview.settings +3 -2
  33. data/vendor/depot_tools/commit_queue.py +1 -1
  34. data/vendor/depot_tools/cpplint.py +2 -0
  35. data/vendor/depot_tools/fetch.py +1 -54
  36. data/vendor/depot_tools/fetch_configs/android.py +2 -2
  37. data/vendor/depot_tools/fetch_configs/breakpad.py +2 -3
  38. data/vendor/depot_tools/fetch_configs/chromium.py +2 -3
  39. data/vendor/depot_tools/fetch_configs/crashpad.py +2 -2
  40. data/vendor/depot_tools/fetch_configs/dart.py +2 -3
  41. data/vendor/depot_tools/fetch_configs/dartino.py +2 -3
  42. data/vendor/depot_tools/fetch_configs/dartium.py +2 -3
  43. data/vendor/depot_tools/fetch_configs/depot_tools.py +3 -6
  44. data/vendor/depot_tools/fetch_configs/gyp.py +2 -3
  45. data/vendor/depot_tools/fetch_configs/infra.py +2 -2
  46. data/vendor/depot_tools/fetch_configs/infra_internal.py +2 -2
  47. data/vendor/depot_tools/fetch_configs/ios.py +2 -2
  48. data/vendor/depot_tools/fetch_configs/ios_internal.py +2 -3
  49. data/vendor/depot_tools/fetch_configs/mojo.py +2 -3
  50. data/vendor/depot_tools/fetch_configs/nacl.py +2 -3
  51. data/vendor/depot_tools/fetch_configs/naclports.py +2 -3
  52. data/vendor/depot_tools/fetch_configs/pdfium.py +2 -2
  53. data/vendor/depot_tools/fetch_configs/skia.py +2 -2
  54. data/vendor/depot_tools/fetch_configs/skia_buildbot.py +2 -2
  55. data/vendor/depot_tools/fetch_configs/syzygy.py +2 -2
  56. data/vendor/depot_tools/fetch_configs/v8.py +2 -3
  57. data/vendor/depot_tools/fetch_configs/webrtc.py +5 -3
  58. data/vendor/depot_tools/fetch_configs/webrtc_android.py +2 -2
  59. data/vendor/depot_tools/fetch_configs/webrtc_ios.py +2 -2
  60. data/vendor/depot_tools/fix_encoding.py +6 -6
  61. data/vendor/depot_tools/gclient.py +136 -368
  62. data/vendor/depot_tools/gclient_scm.py +108 -647
  63. data/vendor/depot_tools/gclient_utils.py +22 -86
  64. data/vendor/depot_tools/gerrit_client.py +105 -0
  65. data/vendor/depot_tools/gerrit_util.py +174 -67
  66. data/vendor/depot_tools/git-crrev-parse +6 -7
  67. data/vendor/depot_tools/git-gs +1 -1
  68. data/vendor/depot_tools/git_cache.py +68 -18
  69. data/vendor/depot_tools/git_cherry_pick_upload.py +4 -4
  70. data/vendor/depot_tools/git_cl.py +1028 -961
  71. data/vendor/depot_tools/git_common.py +2 -3
  72. data/vendor/depot_tools/git_drover.py +0 -1
  73. data/vendor/depot_tools/git_footers.py +3 -43
  74. data/vendor/depot_tools/git_rebase_update.py +9 -1
  75. data/vendor/depot_tools/git_squash_branch.py +1 -1
  76. data/vendor/depot_tools/infra/config/cq.cfg +8 -1
  77. data/vendor/depot_tools/infra/config/recipes.cfg +1 -1
  78. data/vendor/depot_tools/man/html/depot_tools.html +3 -11
  79. data/vendor/depot_tools/man/html/depot_tools_tutorial.html +9 -9
  80. data/vendor/depot_tools/man/html/git-cherry-pick-upload.html +2 -2
  81. data/vendor/depot_tools/man/html/git-drover.html +17 -17
  82. data/vendor/depot_tools/man/html/git-footers.html +2 -2
  83. data/vendor/depot_tools/man/html/git-freeze.html +4 -4
  84. data/vendor/depot_tools/man/html/git-hyper-blame.html +2 -2
  85. data/vendor/depot_tools/man/html/git-map-branches.html +2 -2
  86. data/vendor/depot_tools/man/html/git-map.html +2 -2
  87. data/vendor/depot_tools/man/html/git-mark-merge-base.html +2 -2
  88. data/vendor/depot_tools/man/html/git-nav-downstream.html +2 -2
  89. data/vendor/depot_tools/man/html/git-nav-upstream.html +2 -2
  90. data/vendor/depot_tools/man/html/git-new-branch.html +2 -2
  91. data/vendor/depot_tools/man/html/git-rebase-update.html +2 -2
  92. data/vendor/depot_tools/man/html/git-rename-branch.html +2 -2
  93. data/vendor/depot_tools/man/html/git-reparent-branch.html +2 -2
  94. data/vendor/depot_tools/man/html/git-retry.html +3 -3
  95. data/vendor/depot_tools/man/html/git-squash-branch.html +3 -3
  96. data/vendor/depot_tools/man/html/git-thaw.html +2 -2
  97. data/vendor/depot_tools/man/html/git-upstream-diff.html +3 -3
  98. data/vendor/depot_tools/man/man1/git-cherry-pick-upload.1 +4 -4
  99. data/vendor/depot_tools/man/man1/git-drover.1 +19 -19
  100. data/vendor/depot_tools/man/man1/git-footers.1 +4 -4
  101. data/vendor/depot_tools/man/man1/git-freeze.1 +6 -6
  102. data/vendor/depot_tools/man/man1/git-hyper-blame.1 +4 -4
  103. data/vendor/depot_tools/man/man1/git-map-branches.1 +4 -4
  104. data/vendor/depot_tools/man/man1/git-map.1 +4 -4
  105. data/vendor/depot_tools/man/man1/git-mark-merge-base.1 +4 -4
  106. data/vendor/depot_tools/man/man1/git-nav-downstream.1 +4 -4
  107. data/vendor/depot_tools/man/man1/git-nav-upstream.1 +4 -4
  108. data/vendor/depot_tools/man/man1/git-new-branch.1 +4 -4
  109. data/vendor/depot_tools/man/man1/git-rebase-update.1 +4 -4
  110. data/vendor/depot_tools/man/man1/git-rename-branch.1 +4 -4
  111. data/vendor/depot_tools/man/man1/git-reparent-branch.1 +4 -4
  112. data/vendor/depot_tools/man/man1/git-retry.1 +5 -5
  113. data/vendor/depot_tools/man/man1/git-squash-branch.1 +5 -5
  114. data/vendor/depot_tools/man/man1/git-thaw.1 +4 -4
  115. data/vendor/depot_tools/man/man1/git-upstream-diff.1 +5 -5
  116. data/vendor/depot_tools/man/man7/depot_tools.7 +5 -10
  117. data/vendor/depot_tools/man/man7/depot_tools_tutorial.7 +4 -4
  118. data/vendor/depot_tools/man/src/depot_tools.txt +1 -1
  119. data/vendor/depot_tools/man/src/depot_tools_tutorial.txt +7 -7
  120. data/vendor/depot_tools/man/src/filter_demo_output.py +2 -2
  121. data/vendor/depot_tools/man/src/git-footers.demo.1.sh +1 -1
  122. data/vendor/depot_tools/man/src/git-retry.txt +1 -1
  123. data/vendor/depot_tools/man/src/git-squash-branch.txt +2 -2
  124. data/vendor/depot_tools/man/src/git-upstream-diff.txt +1 -1
  125. data/vendor/depot_tools/my_activity.py +6 -3
  126. data/vendor/depot_tools/my_reviews.py +1 -1
  127. data/vendor/depot_tools/ninja +2 -2
  128. data/vendor/depot_tools/ninja-linux32 +0 -0
  129. data/vendor/depot_tools/ninja-linux64 +0 -0
  130. data/vendor/depot_tools/ninja-mac +0 -0
  131. data/vendor/depot_tools/ninja.exe +0 -0
  132. data/vendor/depot_tools/owners.py +14 -3
  133. data/vendor/depot_tools/presubmit_canned_checks.py +46 -67
  134. data/vendor/depot_tools/presubmit_support.py +109 -371
  135. data/vendor/depot_tools/pylintrc +83 -56
  136. data/vendor/depot_tools/recipe_modules/OWNERS +1 -0
  137. data/vendor/depot_tools/recipe_modules/bot_update/__init__.py +18 -9
  138. data/vendor/depot_tools/recipe_modules/bot_update/api.py +56 -55
  139. data/vendor/depot_tools/recipe_modules/bot_update/example.expected/basic.json +3 -7
  140. data/vendor/depot_tools/recipe_modules/bot_update/example.expected/basic_output_manifest.json +3 -7
  141. data/vendor/depot_tools/recipe_modules/bot_update/example.expected/basic_with_branch_heads.json +3 -7
  142. data/vendor/depot_tools/recipe_modules/bot_update/example.expected/buildbot.json +52 -0
  143. data/vendor/depot_tools/recipe_modules/bot_update/example.expected/clobber.json +19 -10
  144. data/vendor/depot_tools/recipe_modules/bot_update/example.expected/gerrit_no_rebase_patch_ref.json +19 -10
  145. data/vendor/depot_tools/recipe_modules/bot_update/example.expected/gerrit_no_reset.json +19 -10
  146. data/vendor/depot_tools/recipe_modules/bot_update/example.expected/no_shallow.json +19 -10
  147. data/vendor/depot_tools/recipe_modules/bot_update/example.expected/reset_root_solution_revision.json +19 -10
  148. data/vendor/depot_tools/recipe_modules/bot_update/example.expected/trychange.json +3 -7
  149. data/vendor/depot_tools/recipe_modules/bot_update/example.expected/trychange_oauth2.json +2 -54
  150. data/vendor/depot_tools/recipe_modules/bot_update/example.expected/trychange_oauth2_buildbot.json +56 -0
  151. data/vendor/depot_tools/recipe_modules/bot_update/example.expected/{forced.json → trychange_oauth2_json.json} +6 -9
  152. data/vendor/depot_tools/recipe_modules/bot_update/example.expected/trychange_oauth2_json_win.json +54 -0
  153. data/vendor/depot_tools/recipe_modules/bot_update/example.expected/tryjob.json +9 -9
  154. data/vendor/depot_tools/recipe_modules/bot_update/example.expected/tryjob_fail.json +9 -9
  155. data/vendor/depot_tools/recipe_modules/bot_update/example.expected/tryjob_fail_patch.json +9 -9
  156. data/vendor/depot_tools/recipe_modules/bot_update/example.expected/tryjob_fail_patch_download.json +9 -9
  157. data/vendor/depot_tools/recipe_modules/bot_update/example.expected/tryjob_gerrit_angle.json +20 -10
  158. data/vendor/depot_tools/recipe_modules/bot_update/example.expected/tryjob_gerrit_angle_deprecated.json +59 -0
  159. data/vendor/depot_tools/recipe_modules/bot_update/example.expected/tryjob_v8.json +9 -9
  160. data/vendor/depot_tools/recipe_modules/bot_update/example.expected/tryjob_v8_head_by_default.json +20 -10
  161. data/vendor/depot_tools/recipe_modules/bot_update/example.py +45 -63
  162. data/vendor/depot_tools/recipe_modules/bot_update/resources/bot_update.py +210 -807
  163. data/vendor/depot_tools/recipe_modules/bot_update/test_api.py +34 -45
  164. data/vendor/depot_tools/recipe_modules/cipd/api.py +59 -84
  165. data/vendor/depot_tools/recipe_modules/cipd/example.expected/basic.json +71 -117
  166. data/vendor/depot_tools/recipe_modules/cipd/example.expected/describe-failed.json +14 -60
  167. data/vendor/depot_tools/recipe_modules/cipd/example.expected/describe-many-instances.json +71 -117
  168. data/vendor/depot_tools/recipe_modules/cipd/example.expected/mac64.json +71 -117
  169. data/vendor/depot_tools/recipe_modules/cipd/example.expected/win64.json +71 -117
  170. data/vendor/depot_tools/recipe_modules/cipd/example.py +2 -12
  171. data/vendor/depot_tools/recipe_modules/cipd/test_api.py +0 -9
  172. data/vendor/depot_tools/recipe_modules/depot_tools/api.py +6 -0
  173. data/vendor/depot_tools/recipe_modules/depot_tools/example.expected/basic.json +7 -0
  174. data/vendor/depot_tools/recipe_modules/depot_tools/example.expected/win.json +7 -0
  175. data/vendor/depot_tools/recipe_modules/depot_tools/example.py +3 -0
  176. data/vendor/depot_tools/recipe_modules/gclient/__init__.py +4 -0
  177. data/vendor/depot_tools/recipe_modules/gclient/api.py +9 -22
  178. data/vendor/depot_tools/recipe_modules/gclient/config.py +18 -5
  179. data/vendor/depot_tools/recipe_modules/gclient/example.expected/basic.json +14 -14
  180. data/vendor/depot_tools/recipe_modules/gclient/example.expected/buildbot.json +211 -0
  181. data/vendor/depot_tools/recipe_modules/gclient/example.expected/revision.json +16 -14
  182. data/vendor/depot_tools/recipe_modules/gclient/example.expected/tryserver.json +16 -14
  183. data/vendor/depot_tools/recipe_modules/gclient/example.py +13 -11
  184. data/vendor/depot_tools/recipe_modules/gerrit/__init__.py +6 -0
  185. data/vendor/depot_tools/recipe_modules/gerrit/api.py +63 -0
  186. data/vendor/depot_tools/recipe_modules/gerrit/example.expected/basic.json +64 -0
  187. data/vendor/depot_tools/recipe_modules/gerrit/example.py +35 -0
  188. data/vendor/depot_tools/recipe_modules/gerrit/test_api.py +24 -0
  189. data/vendor/depot_tools/recipe_modules/git/__init__.py +4 -0
  190. data/vendor/depot_tools/recipe_modules/git/api.py +155 -142
  191. data/vendor/depot_tools/recipe_modules/git/example.expected/basic.json +43 -17
  192. data/vendor/depot_tools/recipe_modules/git/example.expected/basic_branch.json +43 -17
  193. data/vendor/depot_tools/recipe_modules/git/example.expected/basic_file_name.json +43 -17
  194. data/vendor/depot_tools/recipe_modules/git/example.expected/basic_hash.json +43 -17
  195. data/vendor/depot_tools/recipe_modules/git/example.expected/basic_ref.json +43 -17
  196. data/vendor/depot_tools/recipe_modules/git/example.expected/basic_submodule_update_force.json +43 -17
  197. data/vendor/depot_tools/recipe_modules/git/example.expected/can_fail_build.json +13 -13
  198. data/vendor/depot_tools/recipe_modules/git/example.expected/cannot_fail_build.json +43 -17
  199. data/vendor/depot_tools/recipe_modules/git/example.expected/cat-file_test.json +45 -19
  200. data/vendor/depot_tools/recipe_modules/git/example.expected/count-objects_delta.json +45 -19
  201. data/vendor/depot_tools/recipe_modules/git/example.expected/count-objects_failed.json +43 -17
  202. data/vendor/depot_tools/recipe_modules/git/example.expected/count-objects_with_bad_output.json +43 -17
  203. data/vendor/depot_tools/recipe_modules/git/example.expected/count-objects_with_bad_output_fails_build.json +8 -8
  204. data/vendor/depot_tools/recipe_modules/git/example.expected/curl_trace_file.json +44 -18
  205. data/vendor/depot_tools/recipe_modules/git/example.expected/git-cache-checkout.json +48 -22
  206. data/vendor/depot_tools/recipe_modules/git/example.expected/platform_win.json +43 -17
  207. data/vendor/depot_tools/recipe_modules/git/example.expected/rebase_failed.json +42 -16
  208. data/vendor/depot_tools/recipe_modules/git/example.expected/remote_not_origin.json +43 -17
  209. data/vendor/depot_tools/recipe_modules/git/example.expected/set_got_revision.json +43 -17
  210. data/vendor/depot_tools/recipe_modules/git/example.py +9 -3
  211. data/vendor/depot_tools/recipe_modules/git_cl/__init__.py +4 -0
  212. data/vendor/depot_tools/recipe_modules/git_cl/api.py +8 -8
  213. data/vendor/depot_tools/recipe_modules/git_cl/example.py +1 -1
  214. data/vendor/depot_tools/recipe_modules/gsutil/__init__.py +4 -0
  215. data/vendor/depot_tools/recipe_modules/gsutil/api.py +196 -0
  216. data/vendor/depot_tools/recipe_modules/gsutil/example.expected/basic.json +186 -0
  217. data/vendor/depot_tools/recipe_modules/gsutil/example.py +77 -0
  218. data/vendor/depot_tools/recipe_modules/gsutil/resources/gsutil_smart_retry.py +69 -0
  219. data/vendor/depot_tools/recipe_modules/infra_paths/__init__.py +3 -0
  220. data/vendor/depot_tools/recipe_modules/infra_paths/api.py +20 -3
  221. data/vendor/depot_tools/recipe_modules/infra_paths/example.expected/basic.json +3 -1
  222. data/vendor/depot_tools/recipe_modules/infra_paths/example.expected/paths_buildbot_linux.json +3 -1
  223. data/vendor/depot_tools/recipe_modules/infra_paths/example.expected/paths_buildbot_mac.json +3 -1
  224. data/vendor/depot_tools/recipe_modules/infra_paths/example.expected/paths_buildbot_win.json +3 -1
  225. data/vendor/depot_tools/recipe_modules/infra_paths/example.expected/paths_kitchen_linux.json +3 -1
  226. data/vendor/depot_tools/recipe_modules/infra_paths/example.expected/paths_kitchen_mac.json +3 -1
  227. data/vendor/depot_tools/recipe_modules/infra_paths/example.expected/paths_kitchen_win.json +3 -1
  228. data/vendor/depot_tools/recipe_modules/infra_paths/example.expected/paths_swarmbucket_linux.json +3 -1
  229. data/vendor/depot_tools/recipe_modules/infra_paths/example.expected/paths_swarmbucket_mac.json +3 -1
  230. data/vendor/depot_tools/recipe_modules/infra_paths/example.expected/paths_swarmbucket_win.json +3 -1
  231. data/vendor/depot_tools/recipe_modules/infra_paths/example.py +6 -1
  232. data/vendor/depot_tools/recipe_modules/infra_paths/path_config.py +4 -6
  233. data/vendor/depot_tools/recipe_modules/rietveld/__init__.py +5 -0
  234. data/vendor/depot_tools/recipe_modules/rietveld/api.py +12 -9
  235. data/vendor/depot_tools/recipe_modules/rietveld/example.expected/basic.json +2 -24
  236. data/vendor/depot_tools/recipe_modules/rietveld/example.expected/buildbot.json +30 -0
  237. data/vendor/depot_tools/recipe_modules/rietveld/example.py +12 -6
  238. data/vendor/depot_tools/recipe_modules/tryserver/__init__.py +4 -0
  239. data/vendor/depot_tools/recipe_modules/tryserver/api.py +46 -70
  240. data/vendor/depot_tools/recipe_modules/tryserver/example.expected/set_failure_hash_with_no_steps.json +8 -0
  241. data/vendor/depot_tools/recipe_modules/tryserver/example.expected/{with_svn_patch.json → with_gerrit_patch.json} +1 -31
  242. data/vendor/depot_tools/recipe_modules/tryserver/example.expected/with_gerrit_patch_deprecated.json +39 -0
  243. data/vendor/depot_tools/recipe_modules/tryserver/example.expected/with_git_patch.json +2 -2
  244. data/vendor/depot_tools/recipe_modules/tryserver/example.expected/with_git_patch_luci.json +8 -0
  245. data/vendor/depot_tools/recipe_modules/tryserver/example.expected/with_rietveld_patch.json +3 -3
  246. data/vendor/depot_tools/recipe_modules/tryserver/example.expected/with_rietveld_patch_new.json +3 -3
  247. data/vendor/depot_tools/recipe_modules/tryserver/example.expected/with_wrong_patch.json +1 -1
  248. data/vendor/depot_tools/recipe_modules/tryserver/example.expected/with_wrong_patch_new.json +1 -1
  249. data/vendor/depot_tools/recipe_modules/tryserver/example.py +35 -5
  250. data/vendor/depot_tools/recipes.py +52 -28
  251. data/vendor/depot_tools/repo +216 -69
  252. data/vendor/depot_tools/rietveld.py +20 -15
  253. data/vendor/depot_tools/roll_dep.py +1 -1
  254. data/vendor/depot_tools/scm.py +11 -826
  255. data/vendor/depot_tools/subprocess2.py +5 -5
  256. data/vendor/depot_tools/third_party/cq_client/README.depot_tools.md +2 -0
  257. data/vendor/depot_tools/third_party/cq_client/README.md +5 -1
  258. data/vendor/depot_tools/third_party/cq_client/cq.pb.go +183 -104
  259. data/vendor/depot_tools/third_party/cq_client/cq.proto +43 -27
  260. data/vendor/depot_tools/third_party/cq_client/cq_pb2.py +95 -29
  261. data/vendor/depot_tools/third_party/cq_client/testdata/cq_both.cfg +67 -0
  262. data/vendor/depot_tools/third_party/cq_client/testdata/cq_gerrit.cfg +1 -2
  263. data/vendor/depot_tools/third_party/cq_client/testdata/cq_rietveld.cfg +0 -3
  264. data/vendor/depot_tools/third_party/upload.py +44 -24
  265. data/vendor/depot_tools/win_toolchain/get_toolchain_if_necessary.py +0 -5
  266. metadata +38 -93
  267. data/patches/0004-Reinterpret-thread-hash-for-FreeBSD-too.patch +0 -25
  268. data/vendor/depot_tools/git-auto-svn +0 -6
  269. data/vendor/depot_tools/git_auto_svn.py +0 -122
  270. data/vendor/depot_tools/man/html/git-auto-svn.html +0 -837
  271. data/vendor/depot_tools/man/man1/git-auto-svn.1 +0 -113
  272. data/vendor/depot_tools/man/src/_git-auto-svn_desc.helper.txt +0 -1
  273. data/vendor/depot_tools/man/src/git-auto-svn.txt +0 -69
  274. data/vendor/depot_tools/recipe_modules/bot_update/example.expected/off.json +0 -43
  275. data/vendor/depot_tools/recipe_modules/cipd/example.expected/install-failed.json +0 -31
  276. data/vendor/depot_tools/recipe_modules/cipd/resources/bootstrap.py +0 -218
  277. data/vendor/depot_tools/recipe_modules/tryserver/test_api.py +0 -7
  278. data/vendor/depot_tools/third_party/gsutil/CHECKSUM +0 -1
  279. data/vendor/depot_tools/third_party/gsutil/COPYING +0 -202
  280. data/vendor/depot_tools/third_party/gsutil/LICENSE.third_party +0 -295
  281. data/vendor/depot_tools/third_party/gsutil/MANIFEST.in +0 -5
  282. data/vendor/depot_tools/third_party/gsutil/README +0 -38
  283. data/vendor/depot_tools/third_party/gsutil/README.chromium +0 -25
  284. data/vendor/depot_tools/third_party/gsutil/README.pkg +0 -49
  285. data/vendor/depot_tools/third_party/gsutil/ReleaseNotes.txt +0 -825
  286. data/vendor/depot_tools/third_party/gsutil/VERSION +0 -1
  287. data/vendor/depot_tools/third_party/gsutil/gslib/README +0 -5
  288. data/vendor/depot_tools/third_party/gsutil/gslib/__init__.py +0 -22
  289. data/vendor/depot_tools/third_party/gsutil/gslib/addlhelp/__init__.py +0 -15
  290. data/vendor/depot_tools/third_party/gsutil/gslib/addlhelp/acls.py +0 -234
  291. data/vendor/depot_tools/third_party/gsutil/gslib/addlhelp/anon.py +0 -57
  292. data/vendor/depot_tools/third_party/gsutil/gslib/addlhelp/command_opts.py +0 -116
  293. data/vendor/depot_tools/third_party/gsutil/gslib/addlhelp/dev.py +0 -139
  294. data/vendor/depot_tools/third_party/gsutil/gslib/addlhelp/metadata.py +0 -186
  295. data/vendor/depot_tools/third_party/gsutil/gslib/addlhelp/naming.py +0 -173
  296. data/vendor/depot_tools/third_party/gsutil/gslib/addlhelp/prod.py +0 -160
  297. data/vendor/depot_tools/third_party/gsutil/gslib/addlhelp/projects.py +0 -130
  298. data/vendor/depot_tools/third_party/gsutil/gslib/addlhelp/subdirs.py +0 -110
  299. data/vendor/depot_tools/third_party/gsutil/gslib/addlhelp/support.py +0 -86
  300. data/vendor/depot_tools/third_party/gsutil/gslib/addlhelp/versioning.py +0 -242
  301. data/vendor/depot_tools/third_party/gsutil/gslib/addlhelp/wildcards.py +0 -170
  302. data/vendor/depot_tools/third_party/gsutil/gslib/bucket_listing_ref.py +0 -175
  303. data/vendor/depot_tools/third_party/gsutil/gslib/command.py +0 -725
  304. data/vendor/depot_tools/third_party/gsutil/gslib/command_runner.py +0 -102
  305. data/vendor/depot_tools/third_party/gsutil/gslib/commands/__init__.py +0 -15
  306. data/vendor/depot_tools/third_party/gsutil/gslib/commands/cat.py +0 -131
  307. data/vendor/depot_tools/third_party/gsutil/gslib/commands/chacl.py +0 -523
  308. data/vendor/depot_tools/third_party/gsutil/gslib/commands/config.py +0 -662
  309. data/vendor/depot_tools/third_party/gsutil/gslib/commands/cp.py +0 -1819
  310. data/vendor/depot_tools/third_party/gsutil/gslib/commands/disablelogging.py +0 -101
  311. data/vendor/depot_tools/third_party/gsutil/gslib/commands/enablelogging.py +0 -149
  312. data/vendor/depot_tools/third_party/gsutil/gslib/commands/getacl.py +0 -82
  313. data/vendor/depot_tools/third_party/gsutil/gslib/commands/getcors.py +0 -121
  314. data/vendor/depot_tools/third_party/gsutil/gslib/commands/getdefacl.py +0 -86
  315. data/vendor/depot_tools/third_party/gsutil/gslib/commands/getlogging.py +0 -137
  316. data/vendor/depot_tools/third_party/gsutil/gslib/commands/getversioning.py +0 -116
  317. data/vendor/depot_tools/third_party/gsutil/gslib/commands/getwebcfg.py +0 -122
  318. data/vendor/depot_tools/third_party/gsutil/gslib/commands/help.py +0 -218
  319. data/vendor/depot_tools/third_party/gsutil/gslib/commands/ls.py +0 -578
  320. data/vendor/depot_tools/third_party/gsutil/gslib/commands/mb.py +0 -172
  321. data/vendor/depot_tools/third_party/gsutil/gslib/commands/mv.py +0 -159
  322. data/vendor/depot_tools/third_party/gsutil/gslib/commands/perfdiag.py +0 -903
  323. data/vendor/depot_tools/third_party/gsutil/gslib/commands/rb.py +0 -113
  324. data/vendor/depot_tools/third_party/gsutil/gslib/commands/rm.py +0 -237
  325. data/vendor/depot_tools/third_party/gsutil/gslib/commands/setacl.py +0 -138
  326. data/vendor/depot_tools/third_party/gsutil/gslib/commands/setcors.py +0 -145
  327. data/vendor/depot_tools/third_party/gsutil/gslib/commands/setdefacl.py +0 -105
  328. data/vendor/depot_tools/third_party/gsutil/gslib/commands/setmeta.py +0 -420
  329. data/vendor/depot_tools/third_party/gsutil/gslib/commands/setversioning.py +0 -114
  330. data/vendor/depot_tools/third_party/gsutil/gslib/commands/setwebcfg.py +0 -190
  331. data/vendor/depot_tools/third_party/gsutil/gslib/commands/update.py +0 -305
  332. data/vendor/depot_tools/third_party/gsutil/gslib/commands/version.py +0 -150
  333. data/vendor/depot_tools/third_party/gsutil/gslib/exception.py +0 -76
  334. data/vendor/depot_tools/third_party/gsutil/gslib/help_provider.py +0 -81
  335. data/vendor/depot_tools/third_party/gsutil/gslib/name_expansion.py +0 -550
  336. data/vendor/depot_tools/third_party/gsutil/gslib/no_op_auth_plugin.py +0 -30
  337. data/vendor/depot_tools/third_party/gsutil/gslib/plurality_checkable_iterator.py +0 -56
  338. data/vendor/depot_tools/third_party/gsutil/gslib/project_id.py +0 -67
  339. data/vendor/depot_tools/third_party/gsutil/gslib/storage_uri_builder.py +0 -56
  340. data/vendor/depot_tools/third_party/gsutil/gslib/thread_pool.py +0 -79
  341. data/vendor/depot_tools/third_party/gsutil/gslib/util.py +0 -167
  342. data/vendor/depot_tools/third_party/gsutil/gslib/wildcard_iterator.py +0 -498
  343. data/vendor/depot_tools/third_party/gsutil/gsutil +0 -384
  344. data/vendor/depot_tools/third_party/gsutil/gsutil.spec.in +0 -75
  345. data/vendor/depot_tools/third_party/gsutil/oauth2_plugin/__init__.py +0 -22
  346. data/vendor/depot_tools/third_party/gsutil/oauth2_plugin/oauth2_client.py +0 -630
  347. data/vendor/depot_tools/third_party/gsutil/oauth2_plugin/oauth2_client_test.py +0 -374
  348. data/vendor/depot_tools/third_party/gsutil/oauth2_plugin/oauth2_helper.py +0 -103
  349. data/vendor/depot_tools/third_party/gsutil/oauth2_plugin/oauth2_plugin.py +0 -24
  350. data/vendor/depot_tools/third_party/gsutil/pkg_util.py +0 -60
  351. data/vendor/depot_tools/third_party/gsutil/plugins/__init__.py +0 -0
  352. data/vendor/depot_tools/third_party/gsutil/plugins/sso_auth.py +0 -105
@@ -6,19 +6,19 @@
6
6
 
7
7
  # This git extension converts a chromium commit number to its git commit hash.
8
8
  # It accepts the following input formats:
9
- #
9
+ #
10
10
  # $ git crrev-parse Cr-Commit-Position: refs/heads/master@{#311769}
11
11
  # $ git crrev-parse ' Cr-Commit-Position: refs/heads/master@{#311769}'
12
12
  # $ git crrev-parse 'Cr-Commit-Position: refs/heads/master@{#311769}'
13
13
  # $ git crrev-parse refs/heads/master@{#311769}
14
- #
14
+ #
15
15
  # It also works for branches (assuming you have branches in your local
16
16
  # checkout):
17
- #
17
+ #
18
18
  # $ git crrev-parse refs/branch-heads/2278@{#2}
19
- #
19
+ #
20
20
  # If you don't specify a branch, refs/heads/master is assumed:
21
- #
21
+ #
22
22
  # $ git crrev-parse @{#311769}
23
23
  # $ git crrev-parse 311769
24
24
 
@@ -41,11 +41,10 @@ while [ -n "$1" ]; do
41
41
  remote_ref="${remote_ref/refs\/branch-heads/refs\/remotes\/branch-heads}"
42
42
  num="${commit_pos#*@\{\#}"
43
43
  num="${num%\}}"
44
-
45
44
  if [ -z "$ref" -o -z "$num" ]; then
46
45
  git rev-parse "$1"
47
46
  else
48
- grep_str="Cr-Commit-Position: $ref@{#$num}"
47
+ grep_str="^Cr-Commit-Position: $ref@{#$num}"
49
48
  git rev-list -n 1 --grep="$grep_str" "$remote_ref"
50
49
  fi
51
50
 
@@ -6,4 +6,4 @@ git grep -n -e "$@" -- "*.h" "*.hpp" "*.cpp" "*.c" "*.cc" "*.cpp" "*.inl"\
6
6
  "*.grd" "*.grdp" "*.idl" "*.m" "*.mm" "*.py" "*.sh" "*.cfg" "*.tac" "*.go"\
7
7
  "*.vcproj" "*.vsprops" "*.make" "*.gyp" "*.gypi" "*.isolate" "*.java"\
8
8
  "*.js" "*.html" "*.css" "*.ebuild" "*.pl" "*.pm" "*.yaml" "*.gn" "*.gni"\
9
- "*.json" "DEPS" "*/DEPS"
9
+ "*.json" "DEPS" "*/DEPS" "*.mojom"
@@ -29,7 +29,7 @@ GC_AUTOPACKLIMIT = 50
29
29
  GIT_CACHE_CORRUPT_MESSAGE = 'WARNING: The Git cache is corrupt.'
30
30
 
31
31
  try:
32
- # pylint: disable=E0602
32
+ # pylint: disable=undefined-variable
33
33
  WinErr = WindowsError
34
34
  except NameError:
35
35
  class WinErr(Exception):
@@ -41,6 +41,42 @@ class LockError(Exception):
41
41
  class ClobberNeeded(Exception):
42
42
  pass
43
43
 
44
+
45
+ def exponential_backoff_retry(fn, excs=(Exception,), name=None, count=10,
46
+ sleep_time=0.25, printerr=None):
47
+ """Executes |fn| up to |count| times, backing off exponentially.
48
+
49
+ Args:
50
+ fn (callable): The function to execute. If this raises a handled
51
+ exception, the function will retry with exponential backoff.
52
+ excs (tuple): A tuple of Exception types to handle. If one of these is
53
+ raised by |fn|, a retry will be attempted. If |fn| raises an Exception
54
+ that is not in this list, it will immediately pass through. If |excs|
55
+ is empty, the Exception base class will be used.
56
+ name (str): Optional operation name to print in the retry string.
57
+ count (int): The number of times to try before allowing the exception to
58
+ pass through.
59
+ sleep_time (float): The initial number of seconds to sleep in between
60
+ retries. This will be doubled each retry.
61
+ printerr (callable): Function that will be called with the error string upon
62
+ failures. If None, |logging.warning| will be used.
63
+
64
+ Returns: The return value of the successful fn.
65
+ """
66
+ printerr = printerr or logging.warning
67
+ for i in xrange(count):
68
+ try:
69
+ return fn()
70
+ except excs as e:
71
+ if (i+1) >= count:
72
+ raise
73
+
74
+ printerr('Retrying %s in %.2f second(s) (%d / %d attempts): %s' % (
75
+ (name or 'operation'), sleep_time, (i+1), count, e))
76
+ time.sleep(sleep_time)
77
+ sleep_time *= 2
78
+
79
+
44
80
  class Lockfile(object):
45
81
  """Class to represent a cross-platform process-specific lockfile."""
46
82
 
@@ -79,13 +115,16 @@ class Lockfile(object):
79
115
  """
80
116
  if sys.platform == 'win32':
81
117
  lockfile = os.path.normcase(self.lockfile)
82
- for _ in xrange(3):
118
+
119
+ def delete():
83
120
  exitcode = subprocess.call(['cmd.exe', '/c',
84
121
  'del', '/f', '/q', lockfile])
85
- if exitcode == 0:
86
- return
87
- time.sleep(3)
88
- raise LockError('Failed to remove lock: %s' % lockfile)
122
+ if exitcode != 0:
123
+ raise LockError('Failed to remove lock: %s' % (lockfile,))
124
+ exponential_backoff_retry(
125
+ delete,
126
+ excs=(LockError,),
127
+ name='del [%s]' % (lockfile,))
89
128
  else:
90
129
  os.remove(self.lockfile)
91
130
 
@@ -181,7 +220,7 @@ class Mirror(object):
181
220
  else:
182
221
  self.print = print
183
222
 
184
- def print_without_file(self, message, **kwargs):
223
+ def print_without_file(self, message, **_kwargs):
185
224
  self.print_func(message)
186
225
 
187
226
  @property
@@ -230,6 +269,16 @@ class Mirror(object):
230
269
  setattr(cls, 'cachepath', cachepath)
231
270
  return getattr(cls, 'cachepath')
232
271
 
272
+ def Rename(self, src, dst):
273
+ # This is somehow racy on Windows.
274
+ # Catching OSError because WindowsError isn't portable and
275
+ # pylint complains.
276
+ exponential_backoff_retry(
277
+ lambda: os.rename(src, dst),
278
+ excs=(OSError,),
279
+ name='rename [%s] => [%s]' % (src, dst),
280
+ printerr=self.print)
281
+
233
282
  def RunGit(self, cmd, **kwargs):
234
283
  """Run git in a subprocess."""
235
284
  cwd = kwargs.setdefault('cwd', self.mirror_path)
@@ -324,7 +373,15 @@ class Mirror(object):
324
373
  retcode = 0
325
374
  finally:
326
375
  # Clean up the downloaded zipfile.
327
- gclient_utils.rm_file_or_tree(tempdir)
376
+ #
377
+ # This is somehow racy on Windows.
378
+ # Catching OSError because WindowsError isn't portable and
379
+ # pylint complains.
380
+ exponential_backoff_retry(
381
+ lambda: gclient_utils.rm_file_or_tree(tempdir),
382
+ excs=(OSError,),
383
+ name='rmtree [%s]' % (tempdir,),
384
+ printerr=self.print)
328
385
 
329
386
  if retcode:
330
387
  self.print(
@@ -439,16 +496,9 @@ class Mirror(object):
439
496
  self._fetch(tempdir or self.mirror_path, verbose, depth)
440
497
  finally:
441
498
  if tempdir:
442
- try:
443
- if os.path.exists(self.mirror_path):
444
- gclient_utils.rmtree(self.mirror_path)
445
- os.rename(tempdir, self.mirror_path)
446
- except OSError as e:
447
- # This is somehow racy on Windows.
448
- # Catching OSError because WindowsError isn't portable and
449
- # pylint complains.
450
- self.print('Error moving %s to %s: %s' % (tempdir, self.mirror_path,
451
- str(e)))
499
+ if os.path.exists(self.mirror_path):
500
+ gclient_utils.rmtree(self.mirror_path)
501
+ self.Rename(tempdir, self.mirror_path)
452
502
  if not ignore_lock:
453
503
  lockfile.unlock()
454
504
 
@@ -52,7 +52,7 @@ def cherry_pick(target_branch, commit, auth_config):
52
52
  ])
53
53
 
54
54
  rietveld = Rietveld(config('rietveld.server'), auth_config, author)
55
- # pylint: disable=W0212
55
+ # pylint: disable=protected-access
56
56
  output = rietveld._send(
57
57
  '/upload',
58
58
  payload=payload,
@@ -92,7 +92,7 @@ def cherry_pick(target_branch, commit, auth_config):
92
92
  ('data', filename, content),
93
93
  ])
94
94
 
95
- # pylint: disable=W0212
95
+ # pylint: disable=protected-access
96
96
  print ' Uploading base file for %s:' % filename, rietveld._send(
97
97
  '/%s/upload_content/%s/%s' % (issue, patchset, file_id),
98
98
  payload=payload,
@@ -115,14 +115,14 @@ def cherry_pick(target_branch, commit, auth_config):
115
115
  ('data', filename, content),
116
116
  ])
117
117
 
118
- # pylint: disable=W0212
118
+ # pylint: disable=protected-access
119
119
  print ' Uploading %s:' % filename, rietveld._send(
120
120
  '/%s/upload_content/%s/%s' % (issue, patchset, file_id),
121
121
  payload=payload,
122
122
  content_type=content_type,
123
123
  )
124
124
 
125
- # pylint: disable=W0212
125
+ # pylint: disable=protected-access
126
126
  print 'Finalizing upload:', rietveld._send('/%s/upload_complete/1' % issue)
127
127
 
128
128
 
@@ -13,6 +13,8 @@ from distutils.version import LooseVersion
13
13
  from multiprocessing.pool import ThreadPool
14
14
  import base64
15
15
  import collections
16
+ import contextlib
17
+ import fnmatch
16
18
  import httplib
17
19
  import json
18
20
  import logging
@@ -23,8 +25,6 @@ import re
23
25
  import stat
24
26
  import sys
25
27
  import textwrap
26
- import time
27
- import traceback
28
28
  import urllib
29
29
  import urllib2
30
30
  import urlparse
@@ -33,7 +33,7 @@ import webbrowser
33
33
  import zlib
34
34
 
35
35
  try:
36
- import readline # pylint: disable=F0401,W0611
36
+ import readline # pylint: disable=import-error,W0611
37
37
  except ImportError:
38
38
  pass
39
39
 
@@ -41,8 +41,8 @@ from third_party import colorama
41
41
  from third_party import httplib2
42
42
  from third_party import upload
43
43
  import auth
44
+ import checkout
44
45
  import clang_format
45
- import commit_queue
46
46
  import dart_format
47
47
  import setup_color
48
48
  import fix_encoding
@@ -64,9 +64,8 @@ __version__ = '2.0'
64
64
 
65
65
  COMMIT_BOT_EMAIL = 'commit-bot@chromium.org'
66
66
  DEFAULT_SERVER = 'https://codereview.chromium.org'
67
- POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s'
67
+ POSTUPSTREAM_HOOK = '.git/hooks/post-cl-land'
68
68
  DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup'
69
- GIT_INSTRUCTIONS_URL = 'http://code.google.com/p/chromium/wiki/UsingGit'
70
69
  REFS_THAT_ALIAS_TO_OTHER_REFS = {
71
70
  'refs/remotes/origin/lkgr': 'refs/remotes/origin/master',
72
71
  'refs/remotes/origin/lkcr': 'refs/remotes/origin/master',
@@ -76,18 +75,39 @@ REFS_THAT_ALIAS_TO_OTHER_REFS = {
76
75
  DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)"
77
76
  DEFAULT_LINT_IGNORE_REGEX = r"$^"
78
77
 
78
+ # Buildbucket master name prefix.
79
+ MASTER_PREFIX = 'master.'
80
+
79
81
  # Shortcut since it quickly becomes redundant.
80
82
  Fore = colorama.Fore
81
83
 
82
84
  # Initialized in main()
83
85
  settings = None
84
86
 
87
+ # Used by tests/git_cl_test.py to add extra logging.
88
+ # Inside the weirdly failing test, add this:
89
+ # >>> self.mock(git_cl, '_IS_BEING_TESTED', True)
90
+ # And scroll up to see the strack trace printed.
91
+ _IS_BEING_TESTED = False
92
+
93
+
94
+ def DieWithError(message, change_desc=None):
95
+ if change_desc:
96
+ SaveDescriptionBackup(change_desc)
85
97
 
86
- def DieWithError(message):
87
98
  print(message, file=sys.stderr)
88
99
  sys.exit(1)
89
100
 
90
101
 
102
+ def SaveDescriptionBackup(change_desc):
103
+ backup_path = os.path.expanduser(DESCRIPTION_BACKUP_FILE)
104
+ print('\nError after CL description prompt -- saving description to %s\n' %
105
+ backup_path)
106
+ backup_file = open(backup_path, 'w')
107
+ backup_file.write(change_desc.description)
108
+ backup_file.close()
109
+
110
+
91
111
  def GetNoGitPagerEnv():
92
112
  env = os.environ.copy()
93
113
  # 'cat' is a magical git string that disables pagers on all platforms.
@@ -148,6 +168,13 @@ def BranchExists(branch):
148
168
  return not code
149
169
 
150
170
 
171
+ def time_sleep(seconds):
172
+ # Use this so that it can be mocked in tests without interfering with python
173
+ # system machinery.
174
+ import time # Local import to discourage others from importing time globally.
175
+ return time.sleep(seconds)
176
+
177
+
151
178
  def ask_for_data(prompt):
152
179
  try:
153
180
  return raw_input(prompt)
@@ -218,6 +245,26 @@ def _git_set_branch_config_value(key, value, branch=None, **kwargs):
218
245
  RunGit(args, **kwargs)
219
246
 
220
247
 
248
+ def _get_committer_timestamp(commit):
249
+ """Returns unix timestamp as integer of a committer in a commit.
250
+
251
+ Commit can be whatever git show would recognize, such as HEAD, sha1 or ref.
252
+ """
253
+ # Git also stores timezone offset, but it only affects visual display,
254
+ # actual point in time is defined by this timestamp only.
255
+ return int(RunGit(['show', '-s', '--format=%ct', commit]).strip())
256
+
257
+
258
+ def _git_amend_head(message, committer_timestamp):
259
+ """Amends commit with new message and desired committer_timestamp.
260
+
261
+ Sets committer timezone to UTC.
262
+ """
263
+ env = os.environ.copy()
264
+ env['GIT_COMMITTER_DATE'] = '%d+0000' % committer_timestamp
265
+ return RunGit(['commit', '--amend', '-m', message], env=env)
266
+
267
+
221
268
  def add_git_similarity(parser):
222
269
  parser.add_option(
223
270
  '--similarity', metavar='SIM', type=int, action='store',
@@ -275,10 +322,22 @@ def _prefix_master(master):
275
322
  (tryserver.chromium.linux) by stripping off the prefix 'master.'. This
276
323
  function does the conversion for buildbucket migration.
277
324
  """
278
- prefix = 'master.'
279
- if master.startswith(prefix):
325
+ if master.startswith(MASTER_PREFIX):
280
326
  return master
281
- return '%s%s' % (prefix, master)
327
+ return '%s%s' % (MASTER_PREFIX, master)
328
+
329
+
330
+ def _unprefix_master(bucket):
331
+ """Convert bucket name to shortened master name.
332
+
333
+ Buildbucket uses full master name(master.tryserver.chromium.linux) as bucket
334
+ name, while the developers always use shortened master name
335
+ (tryserver.chromium.linux) by stripping off the prefix 'master.'. This
336
+ function does the conversion for buildbucket migration.
337
+ """
338
+ if bucket.startswith(MASTER_PREFIX):
339
+ return bucket[len(MASTER_PREFIX):]
340
+ return bucket
282
341
 
283
342
 
284
343
  def _buildbucket_retry(operation_name, http, *args, **kwargs):
@@ -313,72 +372,152 @@ def _buildbucket_retry(operation_name, http, *args, **kwargs):
313
372
 
314
373
  # status >= 500 means transient failures.
315
374
  logging.debug('Transient errors when %s. Will retry.', operation_name)
316
- time.sleep(0.5 + 1.5*try_count)
375
+ time_sleep(0.5 + 1.5*try_count)
317
376
  try_count += 1
318
377
  assert False, 'unreachable'
319
378
 
320
379
 
321
- def trigger_try_jobs(auth_config, changelist, options, masters, category):
322
- rietveld_url = settings.GetDefaultServerUrl()
323
- rietveld_host = urlparse.urlparse(rietveld_url).hostname
324
- authenticator = auth.get_authenticator_for_host(rietveld_host, auth_config)
380
+ def _get_bucket_map(changelist, options, option_parser):
381
+ """Returns a dict mapping bucket names to builders and tests,
382
+ for triggering try jobs.
383
+ """
384
+ # If no bots are listed, we try to get a set of builders and tests based
385
+ # on GetPreferredTryMasters functions in PRESUBMIT.py files.
386
+ if not options.bot:
387
+ change = changelist.GetChange(
388
+ changelist.GetCommonAncestorWithUpstream(), None)
389
+ # Get try masters from PRESUBMIT.py files.
390
+ masters = presubmit_support.DoGetTryMasters(
391
+ change=change,
392
+ changed_files=change.LocalPaths(),
393
+ repository_root=settings.GetRoot(),
394
+ default_presubmit=None,
395
+ project=None,
396
+ verbose=options.verbose,
397
+ output_stream=sys.stdout)
398
+ if masters is None:
399
+ return None
400
+ return {_prefix_master(m): b for m, b in masters.iteritems()}
401
+
402
+ if options.bucket:
403
+ return {options.bucket: {b: [] for b in options.bot}}
404
+ if options.master:
405
+ return {_prefix_master(options.master): {b: [] for b in options.bot}}
406
+
407
+ # If bots are listed but no master or bucket, then we need to find out
408
+ # the corresponding master for each bot.
409
+ bucket_map, error_message = _get_bucket_map_for_builders(options.bot)
410
+ if error_message:
411
+ option_parser.error(
412
+ 'Tryserver master cannot be found because: %s\n'
413
+ 'Please manually specify the tryserver master, e.g. '
414
+ '"-m tryserver.chromium.linux".' % error_message)
415
+ return bucket_map
416
+
417
+
418
+ def _get_bucket_map_for_builders(builders):
419
+ """Returns a map of buckets to builders for the given builders."""
420
+ map_url = 'https://builders-map.appspot.com/'
421
+ try:
422
+ builders_map = json.load(urllib2.urlopen(map_url))
423
+ except urllib2.URLError as e:
424
+ return None, ('Failed to fetch builder-to-master map from %s. Error: %s.' %
425
+ (map_url, e))
426
+ except ValueError as e:
427
+ return None, ('Invalid json string from %s. Error: %s.' % (map_url, e))
428
+ if not builders_map:
429
+ return None, 'Failed to build master map.'
430
+
431
+ bucket_map = {}
432
+ for builder in builders:
433
+ masters = builders_map.get(builder, [])
434
+ if not masters:
435
+ return None, ('No matching master for builder %s.' % builder)
436
+ if len(masters) > 1:
437
+ return None, ('The builder name %s exists in multiple masters %s.' %
438
+ (builder, masters))
439
+ bucket = _prefix_master(masters[0])
440
+ bucket_map.setdefault(bucket, {})[builder] = []
441
+
442
+ return bucket_map, None
443
+
444
+
445
+ def _trigger_try_jobs(auth_config, changelist, buckets, options,
446
+ category='git_cl_try', patchset=None):
447
+ """Sends a request to Buildbucket to trigger try jobs for a changelist.
448
+
449
+ Args:
450
+ auth_config: AuthConfig for Rietveld.
451
+ changelist: Changelist that the try jobs are associated with.
452
+ buckets: A nested dict mapping bucket names to builders to tests.
453
+ options: Command-line options.
454
+ """
455
+ assert changelist.GetIssue(), 'CL must be uploaded first'
456
+ codereview_url = changelist.GetCodereviewServer()
457
+ assert codereview_url, 'CL must be uploaded first'
458
+ patchset = patchset or changelist.GetMostRecentPatchset()
459
+ assert patchset, 'CL must be uploaded first'
460
+
461
+ codereview_host = urlparse.urlparse(codereview_url).hostname
462
+ authenticator = auth.get_authenticator_for_host(codereview_host, auth_config)
325
463
  http = authenticator.authorize(httplib2.Http())
326
464
  http.force_exception_to_status_code = True
327
- issue_props = changelist.GetIssueProperties()
328
- issue = changelist.GetIssue()
329
- patchset = changelist.GetMostRecentPatchset()
330
- properties = _get_properties_from_options(options)
331
465
 
332
466
  buildbucket_put_url = (
333
467
  'https://{hostname}/_ah/api/buildbucket/v1/builds/batch'.format(
334
468
  hostname=options.buildbucket_host))
335
- buildset = 'patch/rietveld/{hostname}/{issue}/{patch}'.format(
336
- hostname=rietveld_host,
337
- issue=issue,
469
+ buildset = 'patch/{codereview}/{hostname}/{issue}/{patch}'.format(
470
+ codereview='gerrit' if changelist.IsGerrit() else 'rietveld',
471
+ hostname=codereview_host,
472
+ issue=changelist.GetIssue(),
338
473
  patch=patchset)
339
474
 
475
+ shared_parameters_properties = changelist.GetTryjobProperties(patchset)
476
+ shared_parameters_properties['category'] = category
477
+ if options.clobber:
478
+ shared_parameters_properties['clobber'] = True
479
+ extra_properties = _get_properties_from_options(options)
480
+ if extra_properties:
481
+ shared_parameters_properties.update(extra_properties)
482
+
340
483
  batch_req_body = {'builds': []}
341
484
  print_text = []
342
485
  print_text.append('Tried jobs on:')
343
- for master, builders_and_tests in sorted(masters.iteritems()):
344
- print_text.append('Master: %s' % master)
345
- bucket = _prefix_master(master)
486
+ for bucket, builders_and_tests in sorted(buckets.iteritems()):
487
+ print_text.append('Bucket: %s' % bucket)
488
+ master = None
489
+ if bucket.startswith(MASTER_PREFIX):
490
+ master = _unprefix_master(bucket)
346
491
  for builder, tests in sorted(builders_and_tests.iteritems()):
347
492
  print_text.append(' %s: %s' % (builder, tests))
348
493
  parameters = {
349
494
  'builder_name': builder,
350
495
  'changes': [{
351
- 'author': {'email': issue_props['owner_email']},
496
+ 'author': {'email': changelist.GetIssueOwner()},
352
497
  'revision': options.revision,
353
498
  }],
354
- 'properties': {
355
- 'category': category,
356
- 'issue': issue,
357
- 'master': master,
358
- 'patch_project': issue_props['project'],
359
- 'patch_storage': 'rietveld',
360
- 'patchset': patchset,
361
- 'reason': options.name,
362
- 'rietveld': rietveld_url,
363
- },
499
+ 'properties': shared_parameters_properties.copy(),
364
500
  }
365
501
  if 'presubmit' in builder.lower():
366
502
  parameters['properties']['dry_run'] = 'true'
367
503
  if tests:
368
504
  parameters['properties']['testfilter'] = tests
369
- if properties:
370
- parameters['properties'].update(properties)
371
- if options.clobber:
372
- parameters['properties']['clobber'] = True
505
+
506
+ tags = [
507
+ 'builder:%s' % builder,
508
+ 'buildset:%s' % buildset,
509
+ 'user_agent:git_cl_try',
510
+ ]
511
+ if master:
512
+ parameters['properties']['master'] = master
513
+ tags.append('master:%s' % master)
514
+
373
515
  batch_req_body['builds'].append(
374
516
  {
375
517
  'bucket': bucket,
376
518
  'parameters_json': json.dumps(parameters),
377
519
  'client_operation_id': str(uuid.uuid4()),
378
- 'tags': ['builder:%s' % builder,
379
- 'buildset:%s' % buildset,
380
- 'master:%s' % master,
381
- 'user_agent:git_cl_try']
520
+ 'tags': tags,
382
521
  }
383
522
  )
384
523
 
@@ -395,34 +534,42 @@ def trigger_try_jobs(auth_config, changelist, options, masters, category):
395
534
  print('\n'.join(print_text))
396
535
 
397
536
 
398
- def fetch_try_jobs(auth_config, changelist, options):
537
+ def fetch_try_jobs(auth_config, changelist, buildbucket_host,
538
+ patchset=None):
399
539
  """Fetches try jobs from buildbucket.
400
540
 
401
541
  Returns a map from build id to build info as a dictionary.
402
542
  """
403
- rietveld_url = settings.GetDefaultServerUrl()
404
- rietveld_host = urlparse.urlparse(rietveld_url).hostname
405
- authenticator = auth.get_authenticator_for_host(rietveld_host, auth_config)
543
+ assert buildbucket_host
544
+ assert changelist.GetIssue(), 'CL must be uploaded first'
545
+ assert changelist.GetCodereviewServer(), 'CL must be uploaded first'
546
+ patchset = patchset or changelist.GetMostRecentPatchset()
547
+ assert patchset, 'CL must be uploaded first'
548
+
549
+ codereview_url = changelist.GetCodereviewServer()
550
+ codereview_host = urlparse.urlparse(codereview_url).hostname
551
+ authenticator = auth.get_authenticator_for_host(codereview_host, auth_config)
406
552
  if authenticator.has_cached_credentials():
407
553
  http = authenticator.authorize(httplib2.Http())
408
554
  else:
409
555
  print('Warning: Some results might be missing because %s' %
410
556
  # Get the message on how to login.
411
- (auth.LoginRequiredError(rietveld_host).message,))
557
+ (auth.LoginRequiredError(codereview_host).message,))
412
558
  http = httplib2.Http()
413
559
 
414
560
  http.force_exception_to_status_code = True
415
561
 
416
- buildset = 'patch/rietveld/{hostname}/{issue}/{patch}'.format(
417
- hostname=rietveld_host,
562
+ buildset = 'patch/{codereview}/{hostname}/{issue}/{patch}'.format(
563
+ codereview='gerrit' if changelist.IsGerrit() else 'rietveld',
564
+ hostname=codereview_host,
418
565
  issue=changelist.GetIssue(),
419
- patch=options.patchset)
566
+ patch=patchset)
420
567
  params = {'tag': 'buildset:%s' % buildset}
421
568
 
422
569
  builds = {}
423
570
  while True:
424
571
  url = 'https://{hostname}/_ah/api/buildbucket/v1/search?{params}'.format(
425
- hostname=options.buildbucket_host,
572
+ hostname=buildbucket_host,
426
573
  params=urllib.urlencode(params))
427
574
  content = _buildbucket_retry('fetching try jobs', http, url, 'GET')
428
575
  for build in content.get('builds', []):
@@ -558,48 +705,6 @@ def write_try_results_json(output_file, builds):
558
705
  write_json(output_file, converted)
559
706
 
560
707
 
561
- def MatchSvnGlob(url, base_url, glob_spec, allow_wildcards):
562
- """Return the corresponding git ref if |base_url| together with |glob_spec|
563
- matches the full |url|.
564
-
565
- If |allow_wildcards| is true, |glob_spec| can contain wildcards (see below).
566
- """
567
- fetch_suburl, as_ref = glob_spec.split(':')
568
- if allow_wildcards:
569
- glob_match = re.match('(.+/)?(\*|{[^/]*})(/.+)?', fetch_suburl)
570
- if glob_match:
571
- # Parse specs like "branches/*/src:refs/remotes/svn/*" or
572
- # "branches/{472,597,648}/src:refs/remotes/svn/*".
573
- branch_re = re.escape(base_url)
574
- if glob_match.group(1):
575
- branch_re += '/' + re.escape(glob_match.group(1))
576
- wildcard = glob_match.group(2)
577
- if wildcard == '*':
578
- branch_re += '([^/]*)'
579
- else:
580
- # Escape and replace surrounding braces with parentheses and commas
581
- # with pipe symbols.
582
- wildcard = re.escape(wildcard)
583
- wildcard = re.sub('^\\\\{', '(', wildcard)
584
- wildcard = re.sub('\\\\,', '|', wildcard)
585
- wildcard = re.sub('\\\\}$', ')', wildcard)
586
- branch_re += wildcard
587
- if glob_match.group(3):
588
- branch_re += re.escape(glob_match.group(3))
589
- match = re.match(branch_re, url)
590
- if match:
591
- return re.sub('\*$', match.group(1), as_ref)
592
-
593
- # Parse specs like "trunk/src:refs/remotes/origin/trunk".
594
- if fetch_suburl:
595
- full_url = base_url + '/' + fetch_suburl
596
- else:
597
- full_url = base_url
598
- if full_url == url:
599
- return as_ref
600
- return None
601
-
602
-
603
708
  def print_stats(similarity, find_copies, args):
604
709
  """Prints statistics about the change to the user."""
605
710
  # --no-ext-diff is broken in some versions of Git, so try to work around
@@ -610,8 +715,7 @@ def print_stats(similarity, find_copies, args):
610
715
  del env['GIT_EXTERNAL_DIFF']
611
716
 
612
717
  if find_copies:
613
- similarity_options = ['--find-copies-harder', '-l100000',
614
- '-C%s' % similarity]
718
+ similarity_options = ['-l100000', '-C%s' % similarity]
615
719
  else:
616
720
  similarity_options = ['-M%s' % similarity]
617
721
 
@@ -634,8 +738,6 @@ class Settings(object):
634
738
  self.default_server = None
635
739
  self.cc = None
636
740
  self.root = None
637
- self.is_git_svn = None
638
- self.svn_branch = None
639
741
  self.tree_status_url = None
640
742
  self.viewvc_url = None
641
743
  self.updated = False
@@ -645,7 +747,6 @@ class Settings(object):
645
747
  self.git_editor = None
646
748
  self.project = None
647
749
  self.force_https_commit_url = None
648
- self.pending_ref_prefix = None
649
750
 
650
751
  def LazyUpdateIfNeeded(self):
651
752
  """Updates the settings from a codereview.settings file, if available."""
@@ -691,93 +792,12 @@ class Settings(object):
691
792
  return None
692
793
  git_cache.Mirror.SetCachePath(os.path.dirname(local_url))
693
794
  remote_url = git_cache.Mirror.CacheDirToUrl(local_url)
694
- # Use the /dev/null print_func to avoid terminal spew in WaitForRealCommit.
695
- mirror = git_cache.Mirror(remote_url, print_func = lambda *args: None)
795
+ # Use the /dev/null print_func to avoid terminal spew.
796
+ mirror = git_cache.Mirror(remote_url, print_func=lambda *args: None)
696
797
  if mirror.exists():
697
798
  return mirror
698
799
  return None
699
800
 
700
- def GetIsGitSvn(self):
701
- """Return true if this repo looks like it's using git-svn."""
702
- if self.is_git_svn is None:
703
- if self.GetPendingRefPrefix():
704
- # If PENDING_REF_PREFIX is set then it's a pure git repo no matter what.
705
- self.is_git_svn = False
706
- else:
707
- # If you have any "svn-remote.*" config keys, we think you're using svn.
708
- self.is_git_svn = RunGitWithCode(
709
- ['config', '--local', '--get-regexp', r'^svn-remote\.'])[0] == 0
710
- return self.is_git_svn
711
-
712
- def GetSVNBranch(self):
713
- if self.svn_branch is None:
714
- if not self.GetIsGitSvn():
715
- DieWithError('Repo doesn\'t appear to be a git-svn repo.')
716
-
717
- # Try to figure out which remote branch we're based on.
718
- # Strategy:
719
- # 1) iterate through our branch history and find the svn URL.
720
- # 2) find the svn-remote that fetches from the URL.
721
-
722
- # regexp matching the git-svn line that contains the URL.
723
- git_svn_re = re.compile(r'^\s*git-svn-id: (\S+)@', re.MULTILINE)
724
-
725
- # We don't want to go through all of history, so read a line from the
726
- # pipe at a time.
727
- # The -100 is an arbitrary limit so we don't search forever.
728
- cmd = ['git', 'log', '-100', '--pretty=medium']
729
- proc = subprocess2.Popen(cmd, stdout=subprocess2.PIPE,
730
- env=GetNoGitPagerEnv())
731
- url = None
732
- for line in proc.stdout:
733
- match = git_svn_re.match(line)
734
- if match:
735
- url = match.group(1)
736
- proc.stdout.close() # Cut pipe.
737
- break
738
-
739
- if url:
740
- svn_remote_re = re.compile(r'^svn-remote\.([^.]+)\.url (.*)$')
741
- remotes = RunGit(['config', '--get-regexp',
742
- r'^svn-remote\..*\.url']).splitlines()
743
- for remote in remotes:
744
- match = svn_remote_re.match(remote)
745
- if match:
746
- remote = match.group(1)
747
- base_url = match.group(2)
748
- rewrite_root = RunGit(
749
- ['config', 'svn-remote.%s.rewriteRoot' % remote],
750
- error_ok=True).strip()
751
- if rewrite_root:
752
- base_url = rewrite_root
753
- fetch_spec = RunGit(
754
- ['config', 'svn-remote.%s.fetch' % remote],
755
- error_ok=True).strip()
756
- if fetch_spec:
757
- self.svn_branch = MatchSvnGlob(url, base_url, fetch_spec, False)
758
- if self.svn_branch:
759
- break
760
- branch_spec = RunGit(
761
- ['config', 'svn-remote.%s.branches' % remote],
762
- error_ok=True).strip()
763
- if branch_spec:
764
- self.svn_branch = MatchSvnGlob(url, base_url, branch_spec, True)
765
- if self.svn_branch:
766
- break
767
- tag_spec = RunGit(
768
- ['config', 'svn-remote.%s.tags' % remote],
769
- error_ok=True).strip()
770
- if tag_spec:
771
- self.svn_branch = MatchSvnGlob(url, base_url, tag_spec, True)
772
- if self.svn_branch:
773
- break
774
-
775
- if not self.svn_branch:
776
- DieWithError('Can\'t guess svn branch -- try specifying it on the '
777
- 'command line')
778
-
779
- return self.svn_branch
780
-
781
801
  def GetTreeStatusUrl(self, error_ok=False):
782
802
  if not self.tree_status_url:
783
803
  error_message = ('You must configure your tree status URL by running '
@@ -869,18 +889,6 @@ class Settings(object):
869
889
  self.project = self._GetRietveldConfig('project', error_ok=True)
870
890
  return self.project
871
891
 
872
- def GetForceHttpsCommitUrl(self):
873
- if not self.force_https_commit_url:
874
- self.force_https_commit_url = self._GetRietveldConfig(
875
- 'force-https-commit-url', error_ok=True)
876
- return self.force_https_commit_url
877
-
878
- def GetPendingRefPrefix(self):
879
- if not self.pending_ref_prefix:
880
- self.pending_ref_prefix = self._GetRietveldConfig(
881
- 'pending-ref-prefix', error_ok=True)
882
- return self.pending_ref_prefix
883
-
884
892
  def _GetRietveldConfig(self, param, **kwargs):
885
893
  return self._GetConfig('rietveld.' + param, **kwargs)
886
894
 
@@ -892,6 +900,84 @@ class Settings(object):
892
900
  return RunGit(['config', param], **kwargs).strip()
893
901
 
894
902
 
903
+ @contextlib.contextmanager
904
+ def _get_gerrit_project_config_file(remote_url):
905
+ """Context manager to fetch and store Gerrit's project.config from
906
+ refs/meta/config branch and store it in temp file.
907
+
908
+ Provides a temporary filename or None if there was error.
909
+ """
910
+ error, _ = RunGitWithCode([
911
+ 'fetch', remote_url,
912
+ '+refs/meta/config:refs/git_cl/meta/config'])
913
+ if error:
914
+ # Ref doesn't exist or isn't accessible to current user.
915
+ print('WARNING: failed to fetch project config for %s: %s' %
916
+ (remote_url, error))
917
+ yield None
918
+ return
919
+
920
+ error, project_config_data = RunGitWithCode(
921
+ ['show', 'refs/git_cl/meta/config:project.config'])
922
+ if error:
923
+ print('WARNING: project.config file not found')
924
+ yield None
925
+ return
926
+
927
+ with gclient_utils.temporary_directory() as tempdir:
928
+ project_config_file = os.path.join(tempdir, 'project.config')
929
+ gclient_utils.FileWrite(project_config_file, project_config_data)
930
+ yield project_config_file
931
+
932
+
933
+ def _is_git_numberer_enabled(remote_url, remote_ref):
934
+ """Returns True if Git Numberer is enabled on this ref."""
935
+ # TODO(tandrii): this should be deleted once repos below are 100% on Gerrit.
936
+ KNOWN_PROJECTS_WHITELIST = [
937
+ 'chromium/src',
938
+ 'external/webrtc',
939
+ 'v8/v8',
940
+ ]
941
+
942
+ assert remote_ref and remote_ref.startswith('refs/'), remote_ref
943
+ url_parts = urlparse.urlparse(remote_url)
944
+ project_name = url_parts.path.lstrip('/').rstrip('git./')
945
+ for known in KNOWN_PROJECTS_WHITELIST:
946
+ if project_name.endswith(known):
947
+ break
948
+ else:
949
+ # Early exit to avoid extra fetches for repos that aren't using Git
950
+ # Numberer.
951
+ return False
952
+
953
+ with _get_gerrit_project_config_file(remote_url) as project_config_file:
954
+ if project_config_file is None:
955
+ # Failed to fetch project.config, which shouldn't happen on open source
956
+ # repos KNOWN_PROJECTS_WHITELIST.
957
+ return False
958
+ def get_opts(x):
959
+ code, out = RunGitWithCode(
960
+ ['config', '-f', project_config_file, '--get-all',
961
+ 'plugin.git-numberer.validate-%s-refglob' % x])
962
+ if code == 0:
963
+ return out.strip().splitlines()
964
+ return []
965
+ enabled, disabled = map(get_opts, ['enabled', 'disabled'])
966
+
967
+ logging.info('validator config enabled %s disabled %s refglobs for '
968
+ '(this ref: %s)', enabled, disabled, remote_ref)
969
+
970
+ def match_refglobs(refglobs):
971
+ for refglob in refglobs:
972
+ if remote_ref == refglob or fnmatch.fnmatch(remote_ref, refglob):
973
+ return True
974
+ return False
975
+
976
+ if match_refglobs(disabled):
977
+ return False
978
+ return match_refglobs(enabled)
979
+
980
+
895
981
  def ShortBranchName(branch):
896
982
  """Convert a name like 'refs/heads/foo' to just 'foo'."""
897
983
  return branch.replace('refs/heads/', '', 1)
@@ -934,12 +1020,6 @@ class _ParsedIssueNumberArgument(object):
934
1020
  return self.issue is not None
935
1021
 
936
1022
 
937
- class _RietveldParsedIssueNumberArgument(_ParsedIssueNumberArgument):
938
- def __init__(self, *args, **kwargs):
939
- self.patch_url = kwargs.pop('patch_url', None)
940
- super(_RietveldParsedIssueNumberArgument, self).__init__(*args, **kwargs)
941
-
942
-
943
1023
  def ParseIssueNumberArgument(arg):
944
1024
  """Parses the issue argument and returns _ParsedIssueNumberArgument."""
945
1025
  fail_result = _ParsedIssueNumberArgument()
@@ -960,6 +1040,17 @@ def ParseIssueNumberArgument(arg):
960
1040
  return fail_result
961
1041
 
962
1042
 
1043
+ class GerritChangeNotExists(Exception):
1044
+ def __init__(self, issue, url):
1045
+ self.issue = issue
1046
+ self.url = url
1047
+ super(GerritChangeNotExists, self).__init__()
1048
+
1049
+ def __str__(self):
1050
+ return 'change %s at %s does not exist or you have no access to it' % (
1051
+ self.issue, self.url)
1052
+
1053
+
963
1054
  class Changelist(object):
964
1055
  """Changelist works with one changelist in local branch.
965
1056
 
@@ -1120,28 +1211,19 @@ class Changelist(object):
1120
1211
  if upstream_branch:
1121
1212
  remote = RunGit(['config', 'rietveld.upstream-remote']).strip()
1122
1213
  else:
1123
- # Fall back on trying a git-svn upstream branch.
1124
- if settings.GetIsGitSvn():
1125
- upstream_branch = settings.GetSVNBranch()
1214
+ # Else, try to guess the origin remote.
1215
+ remote_branches = RunGit(['branch', '-r']).split()
1216
+ if 'origin/master' in remote_branches:
1217
+ # Fall back on origin/master if it exits.
1218
+ remote = 'origin'
1219
+ upstream_branch = 'refs/heads/master'
1126
1220
  else:
1127
- # Else, try to guess the origin remote.
1128
- remote_branches = RunGit(['branch', '-r']).split()
1129
- if 'origin/master' in remote_branches:
1130
- # Fall back on origin/master if it exits.
1131
- remote = 'origin'
1132
- upstream_branch = 'refs/heads/master'
1133
- elif 'origin/trunk' in remote_branches:
1134
- # Fall back on origin/trunk if it exists. Generally a shared
1135
- # git-svn clone
1136
- remote = 'origin'
1137
- upstream_branch = 'refs/heads/trunk'
1138
- else:
1139
- DieWithError(
1140
- 'Unable to determine default branch to diff against.\n'
1141
- 'Either pass complete "git diff"-style arguments, like\n'
1142
- ' git cl upload origin/master\n'
1143
- 'or verify this branch is set up to track another \n'
1144
- '(via the --track argument to "git checkout -b ...").')
1221
+ DieWithError(
1222
+ 'Unable to determine default branch to diff against.\n'
1223
+ 'Either pass complete "git diff"-style arguments, like\n'
1224
+ ' git cl upload origin/master\n'
1225
+ 'or verify this branch is set up to track another \n'
1226
+ '(via the --track argument to "git checkout -b ...").')
1145
1227
 
1146
1228
  return remote, upstream_branch
1147
1229
 
@@ -1180,17 +1262,11 @@ class Changelist(object):
1180
1262
  remote, = remotes
1181
1263
  elif 'origin' in remotes:
1182
1264
  remote = 'origin'
1183
- logging.warning('Could not determine which remote this change is '
1184
- 'associated with, so defaulting to "%s". This may '
1185
- 'not be what you want. You may prevent this message '
1186
- 'by running "git svn info" as documented here: %s',
1187
- self._remote,
1188
- GIT_INSTRUCTIONS_URL)
1265
+ logging.warn('Could not determine which remote this change is '
1266
+ 'associated with, so defaulting to "%s".' % self._remote)
1189
1267
  else:
1190
1268
  logging.warn('Could not determine which remote this change is '
1191
- 'associated with. You may prevent this message by '
1192
- 'running "git svn info" as documented here: %s',
1193
- GIT_INSTRUCTIONS_URL)
1269
+ 'associated with.')
1194
1270
  branch = 'HEAD'
1195
1271
  if branch.startswith('refs/remotes'):
1196
1272
  self._remote = (remote, branch)
@@ -1249,19 +1325,6 @@ class Changelist(object):
1249
1325
  """
1250
1326
  return self._GitGetBranchConfigValue('base-url')
1251
1327
 
1252
- def GetGitSvnRemoteUrl(self):
1253
- """Return the configured git-svn remote URL parsed from git svn info.
1254
-
1255
- Returns None if it is not set.
1256
- """
1257
- # URL is dependent on the current directory.
1258
- data = RunGit(['svn', 'info'], cwd=settings.GetRoot())
1259
- if data:
1260
- keys = dict(line.split(': ', 1) for line in data.splitlines()
1261
- if ': ' in line)
1262
- return keys.get('URL', None)
1263
- return None
1264
-
1265
1328
  def GetRemoteUrl(self):
1266
1329
  """Return the configured remote URL, e.g. 'git://example.org/foo.git/'.
1267
1330
 
@@ -1292,15 +1355,17 @@ class Changelist(object):
1292
1355
  return None
1293
1356
  return '%s/%s' % (self._codereview_impl.GetCodereviewServer(), issue)
1294
1357
 
1295
- def GetDescription(self, pretty=False):
1296
- if not self.has_description:
1358
+ def GetDescription(self, pretty=False, force=False):
1359
+ if not self.has_description or force:
1297
1360
  if self.GetIssue():
1298
- self.description = self._codereview_impl.FetchDescription()
1361
+ self.description = self._codereview_impl.FetchDescription(force=force)
1299
1362
  self.has_description = True
1300
1363
  if pretty:
1301
- wrapper = textwrap.TextWrapper()
1364
+ # Set width to 72 columns + 2 space indent.
1365
+ wrapper = textwrap.TextWrapper(width=74, replace_whitespace=True)
1302
1366
  wrapper.initial_indent = wrapper.subsequent_indent = ' '
1303
- return wrapper.fill(self.description)
1367
+ lines = self.description.splitlines()
1368
+ return '\n'.join([wrapper.fill(line) for line in lines])
1304
1369
  return self.description
1305
1370
 
1306
1371
  def GetPatchset(self):
@@ -1366,8 +1431,8 @@ class Changelist(object):
1366
1431
  ('\nFailed to diff against upstream branch %s\n\n'
1367
1432
  'This branch probably doesn\'t exist anymore. To reset the\n'
1368
1433
  'tracking branch, please run\n'
1369
- ' git branch --set-upstream %s trunk\n'
1370
- 'replacing trunk with origin/master or the relevant branch') %
1434
+ ' git branch --set-upstream-to origin/master %s\n'
1435
+ 'or replace origin/master with the relevant branch') %
1371
1436
  (upstream_branch, self.GetBranch()))
1372
1437
 
1373
1438
  issue = self.GetIssue()
@@ -1393,9 +1458,10 @@ class Changelist(object):
1393
1458
  author,
1394
1459
  upstream=upstream_branch)
1395
1460
 
1396
- def UpdateDescription(self, description):
1461
+ def UpdateDescription(self, description, force=False):
1462
+ self._codereview_impl.UpdateDescriptionRemote(description, force=force)
1397
1463
  self.description = description
1398
- return self._codereview_impl.UpdateDescriptionRemote(description)
1464
+ self.has_description = True
1399
1465
 
1400
1466
  def RunHook(self, committing, may_prompt, verbose, change):
1401
1467
  """Calls sys.exit() if the hook fails; returns a HookResults otherwise."""
@@ -1437,10 +1503,11 @@ class Changelist(object):
1437
1503
  base_branch = self.GetCommonAncestorWithUpstream()
1438
1504
  git_diff_args = [base_branch, 'HEAD']
1439
1505
 
1440
- # Make sure authenticated to codereview before running potentially expensive
1441
- # hooks. It is a fast, best efforts check. Codereview still can reject the
1442
- # authentication during the actual upload.
1506
+ # Fast best-effort checks to abort before running potentially
1507
+ # expensive hooks if uploading is likely to fail anyway. Passing these
1508
+ # checks does not guarantee that uploading will not fail.
1443
1509
  self._codereview_impl.EnsureAuthenticated(force=options.force)
1510
+ self._codereview_impl.EnsureCanUploadPatchset()
1444
1511
 
1445
1512
  # Apply watchlists on upload.
1446
1513
  change = self.GetChange(base_branch, None)
@@ -1466,7 +1533,9 @@ class Changelist(object):
1466
1533
  if not options.reviewers and hook_results.reviewers:
1467
1534
  options.reviewers = hook_results.reviewers.split(',')
1468
1535
 
1469
- if self.GetIssue():
1536
+ # TODO(tandrii): Checking local patchset against remote patchset is only
1537
+ # supported for Rietveld. Extend it to Gerrit or remove it completely.
1538
+ if self.GetIssue() and not self.IsGerrit():
1470
1539
  latest_patchset = self.GetMostRecentPatchset()
1471
1540
  local_patchset = self.GetPatchset()
1472
1541
  if (latest_patchset and local_patchset and
@@ -1519,6 +1588,31 @@ class Changelist(object):
1519
1588
  assert self.GetIssue()
1520
1589
  return self._codereview_impl.SetCQState(new_state)
1521
1590
 
1591
+ def TriggerDryRun(self):
1592
+ """Triggers a dry run and prints a warning on failure."""
1593
+ # TODO(qyearsley): Either re-use this method in CMDset_commit
1594
+ # and CMDupload, or change CMDtry to trigger dry runs with
1595
+ # just SetCQState, and catch keyboard interrupt and other
1596
+ # errors in that method.
1597
+ try:
1598
+ self.SetCQState(_CQState.DRY_RUN)
1599
+ print('scheduled CQ Dry Run on %s' % self.GetIssueURL())
1600
+ return 0
1601
+ except KeyboardInterrupt:
1602
+ raise
1603
+ except:
1604
+ print('WARNING: failed to trigger CQ Dry Run.\n'
1605
+ 'Either:\n'
1606
+ ' * your project has no CQ\n'
1607
+ ' * you don\'t have permission to trigger Dry Run\n'
1608
+ ' * bug in this code (see stack trace below).\n'
1609
+ 'Consider specifying which bots to trigger manually '
1610
+ 'or asking your project owners for permissions '
1611
+ 'or contacting Chrome Infrastructure team at '
1612
+ 'https://www.chromium.org/infra\n\n')
1613
+ # Still raise exception so that stack trace is printed.
1614
+ raise
1615
+
1522
1616
  # Forward methods to codereview specific implementation.
1523
1617
 
1524
1618
  def CloseIssue(self):
@@ -1530,12 +1624,24 @@ class Changelist(object):
1530
1624
  def GetCodereviewServer(self):
1531
1625
  return self._codereview_impl.GetCodereviewServer()
1532
1626
 
1627
+ def GetIssueOwner(self):
1628
+ """Get owner from codereview, which may differ from this checkout."""
1629
+ return self._codereview_impl.GetIssueOwner()
1630
+
1533
1631
  def GetApprovingReviewers(self):
1534
1632
  return self._codereview_impl.GetApprovingReviewers()
1535
1633
 
1536
1634
  def GetMostRecentPatchset(self):
1537
1635
  return self._codereview_impl.GetMostRecentPatchset()
1538
1636
 
1637
+ def CannotTriggerTryJobReason(self):
1638
+ """Returns reason (str) if unable trigger tryjobs on this CL or None."""
1639
+ return self._codereview_impl.CannotTriggerTryJobReason()
1640
+
1641
+ def GetTryjobProperties(self, patchset=None):
1642
+ """Returns dictionary of properties to launch tryjob."""
1643
+ return self._codereview_impl.GetTryjobProperties(patchset=patchset)
1644
+
1539
1645
  def __getattr__(self, attr):
1540
1646
  # This is because lots of untested code accesses Rietveld-specific stuff
1541
1647
  # directly, and it's hard to fix for sure. So, just let it work, and fix
@@ -1571,7 +1677,7 @@ class _ChangelistCodereviewBase(object):
1571
1677
  """Returns server URL without end slash, like "https://codereview.com"."""
1572
1678
  raise NotImplementedError()
1573
1679
 
1574
- def FetchDescription(self):
1680
+ def FetchDescription(self, force=False):
1575
1681
  """Fetches and returns description from the codereview server."""
1576
1682
  raise NotImplementedError()
1577
1683
 
@@ -1603,7 +1709,7 @@ class _ChangelistCodereviewBase(object):
1603
1709
  # None is valid return value, otherwise presubmit_support.GerritAccessor.
1604
1710
  return None
1605
1711
 
1606
- def UpdateDescriptionRemote(self, description):
1712
+ def UpdateDescriptionRemote(self, description, force=False):
1607
1713
  """Update the description on codereview site."""
1608
1714
  raise NotImplementedError()
1609
1715
 
@@ -1642,14 +1748,25 @@ class _ChangelistCodereviewBase(object):
1642
1748
  failed."""
1643
1749
  raise NotImplementedError()
1644
1750
 
1645
- def EnsureAuthenticated(self, force):
1751
+ def EnsureAuthenticated(self, force, refresh=False):
1646
1752
  """Best effort check that user is authenticated with codereview server.
1647
1753
 
1648
1754
  Arguments:
1649
1755
  force: whether to skip confirmation questions.
1756
+ refresh: whether to attempt to refresh credentials. Ignored if not
1757
+ applicable.
1650
1758
  """
1651
1759
  raise NotImplementedError()
1652
1760
 
1761
+ def EnsureCanUploadPatchset(self):
1762
+ """Best effort check that uploading isn't supposed to fail for predictable
1763
+ reasons.
1764
+
1765
+ This method should raise informative exception if uploading shouldn't
1766
+ proceed.
1767
+ """
1768
+ pass
1769
+
1653
1770
  def CMDUploadChange(self, options, args, change):
1654
1771
  """Uploads a change to codereview."""
1655
1772
  raise NotImplementedError()
@@ -1661,16 +1778,26 @@ class _ChangelistCodereviewBase(object):
1661
1778
  """
1662
1779
  raise NotImplementedError()
1663
1780
 
1781
+ def CannotTriggerTryJobReason(self):
1782
+ """Returns reason (str) if unable trigger tryjobs on this CL or None."""
1783
+ raise NotImplementedError()
1784
+
1785
+ def GetIssueOwner(self):
1786
+ raise NotImplementedError()
1787
+
1788
+ def GetTryjobProperties(self, patchset=None):
1789
+ raise NotImplementedError()
1790
+
1664
1791
 
1665
1792
  class _RietveldChangelistImpl(_ChangelistCodereviewBase):
1666
- def __init__(self, changelist, auth_config=None, rietveld_server=None):
1793
+ def __init__(self, changelist, auth_config=None, codereview_host=None):
1667
1794
  super(_RietveldChangelistImpl, self).__init__(changelist)
1668
1795
  assert settings, 'must be initialized in _ChangelistCodereviewBase'
1669
- if not rietveld_server:
1796
+ if not codereview_host:
1670
1797
  settings.GetDefaultServerUrl()
1671
1798
 
1672
- self._rietveld_server = rietveld_server
1673
- self._auth_config = auth_config
1799
+ self._rietveld_server = codereview_host
1800
+ self._auth_config = auth_config or auth.make_auth_config()
1674
1801
  self._props = None
1675
1802
  self._rpc_server = None
1676
1803
 
@@ -1685,19 +1812,21 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
1685
1812
  self._rietveld_server = settings.GetDefaultServerUrl()
1686
1813
  return self._rietveld_server
1687
1814
 
1688
- def EnsureAuthenticated(self, force):
1815
+ def EnsureAuthenticated(self, force, refresh=False):
1689
1816
  """Best effort check that user is authenticated with Rietveld server."""
1690
1817
  if self._auth_config.use_oauth2:
1691
1818
  authenticator = auth.get_authenticator_for_host(
1692
1819
  self.GetCodereviewServer(), self._auth_config)
1693
1820
  if not authenticator.has_cached_credentials():
1694
1821
  raise auth.LoginRequiredError(self.GetCodereviewServer())
1822
+ if refresh:
1823
+ authenticator.get_access_token()
1695
1824
 
1696
- def FetchDescription(self):
1825
+ def FetchDescription(self, force=False):
1697
1826
  issue = self.GetIssue()
1698
1827
  assert issue
1699
1828
  try:
1700
- return self.RpcServer().get_description(issue).strip()
1829
+ return self.RpcServer().get_description(issue, force=force).strip()
1701
1830
  except urllib2.HTTPError as e:
1702
1831
  if e.code == 404:
1703
1832
  DieWithError(
@@ -1720,10 +1849,6 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
1720
1849
  def GetMostRecentPatchset(self):
1721
1850
  return self.GetIssueProperties()['patchsets'][-1]
1722
1851
 
1723
- def GetPatchSetDiff(self, issue, patchset):
1724
- return self.RpcServer().get(
1725
- '/download/issue%s_%s.diff' % (issue, patchset))
1726
-
1727
1852
  def GetIssueProperties(self):
1728
1853
  if self._props is None:
1729
1854
  issue = self.GetIssue()
@@ -1733,9 +1858,33 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
1733
1858
  self._props = self.RpcServer().get_issue_properties(issue, True)
1734
1859
  return self._props
1735
1860
 
1861
+ def CannotTriggerTryJobReason(self):
1862
+ props = self.GetIssueProperties()
1863
+ if not props:
1864
+ return 'Rietveld doesn\'t know about your issue %s' % self.GetIssue()
1865
+ if props.get('closed'):
1866
+ return 'CL %s is closed' % self.GetIssue()
1867
+ if props.get('private'):
1868
+ return 'CL %s is private' % self.GetIssue()
1869
+ return None
1870
+
1871
+ def GetTryjobProperties(self, patchset=None):
1872
+ """Returns dictionary of properties to launch tryjob."""
1873
+ project = (self.GetIssueProperties() or {}).get('project')
1874
+ return {
1875
+ 'issue': self.GetIssue(),
1876
+ 'patch_project': project,
1877
+ 'patch_storage': 'rietveld',
1878
+ 'patchset': patchset or self.GetPatchset(),
1879
+ 'rietveld': self.GetCodereviewServer(),
1880
+ }
1881
+
1736
1882
  def GetApprovingReviewers(self):
1737
1883
  return get_approving_reviewers(self.GetIssueProperties())
1738
1884
 
1885
+ def GetIssueOwner(self):
1886
+ return (self.GetIssueProperties() or {}).get('owner_email')
1887
+
1739
1888
  def AddComment(self, message):
1740
1889
  return self.RpcServer().add_comment(self.GetIssue(), message)
1741
1890
 
@@ -1798,9 +1947,8 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
1798
1947
  return 'reply'
1799
1948
  return 'waiting'
1800
1949
 
1801
- def UpdateDescriptionRemote(self, description):
1802
- return self.RpcServer().update_description(
1803
- self.GetIssue(), self.description)
1950
+ def UpdateDescriptionRemote(self, description, force=False):
1951
+ self.RpcServer().update_description(self.GetIssue(), description)
1804
1952
 
1805
1953
  def CloseIssue(self):
1806
1954
  return self.RpcServer().close_issue(self.GetIssue())
@@ -1830,7 +1978,7 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
1830
1978
  if not self._rpc_server:
1831
1979
  self._rpc_server = rietveld.CachingRietveld(
1832
1980
  self.GetCodereviewServer(),
1833
- self._auth_config or auth.make_auth_config())
1981
+ self._auth_config)
1834
1982
  return self._rpc_server
1835
1983
 
1836
1984
  @classmethod
@@ -1861,11 +2009,8 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
1861
2009
  assert new_state == _CQState.DRY_RUN
1862
2010
  self.SetFlags({'commit': '1', 'cq_dry_run': '1'})
1863
2011
 
1864
-
1865
2012
  def CMDPatchWithParsedIssue(self, parsed_issue_arg, reject, nocommit,
1866
2013
  directory):
1867
- # TODO(maruel): Use apply_issue.py
1868
-
1869
2014
  # PatchIssue should never be called with a dirty tree. It is up to the
1870
2015
  # caller to check this, but just in case we assert here since the
1871
2016
  # consequences of the caller not checking this could be dire.
@@ -1875,47 +2020,13 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
1875
2020
  if parsed_issue_arg.hostname:
1876
2021
  self._rietveld_server = 'https://%s' % parsed_issue_arg.hostname
1877
2022
 
1878
- if (isinstance(parsed_issue_arg, _RietveldParsedIssueNumberArgument) and
1879
- parsed_issue_arg.patch_url):
1880
- assert parsed_issue_arg.patchset
1881
- patchset = parsed_issue_arg.patchset
1882
- patch_data = urllib2.urlopen(parsed_issue_arg.patch_url).read()
1883
- else:
1884
- patchset = parsed_issue_arg.patchset or self.GetMostRecentPatchset()
1885
- patch_data = self.GetPatchSetDiff(self.GetIssue(), patchset)
1886
-
1887
- # Switch up to the top-level directory, if necessary, in preparation for
1888
- # applying the patch.
1889
- top = settings.GetRelativeRoot()
1890
- if top:
1891
- os.chdir(top)
1892
-
1893
- # Git patches have a/ at the beginning of source paths. We strip that out
1894
- # with a sed script rather than the -p flag to patch so we can feed either
1895
- # Git or svn-style patches into the same apply command.
1896
- # re.sub() should be used but flags=re.MULTILINE is only in python 2.7.
2023
+ patchset = parsed_issue_arg.patchset or self.GetMostRecentPatchset()
2024
+ patchset_object = self.RpcServer().get_patch(self.GetIssue(), patchset)
2025
+ scm_obj = checkout.GitCheckout(settings.GetRoot(), None, None, None, None)
1897
2026
  try:
1898
- patch_data = subprocess2.check_output(
1899
- ['sed', '-e', 's|^--- a/|--- |; s|^+++ b/|+++ |'], stdin=patch_data)
1900
- except subprocess2.CalledProcessError:
1901
- DieWithError('Git patch mungling failed.')
1902
- logging.info(patch_data)
1903
-
1904
- # We use "git apply" to apply the patch instead of "patch" so that we can
1905
- # pick up file adds.
1906
- # The --index flag means: also insert into the index (so we catch adds).
1907
- cmd = ['git', 'apply', '--index', '-p0']
1908
- if directory:
1909
- cmd.extend(('--directory', directory))
1910
- if reject:
1911
- cmd.append('--reject')
1912
- elif IsGitVersionAtLeast('1.7.12'):
1913
- cmd.append('--3way')
1914
- try:
1915
- subprocess2.check_call(cmd, env=GetNoGitPagerEnv(),
1916
- stdin=patch_data, stdout=subprocess2.VOID)
1917
- except subprocess2.CalledProcessError:
1918
- print('Failed to apply the patch')
2027
+ scm_obj.apply_patch(patchset_object)
2028
+ except Exception as e:
2029
+ print(str(e))
1919
2030
  return 1
1920
2031
 
1921
2032
  # If we had an issue, commit the current state and register the issue.
@@ -1939,24 +2050,23 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
1939
2050
  match = re.match(r'/(\d+)/$', parsed_url.path)
1940
2051
  match2 = re.match(r'ps(\d+)$', parsed_url.fragment)
1941
2052
  if match and match2:
1942
- return _RietveldParsedIssueNumberArgument(
2053
+ return _ParsedIssueNumberArgument(
1943
2054
  issue=int(match.group(1)),
1944
2055
  patchset=int(match2.group(1)),
1945
2056
  hostname=parsed_url.netloc)
1946
2057
  # Typical url: https://domain/<issue_number>[/[other]]
1947
2058
  match = re.match('/(\d+)(/.*)?$', parsed_url.path)
1948
2059
  if match:
1949
- return _RietveldParsedIssueNumberArgument(
2060
+ return _ParsedIssueNumberArgument(
1950
2061
  issue=int(match.group(1)),
1951
2062
  hostname=parsed_url.netloc)
1952
2063
  # Rietveld patch: https://domain/download/issue<number>_<patchset>.diff
1953
2064
  match = re.match(r'/download/issue(\d+)_(\d+).diff$', parsed_url.path)
1954
2065
  if match:
1955
- return _RietveldParsedIssueNumberArgument(
2066
+ return _ParsedIssueNumberArgument(
1956
2067
  issue=int(match.group(1)),
1957
2068
  patchset=int(match.group(2)),
1958
- hostname=parsed_url.netloc,
1959
- patch_url=gclient_utils.UpgradeToHttps(parsed_url.geturl()))
2069
+ hostname=parsed_url.netloc)
1960
2070
  return None
1961
2071
 
1962
2072
  def CMDUploadChange(self, options, args, change):
@@ -1983,8 +2093,12 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
1983
2093
  else:
1984
2094
  if options.title is not None:
1985
2095
  upload_args.extend(['--title', options.title])
1986
- message = (options.title or options.message or
1987
- CreateDescriptionFromLog(args))
2096
+ if options.message:
2097
+ message = options.message
2098
+ else:
2099
+ message = CreateDescriptionFromLog(args)
2100
+ if options.title:
2101
+ message = options.title + '\n\n' + message
1988
2102
  change_desc = ChangeDescription(message)
1989
2103
  if options.reviewers or options.tbr_owners:
1990
2104
  change_desc.update_reviewers(options.reviewers,
@@ -2003,7 +2117,7 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
2003
2117
  change_desc.get_reviewers()))
2004
2118
  if options.send_mail:
2005
2119
  if not change_desc.get_reviewers():
2006
- DieWithError("Must specify reviewers to send email.")
2120
+ DieWithError("Must specify reviewers to send email.", change_desc)
2007
2121
  upload_args.append('--send_mail')
2008
2122
 
2009
2123
  # We check this before applying rietveld.private assuming that in
@@ -2017,6 +2131,8 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
2017
2131
  else:
2018
2132
  cc = self.GetCCList()
2019
2133
  cc = ','.join(filter(None, (cc, ','.join(options.cc))))
2134
+ if change_desc.get_cced():
2135
+ cc = ','.join(filter(None, (cc, ','.join(change_desc.get_cced()))))
2020
2136
  if cc:
2021
2137
  upload_args.extend(['--cc', cc])
2022
2138
 
@@ -2031,16 +2147,12 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
2031
2147
  # projects that have their source spread across multiple repos.
2032
2148
  remote_url = self.GetGitBaseUrlFromConfig()
2033
2149
  if not remote_url:
2034
- if settings.GetIsGitSvn():
2035
- remote_url = self.GetGitSvnRemoteUrl()
2036
- else:
2037
- if self.GetRemoteUrl() and '/' in self.GetUpstreamBranch():
2038
- remote_url = '%s@%s' % (self.GetRemoteUrl(),
2039
- self.GetUpstreamBranch().split('/')[-1])
2150
+ if self.GetRemoteUrl() and '/' in self.GetUpstreamBranch():
2151
+ remote_url = '%s@%s' % (self.GetRemoteUrl(),
2152
+ self.GetUpstreamBranch().split('/')[-1])
2040
2153
  if remote_url:
2041
2154
  remote, remote_branch = self.GetRemoteBranch()
2042
- target_ref = GetTargetRef(remote, remote_branch, options.target_branch,
2043
- settings.GetPendingRefPrefix())
2155
+ target_ref = GetTargetRef(remote, remote_branch, options.target_branch)
2044
2156
  if target_ref:
2045
2157
  upload_args.extend(['--target_ref', target_ref])
2046
2158
 
@@ -2090,12 +2202,7 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
2090
2202
  # If we got an exception after the user typed a description for their
2091
2203
  # change, back up the description before re-raising.
2092
2204
  if change_desc:
2093
- backup_path = os.path.expanduser(DESCRIPTION_BACKUP_FILE)
2094
- print('\nGot exception while uploading -- saving description to %s\n' %
2095
- backup_path)
2096
- backup_file = open(backup_path, 'w')
2097
- backup_file.write(change_desc.description)
2098
- backup_file.close()
2205
+ SaveDescriptionBackup(change_desc)
2099
2206
  raise
2100
2207
 
2101
2208
  if not self.GetIssue():
@@ -2105,13 +2212,20 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
2105
2212
 
2106
2213
 
2107
2214
  class _GerritChangelistImpl(_ChangelistCodereviewBase):
2108
- def __init__(self, changelist, auth_config=None):
2215
+ def __init__(self, changelist, auth_config=None, codereview_host=None):
2109
2216
  # auth_config is Rietveld thing, kept here to preserve interface only.
2110
2217
  super(_GerritChangelistImpl, self).__init__(changelist)
2111
2218
  self._change_id = None
2112
2219
  # Lazily cached values.
2113
- self._gerrit_server = None # e.g. https://chromium-review.googlesource.com
2114
2220
  self._gerrit_host = None # e.g. chromium-review.googlesource.com
2221
+ self._gerrit_server = None # e.g. https://chromium-review.googlesource.com
2222
+ # Map from change number (issue) to its detail cache.
2223
+ self._detail_cache = {}
2224
+
2225
+ if codereview_host is not None:
2226
+ assert not codereview_host.startswith('https://'), codereview_host
2227
+ self._gerrit_host = codereview_host
2228
+ self._gerrit_server = 'https://%s' % codereview_host
2115
2229
 
2116
2230
  def _GetGerritHost(self):
2117
2231
  # Lazy load of configs.
@@ -2161,7 +2275,7 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2161
2275
  def CodereviewServerConfigKey(cls):
2162
2276
  return 'gerritserver'
2163
2277
 
2164
- def EnsureAuthenticated(self, force):
2278
+ def EnsureAuthenticated(self, force, refresh=None):
2165
2279
  """Best effort check that user is authenticated with Gerrit server."""
2166
2280
  if settings.GetGerritSkipEnsureAuthenticated():
2167
2281
  # For projects with unusual authentication schemes.
@@ -2206,6 +2320,26 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2206
2320
  cookie_auth.get_netrc_path(),
2207
2321
  cookie_auth.get_new_password_message(git_host)))
2208
2322
 
2323
+ def EnsureCanUploadPatchset(self):
2324
+ """Best effort check that uploading isn't supposed to fail for predictable
2325
+ reasons.
2326
+
2327
+ This method should raise informative exception if uploading shouldn't
2328
+ proceed.
2329
+ """
2330
+ if not self.GetIssue():
2331
+ return
2332
+
2333
+ # Warm change details cache now to avoid RPCs later, reducing latency for
2334
+ # developers.
2335
+ self.FetchDescription()
2336
+
2337
+ status = self._GetChangeDetail()['status']
2338
+ if status in ('MERGED', 'ABANDONED'):
2339
+ DieWithError('Change %s has been %s, new uploads are not allowed' %
2340
+ (self.GetIssueURL(),
2341
+ 'submitted' if status == 'MERGED' else 'abandoned'))
2342
+
2209
2343
  def _PostUnsetIssueProperties(self):
2210
2344
  """Which branch-specific properties to erase when unsetting issue."""
2211
2345
  return ['gerritsquashhash']
@@ -2240,8 +2374,8 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2240
2374
  * 'unsent' - no reviewers added
2241
2375
  * 'waiting' - waiting for review
2242
2376
  * 'reply' - waiting for owner to reply to review
2243
- * 'not lgtm' - Code-Review -2 from at least one approved reviewer
2244
- * 'lgtm' - Code-Review +2 from at least one approved reviewer
2377
+ * 'not lgtm' - Code-Review disapproval from at least one valid reviewer
2378
+ * 'lgtm' - Code-Review approval from at least one valid reviewer
2245
2379
  * 'commit' - in the commit queue
2246
2380
  * 'closed' - abandoned
2247
2381
  """
@@ -2250,7 +2384,7 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2250
2384
 
2251
2385
  try:
2252
2386
  data = self._GetChangeDetail(['DETAILED_LABELS', 'CURRENT_REVISION'])
2253
- except httplib.HTTPException:
2387
+ except (httplib.HTTPException, GerritChangeNotExists):
2254
2388
  return 'error'
2255
2389
 
2256
2390
  if data['status'] in ('ABANDONED', 'MERGED'):
@@ -2258,8 +2392,15 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2258
2392
 
2259
2393
  cq_label = data['labels'].get('Commit-Queue', {})
2260
2394
  if cq_label:
2261
- # Vote value is a stringified integer, which we expect from 0 to 2.
2262
- vote_value = cq_label.get('value', '0')
2395
+ votes = cq_label.get('all', [])
2396
+ highest_vote = 0
2397
+ for v in votes:
2398
+ highest_vote = max(highest_vote, v.get('value', 0))
2399
+ vote_value = str(highest_vote)
2400
+ if vote_value != '0':
2401
+ # Add a '+' if the value is not 0 to match the values in the label.
2402
+ # The cq_label does not have negatives.
2403
+ vote_value = '+' + vote_value
2263
2404
  vote_text = cq_label.get('values', {}).get(vote_value, '')
2264
2405
  if vote_text.lower() == 'commit':
2265
2406
  return 'commit'
@@ -2275,28 +2416,40 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2275
2416
  return 'unsent'
2276
2417
 
2277
2418
  messages = data.get('messages', [])
2278
- if messages:
2279
- owner = data['owner'].get('_account_id')
2280
- last_message_author = messages[-1].get('author', {}).get('_account_id')
2281
- if owner != last_message_author:
2419
+ owner = data['owner'].get('_account_id')
2420
+ while messages:
2421
+ last_message_author = messages.pop().get('author', {})
2422
+ if last_message_author.get('email') == COMMIT_BOT_EMAIL:
2423
+ # Ignore replies from CQ.
2424
+ continue
2425
+ if last_message_author.get('_account_id') != owner:
2282
2426
  # Some reply from non-owner.
2283
2427
  return 'reply'
2284
-
2285
2428
  return 'waiting'
2286
2429
 
2287
2430
  def GetMostRecentPatchset(self):
2288
2431
  data = self._GetChangeDetail(['CURRENT_REVISION'])
2289
2432
  return data['revisions'][data['current_revision']]['_number']
2290
2433
 
2291
- def FetchDescription(self):
2292
- data = self._GetChangeDetail(['CURRENT_REVISION'])
2434
+ def FetchDescription(self, force=False):
2435
+ data = self._GetChangeDetail(['CURRENT_REVISION', 'CURRENT_COMMIT'],
2436
+ no_cache=force)
2293
2437
  current_rev = data['current_revision']
2294
- url = data['revisions'][current_rev]['fetch']['http']['url']
2295
- return gerrit_util.GetChangeDescriptionFromGitiles(url, current_rev)
2438
+ return data['revisions'][current_rev]['commit']['message']
2439
+
2440
+ def UpdateDescriptionRemote(self, description, force=False):
2441
+ if gerrit_util.HasPendingChangeEdit(self._GetGerritHost(), self.GetIssue()):
2442
+ if not force:
2443
+ ask_for_data(
2444
+ 'The description cannot be modified while the issue has a pending '
2445
+ 'unpublished edit. Either publish the edit in the Gerrit web UI '
2446
+ 'or delete it.\n\n'
2447
+ 'Press Enter to delete the unpublished edit, Ctrl+C to abort.')
2296
2448
 
2297
- def UpdateDescriptionRemote(self, description):
2449
+ gerrit_util.DeletePendingChangeEdit(self._GetGerritHost(),
2450
+ self.GetIssue())
2298
2451
  gerrit_util.SetCommitMessage(self._GetGerritHost(), self.GetIssue(),
2299
- description)
2452
+ description, notify='NONE')
2300
2453
 
2301
2454
  def CloseIssue(self):
2302
2455
  gerrit_util.AbandonChange(self._GetGerritHost(), self.GetIssue(), msg='')
@@ -2312,12 +2465,59 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2312
2465
  gerrit_util.SubmitChange(self._GetGerritHost(), self.GetIssue(),
2313
2466
  wait_for_merge=wait_for_merge)
2314
2467
 
2315
- def _GetChangeDetail(self, options=None, issue=None):
2468
+ def _GetChangeDetail(self, options=None, issue=None,
2469
+ no_cache=False):
2470
+ """Returns details of the issue by querying Gerrit and caching results.
2471
+
2472
+ If fresh data is needed, set no_cache=True which will clear cache and
2473
+ thus new data will be fetched from Gerrit.
2474
+ """
2316
2475
  options = options or []
2317
2476
  issue = issue or self.GetIssue()
2318
- assert issue, 'issue required to query Gerrit'
2319
- return gerrit_util.GetChangeDetail(self._GetGerritHost(), str(issue),
2320
- options)
2477
+ assert issue, 'issue is required to query Gerrit'
2478
+
2479
+ # Optimization to avoid multiple RPCs:
2480
+ if (('CURRENT_REVISION' in options or 'ALL_REVISIONS' in options) and
2481
+ 'CURRENT_COMMIT' not in options):
2482
+ options.append('CURRENT_COMMIT')
2483
+
2484
+ # Normalize issue and options for consistent keys in cache.
2485
+ issue = str(issue)
2486
+ options = [o.upper() for o in options]
2487
+
2488
+ # Check in cache first unless no_cache is True.
2489
+ if no_cache:
2490
+ self._detail_cache.pop(issue, None)
2491
+ else:
2492
+ options_set = frozenset(options)
2493
+ for cached_options_set, data in self._detail_cache.get(issue, []):
2494
+ # Assumption: data fetched before with extra options is suitable
2495
+ # for return for a smaller set of options.
2496
+ # For example, if we cached data for
2497
+ # options=[CURRENT_REVISION, DETAILED_FOOTERS]
2498
+ # and request is for options=[CURRENT_REVISION],
2499
+ # THEN we can return prior cached data.
2500
+ if options_set.issubset(cached_options_set):
2501
+ return data
2502
+
2503
+ try:
2504
+ data = gerrit_util.GetChangeDetail(self._GetGerritHost(), str(issue),
2505
+ options, ignore_404=False)
2506
+ except gerrit_util.GerritError as e:
2507
+ if e.http_status == 404:
2508
+ raise GerritChangeNotExists(issue, self.GetCodereviewServer())
2509
+ raise
2510
+
2511
+ self._detail_cache.setdefault(issue, []).append((frozenset(options), data))
2512
+ return data
2513
+
2514
+ def _GetChangeCommit(self, issue=None):
2515
+ issue = issue or self.GetIssue()
2516
+ assert issue, 'issue is required to query Gerrit'
2517
+ data = gerrit_util.GetChangeCommit(self._GetGerritHost(), str(issue))
2518
+ if not data:
2519
+ raise GerritChangeNotExists(issue, self.GetCodereviewServer())
2520
+ return data
2321
2521
 
2322
2522
  def CMDLand(self, force, bypass_hooks, verbose):
2323
2523
  if git_common.is_dirty_git_tree('land'):
@@ -2344,7 +2544,8 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2344
2544
  if differs:
2345
2545
  if not force:
2346
2546
  ask_for_data(
2347
- 'Do you want to submit latest Gerrit patchset and bypass hooks?')
2547
+ 'Do you want to submit latest Gerrit patchset and bypass hooks?\n'
2548
+ 'Press Enter to continue, Ctrl+C to abort.')
2348
2549
  print('WARNING: bypassing hooks and submitting latest uploaded patchset')
2349
2550
  elif not bypass_hooks:
2350
2551
  hook_results = self.RunHook(
@@ -2357,6 +2558,11 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2357
2558
 
2358
2559
  self.SubmitIssue(wait_for_merge=True)
2359
2560
  print('Issue %s has been submitted.' % self.GetIssueURL())
2561
+ links = self._GetChangeCommit().get('web_links', [])
2562
+ for link in links:
2563
+ if link.get('name') == 'gitiles' and link.get('url'):
2564
+ print('Landed as %s' % link.get('url'))
2565
+ break
2360
2566
  return 0
2361
2567
 
2362
2568
  def CMDPatchWithParsedIssue(self, parsed_issue_arg, reject, nocommit,
@@ -2372,7 +2578,10 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2372
2578
  self._gerrit_host = parsed_issue_arg.hostname
2373
2579
  self._gerrit_server = 'https://%s' % self._gerrit_host
2374
2580
 
2375
- detail = self._GetChangeDetail(['ALL_REVISIONS'])
2581
+ try:
2582
+ detail = self._GetChangeDetail(['ALL_REVISIONS'])
2583
+ except GerritChangeNotExists as e:
2584
+ DieWithError(str(e))
2376
2585
 
2377
2586
  if not parsed_issue_arg.patchset:
2378
2587
  # Use current revision by default.
@@ -2384,7 +2593,7 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2384
2593
  if int(revision_info['_number']) == parsed_issue_arg.patchset:
2385
2594
  break
2386
2595
  else:
2387
- DieWithError('Couldn\'t find patchset %i in issue %i' %
2596
+ DieWithError('Couldn\'t find patchset %i in change %i' %
2388
2597
  (parsed_issue_arg.patchset, self.GetIssue()))
2389
2598
 
2390
2599
  fetch_info = revision_info['fetch']['http']
@@ -2392,7 +2601,7 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2392
2601
  RunGit(['cherry-pick', 'FETCH_HEAD'])
2393
2602
  self.SetIssue(self.GetIssue())
2394
2603
  self.SetPatchset(patchset)
2395
- print('Committed patch for issue %i pathset %i locally' %
2604
+ print('Committed patch for change %i patchset %i locally' %
2396
2605
  (self.GetIssue(), self.GetPatchset()))
2397
2606
  return 0
2398
2607
 
@@ -2453,8 +2662,11 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2453
2662
  gerrit_remote = 'origin'
2454
2663
 
2455
2664
  remote, remote_branch = self.GetRemoteBranch()
2456
- branch = GetTargetRef(remote, remote_branch, options.target_branch,
2457
- pending_prefix='')
2665
+ branch = GetTargetRef(remote, remote_branch, options.target_branch)
2666
+
2667
+ # This may be None; default fallback value is determined in logic below.
2668
+ title = options.title
2669
+ automatic_title = False
2458
2670
 
2459
2671
  if options.squash:
2460
2672
  self._GerritCommitMsgHookCheck(offer_removal=not options.force)
@@ -2463,8 +2675,14 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2463
2675
  message = self.GetDescription()
2464
2676
  if not message:
2465
2677
  DieWithError(
2466
- 'failed to fetch description from current Gerrit issue %d\n'
2678
+ 'failed to fetch description from current Gerrit change %d\n'
2467
2679
  '%s' % (self.GetIssue(), self.GetIssueURL()))
2680
+ if not title:
2681
+ default_title = RunGit(['show', '-s', '--format=%s', 'HEAD']).strip()
2682
+ title = ask_for_data(
2683
+ 'Title for patchset [%s]: ' % default_title) or default_title
2684
+ if title == default_title:
2685
+ automatic_title = True
2468
2686
  change_id = self._GetChangeDetail()['change_id']
2469
2687
  while True:
2470
2688
  footer_change_ids = git_footers.get_footer_change_id(message)
@@ -2472,7 +2690,7 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2472
2690
  break
2473
2691
  if not footer_change_ids:
2474
2692
  message = git_footers.add_footer_change_id(message, change_id)
2475
- print('WARNING: appended missing Change-Id to issue description')
2693
+ print('WARNING: appended missing Change-Id to change description')
2476
2694
  continue
2477
2695
  # There is already a valid footer but with different or several ids.
2478
2696
  # Doing this automatically is non-trivial as we don't want to lose
@@ -2481,9 +2699,9 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2481
2699
  # new description.
2482
2700
  message = '%s\n\nChange-Id: %s' % (message, change_id)
2483
2701
  print(
2484
- 'WARNING: issue %s has Change-Id footer(s):\n'
2702
+ 'WARNING: change %s has Change-Id footer(s):\n'
2485
2703
  ' %s\n'
2486
- 'but issue has Change-Id %s, according to Gerrit.\n'
2704
+ 'but change has Change-Id %s, according to Gerrit.\n'
2487
2705
  'Please, check the proposed correction to the description, '
2488
2706
  'and edit it if necessary but keep the "Change-Id: %s" footer\n'
2489
2707
  % (self.GetIssue(), '\n '.join(footer_change_ids), change_id,
@@ -2500,11 +2718,20 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2500
2718
  # footer.
2501
2719
  assert [change_id] == git_footers.get_footer_change_id(message)
2502
2720
  change_desc = ChangeDescription(message)
2503
- else:
2504
- change_desc = ChangeDescription(
2505
- options.message or CreateDescriptionFromLog(args))
2721
+ else: # if not self.GetIssue()
2722
+ if options.message:
2723
+ message = options.message
2724
+ else:
2725
+ message = CreateDescriptionFromLog(args)
2726
+ if options.title:
2727
+ message = options.title + '\n\n' + message
2728
+ change_desc = ChangeDescription(message)
2506
2729
  if not options.force:
2507
2730
  change_desc.prompt(bug=options.bug)
2731
+ # On first upload, patchset title is always this string, while
2732
+ # --title flag gets converted to first line of message.
2733
+ title = 'Initial upload'
2734
+ automatic_title = True
2508
2735
  if not change_desc.description:
2509
2736
  DieWithError("Description is empty. Aborting...")
2510
2737
  message = change_desc.description
@@ -2534,10 +2761,12 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2534
2761
  if not parent or (RunGitSilent(['rev-parse', upstream_branch + ':']) !=
2535
2762
  RunGitSilent(['rev-parse', parent + ':'])):
2536
2763
  DieWithError(
2537
- 'Upload upstream branch %s first.\n'
2538
- 'Note: maybe you\'ve uploaded it with --no-squash. '
2539
- 'If so, then re-upload it with:\n'
2540
- ' git cl upload --squash\n' % upstream_branch_name)
2764
+ '\nUpload upstream branch %s first.\n'
2765
+ 'It is likely that this branch has been rebased since its last '
2766
+ 'upload, so you just need to upload it again.\n'
2767
+ '(If you uploaded it with --no-squash, then branch dependencies '
2768
+ 'are not supported, and you should reupload with --squash.)'
2769
+ % upstream_branch_name, change_desc)
2541
2770
  else:
2542
2771
  parent = self.GetCommonAncestorWithUpstream()
2543
2772
 
@@ -2580,34 +2809,41 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2580
2809
  print('Adding self-LGTM (Code-Review +1) because of TBRs')
2581
2810
  refspec_opts.append('l=Code-Review+1')
2582
2811
 
2583
- if options.title:
2584
- if not re.match(r'^[\w ]+$', options.title):
2585
- options.title = re.sub(r'[^\w ]', '', options.title)
2586
- print('WARNING: Patchset title may only contain alphanumeric chars '
2587
- 'and spaces. Cleaned up title:\n%s' % options.title)
2588
- if not options.force:
2589
- ask_for_data('Press enter to continue, Ctrl+C to abort')
2812
+ if title:
2813
+ if not re.match(r'^[\w ]+$', title):
2814
+ title = re.sub(r'[^\w ]', '', title)
2815
+ if not automatic_title:
2816
+ print('WARNING: Patchset title may only contain alphanumeric chars '
2817
+ 'and spaces. You can edit it in the UI. '
2818
+ 'See https://crbug.com/663787.\n'
2819
+ 'Cleaned up title: %s' % title)
2590
2820
  # Per doc, spaces must be converted to underscores, and Gerrit will do the
2591
2821
  # reverse on its side.
2592
- refspec_opts.append('m=' + options.title.replace(' ', '_'))
2822
+ refspec_opts.append('m=' + title.replace(' ', '_'))
2593
2823
 
2594
2824
  if options.send_mail:
2595
2825
  if not change_desc.get_reviewers():
2596
- DieWithError('Must specify reviewers to send email.')
2826
+ DieWithError('Must specify reviewers to send email.', change_desc)
2597
2827
  refspec_opts.append('notify=ALL')
2598
2828
  else:
2599
2829
  refspec_opts.append('notify=NONE')
2600
2830
 
2601
- cc = self.GetCCList().split(',')
2602
- if options.cc:
2603
- cc.extend(options.cc)
2604
- cc = filter(None, cc)
2605
- if cc:
2606
- refspec_opts.extend('cc=' + email.strip() for email in cc)
2607
-
2608
2831
  reviewers = change_desc.get_reviewers()
2609
2832
  if reviewers:
2610
- refspec_opts.extend('r=' + email.strip() for email in reviewers)
2833
+ # TODO(tandrii): remove this horrible hack once (Poly)Gerrit fixes their
2834
+ # side for real (b/34702620).
2835
+ def clean_invisible_chars(email):
2836
+ return email.decode('unicode_escape').encode('ascii', 'ignore')
2837
+ refspec_opts.extend('r=' + clean_invisible_chars(email).strip()
2838
+ for email in reviewers)
2839
+
2840
+ if options.private:
2841
+ refspec_opts.append('draft')
2842
+
2843
+ if options.topic:
2844
+ # Documentation on Gerrit topics is here:
2845
+ # https://gerrit-review.googlesource.com/Documentation/user-upload.html#topic
2846
+ refspec_opts.append('topic=%s' % options.topic)
2611
2847
 
2612
2848
  refspec_suffix = ''
2613
2849
  if refspec_opts:
@@ -2616,12 +2852,16 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2616
2852
  'spaces not allowed in refspec: "%s"' % refspec_suffix)
2617
2853
  refspec = '%s:refs/for/%s%s' % (ref_to_push, branch, refspec_suffix)
2618
2854
 
2619
- push_stdout = gclient_utils.CheckCallAndFilter(
2620
- ['git', 'push', gerrit_remote, refspec],
2621
- print_stdout=True,
2622
- # Flush after every line: useful for seeing progress when running as
2623
- # recipe.
2624
- filter_fn=lambda _: sys.stdout.flush())
2855
+ try:
2856
+ push_stdout = gclient_utils.CheckCallAndFilter(
2857
+ ['git', 'push', self.GetRemoteUrl(), refspec],
2858
+ print_stdout=True,
2859
+ # Flush after every line: useful for seeing progress when running as
2860
+ # recipe.
2861
+ filter_fn=lambda _: sys.stdout.flush())
2862
+ except subprocess2.CalledProcessError:
2863
+ DieWithError('Failed to create a change. Please examine output above '
2864
+ 'for the reason of the failure. ', change_desc)
2625
2865
 
2626
2866
  if options.squash:
2627
2867
  regex = re.compile(r'remote:\s+https?://[\w\-\.\/]*/(\d+)\s.*')
@@ -2631,9 +2871,21 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2631
2871
  if len(change_numbers) != 1:
2632
2872
  DieWithError(
2633
2873
  ('Created|Updated %d issues on Gerrit, but only 1 expected.\n'
2634
- 'Change-Id: %s') % (len(change_numbers), change_id))
2874
+ 'Change-Id: %s') % (len(change_numbers), change_id), change_desc)
2635
2875
  self.SetIssue(change_numbers[0])
2636
2876
  self._GitSetBranchConfigValue('gerritsquashhash', ref_to_push)
2877
+
2878
+ # Add cc's from the CC_LIST and --cc flag (if any).
2879
+ cc = self.GetCCList().split(',')
2880
+ if options.cc:
2881
+ cc.extend(options.cc)
2882
+ cc = filter(None, [email.strip() for email in cc])
2883
+ if change_desc.get_cced():
2884
+ cc.extend(change_desc.get_cced())
2885
+ if cc:
2886
+ gerrit_util.AddReviewers(
2887
+ self._GetGerritHost(), self.GetIssue(), cc,
2888
+ is_reviewer=False, notify=bool(options.send_mail))
2637
2889
  return 0
2638
2890
 
2639
2891
  def _AddChangeIdToCommitMessage(self, options, args):
@@ -2655,10 +2907,47 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2655
2907
  vote_map = {
2656
2908
  _CQState.NONE: 0,
2657
2909
  _CQState.DRY_RUN: 1,
2658
- _CQState.COMMIT : 2,
2910
+ _CQState.COMMIT: 2,
2659
2911
  }
2660
- gerrit_util.SetReview(self._GetGerritHost(), self.GetIssue(),
2661
- labels={'Commit-Queue': vote_map[new_state]})
2912
+ kwargs = {'labels': {'Commit-Queue': vote_map[new_state]}}
2913
+ if new_state == _CQState.DRY_RUN:
2914
+ # Don't spam everybody reviewer/owner.
2915
+ kwargs['notify'] = 'NONE'
2916
+ gerrit_util.SetReview(self._GetGerritHost(), self.GetIssue(), **kwargs)
2917
+
2918
+ def CannotTriggerTryJobReason(self):
2919
+ try:
2920
+ data = self._GetChangeDetail()
2921
+ except GerritChangeNotExists:
2922
+ return 'Gerrit doesn\'t know about your change %s' % self.GetIssue()
2923
+
2924
+ if data['status'] in ('ABANDONED', 'MERGED'):
2925
+ return 'CL %s is closed' % self.GetIssue()
2926
+
2927
+ def GetTryjobProperties(self, patchset=None):
2928
+ """Returns dictionary of properties to launch tryjob."""
2929
+ data = self._GetChangeDetail(['ALL_REVISIONS'])
2930
+ patchset = int(patchset or self.GetPatchset())
2931
+ assert patchset
2932
+ revision_data = None # Pylint wants it to be defined.
2933
+ for revision_data in data['revisions'].itervalues():
2934
+ if int(revision_data['_number']) == patchset:
2935
+ break
2936
+ else:
2937
+ raise Exception('Patchset %d is not known in Gerrit change %d' %
2938
+ (patchset, self.GetIssue()))
2939
+ return {
2940
+ 'patch_issue': self.GetIssue(),
2941
+ 'patch_set': patchset or self.GetPatchset(),
2942
+ 'patch_project': data['project'],
2943
+ 'patch_storage': 'gerrit',
2944
+ 'patch_ref': revision_data['fetch']['http']['ref'],
2945
+ 'patch_repository_url': revision_data['fetch']['http']['url'],
2946
+ 'patch_gerrit_url': self.GetCodereviewServer(),
2947
+ }
2948
+
2949
+ def GetIssueOwner(self):
2950
+ return self._GetChangeDetail(['DETAILED_ACCOUNTS'])['owner']['email']
2662
2951
 
2663
2952
 
2664
2953
  _CODEREVIEW_IMPLEMENTATIONS = {
@@ -2744,13 +3033,15 @@ def _get_bug_line_values(default_project, bugs):
2744
3033
  class ChangeDescription(object):
2745
3034
  """Contains a parsed form of the change description."""
2746
3035
  R_LINE = r'^[ \t]*(TBR|R)[ \t]*=[ \t]*(.*?)[ \t]*$'
3036
+ CC_LINE = r'^[ \t]*(CC)[ \t]*=[ \t]*(.*?)[ \t]*$'
2747
3037
  BUG_LINE = r'^[ \t]*(BUG)[ \t]*=[ \t]*(.*?)[ \t]*$'
3038
+ CHERRY_PICK_LINE = r'^\(cherry picked from commit [a-fA-F0-9]{40}\)$'
2748
3039
 
2749
3040
  def __init__(self, description):
2750
3041
  self._description_lines = (description or '').strip().splitlines()
2751
3042
 
2752
3043
  @property # www.logilab.org/ticket/89786
2753
- def description(self): # pylint: disable=E0202
3044
+ def description(self): # pylint: disable=method-hidden
2754
3045
  return '\n'.join(self._description_lines)
2755
3046
 
2756
3047
  def set_description(self, desc):
@@ -2891,6 +3182,63 @@ class ChangeDescription(object):
2891
3182
  if match and (not tbr_only or match.group(1).upper() == 'TBR')]
2892
3183
  return cleanup_list(reviewers)
2893
3184
 
3185
+ def get_cced(self):
3186
+ """Retrieves the list of reviewers."""
3187
+ matches = [re.match(self.CC_LINE, line) for line in self._description_lines]
3188
+ cced = [match.group(2).strip() for match in matches if match]
3189
+ return cleanup_list(cced)
3190
+
3191
+ def update_with_git_number_footers(self, parent_hash, parent_msg, dest_ref):
3192
+ """Updates this commit description given the parent.
3193
+
3194
+ This is essentially what Gnumbd used to do.
3195
+ Consult https://goo.gl/WMmpDe for more details.
3196
+ """
3197
+ assert parent_msg # No, orphan branch creation isn't supported.
3198
+ assert parent_hash
3199
+ assert dest_ref
3200
+ parent_footer_map = git_footers.parse_footers(parent_msg)
3201
+ # This will also happily parse svn-position, which GnumbD is no longer
3202
+ # supporting. While we'd generate correct footers, the verifier plugin
3203
+ # installed in Gerrit will block such commit (ie git push below will fail).
3204
+ parent_position = git_footers.get_position(parent_footer_map)
3205
+
3206
+ # Cherry-picks may have last line obscuring their prior footers,
3207
+ # from git_footers perspective. This is also what Gnumbd did.
3208
+ cp_line = None
3209
+ if (self._description_lines and
3210
+ re.match(self.CHERRY_PICK_LINE, self._description_lines[-1])):
3211
+ cp_line = self._description_lines.pop()
3212
+
3213
+ top_lines, _, parsed_footers = git_footers.split_footers(self.description)
3214
+
3215
+ # Original-ify all Cr- footers, to avoid re-lands, cherry-picks, or just
3216
+ # user interference with actual footers we'd insert below.
3217
+ for i, (k, v) in enumerate(parsed_footers):
3218
+ if k.startswith('Cr-'):
3219
+ parsed_footers[i] = (k.replace('Cr-', 'Cr-Original-'), v)
3220
+
3221
+ # Add Position and Lineage footers based on the parent.
3222
+ lineage = list(reversed(parent_footer_map.get('Cr-Branched-From', [])))
3223
+ if parent_position[0] == dest_ref:
3224
+ # Same branch as parent.
3225
+ number = int(parent_position[1]) + 1
3226
+ else:
3227
+ number = 1 # New branch, and extra lineage.
3228
+ lineage.insert(0, '%s-%s@{#%d}' % (parent_hash, parent_position[0],
3229
+ int(parent_position[1])))
3230
+
3231
+ parsed_footers.append(('Cr-Commit-Position',
3232
+ '%s@{#%d}' % (dest_ref, number)))
3233
+ parsed_footers.extend(('Cr-Branched-From', v) for v in lineage)
3234
+
3235
+ self._description_lines = top_lines
3236
+ if cp_line:
3237
+ self._description_lines.append(cp_line)
3238
+ if self._description_lines[-1] != '':
3239
+ self._description_lines.append('') # Ensure footer separator.
3240
+ self._description_lines.extend('%s: %s' % kv for kv in parsed_footers)
3241
+
2894
3242
 
2895
3243
  def get_approving_reviewers(props):
2896
3244
  """Retrieves the reviewers that approved a CL from the issue properties with
@@ -2939,7 +3287,8 @@ def LoadCodereviewSettingsFromFile(fileobj):
2939
3287
  else:
2940
3288
  RunGit(['config', '--unset-all', fullname], error_ok=unset_error_ok)
2941
3289
 
2942
- SetProperty('server', 'CODE_REVIEW_SERVER')
3290
+ if not keyvals.get('GERRIT_HOST', False):
3291
+ SetProperty('server', 'CODE_REVIEW_SERVER')
2943
3292
  # Only server setting is required. Other settings can be absent.
2944
3293
  # In that case, we ignore errors raised during option deletion attempt.
2945
3294
  SetProperty('cc', 'CC_LIST', unset_error_ok=True)
@@ -2948,11 +3297,8 @@ def LoadCodereviewSettingsFromFile(fileobj):
2948
3297
  SetProperty('viewvc-url', 'VIEW_VC', unset_error_ok=True)
2949
3298
  SetProperty('bug-prefix', 'BUG_PREFIX', unset_error_ok=True)
2950
3299
  SetProperty('cpplint-regex', 'LINT_REGEX', unset_error_ok=True)
2951
- SetProperty('force-https-commit-url', 'FORCE_HTTPS_COMMIT_URL',
2952
- unset_error_ok=True)
2953
3300
  SetProperty('cpplint-ignore-regex', 'LINT_IGNORE_REGEX', unset_error_ok=True)
2954
3301
  SetProperty('project', 'PROJECT', unset_error_ok=True)
2955
- SetProperty('pending-ref-prefix', 'PENDING_REF_PREFIX', unset_error_ok=True)
2956
3302
  SetProperty('run-post-upload-hook', 'RUN_POST_UPLOAD_HOOK',
2957
3303
  unset_error_ok=True)
2958
3304
 
@@ -2968,9 +3314,9 @@ def LoadCodereviewSettingsFromFile(fileobj):
2968
3314
  keyvals['GERRIT_SKIP_ENSURE_AUTHENTICATED']])
2969
3315
 
2970
3316
  if 'PUSH_URL_CONFIG' in keyvals and 'ORIGIN_URL_CONFIG' in keyvals:
2971
- #should be of the form
2972
- #PUSH_URL_CONFIG: url.ssh://gitrw.chromium.org.pushinsteadof
2973
- #ORIGIN_URL_CONFIG: http://src.chromium.org/git
3317
+ # should be of the form
3318
+ # PUSH_URL_CONFIG: url.ssh://gitrw.chromium.org.pushinsteadof
3319
+ # ORIGIN_URL_CONFIG: http://src.chromium.org/git
2974
3320
  RunGit(['config', keyvals['PUSH_URL_CONFIG'],
2975
3321
  keyvals['ORIGIN_URL_CONFIG']])
2976
3322
 
@@ -3024,7 +3370,6 @@ def DownloadGerritHook(force):
3024
3370
  'chmod +x .git/hooks/commit-msg' % src)
3025
3371
 
3026
3372
 
3027
-
3028
3373
  def GetRietveldCodereviewSettingsInteractively():
3029
3374
  """Prompt the user for settings."""
3030
3375
  server = settings.GetDefaultServerUrl(error_ok=True)
@@ -3061,13 +3406,14 @@ def GetRietveldCodereviewSettingsInteractively():
3061
3406
  SetProperty(settings.GetRunPostUploadHook(), 'Run Post Upload Hook',
3062
3407
  'run-post-upload-hook', False)
3063
3408
 
3409
+
3064
3410
  @subcommand.usage('[repo root containing codereview.settings]')
3065
3411
  def CMDconfig(parser, args):
3066
3412
  """Edits configuration for this tree."""
3067
3413
 
3068
- print('WARNING: git cl config works for Rietveld only.\n'
3069
- 'For Gerrit, see http://crbug.com/603116.')
3070
- # TODO(tandrii): add Gerrit support as part of http://crbug.com/603116.
3414
+ print('WARNING: git cl config works for Rietveld only')
3415
+ # TODO(tandrii): remove this once we switch to Gerrit.
3416
+ # See bugs http://crbug.com/637561 and http://crbug.com/600469.
3071
3417
  parser.add_option('--activate-update', action='store_true',
3072
3418
  help='activate auto-updating [rietveld] section in '
3073
3419
  '.git/config')
@@ -3140,48 +3486,51 @@ def get_cl_statuses(changes, fine_grained, max_processes=None):
3140
3486
  # Silence upload.py otherwise it becomes unwieldy.
3141
3487
  upload.verbosity = 0
3142
3488
 
3143
- if fine_grained:
3144
- # Process one branch synchronously to work through authentication, then
3145
- # spawn processes to process all the other branches in parallel.
3146
- if changes:
3147
- def fetch(cl):
3148
- try:
3149
- return (cl, cl.GetStatus())
3150
- except:
3151
- # See http://crbug.com/629863.
3152
- logging.exception('failed to fetch status for %s:', cl)
3153
- raise
3154
- yield fetch(changes[0])
3155
-
3156
- changes_to_fetch = changes[1:]
3157
- if not changes_to_fetch:
3158
- # Exit early if there was only one branch to fetch.
3159
- return
3489
+ if not changes:
3490
+ raise StopIteration()
3160
3491
 
3161
- pool = ThreadPool(
3162
- min(max_processes, len(changes_to_fetch))
3163
- if max_processes is not None
3164
- else max(len(changes_to_fetch), 1))
3165
-
3166
- fetched_cls = set()
3167
- it = pool.imap_unordered(fetch, changes_to_fetch).__iter__()
3168
- while True:
3169
- try:
3170
- row = it.next(timeout=5)
3171
- except multiprocessing.TimeoutError:
3172
- break
3173
-
3174
- fetched_cls.add(row[0])
3175
- yield row
3176
-
3177
- # Add any branches that failed to fetch.
3178
- for cl in set(changes_to_fetch) - fetched_cls:
3179
- yield (cl, 'error')
3180
-
3181
- else:
3492
+ if not fine_grained:
3493
+ # Fast path which doesn't involve querying codereview servers.
3182
3494
  # Do not use GetApprovingReviewers(), since it requires an HTTP request.
3183
3495
  for cl in changes:
3184
3496
  yield (cl, 'waiting' if cl.GetIssueURL() else 'error')
3497
+ return
3498
+
3499
+ # First, sort out authentication issues.
3500
+ logging.debug('ensuring credentials exist')
3501
+ for cl in changes:
3502
+ cl.EnsureAuthenticated(force=False, refresh=True)
3503
+
3504
+ def fetch(cl):
3505
+ try:
3506
+ return (cl, cl.GetStatus())
3507
+ except:
3508
+ # See http://crbug.com/629863.
3509
+ logging.exception('failed to fetch status for %s:', cl)
3510
+ raise
3511
+
3512
+ threads_count = len(changes)
3513
+ if max_processes:
3514
+ threads_count = max(1, min(threads_count, max_processes))
3515
+ logging.debug('querying %d CLs using %d threads', len(changes), threads_count)
3516
+
3517
+ pool = ThreadPool(threads_count)
3518
+ fetched_cls = set()
3519
+ try:
3520
+ it = pool.imap_unordered(fetch, changes).__iter__()
3521
+ while True:
3522
+ try:
3523
+ cl, status = it.next(timeout=5)
3524
+ except multiprocessing.TimeoutError:
3525
+ break
3526
+ fetched_cls.add(cl)
3527
+ yield cl, status
3528
+ finally:
3529
+ pool.close()
3530
+
3531
+ # Add any branches that failed to fetch.
3532
+ for cl in set(changes) - fetched_cls:
3533
+ yield (cl, 'error')
3185
3534
 
3186
3535
 
3187
3536
  def upload_branch_deps(cl, args):
@@ -3208,7 +3557,7 @@ def upload_branch_deps(cl, args):
3208
3557
  if root_branch is None:
3209
3558
  DieWithError('Can\'t find dependent branches from detached HEAD state. '
3210
3559
  'Get on a branch!')
3211
- if not cl.GetIssue() or not cl.GetPatchset():
3560
+ if not cl.GetIssue() or (not cl.IsGerrit() and not cl.GetPatchset()):
3212
3561
  DieWithError('Current branch does not have an uploaded CL. We cannot set '
3213
3562
  'patchset dependencies without an uploaded CL.')
3214
3563
 
@@ -3267,7 +3616,7 @@ def upload_branch_deps(cl, args):
3267
3616
  if CMDupload(OptionParser(), args) != 0:
3268
3617
  print('Upload failed for %s!' % dependent_branch)
3269
3618
  failures[dependent_branch] = 1
3270
- except: # pylint: disable=W0702
3619
+ except: # pylint: disable=bare-except
3271
3620
  failures[dependent_branch] = 1
3272
3621
  print()
3273
3622
  finally:
@@ -3455,10 +3804,13 @@ def CMDstatus(parser, args):
3455
3804
  alignment, ShortBranchName(branch), color, url,
3456
3805
  status_str, reset))
3457
3806
 
3458
- cl = Changelist(auth_config=auth_config)
3807
+
3808
+ branch = GetCurrentBranch()
3459
3809
  print()
3460
- print('Current branch:',)
3461
- print(cl.GetBranch())
3810
+ print('Current branch: %s' % branch)
3811
+ for cl in changes:
3812
+ if cl.GetBranch() == branch:
3813
+ break
3462
3814
  if not cl.GetIssue():
3463
3815
  print('No issue assigned.')
3464
3816
  return 0
@@ -3607,16 +3959,19 @@ def CMDdescription(parser, args):
3607
3959
  parser.add_option('-n', '--new-description',
3608
3960
  help='New description to set for this issue (- for stdin, '
3609
3961
  '+ to load from local commit HEAD)')
3962
+ parser.add_option('-f', '--force', action='store_true',
3963
+ help='Delete any unpublished Gerrit edits for this issue '
3964
+ 'without prompting')
3610
3965
 
3611
3966
  _add_codereview_select_options(parser)
3612
3967
  auth.add_auth_options(parser)
3613
3968
  options, args = parser.parse_args(args)
3614
3969
  _process_codereview_select_options(parser, options)
3615
3970
 
3616
- target_issue = None
3971
+ target_issue_arg = None
3617
3972
  if len(args) > 0:
3618
- target_issue = ParseIssueNumberArgument(args[0])
3619
- if not target_issue.valid:
3973
+ target_issue_arg = ParseIssueNumberArgument(args[0])
3974
+ if not target_issue_arg.valid:
3620
3975
  parser.print_help()
3621
3976
  return 1
3622
3977
 
@@ -3626,10 +3981,9 @@ def CMDdescription(parser, args):
3626
3981
  'auth_config': auth_config,
3627
3982
  'codereview': options.forced_codereview,
3628
3983
  }
3629
- if target_issue:
3630
- kwargs['issue'] = target_issue.issue
3631
- if options.forced_codereview == 'rietveld':
3632
- kwargs['rietveld_server'] = target_issue.hostname
3984
+ if target_issue_arg:
3985
+ kwargs['issue'] = target_issue_arg.issue
3986
+ kwargs['codereview_host'] = target_issue_arg.hostname
3633
3987
 
3634
3988
  cl = Changelist(**kwargs)
3635
3989
 
@@ -3655,7 +4009,7 @@ def CMDdescription(parser, args):
3655
4009
  description.prompt()
3656
4010
 
3657
4011
  if cl.GetDescription() != description.description:
3658
- cl.UpdateDescription(description.description)
4012
+ cl.UpdateDescription(description.description, force=options.force)
3659
4013
  return 0
3660
4014
 
3661
4015
 
@@ -3682,7 +4036,7 @@ def CMDlint(parser, args):
3682
4036
  auth_config = auth.extract_auth_config_from_options(options)
3683
4037
 
3684
4038
  # Access to a protected member _XX of a client class
3685
- # pylint: disable=W0212
4039
+ # pylint: disable=protected-access
3686
4040
  try:
3687
4041
  import cpplint
3688
4042
  import cpplint_chromium
@@ -3731,7 +4085,7 @@ def CMDlint(parser, args):
3731
4085
  def CMDpresubmit(parser, args):
3732
4086
  """Runs presubmit tests on the current changelist."""
3733
4087
  parser.add_option('-u', '--upload', action='store_true',
3734
- help='Run upload hook instead of the push/dcommit hook')
4088
+ help='Run upload hook instead of the push hook')
3735
4089
  parser.add_option('-f', '--force', action='store_true',
3736
4090
  help='Run checks even if tree is dirty')
3737
4091
  auth.add_auth_options(parser)
@@ -3787,14 +4141,13 @@ def GenerateGerritChangeId(message):
3787
4141
  return 'I%s' % change_hash.strip()
3788
4142
 
3789
4143
 
3790
- def GetTargetRef(remote, remote_branch, target_branch, pending_prefix):
4144
+ def GetTargetRef(remote, remote_branch, target_branch):
3791
4145
  """Computes the remote branch ref to use for the CL.
3792
4146
 
3793
4147
  Args:
3794
4148
  remote (str): The git remote for the CL.
3795
4149
  remote_branch (str): The git remote branch for the CL.
3796
4150
  target_branch (str): The target branch specified by the user.
3797
- pending_prefix (str): The pending prefix from the settings.
3798
4151
  """
3799
4152
  if not (remote and remote_branch):
3800
4153
  return None
@@ -3836,9 +4189,7 @@ def GetTargetRef(remote, remote_branch, target_branch, pending_prefix):
3836
4189
  'refs/heads/')
3837
4190
  elif remote_branch.startswith('refs/remotes/branch-heads'):
3838
4191
  remote_branch = remote_branch.replace('refs/remotes/', 'refs/')
3839
- # If a pending prefix exists then replace refs/ with it.
3840
- if pending_prefix:
3841
- remote_branch = remote_branch.replace('refs/', pending_prefix)
4192
+
3842
4193
  return remote_branch
3843
4194
 
3844
4195
 
@@ -3870,14 +4221,15 @@ def CMDupload(parser, args):
3870
4221
  help='bypass watchlists auto CC-ing reviewers')
3871
4222
  parser.add_option('-f', action='store_true', dest='force',
3872
4223
  help="force yes to questions (don't prompt)")
3873
- parser.add_option('-m', dest='message', help='message for patchset')
4224
+ parser.add_option('--message', '-m', dest='message',
4225
+ help='message for patchset')
3874
4226
  parser.add_option('-b', '--bug',
3875
4227
  help='pre-populate the bug number(s) for this issue. '
3876
4228
  'If several, separate with commas')
3877
4229
  parser.add_option('--message-file', dest='message_file',
3878
4230
  help='file which contains message for patchset')
3879
- parser.add_option('-t', dest='title',
3880
- help='title for patchset (Rietveld only)')
4231
+ parser.add_option('--title', '-t', dest='title',
4232
+ help='title for patchset')
3881
4233
  parser.add_option('-r', '--reviewers',
3882
4234
  action='append', default=[],
3883
4235
  help='reviewer email addresses')
@@ -3885,7 +4237,7 @@ def CMDupload(parser, args):
3885
4237
  action='append', default=[],
3886
4238
  help='cc email addresses')
3887
4239
  parser.add_option('-s', '--send-mail', action='store_true',
3888
- help='send email to reviewer immediately')
4240
+ help='send email to reviewer(s) and cc(s) immediately')
3889
4241
  parser.add_option('--emulate_svn_auto_props',
3890
4242
  '--emulate-svn-auto-props',
3891
4243
  action="store_true",
@@ -3905,6 +4257,8 @@ def CMDupload(parser, args):
3905
4257
  parser.add_option('--no-squash', action='store_true',
3906
4258
  help='Don\'t squash multiple commits into one ' +
3907
4259
  '(Gerrit only)')
4260
+ parser.add_option('--topic', default=None,
4261
+ help='Topic to specify when uploading (Gerrit only)')
3908
4262
  parser.add_option('--email', default=None,
3909
4263
  help='email address to use to connect to Rietveld')
3910
4264
  parser.add_option('--tbr-owners', dest='tbr_owners', action='store_true',
@@ -3947,24 +4301,26 @@ def CMDupload(parser, args):
3947
4301
  return cl.CMDUpload(options, args, orig_args)
3948
4302
 
3949
4303
 
3950
- def IsSubmoduleMergeCommit(ref):
3951
- # When submodules are added to the repo, we expect there to be a single
3952
- # non-git-svn merge commit at remote HEAD with a signature comment.
3953
- pattern = '^SVN changes up to revision [0-9]*$'
3954
- cmd = ['rev-list', '--merges', '--grep=%s' % pattern, '%s^!' % ref]
3955
- return RunGit(cmd) != ''
4304
+ @subcommand.usage('DEPRECATED')
4305
+ def CMDdcommit(parser, args):
4306
+ """DEPRECATED: Used to commit the current changelist via git-svn."""
4307
+ message = ('git-cl no longer supports committing to SVN repositories via '
4308
+ 'git-svn. You probably want to use `git cl land` instead.')
4309
+ print(message)
4310
+ return 1
3956
4311
 
3957
4312
 
3958
- def SendUpstream(parser, args, cmd):
3959
- """Common code for CMDland and CmdDCommit
4313
+ @subcommand.usage('[upstream branch to apply against]')
4314
+ def CMDland(parser, args):
4315
+ """Commits the current changelist via git.
3960
4316
 
3961
4317
  In case of Gerrit, uses Gerrit REST api to "submit" the issue, which pushes
3962
4318
  upstream and closes the issue automatically and atomically.
3963
4319
 
3964
4320
  Otherwise (in case of Rietveld):
3965
4321
  Squashes branch into a single commit.
3966
- Updates changelog with metadata (e.g. pointer to review).
3967
- Pushes/dcommits the code upstream.
4322
+ Updates commit message with metadata (e.g. pointer to review).
4323
+ Pushes the code upstream.
3968
4324
  Updates review and closes.
3969
4325
  """
3970
4326
  parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks',
@@ -3999,12 +4355,16 @@ def SendUpstream(parser, args, cmd):
3999
4355
  'the contributor\'s "name <email>". If you can\'t upload such a '
4000
4356
  'commit for review, contact your repository admin and request'
4001
4357
  '"Forge-Author" permission.')
4358
+ if not cl.GetIssue():
4359
+ DieWithError('You must upload the change first to Gerrit.\n'
4360
+ ' If you would rather have `git cl land` upload '
4361
+ 'automatically for you, see http://crbug.com/642759')
4002
4362
  return cl._codereview_impl.CMDLand(options.force, options.bypass_hooks,
4003
4363
  options.verbose)
4004
4364
 
4005
4365
  current = cl.GetBranch()
4006
4366
  remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch())
4007
- if not settings.GetIsGitSvn() and remote == '.':
4367
+ if remote == '.':
4008
4368
  print()
4009
4369
  print('Attempting to push branch %r into another local branch!' % current)
4010
4370
  print()
@@ -4017,7 +4377,7 @@ def SendUpstream(parser, args, cmd):
4017
4377
  print(' Current parent: %r' % upstream_branch)
4018
4378
  return 1
4019
4379
 
4020
- if not args or cmd == 'land':
4380
+ if not args:
4021
4381
  # Default to merging against our best guess of the upstream branch.
4022
4382
  args = [cl.GetUpstreamBranch()]
4023
4383
 
@@ -4027,9 +4387,8 @@ def SendUpstream(parser, args, cmd):
4027
4387
  return 1
4028
4388
 
4029
4389
  base_branch = args[0]
4030
- base_has_submodules = IsSubmoduleMergeCommit(base_branch)
4031
4390
 
4032
- if git_common.is_dirty_git_tree(cmd):
4391
+ if git_common.is_dirty_git_tree('land'):
4033
4392
  return 1
4034
4393
 
4035
4394
  # This rev-list syntax means "show all commits not in my branch that
@@ -4039,30 +4398,9 @@ def SendUpstream(parser, args, cmd):
4039
4398
  if upstream_commits:
4040
4399
  print('Base branch "%s" has %d commits '
4041
4400
  'not in this branch.' % (base_branch, len(upstream_commits)))
4042
- print('Run "git merge %s" before attempting to %s.' % (base_branch, cmd))
4401
+ print('Run "git merge %s" before attempting to land.' % base_branch)
4043
4402
  return 1
4044
4403
 
4045
- # This is the revision `svn dcommit` will commit on top of.
4046
- svn_head = None
4047
- if cmd == 'dcommit' or base_has_submodules:
4048
- svn_head = RunGit(['log', '--grep=^git-svn-id:', '-1',
4049
- '--pretty=format:%H'])
4050
-
4051
- if cmd == 'dcommit':
4052
- # If the base_head is a submodule merge commit, the first parent of the
4053
- # base_head should be a git-svn commit, which is what we're interested in.
4054
- base_svn_head = base_branch
4055
- if base_has_submodules:
4056
- base_svn_head += '^1'
4057
-
4058
- extra_commits = RunGit(['rev-list', '^' + svn_head, base_svn_head])
4059
- if extra_commits:
4060
- print('This branch has %d additional commits not upstreamed yet.'
4061
- % len(extra_commits.splitlines()))
4062
- print('Upstream "%s" or rebase this branch on top of the upstream trunk '
4063
- 'before attempting to %s.' % (base_branch, cmd))
4064
- return 1
4065
-
4066
4404
  merge_base = RunGit(['merge-base', base_branch, 'HEAD']).strip()
4067
4405
  if not options.bypass_hooks:
4068
4406
  author = None
@@ -4080,11 +4418,11 @@ def SendUpstream(parser, args, cmd):
4080
4418
  status = GetTreeStatus()
4081
4419
  if 'closed' == status:
4082
4420
  print('The tree is closed. Please wait for it to reopen. Use '
4083
- '"git cl %s --bypass-hooks" to commit on a closed tree.' % cmd)
4421
+ '"git cl land --bypass-hooks" to commit on a closed tree.')
4084
4422
  return 1
4085
4423
  elif 'unknown' == status:
4086
4424
  print('Unable to determine tree status. Please verify manually and '
4087
- 'use "git cl %s --bypass-hooks" to commit on a closed tree.' % cmd)
4425
+ 'use "git cl land --bypass-hooks" to commit on a closed tree.')
4088
4426
  return 1
4089
4427
 
4090
4428
  change_desc = ChangeDescription(options.message)
@@ -4102,7 +4440,6 @@ def SendUpstream(parser, args, cmd):
4102
4440
  # Keep a separate copy for the commit message, because the commit message
4103
4441
  # contains the link to the Rietveld issue, while the Rietveld message contains
4104
4442
  # the commit viewvc url.
4105
- # Keep a separate copy for the commit message.
4106
4443
  if cl.GetIssue():
4107
4444
  change_desc.update_reviewers(cl.GetApprovingReviewers())
4108
4445
 
@@ -4112,7 +4449,7 @@ def SendUpstream(parser, args, cmd):
4112
4449
  # after it. Add a period on a new line to circumvent this. Also add a space
4113
4450
  # before the period to make sure that Gitiles continues to correctly resolve
4114
4451
  # the URL.
4115
- commit_desc.append_footer('Review URL: %s .' % cl.GetIssueURL())
4452
+ commit_desc.append_footer('Review-Url: %s .' % cl.GetIssueURL())
4116
4453
  if options.contributor:
4117
4454
  commit_desc.append_footer('Patch from %s.' % options.contributor)
4118
4455
 
@@ -4125,9 +4462,7 @@ def SendUpstream(parser, args, cmd):
4125
4462
 
4126
4463
  # We want to squash all this branch's commits into one commit with the proper
4127
4464
  # description. We do this by doing a "reset --soft" to the base branch (which
4128
- # keeps the working copy the same), then dcommitting that. If origin/master
4129
- # has a submodule merge commit, we'll also need to cherry-pick the squashed
4130
- # commit onto a branch based on the git-svn head.
4465
+ # keeps the working copy the same), then landing that.
4131
4466
  MERGE_BRANCH = 'git-cl-commit'
4132
4467
  CHERRY_PICK_BRANCH = 'git-cl-cherry-pick'
4133
4468
  # Delete the branches if they exist.
@@ -4148,8 +4483,6 @@ def SendUpstream(parser, args, cmd):
4148
4483
  # We wrap in a try...finally block so if anything goes wrong,
4149
4484
  # we clean up the branches.
4150
4485
  retcode = -1
4151
- pushed_to_pending = False
4152
- pending_ref = None
4153
4486
  revision = None
4154
4487
  try:
4155
4488
  RunGit(['checkout', '-q', '-b', MERGE_BRANCH])
@@ -4162,237 +4495,75 @@ def SendUpstream(parser, args, cmd):
4162
4495
  ])
4163
4496
  else:
4164
4497
  RunGit(['commit', '-m', commit_desc.description])
4165
- if base_has_submodules:
4166
- cherry_pick_commit = RunGit(['rev-list', 'HEAD^!']).rstrip()
4167
- RunGit(['branch', CHERRY_PICK_BRANCH, svn_head])
4168
- RunGit(['checkout', CHERRY_PICK_BRANCH])
4169
- RunGit(['cherry-pick', cherry_pick_commit])
4170
- if cmd == 'land':
4171
- remote, branch = cl.FetchUpstreamTuple(cl.GetBranch())
4172
- mirror = settings.GetGitMirror(remote)
4173
- pushurl = mirror.url if mirror else remote
4174
- pending_prefix = settings.GetPendingRefPrefix()
4175
- if not pending_prefix or branch.startswith(pending_prefix):
4176
- # If not using refs/pending/heads/* at all, or target ref is already set
4177
- # to pending, then push to the target ref directly.
4178
- retcode, output = RunGitWithCode(
4179
- ['push', '--porcelain', pushurl, 'HEAD:%s' % branch])
4180
- pushed_to_pending = pending_prefix and branch.startswith(pending_prefix)
4181
- else:
4182
- # Cherry-pick the change on top of pending ref and then push it.
4183
- assert branch.startswith('refs/'), branch
4184
- assert pending_prefix[-1] == '/', pending_prefix
4185
- pending_ref = pending_prefix + branch[len('refs/'):]
4186
- retcode, output = PushToGitPending(pushurl, pending_ref, branch)
4187
- pushed_to_pending = (retcode == 0)
4188
- if retcode == 0:
4189
- revision = RunGit(['rev-parse', 'HEAD']).strip()
4498
+
4499
+ remote, branch = cl.FetchUpstreamTuple(cl.GetBranch())
4500
+ mirror = settings.GetGitMirror(remote)
4501
+ if mirror:
4502
+ pushurl = mirror.url
4503
+ git_numberer_enabled = _is_git_numberer_enabled(pushurl, branch)
4190
4504
  else:
4191
- # dcommit the merge branch.
4192
- cmd_args = [
4193
- 'svn', 'dcommit',
4194
- '-C%s' % options.similarity,
4195
- '--no-rebase', '--rmdir',
4196
- ]
4197
- if settings.GetForceHttpsCommitUrl():
4198
- # Allow forcing https commit URLs for some projects that don't allow
4199
- # committing to http URLs (like Google Code).
4200
- remote_url = cl.GetGitSvnRemoteUrl()
4201
- if urlparse.urlparse(remote_url).scheme == 'http':
4202
- remote_url = remote_url.replace('http://', 'https://')
4203
- cmd_args.append('--commit-url=%s' % remote_url)
4204
- _, output = RunGitWithCode(cmd_args)
4205
- if 'Committed r' in output:
4206
- revision = re.match(
4207
- '.*?\nCommitted r(\\d+)', output, re.DOTALL).group(1)
4505
+ pushurl = remote # Usually, this is 'origin'.
4506
+ git_numberer_enabled = _is_git_numberer_enabled(
4507
+ RunGit(['config', 'remote.%s.url' % remote]).strip(), branch)
4508
+
4509
+ if git_numberer_enabled:
4510
+ # TODO(tandrii): maybe do autorebase + retry on failure
4511
+ # http://crbug.com/682934, but better just use Gerrit :)
4512
+ logging.debug('Adding git number footers')
4513
+ parent_msg = RunGit(['show', '-s', '--format=%B', merge_base]).strip()
4514
+ commit_desc.update_with_git_number_footers(merge_base, parent_msg,
4515
+ branch)
4516
+ # Ensure timestamps are monotonically increasing.
4517
+ timestamp = max(1 + _get_committer_timestamp(merge_base),
4518
+ _get_committer_timestamp('HEAD'))
4519
+ _git_amend_head(commit_desc.description, timestamp)
4520
+ change_desc = ChangeDescription(commit_desc.description)
4521
+
4522
+ retcode, output = RunGitWithCode(
4523
+ ['push', '--porcelain', pushurl, 'HEAD:%s' % branch])
4524
+ if retcode == 0:
4525
+ revision = RunGit(['rev-parse', 'HEAD']).strip()
4208
4526
  logging.debug(output)
4527
+ except: # pylint: disable=bare-except
4528
+ if _IS_BEING_TESTED:
4529
+ logging.exception('this is likely your ACTUAL cause of test failure.\n'
4530
+ + '-' * 30 + '8<' + '-' * 30)
4531
+ logging.error('\n' + '-' * 30 + '8<' + '-' * 30 + '\n\n\n')
4532
+ raise
4209
4533
  finally:
4210
4534
  # And then swap back to the original branch and clean up.
4211
4535
  RunGit(['checkout', '-q', cl.GetBranch()])
4212
4536
  RunGit(['branch', '-D', MERGE_BRANCH])
4213
- if base_has_submodules:
4214
- RunGit(['branch', '-D', CHERRY_PICK_BRANCH])
4215
4537
 
4216
4538
  if not revision:
4217
4539
  print('Failed to push. If this persists, please file a bug.')
4218
4540
  return 1
4219
4541
 
4220
- killed = False
4221
- if pushed_to_pending:
4222
- try:
4223
- revision = WaitForRealCommit(remote, revision, base_branch, branch)
4224
- # We set pushed_to_pending to False, since it made it all the way to the
4225
- # real ref.
4226
- pushed_to_pending = False
4227
- except KeyboardInterrupt:
4228
- killed = True
4229
-
4230
4542
  if cl.GetIssue():
4231
- to_pending = ' to pending queue' if pushed_to_pending else ''
4232
4543
  viewvc_url = settings.GetViewVCUrl()
4233
- if not to_pending:
4234
- if viewvc_url and revision:
4235
- change_desc.append_footer(
4236
- 'Committed: %s%s' % (viewvc_url, revision))
4237
- elif revision:
4238
- change_desc.append_footer('Committed: %s' % (revision,))
4544
+ if viewvc_url and revision:
4545
+ change_desc.append_footer(
4546
+ 'Committed: %s%s' % (viewvc_url, revision))
4547
+ elif revision:
4548
+ change_desc.append_footer('Committed: %s' % (revision,))
4239
4549
  print('Closing issue '
4240
4550
  '(you may be prompted for your codereview password)...')
4241
4551
  cl.UpdateDescription(change_desc.description)
4242
4552
  cl.CloseIssue()
4243
4553
  props = cl.GetIssueProperties()
4244
4554
  patch_num = len(props['patchsets'])
4245
- comment = "Committed patchset #%d (id:%d)%s manually as %s" % (
4246
- patch_num, props['patchsets'][-1], to_pending, revision)
4555
+ comment = "Committed patchset #%d (id:%d) manually as %s" % (
4556
+ patch_num, props['patchsets'][-1], revision)
4247
4557
  if options.bypass_hooks:
4248
4558
  comment += ' (tree was closed).' if GetTreeStatus() == 'closed' else '.'
4249
4559
  else:
4250
4560
  comment += ' (presubmit successful).'
4251
4561
  cl.RpcServer().add_comment(cl.GetIssue(), comment)
4252
4562
 
4253
- if pushed_to_pending:
4254
- _, branch = cl.FetchUpstreamTuple(cl.GetBranch())
4255
- print('The commit is in the pending queue (%s).' % pending_ref)
4256
- print('It will show up on %s in ~1 min, once it gets a Cr-Commit-Position '
4257
- 'footer.' % branch)
4258
-
4259
- hook = POSTUPSTREAM_HOOK_PATTERN % cmd
4260
- if os.path.isfile(hook):
4261
- RunCommand([hook, merge_base], error_ok=True)
4262
-
4263
- return 1 if killed else 0
4264
-
4265
-
4266
- def WaitForRealCommit(remote, pushed_commit, local_base_ref, real_ref):
4267
- print()
4268
- print('Waiting for commit to be landed on %s...' % real_ref)
4269
- print('(If you are impatient, you may Ctrl-C once without harm)')
4270
- target_tree = RunGit(['rev-parse', '%s:' % pushed_commit]).strip()
4271
- current_rev = RunGit(['rev-parse', local_base_ref]).strip()
4272
- mirror = settings.GetGitMirror(remote)
4273
-
4274
- loop = 0
4275
- while True:
4276
- sys.stdout.write('fetching (%d)... \r' % loop)
4277
- sys.stdout.flush()
4278
- loop += 1
4279
-
4280
- if mirror:
4281
- mirror.populate()
4282
- RunGit(['retry', 'fetch', remote, real_ref], stderr=subprocess2.VOID)
4283
- to_rev = RunGit(['rev-parse', 'FETCH_HEAD']).strip()
4284
- commits = RunGit(['rev-list', '%s..%s' % (current_rev, to_rev)])
4285
- for commit in commits.splitlines():
4286
- if RunGit(['rev-parse', '%s:' % commit]).strip() == target_tree:
4287
- print('Found commit on %s' % real_ref)
4288
- return commit
4289
-
4290
- current_rev = to_rev
4563
+ if os.path.isfile(POSTUPSTREAM_HOOK):
4564
+ RunCommand([POSTUPSTREAM_HOOK, merge_base], error_ok=True)
4291
4565
 
4292
-
4293
- def PushToGitPending(remote, pending_ref, upstream_ref):
4294
- """Fetches pending_ref, cherry-picks current HEAD on top of it, pushes.
4295
-
4296
- Returns:
4297
- (retcode of last operation, output log of last operation).
4298
- """
4299
- assert pending_ref.startswith('refs/'), pending_ref
4300
- local_pending_ref = 'refs/git-cl/' + pending_ref[len('refs/'):]
4301
- cherry = RunGit(['rev-parse', 'HEAD']).strip()
4302
- code = 0
4303
- out = ''
4304
- max_attempts = 3
4305
- attempts_left = max_attempts
4306
- while attempts_left:
4307
- if attempts_left != max_attempts:
4308
- print('Retrying, %d attempts left...' % (attempts_left - 1,))
4309
- attempts_left -= 1
4310
-
4311
- # Fetch. Retry fetch errors.
4312
- print('Fetching pending ref %s...' % pending_ref)
4313
- code, out = RunGitWithCode(
4314
- ['retry', 'fetch', remote, '+%s:%s' % (pending_ref, local_pending_ref)])
4315
- if code:
4316
- print('Fetch failed with exit code %d.' % code)
4317
- if out.strip():
4318
- print(out.strip())
4319
- continue
4320
-
4321
- # Try to cherry pick. Abort on merge conflicts.
4322
- print('Cherry-picking commit on top of pending ref...')
4323
- RunGitWithCode(['checkout', local_pending_ref], suppress_stderr=True)
4324
- code, out = RunGitWithCode(['cherry-pick', cherry])
4325
- if code:
4326
- print('Your patch doesn\'t apply cleanly to ref \'%s\', '
4327
- 'the following files have merge conflicts:' % pending_ref)
4328
- print(RunGit(['diff', '--name-status', '--diff-filter=U']).strip())
4329
- print('Please rebase your patch and try again.')
4330
- RunGitWithCode(['cherry-pick', '--abort'])
4331
- return code, out
4332
-
4333
- # Applied cleanly, try to push now. Retry on error (flake or non-ff push).
4334
- print('Pushing commit to %s... It can take a while.' % pending_ref)
4335
- code, out = RunGitWithCode(
4336
- ['retry', 'push', '--porcelain', remote, 'HEAD:%s' % pending_ref])
4337
- if code == 0:
4338
- # Success.
4339
- print('Commit pushed to pending ref successfully!')
4340
- return code, out
4341
-
4342
- print('Push failed with exit code %d.' % code)
4343
- if out.strip():
4344
- print(out.strip())
4345
- if IsFatalPushFailure(out):
4346
- print('Fatal push error. Make sure your .netrc credentials and git '
4347
- 'user.email are correct and you have push access to the repo.')
4348
- return code, out
4349
-
4350
- print('All attempts to push to pending ref failed.')
4351
- return code, out
4352
-
4353
-
4354
- def IsFatalPushFailure(push_stdout):
4355
- """True if retrying push won't help."""
4356
- return '(prohibited by Gerrit)' in push_stdout
4357
-
4358
-
4359
- @subcommand.usage('[upstream branch to apply against]')
4360
- def CMDdcommit(parser, args):
4361
- """Commits the current changelist via git-svn."""
4362
- if not settings.GetIsGitSvn():
4363
- if git_footers.get_footer_svn_id():
4364
- # If it looks like previous commits were mirrored with git-svn.
4365
- message = """This repository appears to be a git-svn mirror, but no
4366
- upstream SVN master is set. You probably need to run 'git auto-svn' once."""
4367
- else:
4368
- message = """This doesn't appear to be an SVN repository.
4369
- If your project has a true, writeable git repository, you probably want to run
4370
- 'git cl land' instead.
4371
- If your project has a git mirror of an upstream SVN master, you probably need
4372
- to run 'git svn init'.
4373
-
4374
- Using the wrong command might cause your commit to appear to succeed, and the
4375
- review to be closed, without actually landing upstream. If you choose to
4376
- proceed, please verify that the commit lands upstream as expected."""
4377
- print(message)
4378
- ask_for_data('[Press enter to dcommit or ctrl-C to quit]')
4379
- # TODO(tandrii): kill this post SVN migration with
4380
- # https://codereview.chromium.org/2076683002
4381
- print('WARNING: chrome infrastructure is migrating SVN repos to Git.\n'
4382
- 'Please let us know of this project you are committing to:'
4383
- ' http://crbug.com/600451')
4384
- return SendUpstream(parser, args, 'dcommit')
4385
-
4386
-
4387
- @subcommand.usage('[upstream branch to apply against]')
4388
- def CMDland(parser, args):
4389
- """Commits the current changelist via git."""
4390
- if settings.GetIsGitSvn() or git_footers.get_footer_svn_id():
4391
- print('This appears to be an SVN repository.')
4392
- print('Are you sure you didn\'t mean \'git cl dcommit\'?')
4393
- print('(Ignore if this is the first commit after migrating from svn->git)')
4394
- ask_for_data('[Press enter to push or ctrl-C to quit]')
4395
- return SendUpstream(parser, args, 'land')
4566
+ return 0
4396
4567
 
4397
4568
 
4398
4569
  @subcommand.usage('<patch url or issue id or issue url>')
@@ -4434,7 +4605,7 @@ def CMDpatch(parser, args):
4434
4605
  auth_config = auth.extract_auth_config_from_options(options)
4435
4606
 
4436
4607
 
4437
- if options.reapply :
4608
+ if options.reapply:
4438
4609
  if options.newbranch:
4439
4610
  parser.error('--reapply works on the current branch only')
4440
4611
  if len(args) > 0:
@@ -4446,7 +4617,7 @@ def CMDpatch(parser, args):
4446
4617
  parser.error('current branch must have an associated issue')
4447
4618
 
4448
4619
  upstream = cl.GetUpstreamBranch()
4449
- if upstream == None:
4620
+ if upstream is None:
4450
4621
  parser.error('No upstream branch specified. Cannot reset branch')
4451
4622
 
4452
4623
  RunGit(['reset', '--hard', upstream])
@@ -4485,16 +4656,6 @@ def CMDpatch(parser, args):
4485
4656
  options.directory)
4486
4657
 
4487
4658
 
4488
- def CMDrebase(parser, args):
4489
- """Rebases current branch on top of svn repo."""
4490
- # Provide a wrapper for git svn rebase to help avoid accidental
4491
- # git svn dcommit.
4492
- # It's the only command that doesn't use parser at all since we just defer
4493
- # execution to git-svn.
4494
-
4495
- return RunGitWithCode(['svn', 'rebase'] + args)[1]
4496
-
4497
-
4498
4659
  def GetTreeStatus(url=None):
4499
4660
  """Fetches the tree status and returns either 'open', 'closed',
4500
4661
  'unknown' or 'unset'."""
@@ -4520,37 +4681,6 @@ def GetTreeStatusReason():
4520
4681
  return status['message']
4521
4682
 
4522
4683
 
4523
- def GetBuilderMaster(bot_list):
4524
- """For a given builder, fetch the master from AE if available."""
4525
- map_url = 'https://builders-map.appspot.com/'
4526
- try:
4527
- master_map = json.load(urllib2.urlopen(map_url))
4528
- except urllib2.URLError as e:
4529
- return None, ('Failed to fetch builder-to-master map from %s. Error: %s.' %
4530
- (map_url, e))
4531
- except ValueError as e:
4532
- return None, ('Invalid json string from %s. Error: %s.' % (map_url, e))
4533
- if not master_map:
4534
- return None, 'Failed to build master map.'
4535
-
4536
- result_master = ''
4537
- for bot in bot_list:
4538
- builder = bot.split(':', 1)[0]
4539
- master_list = master_map.get(builder, [])
4540
- if not master_list:
4541
- return None, ('No matching master for builder %s.' % builder)
4542
- elif len(master_list) > 1:
4543
- return None, ('The builder name %s exists in multiple masters %s.' %
4544
- (builder, master_list))
4545
- else:
4546
- cur_master = master_list[0]
4547
- if not result_master:
4548
- result_master = cur_master
4549
- elif result_master != cur_master:
4550
- return None, 'The builders do not belong to the same master.'
4551
- return result_master, None
4552
-
4553
-
4554
4684
  def CMDtree(parser, args):
4555
4685
  """Shows the status of the tree."""
4556
4686
  _, args = parser.parse_args(args)
@@ -4568,52 +4698,50 @@ def CMDtree(parser, args):
4568
4698
 
4569
4699
 
4570
4700
  def CMDtry(parser, args):
4571
- """Triggers try jobs through BuildBucket."""
4572
- group = optparse.OptionGroup(parser, "Try job options")
4701
+ """Triggers try jobs using either BuildBucket or CQ dry run."""
4702
+ group = optparse.OptionGroup(parser, 'Try job options')
4573
4703
  group.add_option(
4574
- "-b", "--bot", action="append",
4575
- help=("IMPORTANT: specify ONE builder per --bot flag. Use it multiple "
4576
- "times to specify multiple builders. ex: "
4577
- "'-b win_rel -b win_layout'. See "
4578
- "the try server waterfall for the builders name and the tests "
4579
- "available."))
4704
+ '-b', '--bot', action='append',
4705
+ help=('IMPORTANT: specify ONE builder per --bot flag. Use it multiple '
4706
+ 'times to specify multiple builders. ex: '
4707
+ '"-b win_rel -b win_layout". See '
4708
+ 'the try server waterfall for the builders name and the tests '
4709
+ 'available.'))
4580
4710
  group.add_option(
4581
- "-m", "--master", default='',
4582
- help=("Specify a try master where to run the tries."))
4711
+ '-B', '--bucket', default='',
4712
+ help=('Buildbucket bucket to send the try requests.'))
4583
4713
  group.add_option(
4584
- "-r", "--revision",
4585
- help="Revision to use for the try job; default: the "
4586
- "revision will be determined by the try server; see "
4587
- "its waterfall for more info")
4714
+ '-m', '--master', default='',
4715
+ help=('Specify a try master where to run the tries.'))
4588
4716
  group.add_option(
4589
- "-c", "--clobber", action="store_true", default=False,
4590
- help="Force a clobber before building; e.g. don't do an "
4591
- "incremental build")
4717
+ '-r', '--revision',
4718
+ help='Revision to use for the try job; default: the revision will '
4719
+ 'be determined by the try recipe that builder runs, which usually '
4720
+ 'defaults to HEAD of origin/master')
4592
4721
  group.add_option(
4593
- "--project",
4594
- help="Override which project to use. Projects are defined "
4595
- "server-side to define what default bot set to use")
4722
+ '-c', '--clobber', action='store_true', default=False,
4723
+ help='Force a clobber before building; that is don\'t do an '
4724
+ 'incremental build')
4596
4725
  group.add_option(
4597
- "-p", "--property", dest="properties", action="append", default=[],
4598
- help="Specify generic properties in the form -p key1=value1 -p "
4599
- "key2=value2 etc (buildbucket only). The value will be treated as "
4600
- "json if decodable, or as string otherwise.")
4726
+ '--project',
4727
+ help='Override which project to use. Projects are defined '
4728
+ 'in recipe to determine to which repository or directory to '
4729
+ 'apply the patch')
4601
4730
  group.add_option(
4602
- "-n", "--name", help="Try job name; default to current branch name")
4731
+ '-p', '--property', dest='properties', action='append', default=[],
4732
+ help='Specify generic properties in the form -p key1=value1 -p '
4733
+ 'key2=value2 etc. The value will be treated as '
4734
+ 'json if decodable, or as string otherwise. '
4735
+ 'NOTE: using this may make your try job not usable for CQ, '
4736
+ 'which will then schedule another try job with default properties')
4603
4737
  group.add_option(
4604
- "--use-rietveld", action="store_true", default=False,
4605
- help="Use Rietveld to trigger try jobs.")
4606
- group.add_option(
4607
- "--buildbucket-host", default='cr-buildbucket.appspot.com',
4608
- help="Host of buildbucket. The default host is %default.")
4738
+ '--buildbucket-host', default='cr-buildbucket.appspot.com',
4739
+ help='Host of buildbucket. The default host is %default.')
4609
4740
  parser.add_option_group(group)
4610
4741
  auth.add_auth_options(parser)
4611
4742
  options, args = parser.parse_args(args)
4612
4743
  auth_config = auth.extract_auth_config_from_options(options)
4613
4744
 
4614
- if options.use_rietveld and options.properties:
4615
- parser.error('Properties can only be specified with buildbucket')
4616
-
4617
4745
  # Make sure that all properties are prop=value pairs.
4618
4746
  bad_params = [x for x in options.properties if '=' not in x]
4619
4747
  if bad_params:
@@ -4627,164 +4755,66 @@ def CMDtry(parser, args):
4627
4755
  parser.error('Need to upload first')
4628
4756
 
4629
4757
  if cl.IsGerrit():
4630
- parser.error(
4631
- 'Not yet supported for Gerrit (http://crbug.com/599931).\n'
4632
- 'If your project has Commit Queue, dry run is a workaround:\n'
4633
- ' git cl set-commit --dry-run')
4634
- # Code below assumes Rietveld issue.
4635
- # TODO(tandrii): actually implement for Gerrit http://crbug.com/599931.
4636
-
4637
- props = cl.GetIssueProperties()
4638
- if props.get('closed'):
4639
- parser.error('Cannot send try jobs for a closed CL')
4640
-
4641
- if props.get('private'):
4642
- parser.error('Cannot use try bots with private issue')
4643
-
4644
- if not options.name:
4645
- options.name = cl.GetBranch()
4646
-
4647
- if options.bot and not options.master:
4648
- options.master, err_msg = GetBuilderMaster(options.bot)
4649
- if err_msg:
4650
- parser.error('Tryserver master cannot be found because: %s\n'
4651
- 'Please manually specify the tryserver master'
4652
- ', e.g. "-m tryserver.chromium.linux".' % err_msg)
4653
-
4654
- def GetMasterMap():
4655
- # Process --bot.
4656
- if not options.bot:
4657
- change = cl.GetChange(cl.GetCommonAncestorWithUpstream(), None)
4658
-
4659
- # Get try masters from PRESUBMIT.py files.
4660
- masters = presubmit_support.DoGetTryMasters(
4661
- change,
4662
- change.LocalPaths(),
4663
- settings.GetRoot(),
4664
- None,
4665
- None,
4666
- options.verbose,
4667
- sys.stdout)
4668
- if masters:
4669
- return masters
4670
-
4671
- # Fall back to deprecated method: get try slaves from PRESUBMIT.py files.
4672
- options.bot = presubmit_support.DoGetTrySlaves(
4673
- change,
4674
- change.LocalPaths(),
4675
- settings.GetRoot(),
4676
- None,
4677
- None,
4678
- options.verbose,
4679
- sys.stdout)
4680
-
4681
- if not options.bot:
4682
- return {}
4683
-
4684
- builders_and_tests = {}
4685
- # TODO(machenbach): The old style command-line options don't support
4686
- # multiple try masters yet.
4687
- old_style = filter(lambda x: isinstance(x, basestring), options.bot)
4688
- new_style = filter(lambda x: isinstance(x, tuple), options.bot)
4689
-
4690
- for bot in old_style:
4691
- if ':' in bot:
4692
- parser.error('Specifying testfilter is no longer supported')
4693
- elif ',' in bot:
4694
- parser.error('Specify one bot per --bot flag')
4695
- else:
4696
- builders_and_tests.setdefault(bot, [])
4758
+ # HACK: warm up Gerrit change detail cache to save on RPCs.
4759
+ cl._codereview_impl._GetChangeDetail(['DETAILED_ACCOUNTS', 'ALL_REVISIONS'])
4760
+
4761
+ error_message = cl.CannotTriggerTryJobReason()
4762
+ if error_message:
4763
+ parser.error('Can\'t trigger try jobs: %s' % error_message)
4697
4764
 
4698
- for bot, tests in new_style:
4699
- builders_and_tests.setdefault(bot, []).extend(tests)
4765
+ if options.bucket and options.master:
4766
+ parser.error('Only one of --bucket and --master may be used.')
4700
4767
 
4701
- # Return a master map with one master to be backwards compatible. The
4702
- # master name defaults to an empty string, which will cause the master
4703
- # not to be set on rietveld (deprecated).
4704
- return {options.master: builders_and_tests}
4768
+ buckets = _get_bucket_map(cl, options, parser)
4705
4769
 
4706
- masters = GetMasterMap()
4707
- if not masters:
4708
- # Default to triggering Dry Run (see http://crbug.com/625697).
4770
+ # If no bots are listed and we couldn't get a list based on PRESUBMIT files,
4771
+ # then we default to triggering a CQ dry run (see http://crbug.com/625697).
4772
+ if not buckets:
4709
4773
  if options.verbose:
4710
4774
  print('git cl try with no bots now defaults to CQ Dry Run.')
4711
- try:
4712
- cl.SetCQState(_CQState.DRY_RUN)
4713
- print('scheduled CQ Dry Run on %s' % cl.GetIssueURL())
4714
- return 0
4715
- except KeyboardInterrupt:
4716
- raise
4717
- except:
4718
- print('WARNING: failed to trigger CQ Dry Run.\n'
4719
- 'Either:\n'
4720
- ' * your project has no CQ\n'
4721
- ' * you don\'t have permission to trigger Dry Run\n'
4722
- ' * bug in this code (see stack trace below).\n'
4723
- 'Consider specifying which bots to trigger manually '
4724
- 'or asking your project owners for permissions '
4725
- 'or contacting Chrome Infrastructure team at '
4726
- 'https://www.chromium.org/infra\n\n')
4727
- # Still raise exception so that stack trace is printed.
4728
- raise
4775
+ return cl.TriggerDryRun()
4729
4776
 
4730
- for builders in masters.itervalues():
4777
+ for builders in buckets.itervalues():
4731
4778
  if any('triggered' in b for b in builders):
4732
4779
  print('ERROR You are trying to send a job to a triggered bot. This type '
4733
- 'of bot requires an\ninitial job from a parent (usually a builder).'
4734
- ' Instead send your job to the parent.\n'
4780
+ 'of bot requires an initial job from a parent (usually a builder). '
4781
+ 'Instead send your job to the parent.\n'
4735
4782
  'Bot list: %s' % builders, file=sys.stderr)
4736
4783
  return 1
4737
4784
 
4738
4785
  patchset = cl.GetMostRecentPatchset()
4739
- if patchset and patchset != cl.GetPatchset():
4740
- print(
4741
- '\nWARNING Mismatch between local config and server. Did a previous '
4742
- 'upload fail?\ngit-cl try always uses latest patchset from rietveld. '
4743
- 'Continuing using\npatchset %s.\n' % patchset)
4744
- if not options.use_rietveld:
4745
- try:
4746
- trigger_try_jobs(auth_config, cl, options, masters, 'git_cl_try')
4747
- except BuildbucketResponseException as ex:
4748
- print('ERROR: %s' % ex)
4749
- return 1
4750
- except Exception as e:
4751
- stacktrace = (''.join(traceback.format_stack()) + traceback.format_exc())
4752
- print('ERROR: Exception when trying to trigger try jobs: %s\n%s' %
4753
- (e, stacktrace))
4754
- return 1
4755
- else:
4756
- try:
4757
- cl.RpcServer().trigger_distributed_try_jobs(
4758
- cl.GetIssue(), patchset, options.name, options.clobber,
4759
- options.revision, masters)
4760
- except urllib2.HTTPError as e:
4761
- if e.code == 404:
4762
- print('404 from rietveld; '
4763
- 'did you mean to use "git try" instead of "git cl try"?')
4764
- return 1
4765
- print('Tried jobs on:')
4786
+ # TODO(tandrii): Checking local patchset against remote patchset is only
4787
+ # supported for Rietveld. Extend it to Gerrit or remove it completely.
4788
+ if not cl.IsGerrit() and patchset != cl.GetPatchset():
4789
+ print('Warning: Codereview server has newer patchsets (%s) than most '
4790
+ 'recent upload from local checkout (%s). Did a previous upload '
4791
+ 'fail?\n'
4792
+ 'By default, git cl try uses the latest patchset from '
4793
+ 'codereview, continuing to use patchset %s.\n' %
4794
+ (patchset, cl.GetPatchset(), patchset))
4766
4795
 
4767
- for (master, builders) in sorted(masters.iteritems()):
4768
- if master:
4769
- print('Master: %s' % master)
4770
- length = max(len(builder) for builder in builders)
4771
- for builder in sorted(builders):
4772
- print(' %*s: %s' % (length, builder, ','.join(builders[builder])))
4796
+ try:
4797
+ _trigger_try_jobs(auth_config, cl, buckets, options, 'git_cl_try',
4798
+ patchset)
4799
+ except BuildbucketResponseException as ex:
4800
+ print('ERROR: %s' % ex)
4801
+ return 1
4773
4802
  return 0
4774
4803
 
4775
4804
 
4776
4805
  def CMDtry_results(parser, args):
4777
- group = optparse.OptionGroup(parser, "Try job results options")
4806
+ """Prints info about try jobs associated with current CL."""
4807
+ group = optparse.OptionGroup(parser, 'Try job results options')
4778
4808
  group.add_option(
4779
- "-p", "--patchset", type=int, help="patchset number if not current.")
4809
+ '-p', '--patchset', type=int, help='patchset number if not current.')
4780
4810
  group.add_option(
4781
- "--print-master", action='store_true', help="print master name as well.")
4811
+ '--print-master', action='store_true', help='print master name as well.')
4782
4812
  group.add_option(
4783
- "--color", action='store_true', default=setup_color.IS_TTY,
4784
- help="force color output, useful when piping output.")
4813
+ '--color', action='store_true', default=setup_color.IS_TTY,
4814
+ help='force color output, useful when piping output.')
4785
4815
  group.add_option(
4786
- "--buildbucket-host", default='cr-buildbucket.appspot.com',
4787
- help="Host of buildbucket. The default host is %default.")
4816
+ '--buildbucket-host', default='cr-buildbucket.appspot.com',
4817
+ help='Host of buildbucket. The default host is %default.')
4788
4818
  group.add_option(
4789
4819
  '--json', help='Path of JSON output file to write try job results to.')
4790
4820
  parser.add_option_group(group)
@@ -4798,23 +4828,29 @@ def CMDtry_results(parser, args):
4798
4828
  if not cl.GetIssue():
4799
4829
  parser.error('Need to upload first')
4800
4830
 
4801
- if not options.patchset:
4802
- options.patchset = cl.GetMostRecentPatchset()
4803
- if options.patchset and options.patchset != cl.GetPatchset():
4804
- print(
4805
- '\nWARNING Mismatch between local config and server. Did a previous '
4806
- 'upload fail?\ngit-cl try always uses latest patchset from rietveld. '
4807
- 'Continuing using\npatchset %s.\n' % options.patchset)
4831
+ patchset = options.patchset
4832
+ if not patchset:
4833
+ patchset = cl.GetMostRecentPatchset()
4834
+ if not patchset:
4835
+ parser.error('Codereview doesn\'t know about issue %s. '
4836
+ 'No access to issue or wrong issue number?\n'
4837
+ 'Either upload first, or pass --patchset explicitely' %
4838
+ cl.GetIssue())
4839
+
4840
+ # TODO(tandrii): Checking local patchset against remote patchset is only
4841
+ # supported for Rietveld. Extend it to Gerrit or remove it completely.
4842
+ if not cl.IsGerrit() and patchset != cl.GetPatchset():
4843
+ print('Warning: Codereview server has newer patchsets (%s) than most '
4844
+ 'recent upload from local checkout (%s). Did a previous upload '
4845
+ 'fail?\n'
4846
+ 'By default, git cl try-results uses the latest patchset from '
4847
+ 'codereview, continuing to use patchset %s.\n' %
4848
+ (patchset, cl.GetPatchset(), patchset))
4808
4849
  try:
4809
- jobs = fetch_try_jobs(auth_config, cl, options)
4850
+ jobs = fetch_try_jobs(auth_config, cl, options.buildbucket_host, patchset)
4810
4851
  except BuildbucketResponseException as ex:
4811
4852
  print('Buildbucket error: %s' % ex)
4812
4853
  return 1
4813
- except Exception as e:
4814
- stacktrace = (''.join(traceback.format_stack()) + traceback.format_exc())
4815
- print('ERROR: Exception when trying to fetch try jobs: %s\n%s' %
4816
- (e, stacktrace))
4817
- return 1
4818
4854
  if options.json:
4819
4855
  write_try_results_json(options.json, jobs)
4820
4856
  else:
@@ -4833,7 +4869,7 @@ def CMDupstream(parser, args):
4833
4869
  if args:
4834
4870
  # One arg means set upstream branch.
4835
4871
  branch = cl.GetBranch()
4836
- RunGit(['branch', '--set-upstream', branch, args[0]])
4872
+ RunGit(['branch', '--set-upstream-to', args[0], branch])
4837
4873
  cl = Changelist()
4838
4874
  print('Upstream branch set to %s' % (cl.GetUpstreamBranch(),))
4839
4875
 
@@ -4880,6 +4916,7 @@ def CMDset_commit(parser, args):
4880
4916
  if options.clear:
4881
4917
  state = _CQState.NONE
4882
4918
  elif options.dry_run:
4919
+ # TODO(qyearsley): Use cl.TriggerDryRun.
4883
4920
  state = _CQState.DRY_RUN
4884
4921
  else:
4885
4922
  state = _CQState.COMMIT
@@ -4995,7 +5032,7 @@ def BuildGitDiffCmd(diff_type, upstream_commit, args):
4995
5032
  """Generates a diff command."""
4996
5033
  # Generate diff for the current branch's changes.
4997
5034
  diff_cmd = ['diff', '--no-ext-diff', '--no-prefix', diff_type,
4998
- upstream_commit, '--' ]
5035
+ upstream_commit, '--']
4999
5036
 
5000
5037
  if args:
5001
5038
  for arg in args:
@@ -5006,10 +5043,12 @@ def BuildGitDiffCmd(diff_type, upstream_commit, args):
5006
5043
 
5007
5044
  return diff_cmd
5008
5045
 
5046
+
5009
5047
  def MatchingFileType(file_name, extensions):
5010
5048
  """Returns true if the file name ends with one of the given extensions."""
5011
5049
  return bool([ext for ext in extensions if file_name.lower().endswith(ext)])
5012
5050
 
5051
+
5013
5052
  @subcommand.usage('[files or directories to diff]')
5014
5053
  def CMDformat(parser, args):
5015
5054
  """Runs auto-formatting tools (clang-format etc.) on the diff."""
@@ -5021,10 +5060,16 @@ def CMDformat(parser, args):
5021
5060
  help='Don\'t modify any file on disk.')
5022
5061
  parser.add_option('--python', action='store_true',
5023
5062
  help='Format python code with yapf (experimental).')
5063
+ parser.add_option('--js', action='store_true',
5064
+ help='Format javascript code with clang-format.')
5024
5065
  parser.add_option('--diff', action='store_true',
5025
5066
  help='Print diff to stdout rather than modifying files.')
5026
5067
  opts, args = parser.parse_args(args)
5027
5068
 
5069
+ # Normalize any remaining args against the current path, so paths relative to
5070
+ # the current directory are still resolved as expected.
5071
+ args = [os.path.join(os.getcwd(), arg) for arg in args]
5072
+
5028
5073
  # git diff generates paths against the root of the repository. Change
5029
5074
  # to that directory so clang-format can find files even within subdirs.
5030
5075
  rel_base_path = settings.GetRelativeRoot()
@@ -5052,6 +5097,9 @@ def CMDformat(parser, args):
5052
5097
  # Filter out files deleted by this CL
5053
5098
  diff_files = [x for x in diff_files if os.path.isfile(x)]
5054
5099
 
5100
+ if opts.js:
5101
+ CLANG_EXTS.append('.js')
5102
+
5055
5103
  clang_diff_files = [x for x in diff_files if MatchingFileType(x, CLANG_EXTS)]
5056
5104
  python_diff_files = [x for x in diff_files if MatchingFileType(x, ['.py'])]
5057
5105
  dart_diff_files = [x for x in diff_files if MatchingFileType(x, ['.dart'])]
@@ -5139,7 +5187,7 @@ def CMDformat(parser, args):
5139
5187
 
5140
5188
  # Format GN build files. Always run on full build files for canonical form.
5141
5189
  if gn_diff_files:
5142
- cmd = ['gn', 'format' ]
5190
+ cmd = ['gn', 'format']
5143
5191
  if opts.dry_run or opts.diff:
5144
5192
  cmd.append('--dry-run')
5145
5193
  for gn_diff_file in gn_diff_files:
@@ -5158,6 +5206,22 @@ def CMDformat(parser, args):
5158
5206
  DieWithError("gn format failed on " + gn_diff_file +
5159
5207
  "\nTry running 'gn format' on this file manually.")
5160
5208
 
5209
+ metrics_xml_files = [
5210
+ 'tools/metrics/actions/actions.xml',
5211
+ 'tools/metrics/histograms/histograms.xml',
5212
+ 'tools/metrics/rappor/rappor.xml']
5213
+ for xml_file in metrics_xml_files:
5214
+ if xml_file in diff_files:
5215
+ tool_dir = top_dir + '/' + os.path.dirname(xml_file)
5216
+ cmd = [tool_dir + '/pretty_print.py', '--non-interactive']
5217
+ if opts.dry_run or opts.diff:
5218
+ cmd.append('--diff')
5219
+ stdout = RunCommand(cmd, cwd=top_dir)
5220
+ if opts.diff:
5221
+ sys.stdout.write(stdout)
5222
+ if opts.dry_run and stdout:
5223
+ return_value = 2 # Not formatted.
5224
+
5161
5225
  return return_value
5162
5226
 
5163
5227
 
@@ -5228,7 +5292,10 @@ class OptionParser(optparse.OptionParser):
5228
5292
  def parse_args(self, args=None, values=None):
5229
5293
  options, args = optparse.OptionParser.parse_args(self, args, values)
5230
5294
  levels = [logging.WARNING, logging.INFO, logging.DEBUG]
5231
- logging.basicConfig(level=levels[min(options.verbose, len(levels) - 1)])
5295
+ logging.basicConfig(
5296
+ level=levels[min(options.verbose, len(levels) - 1)],
5297
+ format='[%(levelname).1s%(asctime)s %(process)d %(thread)d '
5298
+ '%(filename)s] %(message)s')
5232
5299
  return options, args
5233
5300
 
5234
5301