libv8 5.7.492.65.1 → 5.9.211.38.0beta0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (297) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +8 -2
  4. data/CHANGELOG.md +4 -0
  5. data/README.md +4 -3
  6. data/Rakefile +27 -10
  7. data/ext/libv8/builder.rb +4 -0
  8. data/lib/libv8/version.rb +1 -1
  9. data/libv8.gemspec +1 -1
  10. data/patches/0001-Build-a-standalone-static-library.patch +5 -5
  11. data/patches/0002-Don-t-compile-unnecessary-stuff.patch +20 -24
  12. data/patches/0003-Use-the-fPIC-flag-for-the-static-library.patch +5 -5
  13. data/patches/0004-Do-not-embed-debug-symbols-in-macOS-libraries.patch +5 -5
  14. data/patches/0005-Fix-GCC-7-build-errors.patch +147 -0
  15. data/scaleway.png +0 -0
  16. data/vendor/depot_tools/.gitattributes +52 -0
  17. data/vendor/depot_tools/.gitignore +10 -0
  18. data/vendor/depot_tools/README.md +7 -3
  19. data/vendor/depot_tools/apply_issue.bat +4 -3
  20. data/vendor/depot_tools/apply_issue.py +0 -10
  21. data/vendor/depot_tools/autoninja +12 -0
  22. data/vendor/depot_tools/autoninja.bat +9 -0
  23. data/vendor/depot_tools/autoninja.py +70 -0
  24. data/vendor/depot_tools/bootstrap/win/README.md +108 -26
  25. data/vendor/depot_tools/bootstrap/win/git-bash.template.sh +3 -3
  26. data/vendor/depot_tools/bootstrap/win/git.template.bat +2 -2
  27. data/vendor/depot_tools/bootstrap/win/manifest.txt +18 -0
  28. data/vendor/depot_tools/bootstrap/win/manifest_bleeding_edge.txt +18 -0
  29. data/vendor/depot_tools/bootstrap/win/python27.new.bat +49 -0
  30. data/vendor/depot_tools/bootstrap/win/win_tools.bat +55 -59
  31. data/vendor/depot_tools/bootstrap/win/win_tools.py +335 -0
  32. data/vendor/depot_tools/cipd +16 -2
  33. data/vendor/depot_tools/cipd.bat +20 -2
  34. data/vendor/depot_tools/cipd.ps1 +36 -22
  35. data/vendor/depot_tools/cipd_bin_setup.bat +6 -0
  36. data/vendor/depot_tools/cipd_bin_setup.sh +10 -0
  37. data/vendor/depot_tools/cipd_client_version +1 -1
  38. data/vendor/depot_tools/cipd_manifest.txt +9 -0
  39. data/vendor/depot_tools/cit.bat +12 -11
  40. data/vendor/depot_tools/clang-format.bat +4 -3
  41. data/vendor/depot_tools/clang_format_merge_driver.bat +12 -11
  42. data/vendor/depot_tools/commit_queue.bat +4 -3
  43. data/vendor/depot_tools/cpplint.bat +9 -3
  44. data/vendor/depot_tools/cpplint.py +3 -3
  45. data/vendor/depot_tools/depot-tools-auth.bat +4 -3
  46. data/vendor/depot_tools/download_from_google_storage.bat +5 -3
  47. data/vendor/depot_tools/download_from_google_storage.py +6 -1
  48. data/vendor/depot_tools/fetch.bat +5 -4
  49. data/vendor/depot_tools/fetch.py +4 -5
  50. data/vendor/depot_tools/gclient-new-workdir.py +82 -46
  51. data/vendor/depot_tools/gclient.bat +5 -4
  52. data/vendor/depot_tools/gclient.py +713 -319
  53. data/vendor/depot_tools/gclient_eval.py +284 -0
  54. data/vendor/depot_tools/gclient_utils.py +70 -4
  55. data/vendor/depot_tools/gerrit_client.py +26 -1
  56. data/vendor/depot_tools/gerrit_util.py +120 -127
  57. data/vendor/depot_tools/git-crrev-parse +1 -0
  58. data/vendor/depot_tools/git-gs +1 -1
  59. data/vendor/depot_tools/git_cl.py +731 -415
  60. data/vendor/depot_tools/git_common.py +23 -3
  61. data/vendor/depot_tools/git_drover.py +10 -1
  62. data/vendor/depot_tools/git_footers.py +62 -22
  63. data/vendor/depot_tools/git_hyper_blame.py +3 -2
  64. data/vendor/depot_tools/git_map.py +30 -3
  65. data/vendor/depot_tools/git_map_branches.py +18 -4
  66. data/vendor/depot_tools/git_number.py +8 -2
  67. data/vendor/depot_tools/git_retry.py +21 -0
  68. data/vendor/depot_tools/gn.bat +4 -3
  69. data/vendor/depot_tools/infra/config/cq.cfg +1 -5
  70. data/vendor/depot_tools/infra/config/recipes.cfg +18 -16
  71. data/vendor/depot_tools/led +12 -0
  72. data/vendor/depot_tools/led.bat +7 -0
  73. data/vendor/depot_tools/man/html/git-cl.html +9 -1
  74. data/vendor/depot_tools/man/html/git-drover.html +22 -18
  75. data/vendor/depot_tools/man/man1/git-cl.1 +8 -3
  76. data/vendor/depot_tools/man/man1/git-drover.1 +22 -20
  77. data/vendor/depot_tools/man/src/git-cl.txt +3 -0
  78. data/vendor/depot_tools/man/src/git-drover.txt +8 -0
  79. data/vendor/depot_tools/my_activity.py +8 -5
  80. data/vendor/depot_tools/owners.py +103 -11
  81. data/vendor/depot_tools/owners_finder.py +14 -2
  82. data/vendor/depot_tools/presubmit_canned_checks.py +25 -67
  83. data/vendor/depot_tools/presubmit_support.py +87 -35
  84. data/vendor/depot_tools/recipes/OWNERS +2 -0
  85. data/vendor/depot_tools/recipes/README.recipes.md +842 -0
  86. data/vendor/depot_tools/recipes/recipe_modules/bot_update/__init__.py +5 -3
  87. data/vendor/depot_tools/recipes/recipe_modules/bot_update/api.py +181 -60
  88. data/vendor/depot_tools/recipes/recipe_modules/bot_update/{example.expected → examples/full.expected}/apply_gerrit_ref.json +4 -2
  89. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/basic.json +82 -0
  90. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/basic_with_branch_heads.json +149 -0
  91. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/buildbot.json +82 -0
  92. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/clobber.json +149 -0
  93. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/deprecated_got_revision_mapping.json +122 -0
  94. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/gerrit_no_rebase_patch_ref.json +149 -0
  95. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/gerrit_no_reset.json +149 -0
  96. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/no_shallow.json +149 -0
  97. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/reset_root_solution_revision.json +148 -0
  98. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/trychange.json +150 -0
  99. data/vendor/depot_tools/recipes/recipe_modules/bot_update/{example.expected → examples/full.expected}/trychange_oauth2.json +0 -0
  100. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/trychange_oauth2_buildbot.json +152 -0
  101. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/trychange_oauth2_json.json +150 -0
  102. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/trychange_oauth2_json_win.json +150 -0
  103. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob.json +156 -0
  104. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_fail.json +91 -0
  105. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_fail_patch.json +118 -0
  106. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_fail_patch_download.json +118 -0
  107. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_gerrit_angle.json +202 -0
  108. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_gerrit_angle_deprecated.json +158 -0
  109. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_gerrit_feature_branch.json +196 -0
  110. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_gerrit_v8.json +202 -0
  111. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_gerrit_v8_feature_branch.json +202 -0
  112. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_v8.json +162 -0
  113. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_v8_head_by_default.json +162 -0
  114. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/with_tags.json +149 -0
  115. data/vendor/depot_tools/recipes/recipe_modules/bot_update/{example.py → examples/full.py} +46 -8
  116. data/vendor/depot_tools/recipes/recipe_modules/bot_update/resources/bot_update.py +139 -133
  117. data/vendor/depot_tools/recipes/recipe_modules/bot_update/test_api.py +25 -13
  118. data/vendor/depot_tools/recipes/recipe_modules/cipd/api.py +40 -9
  119. data/vendor/depot_tools/recipes/recipe_modules/cipd/{example.expected → examples/full.expected}/basic.json +0 -0
  120. data/vendor/depot_tools/recipes/recipe_modules/cipd/{example.expected → examples/full.expected}/basic_pkg.json +1 -1
  121. data/vendor/depot_tools/recipes/recipe_modules/cipd/{example.expected → examples/full.expected}/describe-failed.json +0 -0
  122. data/vendor/depot_tools/recipes/recipe_modules/cipd/{example.expected → examples/full.expected}/describe-many-instances.json +0 -0
  123. data/vendor/depot_tools/recipes/recipe_modules/cipd/{example.expected → examples/full.expected}/mac64.json +0 -0
  124. data/vendor/depot_tools/recipes/recipe_modules/cipd/{example.expected → examples/full.expected}/pkg_bad_file.json +1 -1
  125. data/vendor/depot_tools/recipes/recipe_modules/cipd/{example.expected → examples/full.expected}/pkg_bad_mode.json +0 -0
  126. data/vendor/depot_tools/recipes/recipe_modules/cipd/{example.expected → examples/full.expected}/pkg_bad_verfile.json +0 -0
  127. data/vendor/depot_tools/recipes/recipe_modules/cipd/{example.expected → examples/full.expected}/win64.json +0 -0
  128. data/vendor/depot_tools/recipes/recipe_modules/cipd/{example.py → examples/full.py} +1 -0
  129. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/junk arch.json +7 -0
  130. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/junk bits.json +7 -0
  131. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/linux_arm_32.json +14 -0
  132. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/linux_arm_64.json +14 -0
  133. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/linux_intel_32.json +14 -0
  134. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/linux_intel_64.json +14 -0
  135. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/linux_mips_64.json +14 -0
  136. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/mac_intel_64.json +14 -0
  137. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/win_intel_32.json +14 -0
  138. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/win_intel_64.json +14 -0
  139. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.py +59 -0
  140. data/vendor/depot_tools/recipes/recipe_modules/depot_tools/api.py +5 -0
  141. data/vendor/depot_tools/recipes/recipe_modules/depot_tools/{example.expected → examples/full.expected}/basic.json +7 -0
  142. data/vendor/depot_tools/recipes/recipe_modules/depot_tools/{example.expected → examples/full.expected}/win.json +7 -0
  143. data/vendor/depot_tools/recipes/recipe_modules/depot_tools/{example.py → examples/full.py} +2 -0
  144. data/vendor/depot_tools/recipes/recipe_modules/gclient/__init__.py +1 -4
  145. data/vendor/depot_tools/recipes/recipe_modules/gclient/api.py +28 -14
  146. data/vendor/depot_tools/recipes/recipe_modules/gclient/config.py +10 -235
  147. data/vendor/depot_tools/recipes/recipe_modules/gclient/{example.expected → examples/full.expected}/basic.json +9 -9
  148. data/vendor/depot_tools/recipes/recipe_modules/gclient/{example.expected → examples/full.expected}/buildbot.json +9 -9
  149. data/vendor/depot_tools/recipes/recipe_modules/gclient/{example.expected → examples/full.expected}/revision.json +9 -9
  150. data/vendor/depot_tools/recipes/recipe_modules/gclient/{example.expected → examples/full.expected}/tryserver.json +9 -9
  151. data/vendor/depot_tools/recipes/recipe_modules/gclient/{example.py → examples/full.py} +5 -21
  152. data/vendor/depot_tools/recipes/recipe_modules/gclient/tests/patch_project.py +45 -0
  153. data/vendor/depot_tools/recipes/recipe_modules/gerrit/__init__.py +1 -0
  154. data/vendor/depot_tools/recipes/recipe_modules/gerrit/api.py +97 -2
  155. data/vendor/depot_tools/recipes/recipe_modules/gerrit/examples/full.expected/basic.json +283 -0
  156. data/vendor/depot_tools/recipes/recipe_modules/gerrit/{example.py → examples/full.py} +31 -2
  157. data/vendor/depot_tools/recipes/recipe_modules/gerrit/test_api.py +30 -1
  158. data/vendor/depot_tools/recipes/recipe_modules/git/__init__.py +1 -4
  159. data/vendor/depot_tools/recipes/recipe_modules/git/api.py +7 -35
  160. data/vendor/depot_tools/recipes/recipe_modules/git/{example.expected → examples/full.expected}/basic.json +3 -3
  161. data/vendor/depot_tools/recipes/recipe_modules/git/{example.expected → examples/full.expected}/basic_branch.json +3 -3
  162. data/vendor/depot_tools/recipes/recipe_modules/git/{example.expected → examples/full.expected}/basic_file_name.json +3 -3
  163. data/vendor/depot_tools/recipes/recipe_modules/git/{example.expected → examples/full.expected}/basic_hash.json +3 -3
  164. data/vendor/depot_tools/recipes/recipe_modules/git/{example.expected → examples/full.expected}/basic_ref.json +3 -3
  165. data/vendor/depot_tools/recipes/recipe_modules/git/{example.expected → examples/full.expected}/basic_submodule_update_force.json +3 -3
  166. data/vendor/depot_tools/recipes/recipe_modules/git/{example.expected → examples/full.expected}/can_fail_build.json +1 -1
  167. data/vendor/depot_tools/recipes/recipe_modules/git/{example.expected → examples/full.expected}/cannot_fail_build.json +3 -3
  168. data/vendor/depot_tools/recipes/recipe_modules/git/{example.expected → examples/full.expected}/cat-file_test.json +3 -3
  169. data/vendor/depot_tools/recipes/recipe_modules/git/{example.expected → examples/full.expected}/count-objects_delta.json +3 -3
  170. data/vendor/depot_tools/recipes/recipe_modules/git/{example.expected → examples/full.expected}/count-objects_failed.json +3 -3
  171. data/vendor/depot_tools/recipes/recipe_modules/git/{example.expected → examples/full.expected}/count-objects_with_bad_output.json +3 -3
  172. data/vendor/depot_tools/recipes/recipe_modules/git/{example.expected → examples/full.expected}/count-objects_with_bad_output_fails_build.json +1 -1
  173. data/vendor/depot_tools/recipes/recipe_modules/git/{example.expected → examples/full.expected}/curl_trace_file.json +3 -3
  174. data/vendor/depot_tools/recipes/recipe_modules/git/{example.expected → examples/full.expected}/git-cache-checkout.json +6 -6
  175. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/platform_win.json +223 -0
  176. data/vendor/depot_tools/recipes/recipe_modules/git/{example.expected → examples/full.expected}/rebase_failed.json +3 -3
  177. data/vendor/depot_tools/recipes/recipe_modules/git/{example.expected → examples/full.expected}/remote_not_origin.json +3 -3
  178. data/vendor/depot_tools/recipes/recipe_modules/git/{example.expected → examples/full.expected}/set_got_revision.json +3 -3
  179. data/vendor/depot_tools/recipes/recipe_modules/git/{example.py → examples/full.py} +2 -1
  180. data/vendor/depot_tools/recipes/recipe_modules/git/resources/git_setup.py +12 -21
  181. data/vendor/depot_tools/recipes/recipe_modules/git_cl/__init__.py +1 -4
  182. data/vendor/depot_tools/recipes/recipe_modules/git_cl/api.py +13 -11
  183. data/vendor/depot_tools/recipes/recipe_modules/git_cl/{example.expected → examples/full.expected}/basic.json +0 -0
  184. data/vendor/depot_tools/recipes/recipe_modules/git_cl/{example.py → examples/full.py} +4 -2
  185. data/vendor/depot_tools/recipes/recipe_modules/gitiles/OWNERS +3 -0
  186. data/vendor/depot_tools/recipes/recipe_modules/gitiles/__init__.py +7 -0
  187. data/vendor/depot_tools/recipes/recipe_modules/gitiles/api.py +135 -0
  188. data/vendor/depot_tools/recipes/recipe_modules/gitiles/examples/full.expected/basic.json +537 -0
  189. data/vendor/depot_tools/recipes/recipe_modules/gitiles/examples/full.py +61 -0
  190. data/vendor/depot_tools/recipes/recipe_modules/gitiles/resources/gerrit_client.py +192 -0
  191. data/vendor/depot_tools/recipes/recipe_modules/gitiles/test_api.py +95 -0
  192. data/vendor/depot_tools/recipes/recipe_modules/gsutil/{example.expected → examples/full.expected}/basic.json +0 -0
  193. data/vendor/depot_tools/recipes/recipe_modules/gsutil/{example.py → examples/full.py} +0 -0
  194. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/__init__.py +0 -3
  195. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/{example.expected → examples/full.expected}/basic.json +3 -2
  196. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/{example.expected → examples/full.expected}/paths_buildbot_linux.json +3 -2
  197. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/{example.expected → examples/full.expected}/paths_buildbot_mac.json +3 -2
  198. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/{example.expected → examples/full.expected}/paths_buildbot_win.json +3 -2
  199. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/{example.expected → examples/full.expected}/paths_generic_linux.json +3 -2
  200. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/{example.expected → examples/full.expected}/paths_generic_mac.json +3 -2
  201. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/paths_generic_win.json +16 -0
  202. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/paths_kitchen_linux.json +16 -0
  203. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/paths_kitchen_mac.json +16 -0
  204. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/paths_kitchen_win.json +16 -0
  205. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/{example.py → examples/full.py} +2 -1
  206. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/path_config.py +12 -3
  207. data/vendor/depot_tools/recipes/recipe_modules/presubmit/__init__.py +1 -0
  208. data/vendor/depot_tools/recipes/recipe_modules/presubmit/api.py +2 -2
  209. data/vendor/depot_tools/recipes/recipe_modules/presubmit/{example.expected → examples/full.expected}/basic.json +1 -1
  210. data/vendor/depot_tools/recipes/recipe_modules/presubmit/{example.py → examples/full.py} +0 -0
  211. data/vendor/depot_tools/recipes/recipe_modules/rietveld/__init__.py +0 -4
  212. data/vendor/depot_tools/recipes/recipe_modules/rietveld/{example.expected → examples/full.expected}/basic.json +0 -0
  213. data/vendor/depot_tools/recipes/recipe_modules/rietveld/{example.expected → examples/full.expected}/buildbot.json +0 -0
  214. data/vendor/depot_tools/recipes/recipe_modules/rietveld/examples/full.expected/no_auth.json +27 -0
  215. data/vendor/depot_tools/recipes/recipe_modules/rietveld/{example.py → examples/full.py} +9 -1
  216. data/vendor/depot_tools/recipes/recipe_modules/tryserver/__init__.py +3 -5
  217. data/vendor/depot_tools/recipes/recipe_modules/tryserver/api.py +21 -96
  218. data/vendor/depot_tools/recipes/recipe_modules/tryserver/{example.expected → examples/full.expected}/basic_tags.json +0 -0
  219. data/vendor/depot_tools/recipes/recipe_modules/tryserver/{example.expected → examples/full.expected}/set_failure_hash_with_no_steps.json +0 -0
  220. data/vendor/depot_tools/recipes/recipe_modules/tryserver/examples/full.expected/with_gerrit_patch.json +56 -0
  221. data/vendor/depot_tools/recipes/recipe_modules/tryserver/{example.expected/with_gerrit_patch_deprecated.json → examples/full.expected/with_git_patch.json} +2 -2
  222. data/vendor/depot_tools/recipes/recipe_modules/tryserver/{example.expected/with_gerrit_patch.json → examples/full.expected/with_git_patch_luci.json} +2 -2
  223. data/vendor/depot_tools/recipes/recipe_modules/tryserver/{example.expected → examples/full.expected}/with_rietveld_patch.json +0 -20
  224. data/vendor/depot_tools/recipes/recipe_modules/tryserver/{example.expected → examples/full.expected}/with_rietveld_patch_new.json +0 -20
  225. data/vendor/depot_tools/recipes/recipe_modules/tryserver/{example.expected → examples/full.expected}/with_wrong_patch.json +1 -13
  226. data/vendor/depot_tools/recipes/recipe_modules/tryserver/{example.expected → examples/full.expected}/with_wrong_patch_new.json +1 -13
  227. data/vendor/depot_tools/recipes/recipe_modules/tryserver/{example.py → examples/full.py} +3 -17
  228. data/vendor/depot_tools/recipes/recipes.py +141 -96
  229. data/vendor/depot_tools/rietveld.py +8 -1
  230. data/vendor/depot_tools/roll-dep-svn.bat +12 -10
  231. data/vendor/depot_tools/roll-dep.bat +5 -3
  232. data/vendor/depot_tools/scm.py +8 -1
  233. data/vendor/depot_tools/setup_color.py +0 -0
  234. data/vendor/depot_tools/split_cl.py +193 -0
  235. data/vendor/depot_tools/third_party/schema/.editorconfig +15 -0
  236. data/vendor/depot_tools/third_party/schema/.gitignore +174 -0
  237. data/vendor/depot_tools/third_party/schema/.travis.yml +37 -0
  238. data/vendor/depot_tools/third_party/schema/LICENSE-MIT +19 -0
  239. data/vendor/depot_tools/third_party/schema/MANIFEST.in +1 -0
  240. data/vendor/depot_tools/third_party/schema/README.chromium +12 -0
  241. data/vendor/depot_tools/third_party/schema/README.rst +382 -0
  242. data/vendor/depot_tools/third_party/schema/__init__.py +1 -0
  243. data/vendor/depot_tools/third_party/schema/schema.py +338 -0
  244. data/vendor/depot_tools/third_party/schema/setup.cfg +5 -0
  245. data/vendor/depot_tools/third_party/schema/setup.py +30 -0
  246. data/vendor/depot_tools/third_party/schema/test_schema.py +556 -0
  247. data/vendor/depot_tools/third_party/schema/tox.ini +33 -0
  248. data/vendor/depot_tools/third_party/upload.py +4 -0
  249. data/vendor/depot_tools/update_depot_tools +4 -16
  250. data/vendor/depot_tools/update_depot_tools.bat +4 -17
  251. data/vendor/depot_tools/update_depot_tools_toggle.py +38 -0
  252. data/vendor/depot_tools/vpython +12 -0
  253. data/vendor/depot_tools/vpython.bat +7 -0
  254. data/vendor/depot_tools/win_toolchain/get_toolchain_if_necessary.py +17 -5
  255. data/vendor/depot_tools/win_toolchain/package_from_installed.py +63 -33
  256. metadata +161 -113
  257. data/vendor/depot_tools/bootstrap/gclient.bat +0 -22
  258. data/vendor/depot_tools/bootstrap/win/get_file.js +0 -119
  259. data/vendor/depot_tools/bootstrap/win/git_bootstrap.py +0 -193
  260. data/vendor/depot_tools/bootstrap/win/git_version.txt +0 -1
  261. data/vendor/depot_tools/bootstrap/win/git_version_bleeding_edge.txt +0 -1
  262. data/vendor/depot_tools/bootstrap/win/python276.new.bat +0 -8
  263. data/vendor/depot_tools/bootstrap/win/svn.new.bat +0 -4
  264. data/vendor/depot_tools/bootstrap/win/svnversion.new.bat +0 -4
  265. data/vendor/depot_tools/bootstrap/win/unzip.js +0 -91
  266. data/vendor/depot_tools/recipes/recipe_modules/bot_update/example.expected/basic.json +0 -53
  267. data/vendor/depot_tools/recipes/recipe_modules/bot_update/example.expected/basic_output_manifest.json +0 -60
  268. data/vendor/depot_tools/recipes/recipe_modules/bot_update/example.expected/basic_with_branch_heads.json +0 -54
  269. data/vendor/depot_tools/recipes/recipe_modules/bot_update/example.expected/buildbot.json +0 -53
  270. data/vendor/depot_tools/recipes/recipe_modules/bot_update/example.expected/clobber.json +0 -54
  271. data/vendor/depot_tools/recipes/recipe_modules/bot_update/example.expected/gerrit_no_rebase_patch_ref.json +0 -54
  272. data/vendor/depot_tools/recipes/recipe_modules/bot_update/example.expected/gerrit_no_reset.json +0 -54
  273. data/vendor/depot_tools/recipes/recipe_modules/bot_update/example.expected/no_shallow.json +0 -54
  274. data/vendor/depot_tools/recipes/recipe_modules/bot_update/example.expected/reset_root_solution_revision.json +0 -53
  275. data/vendor/depot_tools/recipes/recipe_modules/bot_update/example.expected/trychange.json +0 -55
  276. data/vendor/depot_tools/recipes/recipe_modules/bot_update/example.expected/trychange_oauth2_buildbot.json +0 -57
  277. data/vendor/depot_tools/recipes/recipe_modules/bot_update/example.expected/trychange_oauth2_json.json +0 -55
  278. data/vendor/depot_tools/recipes/recipe_modules/bot_update/example.expected/trychange_oauth2_json_win.json +0 -55
  279. data/vendor/depot_tools/recipes/recipe_modules/bot_update/example.expected/tryjob.json +0 -59
  280. data/vendor/depot_tools/recipes/recipe_modules/bot_update/example.expected/tryjob_fail.json +0 -62
  281. data/vendor/depot_tools/recipes/recipe_modules/bot_update/example.expected/tryjob_fail_patch.json +0 -83
  282. data/vendor/depot_tools/recipes/recipe_modules/bot_update/example.expected/tryjob_fail_patch_download.json +0 -84
  283. data/vendor/depot_tools/recipes/recipe_modules/bot_update/example.expected/tryjob_gerrit_angle.json +0 -60
  284. data/vendor/depot_tools/recipes/recipe_modules/bot_update/example.expected/tryjob_gerrit_angle_deprecated.json +0 -60
  285. data/vendor/depot_tools/recipes/recipe_modules/bot_update/example.expected/tryjob_v8.json +0 -62
  286. data/vendor/depot_tools/recipes/recipe_modules/bot_update/example.expected/tryjob_v8_head_by_default.json +0 -62
  287. data/vendor/depot_tools/recipes/recipe_modules/gclient/bundle_extra_paths.txt +0 -4
  288. data/vendor/depot_tools/recipes/recipe_modules/gerrit/example.expected/basic.json +0 -66
  289. data/vendor/depot_tools/recipes/recipe_modules/git/bundle_extra_paths.txt +0 -28
  290. data/vendor/depot_tools/recipes/recipe_modules/git/example.expected/platform_win.json +0 -237
  291. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/example.expected/paths_generic_win.json +0 -15
  292. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/example.expected/paths_kitchen_linux.json +0 -15
  293. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/example.expected/paths_kitchen_mac.json +0 -15
  294. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/example.expected/paths_kitchen_win.json +0 -15
  295. data/vendor/depot_tools/recipes/recipe_modules/presubmit/bundle_extra_paths.txt +0 -30
  296. data/vendor/depot_tools/recipes/recipe_modules/tryserver/example.expected/with_git_patch.json +0 -109
  297. data/vendor/depot_tools/recipes/recipe_modules/tryserver/example.expected/with_git_patch_luci.json +0 -8
@@ -39,6 +39,7 @@ while [ -n "$1" ]; do
39
39
  fi
40
40
  remote_ref="${ref/refs\/heads/refs\/remotes\/origin}"
41
41
  remote_ref="${remote_ref/refs\/branch-heads/refs\/remotes\/branch-heads}"
42
+ remote_ref="${remote_ref//\\}"
42
43
  num="${commit_pos#*@\{\#}"
43
44
  num="${num%\}}"
44
45
  if [ -z "$ref" -o -z "$num" ]; then
@@ -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" "*.mojom"
9
+ "*.json" "DEPS" "*/DEPS" "*.mojom" "*.proto"
@@ -59,6 +59,7 @@ import owners_finder
59
59
  import presubmit_support
60
60
  import rietveld
61
61
  import scm
62
+ import split_cl
62
63
  import subcommand
63
64
  import subprocess2
64
65
  import watchlists
@@ -90,7 +91,7 @@ settings = None
90
91
  # Used by tests/git_cl_test.py to add extra logging.
91
92
  # Inside the weirdly failing test, add this:
92
93
  # >>> self.mock(git_cl, '_IS_BEING_TESTED', True)
93
- # And scroll up to see the strack trace printed.
94
+ # And scroll up to see the stack trace printed.
94
95
  _IS_BEING_TESTED = False
95
96
 
96
97
 
@@ -273,7 +274,7 @@ def _git_set_branch_config_value(key, value, branch=None, **kwargs):
273
274
 
274
275
 
275
276
  def _get_committer_timestamp(commit):
276
- """Returns unix timestamp as integer of a committer in a commit.
277
+ """Returns Unix timestamp as integer of a committer in a commit.
277
278
 
278
279
  Commit can be whatever git show would recognize, such as HEAD, sha1 or ref.
279
280
  """
@@ -305,6 +306,7 @@ def add_git_similarity(parser):
305
306
  help='Disallows git from looking for copies.')
306
307
 
307
308
  old_parser_args = parser.parse_args
309
+
308
310
  def Parse(args):
309
311
  options, args = old_parser_args(args)
310
312
 
@@ -324,10 +326,8 @@ def add_git_similarity(parser):
324
326
  else:
325
327
  _git_set_branch_config_value('git-find-copies', bool(options.find_copies))
326
328
 
327
- print('Using %d%% similarity for rename/copy detection. '
328
- 'Override with --similarity.' % options.similarity)
329
-
330
329
  return options, args
330
+
331
331
  parser.parse_args = Parse
332
332
 
333
333
 
@@ -499,7 +499,7 @@ def _trigger_try_jobs(auth_config, changelist, buckets, options,
499
499
  issue=changelist.GetIssue(),
500
500
  patch=patchset)
501
501
 
502
- shared_parameters_properties = changelist.GetTryjobProperties(patchset)
502
+ shared_parameters_properties = changelist.GetTryJobProperties(patchset)
503
503
  shared_parameters_properties['category'] = category
504
504
  if options.clobber:
505
505
  shared_parameters_properties['clobber'] = True
@@ -611,7 +611,7 @@ def fetch_try_jobs(auth_config, changelist, buildbucket_host,
611
611
  def print_try_jobs(options, builds):
612
612
  """Prints nicely result of fetch_try_jobs."""
613
613
  if not builds:
614
- print('No try jobs scheduled')
614
+ print('No try jobs scheduled.')
615
615
  return
616
616
 
617
617
  # Make a copy, because we'll be modifying builds dictionary.
@@ -626,7 +626,7 @@ def print_try_jobs(options, builds):
626
626
  parameters = json.loads(b['parameters_json'])
627
627
  name = parameters['builder_name']
628
628
  except (ValueError, KeyError) as error:
629
- print('WARNING: failed to get builder name for build %s: %s' % (
629
+ print('WARNING: Failed to get builder name for build %s: %s' % (
630
630
  b['id'], error))
631
631
  name = None
632
632
  builder_names_cache[b['id']] = name
@@ -858,9 +858,10 @@ class Settings(object):
858
858
  return self._GetRietveldConfig('private', error_ok=True)
859
859
 
860
860
  def GetIsGerrit(self):
861
- """Return true if this repo is assosiated with gerrit code review system."""
861
+ """Return true if this repo is associated with gerrit code review system."""
862
862
  if self.is_gerrit is None:
863
- self.is_gerrit = self._GetConfig('gerrit.host', error_ok=True)
863
+ self.is_gerrit = (
864
+ self._GetConfig('gerrit.host', error_ok=True).lower() == 'true')
864
865
  return self.is_gerrit
865
866
 
866
867
  def GetSquashGerritUploads(self):
@@ -939,7 +940,7 @@ def _get_gerrit_project_config_file(remote_url):
939
940
  '+refs/meta/config:refs/git_cl/meta/config'])
940
941
  if error:
941
942
  # Ref doesn't exist or isn't accessible to current user.
942
- print('WARNING: failed to fetch project config for %s: %s' %
943
+ print('WARNING: Failed to fetch project config for %s: %s' %
943
944
  (remote_url, error))
944
945
  yield None
945
946
  return
@@ -1038,34 +1039,53 @@ class _CQState(object):
1038
1039
 
1039
1040
 
1040
1041
  class _ParsedIssueNumberArgument(object):
1041
- def __init__(self, issue=None, patchset=None, hostname=None):
1042
+ def __init__(self, issue=None, patchset=None, hostname=None, codereview=None):
1042
1043
  self.issue = issue
1043
1044
  self.patchset = patchset
1044
1045
  self.hostname = hostname
1046
+ assert codereview in (None, 'rietveld', 'gerrit')
1047
+ self.codereview = codereview
1045
1048
 
1046
1049
  @property
1047
1050
  def valid(self):
1048
1051
  return self.issue is not None
1049
1052
 
1050
1053
 
1051
- def ParseIssueNumberArgument(arg):
1054
+ def ParseIssueNumberArgument(arg, codereview=None):
1052
1055
  """Parses the issue argument and returns _ParsedIssueNumberArgument."""
1053
1056
  fail_result = _ParsedIssueNumberArgument()
1054
1057
 
1055
1058
  if arg.isdigit():
1056
- return _ParsedIssueNumberArgument(issue=int(arg))
1059
+ return _ParsedIssueNumberArgument(issue=int(arg), codereview=codereview)
1057
1060
  if not arg.startswith('http'):
1058
1061
  return fail_result
1062
+
1059
1063
  url = gclient_utils.UpgradeToHttps(arg)
1060
1064
  try:
1061
1065
  parsed_url = urlparse.urlparse(url)
1062
1066
  except ValueError:
1063
1067
  return fail_result
1064
- for cls in _CODEREVIEW_IMPLEMENTATIONS.itervalues():
1065
- tmp = cls.ParseIssueURL(parsed_url)
1066
- if tmp is not None:
1067
- return tmp
1068
- return fail_result
1068
+
1069
+ if codereview is not None:
1070
+ parsed = _CODEREVIEW_IMPLEMENTATIONS[codereview].ParseIssueURL(parsed_url)
1071
+ return parsed or fail_result
1072
+
1073
+ results = {}
1074
+ for name, cls in _CODEREVIEW_IMPLEMENTATIONS.iteritems():
1075
+ parsed = cls.ParseIssueURL(parsed_url)
1076
+ if parsed is not None:
1077
+ results[name] = parsed
1078
+
1079
+ if not results:
1080
+ return fail_result
1081
+ if len(results) == 1:
1082
+ return results.values()[0]
1083
+
1084
+ if parsed_url.netloc and parsed_url.netloc.split('.')[0].endswith('-review'):
1085
+ # This is likely Gerrit.
1086
+ return results['gerrit']
1087
+ # Choose Rietveld as before if URL can parsed by either.
1088
+ return results['rietveld']
1069
1089
 
1070
1090
 
1071
1091
  class GerritChangeNotExists(Exception):
@@ -1173,9 +1193,10 @@ class Changelist(object):
1173
1193
  return self._codereview == 'gerrit'
1174
1194
 
1175
1195
  def GetCCList(self):
1176
- """Return the users cc'd on this CL.
1196
+ """Returns the users cc'd on this CL.
1177
1197
 
1178
- Return is a string suitable for passing to git cl with the --cc flag.
1198
+ The return value is a string suitable for passing to git cl with the --cc
1199
+ flag.
1179
1200
  """
1180
1201
  if self.cc is None:
1181
1202
  base_cc = settings.GetDefaultCCList()
@@ -1190,8 +1211,8 @@ class Changelist(object):
1190
1211
  return self.cc
1191
1212
 
1192
1213
  def SetWatchers(self, watchers):
1193
- """Set the list of email addresses that should be cc'd based on the changed
1194
- files in this CL.
1214
+ """Sets the list of email addresses that should be cc'd based on the changed
1215
+ files in this CL.
1195
1216
  """
1196
1217
  self.watchers = watchers
1197
1218
 
@@ -1315,10 +1336,10 @@ class Changelist(object):
1315
1336
 
1316
1337
  if upstream_git_obj is None:
1317
1338
  if self.GetBranch() is None:
1318
- print('ERROR: unable to determine current branch (detached HEAD?)',
1339
+ print('ERROR: Unable to determine current branch (detached HEAD?)',
1319
1340
  file=sys.stderr)
1320
1341
  else:
1321
- print('ERROR: no upstream branch', file=sys.stderr)
1342
+ print('ERROR: No upstream branch.', file=sys.stderr)
1322
1343
  return False
1323
1344
 
1324
1345
  # Verify the commit we're diffing against is in our current branch.
@@ -1402,6 +1423,22 @@ class Changelist(object):
1402
1423
  return '\n'.join([wrapper.fill(line) for line in lines])
1403
1424
  return self.description
1404
1425
 
1426
+ def GetDescriptionFooters(self):
1427
+ """Returns (non_footer_lines, footers) for the commit message.
1428
+
1429
+ Returns:
1430
+ non_footer_lines (list(str)) - Simple list of description lines without
1431
+ any footer. The lines do not contain newlines, nor does the list contain
1432
+ the empty line between the message and the footers.
1433
+ footers (list(tuple(KEY, VALUE))) - List of parsed footers, e.g.
1434
+ [("Change-Id", "Ideadbeef...."), ...]
1435
+ """
1436
+ raw_description = self.GetDescription()
1437
+ msg_lines, _, footers = git_footers.split_footers(raw_description)
1438
+ if footers:
1439
+ msg_lines = msg_lines[:len(msg_lines)-1]
1440
+ return msg_lines, footers
1441
+
1405
1442
  def GetPatchset(self):
1406
1443
  """Returns the patchset number as a int or None if not set."""
1407
1444
  if self.patchset is None and not self.lookedup_patchset:
@@ -1434,6 +1471,12 @@ class Changelist(object):
1434
1471
  self._codereview_impl.CodereviewServerConfigKey(),
1435
1472
  codereview_server)
1436
1473
  else:
1474
+ desc = self.GetDescription()
1475
+ if desc and git_footers.get_footer_change_id(desc):
1476
+ print('WARNING: The change patched into this branch has a Change-Id. '
1477
+ 'Removing it.')
1478
+ RunGit(['commit', '--amend', '-m',
1479
+ git_footers.remove_footer(desc, 'Change-Id')])
1437
1480
  # Reset all of these just to be clean.
1438
1481
  reset_suffixes = [
1439
1482
  'last-upload-hash',
@@ -1497,13 +1540,36 @@ class Changelist(object):
1497
1540
  self.description = description
1498
1541
  self.has_description = True
1499
1542
 
1543
+ def UpdateDescriptionFooters(self, description_lines, footers, force=False):
1544
+ """Sets the description for this CL remotely.
1545
+
1546
+ You can get description_lines and footers with GetDescriptionFooters.
1547
+
1548
+ Args:
1549
+ description_lines (list(str)) - List of CL description lines without
1550
+ newline characters.
1551
+ footers (list(tuple(KEY, VALUE))) - List of footers, as returned by
1552
+ GetDescriptionFooters. Key must conform to the git footers format (i.e.
1553
+ `List-Of-Tokens`). It will be case-normalized so that each token is
1554
+ title-cased.
1555
+ """
1556
+ new_description = '\n'.join(description_lines)
1557
+ if footers:
1558
+ new_description += '\n'
1559
+ for k, v in footers:
1560
+ foot = '%s: %s' % (git_footers.normalize_name(k), v)
1561
+ if not git_footers.FOOTER_PATTERN.match(foot):
1562
+ raise ValueError('Invalid footer %r' % foot)
1563
+ new_description += foot + '\n'
1564
+ self.UpdateDescription(new_description, force)
1565
+
1500
1566
  def RunHook(self, committing, may_prompt, verbose, change):
1501
1567
  """Calls sys.exit() if the hook fails; returns a HookResults otherwise."""
1502
1568
  try:
1503
1569
  return presubmit_support.DoPresubmitChecks(change, committing,
1504
1570
  verbose=verbose, output_stream=sys.stdout, input_stream=sys.stdin,
1505
1571
  default_presubmit=None, may_prompt=may_prompt,
1506
- rietveld_obj=self._codereview_impl.GetRieveldObjForPresubmit(),
1572
+ rietveld_obj=self._codereview_impl.GetRietveldObjForPresubmit(),
1507
1573
  gerrit_obj=self._codereview_impl.GetGerritObjForPresubmit())
1508
1574
  except presubmit_support.PresubmitFailure as e:
1509
1575
  DieWithError(
@@ -1522,13 +1588,13 @@ class Changelist(object):
1522
1588
  DieWithError('Failed to parse issue argument "%s". '
1523
1589
  'Must be an issue number or a valid URL.' % issue_arg)
1524
1590
  return self._codereview_impl.CMDPatchWithParsedIssue(
1525
- parsed_issue_arg, reject, nocommit, directory)
1591
+ parsed_issue_arg, reject, nocommit, directory, False)
1526
1592
 
1527
1593
  def CMDUpload(self, options, git_diff_args, orig_args):
1528
1594
  """Uploads a change to codereview."""
1595
+ custom_cl_base = None
1529
1596
  if git_diff_args:
1530
- # TODO(ukai): is it ok for gerrit case?
1531
- base_branch = git_diff_args[0]
1597
+ custom_cl_base = base_branch = git_diff_args[0]
1532
1598
  else:
1533
1599
  if self.GetBranch() is None:
1534
1600
  DieWithError('Can\'t upload from detached HEAD state. Get on a branch!')
@@ -1537,6 +1603,16 @@ class Changelist(object):
1537
1603
  base_branch = self.GetCommonAncestorWithUpstream()
1538
1604
  git_diff_args = [base_branch, 'HEAD']
1539
1605
 
1606
+ # Warn about Rietveld deprecation for initial uploads to Rietveld.
1607
+ if not self.IsGerrit() and not self.GetIssue():
1608
+ print('=====================================')
1609
+ print('NOTICE: Rietveld is being deprecated. '
1610
+ 'You can upload changes to Gerrit with')
1611
+ print(' git cl upload --gerrit')
1612
+ print('or set Gerrit to be your default code review tool with')
1613
+ print(' git config gerrit.host true')
1614
+ print('=====================================')
1615
+
1540
1616
  # Fast best-effort checks to abort before running potentially
1541
1617
  # expensive hooks if uploading is likely to fail anyway. Passing these
1542
1618
  # checks does not guarantee that uploading will not fail.
@@ -1551,11 +1627,12 @@ class Changelist(object):
1551
1627
  self.SetWatchers(watchlist.GetWatchersForPaths(files))
1552
1628
 
1553
1629
  if not options.bypass_hooks:
1554
- if options.reviewers or options.tbr_owners:
1630
+ if options.reviewers or options.tbrs or options.add_owners_to:
1555
1631
  # Set the reviewer list now so that presubmit checks can access it.
1556
1632
  change_description = ChangeDescription(change.FullDescriptionText())
1557
1633
  change_description.update_reviewers(options.reviewers,
1558
- options.tbr_owners,
1634
+ options.tbrs,
1635
+ options.add_owners_to,
1559
1636
  change)
1560
1637
  change.SetDescriptionText(change_description.description)
1561
1638
  hook_results = self.RunHook(committing=False,
@@ -1583,7 +1660,7 @@ class Changelist(object):
1583
1660
  confirm_or_exit(action='upload')
1584
1661
 
1585
1662
  print_stats(options.similarity, options.find_copies, git_diff_args)
1586
- ret = self.CMDUploadChange(options, git_diff_args, change)
1663
+ ret = self.CMDUploadChange(options, git_diff_args, custom_cl_base, change)
1587
1664
  if not ret:
1588
1665
  if options.use_commit_queue:
1589
1666
  self.SetCQState(_CQState.COMMIT)
@@ -1614,51 +1691,42 @@ class Changelist(object):
1614
1691
  return ret
1615
1692
 
1616
1693
  def SetCQState(self, new_state):
1617
- """Update the CQ state for latest patchset.
1694
+ """Updates the CQ state for the latest patchset.
1618
1695
 
1619
1696
  Issue must have been already uploaded and known.
1620
1697
  """
1621
1698
  assert new_state in _CQState.ALL_STATES
1622
1699
  assert self.GetIssue()
1623
- return self._codereview_impl.SetCQState(new_state)
1624
-
1625
- def TriggerDryRun(self):
1626
- """Triggers a dry run and prints a warning on failure."""
1627
- # TODO(qyearsley): Either re-use this method in CMDset_commit
1628
- # and CMDupload, or change CMDtry to trigger dry runs with
1629
- # just SetCQState, and catch keyboard interrupt and other
1630
- # errors in that method.
1631
1700
  try:
1632
- self.SetCQState(_CQState.DRY_RUN)
1633
- print('scheduled CQ Dry Run on %s' % self.GetIssueURL())
1701
+ self._codereview_impl.SetCQState(new_state)
1634
1702
  return 0
1635
1703
  except KeyboardInterrupt:
1636
1704
  raise
1637
1705
  except:
1638
- print('WARNING: failed to trigger CQ Dry Run.\n'
1706
+ print('WARNING: Failed to %s.\n'
1639
1707
  'Either:\n'
1640
- ' * your project has no CQ\n'
1641
- ' * you don\'t have permission to trigger Dry Run\n'
1642
- ' * bug in this code (see stack trace below).\n'
1643
- 'Consider specifying which bots to trigger manually '
1644
- 'or asking your project owners for permissions '
1645
- 'or contacting Chrome Infrastructure team at '
1646
- 'https://www.chromium.org/infra\n\n')
1708
+ ' * Your project has no CQ,\n'
1709
+ ' * You don\'t have permission to change the CQ state,\n'
1710
+ ' * There\'s a bug in this code (see stack trace below).\n'
1711
+ 'Consider specifying which bots to trigger manually or asking your '
1712
+ 'project owners for permissions or contacting Chrome Infra at:\n'
1713
+ 'https://www.chromium.org/infra\n\n' %
1714
+ ('cancel CQ' if new_state == _CQState.NONE else 'trigger CQ'))
1647
1715
  # Still raise exception so that stack trace is printed.
1648
1716
  raise
1649
1717
 
1650
1718
  # Forward methods to codereview specific implementation.
1651
1719
 
1652
- def AddComment(self, message):
1653
- return self._codereview_impl.AddComment(message)
1720
+ def AddComment(self, message, publish=None):
1721
+ return self._codereview_impl.AddComment(message, publish=publish)
1654
1722
 
1655
- def GetCommentsSummary(self):
1723
+ def GetCommentsSummary(self, readable=True):
1656
1724
  """Returns list of _CommentSummary for each comment.
1657
1725
 
1658
- Note: comments per file or per line are not included,
1659
- only top-level comments are returned.
1726
+ args:
1727
+ readable: determines whether the output is designed for a human or a machine
1660
1728
  """
1661
- return self._codereview_impl.GetCommentsSummary()
1729
+ return self._codereview_impl.GetCommentsSummary(readable)
1662
1730
 
1663
1731
  def CloseIssue(self):
1664
1732
  return self._codereview_impl.CloseIssue()
@@ -1673,19 +1741,16 @@ class Changelist(object):
1673
1741
  """Get owner from codereview, which may differ from this checkout."""
1674
1742
  return self._codereview_impl.GetIssueOwner()
1675
1743
 
1676
- def GetApprovingReviewers(self):
1677
- return self._codereview_impl.GetApprovingReviewers()
1678
-
1679
1744
  def GetMostRecentPatchset(self):
1680
1745
  return self._codereview_impl.GetMostRecentPatchset()
1681
1746
 
1682
1747
  def CannotTriggerTryJobReason(self):
1683
- """Returns reason (str) if unable trigger tryjobs on this CL or None."""
1748
+ """Returns reason (str) if unable trigger try jobs on this CL or None."""
1684
1749
  return self._codereview_impl.CannotTriggerTryJobReason()
1685
1750
 
1686
- def GetTryjobProperties(self, patchset=None):
1687
- """Returns dictionary of properties to launch tryjob."""
1688
- return self._codereview_impl.GetTryjobProperties(patchset=patchset)
1751
+ def GetTryJobProperties(self, patchset=None):
1752
+ """Returns dictionary of properties to launch try job."""
1753
+ return self._codereview_impl.GetTryJobProperties(patchset=patchset)
1689
1754
 
1690
1755
  def __getattr__(self, attr):
1691
1756
  # This is because lots of untested code accesses Rietveld-specific stuff
@@ -1742,12 +1807,12 @@ class _ChangelistCodereviewBase(object):
1742
1807
  raise NotImplementedError()
1743
1808
 
1744
1809
  def _PostUnsetIssueProperties(self):
1745
- """Which branch-specific properties to erase when unsettin issue."""
1810
+ """Which branch-specific properties to erase when unsetting issue."""
1746
1811
  return []
1747
1812
 
1748
- def GetRieveldObjForPresubmit(self):
1813
+ def GetRietveldObjForPresubmit(self):
1749
1814
  # This is an unfortunate Rietveld-embeddedness in presubmit.
1750
- # For non-Rietveld codereviews, this probably should return a dummy object.
1815
+ # For non-Rietveld code reviews, this probably should return a dummy object.
1751
1816
  raise NotImplementedError()
1752
1817
 
1753
1818
  def GetGerritObjForPresubmit(self):
@@ -1758,30 +1823,23 @@ class _ChangelistCodereviewBase(object):
1758
1823
  """Update the description on codereview site."""
1759
1824
  raise NotImplementedError()
1760
1825
 
1761
- def AddComment(self, message):
1826
+ def AddComment(self, message, publish=None):
1762
1827
  """Posts a comment to the codereview site."""
1763
1828
  raise NotImplementedError()
1764
1829
 
1765
- def GetCommentsSummary(self):
1830
+ def GetCommentsSummary(self, readable=True):
1766
1831
  raise NotImplementedError()
1767
1832
 
1768
1833
  def CloseIssue(self):
1769
1834
  """Closes the issue."""
1770
1835
  raise NotImplementedError()
1771
1836
 
1772
- def GetApprovingReviewers(self):
1773
- """Returns a list of reviewers approving the change.
1774
-
1775
- Note: not necessarily committers.
1776
- """
1777
- raise NotImplementedError()
1778
-
1779
1837
  def GetMostRecentPatchset(self):
1780
1838
  """Returns the most recent patchset number from the codereview site."""
1781
1839
  raise NotImplementedError()
1782
1840
 
1783
1841
  def CMDPatchWithParsedIssue(self, parsed_issue_arg, reject, nocommit,
1784
- directory):
1842
+ directory, force):
1785
1843
  """Fetches and applies the issue.
1786
1844
 
1787
1845
  Arguments:
@@ -1791,6 +1849,7 @@ class _ChangelistCodereviewBase(object):
1791
1849
  nocommit: do not commit the patch, thus leave the tree dirty. Rietveld
1792
1850
  only.
1793
1851
  directory: switch to directory before applying the patch. Rietveld only.
1852
+ force: if true, overwrites existing local state.
1794
1853
  """
1795
1854
  raise NotImplementedError()
1796
1855
 
@@ -1822,29 +1881,30 @@ class _ChangelistCodereviewBase(object):
1822
1881
  """
1823
1882
  raise NotImplementedError()
1824
1883
 
1825
- def CMDUploadChange(self, options, args, change):
1884
+ def CMDUploadChange(self, options, git_diff_args, custom_cl_base, change):
1826
1885
  """Uploads a change to codereview."""
1827
1886
  raise NotImplementedError()
1828
1887
 
1829
1888
  def SetCQState(self, new_state):
1830
- """Update the CQ state for latest patchset.
1889
+ """Updates the CQ state for the latest patchset.
1831
1890
 
1832
1891
  Issue must have been already uploaded and known.
1833
1892
  """
1834
1893
  raise NotImplementedError()
1835
1894
 
1836
1895
  def CannotTriggerTryJobReason(self):
1837
- """Returns reason (str) if unable trigger tryjobs on this CL or None."""
1896
+ """Returns reason (str) if unable trigger try jobs on this CL or None."""
1838
1897
  raise NotImplementedError()
1839
1898
 
1840
1899
  def GetIssueOwner(self):
1841
1900
  raise NotImplementedError()
1842
1901
 
1843
- def GetTryjobProperties(self, patchset=None):
1902
+ def GetTryJobProperties(self, patchset=None):
1844
1903
  raise NotImplementedError()
1845
1904
 
1846
1905
 
1847
1906
  class _RietveldChangelistImpl(_ChangelistCodereviewBase):
1907
+
1848
1908
  def __init__(self, changelist, auth_config=None, codereview_host=None):
1849
1909
  super(_RietveldChangelistImpl, self).__init__(changelist)
1850
1910
  assert settings, 'must be initialized in _ChangelistCodereviewBase'
@@ -1927,8 +1987,8 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
1927
1987
  return 'CL %s is private' % self.GetIssue()
1928
1988
  return None
1929
1989
 
1930
- def GetTryjobProperties(self, patchset=None):
1931
- """Returns dictionary of properties to launch tryjob."""
1990
+ def GetTryJobProperties(self, patchset=None):
1991
+ """Returns dictionary of properties to launch try job."""
1932
1992
  project = (self.GetIssueProperties() or {}).get('project')
1933
1993
  return {
1934
1994
  'issue': self.GetIssue(),
@@ -1938,16 +1998,13 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
1938
1998
  'rietveld': self.GetCodereviewServer(),
1939
1999
  }
1940
2000
 
1941
- def GetApprovingReviewers(self):
1942
- return get_approving_reviewers(self.GetIssueProperties())
1943
-
1944
2001
  def GetIssueOwner(self):
1945
2002
  return (self.GetIssueProperties() or {}).get('owner_email')
1946
2003
 
1947
- def AddComment(self, message):
2004
+ def AddComment(self, message, publish=None):
1948
2005
  return self.RpcServer().add_comment(self.GetIssue(), message)
1949
2006
 
1950
- def GetCommentsSummary(self):
2007
+ def GetCommentsSummary(self, _readable=True):
1951
2008
  summary = []
1952
2009
  for message in self.GetIssueProperties().get('messages', []):
1953
2010
  date = datetime.datetime.strptime(message['date'], '%Y-%m-%d %H:%M:%S.%f')
@@ -1961,17 +2018,18 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
1961
2018
  return summary
1962
2019
 
1963
2020
  def GetStatus(self):
1964
- """Apply a rough heuristic to give a simple summary of an issue's review
2021
+ """Applies a rough heuristic to give a simple summary of an issue's review
1965
2022
  or CQ status, assuming adherence to a common workflow.
1966
2023
 
1967
2024
  Returns None if no issue for this branch, or one of the following keywords:
1968
- * 'error' - error from review tool (including deleted issues)
1969
- * 'unsent' - not sent for review
1970
- * 'waiting' - waiting for review
1971
- * 'reply' - waiting for owner to reply to review
1972
- * 'lgtm' - LGTM from at least one approved reviewer
1973
- * 'commit' - in the commit queue
1974
- * 'closed' - closed
2025
+ * 'error' - error from review tool (including deleted issues)
2026
+ * 'unsent' - not sent for review
2027
+ * 'waiting' - waiting for review
2028
+ * 'reply' - waiting for owner to reply to review
2029
+ * 'not lgtm' - Code-Review label has been set negatively
2030
+ * 'lgtm' - LGTM from at least one approved reviewer
2031
+ * 'commit' - in the commit queue
2032
+ * 'closed' - closed
1975
2033
  """
1976
2034
  if not self.GetIssue():
1977
2035
  return None
@@ -1988,16 +2046,15 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
1988
2046
  # Issue is in the commit queue.
1989
2047
  return 'commit'
1990
2048
 
1991
- try:
1992
- reviewers = self.GetApprovingReviewers()
1993
- except urllib2.HTTPError:
1994
- return 'error'
2049
+ messages = props.get('messages') or []
2050
+ if not messages:
2051
+ # No message was sent.
2052
+ return 'unsent'
1995
2053
 
1996
- if reviewers:
1997
- # Was LGTM'ed.
2054
+ if get_approving_reviewers(props):
1998
2055
  return 'lgtm'
1999
-
2000
- messages = props.get('messages') or []
2056
+ elif get_approving_reviewers(props, disapproval=True):
2057
+ return 'not lgtm'
2001
2058
 
2002
2059
  # Skip CQ messages that don't require owner's action.
2003
2060
  while messages and messages[-1]['sender'] == COMMIT_BOT_EMAIL:
@@ -2011,9 +2068,6 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
2011
2068
  # This is probably a CQ messages warranting user attention.
2012
2069
  break
2013
2070
 
2014
- if not messages:
2015
- # No message was sent.
2016
- return 'unsent'
2017
2071
  if messages[-1]['sender'] != props.get('owner_email'):
2018
2072
  # Non-LGTM reply from non-owner and not CQ bot.
2019
2073
  return 'reply'
@@ -2065,7 +2119,7 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
2065
2119
  def CodereviewServerConfigKey(cls):
2066
2120
  return 'rietveldserver'
2067
2121
 
2068
- def GetRieveldObjForPresubmit(self):
2122
+ def GetRietveldObjForPresubmit(self):
2069
2123
  return self.RpcServer()
2070
2124
 
2071
2125
  def SetCQState(self, new_state):
@@ -2082,7 +2136,7 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
2082
2136
  self.SetFlags({'commit': '1', 'cq_dry_run': '1'})
2083
2137
 
2084
2138
  def CMDPatchWithParsedIssue(self, parsed_issue_arg, reject, nocommit,
2085
- directory):
2139
+ directory, force):
2086
2140
  # PatchIssue should never be called with a dirty tree. It is up to the
2087
2141
  # caller to check this, but just in case we assert here since the
2088
2142
  # consequences of the caller not checking this could be dire.
@@ -2125,23 +2179,26 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
2125
2179
  return _ParsedIssueNumberArgument(
2126
2180
  issue=int(match.group(1)),
2127
2181
  patchset=int(match2.group(1)),
2128
- hostname=parsed_url.netloc)
2182
+ hostname=parsed_url.netloc,
2183
+ codereview='rietveld')
2129
2184
  # Typical url: https://domain/<issue_number>[/[other]]
2130
2185
  match = re.match('/(\d+)(/.*)?$', parsed_url.path)
2131
2186
  if match:
2132
2187
  return _ParsedIssueNumberArgument(
2133
2188
  issue=int(match.group(1)),
2134
- hostname=parsed_url.netloc)
2189
+ hostname=parsed_url.netloc,
2190
+ codereview='rietveld')
2135
2191
  # Rietveld patch: https://domain/download/issue<number>_<patchset>.diff
2136
2192
  match = re.match(r'/download/issue(\d+)_(\d+).diff$', parsed_url.path)
2137
2193
  if match:
2138
2194
  return _ParsedIssueNumberArgument(
2139
2195
  issue=int(match.group(1)),
2140
2196
  patchset=int(match.group(2)),
2141
- hostname=parsed_url.netloc)
2197
+ hostname=parsed_url.netloc,
2198
+ codereview='rietveld')
2142
2199
  return None
2143
2200
 
2144
- def CMDUploadChange(self, options, args, change):
2201
+ def CMDUploadChange(self, options, args, custom_cl_base, change):
2145
2202
  """Upload the patch to Rietveld."""
2146
2203
  upload_args = ['--assume_yes'] # Don't ask about untracked files.
2147
2204
  upload_args.extend(['--server', self.GetCodereviewServer()])
@@ -2172,10 +2229,9 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
2172
2229
  if options.title:
2173
2230
  message = options.title + '\n\n' + message
2174
2231
  change_desc = ChangeDescription(message)
2175
- if options.reviewers or options.tbr_owners:
2176
- change_desc.update_reviewers(options.reviewers,
2177
- options.tbr_owners,
2178
- change)
2232
+ if options.reviewers or options.add_owners_to:
2233
+ change_desc.update_reviewers(options.reviewers, options.tbrs,
2234
+ options.add_owners_to, change)
2179
2235
  if not options.force:
2180
2236
  change_desc.prompt(bug=options.bug, git_footer=False)
2181
2237
 
@@ -2261,6 +2317,11 @@ class _RietveldChangelistImpl(_ChangelistCodereviewBase):
2261
2317
  project = settings.GetProject()
2262
2318
  if project:
2263
2319
  upload_args.extend(['--project', project])
2320
+ else:
2321
+ print()
2322
+ print('WARNING: Uploading without a project specified. Please ensure '
2323
+ 'your repo\'s codereview.settings has a "PROJECT: foo" line.')
2324
+ print()
2264
2325
 
2265
2326
  try:
2266
2327
  upload_args = ['upload'] + upload_args + args
@@ -2307,7 +2368,7 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2307
2368
  # This happens for internal stuff http://crbug.com/614312.
2308
2369
  parsed = urlparse.urlparse(self.GetRemoteUrl())
2309
2370
  if parsed.scheme == 'sso':
2310
- print('WARNING: using non https URLs for remote is likely broken\n'
2371
+ print('WARNING: using non-https URLs for remote is likely broken\n'
2311
2372
  ' Your current remote is: %s' % self.GetRemoteUrl())
2312
2373
  self._gerrit_host = '%s.googlesource.com' % self._gerrit_host
2313
2374
  self._gerrit_server = 'https://%s' % self._gerrit_host
@@ -2366,22 +2427,25 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2366
2427
  if gerrit_auth and git_auth:
2367
2428
  if gerrit_auth == git_auth:
2368
2429
  return
2430
+ all_gsrc = cookie_auth.get_auth_header('d0esN0tEx1st.googlesource.com')
2369
2431
  print((
2370
- 'WARNING: you have different credentials for Gerrit and git hosts.\n'
2371
- ' Check your %s or %s file for credentials of hosts:\n'
2432
+ 'WARNING: You have different credentials for Gerrit and git hosts:\n'
2372
2433
  ' %s\n'
2373
2434
  ' %s\n'
2374
- ' %s') %
2375
- (cookie_auth.get_gitcookies_path(), cookie_auth.get_netrc_path(),
2376
- git_host, self._gerrit_host,
2435
+ ' Consider running the following command:\n'
2436
+ ' git cl creds-check\n'
2437
+ ' %s\n'
2438
+ ' %s') %
2439
+ (git_host, self._gerrit_host,
2440
+ ('Hint: delete creds for .googlesource.com' if all_gsrc else ''),
2377
2441
  cookie_auth.get_new_password_message(git_host)))
2378
2442
  if not force:
2379
2443
  confirm_or_exit('If you know what you are doing', action='continue')
2380
2444
  return
2381
2445
  else:
2382
2446
  missing = (
2383
- [] if gerrit_auth else [self._gerrit_host] +
2384
- [] if git_auth else [git_host])
2447
+ ([] if gerrit_auth else [self._gerrit_host]) +
2448
+ ([] if git_auth else [git_host]))
2385
2449
  DieWithError('Credentials for the following hosts are required:\n'
2386
2450
  ' %s\n'
2387
2451
  'These are read from %s (or legacy %s)\n'
@@ -2414,13 +2478,13 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2414
2478
  return
2415
2479
  logging.debug('change %s owner is %s, cookies user is %s',
2416
2480
  self.GetIssue(), self.GetIssueOwner(), cookies_user)
2417
- # Maybe user has linked accounts or smth like that,
2481
+ # Maybe user has linked accounts or something like that,
2418
2482
  # so ask what Gerrit thinks of this user.
2419
2483
  details = gerrit_util.GetAccountDetails(self._GetGerritHost(), 'self')
2420
2484
  if details['email'] == self.GetIssueOwner():
2421
2485
  return
2422
2486
  if not force:
2423
- print('WARNING: change %s is owned by %s, but you authenticate to Gerrit '
2487
+ print('WARNING: Change %s is owned by %s, but you authenticate to Gerrit '
2424
2488
  'as %s.\n'
2425
2489
  'Uploading may fail due to lack of permissions.' %
2426
2490
  (self.GetIssue(), self.GetIssueOwner(), details['email']))
@@ -2431,7 +2495,7 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2431
2495
  """Which branch-specific properties to erase when unsetting issue."""
2432
2496
  return ['gerritsquashhash']
2433
2497
 
2434
- def GetRieveldObjForPresubmit(self):
2498
+ def GetRietveldObjForPresubmit(self):
2435
2499
  class ThisIsNotRietveldIssue(object):
2436
2500
  def __nonzero__(self):
2437
2501
  # This is a hack to make presubmit_support think that rietveld is not
@@ -2443,7 +2507,7 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2443
2507
  print(
2444
2508
  'You aren\'t using Rietveld at the moment, but Gerrit.\n'
2445
2509
  'Using Rietveld in your PRESUBMIT scripts won\'t work.\n'
2446
- 'Please, either change your PRESUBIT to not use rietveld_obj.%s,\n'
2510
+ 'Please, either change your PRESUBMIT to not use rietveld_obj.%s,\n'
2447
2511
  'or use Rietveld for codereview.\n'
2448
2512
  'See also http://crbug.com/579160.' % attr)
2449
2513
  raise NotImplementedError()
@@ -2457,62 +2521,54 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2457
2521
  or CQ status, assuming adherence to a common workflow.
2458
2522
 
2459
2523
  Returns None if no issue for this branch, or one of the following keywords:
2460
- * 'error' - error from review tool (including deleted issues)
2461
- * 'unsent' - no reviewers added
2462
- * 'waiting' - waiting for review
2463
- * 'reply' - waiting for owner to reply to review
2464
- * 'not lgtm' - Code-Review disapproval from at least one valid reviewer
2465
- * 'lgtm' - Code-Review approval from at least one valid reviewer
2466
- * 'commit' - in the commit queue
2467
- * 'closed' - abandoned
2524
+ * 'error' - error from review tool (including deleted issues)
2525
+ * 'unsent' - no reviewers added
2526
+ * 'waiting' - waiting for review
2527
+ * 'reply' - waiting for uploader to reply to review
2528
+ * 'lgtm' - Code-Review label has been set
2529
+ * 'commit' - in the commit queue
2530
+ * 'closed' - successfully submitted or abandoned
2468
2531
  """
2469
2532
  if not self.GetIssue():
2470
2533
  return None
2471
2534
 
2472
2535
  try:
2473
- data = self._GetChangeDetail(['DETAILED_LABELS', 'CURRENT_REVISION'])
2536
+ data = self._GetChangeDetail([
2537
+ 'DETAILED_LABELS', 'CURRENT_REVISION', 'SUBMITTABLE'])
2474
2538
  except (httplib.HTTPException, GerritChangeNotExists):
2475
2539
  return 'error'
2476
2540
 
2477
2541
  if data['status'] in ('ABANDONED', 'MERGED'):
2478
2542
  return 'closed'
2479
2543
 
2480
- cq_label = data['labels'].get('Commit-Queue', {})
2481
- if cq_label:
2482
- votes = cq_label.get('all', [])
2483
- highest_vote = 0
2484
- for v in votes:
2485
- highest_vote = max(highest_vote, v.get('value', 0))
2486
- vote_value = str(highest_vote)
2487
- if vote_value != '0':
2488
- # Add a '+' if the value is not 0 to match the values in the label.
2489
- # The cq_label does not have negatives.
2490
- vote_value = '+' + vote_value
2491
- vote_text = cq_label.get('values', {}).get(vote_value, '')
2492
- if vote_text.lower() == 'commit':
2493
- return 'commit'
2494
-
2495
- lgtm_label = data['labels'].get('Code-Review', {})
2496
- if lgtm_label:
2497
- if 'rejected' in lgtm_label:
2498
- return 'not lgtm'
2499
- if 'approved' in lgtm_label:
2500
- return 'lgtm'
2544
+ if data['labels'].get('Commit-Queue', {}).get('approved'):
2545
+ # The section will have an "approved" subsection if anyone has voted
2546
+ # the maximum value on the label.
2547
+ return 'commit'
2548
+
2549
+ if data['labels'].get('Code-Review', {}).get('approved'):
2550
+ return 'lgtm'
2501
2551
 
2502
2552
  if not data.get('reviewers', {}).get('REVIEWER', []):
2503
2553
  return 'unsent'
2504
2554
 
2505
- messages = data.get('messages', [])
2506
2555
  owner = data['owner'].get('_account_id')
2507
- while messages:
2508
- last_message_author = messages.pop().get('author', {})
2556
+ messages = sorted(data.get('messages', []), key=lambda m: m.get('updated'))
2557
+ last_message_author = messages.pop().get('author', {})
2558
+ while last_message_author:
2509
2559
  if last_message_author.get('email') == COMMIT_BOT_EMAIL:
2510
2560
  # Ignore replies from CQ.
2561
+ last_message_author = messages.pop().get('author', {})
2511
2562
  continue
2512
- if last_message_author.get('_account_id') != owner:
2563
+ if last_message_author.get('_account_id') == owner:
2564
+ # Most recent message was by owner.
2565
+ return 'waiting'
2566
+ else:
2513
2567
  # Some reply from non-owner.
2514
2568
  return 'reply'
2515
- return 'waiting'
2569
+
2570
+ # Somehow there are no messages even though there are reviewers.
2571
+ return 'unsent'
2516
2572
 
2517
2573
  def GetMostRecentPatchset(self):
2518
2574
  data = self._GetChangeDetail(['CURRENT_REVISION'])
@@ -2537,22 +2593,71 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2537
2593
  gerrit_util.SetCommitMessage(self._GetGerritHost(), self.GetIssue(),
2538
2594
  description, notify='NONE')
2539
2595
 
2540
- def AddComment(self, message):
2596
+ def AddComment(self, message, publish=None):
2541
2597
  gerrit_util.SetReview(self._GetGerritHost(), self.GetIssue(),
2542
- msg=message)
2598
+ msg=message, ready=publish)
2543
2599
 
2544
- def GetCommentsSummary(self):
2600
+ def GetCommentsSummary(self, readable=True):
2545
2601
  # DETAILED_ACCOUNTS is to get emails in accounts.
2546
- data = self._GetChangeDetail(options=['MESSAGES', 'DETAILED_ACCOUNTS'])
2602
+ messages = self._GetChangeDetail(
2603
+ options=['MESSAGES', 'DETAILED_ACCOUNTS']).get('messages', [])
2604
+ file_comments = gerrit_util.GetChangeComments(
2605
+ self._GetGerritHost(), self.GetIssue())
2606
+
2607
+ # Build dictionary of file comments for easy access and sorting later.
2608
+ # {author+date: {path: {patchset: {line: url+message}}}}
2609
+ comments = collections.defaultdict(
2610
+ lambda: collections.defaultdict(lambda: collections.defaultdict(dict)))
2611
+ for path, line_comments in file_comments.iteritems():
2612
+ for comment in line_comments:
2613
+ if comment.get('tag', '').startswith('autogenerated'):
2614
+ continue
2615
+ key = (comment['author']['email'], comment['updated'])
2616
+ if comment.get('side', 'REVISION') == 'PARENT':
2617
+ patchset = 'Base'
2618
+ else:
2619
+ patchset = 'PS%d' % comment['patch_set']
2620
+ line = comment.get('line', 0)
2621
+ url = ('https://%s/c/%s/%s/%s#%s%s' %
2622
+ (self._GetGerritHost(), self.GetIssue(), comment['patch_set'], path,
2623
+ 'b' if comment.get('side') == 'PARENT' else '',
2624
+ str(line) if line else ''))
2625
+ comments[key][path][patchset][line] = (url, comment['message'])
2626
+
2547
2627
  summary = []
2548
- for msg in data.get('messages', []):
2628
+ for msg in messages:
2629
+ # Don't bother showing autogenerated messages.
2630
+ if msg.get('tag') and msg.get('tag').startswith('autogenerated'):
2631
+ continue
2549
2632
  # Gerrit spits out nanoseconds.
2550
2633
  assert len(msg['date'].split('.')[-1]) == 9
2551
2634
  date = datetime.datetime.strptime(msg['date'][:-3],
2552
2635
  '%Y-%m-%d %H:%M:%S.%f')
2636
+ message = msg['message']
2637
+ key = (msg['author']['email'], msg['date'])
2638
+ if key in comments:
2639
+ message += '\n'
2640
+ for path, patchsets in sorted(comments.get(key, {}).items()):
2641
+ if readable:
2642
+ message += '\n%s' % path
2643
+ for patchset, lines in sorted(patchsets.items()):
2644
+ for line, (url, content) in sorted(lines.items()):
2645
+ if line:
2646
+ line_str = 'Line %d' % line
2647
+ path_str = '%s:%d:' % (path, line)
2648
+ else:
2649
+ line_str = 'File comment'
2650
+ path_str = '%s:0:' % path
2651
+ if readable:
2652
+ message += '\n %s, %s: %s' % (patchset, line_str, url)
2653
+ message += '\n %s\n' % content
2654
+ else:
2655
+ message += '\n%s ' % path_str
2656
+ message += '\n%s\n' % content
2657
+
2553
2658
  summary.append(_CommentSummary(
2554
2659
  date=date,
2555
- message=msg['message'],
2660
+ message=message,
2556
2661
  sender=msg['author']['email'],
2557
2662
  # These could be inferred from the text messages and correlated with
2558
2663
  # Code-Review label maximum, however this is not reliable.
@@ -2565,13 +2670,6 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2565
2670
  def CloseIssue(self):
2566
2671
  gerrit_util.AbandonChange(self._GetGerritHost(), self.GetIssue(), msg='')
2567
2672
 
2568
- def GetApprovingReviewers(self):
2569
- """Returns a list of reviewers approving the change.
2570
-
2571
- Note: not necessarily committers.
2572
- """
2573
- raise NotImplementedError()
2574
-
2575
2673
  def SubmitIssue(self, wait_for_merge=True):
2576
2674
  gerrit_util.SubmitChange(self._GetGerritHost(), self.GetIssue(),
2577
2675
  wait_for_merge=wait_for_merge)
@@ -2612,8 +2710,8 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2612
2710
  return data
2613
2711
 
2614
2712
  try:
2615
- data = gerrit_util.GetChangeDetail(self._GetGerritHost(), str(issue),
2616
- options, ignore_404=False)
2713
+ data = gerrit_util.GetChangeDetail(
2714
+ self._GetGerritHost(), str(issue), options)
2617
2715
  except gerrit_util.GerritError as e:
2618
2716
  if e.http_status == 404:
2619
2717
  raise GerritChangeNotExists(issue, self.GetCodereviewServer())
@@ -2625,9 +2723,12 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2625
2723
  def _GetChangeCommit(self, issue=None):
2626
2724
  issue = issue or self.GetIssue()
2627
2725
  assert issue, 'issue is required to query Gerrit'
2628
- data = gerrit_util.GetChangeCommit(self._GetGerritHost(), str(issue))
2629
- if not data:
2630
- raise GerritChangeNotExists(issue, self.GetCodereviewServer())
2726
+ try:
2727
+ data = gerrit_util.GetChangeCommit(self._GetGerritHost(), str(issue))
2728
+ except gerrit_util.GerritError as e:
2729
+ if e.http_status == 404:
2730
+ raise GerritChangeNotExists(issue, self.GetCodereviewServer())
2731
+ raise
2631
2732
  return data
2632
2733
 
2633
2734
  def CMDLand(self, force, bypass_hooks, verbose):
@@ -2645,19 +2746,19 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2645
2746
  last_upload = self._GitGetBranchConfigValue('gerritsquashhash')
2646
2747
  # Note: git diff outputs nothing if there is no diff.
2647
2748
  if not last_upload or RunGit(['diff', last_upload]).strip():
2648
- print('WARNING: some changes from local branch haven\'t been uploaded')
2749
+ print('WARNING: Some changes from local branch haven\'t been uploaded.')
2649
2750
  else:
2650
2751
  if detail['current_revision'] == last_upload:
2651
2752
  differs = False
2652
2753
  else:
2653
- print('WARNING: local branch contents differ from latest uploaded '
2654
- 'patchset')
2754
+ print('WARNING: Local branch contents differ from latest uploaded '
2755
+ 'patchset.')
2655
2756
  if differs:
2656
2757
  if not force:
2657
2758
  confirm_or_exit(
2658
2759
  'Do you want to submit latest Gerrit patchset and bypass hooks?\n',
2659
2760
  action='submit')
2660
- print('WARNING: bypassing hooks and submitting latest uploaded patchset')
2761
+ print('WARNING: Bypassing hooks and submitting latest uploaded patchset.')
2661
2762
  elif not bypass_hooks:
2662
2763
  hook_results = self.RunHook(
2663
2764
  committing=True,
@@ -2672,12 +2773,12 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2672
2773
  links = self._GetChangeCommit().get('web_links', [])
2673
2774
  for link in links:
2674
2775
  if link.get('name') == 'gitiles' and link.get('url'):
2675
- print('Landed as %s' % link.get('url'))
2776
+ print('Landed as: %s' % link.get('url'))
2676
2777
  break
2677
2778
  return 0
2678
2779
 
2679
2780
  def CMDPatchWithParsedIssue(self, parsed_issue_arg, reject, nocommit,
2680
- directory):
2781
+ directory, force):
2681
2782
  assert not reject
2682
2783
  assert not nocommit
2683
2784
  assert not directory
@@ -2709,11 +2810,25 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2709
2810
 
2710
2811
  fetch_info = revision_info['fetch']['http']
2711
2812
  RunGit(['fetch', fetch_info['url'], fetch_info['ref']])
2712
- self.SetIssue(self.GetIssue())
2813
+
2814
+ if force:
2815
+ RunGit(['reset', '--hard', 'FETCH_HEAD'])
2816
+ print('Checked out commit for change %i patchset %i locally' %
2817
+ (parsed_issue_arg.issue, patchset))
2818
+ else:
2819
+ RunGit(['cherry-pick', 'FETCH_HEAD'])
2820
+ print('Committed patch for change %i patchset %i locally.' %
2821
+ (parsed_issue_arg.issue, patchset))
2822
+ print('Note: this created a local commit which does not have '
2823
+ 'the same hash as the one uploaded for review. This will make '
2824
+ 'uploading changes based on top of this branch difficult.\n'
2825
+ 'If you want to do that, use "git cl patch --force" instead.')
2826
+
2827
+ self.SetIssue(parsed_issue_arg.issue)
2713
2828
  self.SetPatchset(patchset)
2714
- RunGit(['cherry-pick', 'FETCH_HEAD'])
2715
- print('Committed patch for change %i patchset %i locally' %
2716
- (self.GetIssue(), self.GetPatchset()))
2829
+ fetched_hash = RunGit(['rev-parse', 'FETCH_HEAD']).strip()
2830
+ self._GitSetBranchConfigValue('last-upload-hash', fetched_hash)
2831
+ self._GitSetBranchConfigValue('gerritsquashhash', fetched_hash)
2717
2832
  return 0
2718
2833
 
2719
2834
  @staticmethod
@@ -2733,7 +2848,8 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2733
2848
  return _ParsedIssueNumberArgument(
2734
2849
  issue=int(match.group(2)),
2735
2850
  patchset=int(match.group(4)) if match.group(4) else None,
2736
- hostname=parsed_url.netloc)
2851
+ hostname=parsed_url.netloc,
2852
+ codereview='gerrit')
2737
2853
  return None
2738
2854
 
2739
2855
  def _GerritCommitMsgHookCheck(self, offer_removal):
@@ -2745,7 +2861,7 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2745
2861
  data = gclient_utils.FileRead(hook)
2746
2862
  if not('From Gerrit Code Review' in data and 'add_ChangeId()' in data):
2747
2863
  return
2748
- print('Warning: you have Gerrit commit-msg hook installed.\n'
2864
+ print('WARNING: You have Gerrit commit-msg hook installed.\n'
2749
2865
  'It is not necessary for uploading with git cl in squash mode, '
2750
2866
  'and may interfere with it in subtle ways.\n'
2751
2867
  'We recommend you remove the commit-msg hook.')
@@ -2756,7 +2872,7 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2756
2872
  else:
2757
2873
  print('OK, will keep Gerrit commit-msg hook in place.')
2758
2874
 
2759
- def CMDUploadChange(self, options, args, change):
2875
+ def CMDUploadChange(self, options, git_diff_args, custom_cl_base, change):
2760
2876
  """Upload the current branch to Gerrit."""
2761
2877
  if options.squash and options.no_squash:
2762
2878
  DieWithError('Can only use one of --squash or --no-squash')
@@ -2767,10 +2883,6 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2767
2883
  elif options.no_squash:
2768
2884
  options.squash = False
2769
2885
 
2770
- # We assume the remote called "origin" is the one we want.
2771
- # It is probably not worthwhile to support different workflows.
2772
- gerrit_remote = 'origin'
2773
-
2774
2886
  remote, remote_branch = self.GetRemoteBranch()
2775
2887
  branch = GetTargetRef(remote, remote_branch, options.target_branch)
2776
2888
 
@@ -2788,11 +2900,20 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2788
2900
  'failed to fetch description from current Gerrit change %d\n'
2789
2901
  '%s' % (self.GetIssue(), self.GetIssueURL()))
2790
2902
  if not title:
2791
- default_title = RunGit(['show', '-s', '--format=%s', 'HEAD']).strip()
2792
- title = ask_for_data(
2793
- 'Title for patchset [%s]: ' % default_title) or default_title
2794
- if title == default_title:
2795
- automatic_title = True
2903
+ if options.message:
2904
+ # When uploading a subsequent patchset, -m|--message is taken
2905
+ # as the patchset title if --title was not provided.
2906
+ title = options.message.strip()
2907
+ else:
2908
+ default_title = RunGit(
2909
+ ['show', '-s', '--format=%s', 'HEAD']).strip()
2910
+ if options.force:
2911
+ title = default_title
2912
+ else:
2913
+ title = ask_for_data(
2914
+ 'Title for patchset [%s]: ' % default_title) or default_title
2915
+ if title == default_title:
2916
+ automatic_title = True
2796
2917
  change_id = self._GetChangeDetail()['change_id']
2797
2918
  while True:
2798
2919
  footer_change_ids = git_footers.get_footer_change_id(message)
@@ -2800,7 +2921,7 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2800
2921
  break
2801
2922
  if not footer_change_ids:
2802
2923
  message = git_footers.add_footer_change_id(message, change_id)
2803
- print('WARNING: appended missing Change-Id to change description')
2924
+ print('WARNING: appended missing Change-Id to change description.')
2804
2925
  continue
2805
2926
  # There is already a valid footer but with different or several ids.
2806
2927
  # Doing this automatically is non-trivial as we don't want to lose
@@ -2832,10 +2953,11 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2832
2953
  if options.message:
2833
2954
  message = options.message
2834
2955
  else:
2835
- message = CreateDescriptionFromLog(args)
2956
+ message = CreateDescriptionFromLog(git_diff_args)
2836
2957
  if options.title:
2837
2958
  message = options.title + '\n\n' + message
2838
2959
  change_desc = ChangeDescription(message)
2960
+
2839
2961
  if not options.force:
2840
2962
  change_desc.prompt(bug=options.bug)
2841
2963
  # On first upload, patchset title is always this string, while
@@ -2844,57 +2966,46 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2844
2966
  automatic_title = True
2845
2967
  if not change_desc.description:
2846
2968
  DieWithError("Description is empty. Aborting...")
2847
- message = change_desc.description
2848
- change_ids = git_footers.get_footer_change_id(message)
2969
+ change_ids = git_footers.get_footer_change_id(change_desc.description)
2849
2970
  if len(change_ids) > 1:
2850
2971
  DieWithError('too many Change-Id footers, at most 1 allowed.')
2851
2972
  if not change_ids:
2852
2973
  # Generate the Change-Id automatically.
2853
- message = git_footers.add_footer_change_id(
2854
- message, GenerateGerritChangeId(message))
2855
- change_desc.set_description(message)
2856
- change_ids = git_footers.get_footer_change_id(message)
2974
+ change_desc.set_description(git_footers.add_footer_change_id(
2975
+ change_desc.description,
2976
+ GenerateGerritChangeId(change_desc.description)))
2977
+ change_ids = git_footers.get_footer_change_id(change_desc.description)
2857
2978
  assert len(change_ids) == 1
2858
2979
  change_id = change_ids[0]
2859
2980
 
2860
- remote, upstream_branch = self.FetchUpstreamTuple(self.GetBranch())
2861
- if remote is '.':
2862
- # If our upstream branch is local, we base our squashed commit on its
2863
- # squashed version.
2864
- upstream_branch_name = scm.GIT.ShortBranchName(upstream_branch)
2865
- # Check the squashed hash of the parent.
2866
- parent = RunGit(['config',
2867
- 'branch.%s.gerritsquashhash' % upstream_branch_name],
2868
- error_ok=True).strip()
2869
- # Verify that the upstream branch has been uploaded too, otherwise
2870
- # Gerrit will create additional CLs when uploading.
2871
- if not parent or (RunGitSilent(['rev-parse', upstream_branch + ':']) !=
2872
- RunGitSilent(['rev-parse', parent + ':'])):
2873
- DieWithError(
2874
- '\nUpload upstream branch %s first.\n'
2875
- 'It is likely that this branch has been rebased since its last '
2876
- 'upload, so you just need to upload it again.\n'
2877
- '(If you uploaded it with --no-squash, then branch dependencies '
2878
- 'are not supported, and you should reupload with --squash.)'
2879
- % upstream_branch_name, change_desc)
2880
- else:
2881
- parent = self.GetCommonAncestorWithUpstream()
2981
+ if options.reviewers or options.tbrs or options.add_owners_to:
2982
+ change_desc.update_reviewers(options.reviewers, options.tbrs,
2983
+ options.add_owners_to, change)
2882
2984
 
2985
+ remote, upstream_branch = self.FetchUpstreamTuple(self.GetBranch())
2986
+ parent = self._ComputeParent(remote, upstream_branch, custom_cl_base,
2987
+ options.force, change_desc)
2883
2988
  tree = RunGit(['rev-parse', 'HEAD:']).strip()
2884
2989
  ref_to_push = RunGit(['commit-tree', tree, '-p', parent,
2885
- '-m', message]).strip()
2990
+ '-m', change_desc.description]).strip()
2886
2991
  else:
2887
2992
  change_desc = ChangeDescription(
2888
- options.message or CreateDescriptionFromLog(args))
2993
+ options.message or CreateDescriptionFromLog(git_diff_args))
2889
2994
  if not change_desc.description:
2890
2995
  DieWithError("Description is empty. Aborting...")
2891
2996
 
2892
2997
  if not git_footers.get_footer_change_id(change_desc.description):
2893
2998
  DownloadGerritHook(False)
2894
- change_desc.set_description(self._AddChangeIdToCommitMessage(options,
2895
- args))
2999
+ change_desc.set_description(
3000
+ self._AddChangeIdToCommitMessage(options, git_diff_args))
3001
+ if options.reviewers or options.tbrs or options.add_owners_to:
3002
+ change_desc.update_reviewers(options.reviewers, options.tbrs,
3003
+ options.add_owners_to, change)
2896
3004
  ref_to_push = 'HEAD'
2897
- parent = '%s/%s' % (gerrit_remote, branch)
3005
+ # For no-squash mode, we assume the remote called "origin" is the one we
3006
+ # want. It is not worthwhile to support different workflows for
3007
+ # no-squash mode.
3008
+ parent = 'origin/%s' % branch
2898
3009
  change_id = git_footers.get_footer_change_id(change_desc.description)[0]
2899
3010
 
2900
3011
  assert change_desc
@@ -2908,16 +3019,28 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2908
3019
  'single commit.')
2909
3020
  confirm_or_exit(action='upload')
2910
3021
 
2911
- if options.reviewers or options.tbr_owners:
2912
- change_desc.update_reviewers(options.reviewers, options.tbr_owners,
2913
- change)
3022
+ if options.reviewers or options.tbrs or options.add_owners_to:
3023
+ change_desc.update_reviewers(options.reviewers, options.tbrs,
3024
+ options.add_owners_to, change)
2914
3025
 
2915
3026
  # Extra options that can be specified at push time. Doc:
2916
3027
  # https://gerrit-review.googlesource.com/Documentation/user-upload.html
2917
3028
  refspec_opts = []
2918
- if change_desc.get_reviewers(tbr_only=True):
2919
- print('Adding self-LGTM (Code-Review +1) because of TBRs')
2920
- refspec_opts.append('l=Code-Review+1')
3029
+
3030
+ # By default, new changes are started in WIP mode, and subsequent patchsets
3031
+ # don't send email. At any time, passing --send-mail will mark the change
3032
+ # ready and send email for that particular patch.
3033
+ if options.send_mail:
3034
+ refspec_opts.append('ready')
3035
+ refspec_opts.append('notify=ALL')
3036
+ else:
3037
+ if not self.GetIssue():
3038
+ refspec_opts.append('wip')
3039
+ else:
3040
+ refspec_opts.append('notify=NONE')
3041
+
3042
+ # TODO(tandrii): options.message should be posted as a comment
3043
+ # if --send-mail is set on non-initial upload as Rietveld used to do it.
2921
3044
 
2922
3045
  if title:
2923
3046
  if not re.match(r'^[\w ]+$', title):
@@ -2931,24 +3054,8 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2931
3054
  # reverse on its side.
2932
3055
  refspec_opts.append('m=' + title.replace(' ', '_'))
2933
3056
 
2934
- if options.send_mail:
2935
- if not change_desc.get_reviewers():
2936
- DieWithError('Must specify reviewers to send email.', change_desc)
2937
- refspec_opts.append('notify=ALL')
2938
- else:
2939
- refspec_opts.append('notify=NONE')
2940
-
2941
- reviewers = change_desc.get_reviewers()
2942
- if reviewers:
2943
- # TODO(tandrii): remove this horrible hack once (Poly)Gerrit fixes their
2944
- # side for real (b/34702620).
2945
- def clean_invisible_chars(email):
2946
- return email.decode('unicode_escape').encode('ascii', 'ignore')
2947
- refspec_opts.extend('r=' + clean_invisible_chars(email).strip()
2948
- for email in reviewers)
2949
-
2950
3057
  if options.private:
2951
- refspec_opts.append('draft')
3058
+ refspec_opts.append('private')
2952
3059
 
2953
3060
  if options.topic:
2954
3061
  # Documentation on Gerrit topics is here:
@@ -2971,7 +3078,11 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2971
3078
  filter_fn=lambda _: sys.stdout.flush())
2972
3079
  except subprocess2.CalledProcessError:
2973
3080
  DieWithError('Failed to create a change. Please examine output above '
2974
- 'for the reason of the failure. ', change_desc)
3081
+ 'for the reason of the failure.\n'
3082
+ 'Hint: run command below to diagnose common Git/Gerrit '
3083
+ 'credential problems:\n'
3084
+ ' git cl creds-check\n',
3085
+ change_desc)
2975
3086
 
2976
3087
  if options.squash:
2977
3088
  regex = re.compile(r'remote:\s+https?://[\w\-\.\/]*/(\d+)\s.*')
@@ -2985,19 +3096,91 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2985
3096
  self.SetIssue(change_numbers[0])
2986
3097
  self._GitSetBranchConfigValue('gerritsquashhash', ref_to_push)
2987
3098
 
3099
+ reviewers = sorted(change_desc.get_reviewers())
3100
+
2988
3101
  # Add cc's from the CC_LIST and --cc flag (if any).
2989
- cc = self.GetCCList().split(',')
3102
+ if not options.private:
3103
+ cc = self.GetCCList().split(',')
3104
+ else:
3105
+ cc = []
2990
3106
  if options.cc:
2991
3107
  cc.extend(options.cc)
2992
3108
  cc = filter(None, [email.strip() for email in cc])
2993
3109
  if change_desc.get_cced():
2994
3110
  cc.extend(change_desc.get_cced())
2995
- if cc:
2996
- gerrit_util.AddReviewers(
2997
- self._GetGerritHost(), self.GetIssue(), cc,
2998
- is_reviewer=False, notify=bool(options.send_mail))
3111
+
3112
+ gerrit_util.AddReviewers(
3113
+ self._GetGerritHost(), self.GetIssue(), reviewers, cc,
3114
+ notify=bool(options.send_mail))
3115
+
3116
+ if change_desc.get_reviewers(tbr_only=True):
3117
+ print('Adding self-LGTM (Code-Review +1) because of TBRs.')
3118
+ gerrit_util.SetReview(
3119
+ self._GetGerritHost(), self.GetIssue(),
3120
+ labels={'Code-Review': 1}, notify=bool(options.send_mail))
3121
+
2999
3122
  return 0
3000
3123
 
3124
+ def _ComputeParent(self, remote, upstream_branch, custom_cl_base, force,
3125
+ change_desc):
3126
+ """Computes parent of the generated commit to be uploaded to Gerrit.
3127
+
3128
+ Returns revision or a ref name.
3129
+ """
3130
+ if custom_cl_base:
3131
+ # Try to avoid creating additional unintended CLs when uploading, unless
3132
+ # user wants to take this risk.
3133
+ local_ref_of_target_remote = self.GetRemoteBranch()[1]
3134
+ code, _ = RunGitWithCode(['merge-base', '--is-ancestor', custom_cl_base,
3135
+ local_ref_of_target_remote])
3136
+ if code == 1:
3137
+ print('\nWARNING: Manually specified base of this CL `%s` '
3138
+ 'doesn\'t seem to belong to target remote branch `%s`.\n\n'
3139
+ 'If you proceed with upload, more than 1 CL may be created by '
3140
+ 'Gerrit as a result, in turn confusing or crashing git cl.\n\n'
3141
+ 'If you are certain that specified base `%s` has already been '
3142
+ 'uploaded to Gerrit as another CL, you may proceed.\n' %
3143
+ (custom_cl_base, local_ref_of_target_remote, custom_cl_base))
3144
+ if not force:
3145
+ confirm_or_exit(
3146
+ 'Do you take responsibility for cleaning up potential mess '
3147
+ 'resulting from proceeding with upload?',
3148
+ action='upload')
3149
+ return custom_cl_base
3150
+
3151
+ if remote != '.':
3152
+ return self.GetCommonAncestorWithUpstream()
3153
+
3154
+ # If our upstream branch is local, we base our squashed commit on its
3155
+ # squashed version.
3156
+ upstream_branch_name = scm.GIT.ShortBranchName(upstream_branch)
3157
+
3158
+ if upstream_branch_name == 'master':
3159
+ return self.GetCommonAncestorWithUpstream()
3160
+
3161
+ # Check the squashed hash of the parent.
3162
+ # TODO(tandrii): consider checking parent change in Gerrit and using its
3163
+ # hash if tree hash of latest parent revision (patchset) in Gerrit matches
3164
+ # the tree hash of the parent branch. The upside is less likely bogus
3165
+ # requests to reupload parent change just because it's uploadhash is
3166
+ # missing, yet the downside likely exists, too (albeit unknown to me yet).
3167
+ parent = RunGit(['config',
3168
+ 'branch.%s.gerritsquashhash' % upstream_branch_name],
3169
+ error_ok=True).strip()
3170
+ # Verify that the upstream branch has been uploaded too, otherwise
3171
+ # Gerrit will create additional CLs when uploading.
3172
+ if not parent or (RunGitSilent(['rev-parse', upstream_branch + ':']) !=
3173
+ RunGitSilent(['rev-parse', parent + ':'])):
3174
+ DieWithError(
3175
+ '\nUpload upstream branch %s first.\n'
3176
+ 'It is likely that this branch has been rebased since its last '
3177
+ 'upload, so you just need to upload it again.\n'
3178
+ '(If you uploaded it with --no-squash, then branch dependencies '
3179
+ 'are not supported, and you should reupload with --squash.)'
3180
+ % upstream_branch_name,
3181
+ change_desc)
3182
+ return parent
3183
+
3001
3184
  def _AddChangeIdToCommitMessage(self, options, args):
3002
3185
  """Re-commits using the current message, assumes the commit hook is in
3003
3186
  place.
@@ -3022,7 +3205,7 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
3022
3205
  kwargs = {'labels': {'Commit-Queue': vote_map[new_state]}}
3023
3206
  if new_state == _CQState.DRY_RUN:
3024
3207
  # Don't spam everybody reviewer/owner.
3025
- kwargs['notify'] = 'NONE'
3208
+ kwargs['notify'] = False
3026
3209
  gerrit_util.SetReview(self._GetGerritHost(), self.GetIssue(), **kwargs)
3027
3210
 
3028
3211
  def CannotTriggerTryJobReason(self):
@@ -3034,8 +3217,8 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
3034
3217
  if data['status'] in ('ABANDONED', 'MERGED'):
3035
3218
  return 'CL %s is closed' % self.GetIssue()
3036
3219
 
3037
- def GetTryjobProperties(self, patchset=None):
3038
- """Returns dictionary of properties to launch tryjob."""
3220
+ def GetTryJobProperties(self, patchset=None):
3221
+ """Returns dictionary of properties to launch try job."""
3039
3222
  data = self._GetChangeDetail(['ALL_REVISIONS'])
3040
3223
  patchset = int(patchset or self.GetPatchset())
3041
3224
  assert patchset
@@ -3165,14 +3348,34 @@ class ChangeDescription(object):
3165
3348
  lines.pop(-1)
3166
3349
  self._description_lines = lines
3167
3350
 
3168
- def update_reviewers(self, reviewers, add_owners_tbr=False, change=None):
3169
- """Rewrites the R=/TBR= line(s) as a single line each."""
3351
+ def update_reviewers(self, reviewers, tbrs, add_owners_to=None, change=None):
3352
+ """Rewrites the R=/TBR= line(s) as a single line each.
3353
+
3354
+ Args:
3355
+ reviewers (list(str)) - list of additional emails to use for reviewers.
3356
+ tbrs (list(str)) - list of additional emails to use for TBRs.
3357
+ add_owners_to (None|'R'|'TBR') - Pass to do an OWNERS lookup for files in
3358
+ the change that are missing OWNER coverage. If this is not None, you
3359
+ must also pass a value for `change`.
3360
+ change (Change) - The Change that should be used for OWNERS lookups.
3361
+ """
3170
3362
  assert isinstance(reviewers, list), reviewers
3171
- if not reviewers and not add_owners_tbr:
3363
+ assert isinstance(tbrs, list), tbrs
3364
+
3365
+ assert add_owners_to in (None, 'TBR', 'R'), add_owners_to
3366
+ assert not add_owners_to or change, add_owners_to
3367
+
3368
+ if not reviewers and not tbrs and not add_owners_to:
3172
3369
  return
3173
- reviewers = reviewers[:]
3174
3370
 
3175
- # Get the set of R= and TBR= lines and remove them from the desciption.
3371
+ reviewers = set(reviewers)
3372
+ tbrs = set(tbrs)
3373
+ LOOKUP = {
3374
+ 'TBR': tbrs,
3375
+ 'R': reviewers,
3376
+ }
3377
+
3378
+ # Get the set of R= and TBR= lines and remove them from the description.
3176
3379
  regexp = re.compile(self.R_LINE)
3177
3380
  matches = [regexp.match(line) for line in self._description_lines]
3178
3381
  new_desc = [l for i, l in enumerate(self._description_lines)
@@ -3180,29 +3383,27 @@ class ChangeDescription(object):
3180
3383
  self.set_description(new_desc)
3181
3384
 
3182
3385
  # Construct new unified R= and TBR= lines.
3183
- r_names = []
3184
- tbr_names = []
3386
+
3387
+ # First, update tbrs/reviewers with names from the R=/TBR= lines (if any).
3185
3388
  for match in matches:
3186
3389
  if not match:
3187
3390
  continue
3188
- people = cleanup_list([match.group(2).strip()])
3189
- if match.group(1) == 'TBR':
3190
- tbr_names.extend(people)
3191
- else:
3192
- r_names.extend(people)
3193
- for name in r_names:
3194
- if name not in reviewers:
3195
- reviewers.append(name)
3196
- if add_owners_tbr:
3391
+ LOOKUP[match.group(1)].update(cleanup_list([match.group(2).strip()]))
3392
+
3393
+ # Next, maybe fill in OWNERS coverage gaps to either tbrs/reviewers.
3394
+ if add_owners_to:
3197
3395
  owners_db = owners.Database(change.RepositoryRoot(),
3198
- fopen=file, os_path=os.path)
3199
- all_reviewers = set(tbr_names + reviewers)
3396
+ fopen=file, os_path=os.path)
3200
3397
  missing_files = owners_db.files_not_covered_by(change.LocalPaths(),
3201
- all_reviewers)
3202
- tbr_names.extend(owners_db.reviewers_for(missing_files,
3203
- change.author_email))
3204
- new_r_line = 'R=' + ', '.join(reviewers) if reviewers else None
3205
- new_tbr_line = 'TBR=' + ', '.join(tbr_names) if tbr_names else None
3398
+ (tbrs | reviewers))
3399
+ LOOKUP[add_owners_to].update(
3400
+ owners_db.reviewers_for(missing_files, change.author_email))
3401
+
3402
+ # If any folks ended up in both groups, remove them from tbrs.
3403
+ tbrs -= reviewers
3404
+
3405
+ new_r_line = 'R=' + ', '.join(sorted(reviewers)) if reviewers else None
3406
+ new_tbr_line = 'TBR=' + ', '.join(sorted(tbrs)) if tbrs else None
3206
3407
 
3207
3408
  # Put the new lines in the description where the old first R= line was.
3208
3409
  line_loc = next((i for i, match in enumerate(matches) if match), -1)
@@ -3322,13 +3523,14 @@ class ChangeDescription(object):
3322
3523
  re.match(self.CHERRY_PICK_LINE, self._description_lines[-1])):
3323
3524
  cp_line = self._description_lines.pop()
3324
3525
 
3325
- top_lines, _, parsed_footers = git_footers.split_footers(self.description)
3526
+ top_lines, footer_lines, _ = git_footers.split_footers(self.description)
3326
3527
 
3327
3528
  # Original-ify all Cr- footers, to avoid re-lands, cherry-picks, or just
3328
3529
  # user interference with actual footers we'd insert below.
3329
- for i, (k, v) in enumerate(parsed_footers):
3330
- if k.startswith('Cr-'):
3331
- parsed_footers[i] = (k.replace('Cr-', 'Cr-Original-'), v)
3530
+ for i, line in enumerate(footer_lines):
3531
+ k, v = git_footers.parse_footer(line) or (None, None)
3532
+ if k and k.startswith('Cr-'):
3533
+ footer_lines[i] = '%s: %s' % ('Cr-Original-' + k[len('Cr-'):], v)
3332
3534
 
3333
3535
  # Add Position and Lineage footers based on the parent.
3334
3536
  lineage = list(reversed(parent_footer_map.get('Cr-Branched-From', [])))
@@ -3340,30 +3542,32 @@ class ChangeDescription(object):
3340
3542
  lineage.insert(0, '%s-%s@{#%d}' % (parent_hash, parent_position[0],
3341
3543
  int(parent_position[1])))
3342
3544
 
3343
- parsed_footers.append(('Cr-Commit-Position',
3344
- '%s@{#%d}' % (dest_ref, number)))
3345
- parsed_footers.extend(('Cr-Branched-From', v) for v in lineage)
3545
+ footer_lines.append('Cr-Commit-Position: %s@{#%d}' % (dest_ref, number))
3546
+ footer_lines.extend('Cr-Branched-From: %s' % v for v in lineage)
3346
3547
 
3347
3548
  self._description_lines = top_lines
3348
3549
  if cp_line:
3349
3550
  self._description_lines.append(cp_line)
3350
3551
  if self._description_lines[-1] != '':
3351
3552
  self._description_lines.append('') # Ensure footer separator.
3352
- self._description_lines.extend('%s: %s' % kv for kv in parsed_footers)
3553
+ self._description_lines.extend(footer_lines)
3353
3554
 
3354
3555
 
3355
- def get_approving_reviewers(props):
3556
+ def get_approving_reviewers(props, disapproval=False):
3356
3557
  """Retrieves the reviewers that approved a CL from the issue properties with
3357
3558
  messages.
3358
3559
 
3359
3560
  Note that the list may contain reviewers that are not committer, thus are not
3360
3561
  considered by the CQ.
3562
+
3563
+ If disapproval is True, instead returns reviewers who 'not lgtm'd the CL.
3361
3564
  """
3565
+ approval_type = 'disapproval' if disapproval else 'approval'
3362
3566
  return sorted(
3363
3567
  set(
3364
3568
  message['sender']
3365
3569
  for message in props['messages']
3366
- if message['approval'] and message['sender'] in props['reviewers']
3570
+ if message[approval_type] and message['sender'] in props['reviewers']
3367
3571
  )
3368
3572
  )
3369
3573
 
@@ -3520,7 +3724,7 @@ def GetRietveldCodereviewSettingsInteractively():
3520
3724
 
3521
3725
 
3522
3726
  class _GitCookiesChecker(object):
3523
- """Provides facilties for validating and suggesting fixes to .gitcookies."""
3727
+ """Provides facilities for validating and suggesting fixes to .gitcookies."""
3524
3728
 
3525
3729
  _GOOGLESOURCE = 'googlesource.com'
3526
3730
 
@@ -3535,6 +3739,7 @@ class _GitCookiesChecker(object):
3535
3739
  default = gerrit_util.CookiesAuthenticator.get_gitcookies_path()
3536
3740
  configured_path = RunGitSilent(
3537
3741
  ['config', '--global', 'http.cookiefile']).strip()
3742
+ configured_path = os.path.expanduser(configured_path)
3538
3743
  if configured_path:
3539
3744
  self._ensure_default_gitcookies_path(configured_path, default)
3540
3745
  else:
@@ -3548,7 +3753,7 @@ class _GitCookiesChecker(object):
3548
3753
  configured_path)
3549
3754
  return
3550
3755
 
3551
- print('WARNING: you have configured custom path to .gitcookies: %s\n'
3756
+ print('WARNING: You have configured custom path to .gitcookies: %s\n'
3552
3757
  'Gerrit and other depot_tools expect .gitcookies at %s\n' %
3553
3758
  (configured_path, default_path))
3554
3759
 
@@ -3646,6 +3851,12 @@ class _GitCookiesChecker(object):
3646
3851
  prefix = git_host.split('.', 1)[0]
3647
3852
  return prefix + '-review.' + self._GOOGLESOURCE
3648
3853
 
3854
+ def _get_counterpart_host(self, host):
3855
+ assert host.endswith(self._GOOGLESOURCE)
3856
+ git = self._canonical_git_googlesource_host(host)
3857
+ gerrit = self._canonical_gerrit_googlesource_host(git)
3858
+ return git if gerrit == host else gerrit
3859
+
3649
3860
  def has_generic_host(self):
3650
3861
  """Returns whether generic .googlesource.com has been configured.
3651
3862
 
@@ -3671,14 +3882,14 @@ class _GitCookiesChecker(object):
3671
3882
 
3672
3883
  def get_partially_configured_hosts(self):
3673
3884
  return set(
3674
- host for host, identities_pair in
3675
- self._get_git_gerrit_identity_pairs().iteritems()
3676
- if None in identities_pair and host != '.' + self._GOOGLESOURCE)
3885
+ (host if i1 else self._canonical_gerrit_googlesource_host(host))
3886
+ for host, (i1, i2) in self._get_git_gerrit_identity_pairs().iteritems()
3887
+ if None in (i1, i2) and host != '.' + self._GOOGLESOURCE)
3677
3888
 
3678
3889
  def get_conflicting_hosts(self):
3679
3890
  return set(
3680
- host for host, (i1, i2) in
3681
- self._get_git_gerrit_identity_pairs().iteritems()
3891
+ host
3892
+ for host, (i1, i2) in self._get_git_gerrit_identity_pairs().iteritems()
3682
3893
  if None not in (i1, i2) and i1 != i2)
3683
3894
 
3684
3895
  def get_duplicated_hosts(self):
@@ -3705,61 +3916,83 @@ class _GitCookiesChecker(object):
3705
3916
  return hosts
3706
3917
 
3707
3918
  @staticmethod
3708
- def print_hosts(hosts, extra_column_func=None):
3919
+ def _format_hosts(hosts, extra_column_func=None):
3709
3920
  hosts = sorted(hosts)
3710
3921
  assert hosts
3711
3922
  if extra_column_func is None:
3712
3923
  extras = [''] * len(hosts)
3713
3924
  else:
3714
3925
  extras = [extra_column_func(host) for host in hosts]
3715
- tmpl = ' %%-%ds %%-%ds' % (max(map(len, hosts)), max(map(len, extras)))
3926
+ tmpl = '%%-%ds %%-%ds' % (max(map(len, hosts)), max(map(len, extras)))
3927
+ lines = []
3716
3928
  for he in zip(hosts, extras):
3717
- print(tmpl % he)
3718
- print()
3719
-
3720
- def find_and_report_problems(self):
3721
- """Returns True if there was at least one problem, else False."""
3722
- problems = [False]
3723
- def add_problem():
3724
- if not problems[0]:
3725
- print('\n\n.gitcookies problem report:\n')
3726
- problems[0] = True
3929
+ lines.append(tmpl % he)
3930
+ return lines
3727
3931
 
3932
+ def _find_problems(self):
3728
3933
  if self.has_generic_host():
3729
- add_problem()
3730
- print(' .googlesource.com record detected\n'
3731
- ' Chrome Infrastructure team recommends to list full host names '
3732
- 'explicitly.\n')
3934
+ yield ('.googlesource.com wildcard record detected',
3935
+ ['Chrome Infrastructure team recommends to list full host names '
3936
+ 'explicitly.'],
3937
+ None)
3733
3938
 
3734
3939
  dups = self.get_duplicated_hosts()
3735
3940
  if dups:
3736
- add_problem()
3737
- print(' The following hosts were defined twice:\n')
3738
- self.print_hosts(dups)
3941
+ yield ('The following hosts were defined twice',
3942
+ self._format_hosts(dups),
3943
+ None)
3739
3944
 
3740
3945
  partial = self.get_partially_configured_hosts()
3741
3946
  if partial:
3742
- add_problem()
3743
- print(' Credentials should come in pairs for Git and Gerrit hosts. '
3744
- 'These hosts are missing:')
3745
- self.print_hosts(partial)
3947
+ yield ('Credentials should come in pairs for Git and Gerrit hosts. '
3948
+ 'These hosts are missing',
3949
+ self._format_hosts(partial, lambda host: 'but %s defined' %
3950
+ self._get_counterpart_host(host)),
3951
+ partial)
3746
3952
 
3747
3953
  conflicting = self.get_conflicting_hosts()
3748
3954
  if conflicting:
3749
- add_problem()
3750
- print(' The following Git hosts have differing credentials from their '
3751
- 'Gerrit counterparts:\n')
3752
- self.print_hosts(conflicting, lambda host: '%s vs %s' %
3753
- tuple(self._get_git_gerrit_identity_pairs()[host]))
3955
+ yield ('The following Git hosts have differing credentials from their '
3956
+ 'Gerrit counterparts',
3957
+ self._format_hosts(conflicting, lambda host: '%s vs %s' %
3958
+ tuple(self._get_git_gerrit_identity_pairs()[host])),
3959
+ conflicting)
3754
3960
 
3755
3961
  wrong = self.get_hosts_with_wrong_identities()
3756
3962
  if wrong:
3757
- add_problem()
3758
- print(' These hosts likely use wrong identity:\n')
3759
- self.print_hosts(wrong, lambda host: '%s but %s recommended' %
3760
- (self._get_git_gerrit_identity_pairs()[host][0],
3761
- self._EXPECTED_HOST_IDENTITY_DOMAINS[host]))
3762
- return problems[0]
3963
+ yield ('These hosts likely use wrong identity',
3964
+ self._format_hosts(wrong, lambda host: '%s but %s recommended' %
3965
+ (self._get_git_gerrit_identity_pairs()[host][0],
3966
+ self._EXPECTED_HOST_IDENTITY_DOMAINS[host])),
3967
+ wrong)
3968
+
3969
+ def find_and_report_problems(self):
3970
+ """Returns True if there was at least one problem, else False."""
3971
+ found = False
3972
+ bad_hosts = set()
3973
+ for title, sublines, hosts in self._find_problems():
3974
+ if not found:
3975
+ found = True
3976
+ print('\n\n.gitcookies problem report:\n')
3977
+ bad_hosts.update(hosts or [])
3978
+ print(' %s%s' % (title , (':' if sublines else '')))
3979
+ if sublines:
3980
+ print()
3981
+ print(' %s' % '\n '.join(sublines))
3982
+ print()
3983
+
3984
+ if bad_hosts:
3985
+ assert found
3986
+ print(' You can manually remove corresponding lines in your %s file and '
3987
+ 'visit the following URLs with correct account to generate '
3988
+ 'correct credential lines:\n' %
3989
+ gerrit_util.CookiesAuthenticator.get_gitcookies_path())
3990
+ print(' %s' % '\n '.join(sorted(set(
3991
+ gerrit_util.CookiesAuthenticator().get_new_password_url(
3992
+ self._canonical_git_googlesource_host(host))
3993
+ for host in bad_hosts
3994
+ ))))
3995
+ return found
3763
3996
 
3764
3997
 
3765
3998
  def CMDcreds_check(parser, args):
@@ -3776,7 +4009,7 @@ def CMDcreds_check(parser, args):
3776
4009
  checker.print_current_creds(include_netrc=True)
3777
4010
 
3778
4011
  if not checker.find_and_report_problems():
3779
- print('\nNo problems detected in your .gitcookies')
4012
+ print('\nNo problems detected in your .gitcookies file.')
3780
4013
  return 0
3781
4014
  return 1
3782
4015
 
@@ -3785,7 +4018,7 @@ def CMDcreds_check(parser, args):
3785
4018
  def CMDconfig(parser, args):
3786
4019
  """Edits configuration for this tree."""
3787
4020
 
3788
- print('WARNING: git cl config works for Rietveld only')
4021
+ print('WARNING: git cl config works for Rietveld only.')
3789
4022
  # TODO(tandrii): remove this once we switch to Gerrit.
3790
4023
  # See bugs http://crbug.com/637561 and http://crbug.com/600469.
3791
4024
  parser.add_option('--activate-update', action='store_true',
@@ -3835,9 +4068,10 @@ def CMDbaseurl(parser, args):
3835
4068
  def color_for_status(status):
3836
4069
  """Maps a Changelist status to color, for CMDstatus and other tools."""
3837
4070
  return {
3838
- 'unsent': Fore.RED,
4071
+ 'unsent': Fore.YELLOW,
3839
4072
  'waiting': Fore.BLUE,
3840
4073
  'reply': Fore.YELLOW,
4074
+ 'not lgtm': Fore.RED,
3841
4075
  'lgtm': Fore.GREEN,
3842
4076
  'commit': Fore.MAGENTA,
3843
4077
  'closed': Fore.CYAN,
@@ -3865,7 +4099,7 @@ def get_cl_statuses(changes, fine_grained, max_processes=None):
3865
4099
 
3866
4100
  if not fine_grained:
3867
4101
  # Fast path which doesn't involve querying codereview servers.
3868
- # Do not use GetApprovingReviewers(), since it requires an HTTP request.
4102
+ # Do not use get_approving_reviewers(), since it requires an HTTP request.
3869
4103
  for cl in changes:
3870
4104
  yield (cl, 'waiting' if cl.GetIssueURL() else 'error')
3871
4105
  return
@@ -4091,12 +4325,13 @@ def CMDstatus(parser, args):
4091
4325
  """Show status of changelists.
4092
4326
 
4093
4327
  Colors are used to tell the state of the CL unless --fast is used:
4094
- - Red not sent for review or broken
4095
4328
  - Blue waiting for review
4096
- - Yellow waiting for you to reply to review
4329
+ - Yellow waiting for you to reply to review, or not yet sent
4097
4330
  - Green LGTM'ed
4331
+ - Red 'not LGTM'ed
4098
4332
  - Magenta in the commit queue
4099
4333
  - Cyan was committed, branch can be deleted
4334
+ - White error, or unknown status
4100
4335
 
4101
4336
  Also see 'git cl comments'.
4102
4337
  """
@@ -4201,7 +4436,7 @@ def colorize_CMDstatus_doc():
4201
4436
  def colorize_line(line):
4202
4437
  for color in colors:
4203
4438
  if color in line.upper():
4204
- # Extract whitespaces first and the leading '-'.
4439
+ # Extract whitespace first and the leading '-'.
4205
4440
  indent = len(line) - len(line.lstrip(' ')) + 1
4206
4441
  return line[:indent] + getattr(Fore, color) + line[indent:] + Fore.RESET
4207
4442
  return line
@@ -4211,8 +4446,11 @@ def colorize_CMDstatus_doc():
4211
4446
 
4212
4447
 
4213
4448
  def write_json(path, contents):
4214
- with open(path, 'w') as f:
4215
- json.dump(contents, f)
4449
+ if path == '-':
4450
+ json.dump(contents, sys.stdout)
4451
+ else:
4452
+ with open(path, 'w') as f:
4453
+ json.dump(contents, f)
4216
4454
 
4217
4455
 
4218
4456
  @subcommand.usage('[issue_number]')
@@ -4225,7 +4463,8 @@ def CMDissue(parser, args):
4225
4463
  help='Lookup the branch(es) for the specified issues. If '
4226
4464
  'no issues are specified, all branches with mapped '
4227
4465
  'issues will be listed.')
4228
- parser.add_option('--json', help='Path to JSON output file.')
4466
+ parser.add_option('--json',
4467
+ help='Path to JSON output file, or "-" for stdout.')
4229
4468
  _add_codereview_select_options(parser)
4230
4469
  options, args = parser.parse_args(args)
4231
4470
  _process_codereview_select_options(parser, options)
@@ -4233,7 +4472,6 @@ def CMDissue(parser, args):
4233
4472
  if options.reverse:
4234
4473
  branches = RunGit(['for-each-ref', 'refs/heads',
4235
4474
  '--format=%(refname:short)']).splitlines()
4236
-
4237
4475
  # Reverse issue lookup.
4238
4476
  issue_branch_map = {}
4239
4477
  for branch in branches:
@@ -4250,21 +4488,24 @@ def CMDissue(parser, args):
4250
4488
  issue, ', '.join(issue_branch_map.get(int(issue)) or ('None',))))
4251
4489
  if options.json:
4252
4490
  write_json(options.json, result)
4491
+ return 0
4492
+
4493
+ if len(args) > 0:
4494
+ issue = ParseIssueNumberArgument(args[0], options.forced_codereview)
4495
+ if not issue.valid:
4496
+ DieWithError('Pass a url or number to set the issue, 0 to unset it, '
4497
+ 'or no argument to list it.\n'
4498
+ 'Maybe you want to run git cl status?')
4499
+ cl = Changelist(codereview=issue.codereview)
4500
+ cl.SetIssue(issue.issue)
4253
4501
  else:
4254
4502
  cl = Changelist(codereview=options.forced_codereview)
4255
- if len(args) > 0:
4256
- try:
4257
- issue = int(args[0])
4258
- except ValueError:
4259
- DieWithError('Pass a number to set the issue or none to list it.\n'
4260
- 'Maybe you want to run git cl status?')
4261
- cl.SetIssue(issue)
4262
- print('Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL()))
4263
- if options.json:
4264
- write_json(options.json, {
4265
- 'issue': cl.GetIssue(),
4266
- 'issue_url': cl.GetIssueURL(),
4267
- })
4503
+ print('Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL()))
4504
+ if options.json:
4505
+ write_json(options.json, {
4506
+ 'issue': cl.GetIssue(),
4507
+ 'issue_url': cl.GetIssueURL(),
4508
+ })
4268
4509
  return 0
4269
4510
 
4270
4511
 
@@ -4275,8 +4516,12 @@ def CMDcomments(parser, args):
4275
4516
  parser.add_option('-i', '--issue', dest='issue',
4276
4517
  help='review issue id (defaults to current issue). '
4277
4518
  'If given, requires --rietveld or --gerrit')
4519
+ parser.add_option('-m', '--machine-readable', dest='readable',
4520
+ action='store_false', default=True,
4521
+ help='output comments in a format compatible with '
4522
+ 'editor parsing')
4278
4523
  parser.add_option('-j', '--json-file',
4279
- help='File to write JSON summary to')
4524
+ help='File to write JSON summary to, or "-" for stdout')
4280
4525
  auth.add_auth_options(parser)
4281
4526
  _add_codereview_select_options(parser)
4282
4527
  options, args = parser.parse_args(args)
@@ -4293,16 +4538,15 @@ def CMDcomments(parser, args):
4293
4538
  parser.error('--gerrit or --rietveld is required if --issue is specified')
4294
4539
 
4295
4540
  cl = Changelist(issue=issue,
4296
- # TODO(tandrii): remove 'rietveld' default.
4297
- codereview=options.forced_codereview or (
4298
- 'rietveld' if issue else None),
4541
+ codereview=options.forced_codereview,
4299
4542
  auth_config=auth_config)
4300
4543
 
4301
4544
  if options.comment:
4302
4545
  cl.AddComment(options.comment)
4303
4546
  return 0
4304
4547
 
4305
- summary = sorted(cl.GetCommentsSummary(), key=lambda c: c.date)
4548
+ summary = sorted(cl.GetCommentsSummary(readable=options.readable),
4549
+ key=lambda c: c.date)
4306
4550
  for comment in summary:
4307
4551
  if comment.disapproval:
4308
4552
  color = Fore.RED
@@ -4348,10 +4592,10 @@ def CMDdescription(parser, args):
4348
4592
 
4349
4593
  target_issue_arg = None
4350
4594
  if len(args) > 0:
4351
- target_issue_arg = ParseIssueNumberArgument(args[0])
4595
+ target_issue_arg = ParseIssueNumberArgument(args[0],
4596
+ options.forced_codereview)
4352
4597
  if not target_issue_arg.valid:
4353
- parser.print_help()
4354
- return 1
4598
+ parser.error('invalid codereview url or CL id')
4355
4599
 
4356
4600
  auth_config = auth.extract_auth_config_from_options(options)
4357
4601
 
@@ -4359,14 +4603,23 @@ def CMDdescription(parser, args):
4359
4603
  'auth_config': auth_config,
4360
4604
  'codereview': options.forced_codereview,
4361
4605
  }
4606
+ detected_codereview_from_url = False
4362
4607
  if target_issue_arg:
4363
4608
  kwargs['issue'] = target_issue_arg.issue
4364
4609
  kwargs['codereview_host'] = target_issue_arg.hostname
4610
+ if target_issue_arg.codereview and not options.forced_codereview:
4611
+ detected_codereview_from_url = True
4612
+ kwargs['codereview'] = target_issue_arg.codereview
4365
4613
 
4366
4614
  cl = Changelist(**kwargs)
4367
-
4368
4615
  if not cl.GetIssue():
4616
+ assert not detected_codereview_from_url
4369
4617
  DieWithError('This branch has no associated changelist.')
4618
+
4619
+ if detected_codereview_from_url:
4620
+ logging.info('canonical issue/change URL: %s (type: %s)\n',
4621
+ cl.GetIssueURL(), target_issue_arg.codereview)
4622
+
4370
4623
  description = ChangeDescription(cl.GetDescription())
4371
4624
 
4372
4625
  if options.display:
@@ -4531,7 +4784,7 @@ def GetTargetRef(remote, remote_branch, target_branch):
4531
4784
  return None
4532
4785
 
4533
4786
  if target_branch:
4534
- # Cannonicalize branch references to the equivalent local full symbolic
4787
+ # Canonicalize branch references to the equivalent local full symbolic
4535
4788
  # refs, which are then translated into the remote full symbolic refs
4536
4789
  # below.
4537
4790
  if '/' not in target_branch:
@@ -4611,6 +4864,9 @@ def CMDupload(parser, args):
4611
4864
  parser.add_option('-r', '--reviewers',
4612
4865
  action='append', default=[],
4613
4866
  help='reviewer email addresses')
4867
+ parser.add_option('--tbrs',
4868
+ action='append', default=[],
4869
+ help='TBR email addresses')
4614
4870
  parser.add_option('--cc',
4615
4871
  action='append', default=[],
4616
4872
  help='cc email addresses')
@@ -4639,8 +4895,10 @@ def CMDupload(parser, args):
4639
4895
  help='Topic to specify when uploading (Gerrit only)')
4640
4896
  parser.add_option('--email', default=None,
4641
4897
  help='email address to use to connect to Rietveld')
4642
- parser.add_option('--tbr-owners', dest='tbr_owners', action='store_true',
4643
- help='add a set of OWNERS to TBR')
4898
+ parser.add_option('--tbr-owners', dest='add_owners_to', action='store_const',
4899
+ const='TBR', help='add a set of OWNERS to TBR')
4900
+ parser.add_option('--r-owners', dest='add_owners_to', action='store_const',
4901
+ const='R', help='add a set of OWNERS to R')
4644
4902
  parser.add_option('-d', '--cq-dry-run', dest='cq_dry_run',
4645
4903
  action='store_true',
4646
4904
  help='Send the patchset to do a CQ dry run right after '
@@ -4661,6 +4919,7 @@ def CMDupload(parser, args):
4661
4919
  return 1
4662
4920
 
4663
4921
  options.reviewers = cleanup_list(options.reviewers)
4922
+ options.tbrs = cleanup_list(options.tbrs)
4664
4923
  options.cc = cleanup_list(options.cc)
4665
4924
 
4666
4925
  if options.message_file:
@@ -4679,6 +4938,31 @@ def CMDupload(parser, args):
4679
4938
  return cl.CMDUpload(options, args, orig_args)
4680
4939
 
4681
4940
 
4941
+ @subcommand.usage('--description=<description file>')
4942
+ def CMDsplit(parser, args):
4943
+ """Splits a branch into smaller branches and uploads CLs.
4944
+
4945
+ Creates a branch and uploads a CL for each group of files modified in the
4946
+ current branch that share a common OWNERS file. In the CL description and
4947
+ comment, the string '$directory', is replaced with the directory containing
4948
+ the shared OWNERS file.
4949
+ """
4950
+ parser.add_option("-d", "--description", dest="description_file",
4951
+ help="A text file containing a CL description. ")
4952
+ parser.add_option("-c", "--comment", dest="comment_file",
4953
+ help="A text file containing a CL comment.")
4954
+ options, _ = parser.parse_args(args)
4955
+
4956
+ if not options.description_file:
4957
+ parser.error('No --description flag specified.')
4958
+
4959
+ def WrappedCMDupload(args):
4960
+ return CMDupload(OptionParser(), args)
4961
+
4962
+ return split_cl.SplitCl(options.description_file, options.comment_file,
4963
+ Changelist, WrappedCMDupload)
4964
+
4965
+
4682
4966
  @subcommand.usage('DEPRECATED')
4683
4967
  def CMDdcommit(parser, args):
4684
4968
  """DEPRECATED: Used to commit the current changelist via git-svn."""
@@ -4766,7 +5050,7 @@ def CMDland(parser, args):
4766
5050
 
4767
5051
  if options.contributor:
4768
5052
  if not re.match('^.*\s<\S+@\S+>$', options.contributor):
4769
- print("Please provide contibutor as 'First Last <email@example.com>'")
5053
+ print("Please provide contributor as 'First Last <email@example.com>'")
4770
5054
  return 1
4771
5055
 
4772
5056
  base_branch = args[0]
@@ -4824,7 +5108,8 @@ def CMDland(parser, args):
4824
5108
  # contains the link to the Rietveld issue, while the Rietveld message contains
4825
5109
  # the commit viewvc url.
4826
5110
  if cl.GetIssue():
4827
- change_desc.update_reviewers(cl.GetApprovingReviewers())
5111
+ change_desc.update_reviewers(
5112
+ get_approving_reviewers(cl.GetIssueProperties()), [])
4828
5113
 
4829
5114
  commit_desc = ChangeDescription(change_desc.description)
4830
5115
  if cl.GetIssue():
@@ -4996,7 +5281,10 @@ def PushToGitWithAutoRebase(remote, branch, original_description,
4996
5281
  break
4997
5282
  if IsFatalPushFailure(out):
4998
5283
  print('Fatal push error. Make sure your .netrc credentials and git '
4999
- 'user.email are correct and you have push access to the repo.')
5284
+ 'user.email are correct and you have push access to the repo.\n'
5285
+ 'Hint: run command below to diangose common Git/Gerrit credential '
5286
+ 'problems:\n'
5287
+ ' git cl creds-check\n')
5000
5288
  break
5001
5289
  return code
5002
5290
 
@@ -5012,9 +5300,9 @@ def CMDpatch(parser, args):
5012
5300
  parser.add_option('-b', dest='newbranch',
5013
5301
  help='create a new branch off trunk for the patch')
5014
5302
  parser.add_option('-f', '--force', action='store_true',
5015
- help='with -b, clobber any existing branch')
5303
+ help='overwrite state on the current or chosen branch')
5016
5304
  parser.add_option('-d', '--directory', action='store', metavar='DIR',
5017
- help='Change to the directory DIR immediately, '
5305
+ help='change to the directory DIR immediately, '
5018
5306
  'before doing anything else. Rietveld only.')
5019
5307
  parser.add_option('--reject', action='store_true',
5020
5308
  help='failed patches spew .rej files rather than '
@@ -5044,7 +5332,6 @@ def CMDpatch(parser, args):
5044
5332
  _process_codereview_select_options(parser, options)
5045
5333
  auth_config = auth.extract_auth_config_from_options(options)
5046
5334
 
5047
-
5048
5335
  if options.reapply:
5049
5336
  if options.newbranch:
5050
5337
  parser.error('--reapply works on the current branch only')
@@ -5070,6 +5357,22 @@ def CMDpatch(parser, args):
5070
5357
  if len(args) != 1 or not args[0]:
5071
5358
  parser.error('Must specify issue number or url')
5072
5359
 
5360
+ target_issue_arg = ParseIssueNumberArgument(args[0],
5361
+ options.forced_codereview)
5362
+ if not target_issue_arg.valid:
5363
+ parser.error('invalid codereview url or CL id')
5364
+
5365
+ cl_kwargs = {
5366
+ 'auth_config': auth_config,
5367
+ 'codereview_host': target_issue_arg.hostname,
5368
+ 'codereview': options.forced_codereview,
5369
+ }
5370
+ detected_codereview_from_url = False
5371
+ if target_issue_arg.codereview and not options.forced_codereview:
5372
+ detected_codereview_from_url = True
5373
+ cl_kwargs['codereview'] = target_issue_arg.codereview
5374
+ cl_kwargs['issue'] = target_issue_arg.issue
5375
+
5073
5376
  # We don't want uncommitted changes mixed up with the patch.
5074
5377
  if git_common.is_dirty_git_tree('patch'):
5075
5378
  return 1
@@ -5082,7 +5385,7 @@ def CMDpatch(parser, args):
5082
5385
  elif not GetCurrentBranch():
5083
5386
  DieWithError('A branch is required to apply patch. Hint: use -b option.')
5084
5387
 
5085
- cl = Changelist(auth_config=auth_config, codereview=options.forced_codereview)
5388
+ cl = Changelist(**cl_kwargs)
5086
5389
 
5087
5390
  if cl.IsGerrit():
5088
5391
  if options.reject:
@@ -5092,8 +5395,13 @@ def CMDpatch(parser, args):
5092
5395
  if options.directory:
5093
5396
  parser.error('--directory is not supported with Gerrit codereview.')
5094
5397
 
5095
- return cl.CMDPatchIssue(args[0], options.reject, options.nocommit,
5096
- options.directory)
5398
+ if detected_codereview_from_url:
5399
+ print('canonical issue/change URL: %s (type: %s)\n' %
5400
+ (cl.GetIssueURL(), target_issue_arg.codereview))
5401
+
5402
+ return cl.CMDPatchWithParsedIssue(target_issue_arg, options.reject,
5403
+ options.nocommit, options.directory,
5404
+ options.force)
5097
5405
 
5098
5406
 
5099
5407
  def GetTreeStatus(url=None):
@@ -5211,8 +5519,9 @@ def CMDtry(parser, args):
5211
5519
  # then we default to triggering a CQ dry run (see http://crbug.com/625697).
5212
5520
  if not buckets:
5213
5521
  if options.verbose:
5214
- print('git cl try with no bots now defaults to CQ Dry Run.')
5215
- return cl.TriggerDryRun()
5522
+ print('git cl try with no bots now defaults to CQ dry run.')
5523
+ print('Scheduling CQ dry run on: %s' % cl.GetIssueURL())
5524
+ return cl.SetCQState(_CQState.DRY_RUN)
5216
5525
 
5217
5526
  for builders in buckets.itervalues():
5218
5527
  if any('triggered' in b for b in builders):
@@ -5256,7 +5565,8 @@ def CMDtry_results(parser, args):
5256
5565
  '--buildbucket-host', default='cr-buildbucket.appspot.com',
5257
5566
  help='Host of buildbucket. The default host is %default.')
5258
5567
  group.add_option(
5259
- '--json', help='Path of JSON output file to write try job results to.')
5568
+ '--json', help=('Path of JSON output file to write try job results to,'
5569
+ 'or "-" for stdout.'))
5260
5570
  parser.add_option_group(group)
5261
5571
  auth.add_auth_options(parser)
5262
5572
  options, args = parser.parse_args(args)
@@ -5356,7 +5666,6 @@ def CMDset_commit(parser, args):
5356
5666
  if options.clear:
5357
5667
  state = _CQState.NONE
5358
5668
  elif options.dry_run:
5359
- # TODO(qyearsley): Use cl.TriggerDryRun.
5360
5669
  state = _CQState.DRY_RUN
5361
5670
  else:
5362
5671
  state = _CQState.COMMIT
@@ -5438,7 +5747,7 @@ def CMDdiff(parser, args):
5438
5747
 
5439
5748
 
5440
5749
  def CMDowners(parser, args):
5441
- """Interactively find the owners for reviewing."""
5750
+ """Interactively finds potential owners for reviewing."""
5442
5751
  parser.add_option(
5443
5752
  '--no-color',
5444
5753
  action='store_true',
@@ -5463,9 +5772,10 @@ def CMDowners(parser, args):
5463
5772
  return owners_finder.OwnersFinder(
5464
5773
  [f.LocalPath() for f in
5465
5774
  cl.GetChange(base_branch, None).AffectedFiles()],
5466
- change.RepositoryRoot(), author,
5467
- fopen=file, os_path=os.path,
5468
- disable_color=options.no_color).run()
5775
+ change.RepositoryRoot(),
5776
+ author, fopen=file, os_path=os.path,
5777
+ disable_color=options.no_color,
5778
+ override_files=change.OriginalOwnersFiles()).run()
5469
5779
 
5470
5780
 
5471
5781
  def BuildGitDiffCmd(diff_type, upstream_commit, args):
@@ -5646,24 +5956,30 @@ def CMDformat(parser, args):
5646
5956
  DieWithError("gn format failed on " + gn_diff_file +
5647
5957
  "\nTry running 'gn format' on this file manually.")
5648
5958
 
5649
- metrics_xml_files = [
5650
- os.path.join('tools', 'metrics', 'actions', 'actions.xml'),
5651
- os.path.join('tools', 'metrics', 'histograms', 'histograms.xml'),
5652
- os.path.join('tools', 'metrics', 'rappor', 'rappor.xml')]
5653
- for xml_file in metrics_xml_files:
5654
- if xml_file in diff_files:
5655
- tool_dir = os.path.join(top_dir, os.path.dirname(xml_file))
5656
- cmd = [os.path.join(tool_dir, 'pretty_print.py'), '--non-interactive']
5657
- if opts.dry_run or opts.diff:
5658
- cmd.append('--diff')
5659
- stdout = RunCommand(cmd, cwd=top_dir)
5660
- if opts.diff:
5661
- sys.stdout.write(stdout)
5662
- if opts.dry_run and stdout:
5663
- return_value = 2 # Not formatted.
5959
+ for xml_dir in GetDirtyMetricsDirs(diff_files):
5960
+ tool_dir = os.path.join(top_dir, xml_dir)
5961
+ cmd = [os.path.join(tool_dir, 'pretty_print.py'), '--non-interactive']
5962
+ if opts.dry_run or opts.diff:
5963
+ cmd.append('--diff')
5964
+ stdout = RunCommand(cmd, cwd=top_dir)
5965
+ if opts.diff:
5966
+ sys.stdout.write(stdout)
5967
+ if opts.dry_run and stdout:
5968
+ return_value = 2 # Not formatted.
5664
5969
 
5665
5970
  return return_value
5666
5971
 
5972
+ def GetDirtyMetricsDirs(diff_files):
5973
+ xml_diff_files = [x for x in diff_files if MatchingFileType(x, ['.xml'])]
5974
+ metrics_xml_dirs = [
5975
+ os.path.join('tools', 'metrics', 'actions'),
5976
+ os.path.join('tools', 'metrics', 'histograms'),
5977
+ os.path.join('tools', 'metrics', 'rappor'),
5978
+ os.path.join('tools', 'metrics', 'ukm')]
5979
+ for xml_dir in metrics_xml_dirs:
5980
+ if any(file.startswith(xml_dir) for file in xml_diff_files):
5981
+ yield xml_dir
5982
+
5667
5983
 
5668
5984
  @subcommand.usage('<codereview url or issue id>')
5669
5985
  def CMDcheckout(parser, args):
@@ -5676,8 +5992,8 @@ def CMDcheckout(parser, args):
5676
5992
 
5677
5993
  issue_arg = ParseIssueNumberArgument(args[0])
5678
5994
  if not issue_arg.valid:
5679
- parser.print_help()
5680
- return 1
5995
+ parser.error('invalid codereview url or CL id')
5996
+
5681
5997
  target_issue = str(issue_arg.issue)
5682
5998
 
5683
5999
  def find_issues(issueprefix):