libv8 6.7.288.46.1 → 7.3.492.27.0beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (408) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -0
  3. data/CHANGELOG.md +4 -0
  4. data/README.md +2 -0
  5. data/ext/libv8/builder.rb +6 -2
  6. data/lib/libv8/version.rb +1 -1
  7. data/vendor/depot_tools/.gitattributes +1 -0
  8. data/vendor/depot_tools/.gitignore +7 -0
  9. data/vendor/depot_tools/CROS_OWNERS +5 -0
  10. data/vendor/depot_tools/OWNERS +12 -1
  11. data/vendor/depot_tools/PRESUBMIT.py +16 -9
  12. data/vendor/depot_tools/README.md +9 -2
  13. data/vendor/depot_tools/autoninja +14 -6
  14. data/vendor/depot_tools/autoninja.bat +11 -1
  15. data/vendor/depot_tools/autoninja.py +40 -18
  16. data/vendor/depot_tools/bb +12 -0
  17. data/vendor/depot_tools/bb.bat +7 -0
  18. data/vendor/depot_tools/bootstrap/win/manifest.txt +1 -1
  19. data/vendor/depot_tools/bootstrap/win/manifest_bleeding_edge.txt +1 -1
  20. data/vendor/depot_tools/bootstrap/win/win_tools.py +2 -1
  21. data/vendor/depot_tools/buildbucket.py +57 -4
  22. data/vendor/depot_tools/cipd +157 -44
  23. data/vendor/depot_tools/cipd.bat +51 -14
  24. data/vendor/depot_tools/cipd.ps1 +104 -42
  25. data/vendor/depot_tools/cipd_client_version +1 -1
  26. data/vendor/depot_tools/cipd_client_version.digests +21 -0
  27. data/vendor/depot_tools/cipd_manifest.txt +19 -6
  28. data/vendor/depot_tools/cipd_manifest.versions +318 -0
  29. data/vendor/depot_tools/clang_format.py +4 -4
  30. data/vendor/depot_tools/cpplint.py +44 -199
  31. data/vendor/depot_tools/dart_format.py +2 -2
  32. data/vendor/depot_tools/detect_host_arch.py +8 -3
  33. data/vendor/depot_tools/download_from_google_storage.py +47 -39
  34. data/vendor/depot_tools/fetch.py +30 -18
  35. data/vendor/depot_tools/fetch_configs/android_internal.py +34 -0
  36. data/vendor/depot_tools/fetch_configs/chromium.py +18 -1
  37. data/vendor/depot_tools/fetch_configs/config_util.py +4 -2
  38. data/vendor/depot_tools/fetch_configs/inspector_protocol.py +40 -0
  39. data/vendor/depot_tools/fetch_configs/node-ci.py +41 -0
  40. data/vendor/depot_tools/fix_encoding.py +3 -3
  41. data/vendor/depot_tools/gclient +1 -1
  42. data/vendor/depot_tools/gclient.py +415 -198
  43. data/vendor/depot_tools/gclient_eval.py +220 -171
  44. data/vendor/depot_tools/gclient_paths.py +142 -0
  45. data/vendor/depot_tools/gclient_scm.py +200 -51
  46. data/vendor/depot_tools/gclient_utils.py +88 -191
  47. data/vendor/depot_tools/gerrit_client.py +13 -0
  48. data/vendor/depot_tools/gerrit_util.py +158 -23
  49. data/vendor/depot_tools/git-nav-upstream +1 -1
  50. data/vendor/depot_tools/git_cache.py +77 -24
  51. data/vendor/depot_tools/git_cl.py +705 -1099
  52. data/vendor/depot_tools/git_common.py +9 -6
  53. data/vendor/depot_tools/git_map_branches.py +19 -2
  54. data/vendor/depot_tools/git_nav_downstream.py +3 -4
  55. data/vendor/depot_tools/git_rebase_update.py +14 -0
  56. data/vendor/depot_tools/git_reparent_branch.py +8 -2
  57. data/vendor/depot_tools/gn.py +38 -3
  58. data/vendor/depot_tools/gsutil.py +8 -3
  59. data/vendor/depot_tools/gsutil.py.bat +15 -0
  60. data/vendor/depot_tools/gsutil.vpython +16 -0
  61. data/vendor/depot_tools/infra/config/OWNERS +0 -1
  62. data/vendor/depot_tools/infra/config/recipes.cfg +3 -2
  63. data/vendor/depot_tools/lucicfg +12 -0
  64. data/vendor/depot_tools/lucicfg.bat +7 -0
  65. data/vendor/depot_tools/man/html/git-map-branches.html +34 -2
  66. data/vendor/depot_tools/man/html/git-new-branch.html +40 -32
  67. data/vendor/depot_tools/man/man1/git-map-branches.1 +24 -5
  68. data/vendor/depot_tools/man/man1/git-new-branch.1 +35 -27
  69. data/vendor/depot_tools/man/src/git-map-branches.demo.1.sh +1 -0
  70. data/vendor/depot_tools/man/src/git-map-branches.txt +10 -0
  71. data/vendor/depot_tools/man/src/git-new-branch.demo.1.sh +9 -4
  72. data/vendor/depot_tools/man/src/git-new-branch.txt +1 -1
  73. data/vendor/depot_tools/metrics.README.md +98 -0
  74. data/vendor/depot_tools/metrics.py +296 -0
  75. data/vendor/depot_tools/metrics_utils.py +303 -0
  76. data/vendor/depot_tools/my_activity.py +91 -29
  77. data/vendor/depot_tools/ninja +1 -1
  78. data/vendor/depot_tools/ninjalog.README.md +64 -0
  79. data/vendor/depot_tools/ninjalog_uploader.py +232 -0
  80. data/vendor/depot_tools/ninjalog_uploader_wrapper.py +116 -0
  81. data/vendor/depot_tools/owners.py +30 -13
  82. data/vendor/depot_tools/owners_finder.py +5 -2
  83. data/vendor/depot_tools/presubmit_canned_checks.py +188 -29
  84. data/vendor/depot_tools/presubmit_support.py +18 -41
  85. data/vendor/depot_tools/pylintrc +23 -19
  86. data/vendor/depot_tools/recipes/OWNERS +2 -0
  87. data/vendor/depot_tools/recipes/README.recipes.md +344 -151
  88. data/vendor/depot_tools/recipes/recipe_modules/bot_update/OWNERS +2 -0
  89. data/vendor/depot_tools/recipes/recipe_modules/bot_update/__init__.py +2 -16
  90. data/vendor/depot_tools/recipes/recipe_modules/bot_update/api.py +141 -99
  91. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/basic.json +5 -8
  92. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/basic_luci.json +5 -8
  93. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/basic_with_branch_heads.json +6 -98
  94. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/clobber.json +4 -9
  95. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/deprecated_got_revision_mapping.json +45 -5
  96. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/gerrit_no_rebase_patch_ref.json +4 -9
  97. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/gerrit_no_reset.json +4 -9
  98. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/{tryjob.json → input_commit_with_id_without_repo.json} +6 -11
  99. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/{tryjob_empty_revision.json → multiple_patch_refs.json} +8 -9
  100. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/no_apply_patch_on_gclient.json +19 -29
  101. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/{trychange.json → refs.json} +4 -9
  102. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/reset_root_solution_revision.json +4 -9
  103. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_fail.json +51 -6
  104. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_fail_patch.json +50 -6
  105. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_fail_patch_download.json +51 -6
  106. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_gerrit_angle.json +17 -25
  107. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_gerrit_branch_heads.json +17 -25
  108. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_gerrit_feature_branch.json +18 -26
  109. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_gerrit_v8_feature_branch.json +18 -26
  110. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_gerrit_webrtc.json +26 -28
  111. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_v8.json +45 -5
  112. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_v8_head_by_default.json +17 -25
  113. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/unrecognized_commit_repo.json +13 -0
  114. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/with_manifest_name.json +13 -152
  115. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/with_tags.json +4 -9
  116. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.py +185 -202
  117. data/vendor/depot_tools/recipes/recipe_modules/bot_update/resources/bot_update.py +52 -157
  118. data/vendor/depot_tools/recipes/recipe_modules/bot_update/test_api.py +5 -14
  119. data/vendor/depot_tools/recipes/recipe_modules/bot_update/tests/ensure_checkout.py +34 -0
  120. data/vendor/depot_tools/recipes/recipe_modules/cipd/api.py +14 -2
  121. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/full.expected/basic.json +4 -5
  122. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/full.expected/basic_pkg.json +4 -5
  123. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/full.expected/describe-failed.json +7 -5
  124. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/full.expected/describe-many-instances.json +4 -5
  125. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/full.expected/mac64.json +4 -5
  126. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/full.expected/pkg_bad_file.json +9 -3
  127. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/full.expected/pkg_bad_mode.json +9 -3
  128. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/full.expected/pkg_bad_verfile.json +9 -3
  129. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/full.expected/win64.json +4 -5
  130. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/junk arch.json +2 -3
  131. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/junk bits.json +2 -3
  132. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/linux_arm_32.json +2 -3
  133. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/linux_arm_64.json +2 -3
  134. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/linux_intel_32.json +2 -3
  135. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/linux_intel_64.json +2 -3
  136. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/linux_mips_64.json +2 -3
  137. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/mac_intel_64.json +2 -3
  138. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/win_intel_32.json +2 -3
  139. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/win_intel_64.json +2 -3
  140. data/vendor/depot_tools/recipes/recipe_modules/depot_tools/api.py +13 -8
  141. data/vendor/depot_tools/recipes/recipe_modules/depot_tools/examples/full.expected/basic.json +18 -12
  142. data/vendor/depot_tools/recipes/recipe_modules/depot_tools/examples/full.expected/basic_luci.json +18 -12
  143. data/vendor/depot_tools/recipes/recipe_modules/depot_tools/examples/full.expected/win.json +18 -12
  144. data/vendor/depot_tools/recipes/recipe_modules/depot_tools/examples/full.py +3 -0
  145. data/vendor/depot_tools/recipes/recipe_modules/gclient/__init__.py +1 -0
  146. data/vendor/depot_tools/recipes/recipe_modules/gclient/api.py +58 -46
  147. data/vendor/depot_tools/recipes/recipe_modules/gclient/config.py +65 -22
  148. data/vendor/depot_tools/recipes/recipe_modules/gclient/examples/full.expected/basic.json +20 -21
  149. data/vendor/depot_tools/recipes/recipe_modules/gclient/examples/full.expected/buildbot.json +20 -21
  150. data/vendor/depot_tools/recipes/recipe_modules/gclient/examples/full.expected/revision.json +20 -21
  151. data/vendor/depot_tools/recipes/recipe_modules/gclient/examples/full.expected/tryserver.json +20 -21
  152. data/vendor/depot_tools/recipes/recipe_modules/gclient/examples/full.py +5 -2
  153. data/vendor/depot_tools/recipes/recipe_modules/gclient/tests/patch_project.py +62 -14
  154. data/vendor/depot_tools/recipes/recipe_modules/gerrit/api.py +24 -38
  155. data/vendor/depot_tools/recipes/recipe_modules/gerrit/examples/full.expected/basic.json +56 -50
  156. data/vendor/depot_tools/recipes/recipe_modules/gerrit/examples/full.py +15 -9
  157. data/vendor/depot_tools/recipes/recipe_modules/git/__init__.py +4 -1
  158. data/vendor/depot_tools/recipes/recipe_modules/git/api.py +34 -22
  159. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/basic.json +5 -6
  160. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/basic_branch.json +5 -6
  161. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/basic_file_name.json +5 -6
  162. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/basic_hash.json +5 -6
  163. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/basic_luci.json +222 -0
  164. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/basic_ref.json +5 -6
  165. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/basic_submodule_update_force.json +5 -6
  166. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/basic_tags.json +224 -0
  167. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/can_fail_build.json +10 -6
  168. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/cannot_fail_build.json +5 -7
  169. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/cat-file_test.json +5 -6
  170. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/count-objects_delta.json +5 -6
  171. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/count-objects_failed.json +5 -7
  172. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/count-objects_with_bad_output.json +5 -6
  173. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/count-objects_with_bad_output_fails_build.json +10 -5
  174. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/curl_trace_file.json +5 -6
  175. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/git-cache-checkout.json +8 -9
  176. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/platform_win.json +5 -6
  177. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/rebase_failed.json +12 -8
  178. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/remote_not_origin.json +5 -6
  179. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/set_got_revision.json +5 -6
  180. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.py +27 -11
  181. data/vendor/depot_tools/recipes/recipe_modules/git_cl/api.py +1 -1
  182. data/vendor/depot_tools/recipes/recipe_modules/git_cl/examples/full.expected/basic.json +12 -13
  183. data/vendor/depot_tools/recipes/recipe_modules/gitiles/__init__.py +5 -0
  184. data/vendor/depot_tools/recipes/recipe_modules/gitiles/api.py +120 -5
  185. data/vendor/depot_tools/recipes/recipe_modules/gitiles/examples/full.expected/basic.json +45 -3
  186. data/vendor/depot_tools/recipes/recipe_modules/gitiles/examples/full.py +25 -0
  187. data/vendor/depot_tools/recipes/recipe_modules/gitiles/resources/gerrit_client.py +56 -4
  188. data/vendor/depot_tools/recipes/recipe_modules/gitiles/tests/parse_repo_url.expected/basic.json +6 -0
  189. data/vendor/depot_tools/recipes/recipe_modules/gitiles/tests/parse_repo_url.py +49 -0
  190. data/vendor/depot_tools/recipes/recipe_modules/gsutil/api.py +24 -13
  191. data/vendor/depot_tools/recipes/recipe_modules/gsutil/examples/full.expected/basic.json +13 -14
  192. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/basic.json +2 -3
  193. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/paths_buildbot_linux.json +2 -3
  194. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/paths_buildbot_mac.json +2 -3
  195. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/paths_buildbot_win.json +2 -3
  196. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/paths_generic_linux.json +2 -3
  197. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/paths_generic_mac.json +2 -3
  198. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/paths_generic_win.json +2 -3
  199. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/paths_kitchen_linux.json +2 -3
  200. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/paths_kitchen_mac.json +2 -3
  201. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/paths_kitchen_win.json +2 -3
  202. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/path_config.py +1 -2
  203. data/vendor/depot_tools/recipes/recipe_modules/osx_sdk/__init__.py +35 -0
  204. data/vendor/depot_tools/recipes/recipe_modules/osx_sdk/api.py +116 -0
  205. data/vendor/depot_tools/recipes/recipe_modules/osx_sdk/examples/full.expected/linux.json +22 -0
  206. data/vendor/depot_tools/recipes/recipe_modules/osx_sdk/examples/full.expected/mac.json +82 -0
  207. data/vendor/depot_tools/recipes/recipe_modules/osx_sdk/examples/full.expected/win.json +22 -0
  208. data/vendor/depot_tools/recipes/recipe_modules/osx_sdk/examples/full.py +23 -0
  209. data/vendor/depot_tools/recipes/recipe_modules/presubmit/__init__.py +1 -0
  210. data/vendor/depot_tools/recipes/recipe_modules/presubmit/api.py +2 -7
  211. data/vendor/depot_tools/recipes/recipe_modules/presubmit/examples/full.expected/basic.json +7 -6
  212. data/vendor/depot_tools/recipes/recipe_modules/tryserver/__init__.py +1 -0
  213. data/vendor/depot_tools/recipes/recipe_modules/tryserver/api.py +117 -8
  214. data/vendor/depot_tools/recipes/recipe_modules/tryserver/examples/full.expected/basic_tags.json +4 -5
  215. data/vendor/depot_tools/recipes/recipe_modules/tryserver/examples/full.expected/set_failure_hash_with_no_steps.json +7 -4
  216. data/vendor/depot_tools/recipes/recipe_modules/tryserver/examples/full.expected/with_gerrit_patch.json +98 -7
  217. data/vendor/depot_tools/recipes/recipe_modules/tryserver/examples/full.expected/with_gerrit_patch_and_target_ref.json +147 -0
  218. data/vendor/depot_tools/recipes/recipe_modules/tryserver/examples/full.expected/with_git_patch.json +8 -5
  219. data/vendor/depot_tools/recipes/recipe_modules/tryserver/examples/full.expected/with_git_patch_luci.json +8 -5
  220. data/vendor/depot_tools/recipes/recipe_modules/tryserver/examples/full.expected/with_wrong_patch.json +9 -6
  221. data/vendor/depot_tools/recipes/recipe_modules/tryserver/examples/full.expected/with_wrong_patch_new.json +9 -6
  222. data/vendor/depot_tools/recipes/recipe_modules/tryserver/examples/full.py +27 -2
  223. data/vendor/depot_tools/recipes/recipe_modules/tryserver/test_api.py +14 -0
  224. data/vendor/depot_tools/recipes/recipe_modules/windows_sdk/__init__.py +25 -0
  225. data/vendor/depot_tools/recipes/recipe_modules/windows_sdk/api.py +137 -0
  226. data/vendor/depot_tools/recipes/recipe_modules/windows_sdk/examples/full.expected/linux.json +22 -0
  227. data/vendor/depot_tools/recipes/recipe_modules/windows_sdk/examples/full.expected/mac.json +22 -0
  228. data/vendor/depot_tools/recipes/recipe_modules/windows_sdk/examples/full.expected/win.json +107 -0
  229. data/vendor/depot_tools/recipes/recipe_modules/windows_sdk/examples/full.py +26 -0
  230. data/vendor/depot_tools/recipes/recipes.py +37 -27
  231. data/vendor/depot_tools/recipes/recipes/fetch_end_to_end_test.expected/basic.json +7 -10
  232. data/vendor/depot_tools/repo +34 -8
  233. data/vendor/depot_tools/roll_dep.py +52 -49
  234. data/vendor/depot_tools/scm.py +38 -23
  235. data/vendor/depot_tools/setup_color.py +4 -2
  236. data/vendor/depot_tools/split_cl.py +32 -4
  237. data/vendor/depot_tools/subprocess2.py +22 -4
  238. data/vendor/depot_tools/third_party/httplib2/README.chromium +2 -2
  239. data/vendor/depot_tools/third_party/httplib2/__init__.py +242 -158
  240. data/vendor/depot_tools/third_party/httplib2/cacerts.txt +57 -44
  241. data/vendor/depot_tools/third_party/httplib2/socks.py +15 -5
  242. data/vendor/depot_tools/third_party/logilab/README.chromium +2 -4
  243. data/vendor/depot_tools/third_party/logilab/astroid/README.chromium +2 -1
  244. data/vendor/depot_tools/third_party/logilab/astroid/__init__.py +10 -5
  245. data/vendor/depot_tools/third_party/logilab/astroid/__pkginfo__.py +5 -5
  246. data/vendor/depot_tools/third_party/logilab/astroid/arguments.py +233 -0
  247. data/vendor/depot_tools/third_party/logilab/astroid/as_string.py +82 -33
  248. data/vendor/depot_tools/third_party/logilab/astroid/bases.py +137 -153
  249. data/vendor/depot_tools/third_party/logilab/astroid/brain/{builtin_inference.py → brain_builtin_inference.py} +117 -26
  250. data/vendor/depot_tools/third_party/logilab/astroid/brain/brain_dateutil.py +15 -0
  251. data/vendor/depot_tools/third_party/logilab/astroid/brain/{py2gi.py → brain_gi.py} +48 -8
  252. data/vendor/depot_tools/third_party/logilab/astroid/brain/{py2mechanize.py → brain_mechanize.py} +0 -0
  253. data/vendor/depot_tools/third_party/logilab/astroid/brain/{pynose.py → brain_nose.py} +4 -1
  254. data/vendor/depot_tools/third_party/logilab/astroid/brain/brain_numpy.py +62 -0
  255. data/vendor/depot_tools/third_party/logilab/astroid/brain/brain_pytest.py +76 -0
  256. data/vendor/depot_tools/third_party/logilab/astroid/brain/brain_qt.py +44 -0
  257. data/vendor/depot_tools/third_party/logilab/astroid/brain/{pysix_moves.py → brain_six.py} +28 -1
  258. data/vendor/depot_tools/third_party/logilab/astroid/brain/brain_ssl.py +65 -0
  259. data/vendor/depot_tools/third_party/logilab/astroid/brain/brain_stdlib.py +473 -0
  260. data/vendor/depot_tools/third_party/logilab/astroid/builder.py +104 -81
  261. data/vendor/depot_tools/third_party/logilab/astroid/context.py +81 -0
  262. data/vendor/depot_tools/third_party/logilab/astroid/decorators.py +75 -0
  263. data/vendor/depot_tools/third_party/logilab/astroid/exceptions.py +20 -0
  264. data/vendor/depot_tools/third_party/logilab/astroid/inference.py +137 -183
  265. data/vendor/depot_tools/third_party/logilab/astroid/manager.py +45 -169
  266. data/vendor/depot_tools/third_party/logilab/astroid/mixins.py +37 -14
  267. data/vendor/depot_tools/third_party/logilab/astroid/modutils.py +112 -41
  268. data/vendor/depot_tools/third_party/logilab/astroid/node_classes.py +243 -156
  269. data/vendor/depot_tools/third_party/logilab/astroid/nodes.py +35 -22
  270. data/vendor/depot_tools/third_party/logilab/astroid/objects.py +186 -0
  271. data/vendor/depot_tools/third_party/logilab/astroid/protocols.py +157 -102
  272. data/vendor/depot_tools/third_party/logilab/astroid/raw_building.py +32 -8
  273. data/vendor/depot_tools/third_party/logilab/astroid/rebuilder.py +372 -309
  274. data/vendor/depot_tools/third_party/logilab/astroid/scoped_nodes.py +652 -420
  275. data/vendor/depot_tools/third_party/logilab/astroid/test_utils.py +4 -21
  276. data/vendor/depot_tools/third_party/logilab/astroid/transforms.py +96 -0
  277. data/vendor/depot_tools/third_party/logilab/astroid/util.py +89 -0
  278. data/vendor/depot_tools/third_party/logilab/lazy_object_proxy/LICENSE +19 -0
  279. data/vendor/depot_tools/third_party/logilab/lazy_object_proxy/README.chromium +11 -0
  280. data/vendor/depot_tools/third_party/logilab/lazy_object_proxy/__init__.py +20 -0
  281. data/vendor/depot_tools/third_party/logilab/lazy_object_proxy/cext.c +1421 -0
  282. data/vendor/depot_tools/third_party/logilab/lazy_object_proxy/compat.py +9 -0
  283. data/vendor/depot_tools/third_party/logilab/lazy_object_proxy/simple.py +246 -0
  284. data/vendor/depot_tools/third_party/logilab/lazy_object_proxy/slots.py +414 -0
  285. data/vendor/depot_tools/third_party/logilab/lazy_object_proxy/utils.py +13 -0
  286. data/vendor/depot_tools/third_party/logilab/wrapt/LICENSE +24 -0
  287. data/vendor/depot_tools/third_party/logilab/wrapt/README.chromium +11 -0
  288. data/vendor/depot_tools/third_party/logilab/wrapt/__init__.py +19 -0
  289. data/vendor/depot_tools/third_party/logilab/wrapt/_wrappers.c +2729 -0
  290. data/vendor/depot_tools/third_party/logilab/wrapt/arguments.py +96 -0
  291. data/vendor/depot_tools/third_party/logilab/wrapt/decorators.py +512 -0
  292. data/vendor/depot_tools/third_party/logilab/wrapt/importer.py +228 -0
  293. data/vendor/depot_tools/third_party/logilab/wrapt/wrappers.py +901 -0
  294. data/vendor/depot_tools/third_party/pylint/README.chromium +2 -25
  295. data/vendor/depot_tools/third_party/pylint/__pkginfo__.py +13 -3
  296. data/vendor/depot_tools/third_party/pylint/checkers/__init__.py +1 -2
  297. data/vendor/depot_tools/third_party/pylint/checkers/async.py +82 -0
  298. data/vendor/depot_tools/third_party/pylint/checkers/base.py +893 -119
  299. data/vendor/depot_tools/third_party/pylint/checkers/classes.py +342 -204
  300. data/vendor/depot_tools/third_party/pylint/checkers/design_analysis.py +51 -34
  301. data/vendor/depot_tools/third_party/pylint/checkers/exceptions.py +84 -47
  302. data/vendor/depot_tools/third_party/pylint/checkers/format.py +55 -30
  303. data/vendor/depot_tools/third_party/pylint/checkers/imports.py +314 -73
  304. data/vendor/depot_tools/third_party/pylint/checkers/logging.py +10 -8
  305. data/vendor/depot_tools/third_party/pylint/checkers/misc.py +2 -1
  306. data/vendor/depot_tools/third_party/pylint/checkers/newstyle.py +45 -48
  307. data/vendor/depot_tools/third_party/pylint/checkers/python3.py +31 -21
  308. data/vendor/depot_tools/third_party/pylint/checkers/raw_metrics.py +3 -3
  309. data/vendor/depot_tools/third_party/pylint/checkers/similar.py +4 -5
  310. data/vendor/depot_tools/third_party/pylint/checkers/spelling.py +24 -10
  311. data/vendor/depot_tools/third_party/pylint/checkers/stdlib.py +120 -56
  312. data/vendor/depot_tools/third_party/pylint/checkers/strings.py +38 -35
  313. data/vendor/depot_tools/third_party/pylint/checkers/typecheck.py +485 -138
  314. data/vendor/depot_tools/third_party/pylint/checkers/utils.py +319 -142
  315. data/vendor/depot_tools/third_party/pylint/checkers/variables.py +329 -207
  316. data/vendor/depot_tools/third_party/pylint/config.py +739 -76
  317. data/vendor/depot_tools/third_party/pylint/epylint.py +9 -5
  318. data/vendor/depot_tools/third_party/pylint/extensions/__init__.py +0 -0
  319. data/vendor/depot_tools/third_party/pylint/extensions/check_docs.py +311 -0
  320. data/vendor/depot_tools/third_party/pylint/extensions/check_elif.py +62 -0
  321. data/vendor/depot_tools/third_party/{logilab/common → pylint}/graph.py +30 -133
  322. data/vendor/depot_tools/third_party/pylint/gui.py +2 -2
  323. data/vendor/depot_tools/third_party/pylint/interfaces.py +21 -3
  324. data/vendor/depot_tools/third_party/pylint/lint.py +123 -140
  325. data/vendor/depot_tools/third_party/pylint/pyreverse/diadefslib.py +10 -13
  326. data/vendor/depot_tools/third_party/pylint/pyreverse/diagrams.py +15 -4
  327. data/vendor/depot_tools/third_party/pylint/pyreverse/inspector.py +372 -0
  328. data/vendor/depot_tools/third_party/pylint/pyreverse/main.py +30 -7
  329. data/vendor/depot_tools/third_party/pylint/pyreverse/utils.py +80 -2
  330. data/vendor/depot_tools/third_party/{logilab/common → pylint/pyreverse}/vcgutils.py +19 -37
  331. data/vendor/depot_tools/third_party/pylint/pyreverse/writer.py +3 -4
  332. data/vendor/depot_tools/third_party/pylint/reporters/__init__.py +34 -18
  333. data/vendor/depot_tools/third_party/pylint/reporters/guireporter.py +1 -1
  334. data/vendor/depot_tools/third_party/pylint/reporters/html.py +10 -3
  335. data/vendor/depot_tools/third_party/pylint/reporters/json.py +10 -4
  336. data/vendor/depot_tools/third_party/pylint/reporters/text.py +94 -3
  337. data/vendor/depot_tools/third_party/pylint/reporters/ureports/__init__.py +106 -0
  338. data/vendor/depot_tools/third_party/{logilab/common → pylint/reporters}/ureports/html_writer.py +17 -57
  339. data/vendor/depot_tools/third_party/{logilab/common → pylint/reporters}/ureports/nodes.py +52 -74
  340. data/vendor/depot_tools/third_party/{logilab/common → pylint/reporters}/ureports/text_writer.py +14 -60
  341. data/vendor/depot_tools/third_party/pylint/testutils.py +22 -20
  342. data/vendor/depot_tools/third_party/pylint/utils.py +268 -44
  343. data/vendor/depot_tools/third_party/repo/progress.py +42 -0
  344. data/vendor/depot_tools/update_depot_tools +1 -1
  345. data/vendor/depot_tools/upload_metrics.py +25 -0
  346. data/vendor/depot_tools/win_toolchain/get_toolchain_if_necessary.py +45 -15
  347. data/vendor/depot_tools/win_toolchain/package_from_installed.py +71 -24
  348. data/vendor/depot_tools/yapf +1 -1
  349. data/vendor/depot_tools/yapf.bat +1 -1
  350. metadata +92 -77
  351. data/vendor/depot_tools/git-crsync +0 -3
  352. data/vendor/depot_tools/infra/config/cq.cfg +0 -32
  353. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/apply_gerrit_ref.json +0 -29
  354. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/apply_gerrit_ref_custom.json +0 -29
  355. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/buildbot.json +0 -105
  356. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/shallow.json +0 -195
  357. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_gerrit_angle_deprecated.json +0 -248
  358. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_gerrit_v8.json +0 -248
  359. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/with_manifest_name_no_patch.json +0 -105
  360. data/vendor/depot_tools/recipes/recipe_modules/bot_update/resources/apply_gerrit.py +0 -33
  361. data/vendor/depot_tools/third_party/logilab/astroid/brain/py2pytest.py +0 -31
  362. data/vendor/depot_tools/third_party/logilab/astroid/brain/py2qt4.py +0 -22
  363. data/vendor/depot_tools/third_party/logilab/astroid/brain/py2stdlib.py +0 -334
  364. data/vendor/depot_tools/third_party/logilab/astroid/inspector.py +0 -273
  365. data/vendor/depot_tools/third_party/logilab/astroid/utils.py +0 -239
  366. data/vendor/depot_tools/third_party/logilab/common/LICENSE.txt +0 -339
  367. data/vendor/depot_tools/third_party/logilab/common/README.chromium +0 -11
  368. data/vendor/depot_tools/third_party/logilab/common/__init__.py +0 -175
  369. data/vendor/depot_tools/third_party/logilab/common/__pkginfo__.py +0 -57
  370. data/vendor/depot_tools/third_party/logilab/common/cache.py +0 -114
  371. data/vendor/depot_tools/third_party/logilab/common/changelog.py +0 -238
  372. data/vendor/depot_tools/third_party/logilab/common/clcommands.py +0 -334
  373. data/vendor/depot_tools/third_party/logilab/common/cli.py +0 -211
  374. data/vendor/depot_tools/third_party/logilab/common/compat.py +0 -78
  375. data/vendor/depot_tools/third_party/logilab/common/configuration.py +0 -1105
  376. data/vendor/depot_tools/third_party/logilab/common/contexts.py +0 -5
  377. data/vendor/depot_tools/third_party/logilab/common/corbautils.py +0 -117
  378. data/vendor/depot_tools/third_party/logilab/common/daemon.py +0 -101
  379. data/vendor/depot_tools/third_party/logilab/common/date.py +0 -335
  380. data/vendor/depot_tools/third_party/logilab/common/dbf.py +0 -231
  381. data/vendor/depot_tools/third_party/logilab/common/debugger.py +0 -214
  382. data/vendor/depot_tools/third_party/logilab/common/decorators.py +0 -281
  383. data/vendor/depot_tools/third_party/logilab/common/deprecation.py +0 -189
  384. data/vendor/depot_tools/third_party/logilab/common/fileutils.py +0 -404
  385. data/vendor/depot_tools/third_party/logilab/common/interface.py +0 -71
  386. data/vendor/depot_tools/third_party/logilab/common/logging_ext.py +0 -195
  387. data/vendor/depot_tools/third_party/logilab/common/modutils.py +0 -702
  388. data/vendor/depot_tools/third_party/logilab/common/optik_ext.py +0 -392
  389. data/vendor/depot_tools/third_party/logilab/common/optparser.py +0 -92
  390. data/vendor/depot_tools/third_party/logilab/common/proc.py +0 -277
  391. data/vendor/depot_tools/third_party/logilab/common/pyro_ext.py +0 -180
  392. data/vendor/depot_tools/third_party/logilab/common/pytest.py +0 -1199
  393. data/vendor/depot_tools/third_party/logilab/common/registry.py +0 -1119
  394. data/vendor/depot_tools/third_party/logilab/common/shellutils.py +0 -462
  395. data/vendor/depot_tools/third_party/logilab/common/sphinx_ext.py +0 -87
  396. data/vendor/depot_tools/third_party/logilab/common/sphinxutils.py +0 -122
  397. data/vendor/depot_tools/third_party/logilab/common/table.py +0 -929
  398. data/vendor/depot_tools/third_party/logilab/common/tasksqueue.py +0 -101
  399. data/vendor/depot_tools/third_party/logilab/common/testlib.py +0 -1392
  400. data/vendor/depot_tools/third_party/logilab/common/textutils.py +0 -537
  401. data/vendor/depot_tools/third_party/logilab/common/tree.py +0 -369
  402. data/vendor/depot_tools/third_party/logilab/common/umessage.py +0 -194
  403. data/vendor/depot_tools/third_party/logilab/common/ureports/__init__.py +0 -172
  404. data/vendor/depot_tools/third_party/logilab/common/ureports/docbook_writer.py +0 -140
  405. data/vendor/depot_tools/third_party/logilab/common/urllib2ext.py +0 -89
  406. data/vendor/depot_tools/third_party/logilab/common/visitor.py +0 -109
  407. data/vendor/depot_tools/third_party/logilab/common/xmlrpcutils.py +0 -131
  408. data/vendor/depot_tools/third_party/logilab/common/xmlutils.py +0 -61
@@ -5,7 +5,7 @@
5
5
 
6
6
  # Copyright (C) 2008 Evan Martin <martine@danga.com>
7
7
 
8
- """A git-command for integrating reviews on Rietveld and Gerrit."""
8
+ """A git-command for integrating reviews on Gerrit."""
9
9
 
10
10
  from __future__ import print_function
11
11
 
@@ -29,6 +29,7 @@ import stat
29
29
  import sys
30
30
  import tempfile
31
31
  import textwrap
32
+ import time
32
33
  import urllib
33
34
  import urllib2
34
35
  import urlparse
@@ -43,7 +44,6 @@ except ImportError:
43
44
 
44
45
  from third_party import colorama
45
46
  from third_party import httplib2
46
- from third_party import upload
47
47
  import auth
48
48
  import checkout
49
49
  import clang_format
@@ -55,10 +55,11 @@ import gerrit_util
55
55
  import git_cache
56
56
  import git_common
57
57
  import git_footers
58
+ import metrics
59
+ import metrics_utils
58
60
  import owners
59
61
  import owners_finder
60
62
  import presubmit_support
61
- import rietveld
62
63
  import scm
63
64
  import split_cl
64
65
  import subcommand
@@ -68,7 +69,6 @@ import watchlists
68
69
  __version__ = '2.0'
69
70
 
70
71
  COMMIT_BOT_EMAIL = 'commit-bot@chromium.org'
71
- DEFAULT_SERVER = 'https://codereview.chromium.org'
72
72
  POSTUPSTREAM_HOOK = '.git/hooks/post-cl-land'
73
73
  DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup'
74
74
  REFS_THAT_ALIAS_TO_OTHER_REFS = {
@@ -80,6 +80,9 @@ REFS_THAT_ALIAS_TO_OTHER_REFS = {
80
80
  DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)"
81
81
  DEFAULT_LINT_IGNORE_REGEX = r"$^"
82
82
 
83
+ # File name for yapf style config files.
84
+ YAPF_CONFIG_FILENAME = '.style.yapf'
85
+
83
86
  # Buildbucket master name prefix.
84
87
  MASTER_PREFIX = 'master.'
85
88
 
@@ -106,8 +109,7 @@ def DieWithError(message, change_desc=None):
106
109
 
107
110
  def SaveDescriptionBackup(change_desc):
108
111
  backup_path = os.path.expanduser(DESCRIPTION_BACKUP_FILE)
109
- print('\nError after CL description prompt -- saving description to %s\n' %
110
- backup_path)
112
+ print('\nsaving CL description to %s\n' % backup_path)
111
113
  backup_file = open(backup_path, 'w')
112
114
  backup_file.write(change_desc.description)
113
115
  backup_file.close()
@@ -176,10 +178,15 @@ def BranchExists(branch):
176
178
  def time_sleep(seconds):
177
179
  # Use this so that it can be mocked in tests without interfering with python
178
180
  # system machinery.
179
- import time # Local import to discourage others from importing time globally.
180
181
  return time.sleep(seconds)
181
182
 
182
183
 
184
+ def time_time():
185
+ # Use this so that it can be mocked in tests without interfering with python
186
+ # system machinery.
187
+ return time.time()
188
+
189
+
183
190
  def ask_for_data(prompt):
184
191
  try:
185
192
  return raw_input(prompt)
@@ -452,8 +459,7 @@ def _trigger_try_jobs(auth_config, changelist, buckets, options, patchset):
452
459
  buildbucket_put_url = (
453
460
  'https://{hostname}/_ah/api/buildbucket/v1/builds/batch'.format(
454
461
  hostname=options.buildbucket_host))
455
- buildset = 'patch/{codereview}/{hostname}/{issue}/{patch}'.format(
456
- codereview='gerrit' if changelist.IsGerrit() else 'rietveld',
462
+ buildset = 'patch/gerrit/{hostname}/{issue}/{patch}'.format(
457
463
  hostname=codereview_host,
458
464
  issue=changelist.GetIssue(),
459
465
  patch=patchset)
@@ -545,8 +551,7 @@ def fetch_try_jobs(auth_config, changelist, buildbucket_host,
545
551
 
546
552
  http.force_exception_to_status_code = True
547
553
 
548
- buildset = 'patch/{codereview}/{hostname}/{issue}/{patch}'.format(
549
- codereview='gerrit' if changelist.IsGerrit() else 'rietveld',
554
+ buildset = 'patch/gerrit/{hostname}/{issue}/{patch}'.format(
550
555
  hostname=codereview_host,
551
556
  issue=changelist.GetIssue(),
552
557
  patch=patchset)
@@ -666,6 +671,88 @@ def print_try_jobs(options, builds):
666
671
  print('Total: %d try jobs' % total)
667
672
 
668
673
 
674
+ def _ComputeDiffLineRanges(files, upstream_commit):
675
+ """Gets the changed line ranges for each file since upstream_commit.
676
+
677
+ Parses a git diff on provided files and returns a dict that maps a file name
678
+ to an ordered list of range tuples in the form (start_line, count).
679
+ Ranges are in the same format as a git diff.
680
+ """
681
+ # If files is empty then diff_output will be a full diff.
682
+ if len(files) == 0:
683
+ return {}
684
+
685
+ # Take the git diff and find the line ranges where there are changes.
686
+ diff_cmd = BuildGitDiffCmd('-U0', upstream_commit, files, allow_prefix=True)
687
+ diff_output = RunGit(diff_cmd)
688
+
689
+ pattern = r'(?:^diff --git a/(?:.*) b/(.*))|(?:^@@.*\+(.*) @@)'
690
+ # 2 capture groups
691
+ # 0 == fname of diff file
692
+ # 1 == 'diff_start,diff_count' or 'diff_start'
693
+ # will match each of
694
+ # diff --git a/foo.foo b/foo.py
695
+ # @@ -12,2 +14,3 @@
696
+ # @@ -12,2 +17 @@
697
+ # running re.findall on the above string with pattern will give
698
+ # [('foo.py', ''), ('', '14,3'), ('', '17')]
699
+
700
+ curr_file = None
701
+ line_diffs = {}
702
+ for match in re.findall(pattern, diff_output, flags=re.MULTILINE):
703
+ if match[0] != '':
704
+ # Will match the second filename in diff --git a/a.py b/b.py.
705
+ curr_file = match[0]
706
+ line_diffs[curr_file] = []
707
+ else:
708
+ # Matches +14,3
709
+ if ',' in match[1]:
710
+ diff_start, diff_count = match[1].split(',')
711
+ else:
712
+ # Single line changes are of the form +12 instead of +12,1.
713
+ diff_start = match[1]
714
+ diff_count = 1
715
+
716
+ diff_start = int(diff_start)
717
+ diff_count = int(diff_count)
718
+
719
+ # If diff_count == 0 this is a removal we can ignore.
720
+ line_diffs[curr_file].append((diff_start, diff_count))
721
+
722
+ return line_diffs
723
+
724
+
725
+ def _FindYapfConfigFile(fpath, yapf_config_cache, top_dir=None):
726
+ """Checks if a yapf file is in any parent directory of fpath until top_dir.
727
+
728
+ Recursively checks parent directories to find yapf file and if no yapf file
729
+ is found returns None. Uses yapf_config_cache as a cache for
730
+ previously found configs.
731
+ """
732
+ fpath = os.path.abspath(fpath)
733
+ # Return result if we've already computed it.
734
+ if fpath in yapf_config_cache:
735
+ return yapf_config_cache[fpath]
736
+
737
+ parent_dir = os.path.dirname(fpath)
738
+ if os.path.isfile(fpath):
739
+ ret = _FindYapfConfigFile(parent_dir, yapf_config_cache, top_dir)
740
+ else:
741
+ # Otherwise fpath is a directory
742
+ yapf_file = os.path.join(fpath, YAPF_CONFIG_FILENAME)
743
+ if os.path.isfile(yapf_file):
744
+ ret = yapf_file
745
+ elif fpath == top_dir or parent_dir == fpath:
746
+ # If we're at the top level directory, or if we're at root
747
+ # there is no provided style.
748
+ ret = None
749
+ else:
750
+ # Otherwise recurse on the current directory.
751
+ ret = _FindYapfConfigFile(parent_dir, yapf_config_cache, top_dir)
752
+ yapf_config_cache[fpath] = ret
753
+ return ret
754
+
755
+
669
756
  def write_try_results_json(output_file, builds):
670
757
  """Writes a subset of the data from fetch_try_jobs to a file as JSON.
671
758
 
@@ -691,7 +778,7 @@ def write_try_results_json(output_file, builds):
691
778
 
692
779
  converted = []
693
780
  for _, build in sorted(builds.items()):
694
- converted.append(convert_build_dict(build))
781
+ converted.append(convert_build_dict(build))
695
782
  write_json(output_file, converted)
696
783
 
697
784
 
@@ -719,7 +806,6 @@ class BuildbucketResponseException(Exception):
719
806
 
720
807
  class Settings(object):
721
808
  def __init__(self):
722
- self.default_server = None
723
809
  self.cc = None
724
810
  self.root = None
725
811
  self.tree_status_url = None
@@ -729,8 +815,6 @@ class Settings(object):
729
815
  self.squash_gerrit_uploads = None
730
816
  self.gerrit_skip_ensure_authenticated = None
731
817
  self.git_editor = None
732
- self.project = None
733
- self.force_https_commit_url = None
734
818
 
735
819
  def LazyUpdateIfNeeded(self):
736
820
  """Updates the settings from a codereview.settings file, if available."""
@@ -746,20 +830,6 @@ class Settings(object):
746
830
  LoadCodereviewSettingsFromFile(cr_settings_file)
747
831
  self.updated = True
748
832
 
749
- def GetDefaultServerUrl(self, error_ok=False):
750
- if not self.default_server:
751
- self.LazyUpdateIfNeeded()
752
- self.default_server = gclient_utils.UpgradeToHttps(
753
- self._GetRietveldConfig('server', error_ok=True))
754
- if error_ok:
755
- return self.default_server
756
- if not self.default_server:
757
- error_message = ('Could not find settings file. You must configure '
758
- 'your review setup by running "git cl config".')
759
- self.default_server = gclient_utils.UpgradeToHttps(
760
- self._GetRietveldConfig('server', error_message=error_message))
761
- return self.default_server
762
-
763
833
  @staticmethod
764
834
  def GetRelativeRoot():
765
835
  return RunGit(['rev-parse', '--show-cdup']).strip()
@@ -769,50 +839,30 @@ class Settings(object):
769
839
  self.root = os.path.abspath(self.GetRelativeRoot())
770
840
  return self.root
771
841
 
772
- def GetGitMirror(self, remote='origin'):
773
- """If this checkout is from a local git mirror, return a Mirror object."""
774
- local_url = RunGit(['config', '--get', 'remote.%s.url' % remote]).strip()
775
- if not os.path.isdir(local_url):
776
- return None
777
- git_cache.Mirror.SetCachePath(os.path.dirname(local_url))
778
- remote_url = git_cache.Mirror.CacheDirToUrl(local_url)
779
- # Use the /dev/null print_func to avoid terminal spew.
780
- mirror = git_cache.Mirror(remote_url, print_func=lambda *args: None)
781
- if mirror.exists():
782
- return mirror
783
- return None
784
-
785
842
  def GetTreeStatusUrl(self, error_ok=False):
786
843
  if not self.tree_status_url:
787
844
  error_message = ('You must configure your tree status URL by running '
788
845
  '"git cl config".')
789
- self.tree_status_url = self._GetRietveldConfig(
790
- 'tree-status-url', error_ok=error_ok, error_message=error_message)
846
+ self.tree_status_url = self._GetConfig(
847
+ 'rietveld.tree-status-url', error_ok=error_ok,
848
+ error_message=error_message)
791
849
  return self.tree_status_url
792
850
 
793
851
  def GetViewVCUrl(self):
794
852
  if not self.viewvc_url:
795
- self.viewvc_url = self._GetRietveldConfig('viewvc-url', error_ok=True)
853
+ self.viewvc_url = self._GetConfig('rietveld.viewvc-url', error_ok=True)
796
854
  return self.viewvc_url
797
855
 
798
856
  def GetBugPrefix(self):
799
- return self._GetRietveldConfig('bug-prefix', error_ok=True)
800
-
801
- def GetIsSkipDependencyUpload(self, branch_name):
802
- """Returns true if specified branch should skip dep uploads."""
803
- return self._GetBranchConfig(branch_name, 'skip-deps-uploads',
804
- error_ok=True)
857
+ return self._GetConfig('rietveld.bug-prefix', error_ok=True)
805
858
 
806
859
  def GetRunPostUploadHook(self):
807
- run_post_upload_hook = self._GetRietveldConfig(
808
- 'run-post-upload-hook', error_ok=True)
860
+ run_post_upload_hook = self._GetConfig(
861
+ 'rietveld.run-post-upload-hook', error_ok=True)
809
862
  return run_post_upload_hook == "True"
810
863
 
811
864
  def GetDefaultCCList(self):
812
- return self._GetRietveldConfig('cc', error_ok=True)
813
-
814
- def GetDefaultPrivateFlag(self):
815
- return self._GetRietveldConfig('private', error_ok=True)
865
+ return self._GetConfig('rietveld.cc', error_ok=True)
816
866
 
817
867
  def GetIsGerrit(self):
818
868
  """Return true if this repo is associated with gerrit code review system."""
@@ -858,28 +908,21 @@ class Settings(object):
858
908
  def GetGitEditor(self):
859
909
  """Return the editor specified in the git config, or None if none is."""
860
910
  if self.git_editor is None:
861
- self.git_editor = self._GetConfig('core.editor', error_ok=True)
911
+ # Git requires single quotes for paths with spaces. We need to replace
912
+ # them with double quotes for Windows to treat such paths as a single
913
+ # path.
914
+ self.git_editor = self._GetConfig(
915
+ 'core.editor', error_ok=True).replace('\'', '"')
862
916
  return self.git_editor or None
863
917
 
864
918
  def GetLintRegex(self):
865
- return (self._GetRietveldConfig('cpplint-regex', error_ok=True) or
919
+ return (self._GetConfig('rietveld.cpplint-regex', error_ok=True) or
866
920
  DEFAULT_LINT_REGEX)
867
921
 
868
922
  def GetLintIgnoreRegex(self):
869
- return (self._GetRietveldConfig('cpplint-ignore-regex', error_ok=True) or
923
+ return (self._GetConfig('rietveld.cpplint-ignore-regex', error_ok=True) or
870
924
  DEFAULT_LINT_IGNORE_REGEX)
871
925
 
872
- def GetProject(self):
873
- if not self.project:
874
- self.project = self._GetRietveldConfig('project', error_ok=True)
875
- return self.project
876
-
877
- def _GetRietveldConfig(self, param, **kwargs):
878
- return self._GetConfig('rietveld.' + param, **kwargs)
879
-
880
- def _GetBranchConfig(self, branch_name, param, **kwargs):
881
- return self._GetConfig('branch.' + branch_name + '.' + param, **kwargs)
882
-
883
926
  def _GetConfig(self, param, **kwargs):
884
927
  self.LazyUpdateIfNeeded()
885
928
  return RunGit(['config', param], **kwargs).strip()
@@ -915,57 +958,6 @@ def _get_gerrit_project_config_file(remote_url):
915
958
  yield project_config_file
916
959
 
917
960
 
918
- def _is_git_numberer_enabled(remote_url, remote_ref):
919
- """Returns True if Git Numberer is enabled on this ref."""
920
- # TODO(tandrii): this should be deleted once repos below are 100% on Gerrit.
921
- KNOWN_PROJECTS_WHITELIST = [
922
- 'chromium/src',
923
- 'external/webrtc',
924
- 'v8/v8',
925
- 'infra/experimental',
926
- # For webrtc.googlesource.com/src.
927
- 'src',
928
- ]
929
-
930
- assert remote_ref and remote_ref.startswith('refs/'), remote_ref
931
- url_parts = urlparse.urlparse(remote_url)
932
- project_name = url_parts.path.lstrip('/').rstrip('git./')
933
- for known in KNOWN_PROJECTS_WHITELIST:
934
- if project_name.endswith(known):
935
- break
936
- else:
937
- # Early exit to avoid extra fetches for repos that aren't using Git
938
- # Numberer.
939
- return False
940
-
941
- with _get_gerrit_project_config_file(remote_url) as project_config_file:
942
- if project_config_file is None:
943
- # Failed to fetch project.config, which shouldn't happen on open source
944
- # repos KNOWN_PROJECTS_WHITELIST.
945
- return False
946
- def get_opts(x):
947
- code, out = RunGitWithCode(
948
- ['config', '-f', project_config_file, '--get-all',
949
- 'plugin.git-numberer.validate-%s-refglob' % x])
950
- if code == 0:
951
- return out.strip().splitlines()
952
- return []
953
- enabled, disabled = map(get_opts, ['enabled', 'disabled'])
954
-
955
- logging.info('validator config enabled %s disabled %s refglobs for '
956
- '(this ref: %s)', enabled, disabled, remote_ref)
957
-
958
- def match_refglobs(refglobs):
959
- for refglob in refglobs:
960
- if remote_ref == refglob or fnmatch.fnmatch(remote_ref, refglob):
961
- return True
962
- return False
963
-
964
- if match_refglobs(disabled):
965
- return False
966
- return match_refglobs(enabled)
967
-
968
-
969
961
  def ShortBranchName(branch):
970
962
  """Convert a name like 'refs/heads/foo' to just 'foo'."""
971
963
  return branch.replace('refs/heads/', '', 1)
@@ -1002,7 +994,7 @@ class _ParsedIssueNumberArgument(object):
1002
994
  self.issue = issue
1003
995
  self.patchset = patchset
1004
996
  self.hostname = hostname
1005
- assert codereview in (None, 'rietveld', 'gerrit')
997
+ assert codereview in (None, 'gerrit', 'rietveld')
1006
998
  self.codereview = codereview
1007
999
 
1008
1000
  @property
@@ -1029,22 +1021,21 @@ def ParseIssueNumberArgument(arg, codereview=None):
1029
1021
  parsed = _CODEREVIEW_IMPLEMENTATIONS[codereview].ParseIssueURL(parsed_url)
1030
1022
  return parsed or fail_result
1031
1023
 
1032
- results = {}
1033
- for name, cls in _CODEREVIEW_IMPLEMENTATIONS.iteritems():
1034
- parsed = cls.ParseIssueURL(parsed_url)
1035
- if parsed is not None:
1036
- results[name] = parsed
1024
+ return _GerritChangelistImpl.ParseIssueURL(parsed_url) or fail_result
1037
1025
 
1038
- if not results:
1039
- return fail_result
1040
- if len(results) == 1:
1041
- return results.values()[0]
1042
1026
 
1043
- if parsed_url.netloc and parsed_url.netloc.split('.')[0].endswith('-review'):
1044
- # This is likely Gerrit.
1045
- return results['gerrit']
1046
- # Choose Rietveld as before if URL can parsed by either.
1047
- return results['rietveld']
1027
+ def _create_description_from_log(args):
1028
+ """Pulls out the commit log to use as a base for the CL description."""
1029
+ log_args = []
1030
+ if len(args) == 1 and not args[0].endswith('.'):
1031
+ log_args = [args[0] + '..']
1032
+ elif len(args) == 1 and args[0].endswith('...'):
1033
+ log_args = [args[0][:-1]]
1034
+ elif len(args) == 2:
1035
+ log_args = [args[0] + '..' + args[1]]
1036
+ else:
1037
+ log_args = args[:] # Hope for the best!
1038
+ return RunGit(['log', '--pretty=format:%s\n\n%b'] + log_args)
1048
1039
 
1049
1040
 
1050
1041
  class GerritChangeNotExists(Exception):
@@ -1059,7 +1050,7 @@ class GerritChangeNotExists(Exception):
1059
1050
 
1060
1051
 
1061
1052
  _CommentSummary = collections.namedtuple(
1062
- '_CommentSummary', ['date', 'message', 'sender',
1053
+ '_CommentSummary', ['date', 'message', 'sender', 'autogenerated',
1063
1054
  # TODO(tandrii): these two aren't known in Gerrit.
1064
1055
  'approval', 'disapproval'])
1065
1056
 
@@ -1113,6 +1104,7 @@ class Changelist(object):
1113
1104
  self.cc = None
1114
1105
  self.more_cc = []
1115
1106
  self._remote = None
1107
+ self._cached_remote_url = (False, None) # (is_cached, value)
1116
1108
 
1117
1109
  self._codereview_impl = None
1118
1110
  self._codereview = None
@@ -1122,7 +1114,9 @@ class Changelist(object):
1122
1114
 
1123
1115
  def _load_codereview_impl(self, codereview=None, **kwargs):
1124
1116
  if codereview:
1125
- assert codereview in _CODEREVIEW_IMPLEMENTATIONS
1117
+ assert codereview in _CODEREVIEW_IMPLEMENTATIONS, (
1118
+ 'codereview {} not in {}'.format(codereview,
1119
+ _CODEREVIEW_IMPLEMENTATIONS))
1126
1120
  cls = _CODEREVIEW_IMPLEMENTATIONS[codereview]
1127
1121
  self._codereview = codereview
1128
1122
  self._codereview_impl = cls(self, **kwargs)
@@ -1143,9 +1137,9 @@ class Changelist(object):
1143
1137
  self.issue = int(issue)
1144
1138
  return
1145
1139
 
1146
- # No issue is set for this branch, so decide based on repo-wide settings.
1140
+ # No issue is set for this branch, so default to gerrit.
1147
1141
  return self._load_codereview_impl(
1148
- codereview='gerrit' if settings.GetIsGerrit() else 'rietveld',
1142
+ codereview='gerrit',
1149
1143
  **kwargs)
1150
1144
 
1151
1145
  def IsGerrit(self):
@@ -1342,14 +1336,47 @@ class Changelist(object):
1342
1336
 
1343
1337
  Returns None if there is no remote.
1344
1338
  """
1339
+ is_cached, value = self._cached_remote_url
1340
+ if is_cached:
1341
+ return value
1342
+
1345
1343
  remote, _ = self.GetRemoteBranch()
1346
1344
  url = RunGit(['config', 'remote.%s.url' % remote], error_ok=True).strip()
1347
1345
 
1348
- # If URL is pointing to a local directory, it is probably a git cache.
1349
- if os.path.isdir(url):
1350
- url = RunGit(['config', 'remote.%s.url' % remote],
1351
- error_ok=True,
1352
- cwd=url).strip()
1346
+ # Check if the remote url can be parsed as an URL.
1347
+ host = urlparse.urlparse(url).netloc
1348
+ if host:
1349
+ self._cached_remote_url = (True, url)
1350
+ return url
1351
+
1352
+ # If it cannot be parsed as an url, assume it is a local directory, probably
1353
+ # a git cache.
1354
+ logging.warning('"%s" doesn\'t appear to point to a git host. '
1355
+ 'Interpreting it as a local directory.', url)
1356
+ if not os.path.isdir(url):
1357
+ logging.error(
1358
+ 'Remote "%s" for branch "%s" points to "%s", but it doesn\'t exist.',
1359
+ remote, url, self.GetBranch())
1360
+ return None
1361
+
1362
+ cache_path = url
1363
+ url = RunGit(['config', 'remote.%s.url' % remote],
1364
+ error_ok=True,
1365
+ cwd=url).strip()
1366
+
1367
+ host = urlparse.urlparse(url).netloc
1368
+ if not host:
1369
+ logging.error(
1370
+ 'Remote "%(remote)s" for branch "%(branch)s" points to '
1371
+ '"%(cache_path)s", but it is misconfigured.\n'
1372
+ '"%(cache_path)s" must be a git repo and must have a remote named '
1373
+ '"%(remote)s" pointing to the git host.', {
1374
+ 'remote': remote,
1375
+ 'cache_path': cache_path,
1376
+ 'branch': self.GetBranch()})
1377
+ return None
1378
+
1379
+ self._cached_remote_url = (True, url)
1353
1380
  return url
1354
1381
 
1355
1382
  def GetIssue(self):
@@ -1547,6 +1574,7 @@ class Changelist(object):
1547
1574
 
1548
1575
  def CMDUpload(self, options, git_diff_args, orig_args):
1549
1576
  """Uploads a change to codereview."""
1577
+ assert self.IsGerrit()
1550
1578
  custom_cl_base = None
1551
1579
  if git_diff_args:
1552
1580
  custom_cl_base = base_branch = git_diff_args[0]
@@ -1558,15 +1586,6 @@ class Changelist(object):
1558
1586
  base_branch = self.GetCommonAncestorWithUpstream()
1559
1587
  git_diff_args = [base_branch, 'HEAD']
1560
1588
 
1561
- # Warn about Rietveld deprecation for initial uploads to Rietveld.
1562
- if not self.IsGerrit() and not self.GetIssue():
1563
- print('=====================================')
1564
- print('NOTICE: Rietveld is being deprecated. '
1565
- 'You can upload changes to Gerrit with')
1566
- print(' git cl upload --gerrit')
1567
- print('or set Gerrit to be your default code review tool with')
1568
- print(' git config gerrit.host true')
1569
- print('=====================================')
1570
1589
 
1571
1590
  # Fast best-effort checks to abort before running potentially
1572
1591
  # expensive hooks if uploading is likely to fail anyway. Passing these
@@ -1600,33 +1619,9 @@ class Changelist(object):
1600
1619
  options.reviewers = hook_results.reviewers.split(',')
1601
1620
  self.ExtendCC(hook_results.more_cc)
1602
1621
 
1603
- # TODO(tandrii): Checking local patchset against remote patchset is only
1604
- # supported for Rietveld. Extend it to Gerrit or remove it completely.
1605
- if self.GetIssue() and not self.IsGerrit():
1606
- latest_patchset = self.GetMostRecentPatchset()
1607
- local_patchset = self.GetPatchset()
1608
- if (latest_patchset and local_patchset and
1609
- local_patchset != latest_patchset):
1610
- print('The last upload made from this repository was patchset #%d but '
1611
- 'the most recent patchset on the server is #%d.'
1612
- % (local_patchset, latest_patchset))
1613
- print('Uploading will still work, but if you\'ve uploaded to this '
1614
- 'issue from another machine or branch the patch you\'re '
1615
- 'uploading now might not include those changes.')
1616
- confirm_or_exit(action='upload')
1617
-
1618
1622
  print_stats(git_diff_args)
1619
1623
  ret = self.CMDUploadChange(options, git_diff_args, custom_cl_base, change)
1620
1624
  if not ret:
1621
- if self.IsGerrit():
1622
- self.SetLabels(options.enable_auto_submit, options.use_commit_queue,
1623
- options.cq_dry_run);
1624
- else:
1625
- if options.use_commit_queue:
1626
- self.SetCQState(_CQState.COMMIT)
1627
- elif options.cq_dry_run:
1628
- self.SetCQState(_CQState.DRY_RUN)
1629
-
1630
1625
  _git_set_branch_config_value('last-upload-hash',
1631
1626
  RunGit(['rev-parse', 'HEAD']).strip())
1632
1627
  # Run post upload hooks, if specified.
@@ -1650,41 +1645,6 @@ class Changelist(object):
1650
1645
  ret = upload_branch_deps(self, orig_args)
1651
1646
  return ret
1652
1647
 
1653
- def SetLabels(self, enable_auto_submit, use_commit_queue, cq_dry_run):
1654
- """Sets labels on the change based on the provided flags.
1655
-
1656
- Sets labels if issue is already uploaded and known, else returns without
1657
- doing anything.
1658
-
1659
- Args:
1660
- enable_auto_submit: Sets Auto-Submit+1 on the change.
1661
- use_commit_queue: Sets Commit-Queue+2 on the change.
1662
- cq_dry_run: Sets Commit-Queue+1 on the change. Overrides Commit-Queue+2 if
1663
- both use_commit_queue and cq_dry_run are true.
1664
- """
1665
- if not self.GetIssue():
1666
- return
1667
- try:
1668
- self._codereview_impl.SetLabels(enable_auto_submit, use_commit_queue,
1669
- cq_dry_run)
1670
- return 0
1671
- except KeyboardInterrupt:
1672
- raise
1673
- except:
1674
- labels = []
1675
- if enable_auto_submit:
1676
- labels.append('Auto-Submit')
1677
- if use_commit_queue or cq_dry_run:
1678
- labels.append('Commit-Queue')
1679
- print('WARNING: Failed to set label(s) on your change: %s\n'
1680
- 'Either:\n'
1681
- ' * Your project does not have the above label(s),\n'
1682
- ' * You don\'t have permission to set the above label(s),\n'
1683
- ' * There\'s a bug in this code (see stack trace below).\n' %
1684
- (', '.join(labels)))
1685
- # Still raise exception so that stack trace is printed.
1686
- raise
1687
-
1688
1648
  def SetCQState(self, new_state):
1689
1649
  """Updates the CQ state for the latest patchset.
1690
1650
 
@@ -1878,13 +1838,6 @@ class _ChangelistCodereviewBase(object):
1878
1838
  """Uploads a change to codereview."""
1879
1839
  raise NotImplementedError()
1880
1840
 
1881
- def SetLabels(self, enable_auto_submit, use_commit_queue, cq_dry_run):
1882
- """Sets labels on the change based on the provided flags.
1883
-
1884
- Issue must have been already uploaded and known.
1885
- """
1886
- raise NotImplementedError()
1887
-
1888
1841
  def SetCQState(self, new_state):
1889
1842
  """Updates the CQ state for the latest patchset.
1890
1843
 
@@ -1906,446 +1859,6 @@ class _ChangelistCodereviewBase(object):
1906
1859
  raise NotImplementedError()
1907
1860
 
1908
1861
 
1909
- class _RietveldChangelistImpl(_ChangelistCodereviewBase):
1910
-
1911
- def __init__(self, changelist, auth_config=None, codereview_host=None):
1912
- super(_RietveldChangelistImpl, self).__init__(changelist)
1913
- assert settings, 'must be initialized in _ChangelistCodereviewBase'
1914
- if not codereview_host:
1915
- settings.GetDefaultServerUrl()
1916
-
1917
- self._rietveld_server = codereview_host
1918
- self._auth_config = auth_config or auth.make_auth_config()
1919
- self._props = None
1920
- self._rpc_server = None
1921
-
1922
- def GetCodereviewServer(self):
1923
- if not self._rietveld_server:
1924
- # If we're on a branch then get the server potentially associated
1925
- # with that branch.
1926
- if self.GetIssue():
1927
- self._rietveld_server = gclient_utils.UpgradeToHttps(
1928
- self._GitGetBranchConfigValue(self.CodereviewServerConfigKey()))
1929
- if not self._rietveld_server:
1930
- self._rietveld_server = settings.GetDefaultServerUrl()
1931
- return self._rietveld_server
1932
-
1933
- def EnsureAuthenticated(self, force, refresh=False):
1934
- """Best effort check that user is authenticated with Rietveld server."""
1935
- if self._auth_config.use_oauth2:
1936
- authenticator = auth.get_authenticator_for_host(
1937
- self.GetCodereviewServer(), self._auth_config)
1938
- if not authenticator.has_cached_credentials():
1939
- raise auth.LoginRequiredError(self.GetCodereviewServer())
1940
- if refresh:
1941
- authenticator.get_access_token()
1942
-
1943
- def EnsureCanUploadPatchset(self, force):
1944
- # No checks for Rietveld because we are deprecating Rietveld.
1945
- pass
1946
-
1947
- def FetchDescription(self, force=False):
1948
- issue = self.GetIssue()
1949
- assert issue
1950
- try:
1951
- return self.RpcServer().get_description(issue, force=force).strip()
1952
- except urllib2.HTTPError as e:
1953
- if e.code == 404:
1954
- DieWithError(
1955
- ('\nWhile fetching the description for issue %d, received a '
1956
- '404 (not found)\n'
1957
- 'error. It is likely that you deleted this '
1958
- 'issue on the server. If this is the\n'
1959
- 'case, please run\n\n'
1960
- ' git cl issue 0\n\n'
1961
- 'to clear the association with the deleted issue. Then run '
1962
- 'this command again.') % issue)
1963
- else:
1964
- DieWithError(
1965
- '\nFailed to fetch issue description. HTTP error %d' % e.code)
1966
- except urllib2.URLError as e:
1967
- print('Warning: Failed to retrieve CL description due to network '
1968
- 'failure.', file=sys.stderr)
1969
- return ''
1970
-
1971
- def GetMostRecentPatchset(self):
1972
- return self.GetIssueProperties()['patchsets'][-1]
1973
-
1974
- def GetIssueProperties(self):
1975
- if self._props is None:
1976
- issue = self.GetIssue()
1977
- if not issue:
1978
- self._props = {}
1979
- else:
1980
- self._props = self.RpcServer().get_issue_properties(issue, True)
1981
- return self._props
1982
-
1983
- def CannotTriggerTryJobReason(self):
1984
- props = self.GetIssueProperties()
1985
- if not props:
1986
- return 'Rietveld doesn\'t know about your issue %s' % self.GetIssue()
1987
- if props.get('closed'):
1988
- return 'CL %s is closed' % self.GetIssue()
1989
- if props.get('private'):
1990
- return 'CL %s is private' % self.GetIssue()
1991
- return None
1992
-
1993
- def GetTryJobProperties(self, patchset=None):
1994
- """Returns dictionary of properties to launch try job."""
1995
- project = (self.GetIssueProperties() or {}).get('project')
1996
- return {
1997
- 'issue': self.GetIssue(),
1998
- 'patch_project': project,
1999
- 'patch_storage': 'rietveld',
2000
- 'patchset': patchset or self.GetPatchset(),
2001
- 'rietveld': self.GetCodereviewServer(),
2002
- }
2003
-
2004
- def GetIssueOwner(self):
2005
- return (self.GetIssueProperties() or {}).get('owner_email')
2006
-
2007
- def GetReviewers(self):
2008
- return (self.GetIssueProperties() or {}).get('reviewers')
2009
-
2010
- def AddComment(self, message, publish=None):
2011
- return self.RpcServer().add_comment(self.GetIssue(), message)
2012
-
2013
- def GetCommentsSummary(self, _readable=True):
2014
- summary = []
2015
- for message in self.GetIssueProperties().get('messages', []):
2016
- date = datetime.datetime.strptime(message['date'], '%Y-%m-%d %H:%M:%S.%f')
2017
- summary.append(_CommentSummary(
2018
- date=date,
2019
- disapproval=bool(message['disapproval']),
2020
- approval=bool(message['approval']),
2021
- sender=message['sender'],
2022
- message=message['text'],
2023
- ))
2024
- return summary
2025
-
2026
- def GetStatus(self):
2027
- """Applies a rough heuristic to give a simple summary of an issue's review
2028
- or CQ status, assuming adherence to a common workflow.
2029
-
2030
- Returns None if no issue for this branch, or one of the following keywords:
2031
- * 'error' - error from review tool (including deleted issues)
2032
- * 'unsent' - not sent for review
2033
- * 'waiting' - waiting for review
2034
- * 'reply' - waiting for owner to reply to review
2035
- * 'not lgtm' - Code-Review label has been set negatively
2036
- * 'lgtm' - LGTM from at least one approved reviewer
2037
- * 'commit' - in the commit queue
2038
- * 'closed' - closed
2039
- """
2040
- if not self.GetIssue():
2041
- return None
2042
-
2043
- try:
2044
- props = self.GetIssueProperties()
2045
- except urllib2.HTTPError:
2046
- return 'error'
2047
-
2048
- if props.get('closed'):
2049
- # Issue is closed.
2050
- return 'closed'
2051
- if props.get('commit') and not props.get('cq_dry_run', False):
2052
- # Issue is in the commit queue.
2053
- return 'commit'
2054
-
2055
- messages = props.get('messages') or []
2056
- if not messages:
2057
- # No message was sent.
2058
- return 'unsent'
2059
-
2060
- if get_approving_reviewers(props):
2061
- return 'lgtm'
2062
- elif get_approving_reviewers(props, disapproval=True):
2063
- return 'not lgtm'
2064
-
2065
- # Skip CQ messages that don't require owner's action.
2066
- while messages and messages[-1]['sender'] == COMMIT_BOT_EMAIL:
2067
- if 'Dry run:' in messages[-1]['text']:
2068
- messages.pop()
2069
- elif 'The CQ bit was unchecked' in messages[-1]['text']:
2070
- # This message always follows prior messages from CQ,
2071
- # so skip this too.
2072
- messages.pop()
2073
- else:
2074
- # This is probably a CQ messages warranting user attention.
2075
- break
2076
-
2077
- if messages[-1]['sender'] != props.get('owner_email'):
2078
- # Non-LGTM reply from non-owner and not CQ bot.
2079
- return 'reply'
2080
- return 'waiting'
2081
-
2082
- def UpdateDescriptionRemote(self, description, force=False):
2083
- self.RpcServer().update_description(self.GetIssue(), description)
2084
-
2085
- def CloseIssue(self):
2086
- return self.RpcServer().close_issue(self.GetIssue())
2087
-
2088
- def SetFlag(self, flag, value):
2089
- return self.SetFlags({flag: value})
2090
-
2091
- def SetFlags(self, flags):
2092
- """Sets flags on this CL/patchset in Rietveld.
2093
- """
2094
- patchset = self.GetPatchset() or self.GetMostRecentPatchset()
2095
- try:
2096
- return self.RpcServer().set_flags(
2097
- self.GetIssue(), patchset, flags)
2098
- except urllib2.HTTPError as e:
2099
- if e.code == 404:
2100
- DieWithError('The issue %s doesn\'t exist.' % self.GetIssue())
2101
- if e.code == 403:
2102
- DieWithError(
2103
- ('Access denied to issue %s. Maybe the patchset %s doesn\'t '
2104
- 'match?') % (self.GetIssue(), patchset))
2105
- raise
2106
-
2107
- def RpcServer(self):
2108
- """Returns an upload.RpcServer() to access this review's rietveld instance.
2109
- """
2110
- if not self._rpc_server:
2111
- self._rpc_server = rietveld.CachingRietveld(
2112
- self.GetCodereviewServer(),
2113
- self._auth_config)
2114
- return self._rpc_server
2115
-
2116
- @classmethod
2117
- def IssueConfigKey(cls):
2118
- return 'rietveldissue'
2119
-
2120
- @classmethod
2121
- def PatchsetConfigKey(cls):
2122
- return 'rietveldpatchset'
2123
-
2124
- @classmethod
2125
- def CodereviewServerConfigKey(cls):
2126
- return 'rietveldserver'
2127
-
2128
- def SetLabels(self, enable_auto_submit, use_commit_queue, cq_dry_run):
2129
- raise NotImplementedError()
2130
-
2131
- def SetCQState(self, new_state):
2132
- props = self.GetIssueProperties()
2133
- if props.get('private'):
2134
- DieWithError('Cannot set-commit on private issue')
2135
-
2136
- if new_state == _CQState.COMMIT:
2137
- self.SetFlags({'commit': '1', 'cq_dry_run': '0'})
2138
- elif new_state == _CQState.NONE:
2139
- self.SetFlags({'commit': '0', 'cq_dry_run': '0'})
2140
- else:
2141
- assert new_state == _CQState.DRY_RUN
2142
- self.SetFlags({'commit': '1', 'cq_dry_run': '1'})
2143
-
2144
- def CMDPatchWithParsedIssue(self, parsed_issue_arg, reject, nocommit,
2145
- directory, force):
2146
- # PatchIssue should never be called with a dirty tree. It is up to the
2147
- # caller to check this, but just in case we assert here since the
2148
- # consequences of the caller not checking this could be dire.
2149
- assert(not git_common.is_dirty_git_tree('apply'))
2150
- assert(parsed_issue_arg.valid)
2151
- self._changelist.issue = parsed_issue_arg.issue
2152
- if parsed_issue_arg.hostname:
2153
- self._rietveld_server = 'https://%s' % parsed_issue_arg.hostname
2154
-
2155
- patchset = parsed_issue_arg.patchset or self.GetMostRecentPatchset()
2156
- patchset_object = self.RpcServer().get_patch(self.GetIssue(), patchset)
2157
- scm_obj = checkout.GitCheckout(settings.GetRoot(), None, None, None, None)
2158
- try:
2159
- scm_obj.apply_patch(patchset_object)
2160
- except Exception as e:
2161
- print(str(e))
2162
- return 1
2163
-
2164
- # If we had an issue, commit the current state and register the issue.
2165
- if not nocommit:
2166
- self.SetIssue(self.GetIssue())
2167
- self.SetPatchset(patchset)
2168
- RunGit(['commit', '-m', (self.GetDescription() + '\n\n' +
2169
- 'patch from issue %(i)s at patchset '
2170
- '%(p)s (http://crrev.com/%(i)s#ps%(p)s)'
2171
- % {'i': self.GetIssue(), 'p': patchset})])
2172
- print('Committed patch locally.')
2173
- else:
2174
- print('Patch applied to index.')
2175
- return 0
2176
-
2177
- @staticmethod
2178
- def ParseIssueURL(parsed_url):
2179
- if not parsed_url.scheme or not parsed_url.scheme.startswith('http'):
2180
- return None
2181
- # Rietveld patch: https://domain/<number>/#ps<patchset>
2182
- match = re.match(r'/(\d+)/$', parsed_url.path)
2183
- match2 = re.match(r'ps(\d+)$', parsed_url.fragment)
2184
- if match and match2:
2185
- return _ParsedIssueNumberArgument(
2186
- issue=int(match.group(1)),
2187
- patchset=int(match2.group(1)),
2188
- hostname=parsed_url.netloc,
2189
- codereview='rietveld')
2190
- # Typical url: https://domain/<issue_number>[/[other]]
2191
- match = re.match('/(\d+)(/.*)?$', parsed_url.path)
2192
- if match:
2193
- return _ParsedIssueNumberArgument(
2194
- issue=int(match.group(1)),
2195
- hostname=parsed_url.netloc,
2196
- codereview='rietveld')
2197
- # Rietveld patch: https://domain/download/issue<number>_<patchset>.diff
2198
- match = re.match(r'/download/issue(\d+)_(\d+).diff$', parsed_url.path)
2199
- if match:
2200
- return _ParsedIssueNumberArgument(
2201
- issue=int(match.group(1)),
2202
- patchset=int(match.group(2)),
2203
- hostname=parsed_url.netloc,
2204
- codereview='rietveld')
2205
- return None
2206
-
2207
- def CMDUploadChange(self, options, args, custom_cl_base, change):
2208
- """Upload the patch to Rietveld."""
2209
- upload_args = ['--assume_yes'] # Don't ask about untracked files.
2210
- upload_args.extend(['--server', self.GetCodereviewServer()])
2211
- upload_args.extend(auth.auth_config_to_command_options(self._auth_config))
2212
- if options.emulate_svn_auto_props:
2213
- upload_args.append('--emulate_svn_auto_props')
2214
-
2215
- change_desc = None
2216
-
2217
- if options.email is not None:
2218
- upload_args.extend(['--email', options.email])
2219
-
2220
- if self.GetIssue():
2221
- if options.title is not None:
2222
- upload_args.extend(['--title', options.title])
2223
- if options.message:
2224
- upload_args.extend(['--message', options.message])
2225
- upload_args.extend(['--issue', str(self.GetIssue())])
2226
- print('This branch is associated with issue %s. '
2227
- 'Adding patch to that issue.' % self.GetIssue())
2228
- else:
2229
- if options.title is not None:
2230
- upload_args.extend(['--title', options.title])
2231
- if options.message:
2232
- message = options.message
2233
- else:
2234
- message = CreateDescriptionFromLog(args)
2235
- if options.title:
2236
- message = options.title + '\n\n' + message
2237
- change_desc = ChangeDescription(message)
2238
- if options.reviewers or options.add_owners_to:
2239
- change_desc.update_reviewers(options.reviewers, options.tbrs,
2240
- options.add_owners_to, change)
2241
- if not options.force:
2242
- change_desc.prompt(bug=options.bug, git_footer=False)
2243
-
2244
- if not change_desc.description:
2245
- print('Description is empty; aborting.')
2246
- return 1
2247
-
2248
- upload_args.extend(['--message', change_desc.description])
2249
- if change_desc.get_reviewers():
2250
- upload_args.append('--reviewers=%s' % ','.join(
2251
- change_desc.get_reviewers()))
2252
- if options.send_mail:
2253
- if not change_desc.get_reviewers():
2254
- DieWithError("Must specify reviewers to send email.", change_desc)
2255
- upload_args.append('--send_mail')
2256
-
2257
- # We check this before applying rietveld.private assuming that in
2258
- # rietveld.cc only addresses which we can send private CLs to are listed
2259
- # if rietveld.private is set, and so we should ignore rietveld.cc only
2260
- # when --private is specified explicitly on the command line.
2261
- if options.private:
2262
- logging.warn('rietveld.cc is ignored since private flag is specified. '
2263
- 'You need to review and add them manually if necessary.')
2264
- cc = self.GetCCListWithoutDefault()
2265
- else:
2266
- cc = self.GetCCList()
2267
- cc = ','.join(filter(None, (cc, ','.join(options.cc))))
2268
- if change_desc.get_cced():
2269
- cc = ','.join(filter(None, (cc, ','.join(change_desc.get_cced()))))
2270
- if cc:
2271
- upload_args.extend(['--cc', cc])
2272
-
2273
- if options.private or settings.GetDefaultPrivateFlag() == "True":
2274
- upload_args.append('--private')
2275
-
2276
- # Include the upstream repo's URL in the change -- this is useful for
2277
- # projects that have their source spread across multiple repos.
2278
- remote_url = self.GetGitBaseUrlFromConfig()
2279
- if not remote_url:
2280
- if self.GetRemoteUrl() and '/' in self.GetUpstreamBranch():
2281
- remote_url = '%s@%s' % (self.GetRemoteUrl(),
2282
- self.GetUpstreamBranch().split('/')[-1])
2283
- if remote_url:
2284
- remote, remote_branch = self.GetRemoteBranch()
2285
- target_ref = GetTargetRef(remote, remote_branch, options.target_branch)
2286
- if target_ref:
2287
- upload_args.extend(['--target_ref', target_ref])
2288
-
2289
- # Look for dependent patchsets. See crbug.com/480453 for more details.
2290
- remote, upstream_branch = self.FetchUpstreamTuple(self.GetBranch())
2291
- upstream_branch = ShortBranchName(upstream_branch)
2292
- if remote is '.':
2293
- # A local branch is being tracked.
2294
- local_branch = upstream_branch
2295
- if settings.GetIsSkipDependencyUpload(local_branch):
2296
- print()
2297
- print('Skipping dependency patchset upload because git config '
2298
- 'branch.%s.skip-deps-uploads is set to True.' % local_branch)
2299
- print()
2300
- else:
2301
- auth_config = auth.extract_auth_config_from_options(options)
2302
- branch_cl = Changelist(branchref='refs/heads/'+local_branch,
2303
- auth_config=auth_config)
2304
- branch_cl_issue_url = branch_cl.GetIssueURL()
2305
- branch_cl_issue = branch_cl.GetIssue()
2306
- branch_cl_patchset = branch_cl.GetPatchset()
2307
- if branch_cl_issue_url and branch_cl_issue and branch_cl_patchset:
2308
- upload_args.extend(
2309
- ['--depends_on_patchset', '%s:%s' % (
2310
- branch_cl_issue, branch_cl_patchset)])
2311
- print(
2312
- '\n'
2313
- 'The current branch (%s) is tracking a local branch (%s) with '
2314
- 'an associated CL.\n'
2315
- 'Adding %s/#ps%s as a dependency patchset.\n'
2316
- '\n' % (self.GetBranch(), local_branch, branch_cl_issue_url,
2317
- branch_cl_patchset))
2318
-
2319
- project = settings.GetProject()
2320
- if project:
2321
- upload_args.extend(['--project', project])
2322
- else:
2323
- print()
2324
- print('WARNING: Uploading without a project specified. Please ensure '
2325
- 'your repo\'s codereview.settings has a "PROJECT: foo" line.')
2326
- print()
2327
-
2328
- try:
2329
- upload_args = ['upload'] + upload_args + args
2330
- logging.info('upload.RealMain(%s)', upload_args)
2331
- issue, patchset = upload.RealMain(upload_args)
2332
- issue = int(issue)
2333
- patchset = int(patchset)
2334
- except KeyboardInterrupt:
2335
- sys.exit(1)
2336
- except:
2337
- # If we got an exception after the user typed a description for their
2338
- # change, back up the description before re-raising.
2339
- if change_desc:
2340
- SaveDescriptionBackup(change_desc)
2341
- raise
2342
-
2343
- if not self.GetIssue():
2344
- self.SetIssue(issue)
2345
- self.SetPatchset(patchset)
2346
- return 0
2347
-
2348
-
2349
1862
  class _GerritChangelistImpl(_ChangelistCodereviewBase):
2350
1863
  def __init__(self, changelist, auth_config=None, codereview_host=None):
2351
1864
  # auth_config is Rietveld thing, kept here to preserve interface only.
@@ -2378,7 +1891,10 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2378
1891
 
2379
1892
  def _GetGitHost(self):
2380
1893
  """Returns git host to be used when uploading change to Gerrit."""
2381
- return urlparse.urlparse(self.GetRemoteUrl()).netloc
1894
+ remote_url = self.GetRemoteUrl()
1895
+ if not remote_url:
1896
+ return None
1897
+ return urlparse.urlparse(remote_url).netloc
2382
1898
 
2383
1899
  def GetCodereviewServer(self):
2384
1900
  if not self._gerrit_server:
@@ -2398,6 +1914,37 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2398
1914
  self._gerrit_server = 'https://%s' % self._gerrit_host
2399
1915
  return self._gerrit_server
2400
1916
 
1917
+ def _GetGerritProject(self):
1918
+ """Returns Gerrit project name based on remote git URL."""
1919
+ remote_url = self.GetRemoteUrl()
1920
+ if remote_url is None:
1921
+ logging.warn('can\'t detect Gerrit project.')
1922
+ return None
1923
+ project = urlparse.urlparse(remote_url).path.strip('/')
1924
+ if project.endswith('.git'):
1925
+ project = project[:-len('.git')]
1926
+ # *.googlesource.com hosts ensure that Git/Gerrit projects don't start with
1927
+ # 'a/' prefix, because 'a/' prefix is used to force authentication in
1928
+ # gitiles/git-over-https protocol. E.g.,
1929
+ # https://chromium.googlesource.com/a/v8/v8 refers to the same repo/project
1930
+ # as
1931
+ # https://chromium.googlesource.com/v8/v8
1932
+ if project.startswith('a/'):
1933
+ project = project[len('a/'):]
1934
+ return project
1935
+
1936
+ def _GerritChangeIdentifier(self):
1937
+ """Handy method for gerrit_util.ChangeIdentifier for a given CL.
1938
+
1939
+ Not to be confused by value of "Change-Id:" footer.
1940
+ If Gerrit project can be determined, this will speed up Gerrit HTTP API RPC.
1941
+ """
1942
+ project = self._GetGerritProject()
1943
+ if project:
1944
+ return gerrit_util.ChangeIdentifier(project, self.GetIssue())
1945
+ # Fall back on still unique, but less efficient change number.
1946
+ return str(self.GetIssue())
1947
+
2401
1948
  @classmethod
2402
1949
  def IssueConfigKey(cls):
2403
1950
  return 'gerritissue'
@@ -2416,13 +1963,16 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2416
1963
  # For projects with unusual authentication schemes.
2417
1964
  # See http://crbug.com/603378.
2418
1965
  return
2419
- # Lazy-loader to identify Gerrit and Git hosts.
2420
- if gerrit_util.GceAuthenticator.is_gce():
1966
+
1967
+ # Check presence of cookies only if using cookies-based auth method.
1968
+ cookie_auth = gerrit_util.Authenticator.get()
1969
+ if not isinstance(cookie_auth, gerrit_util.CookiesAuthenticator):
2421
1970
  return
1971
+
1972
+ # Lazy-loader to identify Gerrit and Git hosts.
2422
1973
  self.GetCodereviewServer()
2423
1974
  git_host = self._GetGitHost()
2424
- assert self._gerrit_server and self._gerrit_host
2425
- cookie_auth = gerrit_util.CookiesAuthenticator()
1975
+ assert self._gerrit_server and self._gerrit_host and git_host
2426
1976
 
2427
1977
  gerrit_auth = cookie_auth.get_auth_header(self._gerrit_host)
2428
1978
  git_auth = cookie_auth.get_auth_header(git_host)
@@ -2464,7 +2014,7 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2464
2014
  # Warm change details cache now to avoid RPCs later, reducing latency for
2465
2015
  # developers.
2466
2016
  self._GetChangeDetail(
2467
- ['DETAILED_ACCOUNTS', 'CURRENT_REVISION', 'CURRENT_COMMIT'])
2017
+ ['DETAILED_ACCOUNTS', 'CURRENT_REVISION', 'CURRENT_COMMIT', 'LABELS'])
2468
2018
 
2469
2019
  status = self._GetChangeDetail()['status']
2470
2020
  if status in ('MERGED', 'ABANDONED'):
@@ -2472,10 +2022,15 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2472
2022
  (self.GetIssueURL(),
2473
2023
  'submitted' if status == 'MERGED' else 'abandoned'))
2474
2024
 
2475
- if gerrit_util.GceAuthenticator.is_gce():
2025
+ # TODO(vadimsh): For some reason the chunk of code below was skipped if
2026
+ # 'is_gce' is True. I'm just refactoring it to be 'skip if not cookies'.
2027
+ # Apparently this check is not very important? Otherwise get_auth_email
2028
+ # could have been added to other implementations of Authenticator.
2029
+ cookies_auth = gerrit_util.Authenticator.get()
2030
+ if not isinstance(cookies_auth, gerrit_util.CookiesAuthenticator):
2476
2031
  return
2477
- cookies_user = gerrit_util.CookiesAuthenticator().get_auth_email(
2478
- self._GetGerritHost())
2032
+
2033
+ cookies_user = cookies_auth.get_auth_email(self._GetGerritHost())
2479
2034
  if self.GetIssueOwner() == cookies_user:
2480
2035
  return
2481
2036
  logging.debug('change %s owner is %s, cookies user is %s',
@@ -2492,7 +2047,6 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2492
2047
  (self.GetIssue(), self.GetIssueOwner(), details['email']))
2493
2048
  confirm_or_exit(action='upload')
2494
2049
 
2495
-
2496
2050
  def _PostUnsetIssueProperties(self):
2497
2051
  """Which branch-specific properties to erase when unsetting issue."""
2498
2052
  return ['gerritsquashhash']
@@ -2510,6 +2064,7 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2510
2064
  * 'waiting' - waiting for review
2511
2065
  * 'reply' - waiting for uploader to reply to review
2512
2066
  * 'lgtm' - Code-Review label has been set
2067
+ * 'dry-run' - dry-running in the commit queue
2513
2068
  * 'commit' - in the commit queue
2514
2069
  * 'closed' - successfully submitted or abandoned
2515
2070
  """
@@ -2525,10 +2080,14 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2525
2080
  if data['status'] in ('ABANDONED', 'MERGED'):
2526
2081
  return 'closed'
2527
2082
 
2528
- if data['labels'].get('Commit-Queue', {}).get('approved'):
2529
- # The section will have an "approved" subsection if anyone has voted
2530
- # the maximum value on the label.
2083
+ cq_label = data['labels'].get('Commit-Queue', {})
2084
+ max_cq_vote = 0
2085
+ for vote in cq_label.get('all', []):
2086
+ max_cq_vote = max(max_cq_vote, vote.get('value', 0))
2087
+ if max_cq_vote == 2:
2531
2088
  return 'commit'
2089
+ if max_cq_vote == 1:
2090
+ return 'dry-run'
2532
2091
 
2533
2092
  if data['labels'].get('Code-Review', {}).get('approved'):
2534
2093
  return 'lgtm'
@@ -2564,31 +2123,48 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2564
2123
  data = self._GetChangeDetail(['CURRENT_REVISION', 'CURRENT_COMMIT'],
2565
2124
  no_cache=force)
2566
2125
  current_rev = data['current_revision']
2567
- return data['revisions'][current_rev]['commit']['message']
2126
+ return data['revisions'][current_rev]['commit']['message'].encode(
2127
+ 'utf-8', 'ignore')
2568
2128
 
2569
2129
  def UpdateDescriptionRemote(self, description, force=False):
2570
- if gerrit_util.HasPendingChangeEdit(self._GetGerritHost(), self.GetIssue()):
2130
+ if gerrit_util.HasPendingChangeEdit(
2131
+ self._GetGerritHost(), self._GerritChangeIdentifier()):
2571
2132
  if not force:
2572
2133
  confirm_or_exit(
2573
2134
  'The description cannot be modified while the issue has a pending '
2574
2135
  'unpublished edit. Either publish the edit in the Gerrit web UI '
2575
2136
  'or delete it.\n\n', action='delete the unpublished edit')
2576
2137
 
2577
- gerrit_util.DeletePendingChangeEdit(self._GetGerritHost(),
2578
- self.GetIssue())
2579
- gerrit_util.SetCommitMessage(self._GetGerritHost(), self.GetIssue(),
2580
- description, notify='NONE')
2138
+ gerrit_util.DeletePendingChangeEdit(
2139
+ self._GetGerritHost(), self._GerritChangeIdentifier())
2140
+ gerrit_util.SetCommitMessage(
2141
+ self._GetGerritHost(), self._GerritChangeIdentifier(),
2142
+ description, notify='NONE')
2581
2143
 
2582
2144
  def AddComment(self, message, publish=None):
2583
- gerrit_util.SetReview(self._GetGerritHost(), self.GetIssue(),
2584
- msg=message, ready=publish)
2145
+ gerrit_util.SetReview(
2146
+ self._GetGerritHost(), self._GerritChangeIdentifier(),
2147
+ msg=message, ready=publish)
2585
2148
 
2586
2149
  def GetCommentsSummary(self, readable=True):
2587
2150
  # DETAILED_ACCOUNTS is to get emails in accounts.
2151
+ # CURRENT_REVISION is included to get the latest patchset so that
2152
+ # only the robot comments from the latest patchset can be shown.
2588
2153
  messages = self._GetChangeDetail(
2589
- options=['MESSAGES', 'DETAILED_ACCOUNTS']).get('messages', [])
2154
+ options=['MESSAGES', 'DETAILED_ACCOUNTS',
2155
+ 'CURRENT_REVISION']).get('messages', [])
2590
2156
  file_comments = gerrit_util.GetChangeComments(
2591
- self._GetGerritHost(), self.GetIssue())
2157
+ self._GetGerritHost(), self._GerritChangeIdentifier())
2158
+ robot_file_comments = gerrit_util.GetChangeRobotComments(
2159
+ self._GetGerritHost(), self._GerritChangeIdentifier())
2160
+
2161
+ # Add the robot comments onto the list of comments, but only
2162
+ # keep those that are from the latest pachset.
2163
+ latest_patch_set = self.GetMostRecentPatchset()
2164
+ for path, robot_comments in robot_file_comments.iteritems():
2165
+ line_comments = file_comments.setdefault(path, [])
2166
+ line_comments.extend(
2167
+ [c for c in robot_comments if c['patch_set'] == latest_patch_set])
2592
2168
 
2593
2169
  # Build dictionary of file comments for easy access and sorting later.
2594
2170
  # {author+date: {path: {patchset: {line: url+message}}}}
@@ -2596,7 +2172,8 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2596
2172
  lambda: collections.defaultdict(lambda: collections.defaultdict(dict)))
2597
2173
  for path, line_comments in file_comments.iteritems():
2598
2174
  for comment in line_comments:
2599
- if comment.get('tag', '').startswith('autogenerated'):
2175
+ tag = comment.get('tag', '')
2176
+ if tag.startswith('autogenerated') and 'robot_id' not in comment:
2600
2177
  continue
2601
2178
  key = (comment['author']['email'], comment['updated'])
2602
2179
  if comment.get('side', 'REVISION') == 'PARENT':
@@ -2610,66 +2187,76 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2610
2187
  str(line) if line else ''))
2611
2188
  comments[key][path][patchset][line] = (url, comment['message'])
2612
2189
 
2613
- summary = []
2190
+ summaries = []
2614
2191
  for msg in messages:
2615
- # Don't bother showing autogenerated messages.
2616
- if msg.get('tag') and msg.get('tag').startswith('autogenerated'):
2617
- continue
2618
- # Gerrit spits out nanoseconds.
2619
- assert len(msg['date'].split('.')[-1]) == 9
2620
- date = datetime.datetime.strptime(msg['date'][:-3],
2621
- '%Y-%m-%d %H:%M:%S.%f')
2622
- message = msg['message']
2623
- key = (msg['author']['email'], msg['date'])
2624
- if key in comments:
2625
- message += '\n'
2626
- for path, patchsets in sorted(comments.get(key, {}).items()):
2627
- if readable:
2628
- message += '\n%s' % path
2629
- for patchset, lines in sorted(patchsets.items()):
2630
- for line, (url, content) in sorted(lines.items()):
2631
- if line:
2632
- line_str = 'Line %d' % line
2633
- path_str = '%s:%d:' % (path, line)
2634
- else:
2635
- line_str = 'File comment'
2636
- path_str = '%s:0:' % path
2637
- if readable:
2638
- message += '\n %s, %s: %s' % (patchset, line_str, url)
2639
- message += '\n %s\n' % content
2640
- else:
2641
- message += '\n%s ' % path_str
2642
- message += '\n%s\n' % content
2643
-
2644
- summary.append(_CommentSummary(
2645
- date=date,
2646
- message=message,
2647
- sender=msg['author']['email'],
2648
- # These could be inferred from the text messages and correlated with
2649
- # Code-Review label maximum, however this is not reliable.
2650
- # Leaving as is until the need arises.
2651
- approval=False,
2652
- disapproval=False,
2653
- ))
2654
- return summary
2192
+ summary = self._BuildCommentSummary(msg, comments, readable)
2193
+ if summary:
2194
+ summaries.append(summary)
2195
+ return summaries
2196
+
2197
+ @staticmethod
2198
+ def _BuildCommentSummary(msg, comments, readable):
2199
+ key = (msg['author']['email'], msg['date'])
2200
+ # Don't bother showing autogenerated messages that don't have associated
2201
+ # file or line comments. this will filter out most autogenerated
2202
+ # messages, but will keep robot comments like those from Tricium.
2203
+ is_autogenerated = msg.get('tag', '').startswith('autogenerated')
2204
+ if is_autogenerated and not comments.get(key):
2205
+ return None
2206
+ message = msg['message']
2207
+ # Gerrit spits out nanoseconds.
2208
+ assert len(msg['date'].split('.')[-1]) == 9
2209
+ date = datetime.datetime.strptime(msg['date'][:-3],
2210
+ '%Y-%m-%d %H:%M:%S.%f')
2211
+ if key in comments:
2212
+ message += '\n'
2213
+ for path, patchsets in sorted(comments.get(key, {}).items()):
2214
+ if readable:
2215
+ message += '\n%s' % path
2216
+ for patchset, lines in sorted(patchsets.items()):
2217
+ for line, (url, content) in sorted(lines.items()):
2218
+ if line:
2219
+ line_str = 'Line %d' % line
2220
+ path_str = '%s:%d:' % (path, line)
2221
+ else:
2222
+ line_str = 'File comment'
2223
+ path_str = '%s:0:' % path
2224
+ if readable:
2225
+ message += '\n %s, %s: %s' % (patchset, line_str, url)
2226
+ message += '\n %s\n' % content
2227
+ else:
2228
+ message += '\n%s ' % path_str
2229
+ message += '\n%s\n' % content
2230
+
2231
+ return _CommentSummary(
2232
+ date=date,
2233
+ message=message,
2234
+ sender=msg['author']['email'],
2235
+ autogenerated=is_autogenerated,
2236
+ # These could be inferred from the text messages and correlated with
2237
+ # Code-Review label maximum, however this is not reliable.
2238
+ # Leaving as is until the need arises.
2239
+ approval=False,
2240
+ disapproval=False,
2241
+ )
2655
2242
 
2656
2243
  def CloseIssue(self):
2657
- gerrit_util.AbandonChange(self._GetGerritHost(), self.GetIssue(), msg='')
2244
+ gerrit_util.AbandonChange(
2245
+ self._GetGerritHost(), self._GerritChangeIdentifier(), msg='')
2658
2246
 
2659
2247
  def SubmitIssue(self, wait_for_merge=True):
2660
- gerrit_util.SubmitChange(self._GetGerritHost(), self.GetIssue(),
2661
- wait_for_merge=wait_for_merge)
2248
+ gerrit_util.SubmitChange(
2249
+ self._GetGerritHost(), self._GerritChangeIdentifier(),
2250
+ wait_for_merge=wait_for_merge)
2662
2251
 
2663
- def _GetChangeDetail(self, options=None, issue=None,
2664
- no_cache=False):
2665
- """Returns details of the issue by querying Gerrit and caching results.
2252
+ def _GetChangeDetail(self, options=None, no_cache=False):
2253
+ """Returns details of associated Gerrit change and caching results.
2666
2254
 
2667
2255
  If fresh data is needed, set no_cache=True which will clear cache and
2668
2256
  thus new data will be fetched from Gerrit.
2669
2257
  """
2670
2258
  options = options or []
2671
- issue = issue or self.GetIssue()
2672
- assert issue, 'issue is required to query Gerrit'
2259
+ assert self.GetIssue(), 'issue is required to query Gerrit'
2673
2260
 
2674
2261
  # Optimization to avoid multiple RPCs:
2675
2262
  if (('CURRENT_REVISION' in options or 'ALL_REVISIONS' in options) and
@@ -2677,15 +2264,15 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2677
2264
  options.append('CURRENT_COMMIT')
2678
2265
 
2679
2266
  # Normalize issue and options for consistent keys in cache.
2680
- issue = str(issue)
2267
+ cache_key = str(self.GetIssue())
2681
2268
  options = [o.upper() for o in options]
2682
2269
 
2683
2270
  # Check in cache first unless no_cache is True.
2684
2271
  if no_cache:
2685
- self._detail_cache.pop(issue, None)
2272
+ self._detail_cache.pop(cache_key, None)
2686
2273
  else:
2687
2274
  options_set = frozenset(options)
2688
- for cached_options_set, data in self._detail_cache.get(issue, []):
2275
+ for cached_options_set, data in self._detail_cache.get(cache_key, []):
2689
2276
  # Assumption: data fetched before with extra options is suitable
2690
2277
  # for return for a smaller set of options.
2691
2278
  # For example, if we cached data for
@@ -2697,37 +2284,48 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2697
2284
 
2698
2285
  try:
2699
2286
  data = gerrit_util.GetChangeDetail(
2700
- self._GetGerritHost(), str(issue), options)
2287
+ self._GetGerritHost(), self._GerritChangeIdentifier(), options)
2701
2288
  except gerrit_util.GerritError as e:
2702
2289
  if e.http_status == 404:
2703
- raise GerritChangeNotExists(issue, self.GetCodereviewServer())
2290
+ raise GerritChangeNotExists(self.GetIssue(), self.GetCodereviewServer())
2704
2291
  raise
2705
2292
 
2706
- self._detail_cache.setdefault(issue, []).append((frozenset(options), data))
2293
+ self._detail_cache.setdefault(cache_key, []).append(
2294
+ (frozenset(options), data))
2707
2295
  return data
2708
2296
 
2709
- def _GetChangeCommit(self, issue=None):
2710
- issue = issue or self.GetIssue()
2711
- assert issue, 'issue is required to query Gerrit'
2297
+ def _GetChangeCommit(self):
2298
+ assert self.GetIssue(), 'issue must be set to query Gerrit'
2712
2299
  try:
2713
- data = gerrit_util.GetChangeCommit(self._GetGerritHost(), str(issue))
2300
+ data = gerrit_util.GetChangeCommit(
2301
+ self._GetGerritHost(), self._GerritChangeIdentifier())
2714
2302
  except gerrit_util.GerritError as e:
2715
2303
  if e.http_status == 404:
2716
- raise GerritChangeNotExists(issue, self.GetCodereviewServer())
2304
+ raise GerritChangeNotExists(self.GetIssue(), self.GetCodereviewServer())
2717
2305
  raise
2718
2306
  return data
2719
2307
 
2308
+ def _IsCqConfigured(self):
2309
+ detail = self._GetChangeDetail(['LABELS'])
2310
+ if not u'Commit-Queue' in detail.get('labels', {}):
2311
+ return False
2312
+ # TODO(crbug/753213): Remove temporary hack
2313
+ if ('https://chromium.googlesource.com/chromium/src' ==
2314
+ self._changelist.GetRemoteUrl() and
2315
+ detail['branch'].startswith('refs/branch-heads/')):
2316
+ return False
2317
+ return True
2318
+
2720
2319
  def CMDLand(self, force, bypass_hooks, verbose, parallel):
2721
2320
  if git_common.is_dirty_git_tree('land'):
2722
2321
  return 1
2322
+
2723
2323
  detail = self._GetChangeDetail(['CURRENT_REVISION', 'LABELS'])
2724
- if u'Commit-Queue' in detail.get('labels', {}):
2725
- if not force:
2726
- confirm_or_exit('\nIt seems this repository has a Commit Queue, '
2324
+ if not force and self._IsCqConfigured():
2325
+ confirm_or_exit('\nIt seems this repository has a Commit Queue, '
2727
2326
  'which can test and land changes for you. '
2728
2327
  'Are you sure you wish to bypass it?\n',
2729
2328
  action='bypass CQ')
2730
-
2731
2329
  differs = True
2732
2330
  last_upload = self._GitGetBranchConfigValue('gerritsquashhash')
2733
2331
  # Note: git diff outputs nothing if there is no diff.
@@ -2797,7 +2395,10 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2797
2395
  remote_url = self._changelist.GetRemoteUrl()
2798
2396
  if remote_url.endswith('.git'):
2799
2397
  remote_url = remote_url[:-len('.git')]
2398
+ remote_url = remote_url.rstrip('/')
2399
+
2800
2400
  fetch_info = revision_info['fetch']['http']
2401
+ fetch_info['url'] = fetch_info['url'].rstrip('/')
2801
2402
 
2802
2403
  if remote_url != fetch_info['url']:
2803
2404
  DieWithError('Trying to patch a change from %s but this repo appears '
@@ -2890,7 +2491,6 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2890
2491
 
2891
2492
  remote, remote_branch = self.GetRemoteBranch()
2892
2493
  branch = GetTargetRef(remote, remote_branch, options.target_branch)
2893
-
2894
2494
  # This may be None; default fallback value is determined in logic below.
2895
2495
  title = options.title
2896
2496
 
@@ -2961,7 +2561,7 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
2961
2561
  if options.message:
2962
2562
  message = options.message
2963
2563
  else:
2964
- message = CreateDescriptionFromLog(git_diff_args)
2564
+ message = _create_description_from_log(git_diff_args)
2965
2565
  if options.title:
2966
2566
  message = options.title + '\n\n' + message
2967
2567
  change_desc = ChangeDescription(message)
@@ -3001,7 +2601,7 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
3001
2601
  os.remove(desc_tempfile.name)
3002
2602
  else:
3003
2603
  change_desc = ChangeDescription(
3004
- options.message or CreateDescriptionFromLog(git_diff_args))
2604
+ options.message or _create_description_from_log(git_diff_args))
3005
2605
  if not change_desc.description:
3006
2606
  DieWithError("Description is empty. Aborting...")
3007
2607
 
@@ -3020,6 +2620,7 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
3020
2620
  change_id = git_footers.get_footer_change_id(change_desc.description)[0]
3021
2621
 
3022
2622
  assert change_desc
2623
+ SaveDescriptionBackup(change_desc)
3023
2624
  commits = RunGitSilent(['rev-list', '%s..%s' % (parent,
3024
2625
  ref_to_push)]).splitlines()
3025
2626
  if len(commits) > 1:
@@ -3034,6 +2635,27 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
3034
2635
  change_desc.update_reviewers(options.reviewers, options.tbrs,
3035
2636
  options.add_owners_to, change)
3036
2637
 
2638
+ reviewers = sorted(change_desc.get_reviewers())
2639
+ # Add cc's from the CC_LIST and --cc flag (if any).
2640
+ if not options.private and not options.no_autocc:
2641
+ cc = self.GetCCList().split(',')
2642
+ else:
2643
+ cc = []
2644
+ if options.cc:
2645
+ cc.extend(options.cc)
2646
+ cc = filter(None, [email.strip() for email in cc])
2647
+ if change_desc.get_cced():
2648
+ cc.extend(change_desc.get_cced())
2649
+ if self._GetGerritHost() == 'chromium-review.googlesource.com':
2650
+ valid_accounts = set(reviewers + cc)
2651
+ # TODO(crbug/877717): relax this for all hosts.
2652
+ else:
2653
+ valid_accounts = gerrit_util.ValidAccounts(
2654
+ self._GetGerritHost(), reviewers + cc)
2655
+ logging.info('accounts %s are recognized, %s invalid',
2656
+ sorted(valid_accounts),
2657
+ set(reviewers + cc).difference(set(valid_accounts)))
2658
+
3037
2659
  # Extra options that can be specified at push time. Doc:
3038
2660
  # https://gerrit-review.googlesource.com/Documentation/user-upload.html
3039
2661
  refspec_opts = []
@@ -3059,11 +2681,39 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
3059
2681
  if options.private:
3060
2682
  refspec_opts.append('private')
3061
2683
 
2684
+ for r in sorted(reviewers):
2685
+ if r in valid_accounts:
2686
+ refspec_opts.append('r=%s' % r)
2687
+ reviewers.remove(r)
2688
+ else:
2689
+ # TODO(tandrii): this should probably be a hard failure.
2690
+ print('WARNING: reviewer %s doesn\'t have a Gerrit account, skipping'
2691
+ % r)
2692
+ for c in sorted(cc):
2693
+ # refspec option will be rejected if cc doesn't correspond to an
2694
+ # account, even though REST call to add such arbitrary cc may succeed.
2695
+ if c in valid_accounts:
2696
+ refspec_opts.append('cc=%s' % c)
2697
+ cc.remove(c)
2698
+
3062
2699
  if options.topic:
3063
2700
  # Documentation on Gerrit topics is here:
3064
2701
  # https://gerrit-review.googlesource.com/Documentation/user-upload.html#topic
3065
2702
  refspec_opts.append('topic=%s' % options.topic)
3066
2703
 
2704
+ if options.enable_auto_submit:
2705
+ refspec_opts.append('l=Auto-Submit+1')
2706
+ if options.use_commit_queue:
2707
+ refspec_opts.append('l=Commit-Queue+2')
2708
+ elif options.cq_dry_run:
2709
+ refspec_opts.append('l=Commit-Queue+1')
2710
+
2711
+ if change_desc.get_reviewers(tbr_only=True):
2712
+ score = gerrit_util.GetCodeReviewTbrScore(
2713
+ self._GetGerritHost(),
2714
+ self._GetGerritProject())
2715
+ refspec_opts.append('l=Code-Review+%s' % score)
2716
+
3067
2717
  # Gerrit sorts hashtags, so order is not important.
3068
2718
  hashtags = {change_desc.sanitize_hash_tag(t) for t in options.hashtags}
3069
2719
  if not self.GetIssue():
@@ -3078,19 +2728,29 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
3078
2728
  refspec = '%s:refs/for/%s%s' % (ref_to_push, branch, refspec_suffix)
3079
2729
 
3080
2730
  try:
2731
+ push_returncode = 0
2732
+ before_push = time_time()
3081
2733
  push_stdout = gclient_utils.CheckCallAndFilter(
3082
2734
  ['git', 'push', self.GetRemoteUrl(), refspec],
3083
2735
  print_stdout=True,
3084
2736
  # Flush after every line: useful for seeing progress when running as
3085
2737
  # recipe.
3086
2738
  filter_fn=lambda _: sys.stdout.flush())
3087
- except subprocess2.CalledProcessError:
2739
+ except subprocess2.CalledProcessError as e:
2740
+ push_returncode = e.returncode
3088
2741
  DieWithError('Failed to create a change. Please examine output above '
3089
2742
  'for the reason of the failure.\n'
3090
2743
  'Hint: run command below to diagnose common Git/Gerrit '
3091
2744
  'credential problems:\n'
3092
2745
  ' git cl creds-check\n',
3093
2746
  change_desc)
2747
+ finally:
2748
+ metrics.collector.add_repeated('sub_commands', {
2749
+ 'command': 'git push',
2750
+ 'execution_time': time_time() - before_push,
2751
+ 'exit_code': push_returncode,
2752
+ 'arguments': metrics_utils.extract_known_subcommand_args(refspec_opts),
2753
+ })
3094
2754
 
3095
2755
  if options.squash:
3096
2756
  regex = re.compile(r'remote:\s+https?://[\w\-\.\+\/#]*/(\d+)\s.*')
@@ -3104,33 +2764,14 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
3104
2764
  self.SetIssue(change_numbers[0])
3105
2765
  self._GitSetBranchConfigValue('gerritsquashhash', ref_to_push)
3106
2766
 
3107
- reviewers = sorted(change_desc.get_reviewers())
3108
-
3109
- # Add cc's from the CC_LIST and --cc flag (if any).
3110
- if not options.private:
3111
- cc = self.GetCCList().split(',')
3112
- else:
3113
- cc = []
3114
- if options.cc:
3115
- cc.extend(options.cc)
3116
- cc = filter(None, [email.strip() for email in cc])
3117
- if change_desc.get_cced():
3118
- cc.extend(change_desc.get_cced())
3119
-
3120
- gerrit_util.AddReviewers(
3121
- self._GetGerritHost(), self.GetIssue(), reviewers, cc,
3122
- notify=bool(options.send_mail))
3123
-
3124
- if change_desc.get_reviewers(tbr_only=True):
3125
- labels = self._GetChangeDetail(['LABELS']).get('labels', {})
3126
- score = 1
3127
- if 'Code-Review' in labels and 'values' in labels['Code-Review']:
3128
- score = max([int(x) for x in labels['Code-Review']['values'].keys()])
3129
- print('Adding self-LGTM (Code-Review +%d) because of TBRs.' % score)
3130
- gerrit_util.SetReview(
3131
- self._GetGerritHost(), self.GetIssue(),
3132
- msg='Self-approving for TBR',
3133
- labels={'Code-Review': score})
2767
+ if self.GetIssue() and (reviewers or cc):
2768
+ # GetIssue() is not set in case of non-squash uploads according to tests.
2769
+ # TODO(agable): non-squash uploads in git cl should be removed.
2770
+ gerrit_util.AddReviewers(
2771
+ self._GetGerritHost(),
2772
+ self._GerritChangeIdentifier(),
2773
+ reviewers, cc,
2774
+ notify=bool(options.send_mail))
3134
2775
 
3135
2776
  return 0
3136
2777
 
@@ -3198,31 +2839,16 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
3198
2839
  """Re-commits using the current message, assumes the commit hook is in
3199
2840
  place.
3200
2841
  """
3201
- log_desc = options.message or CreateDescriptionFromLog(args)
2842
+ log_desc = options.message or _create_description_from_log(args)
3202
2843
  git_command = ['commit', '--amend', '-m', log_desc]
3203
2844
  RunGit(git_command)
3204
- new_log_desc = CreateDescriptionFromLog(args)
2845
+ new_log_desc = _create_description_from_log(args)
3205
2846
  if git_footers.get_footer_change_id(new_log_desc):
3206
2847
  print('git-cl: Added Change-Id to commit message.')
3207
2848
  return new_log_desc
3208
2849
  else:
3209
2850
  DieWithError('ERROR: Gerrit commit-msg hook not installed.')
3210
2851
 
3211
- def SetLabels(self, enable_auto_submit, use_commit_queue, cq_dry_run):
3212
- """Sets labels on the change based on the provided flags."""
3213
- labels = {}
3214
- notify = None;
3215
- if enable_auto_submit:
3216
- labels['Auto-Submit'] = 1
3217
- if use_commit_queue:
3218
- labels['Commit-Queue'] = 2
3219
- elif cq_dry_run:
3220
- labels['Commit-Queue'] = 1
3221
- notify = False
3222
- if labels:
3223
- gerrit_util.SetReview(self._GetGerritHost(), self.GetIssue(),
3224
- labels=labels, notify=notify)
3225
-
3226
2852
  def SetCQState(self, new_state):
3227
2853
  """Sets the Commit-Queue label assuming canonical CQ config for Gerrit."""
3228
2854
  vote_map = {
@@ -3232,8 +2858,9 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
3232
2858
  }
3233
2859
  labels = {'Commit-Queue': vote_map[new_state]}
3234
2860
  notify = False if new_state == _CQState.DRY_RUN else None
3235
- gerrit_util.SetReview(self._GetGerritHost(), self.GetIssue(),
3236
- labels=labels, notify=notify)
2861
+ gerrit_util.SetReview(
2862
+ self._GetGerritHost(), self._GerritChangeIdentifier(),
2863
+ labels=labels, notify=notify)
3237
2864
 
3238
2865
  def CannotTriggerTryJobReason(self):
3239
2866
  try:
@@ -3271,11 +2898,10 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
3271
2898
 
3272
2899
  def GetReviewers(self):
3273
2900
  details = self._GetChangeDetail(['DETAILED_ACCOUNTS'])
3274
- return [reviewer['email'] for reviewer in details['reviewers']['REVIEWER']]
2901
+ return [r['email'] for r in details['reviewers'].get('REVIEWER', [])]
3275
2902
 
3276
2903
 
3277
2904
  _CODEREVIEW_IMPLEMENTATIONS = {
3278
- 'rietveld': _RietveldChangelistImpl,
3279
2905
  'gerrit': _GerritChangelistImpl,
3280
2906
  }
3281
2907
 
@@ -3310,13 +2936,11 @@ def _add_codereview_select_options(parser):
3310
2936
 
3311
2937
 
3312
2938
  def _process_codereview_select_options(parser, options):
3313
- if options.gerrit and options.rietveld:
3314
- parser.error('Options --gerrit and --rietveld are mutually exclusive')
2939
+ if options.rietveld:
2940
+ parser.error('--rietveld is no longer supported')
3315
2941
  options.forced_codereview = None
3316
2942
  if options.gerrit:
3317
2943
  options.forced_codereview = 'gerrit'
3318
- elif options.rietveld:
3319
- options.forced_codereview = 'rietveld'
3320
2944
 
3321
2945
 
3322
2946
  def _get_bug_line_values(default_project, bugs):
@@ -3464,8 +3088,8 @@ class ChangeDescription(object):
3464
3088
  ] + self._description_lines)
3465
3089
 
3466
3090
  regexp = re.compile(self.BUG_LINE)
3091
+ prefix = settings.GetBugPrefix()
3467
3092
  if not any((regexp.match(line) for line in self._description_lines)):
3468
- prefix = settings.GetBugPrefix()
3469
3093
  values = list(_get_bug_line_values(prefix, bug or '')) or [prefix]
3470
3094
  if git_footer:
3471
3095
  self.append_footer('Bug: %s' % ', '.join(values))
@@ -3481,7 +3105,9 @@ class ChangeDescription(object):
3481
3105
 
3482
3106
  # Strip off comments and default inserted "Bug:" line.
3483
3107
  clean_lines = [line.rstrip() for line in lines if not
3484
- (line.startswith('#') or line.rstrip() == "Bug:")]
3108
+ (line.startswith('#') or
3109
+ line.rstrip() == "Bug:" or
3110
+ line.rstrip() == "Bug: " + prefix)]
3485
3111
  if not clean_lines:
3486
3112
  DieWithError('No CL description, aborting')
3487
3113
  self.set_description(clean_lines)
@@ -3675,13 +3301,11 @@ def LoadCodereviewSettingsFromFile(fileobj):
3675
3301
  # Only server setting is required. Other settings can be absent.
3676
3302
  # In that case, we ignore errors raised during option deletion attempt.
3677
3303
  SetProperty('cc', 'CC_LIST', unset_error_ok=True)
3678
- SetProperty('private', 'PRIVATE', unset_error_ok=True)
3679
3304
  SetProperty('tree-status-url', 'STATUS', unset_error_ok=True)
3680
3305
  SetProperty('viewvc-url', 'VIEW_VC', unset_error_ok=True)
3681
3306
  SetProperty('bug-prefix', 'BUG_PREFIX', unset_error_ok=True)
3682
3307
  SetProperty('cpplint-regex', 'LINT_REGEX', unset_error_ok=True)
3683
3308
  SetProperty('cpplint-ignore-regex', 'LINT_IGNORE_REGEX', unset_error_ok=True)
3684
- SetProperty('project', 'PROJECT', unset_error_ok=True)
3685
3309
  SetProperty('run-post-upload-hook', 'RUN_POST_UPLOAD_HOOK',
3686
3310
  unset_error_ok=True)
3687
3311
 
@@ -3753,43 +3377,6 @@ def DownloadGerritHook(force):
3753
3377
  'chmod +x .git/hooks/commit-msg' % src)
3754
3378
 
3755
3379
 
3756
- def GetRietveldCodereviewSettingsInteractively():
3757
- """Prompt the user for settings."""
3758
- server = settings.GetDefaultServerUrl(error_ok=True)
3759
- prompt = 'Rietveld server (host[:port])'
3760
- prompt += ' [%s]' % (server or DEFAULT_SERVER)
3761
- newserver = ask_for_data(prompt + ':')
3762
- if not server and not newserver:
3763
- newserver = DEFAULT_SERVER
3764
- if newserver:
3765
- newserver = gclient_utils.UpgradeToHttps(newserver)
3766
- if newserver != server:
3767
- RunGit(['config', 'rietveld.server', newserver])
3768
-
3769
- def SetProperty(initial, caption, name, is_url):
3770
- prompt = caption
3771
- if initial:
3772
- prompt += ' ("x" to clear) [%s]' % initial
3773
- new_val = ask_for_data(prompt + ':')
3774
- if new_val == 'x':
3775
- RunGit(['config', '--unset-all', 'rietveld.' + name], error_ok=True)
3776
- elif new_val:
3777
- if is_url:
3778
- new_val = gclient_utils.UpgradeToHttps(new_val)
3779
- if new_val != initial:
3780
- RunGit(['config', 'rietveld.' + name, new_val])
3781
-
3782
- SetProperty(settings.GetDefaultCCList(), 'CC list', 'cc', False)
3783
- SetProperty(settings.GetDefaultPrivateFlag(),
3784
- 'Private flag (rietveld only)', 'private', False)
3785
- SetProperty(settings.GetTreeStatusUrl(error_ok=True), 'Tree status URL',
3786
- 'tree-status-url', False)
3787
- SetProperty(settings.GetViewVCUrl(), 'ViewVC URL', 'viewvc-url', True)
3788
- SetProperty(settings.GetBugPrefix(), 'Bug Prefix', 'bug-prefix', False)
3789
- SetProperty(settings.GetRunPostUploadHook(), 'Run Post Upload Hook',
3790
- 'run-post-upload-hook', False)
3791
-
3792
-
3793
3380
  class _GitCookiesChecker(object):
3794
3381
  """Provides facilities for validating and suggesting fixes to .gitcookies."""
3795
3382
 
@@ -4068,14 +3655,22 @@ class _GitCookiesChecker(object):
4068
3655
  return found
4069
3656
 
4070
3657
 
3658
+ @metrics.collector.collect_metrics('git cl creds-check')
4071
3659
  def CMDcreds_check(parser, args):
4072
3660
  """Checks credentials and suggests changes."""
4073
3661
  _, _ = parser.parse_args(args)
4074
3662
 
4075
- if gerrit_util.GceAuthenticator.is_gce():
3663
+ # Code below checks .gitcookies. Abort if using something else.
3664
+ authn = gerrit_util.Authenticator.get()
3665
+ if not isinstance(authn, gerrit_util.CookiesAuthenticator):
3666
+ if isinstance(authn, gerrit_util.GceAuthenticator):
3667
+ DieWithError(
3668
+ 'This command is not designed for GCE, are you on a bot?\n'
3669
+ 'If you need to run this on GCE, export SKIP_GCE_AUTH_FOR_GIT=1 '
3670
+ 'in your env.')
4076
3671
  DieWithError(
4077
- 'This command is not designed for GCE, are you on a bot?\n'
4078
- 'If you need to run this, export SKIP_GCE_AUTH_FOR_GIT=1 in your env.')
3672
+ 'This command is not designed for bot environment. It checks '
3673
+ '~/.gitcookies file not generally used on bots.')
4079
3674
 
4080
3675
  checker = _GitCookiesChecker()
4081
3676
  checker.ensure_configured_gitcookies()
@@ -4089,42 +3684,7 @@ def CMDcreds_check(parser, args):
4089
3684
  return 1
4090
3685
 
4091
3686
 
4092
- @subcommand.usage('[repo root containing codereview.settings]')
4093
- def CMDconfig(parser, args):
4094
- """Edits configuration for this tree."""
4095
-
4096
- print('WARNING: git cl config works for Rietveld only.')
4097
- # TODO(tandrii): remove this once we switch to Gerrit.
4098
- # See bugs http://crbug.com/637561 and http://crbug.com/600469.
4099
- parser.add_option('--activate-update', action='store_true',
4100
- help='activate auto-updating [rietveld] section in '
4101
- '.git/config')
4102
- parser.add_option('--deactivate-update', action='store_true',
4103
- help='deactivate auto-updating [rietveld] section in '
4104
- '.git/config')
4105
- options, args = parser.parse_args(args)
4106
-
4107
- if options.deactivate_update:
4108
- RunGit(['config', 'rietveld.autoupdate', 'false'])
4109
- return
4110
-
4111
- if options.activate_update:
4112
- RunGit(['config', '--unset', 'rietveld.autoupdate'])
4113
- return
4114
-
4115
- if len(args) == 0:
4116
- GetRietveldCodereviewSettingsInteractively()
4117
- return 0
4118
-
4119
- url = args[0]
4120
- if not url.endswith('codereview.settings'):
4121
- url = os.path.join(url, 'codereview.settings')
4122
-
4123
- # Load code review settings and download hooks (if available).
4124
- LoadCodereviewSettingsFromFile(urllib2.urlopen(url))
4125
- return 0
4126
-
4127
-
3687
+ @metrics.collector.collect_metrics('git cl baseurl')
4128
3688
  def CMDbaseurl(parser, args):
4129
3689
  """Gets or sets base-url for this branch."""
4130
3690
  branchref = RunGit(['symbolic-ref', 'HEAD']).strip()
@@ -4139,7 +3699,6 @@ def CMDbaseurl(parser, args):
4139
3699
  return RunGit(['config', 'branch.%s.base-url' % branch, args[0]],
4140
3700
  error_ok=False).strip()
4141
3701
 
4142
-
4143
3702
  def color_for_status(status):
4144
3703
  """Maps a Changelist status to color, for CMDstatus and other tools."""
4145
3704
  return {
@@ -4166,9 +3725,6 @@ def get_cl_statuses(changes, fine_grained, max_processes=None):
4166
3725
 
4167
3726
  See GetStatus() for a list of possible statuses.
4168
3727
  """
4169
- # Silence upload.py otherwise it becomes unwieldy.
4170
- upload.verbosity = 0
4171
-
4172
3728
  if not changes:
4173
3729
  raise StopIteration()
4174
3730
 
@@ -4240,7 +3796,7 @@ def upload_branch_deps(cl, args):
4240
3796
  if root_branch is None:
4241
3797
  DieWithError('Can\'t find dependent branches from detached HEAD state. '
4242
3798
  'Get on a branch!')
4243
- if not cl.GetIssue() or (not cl.IsGerrit() and not cl.GetPatchset()):
3799
+ if not cl.GetIssue():
4244
3800
  DieWithError('Current branch does not have an uploaded CL. We cannot set '
4245
3801
  'patchset dependencies without an uploaded CL.')
4246
3802
 
@@ -4280,10 +3836,6 @@ def upload_branch_deps(cl, args):
4280
3836
  confirm_or_exit('This command will checkout all dependent branches and run '
4281
3837
  '"git cl upload".', action='continue')
4282
3838
 
4283
- # Add a default patchset title to all upload calls in Rietveld.
4284
- if not cl.IsGerrit():
4285
- args.extend(['-t', 'Updated patchset dependency'])
4286
-
4287
3839
  # Record all dependents that failed to upload.
4288
3840
  failures = {}
4289
3841
  # Go through all dependents, checkout the branch and upload.
@@ -4315,6 +3867,7 @@ def upload_branch_deps(cl, args):
4315
3867
  return 0
4316
3868
 
4317
3869
 
3870
+ @metrics.collector.collect_metrics('git cl archive')
4318
3871
  def CMDarchive(parser, args):
4319
3872
  """Archives and deletes branches associated with closed changelists."""
4320
3873
  parser.add_option(
@@ -4351,7 +3904,7 @@ def CMDarchive(parser, args):
4351
3904
  proposal = [(cl.GetBranch(),
4352
3905
  'git-cl-archived-%s-%s' % (cl.GetIssue(), cl.GetBranch()))
4353
3906
  for cl, status in statuses
4354
- if status == 'closed']
3907
+ if status in ('closed', 'rietveld-not-supported')]
4355
3908
  proposal.sort()
4356
3909
 
4357
3910
  if not proposal:
@@ -4396,6 +3949,7 @@ def CMDarchive(parser, args):
4396
3949
  return 0
4397
3950
 
4398
3951
 
3952
+ @metrics.collector.collect_metrics('git cl status')
4399
3953
  def CMDstatus(parser, args):
4400
3954
  """Show status of changelists.
4401
3955
 
@@ -4410,6 +3964,10 @@ def CMDstatus(parser, args):
4410
3964
 
4411
3965
  Also see 'git cl comments'.
4412
3966
  """
3967
+ parser.add_option(
3968
+ '--no-branch-color',
3969
+ action='store_true',
3970
+ help='Disable colorized branch names')
4413
3971
  parser.add_option('--field',
4414
3972
  help='print only specific field (desc|id|patch|status|url)')
4415
3973
  parser.add_option('-f', '--fast', action='store_true',
@@ -4464,8 +4022,26 @@ def CMDstatus(parser, args):
4464
4022
  fine_grained=not options.fast,
4465
4023
  max_processes=options.maxjobs)
4466
4024
 
4025
+ current_branch = GetCurrentBranch()
4026
+
4027
+ def FormatBranchName(branch, colorize=False):
4028
+ """Simulates 'git branch' behavior. Colorizes and prefixes branch name with
4029
+ an asterisk when it is the current branch."""
4030
+
4031
+ asterisk = ""
4032
+ color = Fore.RESET
4033
+ if branch == current_branch:
4034
+ asterisk = "* "
4035
+ color = Fore.GREEN
4036
+ branch_name = ShortBranchName(branch)
4037
+
4038
+ if colorize:
4039
+ return asterisk + color + branch_name + Fore.RESET
4040
+ return asterisk + branch_name
4041
+
4467
4042
  branch_statuses = {}
4468
- alignment = max(5, max(len(ShortBranchName(c.GetBranch())) for c in changes))
4043
+
4044
+ alignment = max(5, max(len(FormatBranchName(c.GetBranch())) for c in changes))
4469
4045
  for cl in sorted(changes, key=lambda c: c.GetBranch()):
4470
4046
  branch = cl.GetBranch()
4471
4047
  while branch not in branch_statuses:
@@ -4483,16 +4059,19 @@ def CMDstatus(parser, args):
4483
4059
  color = ''
4484
4060
  reset = ''
4485
4061
  status_str = '(%s)' % status if status else ''
4486
- print(' %*s : %s%s %s%s' % (
4487
- alignment, ShortBranchName(branch), color, url,
4488
- status_str, reset))
4489
4062
 
4063
+ branch_display = FormatBranchName(branch)
4064
+ padding = ' ' * (alignment - len(branch_display))
4065
+ if not options.no_branch_color:
4066
+ branch_display = FormatBranchName(branch, colorize=True)
4067
+
4068
+ print(' %s : %s%s %s%s' % (padding + branch_display, color, url,
4069
+ status_str, reset))
4490
4070
 
4491
- branch = GetCurrentBranch()
4492
4071
  print()
4493
- print('Current branch: %s' % branch)
4072
+ print('Current branch: %s' % current_branch)
4494
4073
  for cl in changes:
4495
- if cl.GetBranch() == branch:
4074
+ if cl.GetBranch() == current_branch:
4496
4075
  break
4497
4076
  if not cl.GetIssue():
4498
4077
  print('No issue assigned.')
@@ -4529,6 +4108,7 @@ def write_json(path, contents):
4529
4108
 
4530
4109
 
4531
4110
  @subcommand.usage('[issue_number]')
4111
+ @metrics.collector.collect_metrics('git cl issue')
4532
4112
  def CMDissue(parser, args):
4533
4113
  """Sets or displays the current code review issue number.
4534
4114
 
@@ -4549,18 +4129,32 @@ def CMDissue(parser, args):
4549
4129
  '--format=%(refname)']).splitlines()
4550
4130
  # Reverse issue lookup.
4551
4131
  issue_branch_map = {}
4132
+
4133
+ git_config = {}
4134
+ for config in RunGit(['config', '--get-regexp',
4135
+ r'branch\..*issue']).splitlines():
4136
+ name, _space, val = config.partition(' ')
4137
+ git_config[name] = val
4138
+
4552
4139
  for branch in branches:
4553
- cl = Changelist(branchref=branch)
4554
- issue_branch_map.setdefault(cl.GetIssue(), []).append(branch)
4140
+ for cls in _CODEREVIEW_IMPLEMENTATIONS.values():
4141
+ config_key = _git_branch_config_key(ShortBranchName(branch),
4142
+ cls.IssueConfigKey())
4143
+ issue = git_config.get(config_key)
4144
+ if issue:
4145
+ issue_branch_map.setdefault(int(issue), []).append(branch)
4555
4146
  if not args:
4556
4147
  args = sorted(issue_branch_map.iterkeys())
4557
4148
  result = {}
4558
4149
  for issue in args:
4559
- if not issue:
4150
+ try:
4151
+ issue_num = int(issue)
4152
+ except ValueError:
4153
+ print('ERROR cannot parse issue number: %s' % issue, file=sys.stderr)
4560
4154
  continue
4561
- result[int(issue)] = issue_branch_map.get(int(issue))
4155
+ result[issue_num] = issue_branch_map.get(issue_num)
4562
4156
  print('Branch for issue number %s: %s' % (
4563
- issue, ', '.join(issue_branch_map.get(int(issue)) or ('None',))))
4157
+ issue, ', '.join(issue_branch_map.get(issue_num) or ('None',))))
4564
4158
  if options.json:
4565
4159
  write_json(options.json, result)
4566
4160
  return 0
@@ -4584,10 +4178,13 @@ def CMDissue(parser, args):
4584
4178
  return 0
4585
4179
 
4586
4180
 
4181
+ @metrics.collector.collect_metrics('git cl comments')
4587
4182
  def CMDcomments(parser, args):
4588
4183
  """Shows or posts review comments for any changelist."""
4589
4184
  parser.add_option('-a', '--add-comment', dest='comment',
4590
4185
  help='comment to add to an issue')
4186
+ parser.add_option('-p', '--publish', action='store_true',
4187
+ help='marks CL as ready and sends comment to reviewers')
4591
4188
  parser.add_option('-i', '--issue', dest='issue',
4592
4189
  help='review issue id (defaults to current issue). '
4593
4190
  'If given, requires --rietveld or --gerrit')
@@ -4609,15 +4206,14 @@ def CMDcomments(parser, args):
4609
4206
  issue = int(options.issue)
4610
4207
  except ValueError:
4611
4208
  DieWithError('A review issue id is expected to be a number')
4612
- if not options.forced_codereview:
4613
- parser.error('--gerrit or --rietveld is required if --issue is specified')
4614
4209
 
4615
- cl = Changelist(issue=issue,
4616
- codereview=options.forced_codereview,
4617
- auth_config=auth_config)
4210
+ cl = Changelist(issue=issue, codereview='gerrit', auth_config=auth_config)
4211
+
4212
+ if not cl.IsGerrit():
4213
+ parser.error('rietveld is not supported')
4618
4214
 
4619
4215
  if options.comment:
4620
- cl.AddComment(options.comment)
4216
+ cl.AddComment(options.comment, options.publish)
4621
4217
  return 0
4622
4218
 
4623
4219
  summary = sorted(cl.GetCommentsSummary(readable=options.readable),
@@ -4629,6 +4225,8 @@ def CMDcomments(parser, args):
4629
4225
  color = Fore.GREEN
4630
4226
  elif comment.sender == cl.GetIssueOwner():
4631
4227
  color = Fore.MAGENTA
4228
+ elif comment.autogenerated:
4229
+ color = Fore.CYAN
4632
4230
  else:
4633
4231
  color = Fore.BLUE
4634
4232
  print('\n%s%s %s%s\n%s' % (
@@ -4643,12 +4241,12 @@ def CMDcomments(parser, args):
4643
4241
  dct = c.__dict__.copy()
4644
4242
  dct['date'] = dct['date'].strftime('%Y-%m-%d %H:%M:%S.%f')
4645
4243
  return dct
4646
- with open(options.json_file, 'wb') as f:
4647
- json.dump(map(pre_serialize, summary), f)
4244
+ write_json(options.json_file, map(pre_serialize, summary))
4648
4245
  return 0
4649
4246
 
4650
4247
 
4651
4248
  @subcommand.usage('[codereview url or issue id]')
4249
+ @metrics.collector.collect_metrics('git cl description')
4652
4250
  def CMDdescription(parser, args):
4653
4251
  """Brings up the editor for the current CL's description."""
4654
4252
  parser.add_option('-d', '--display', action='store_true',
@@ -4672,11 +4270,9 @@ def CMDdescription(parser, args):
4672
4270
  if not target_issue_arg.valid:
4673
4271
  parser.error('invalid codereview url or CL id')
4674
4272
 
4675
- auth_config = auth.extract_auth_config_from_options(options)
4676
-
4677
4273
  kwargs = {
4678
- 'auth_config': auth_config,
4679
- 'codereview': options.forced_codereview,
4274
+ 'auth_config': auth.extract_auth_config_from_options(options),
4275
+ 'codereview': options.forced_codereview,
4680
4276
  }
4681
4277
  detected_codereview_from_url = False
4682
4278
  if target_issue_arg:
@@ -4719,20 +4315,7 @@ def CMDdescription(parser, args):
4719
4315
  return 0
4720
4316
 
4721
4317
 
4722
- def CreateDescriptionFromLog(args):
4723
- """Pulls out the commit log to use as a base for the CL description."""
4724
- log_args = []
4725
- if len(args) == 1 and not args[0].endswith('.'):
4726
- log_args = [args[0] + '..']
4727
- elif len(args) == 1 and args[0].endswith('...'):
4728
- log_args = [args[0][:-1]]
4729
- elif len(args) == 2:
4730
- log_args = [args[0] + '..' + args[1]]
4731
- else:
4732
- log_args = args[:] # Hope for the best!
4733
- return RunGit(['log', '--pretty=format:%s\n\n%b'] + log_args)
4734
-
4735
-
4318
+ @metrics.collector.collect_metrics('git cl lint')
4736
4319
  def CMDlint(parser, args):
4737
4320
  """Runs cpplint on the current changelist."""
4738
4321
  parser.add_option('--filter', action='append', metavar='-x,+y',
@@ -4788,6 +4371,7 @@ def CMDlint(parser, args):
4788
4371
  return 0
4789
4372
 
4790
4373
 
4374
+ @metrics.collector.collect_metrics('git cl presubmit')
4791
4375
  def CMDpresubmit(parser, args):
4792
4376
  """Runs presubmit tests on the current changelist."""
4793
4377
  parser.add_option('-u', '--upload', action='store_true',
@@ -4864,7 +4448,7 @@ def GenerateGerritChangeId(message):
4864
4448
  # entropy.
4865
4449
  lines.append(message)
4866
4450
  change_hash = RunCommand(['git', 'hash-object', '-t', 'commit', '--stdin'],
4867
- stdin='\n'.join(lines))
4451
+ stdin=('\n'.join(lines)).encode())
4868
4452
  return 'I%s' % change_hash.strip()
4869
4453
 
4870
4454
 
@@ -4932,6 +4516,7 @@ def cleanup_list(l):
4932
4516
 
4933
4517
 
4934
4518
  @subcommand.usage('[flags]')
4519
+ @metrics.collector.collect_metrics('git cl upload')
4935
4520
  def CMDupload(parser, args):
4936
4521
  """Uploads the current changelist to codereview.
4937
4522
 
@@ -4982,11 +4567,6 @@ def CMDupload(parser, args):
4982
4567
  'can be applied multiple times'))
4983
4568
  parser.add_option('-s', '--send-mail', action='store_true',
4984
4569
  help='send email to reviewer(s) and cc(s) immediately')
4985
- parser.add_option('--emulate_svn_auto_props',
4986
- '--emulate-svn-auto-props',
4987
- action="store_true",
4988
- dest="emulate_svn_auto_props",
4989
- help="Emulate Subversion's auto properties feature.")
4990
4570
  parser.add_option('-c', '--use-commit-queue', action='store_true',
4991
4571
  help='tell the commit queue to commit this patchset; '
4992
4572
  'implies --send-mail')
@@ -5020,11 +4600,10 @@ def CMDupload(parser, args):
5020
4600
  help='Run all tests specified by input_api.RunTests in all '
5021
4601
  'PRESUBMIT files in parallel.')
5022
4602
 
5023
- # TODO: remove Rietveld flags
4603
+ parser.add_option('--no-autocc', action='store_true',
4604
+ help='Disables automatic addition of CC emails')
5024
4605
  parser.add_option('--private', action='store_true',
5025
- help='set the review private (rietveld only)')
5026
- parser.add_option('--email', default=None,
5027
- help='email address to use to connect to Rietveld')
4606
+ help='Set the review private. This implies --no-autocc.')
5028
4607
 
5029
4608
  orig_args = args
5030
4609
  auth.add_auth_options(parser)
@@ -5056,10 +4635,22 @@ def CMDupload(parser, args):
5056
4635
  settings.GetIsGerrit()
5057
4636
 
5058
4637
  cl = Changelist(auth_config=auth_config, codereview=options.forced_codereview)
4638
+ if not cl.IsGerrit():
4639
+ # Error out with instructions for repos not yet configured for Gerrit.
4640
+ print('=====================================')
4641
+ print('NOTICE: Rietveld is no longer supported. '
4642
+ 'You can upload changes to Gerrit with')
4643
+ print(' git cl upload --gerrit')
4644
+ print('or set Gerrit to be your default code review tool with')
4645
+ print(' git config gerrit.host true')
4646
+ print('=====================================')
4647
+ return 1
4648
+
5059
4649
  return cl.CMDUpload(options, args, orig_args)
5060
4650
 
5061
4651
 
5062
4652
  @subcommand.usage('--description=<description file>')
4653
+ @metrics.collector.collect_metrics('git cl split')
5063
4654
  def CMDsplit(parser, args):
5064
4655
  """Splits a branch into smaller branches and uploads CLs.
5065
4656
 
@@ -5077,6 +4668,18 @@ def CMDsplit(parser, args):
5077
4668
  default=False,
5078
4669
  help="List the files and reviewers for each CL that would "
5079
4670
  "be created, but don't create branches or CLs.")
4671
+ parser.add_option("--cq-dry-run", action='store_true',
4672
+ help="If set, will do a cq dry run for each uploaded CL. "
4673
+ "Please be careful when doing this; more than ~10 CLs "
4674
+ "has the potential to overload our build "
4675
+ "infrastructure. Try to upload these not during high "
4676
+ "load times (usually 11-3 Mountain View time). Email "
4677
+ "infra-dev@chromium.org with any questions.")
4678
+ parser.add_option('-a', '--enable-auto-submit', action='store_true',
4679
+ default=True,
4680
+ help='Sends your change to the CQ after an approval. Only '
4681
+ 'works on repos that have the Auto-Submit label '
4682
+ 'enabled')
5080
4683
  options, _ = parser.parse_args(args)
5081
4684
 
5082
4685
  if not options.description_file:
@@ -5086,10 +4689,12 @@ def CMDsplit(parser, args):
5086
4689
  return CMDupload(OptionParser(), args)
5087
4690
 
5088
4691
  return split_cl.SplitCl(options.description_file, options.comment_file,
5089
- Changelist, WrappedCMDupload, options.dry_run)
4692
+ Changelist, WrappedCMDupload, options.dry_run,
4693
+ options.cq_dry_run, options.enable_auto_submit)
5090
4694
 
5091
4695
 
5092
4696
  @subcommand.usage('DEPRECATED')
4697
+ @metrics.collector.collect_metrics('git cl commit')
5093
4698
  def CMDdcommit(parser, args):
5094
4699
  """DEPRECATED: Used to commit the current changelist via git-svn."""
5095
4700
  message = ('git-cl no longer supports committing to SVN repositories via '
@@ -5104,28 +4709,17 @@ CHERRY_PICK_BRANCH = 'git-cl-cherry-pick'
5104
4709
 
5105
4710
 
5106
4711
  @subcommand.usage('[upstream branch to apply against]')
4712
+ @metrics.collector.collect_metrics('git cl land')
5107
4713
  def CMDland(parser, args):
5108
4714
  """Commits the current changelist via git.
5109
4715
 
5110
4716
  In case of Gerrit, uses Gerrit REST api to "submit" the issue, which pushes
5111
4717
  upstream and closes the issue automatically and atomically.
5112
-
5113
- Otherwise (in case of Rietveld):
5114
- Squashes branch into a single commit.
5115
- Updates commit message with metadata (e.g. pointer to review).
5116
- Pushes the code upstream.
5117
- Updates review and closes.
5118
4718
  """
5119
4719
  parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks',
5120
4720
  help='bypass upload presubmit hook')
5121
- parser.add_option('-m', dest='message',
5122
- help="override review description")
5123
4721
  parser.add_option('-f', '--force', action='store_true', dest='force',
5124
4722
  help="force yes to questions (don't prompt)")
5125
- parser.add_option('-c', dest='contributor',
5126
- help="external contributor for patch (appended to " +
5127
- "description and used as author for git). Should be " +
5128
- "formatted as 'First Last <email@example.com>'")
5129
4723
  parser.add_option('--parallel', action='store_true',
5130
4724
  help='Run all tests specified by input_api.RunTests in all '
5131
4725
  'PRESUBMIT files in parallel.')
@@ -5138,19 +4732,6 @@ def CMDland(parser, args):
5138
4732
  if not cl.IsGerrit():
5139
4733
  parser.error('rietveld is not supported')
5140
4734
 
5141
- if options.message:
5142
- # This could be implemented, but it requires sending a new patch to
5143
- # Gerrit, as Gerrit unlike Rietveld versions messages with patchsets.
5144
- # Besides, Gerrit has the ability to change the commit message on submit
5145
- # automatically, thus there is no need to support this option (so far?).
5146
- parser.error('-m MESSAGE option is not supported for Gerrit.')
5147
- if options.contributor:
5148
- parser.error(
5149
- '-c CONTRIBUTOR option is not supported for Gerrit.\n'
5150
- 'Before uploading a commit to Gerrit, ensure it\'s author field is '
5151
- 'the contributor\'s "name <email>". If you can\'t upload such a '
5152
- 'commit for review, contact your repository admin and request'
5153
- '"Forge-Author" permission.')
5154
4735
  if not cl.GetIssue():
5155
4736
  DieWithError('You must upload the change first to Gerrit.\n'
5156
4737
  ' If you would rather have `git cl land` upload '
@@ -5159,80 +4740,8 @@ def CMDland(parser, args):
5159
4740
  options.verbose, options.parallel)
5160
4741
 
5161
4742
 
5162
- def PushToGitWithAutoRebase(remote, branch, original_description,
5163
- git_numberer_enabled, max_attempts=3):
5164
- """Pushes current HEAD commit on top of remote's branch.
5165
-
5166
- Attempts to fetch and autorebase on push failures.
5167
- Adds git number footers on the fly.
5168
-
5169
- Returns integer code from last command.
5170
- """
5171
- cherry = RunGit(['rev-parse', 'HEAD']).strip()
5172
- code = 0
5173
- attempts_left = max_attempts
5174
- while attempts_left:
5175
- attempts_left -= 1
5176
- print('Attempt %d of %d' % (max_attempts - attempts_left, max_attempts))
5177
-
5178
- # Fetch remote/branch into local cherry_pick_branch, overriding the latter.
5179
- # If fetch fails, retry.
5180
- print('Fetching %s/%s...' % (remote, branch))
5181
- code, out = RunGitWithCode(
5182
- ['retry', 'fetch', remote,
5183
- '+%s:refs/heads/%s' % (branch, CHERRY_PICK_BRANCH)])
5184
- if code:
5185
- print('Fetch failed with exit code %d.' % code)
5186
- print(out.strip())
5187
- continue
5188
-
5189
- print('Cherry-picking commit on top of latest %s' % branch)
5190
- RunGitWithCode(['checkout', 'refs/heads/%s' % CHERRY_PICK_BRANCH],
5191
- suppress_stderr=True)
5192
- parent_hash = RunGit(['rev-parse', 'HEAD']).strip()
5193
- code, out = RunGitWithCode(['cherry-pick', cherry])
5194
- if code:
5195
- print('Your patch doesn\'t apply cleanly to \'%s\' HEAD @ %s, '
5196
- 'the following files have merge conflicts:' %
5197
- (branch, parent_hash))
5198
- print(RunGit(['-c', 'core.quotePath=false', 'diff',
5199
- '--name-status', '--diff-filter=U']).strip())
5200
- print('Please rebase your patch and try again.')
5201
- RunGitWithCode(['cherry-pick', '--abort'])
5202
- break
5203
-
5204
- commit_desc = ChangeDescription(original_description)
5205
- if git_numberer_enabled:
5206
- logging.debug('Adding git number footers')
5207
- parent_msg = RunGit(['show', '-s', '--format=%B', parent_hash]).strip()
5208
- commit_desc.update_with_git_number_footers(parent_hash, parent_msg,
5209
- branch)
5210
- # Ensure timestamps are monotonically increasing.
5211
- timestamp = max(1 + _get_committer_timestamp(parent_hash),
5212
- _get_committer_timestamp('HEAD'))
5213
- _git_amend_head(commit_desc.description, timestamp)
5214
-
5215
- code, out = RunGitWithCode(
5216
- ['push', '--porcelain', remote, 'HEAD:%s' % branch])
5217
- print(out)
5218
- if code == 0:
5219
- break
5220
- if IsFatalPushFailure(out):
5221
- print('Fatal push error. Make sure your .netrc credentials and git '
5222
- 'user.email are correct and you have push access to the repo.\n'
5223
- 'Hint: run command below to diangose common Git/Gerrit credential '
5224
- 'problems:\n'
5225
- ' git cl creds-check\n')
5226
- break
5227
- return code
5228
-
5229
-
5230
- def IsFatalPushFailure(push_stdout):
5231
- """True if retrying push won't help."""
5232
- return '(prohibited by Gerrit)' in push_stdout
5233
-
5234
-
5235
4743
  @subcommand.usage('<patch url or issue id or issue url>')
4744
+ @metrics.collector.collect_metrics('git cl patch')
5236
4745
  def CMDpatch(parser, args):
5237
4746
  """Patches in a code review."""
5238
4747
  parser.add_option('-b', dest='newbranch',
@@ -5363,6 +4872,7 @@ def GetTreeStatusReason():
5363
4872
  return status['message']
5364
4873
 
5365
4874
 
4875
+ @metrics.collector.collect_metrics('git cl tree')
5366
4876
  def CMDtree(parser, args):
5367
4877
  """Shows the status of the tree."""
5368
4878
  _, args = parser.parse_args(args)
@@ -5379,6 +4889,7 @@ def CMDtree(parser, args):
5379
4889
  return 0
5380
4890
 
5381
4891
 
4892
+ @metrics.collector.collect_metrics('git cl try')
5382
4893
  def CMDtry(parser, args):
5383
4894
  """Triggers try jobs using either BuildBucket or CQ dry run."""
5384
4895
  group = optparse.OptionGroup(parser, 'Try job options')
@@ -5474,16 +4985,6 @@ def CMDtry(parser, args):
5474
4985
  return 1
5475
4986
 
5476
4987
  patchset = cl.GetMostRecentPatchset()
5477
- # TODO(tandrii): Checking local patchset against remote patchset is only
5478
- # supported for Rietveld. Extend it to Gerrit or remove it completely.
5479
- if not cl.IsGerrit() and patchset != cl.GetPatchset():
5480
- print('Warning: Codereview server has newer patchsets (%s) than most '
5481
- 'recent upload from local checkout (%s). Did a previous upload '
5482
- 'fail?\n'
5483
- 'By default, git cl try uses the latest patchset from '
5484
- 'codereview, continuing to use patchset %s.\n' %
5485
- (patchset, cl.GetPatchset(), patchset))
5486
-
5487
4988
  try:
5488
4989
  _trigger_try_jobs(auth_config, cl, buckets, options, patchset)
5489
4990
  except BuildbucketResponseException as ex:
@@ -5492,6 +4993,7 @@ def CMDtry(parser, args):
5492
4993
  return 0
5493
4994
 
5494
4995
 
4996
+ @metrics.collector.collect_metrics('git cl try-results')
5495
4997
  def CMDtry_results(parser, args):
5496
4998
  """Prints info about try jobs associated with current CL."""
5497
4999
  group = optparse.OptionGroup(parser, 'Try job results options')
@@ -5532,15 +5034,6 @@ def CMDtry_results(parser, args):
5532
5034
  'Either upload first, or pass --patchset explicitly' %
5533
5035
  cl.GetIssue())
5534
5036
 
5535
- # TODO(tandrii): Checking local patchset against remote patchset is only
5536
- # supported for Rietveld. Extend it to Gerrit or remove it completely.
5537
- if not cl.IsGerrit() and patchset != cl.GetPatchset():
5538
- print('Warning: Codereview server has newer patchsets (%s) than most '
5539
- 'recent upload from local checkout (%s). Did a previous upload '
5540
- 'fail?\n'
5541
- 'By default, git cl try-results uses the latest patchset from '
5542
- 'codereview, continuing to use patchset %s.\n' %
5543
- (patchset, cl.GetPatchset(), patchset))
5544
5037
  try:
5545
5038
  jobs = fetch_try_jobs(auth_config, cl, options.buildbucket_host, patchset)
5546
5039
  except BuildbucketResponseException as ex:
@@ -5554,6 +5047,7 @@ def CMDtry_results(parser, args):
5554
5047
 
5555
5048
 
5556
5049
  @subcommand.usage('[new upstream branch]')
5050
+ @metrics.collector.collect_metrics('git cl upstream')
5557
5051
  def CMDupstream(parser, args):
5558
5052
  """Prints or sets the name of the upstream branch, if any."""
5559
5053
  _, args = parser.parse_args(args)
@@ -5575,6 +5069,7 @@ def CMDupstream(parser, args):
5575
5069
  return 0
5576
5070
 
5577
5071
 
5072
+ @metrics.collector.collect_metrics('git cl web')
5578
5073
  def CMDweb(parser, args):
5579
5074
  """Opens the current CL in the web browser."""
5580
5075
  _, args = parser.parse_args(args)
@@ -5586,10 +5081,23 @@ def CMDweb(parser, args):
5586
5081
  print('ERROR No issue to open', file=sys.stderr)
5587
5082
  return 1
5588
5083
 
5589
- webbrowser.open(issue_url)
5084
+ # Redirect I/O before invoking browser to hide its output. For example, this
5085
+ # allows to hide "Created new window in existing browser session." message
5086
+ # from Chrome. Based on https://stackoverflow.com/a/2323563.
5087
+ saved_stdout = os.dup(1)
5088
+ saved_stderr = os.dup(2)
5089
+ os.close(1)
5090
+ os.close(2)
5091
+ os.open(os.devnull, os.O_RDWR)
5092
+ try:
5093
+ webbrowser.open(issue_url)
5094
+ finally:
5095
+ os.dup2(saved_stdout, 1)
5096
+ os.dup2(saved_stderr, 2)
5590
5097
  return 0
5591
5098
 
5592
5099
 
5100
+ @metrics.collector.collect_metrics('git cl set-commit')
5593
5101
  def CMDset_commit(parser, args):
5594
5102
  """Sets the commit bit to trigger the Commit Queue."""
5595
5103
  parser.add_option('-d', '--dry-run', action='store_true',
@@ -5620,6 +5128,7 @@ def CMDset_commit(parser, args):
5620
5128
  return 0
5621
5129
 
5622
5130
 
5131
+ @metrics.collector.collect_metrics('git cl set-close')
5623
5132
  def CMDset_close(parser, args):
5624
5133
  """Closes the issue."""
5625
5134
  _add_codereview_issue_select_options(parser)
@@ -5638,6 +5147,7 @@ def CMDset_close(parser, args):
5638
5147
  return 0
5639
5148
 
5640
5149
 
5150
+ @metrics.collector.collect_metrics('git cl diff')
5641
5151
  def CMDdiff(parser, args):
5642
5152
  """Shows differences between local tree and last upload."""
5643
5153
  parser.add_option(
@@ -5676,12 +5186,17 @@ def CMDdiff(parser, args):
5676
5186
  return 0
5677
5187
 
5678
5188
 
5189
+ @metrics.collector.collect_metrics('git cl owners')
5679
5190
  def CMDowners(parser, args):
5680
5191
  """Finds potential owners for reviewing."""
5681
5192
  parser.add_option(
5682
5193
  '--ignore-current',
5683
5194
  action='store_true',
5684
5195
  help='Ignore the CL\'s current reviewers and start from scratch.')
5196
+ parser.add_option(
5197
+ '--ignore-self',
5198
+ action='store_true',
5199
+ help='Do not consider CL\'s author as an owners.')
5685
5200
  parser.add_option(
5686
5201
  '--no-color',
5687
5202
  action='store_true',
@@ -5721,15 +5236,23 @@ def CMDowners(parser, args):
5721
5236
  [] if options.ignore_current else cl.GetReviewers(),
5722
5237
  fopen=file, os_path=os.path,
5723
5238
  disable_color=options.no_color,
5724
- override_files=change.OriginalOwnersFiles()).run()
5239
+ override_files=change.OriginalOwnersFiles(),
5240
+ ignore_author=options.ignore_self).run()
5725
5241
 
5726
5242
 
5727
- def BuildGitDiffCmd(diff_type, upstream_commit, args):
5243
+ def BuildGitDiffCmd(diff_type, upstream_commit, args, allow_prefix=False):
5728
5244
  """Generates a diff command."""
5729
5245
  # Generate diff for the current branch's changes.
5730
- diff_cmd = ['-c', 'core.quotePath=false', 'diff',
5731
- '--no-ext-diff', '--no-prefix', diff_type,
5732
- upstream_commit, '--']
5246
+ diff_cmd = ['-c', 'core.quotePath=false', 'diff', '--no-ext-diff']
5247
+
5248
+ if allow_prefix:
5249
+ # explicitly setting --src-prefix and --dst-prefix is necessary in the
5250
+ # case that diff.noprefix is set in the user's git config.
5251
+ diff_cmd += ['--src-prefix=a/', '--dst-prefix=b/']
5252
+ else:
5253
+ diff_cmd += ['--no-prefix']
5254
+
5255
+ diff_cmd += [diff_type, upstream_commit, '--']
5733
5256
 
5734
5257
  if args:
5735
5258
  for arg in args:
@@ -5747,6 +5270,7 @@ def MatchingFileType(file_name, extensions):
5747
5270
 
5748
5271
 
5749
5272
  @subcommand.usage('[files or directories to diff]')
5273
+ @metrics.collector.collect_metrics('git cl format')
5750
5274
  def CMDformat(parser, args):
5751
5275
  """Runs auto-formatting tools (clang-format etc.) on the diff."""
5752
5276
  CLANG_EXTS = ['.cc', '.cpp', '.h', '.m', '.mm', '.proto', '.java']
@@ -5755,8 +5279,20 @@ def CMDformat(parser, args):
5755
5279
  help='Reformat the full content of all touched files')
5756
5280
  parser.add_option('--dry-run', action='store_true',
5757
5281
  help='Don\'t modify any file on disk.')
5758
- parser.add_option('--python', action='store_true',
5759
- help='Format python code with yapf (experimental).')
5282
+ parser.add_option(
5283
+ '--python',
5284
+ action='store_true',
5285
+ default=None,
5286
+ help='Enables python formatting on all python files.')
5287
+ parser.add_option(
5288
+ '--no-python',
5289
+ action='store_true',
5290
+ dest='python',
5291
+ help='Disables python formatting on all python files. '
5292
+ 'Takes precedence over --python. '
5293
+ 'If neither --python or --no-python are set, python '
5294
+ 'files that have a .style.yapf file in an ancestor '
5295
+ 'directory will be formatted.')
5760
5296
  parser.add_option('--js', action='store_true',
5761
5297
  help='Format javascript code with clang-format.')
5762
5298
  parser.add_option('--diff', action='store_true',
@@ -5797,7 +5333,7 @@ def CMDformat(parser, args):
5797
5333
  diff_files = [x for x in diff_files if os.path.isfile(x)]
5798
5334
 
5799
5335
  if opts.js:
5800
- CLANG_EXTS.append('.js')
5336
+ CLANG_EXTS.extend(['.js', '.ts'])
5801
5337
 
5802
5338
  clang_diff_files = [x for x in diff_files if MatchingFileType(x, CLANG_EXTS)]
5803
5339
  python_diff_files = [x for x in diff_files if MatchingFileType(x, ['.py'])]
@@ -5849,26 +5385,73 @@ def CMDformat(parser, args):
5849
5385
 
5850
5386
  # Similar code to above, but using yapf on .py files rather than clang-format
5851
5387
  # on C/C++ files
5852
- if opts.python:
5853
- yapf_tool = gclient_utils.FindExecutable('yapf')
5854
- if yapf_tool is None:
5855
- DieWithError('yapf not found in PATH')
5856
-
5857
- if opts.full:
5858
- if python_diff_files:
5859
- if opts.dry_run or opts.diff:
5860
- cmd = [yapf_tool, '--diff'] + python_diff_files
5861
- stdout = RunCommand(cmd, error_ok=True, cwd=top_dir)
5862
- if opts.diff:
5863
- sys.stdout.write(stdout)
5864
- elif len(stdout) > 0:
5865
- return_value = 2
5866
- else:
5867
- RunCommand([yapf_tool, '-i'] + python_diff_files, cwd=top_dir)
5388
+ py_explicitly_disabled = opts.python is not None and not opts.python
5389
+ if python_diff_files and not py_explicitly_disabled:
5390
+ depot_tools_path = os.path.dirname(os.path.abspath(__file__))
5391
+ yapf_tool = os.path.join(depot_tools_path, 'yapf')
5392
+ if sys.platform.startswith('win'):
5393
+ yapf_tool += '.bat'
5394
+
5395
+ # If we couldn't find a yapf file we'll default to the chromium style
5396
+ # specified in depot_tools.
5397
+ chromium_default_yapf_style = os.path.join(depot_tools_path,
5398
+ YAPF_CONFIG_FILENAME)
5399
+ # Used for caching.
5400
+ yapf_configs = {}
5401
+ for f in python_diff_files:
5402
+ # Find the yapf style config for the current file, defaults to depot
5403
+ # tools default.
5404
+ _FindYapfConfigFile(f, yapf_configs, top_dir)
5405
+
5406
+ # Turn on python formatting by default if a yapf config is specified.
5407
+ # This breaks in the case of this repo though since the specified
5408
+ # style file is also the global default.
5409
+ if opts.python is None:
5410
+ filtered_py_files = []
5411
+ for f in python_diff_files:
5412
+ if _FindYapfConfigFile(f, yapf_configs, top_dir) is not None:
5413
+ filtered_py_files.append(f)
5868
5414
  else:
5869
- # TODO(sbc): yapf --lines mode still has some issues.
5870
- # https://github.com/google/yapf/issues/154
5871
- DieWithError('--python currently only works with --full')
5415
+ filtered_py_files = python_diff_files
5416
+
5417
+ # Note: yapf still seems to fix indentation of the entire file
5418
+ # even if line ranges are specified.
5419
+ # See https://github.com/google/yapf/issues/499
5420
+ if not opts.full and filtered_py_files:
5421
+ py_line_diffs = _ComputeDiffLineRanges(filtered_py_files, upstream_commit)
5422
+
5423
+ for f in filtered_py_files:
5424
+ yapf_config = _FindYapfConfigFile(f, yapf_configs, top_dir)
5425
+ if yapf_config is None:
5426
+ yapf_config = chromium_default_yapf_style
5427
+
5428
+ cmd = [yapf_tool, '--style', yapf_config, f]
5429
+
5430
+ has_formattable_lines = False
5431
+ if not opts.full:
5432
+ # Only run yapf over changed line ranges.
5433
+ for diff_start, diff_len in py_line_diffs[f]:
5434
+ diff_end = diff_start + diff_len - 1
5435
+ # Yapf errors out if diff_end < diff_start but this
5436
+ # is a valid line range diff for a removal.
5437
+ if diff_end >= diff_start:
5438
+ has_formattable_lines = True
5439
+ cmd += ['-l', '{}-{}'.format(diff_start, diff_end)]
5440
+ # If all line diffs were removals we have nothing to format.
5441
+ if not has_formattable_lines:
5442
+ continue
5443
+
5444
+ if opts.diff or opts.dry_run:
5445
+ cmd += ['--diff']
5446
+ # Will return non-zero exit code if non-empty diff.
5447
+ stdout = RunCommand(cmd, error_ok=True, cwd=top_dir)
5448
+ if opts.diff:
5449
+ sys.stdout.write(stdout)
5450
+ elif len(stdout) > 0:
5451
+ return_value = 2
5452
+ else:
5453
+ cmd += ['-i']
5454
+ RunCommand(cmd, cwd=top_dir)
5872
5455
 
5873
5456
  # Dart's formatter does not have the nice property of only operating on
5874
5457
  # modified chunks, so hard code full.
@@ -5939,6 +5522,7 @@ def GetDirtyMetricsDirs(diff_files):
5939
5522
 
5940
5523
 
5941
5524
  @subcommand.usage('<codereview url or issue id>')
5525
+ @metrics.collector.collect_metrics('git cl checkout')
5942
5526
  def CMDcheckout(parser, args):
5943
5527
  """Checks out a branch associated with a given Rietveld or Gerrit issue."""
5944
5528
  _, args = parser.parse_args(args)
@@ -6002,13 +5586,42 @@ class OptionParser(optparse.OptionParser):
6002
5586
  '-v', '--verbose', action='count', default=0,
6003
5587
  help='Use 2 times for more debugging info')
6004
5588
 
6005
- def parse_args(self, args=None, values=None):
6006
- options, args = optparse.OptionParser.parse_args(self, args, values)
5589
+ def parse_args(self, args=None, _values=None):
5590
+ try:
5591
+ return self._parse_args(args)
5592
+ finally:
5593
+ # Regardless of success or failure of args parsing, we want to report
5594
+ # metrics, but only after logging has been initialized (if parsing
5595
+ # succeeded).
5596
+ global settings
5597
+ settings = Settings()
5598
+
5599
+ if not metrics.DISABLE_METRICS_COLLECTION:
5600
+ # GetViewVCUrl ultimately calls logging method.
5601
+ project_url = settings.GetViewVCUrl().strip('/+')
5602
+ if project_url in metrics_utils.KNOWN_PROJECT_URLS:
5603
+ metrics.collector.add('project_urls', [project_url])
5604
+
5605
+ def _parse_args(self, args=None):
5606
+ # Create an optparse.Values object that will store only the actual passed
5607
+ # options, without the defaults.
5608
+ actual_options = optparse.Values()
5609
+ _, args = optparse.OptionParser.parse_args(self, args, actual_options)
5610
+ # Create an optparse.Values object with the default options.
5611
+ options = optparse.Values(self.get_default_values().__dict__)
5612
+ # Update it with the options passed by the user.
5613
+ options._update_careful(actual_options.__dict__)
5614
+ # Store the options passed by the user in an _actual_options attribute.
5615
+ # We store only the keys, and not the values, since the values can contain
5616
+ # arbitrary information, which might be PII.
5617
+ metrics.collector.add('arguments', actual_options.__dict__.keys())
5618
+
6007
5619
  levels = [logging.WARNING, logging.INFO, logging.DEBUG]
6008
5620
  logging.basicConfig(
6009
5621
  level=levels[min(options.verbose, len(levels) - 1)],
6010
5622
  format='[%(levelname).1s%(asctime)s %(process)d %(thread)d '
6011
5623
  '%(filename)s] %(message)s')
5624
+
6012
5625
  return options, args
6013
5626
 
6014
5627
 
@@ -6018,10 +5631,6 @@ def main(argv):
6018
5631
  (sys.version.split(' ', 1)[0],), file=sys.stderr)
6019
5632
  return 2
6020
5633
 
6021
- # Reload settings.
6022
- global settings
6023
- settings = Settings()
6024
-
6025
5634
  colorize_CMDstatus_doc()
6026
5635
  dispatcher = subcommand.CommandDispatcher(__name__)
6027
5636
  try:
@@ -6042,8 +5651,5 @@ if __name__ == '__main__':
6042
5651
  # unit testing.
6043
5652
  fix_encoding.fix_encoding()
6044
5653
  setup_color.init()
6045
- try:
5654
+ with metrics.collector.print_notice_and_exit():
6046
5655
  sys.exit(main(sys.argv[1:]))
6047
- except KeyboardInterrupt:
6048
- sys.stderr.write('interrupted\n')
6049
- sys.exit(1)