libv8 7.8.279.23.0beta1 → 8.4.255.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (719) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +11 -14
  4. data/CHANGELOG.md +7 -1
  5. data/README.md +10 -11
  6. data/Rakefile +30 -36
  7. data/ext/libv8/builder.rb +2 -1
  8. data/lib/libv8/version.rb +1 -1
  9. data/libv8.gemspec +1 -3
  10. data/vendor/depot_tools/{cipd.ps1 → .cipd_impl.ps1} +0 -0
  11. data/vendor/depot_tools/.gitattributes +1 -1
  12. data/vendor/depot_tools/.gitignore +3 -1
  13. data/vendor/depot_tools/.style.yapf +3 -1
  14. data/vendor/depot_tools/.vpython +55 -0
  15. data/vendor/depot_tools/.vpython3 +23 -0
  16. data/vendor/depot_tools/CROS_OWNERS +3 -1
  17. data/vendor/depot_tools/GOMA_OWNERS +9 -0
  18. data/vendor/depot_tools/LUCI_OWNERS +5 -0
  19. data/vendor/depot_tools/OWNERS +21 -6
  20. data/vendor/depot_tools/PRESUBMIT.py +35 -18
  21. data/vendor/depot_tools/README.git-cl.md +0 -14
  22. data/vendor/depot_tools/README.md +1 -1
  23. data/vendor/depot_tools/WATCHLISTS +2 -2
  24. data/vendor/depot_tools/auth.py +60 -772
  25. data/vendor/depot_tools/autoninja +12 -6
  26. data/vendor/depot_tools/autoninja.bat +6 -6
  27. data/vendor/depot_tools/autoninja.py +64 -27
  28. data/vendor/depot_tools/bb +1 -1
  29. data/vendor/depot_tools/bootstrap/{win/README.md → README.md} +28 -14
  30. data/vendor/depot_tools/bootstrap/{win/win_tools.py → bootstrap.py} +60 -43
  31. data/vendor/depot_tools/bootstrap/{win/git-bash.template.sh → git-bash.template.sh} +1 -1
  32. data/vendor/depot_tools/bootstrap/{win/git.template.bat → git.template.bat} +0 -0
  33. data/vendor/depot_tools/bootstrap/manifest.txt +27 -0
  34. data/vendor/depot_tools/bootstrap/manifest_bleeding_edge.txt +27 -0
  35. data/vendor/depot_tools/bootstrap/{win/profile.d.python.sh → profile.d.python.sh} +0 -0
  36. data/vendor/depot_tools/bootstrap/{win/python27.bleeding_edge.bat → python27.bat} +0 -0
  37. data/vendor/depot_tools/bootstrap/{win/python27.new.bat → python3.bat} +3 -3
  38. data/vendor/depot_tools/bootstrap/{win/win_tools.bat → win_tools.bat} +21 -16
  39. data/vendor/depot_tools/bootstrap_python3 +35 -0
  40. data/vendor/depot_tools/cbuildbot +1 -1
  41. data/vendor/depot_tools/chrome_set_ver +1 -1
  42. data/vendor/depot_tools/cipd +43 -39
  43. data/vendor/depot_tools/cipd.bat +2 -2
  44. data/vendor/depot_tools/cipd_client_version +1 -1
  45. data/vendor/depot_tools/cipd_client_version.digests +15 -14
  46. data/vendor/depot_tools/cipd_manifest.txt +31 -8
  47. data/vendor/depot_tools/cipd_manifest.versions +278 -158
  48. data/vendor/depot_tools/cit.py +9 -7
  49. data/vendor/depot_tools/clang_format.py +4 -1
  50. data/vendor/depot_tools/clang_format_merge_driver.py +10 -8
  51. data/vendor/depot_tools/compile_single_file.py +7 -2
  52. data/vendor/depot_tools/cpplint.py +51 -45
  53. data/vendor/depot_tools/cros +87 -0
  54. data/vendor/depot_tools/cros_sdk +1 -1
  55. data/vendor/depot_tools/crosjobs +13 -0
  56. data/vendor/depot_tools/detect_host_arch.py +0 -1
  57. data/vendor/depot_tools/dirmd +12 -0
  58. data/vendor/depot_tools/dirmd.bat +7 -0
  59. data/vendor/depot_tools/download_from_google_storage.py +47 -27
  60. data/vendor/depot_tools/ensure_bootstrap +14 -0
  61. data/vendor/depot_tools/fetch +14 -1
  62. data/vendor/depot_tools/fetch.bat +14 -1
  63. data/vendor/depot_tools/fetch.py +5 -7
  64. data/vendor/depot_tools/fetch_configs/chromium.py +6 -4
  65. data/vendor/depot_tools/fetch_configs/devtools-frontend.py +44 -0
  66. data/vendor/depot_tools/fetch_configs/ios_internal.py +10 -19
  67. data/vendor/depot_tools/fix_encoding.py +19 -5
  68. data/vendor/depot_tools/gclient +28 -12
  69. data/vendor/depot_tools/gclient-new-workdir.py +2 -0
  70. data/vendor/depot_tools/gclient.bat +18 -1
  71. data/vendor/depot_tools/gclient.py +156 -118
  72. data/vendor/depot_tools/gclient_eval.py +119 -107
  73. data/vendor/depot_tools/gclient_paths.py +67 -57
  74. data/vendor/depot_tools/gclient_scm.py +180 -169
  75. data/vendor/depot_tools/gclient_utils.py +177 -124
  76. data/vendor/depot_tools/gerrit_client.py +21 -13
  77. data/vendor/depot_tools/gerrit_util.py +188 -228
  78. data/vendor/depot_tools/git-cache +1 -1
  79. data/vendor/depot_tools/git-cl +1 -1
  80. data/vendor/depot_tools/git-drover +1 -1
  81. data/vendor/depot_tools/git-find-releases +1 -1
  82. data/vendor/depot_tools/git-footers +1 -1
  83. data/vendor/depot_tools/git-freeze +1 -1
  84. data/vendor/depot_tools/git-hyper-blame +1 -1
  85. data/vendor/depot_tools/git-map +1 -1
  86. data/vendor/depot_tools/git-map-branches +1 -1
  87. data/vendor/depot_tools/git-mark-merge-base +1 -1
  88. data/vendor/depot_tools/git-nav-downstream +1 -1
  89. data/vendor/depot_tools/git-new-branch +1 -1
  90. data/vendor/depot_tools/git-number +1 -1
  91. data/vendor/depot_tools/git-rebase-update +1 -1
  92. data/vendor/depot_tools/git-rename-branch +1 -1
  93. data/vendor/depot_tools/git-reparent-branch +1 -1
  94. data/vendor/depot_tools/git-retry +1 -1
  95. data/vendor/depot_tools/git-squash-branch +1 -1
  96. data/vendor/depot_tools/git-thaw +1 -1
  97. data/vendor/depot_tools/git-upstream-diff +1 -1
  98. data/vendor/depot_tools/git_cache.py +195 -281
  99. data/vendor/depot_tools/git_cl.py +1506 -2075
  100. data/vendor/depot_tools/git_cl_completion.sh +16 -2
  101. data/vendor/depot_tools/git_common.py +77 -32
  102. data/vendor/depot_tools/git_drover.py +17 -8
  103. data/vendor/depot_tools/git_find_releases.py +11 -9
  104. data/vendor/depot_tools/git_footers.py +13 -9
  105. data/vendor/depot_tools/git_freezer.py +3 -1
  106. data/vendor/depot_tools/git_hyper_blame.py +25 -32
  107. data/vendor/depot_tools/git_map.py +115 -93
  108. data/vendor/depot_tools/git_map_branches.py +11 -10
  109. data/vendor/depot_tools/git_mark_merge_base.py +8 -6
  110. data/vendor/depot_tools/git_nav_downstream.py +9 -6
  111. data/vendor/depot_tools/git_new_branch.py +39 -33
  112. data/vendor/depot_tools/git_number.py +28 -17
  113. data/vendor/depot_tools/git_rebase_update.py +50 -49
  114. data/vendor/depot_tools/git_rename_branch.py +2 -2
  115. data/vendor/depot_tools/git_reparent_branch.py +8 -6
  116. data/vendor/depot_tools/git_upstream_diff.py +4 -2
  117. data/vendor/depot_tools/gn.py +6 -4
  118. data/vendor/depot_tools/goma_auth +12 -0
  119. data/vendor/depot_tools/goma_auth.bat +8 -0
  120. data/vendor/depot_tools/goma_ctl +12 -0
  121. data/vendor/depot_tools/goma_ctl.bat +8 -0
  122. data/vendor/depot_tools/gsutil.py +5 -2
  123. data/vendor/depot_tools/gsutil.py.bat +8 -0
  124. data/vendor/depot_tools/gsutil.vpython +3 -1
  125. data/vendor/depot_tools/infra/config/OWNERS +2 -2
  126. data/vendor/depot_tools/infra/config/recipes.cfg +3 -2
  127. data/vendor/depot_tools/led +1 -1
  128. data/vendor/depot_tools/lockfile.py +116 -0
  129. data/vendor/depot_tools/luci-auth +1 -1
  130. data/vendor/depot_tools/lucicfg +1 -1
  131. data/vendor/depot_tools/mac_toolchain +1 -1
  132. data/vendor/depot_tools/man/html/depot_tools.html +1 -1
  133. data/vendor/depot_tools/man/html/depot_tools_tutorial.html +31 -25
  134. data/vendor/depot_tools/man/html/git-cl.html +1 -1
  135. data/vendor/depot_tools/man/html/git-drover.html +18 -18
  136. data/vendor/depot_tools/man/html/git-footers.html +1 -1
  137. data/vendor/depot_tools/man/html/git-freeze.html +3 -3
  138. data/vendor/depot_tools/man/html/git-hyper-blame.html +1 -1
  139. data/vendor/depot_tools/man/html/git-map-branches.html +2 -2
  140. data/vendor/depot_tools/man/html/git-map.html +1 -1
  141. data/vendor/depot_tools/man/html/git-mark-merge-base.html +1 -1
  142. data/vendor/depot_tools/man/html/git-nav-downstream.html +3 -3
  143. data/vendor/depot_tools/man/html/git-nav-upstream.html +12 -6
  144. data/vendor/depot_tools/man/html/git-new-branch.html +1 -1
  145. data/vendor/depot_tools/man/html/git-rebase-update.html +20 -1
  146. data/vendor/depot_tools/man/html/git-rename-branch.html +1 -1
  147. data/vendor/depot_tools/man/html/git-reparent-branch.html +1 -1
  148. data/vendor/depot_tools/man/html/git-retry.html +1 -1
  149. data/vendor/depot_tools/man/html/git-squash-branch.html +1 -1
  150. data/vendor/depot_tools/man/html/git-thaw.html +1 -1
  151. data/vendor/depot_tools/man/html/git-upstream-diff.html +10 -6
  152. data/vendor/depot_tools/man/man1/git-cl.1 +4 -4
  153. data/vendor/depot_tools/man/man1/git-drover.1 +21 -21
  154. data/vendor/depot_tools/man/man1/git-footers.1 +4 -4
  155. data/vendor/depot_tools/man/man1/git-freeze.1 +6 -6
  156. data/vendor/depot_tools/man/man1/git-hyper-blame.1 +4 -4
  157. data/vendor/depot_tools/man/man1/git-map-branches.1 +4 -4
  158. data/vendor/depot_tools/man/man1/git-map.1 +4 -4
  159. data/vendor/depot_tools/man/man1/git-mark-merge-base.1 +4 -4
  160. data/vendor/depot_tools/man/man1/git-nav-downstream.1 +6 -6
  161. data/vendor/depot_tools/man/man1/git-nav-upstream.1 +15 -9
  162. data/vendor/depot_tools/man/man1/git-new-branch.1 +3 -3
  163. data/vendor/depot_tools/man/man1/git-rebase-update.1 +14 -4
  164. data/vendor/depot_tools/man/man1/git-rename-branch.1 +4 -4
  165. data/vendor/depot_tools/man/man1/git-reparent-branch.1 +4 -4
  166. data/vendor/depot_tools/man/man1/git-retry.1 +4 -4
  167. data/vendor/depot_tools/man/man1/git-squash-branch.1 +4 -4
  168. data/vendor/depot_tools/man/man1/git-thaw.1 +4 -4
  169. data/vendor/depot_tools/man/man1/git-upstream-diff.1 +7 -13
  170. data/vendor/depot_tools/man/man7/depot_tools.7 +4 -4
  171. data/vendor/depot_tools/man/man7/depot_tools_tutorial.7 +31 -25
  172. data/vendor/depot_tools/man/src/common_demo_functions.sh +2 -2
  173. data/vendor/depot_tools/man/src/depot_tools_tutorial.txt +3 -3
  174. data/vendor/depot_tools/man/src/filter_demo_output.py +3 -1
  175. data/vendor/depot_tools/man/src/git-new-branch.txt +2 -1
  176. data/vendor/depot_tools/man/src/git-rebase-update.txt +8 -1
  177. data/vendor/depot_tools/metrics.README.md +5 -2
  178. data/vendor/depot_tools/metrics.py +4 -4
  179. data/vendor/depot_tools/metrics_utils.py +3 -3
  180. data/vendor/depot_tools/my_activity.py +85 -251
  181. data/vendor/depot_tools/ninja +1 -0
  182. data/vendor/depot_tools/ninja-mac +0 -0
  183. data/vendor/depot_tools/ninjalog_uploader.py +146 -145
  184. data/vendor/depot_tools/ninjalog_uploader_wrapper.py +69 -60
  185. data/vendor/depot_tools/owners.py +57 -21
  186. data/vendor/depot_tools/owners_finder.py +28 -14
  187. data/vendor/depot_tools/post_build_ninja_summary.py +76 -48
  188. data/vendor/depot_tools/presubmit_canned_checks.py +293 -106
  189. data/vendor/depot_tools/presubmit_support.py +527 -333
  190. data/vendor/depot_tools/prpc +1 -1
  191. data/vendor/depot_tools/pylint +2 -12
  192. data/vendor/depot_tools/pylint-1.5 +78 -0
  193. data/vendor/depot_tools/pylint-1.6 +78 -0
  194. data/vendor/depot_tools/pylint-1.7 +78 -0
  195. data/vendor/depot_tools/pylint-1.8 +78 -0
  196. data/vendor/depot_tools/pylint-1.9 +78 -0
  197. data/vendor/depot_tools/{depot-tools-auth.bat → pylint.bat} +2 -2
  198. data/vendor/depot_tools/pylint_main.py +45 -0
  199. data/vendor/depot_tools/pylintrc +14 -1
  200. data/vendor/depot_tools/python-bin/python3 +7 -0
  201. data/vendor/depot_tools/python_runner.sh +13 -8
  202. data/vendor/depot_tools/rdb +12 -0
  203. data/vendor/depot_tools/rdb.bat +7 -0
  204. data/vendor/depot_tools/recipes/OWNERS +0 -1
  205. data/vendor/depot_tools/recipes/README.recipes.md +215 -151
  206. data/vendor/depot_tools/recipes/recipe_modules/OWNERS +1 -1
  207. data/vendor/depot_tools/recipes/recipe_modules/bot_update/__init__.py +17 -17
  208. data/vendor/depot_tools/recipes/recipe_modules/bot_update/api.py +65 -32
  209. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/basic.json +12 -4
  210. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/basic_luci.json +11 -3
  211. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/basic_with_branch_heads.json +12 -4
  212. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/clobber.json +22 -8
  213. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/deprecated_got_revision_mapping.json +27 -9
  214. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/gerrit_no_rebase_patch_ref.json +22 -8
  215. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/gerrit_no_reset.json +22 -8
  216. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/input_commit_with_id_without_repo.json +22 -8
  217. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/multiple_patch_refs.json +22 -8
  218. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/no_apply_patch_on_gclient.json +27 -9
  219. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/no_cp_checkout_HEAD.json +12 -4
  220. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/no_cp_checkout_a_branch_head.json +10 -4
  221. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/no_cp_checkout_a_specific_commit.json +12 -4
  222. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/no_cp_checkout_master.json +10 -4
  223. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/refs.json +22 -8
  224. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/reset_root_solution_revision.json +22 -8
  225. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/resolve_chromium_fixed_version.json +117 -0
  226. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_fail.json +16 -10
  227. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_fail_patch.json +34 -10
  228. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_fail_patch_download.json +16 -10
  229. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_gerrit_angle.json +27 -9
  230. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_gerrit_branch_heads.json +27 -9
  231. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_gerrit_feature_branch.json +27 -9
  232. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_gerrit_v8_feature_branch.json +27 -9
  233. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_gerrit_webrtc.json +27 -9
  234. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_v8.json +27 -9
  235. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/tryjob_v8_head_by_default.json +27 -9
  236. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/unrecognized_commit_repo.json +0 -5
  237. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/with_tags.json +22 -8
  238. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.py +7 -23
  239. data/vendor/depot_tools/recipes/recipe_modules/bot_update/resources/bot_update.py +187 -114
  240. data/vendor/depot_tools/recipes/recipe_modules/bot_update/test_api.py +2 -2
  241. data/vendor/depot_tools/recipes/recipe_modules/bot_update/tests/do_not_retry_patch_failures_in_cq.py +42 -0
  242. data/vendor/depot_tools/recipes/recipe_modules/bot_update/tests/ensure_checkout.py +1 -0
  243. data/vendor/depot_tools/recipes/recipe_modules/cipd/__init__.py +0 -1
  244. data/vendor/depot_tools/recipes/recipe_modules/cipd/api.py +2 -2
  245. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/full.expected/basic.json +0 -1
  246. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/full.expected/basic_pkg.json +0 -1
  247. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/full.expected/describe-failed.json +2 -4
  248. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/full.expected/describe-many-instances.json +0 -1
  249. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/full.expected/mac64.json +0 -1
  250. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/full.expected/pkg_bad_file.json +26 -6
  251. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/full.expected/pkg_bad_mode.json +23 -5
  252. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/full.expected/pkg_bad_verfile.json +23 -5
  253. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/full.expected/win64.json +0 -1
  254. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/junk arch.json +0 -1
  255. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/junk bits.json +0 -1
  256. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/linux_arm_32.json +0 -1
  257. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/linux_arm_64.json +0 -1
  258. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/linux_intel_32.json +0 -1
  259. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/linux_intel_64.json +0 -1
  260. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/linux_mips_64.json +0 -1
  261. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/mac_intel_64.json +0 -1
  262. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/win_intel_32.json +0 -1
  263. data/vendor/depot_tools/recipes/recipe_modules/cipd/examples/platform_suffix.expected/win_intel_64.json +0 -1
  264. data/vendor/depot_tools/recipes/recipe_modules/cipd/test_api.py +1 -1
  265. data/vendor/depot_tools/recipes/recipe_modules/depot_tools/api.py +4 -1
  266. data/vendor/depot_tools/recipes/recipe_modules/depot_tools/examples/full.expected/basic.json +4 -2
  267. data/vendor/depot_tools/recipes/recipe_modules/depot_tools/examples/full.expected/basic_luci.json +3 -1
  268. data/vendor/depot_tools/recipes/recipe_modules/depot_tools/examples/full.expected/win.json +4 -2
  269. data/vendor/depot_tools/recipes/recipe_modules/gclient/__init__.py +2 -1
  270. data/vendor/depot_tools/recipes/recipe_modules/gclient/api.py +82 -13
  271. data/vendor/depot_tools/recipes/recipe_modules/gclient/config.py +21 -9
  272. data/vendor/depot_tools/recipes/recipe_modules/gclient/examples/full.expected/basic.json +3 -4
  273. data/vendor/depot_tools/recipes/recipe_modules/gclient/examples/full.expected/revision.json +3 -6
  274. data/vendor/depot_tools/recipes/recipe_modules/gclient/examples/full.expected/tryserver.json +3 -4
  275. data/vendor/depot_tools/recipes/recipe_modules/gclient/examples/full.py +7 -7
  276. data/vendor/depot_tools/recipes/recipe_modules/gclient/resources/diff_deps.py +16 -0
  277. data/vendor/depot_tools/recipes/recipe_modules/gclient/test_api.py +4 -0
  278. data/vendor/depot_tools/recipes/recipe_modules/gclient/tests/diff_deps.expected/basic.json +55 -0
  279. data/vendor/depot_tools/recipes/recipe_modules/gclient/tests/diff_deps.expected/dont have revision yet.json +84 -0
  280. data/vendor/depot_tools/recipes/recipe_modules/gclient/tests/diff_deps.expected/no change, exception.json +83 -0
  281. data/vendor/depot_tools/recipes/recipe_modules/gclient/tests/diff_deps.expected/windows.json +55 -0
  282. data/vendor/depot_tools/recipes/recipe_modules/gclient/tests/diff_deps.py +88 -0
  283. data/vendor/depot_tools/recipes/recipe_modules/gclient/tests/patch_project.py +9 -21
  284. data/vendor/depot_tools/recipes/recipe_modules/gclient/tests/sync_failure.py +24 -0
  285. data/vendor/depot_tools/recipes/recipe_modules/gerrit/api.py +2 -1
  286. data/vendor/depot_tools/recipes/recipe_modules/gerrit/examples/full.expected/basic.json +7 -12
  287. data/vendor/depot_tools/recipes/recipe_modules/git/__init__.py +0 -2
  288. data/vendor/depot_tools/recipes/recipe_modules/git/api.py +9 -17
  289. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/basic.json +2 -8
  290. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/basic_branch.json +2 -8
  291. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/basic_file_name.json +2 -8
  292. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/basic_hash.json +2 -8
  293. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/basic_ref.json +2 -8
  294. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/basic_submodule_update_force.json +2 -8
  295. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/basic_tags.json +2 -8
  296. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/can_fail_build.json +3 -13
  297. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/cannot_fail_build.json +2 -8
  298. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/cat-file_test.json +4 -12
  299. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/count-objects_delta.json +2 -10
  300. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/count-objects_failed.json +1 -7
  301. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/count-objects_with_bad_output.json +1 -7
  302. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/count-objects_with_bad_output_fails_build.json +0 -8
  303. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/curl_trace_file.json +3 -10
  304. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/git-cache-checkout.json +3 -11
  305. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/platform_win.json +2 -8
  306. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/rebase_failed.json +3 -13
  307. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/remote_not_origin.json +2 -8
  308. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/set_got_revision.json +2 -8
  309. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.py +0 -5
  310. data/vendor/depot_tools/recipes/recipe_modules/git_cl/__init__.py +1 -1
  311. data/vendor/depot_tools/recipes/recipe_modules/git_cl/api.py +7 -13
  312. data/vendor/depot_tools/recipes/recipe_modules/git_cl/examples/full.expected/basic.json +21 -16
  313. data/vendor/depot_tools/recipes/recipe_modules/git_cl/examples/full.py +1 -2
  314. data/vendor/depot_tools/recipes/recipe_modules/gitiles/api.py +1 -1
  315. data/vendor/depot_tools/recipes/recipe_modules/gitiles/examples/full.expected/basic.json +0 -1
  316. data/vendor/depot_tools/recipes/recipe_modules/gitiles/resources/gerrit_client.py +2 -2
  317. data/vendor/depot_tools/recipes/recipe_modules/gitiles/tests/parse_repo_url.expected/basic.json +0 -1
  318. data/vendor/depot_tools/recipes/recipe_modules/gsutil/api.py +23 -8
  319. data/vendor/depot_tools/recipes/recipe_modules/gsutil/examples/full.expected/basic.json +52 -1
  320. data/vendor/depot_tools/recipes/recipe_modules/gsutil/examples/full.py +15 -0
  321. data/vendor/depot_tools/recipes/recipe_modules/osx_sdk/__init__.py +1 -0
  322. data/vendor/depot_tools/recipes/recipe_modules/osx_sdk/api.py +32 -8
  323. data/vendor/depot_tools/recipes/recipe_modules/osx_sdk/examples/full.expected/ancient_version.json +83 -0
  324. data/vendor/depot_tools/recipes/recipe_modules/osx_sdk/examples/full.expected/automatic_version.json +83 -0
  325. data/vendor/depot_tools/recipes/recipe_modules/osx_sdk/examples/full.expected/explicit_version.json +83 -0
  326. data/vendor/depot_tools/recipes/recipe_modules/osx_sdk/examples/full.expected/linux.json +0 -1
  327. data/vendor/depot_tools/recipes/recipe_modules/osx_sdk/examples/full.expected/mac.json +5 -4
  328. data/vendor/depot_tools/recipes/recipe_modules/osx_sdk/examples/full.expected/win.json +0 -1
  329. data/vendor/depot_tools/recipes/recipe_modules/osx_sdk/examples/full.py +21 -2
  330. data/vendor/depot_tools/recipes/recipe_modules/presubmit/__init__.py +20 -0
  331. data/vendor/depot_tools/recipes/recipe_modules/presubmit/api.py +235 -3
  332. data/vendor/depot_tools/recipes/recipe_modules/presubmit/examples/full.expected/basic.json +13 -5
  333. data/vendor/depot_tools/recipes/recipe_modules/presubmit/examples/full.py +5 -1
  334. data/vendor/depot_tools/recipes/recipe_modules/presubmit/properties.proto +14 -0
  335. data/vendor/depot_tools/recipes/recipe_modules/presubmit/test_api.py +19 -0
  336. data/vendor/depot_tools/recipes/recipe_modules/presubmit/tests/execute.py +247 -0
  337. data/vendor/depot_tools/recipes/recipe_modules/presubmit/tests/prepare.py +49 -0
  338. data/vendor/depot_tools/recipes/recipe_modules/tryserver/api.py +33 -45
  339. data/vendor/depot_tools/recipes/recipe_modules/tryserver/examples/full.expected/basic_tags.json +0 -1
  340. data/vendor/depot_tools/recipes/recipe_modules/tryserver/examples/full.expected/with_gerrit_patch.json +64 -21
  341. data/vendor/depot_tools/recipes/recipe_modules/tryserver/examples/full.expected/with_gerrit_patch_and_target_ref.json +64 -21
  342. data/vendor/depot_tools/recipes/recipe_modules/tryserver/examples/full.expected/with_wrong_patch.json +1 -22
  343. data/vendor/depot_tools/recipes/recipe_modules/tryserver/examples/full.expected/with_wrong_patch_new.json +1 -22
  344. data/vendor/depot_tools/recipes/recipe_modules/tryserver/examples/full.py +5 -32
  345. data/vendor/depot_tools/recipes/recipe_modules/tryserver/tests/gerrit_change_fetch_ref_timeout.py +29 -0
  346. data/vendor/depot_tools/recipes/recipe_modules/tryserver/tests/gerrit_change_owner.expected/basic.json +5 -0
  347. data/vendor/depot_tools/recipes/recipe_modules/tryserver/tests/gerrit_change_owner.py +22 -0
  348. data/vendor/depot_tools/recipes/recipe_modules/tryserver/tests/gerrit_change_target_ref.py +32 -0
  349. data/vendor/depot_tools/recipes/recipe_modules/windows_sdk/examples/full.expected/linux.json +0 -1
  350. data/vendor/depot_tools/recipes/recipe_modules/windows_sdk/examples/full.expected/mac.json +0 -1
  351. data/vendor/depot_tools/recipes/recipe_modules/windows_sdk/examples/full.expected/win.json +2 -1
  352. data/vendor/depot_tools/recipes/recipe_modules/windows_sdk/examples/full.py +1 -6
  353. data/vendor/depot_tools/recipes/recipes.py +49 -28
  354. data/vendor/depot_tools/recipes/recipes/fetch_end_to_end_test.expected/basic.json +11 -6
  355. data/vendor/depot_tools/repo +698 -540
  356. data/vendor/depot_tools/roll-dep +14 -1
  357. data/vendor/depot_tools/roll-dep.bat +10 -1
  358. data/vendor/depot_tools/roll_dep.py +15 -10
  359. data/vendor/depot_tools/scm.py +94 -74
  360. data/vendor/depot_tools/setup_color.py +36 -2
  361. data/vendor/depot_tools/split_cl.py +44 -32
  362. data/vendor/depot_tools/subcommand.py +6 -6
  363. data/vendor/depot_tools/subprocess2.py +38 -322
  364. data/vendor/depot_tools/third_party/colorama/LICENSE.txt +0 -1
  365. data/vendor/depot_tools/third_party/colorama/README.chromium +4 -5
  366. data/vendor/depot_tools/third_party/colorama/README.rst +346 -0
  367. data/vendor/depot_tools/third_party/colorama/__init__.py +3 -4
  368. data/vendor/depot_tools/third_party/colorama/ansi.py +82 -30
  369. data/vendor/depot_tools/third_party/colorama/ansitowin32.py +105 -37
  370. data/vendor/depot_tools/third_party/colorama/initialise.py +39 -15
  371. data/vendor/depot_tools/third_party/colorama/win32.py +46 -28
  372. data/vendor/depot_tools/third_party/colorama/winterm.py +80 -31
  373. data/vendor/depot_tools/update_depot_tools +18 -8
  374. data/vendor/depot_tools/update_depot_tools.bat +19 -15
  375. data/vendor/depot_tools/upload_metrics.py +7 -6
  376. data/vendor/depot_tools/upload_to_google_storage.py +22 -15
  377. data/vendor/depot_tools/vpython +4 -4
  378. data/vendor/depot_tools/vpython.bat +1 -1
  379. data/vendor/depot_tools/vpython3 +55 -0
  380. data/vendor/depot_tools/vpython3.bat +12 -0
  381. data/vendor/depot_tools/watchlists.py +14 -11
  382. data/vendor/depot_tools/weekly +4 -2
  383. data/vendor/depot_tools/win32imports.py +61 -0
  384. data/vendor/depot_tools/win_toolchain/get_toolchain_if_necessary.py +49 -41
  385. data/vendor/depot_tools/win_toolchain/package_from_installed.py +142 -149
  386. data/vendor/depot_tools/wtf +5 -3
  387. data/vendor/depot_tools/yapf +5 -1
  388. metadata +66 -345
  389. data/vendor/depot_tools/README.testing +0 -23
  390. data/vendor/depot_tools/annotated_gclient.py +0 -89
  391. data/vendor/depot_tools/appengine_mapper.py +0 -23
  392. data/vendor/depot_tools/bootstrap/win/manifest.txt +0 -20
  393. data/vendor/depot_tools/bootstrap/win/manifest_bleeding_edge.txt +0 -20
  394. data/vendor/depot_tools/bootstrap/win/pylint.new.bat +0 -7
  395. data/vendor/depot_tools/buildbucket.py +0 -186
  396. data/vendor/depot_tools/checkout.py +0 -431
  397. data/vendor/depot_tools/cros +0 -1
  398. data/vendor/depot_tools/dart_format.py +0 -58
  399. data/vendor/depot_tools/depot-tools-auth +0 -8
  400. data/vendor/depot_tools/depot-tools-auth.py +0 -102
  401. data/vendor/depot_tools/my_reviews.py +0 -396
  402. data/vendor/depot_tools/patch.py +0 -548
  403. data/vendor/depot_tools/pylint.py +0 -30
  404. data/vendor/depot_tools/recipes/recipe_modules/bot_update/examples/full.expected/with_manifest_name.json +0 -48
  405. data/vendor/depot_tools/recipes/recipe_modules/gclient/examples/full.expected/buildbot.json +0 -239
  406. data/vendor/depot_tools/recipes/recipe_modules/git/examples/full.expected/basic_luci.json +0 -222
  407. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/__init__.py +0 -4
  408. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/api.py +0 -29
  409. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/basic.json +0 -15
  410. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/paths_buildbot_linux.json +0 -15
  411. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/paths_buildbot_mac.json +0 -15
  412. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/paths_buildbot_win.json +0 -15
  413. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/paths_generic_linux.json +0 -15
  414. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/paths_generic_mac.json +0 -15
  415. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/paths_generic_win.json +0 -15
  416. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/paths_kitchen_linux.json +0 -15
  417. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/paths_kitchen_mac.json +0 -15
  418. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.expected/paths_kitchen_win.json +0 -15
  419. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/examples/full.py +0 -33
  420. data/vendor/depot_tools/recipes/recipe_modules/infra_paths/path_config.py +0 -66
  421. data/vendor/depot_tools/recipes/recipe_modules/tryserver/examples/full.expected/set_failure_hash_with_no_steps.json +0 -11
  422. data/vendor/depot_tools/recipes/recipe_modules/tryserver/examples/full.expected/with_git_patch.json +0 -45
  423. data/vendor/depot_tools/recipes/recipe_modules/tryserver/examples/full.expected/with_git_patch_luci.json +0 -45
  424. data/vendor/depot_tools/rietveld.py +0 -779
  425. data/vendor/depot_tools/roll-dep-svn +0 -8
  426. data/vendor/depot_tools/roll-dep-svn.bat +0 -12
  427. data/vendor/depot_tools/roll_dep_svn.py +0 -430
  428. data/vendor/depot_tools/support/chromite_wrapper +0 -96
  429. data/vendor/depot_tools/third_party/boto/LICENSE +0 -18
  430. data/vendor/depot_tools/third_party/boto/README.chromium +0 -43
  431. data/vendor/depot_tools/third_party/boto/README.rst +0 -163
  432. data/vendor/depot_tools/third_party/boto/__init__.py +0 -793
  433. data/vendor/depot_tools/third_party/boto/auth.py +0 -682
  434. data/vendor/depot_tools/third_party/boto/auth_handler.py +0 -58
  435. data/vendor/depot_tools/third_party/boto/cacerts/__init__.py +0 -22
  436. data/vendor/depot_tools/third_party/boto/cacerts/cacerts.txt +0 -2183
  437. data/vendor/depot_tools/third_party/boto/compat.py +0 -28
  438. data/vendor/depot_tools/third_party/boto/connection.py +0 -1081
  439. data/vendor/depot_tools/third_party/boto/contrib/__init__.py +0 -22
  440. data/vendor/depot_tools/third_party/boto/contrib/ymlmessage.py +0 -52
  441. data/vendor/depot_tools/third_party/boto/core/README +0 -58
  442. data/vendor/depot_tools/third_party/boto/core/__init__.py +0 -23
  443. data/vendor/depot_tools/third_party/boto/core/auth.py +0 -78
  444. data/vendor/depot_tools/third_party/boto/core/credentials.py +0 -154
  445. data/vendor/depot_tools/third_party/boto/core/dictresponse.py +0 -178
  446. data/vendor/depot_tools/third_party/boto/core/service.py +0 -67
  447. data/vendor/depot_tools/third_party/boto/datapipeline/__init__.py +0 -0
  448. data/vendor/depot_tools/third_party/boto/datapipeline/exceptions.py +0 -42
  449. data/vendor/depot_tools/third_party/boto/datapipeline/layer1.py +0 -546
  450. data/vendor/depot_tools/third_party/boto/ecs/__init__.py +0 -90
  451. data/vendor/depot_tools/third_party/boto/ecs/item.py +0 -153
  452. data/vendor/depot_tools/third_party/boto/exception.py +0 -476
  453. data/vendor/depot_tools/third_party/boto/file/README +0 -49
  454. data/vendor/depot_tools/third_party/boto/file/__init__.py +0 -28
  455. data/vendor/depot_tools/third_party/boto/file/bucket.py +0 -112
  456. data/vendor/depot_tools/third_party/boto/file/connection.py +0 -33
  457. data/vendor/depot_tools/third_party/boto/file/key.py +0 -199
  458. data/vendor/depot_tools/third_party/boto/file/simpleresultset.py +0 -30
  459. data/vendor/depot_tools/third_party/boto/fps/__init__.py +0 -21
  460. data/vendor/depot_tools/third_party/boto/fps/connection.py +0 -369
  461. data/vendor/depot_tools/third_party/boto/fps/exception.py +0 -344
  462. data/vendor/depot_tools/third_party/boto/fps/response.py +0 -175
  463. data/vendor/depot_tools/third_party/boto/gs/__init__.py +0 -22
  464. data/vendor/depot_tools/third_party/boto/gs/acl.py +0 -304
  465. data/vendor/depot_tools/third_party/boto/gs/bucket.py +0 -870
  466. data/vendor/depot_tools/third_party/boto/gs/bucketlistresultset.py +0 -64
  467. data/vendor/depot_tools/third_party/boto/gs/connection.py +0 -103
  468. data/vendor/depot_tools/third_party/boto/gs/cors.py +0 -169
  469. data/vendor/depot_tools/third_party/boto/gs/key.py +0 -704
  470. data/vendor/depot_tools/third_party/boto/gs/resumable_upload_handler.py +0 -659
  471. data/vendor/depot_tools/third_party/boto/gs/user.py +0 -54
  472. data/vendor/depot_tools/third_party/boto/handler.py +0 -44
  473. data/vendor/depot_tools/third_party/boto/https_connection.py +0 -124
  474. data/vendor/depot_tools/third_party/boto/jsonresponse.py +0 -163
  475. data/vendor/depot_tools/third_party/boto/manage/__init__.py +0 -23
  476. data/vendor/depot_tools/third_party/boto/manage/cmdshell.py +0 -241
  477. data/vendor/depot_tools/third_party/boto/manage/propget.py +0 -64
  478. data/vendor/depot_tools/third_party/boto/manage/server.py +0 -556
  479. data/vendor/depot_tools/third_party/boto/manage/task.py +0 -175
  480. data/vendor/depot_tools/third_party/boto/manage/test_manage.py +0 -34
  481. data/vendor/depot_tools/third_party/boto/manage/volume.py +0 -420
  482. data/vendor/depot_tools/third_party/boto/mashups/__init__.py +0 -23
  483. data/vendor/depot_tools/third_party/boto/mashups/interactive.py +0 -97
  484. data/vendor/depot_tools/third_party/boto/mashups/iobject.py +0 -115
  485. data/vendor/depot_tools/third_party/boto/mashups/order.py +0 -211
  486. data/vendor/depot_tools/third_party/boto/mashups/server.py +0 -395
  487. data/vendor/depot_tools/third_party/boto/plugin.py +0 -90
  488. data/vendor/depot_tools/third_party/boto/provider.py +0 -337
  489. data/vendor/depot_tools/third_party/boto/pyami/__init__.py +0 -22
  490. data/vendor/depot_tools/third_party/boto/pyami/bootstrap.py +0 -134
  491. data/vendor/depot_tools/third_party/boto/pyami/config.py +0 -229
  492. data/vendor/depot_tools/third_party/boto/pyami/copybot.cfg +0 -60
  493. data/vendor/depot_tools/third_party/boto/pyami/copybot.py +0 -97
  494. data/vendor/depot_tools/third_party/boto/pyami/helloworld.py +0 -28
  495. data/vendor/depot_tools/third_party/boto/pyami/launch_ami.py +0 -178
  496. data/vendor/depot_tools/third_party/boto/pyami/scriptbase.py +0 -44
  497. data/vendor/depot_tools/third_party/boto/pyami/startup.py +0 -60
  498. data/vendor/depot_tools/third_party/boto/regioninfo.py +0 -63
  499. data/vendor/depot_tools/third_party/boto/resultset.py +0 -169
  500. data/vendor/depot_tools/third_party/boto/roboto/__init__.py +0 -1
  501. data/vendor/depot_tools/third_party/boto/roboto/awsqueryrequest.py +0 -504
  502. data/vendor/depot_tools/third_party/boto/roboto/awsqueryservice.py +0 -121
  503. data/vendor/depot_tools/third_party/boto/roboto/param.py +0 -147
  504. data/vendor/depot_tools/third_party/boto/s3/__init__.py +0 -84
  505. data/vendor/depot_tools/third_party/boto/s3/acl.py +0 -164
  506. data/vendor/depot_tools/third_party/boto/s3/bucket.py +0 -1634
  507. data/vendor/depot_tools/third_party/boto/s3/bucketlistresultset.py +0 -139
  508. data/vendor/depot_tools/third_party/boto/s3/bucketlogging.py +0 -83
  509. data/vendor/depot_tools/third_party/boto/s3/connection.py +0 -540
  510. data/vendor/depot_tools/third_party/boto/s3/cors.py +0 -210
  511. data/vendor/depot_tools/third_party/boto/s3/deletemarker.py +0 -55
  512. data/vendor/depot_tools/third_party/boto/s3/key.py +0 -1712
  513. data/vendor/depot_tools/third_party/boto/s3/keyfile.py +0 -134
  514. data/vendor/depot_tools/third_party/boto/s3/lifecycle.py +0 -231
  515. data/vendor/depot_tools/third_party/boto/s3/multidelete.py +0 -138
  516. data/vendor/depot_tools/third_party/boto/s3/multipart.py +0 -315
  517. data/vendor/depot_tools/third_party/boto/s3/prefix.py +0 -42
  518. data/vendor/depot_tools/third_party/boto/s3/resumable_download_handler.py +0 -339
  519. data/vendor/depot_tools/third_party/boto/s3/tagging.py +0 -71
  520. data/vendor/depot_tools/third_party/boto/s3/user.py +0 -49
  521. data/vendor/depot_tools/third_party/boto/s3/website.py +0 -237
  522. data/vendor/depot_tools/third_party/boto/services/__init__.py +0 -23
  523. data/vendor/depot_tools/third_party/boto/services/bs.py +0 -179
  524. data/vendor/depot_tools/third_party/boto/services/message.py +0 -58
  525. data/vendor/depot_tools/third_party/boto/services/result.py +0 -136
  526. data/vendor/depot_tools/third_party/boto/services/service.py +0 -161
  527. data/vendor/depot_tools/third_party/boto/services/servicedef.py +0 -91
  528. data/vendor/depot_tools/third_party/boto/services/sonofmmm.cfg +0 -43
  529. data/vendor/depot_tools/third_party/boto/services/sonofmmm.py +0 -81
  530. data/vendor/depot_tools/third_party/boto/services/submit.py +0 -88
  531. data/vendor/depot_tools/third_party/boto/ses/__init__.py +0 -54
  532. data/vendor/depot_tools/third_party/boto/ses/connection.py +0 -521
  533. data/vendor/depot_tools/third_party/boto/ses/exceptions.py +0 -77
  534. data/vendor/depot_tools/third_party/boto/storage_uri.py +0 -835
  535. data/vendor/depot_tools/third_party/boto/sts/__init__.py +0 -55
  536. data/vendor/depot_tools/third_party/boto/sts/connection.py +0 -207
  537. data/vendor/depot_tools/third_party/boto/sts/credentials.py +0 -215
  538. data/vendor/depot_tools/third_party/boto/utils.py +0 -927
  539. data/vendor/depot_tools/third_party/colorama/README.txt +0 -304
  540. data/vendor/depot_tools/third_party/fancy_urllib/README +0 -22
  541. data/vendor/depot_tools/third_party/fancy_urllib/__init__.py +0 -460
  542. data/vendor/depot_tools/third_party/logilab/README.chromium +0 -6
  543. data/vendor/depot_tools/third_party/logilab/__init__.py +0 -0
  544. data/vendor/depot_tools/third_party/logilab/astroid/LICENSE.txt +0 -340
  545. data/vendor/depot_tools/third_party/logilab/astroid/README.chromium +0 -11
  546. data/vendor/depot_tools/third_party/logilab/astroid/__init__.py +0 -136
  547. data/vendor/depot_tools/third_party/logilab/astroid/__pkginfo__.py +0 -42
  548. data/vendor/depot_tools/third_party/logilab/astroid/arguments.py +0 -233
  549. data/vendor/depot_tools/third_party/logilab/astroid/as_string.py +0 -548
  550. data/vendor/depot_tools/third_party/logilab/astroid/astpeephole.py +0 -86
  551. data/vendor/depot_tools/third_party/logilab/astroid/bases.py +0 -636
  552. data/vendor/depot_tools/third_party/logilab/astroid/brain/brain_builtin_inference.py +0 -336
  553. data/vendor/depot_tools/third_party/logilab/astroid/brain/brain_dateutil.py +0 -15
  554. data/vendor/depot_tools/third_party/logilab/astroid/brain/brain_gi.py +0 -195
  555. data/vendor/depot_tools/third_party/logilab/astroid/brain/brain_mechanize.py +0 -18
  556. data/vendor/depot_tools/third_party/logilab/astroid/brain/brain_nose.py +0 -82
  557. data/vendor/depot_tools/third_party/logilab/astroid/brain/brain_numpy.py +0 -62
  558. data/vendor/depot_tools/third_party/logilab/astroid/brain/brain_pytest.py +0 -76
  559. data/vendor/depot_tools/third_party/logilab/astroid/brain/brain_qt.py +0 -44
  560. data/vendor/depot_tools/third_party/logilab/astroid/brain/brain_six.py +0 -288
  561. data/vendor/depot_tools/third_party/logilab/astroid/brain/brain_ssl.py +0 -65
  562. data/vendor/depot_tools/third_party/logilab/astroid/brain/brain_stdlib.py +0 -473
  563. data/vendor/depot_tools/third_party/logilab/astroid/builder.py +0 -263
  564. data/vendor/depot_tools/third_party/logilab/astroid/context.py +0 -81
  565. data/vendor/depot_tools/third_party/logilab/astroid/decorators.py +0 -75
  566. data/vendor/depot_tools/third_party/logilab/astroid/exceptions.py +0 -71
  567. data/vendor/depot_tools/third_party/logilab/astroid/inference.py +0 -359
  568. data/vendor/depot_tools/third_party/logilab/astroid/manager.py +0 -267
  569. data/vendor/depot_tools/third_party/logilab/astroid/mixins.py +0 -147
  570. data/vendor/depot_tools/third_party/logilab/astroid/modutils.py +0 -741
  571. data/vendor/depot_tools/third_party/logilab/astroid/node_classes.py +0 -1053
  572. data/vendor/depot_tools/third_party/logilab/astroid/nodes.py +0 -87
  573. data/vendor/depot_tools/third_party/logilab/astroid/objects.py +0 -186
  574. data/vendor/depot_tools/third_party/logilab/astroid/protocols.py +0 -470
  575. data/vendor/depot_tools/third_party/logilab/astroid/raw_building.py +0 -390
  576. data/vendor/depot_tools/third_party/logilab/astroid/rebuilder.py +0 -989
  577. data/vendor/depot_tools/third_party/logilab/astroid/scoped_nodes.py +0 -1716
  578. data/vendor/depot_tools/third_party/logilab/astroid/test_utils.py +0 -201
  579. data/vendor/depot_tools/third_party/logilab/astroid/transforms.py +0 -96
  580. data/vendor/depot_tools/third_party/logilab/astroid/util.py +0 -89
  581. data/vendor/depot_tools/third_party/logilab/lazy_object_proxy/LICENSE +0 -19
  582. data/vendor/depot_tools/third_party/logilab/lazy_object_proxy/README.chromium +0 -11
  583. data/vendor/depot_tools/third_party/logilab/lazy_object_proxy/__init__.py +0 -20
  584. data/vendor/depot_tools/third_party/logilab/lazy_object_proxy/cext.c +0 -1421
  585. data/vendor/depot_tools/third_party/logilab/lazy_object_proxy/compat.py +0 -9
  586. data/vendor/depot_tools/third_party/logilab/lazy_object_proxy/simple.py +0 -246
  587. data/vendor/depot_tools/third_party/logilab/lazy_object_proxy/slots.py +0 -414
  588. data/vendor/depot_tools/third_party/logilab/lazy_object_proxy/utils.py +0 -13
  589. data/vendor/depot_tools/third_party/logilab/wrapt/LICENSE +0 -24
  590. data/vendor/depot_tools/third_party/logilab/wrapt/README.chromium +0 -11
  591. data/vendor/depot_tools/third_party/logilab/wrapt/__init__.py +0 -19
  592. data/vendor/depot_tools/third_party/logilab/wrapt/_wrappers.c +0 -2729
  593. data/vendor/depot_tools/third_party/logilab/wrapt/arguments.py +0 -96
  594. data/vendor/depot_tools/third_party/logilab/wrapt/decorators.py +0 -512
  595. data/vendor/depot_tools/third_party/logilab/wrapt/importer.py +0 -228
  596. data/vendor/depot_tools/third_party/logilab/wrapt/wrappers.py +0 -901
  597. data/vendor/depot_tools/third_party/mock/LICENSE.txt +0 -26
  598. data/vendor/depot_tools/third_party/mock/README.chromium +0 -24
  599. data/vendor/depot_tools/third_party/mock/__init__.py +0 -2366
  600. data/vendor/depot_tools/third_party/oauth2client/LICENSE +0 -202
  601. data/vendor/depot_tools/third_party/oauth2client/MODIFICATIONS.diff +0 -66
  602. data/vendor/depot_tools/third_party/oauth2client/README.chromium +0 -15
  603. data/vendor/depot_tools/third_party/oauth2client/__init__.py +0 -5
  604. data/vendor/depot_tools/third_party/oauth2client/anyjson.py +0 -32
  605. data/vendor/depot_tools/third_party/oauth2client/appengine.py +0 -963
  606. data/vendor/depot_tools/third_party/oauth2client/client.py +0 -1363
  607. data/vendor/depot_tools/third_party/oauth2client/clientsecrets.py +0 -153
  608. data/vendor/depot_tools/third_party/oauth2client/crypt.py +0 -377
  609. data/vendor/depot_tools/third_party/oauth2client/django_orm.py +0 -134
  610. data/vendor/depot_tools/third_party/oauth2client/file.py +0 -124
  611. data/vendor/depot_tools/third_party/oauth2client/gce.py +0 -90
  612. data/vendor/depot_tools/third_party/oauth2client/keyring_storage.py +0 -109
  613. data/vendor/depot_tools/third_party/oauth2client/locked_file.py +0 -373
  614. data/vendor/depot_tools/third_party/oauth2client/multistore_file.py +0 -465
  615. data/vendor/depot_tools/third_party/oauth2client/old_run.py +0 -160
  616. data/vendor/depot_tools/third_party/oauth2client/tools.py +0 -243
  617. data/vendor/depot_tools/third_party/oauth2client/util.py +0 -196
  618. data/vendor/depot_tools/third_party/oauth2client/xsrfutil.py +0 -113
  619. data/vendor/depot_tools/third_party/protobuf26/README.chromium +0 -23
  620. data/vendor/depot_tools/third_party/protobuf26/__init__.py +0 -0
  621. data/vendor/depot_tools/third_party/protobuf26/compiler/__init__.py +0 -0
  622. data/vendor/depot_tools/third_party/protobuf26/compiler/plugin_pb2.py +0 -184
  623. data/vendor/depot_tools/third_party/protobuf26/descriptor.py +0 -849
  624. data/vendor/depot_tools/third_party/protobuf26/descriptor_database.py +0 -137
  625. data/vendor/depot_tools/third_party/protobuf26/descriptor_pb2.py +0 -1522
  626. data/vendor/depot_tools/third_party/protobuf26/descriptor_pool.py +0 -643
  627. data/vendor/depot_tools/third_party/protobuf26/internal/__init__.py +0 -0
  628. data/vendor/depot_tools/third_party/protobuf26/internal/api_implementation.py +0 -89
  629. data/vendor/depot_tools/third_party/protobuf26/internal/containers.py +0 -269
  630. data/vendor/depot_tools/third_party/protobuf26/internal/cpp_message.py +0 -663
  631. data/vendor/depot_tools/third_party/protobuf26/internal/decoder.py +0 -831
  632. data/vendor/depot_tools/third_party/protobuf26/internal/encoder.py +0 -788
  633. data/vendor/depot_tools/third_party/protobuf26/internal/enum_type_wrapper.py +0 -89
  634. data/vendor/depot_tools/third_party/protobuf26/internal/message_listener.py +0 -78
  635. data/vendor/depot_tools/third_party/protobuf26/internal/python_message.py +0 -1247
  636. data/vendor/depot_tools/third_party/protobuf26/internal/type_checkers.py +0 -328
  637. data/vendor/depot_tools/third_party/protobuf26/internal/wire_format.py +0 -268
  638. data/vendor/depot_tools/third_party/protobuf26/message.py +0 -284
  639. data/vendor/depot_tools/third_party/protobuf26/message_factory.py +0 -155
  640. data/vendor/depot_tools/third_party/protobuf26/reflection.py +0 -205
  641. data/vendor/depot_tools/third_party/protobuf26/service.py +0 -226
  642. data/vendor/depot_tools/third_party/protobuf26/service_reflection.py +0 -284
  643. data/vendor/depot_tools/third_party/protobuf26/symbol_database.py +0 -185
  644. data/vendor/depot_tools/third_party/protobuf26/text_encoding.py +0 -110
  645. data/vendor/depot_tools/third_party/protobuf26/text_format.py +0 -873
  646. data/vendor/depot_tools/third_party/pylint.py +0 -37
  647. data/vendor/depot_tools/third_party/pylint/LICENSE.txt +0 -340
  648. data/vendor/depot_tools/third_party/pylint/README.chromium +0 -30
  649. data/vendor/depot_tools/third_party/pylint/__init__.py +0 -46
  650. data/vendor/depot_tools/third_party/pylint/__main__.py +0 -3
  651. data/vendor/depot_tools/third_party/pylint/__pkginfo__.py +0 -80
  652. data/vendor/depot_tools/third_party/pylint/checkers/__init__.py +0 -123
  653. data/vendor/depot_tools/third_party/pylint/checkers/async.py +0 -82
  654. data/vendor/depot_tools/third_party/pylint/checkers/base.py +0 -2010
  655. data/vendor/depot_tools/third_party/pylint/checkers/classes.py +0 -1120
  656. data/vendor/depot_tools/third_party/pylint/checkers/design_analysis.py +0 -348
  657. data/vendor/depot_tools/third_party/pylint/checkers/exceptions.py +0 -369
  658. data/vendor/depot_tools/third_party/pylint/checkers/format.py +0 -993
  659. data/vendor/depot_tools/third_party/pylint/checkers/imports.py +0 -654
  660. data/vendor/depot_tools/third_party/pylint/checkers/logging.py +0 -258
  661. data/vendor/depot_tools/third_party/pylint/checkers/misc.py +0 -105
  662. data/vendor/depot_tools/third_party/pylint/checkers/newstyle.py +0 -169
  663. data/vendor/depot_tools/third_party/pylint/checkers/python3.py +0 -591
  664. data/vendor/depot_tools/third_party/pylint/checkers/raw_metrics.py +0 -129
  665. data/vendor/depot_tools/third_party/pylint/checkers/similar.py +0 -371
  666. data/vendor/depot_tools/third_party/pylint/checkers/spelling.py +0 -264
  667. data/vendor/depot_tools/third_party/pylint/checkers/stdlib.py +0 -280
  668. data/vendor/depot_tools/third_party/pylint/checkers/strings.py +0 -618
  669. data/vendor/depot_tools/third_party/pylint/checkers/typecheck.py +0 -974
  670. data/vendor/depot_tools/third_party/pylint/checkers/utils.py +0 -741
  671. data/vendor/depot_tools/third_party/pylint/checkers/variables.py +0 -1191
  672. data/vendor/depot_tools/third_party/pylint/config.py +0 -820
  673. data/vendor/depot_tools/third_party/pylint/epylint.py +0 -181
  674. data/vendor/depot_tools/third_party/pylint/extensions/__init__.py +0 -0
  675. data/vendor/depot_tools/third_party/pylint/extensions/check_docs.py +0 -311
  676. data/vendor/depot_tools/third_party/pylint/extensions/check_elif.py +0 -62
  677. data/vendor/depot_tools/third_party/pylint/graph.py +0 -179
  678. data/vendor/depot_tools/third_party/pylint/gui.py +0 -531
  679. data/vendor/depot_tools/third_party/pylint/interfaces.py +0 -102
  680. data/vendor/depot_tools/third_party/pylint/lint.py +0 -1381
  681. data/vendor/depot_tools/third_party/pylint/pyreverse/__init__.py +0 -5
  682. data/vendor/depot_tools/third_party/pylint/pyreverse/diadefslib.py +0 -230
  683. data/vendor/depot_tools/third_party/pylint/pyreverse/diagrams.py +0 -258
  684. data/vendor/depot_tools/third_party/pylint/pyreverse/inspector.py +0 -372
  685. data/vendor/depot_tools/third_party/pylint/pyreverse/main.py +0 -147
  686. data/vendor/depot_tools/third_party/pylint/pyreverse/utils.py +0 -210
  687. data/vendor/depot_tools/third_party/pylint/pyreverse/vcgutils.py +0 -198
  688. data/vendor/depot_tools/third_party/pylint/pyreverse/writer.py +0 -198
  689. data/vendor/depot_tools/third_party/pylint/reporters/__init__.py +0 -149
  690. data/vendor/depot_tools/third_party/pylint/reporters/guireporter.py +0 -27
  691. data/vendor/depot_tools/third_party/pylint/reporters/html.py +0 -108
  692. data/vendor/depot_tools/third_party/pylint/reporters/json.py +0 -64
  693. data/vendor/depot_tools/third_party/pylint/reporters/text.py +0 -237
  694. data/vendor/depot_tools/third_party/pylint/reporters/ureports/__init__.py +0 -106
  695. data/vendor/depot_tools/third_party/pylint/reporters/ureports/html_writer.py +0 -93
  696. data/vendor/depot_tools/third_party/pylint/reporters/ureports/nodes.py +0 -181
  697. data/vendor/depot_tools/third_party/pylint/reporters/ureports/text_writer.py +0 -99
  698. data/vendor/depot_tools/third_party/pylint/testutils.py +0 -414
  699. data/vendor/depot_tools/third_party/pylint/utils.py +0 -1148
  700. data/vendor/depot_tools/third_party/pymox/COPYING +0 -202
  701. data/vendor/depot_tools/third_party/pymox/MANIFEST.in +0 -5
  702. data/vendor/depot_tools/third_party/pymox/README +0 -56
  703. data/vendor/depot_tools/third_party/pymox/__init__.py +0 -0
  704. data/vendor/depot_tools/third_party/pymox/mox.py +0 -1643
  705. data/vendor/depot_tools/third_party/pymox/mox_test.py +0 -1708
  706. data/vendor/depot_tools/third_party/pymox/mox_test_helper.py +0 -76
  707. data/vendor/depot_tools/third_party/pymox/setup.py +0 -14
  708. data/vendor/depot_tools/third_party/pymox/stubout.py +0 -142
  709. data/vendor/depot_tools/third_party/pymox/stubout_test.py +0 -47
  710. data/vendor/depot_tools/third_party/pymox/stubout_testee.py +0 -2
  711. data/vendor/depot_tools/third_party/simplejson/LICENSE.txt +0 -19
  712. data/vendor/depot_tools/third_party/simplejson/PKG-INFO +0 -29
  713. data/vendor/depot_tools/third_party/simplejson/__init__.py +0 -437
  714. data/vendor/depot_tools/third_party/simplejson/decoder.py +0 -421
  715. data/vendor/depot_tools/third_party/simplejson/encoder.py +0 -501
  716. data/vendor/depot_tools/third_party/simplejson/ordered_dict.py +0 -119
  717. data/vendor/depot_tools/third_party/simplejson/scanner.py +0 -77
  718. data/vendor/depot_tools/third_party/simplejson/tool.py +0 -39
  719. data/vendor/depot_tools/third_party/upload.py +0 -2565
@@ -1,119 +0,0 @@
1
- """Drop-in replacement for collections.OrderedDict by Raymond Hettinger
2
-
3
- http://code.activestate.com/recipes/576693/
4
-
5
- """
6
- from UserDict import DictMixin
7
-
8
- # Modified from original to support Python 2.4, see
9
- # http://code.google.com/p/simplejson/issues/detail?id=53
10
- try:
11
- all
12
- except NameError:
13
- def all(seq):
14
- for elem in seq:
15
- if not elem:
16
- return False
17
- return True
18
-
19
- class OrderedDict(dict, DictMixin):
20
-
21
- def __init__(self, *args, **kwds):
22
- if len(args) > 1:
23
- raise TypeError('expected at most 1 arguments, got %d' % len(args))
24
- try:
25
- self.__end
26
- except AttributeError:
27
- self.clear()
28
- self.update(*args, **kwds)
29
-
30
- def clear(self):
31
- self.__end = end = []
32
- end += [None, end, end] # sentinel node for doubly linked list
33
- self.__map = {} # key --> [key, prev, next]
34
- dict.clear(self)
35
-
36
- def __setitem__(self, key, value):
37
- if key not in self:
38
- end = self.__end
39
- curr = end[1]
40
- curr[2] = end[1] = self.__map[key] = [key, curr, end]
41
- dict.__setitem__(self, key, value)
42
-
43
- def __delitem__(self, key):
44
- dict.__delitem__(self, key)
45
- key, prev, next = self.__map.pop(key)
46
- prev[2] = next
47
- next[1] = prev
48
-
49
- def __iter__(self):
50
- end = self.__end
51
- curr = end[2]
52
- while curr is not end:
53
- yield curr[0]
54
- curr = curr[2]
55
-
56
- def __reversed__(self):
57
- end = self.__end
58
- curr = end[1]
59
- while curr is not end:
60
- yield curr[0]
61
- curr = curr[1]
62
-
63
- def popitem(self, last=True):
64
- if not self:
65
- raise KeyError('dictionary is empty')
66
- # Modified from original to support Python 2.4, see
67
- # http://code.google.com/p/simplejson/issues/detail?id=53
68
- if last:
69
- key = reversed(self).next()
70
- else:
71
- key = iter(self).next()
72
- value = self.pop(key)
73
- return key, value
74
-
75
- def __reduce__(self):
76
- items = [[k, self[k]] for k in self]
77
- tmp = self.__map, self.__end
78
- del self.__map, self.__end
79
- inst_dict = vars(self).copy()
80
- self.__map, self.__end = tmp
81
- if inst_dict:
82
- return (self.__class__, (items,), inst_dict)
83
- return self.__class__, (items,)
84
-
85
- def keys(self):
86
- return list(self)
87
-
88
- setdefault = DictMixin.setdefault
89
- update = DictMixin.update
90
- pop = DictMixin.pop
91
- values = DictMixin.values
92
- items = DictMixin.items
93
- iterkeys = DictMixin.iterkeys
94
- itervalues = DictMixin.itervalues
95
- iteritems = DictMixin.iteritems
96
-
97
- def __repr__(self):
98
- if not self:
99
- return '%s()' % (self.__class__.__name__,)
100
- return '%s(%r)' % (self.__class__.__name__, self.items())
101
-
102
- def copy(self):
103
- return self.__class__(self)
104
-
105
- @classmethod
106
- def fromkeys(cls, iterable, value=None):
107
- d = cls()
108
- for key in iterable:
109
- d[key] = value
110
- return d
111
-
112
- def __eq__(self, other):
113
- if isinstance(other, OrderedDict):
114
- return len(self)==len(other) and \
115
- all(p==q for p, q in zip(self.items(), other.items()))
116
- return dict.__eq__(self, other)
117
-
118
- def __ne__(self, other):
119
- return not self == other
@@ -1,77 +0,0 @@
1
- """JSON token scanner
2
- """
3
- import re
4
- def _import_c_make_scanner():
5
- try:
6
- from simplejson._speedups import make_scanner
7
- return make_scanner
8
- except ImportError:
9
- return None
10
- c_make_scanner = _import_c_make_scanner()
11
-
12
- __all__ = ['make_scanner']
13
-
14
- NUMBER_RE = re.compile(
15
- r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
16
- (re.VERBOSE | re.MULTILINE | re.DOTALL))
17
-
18
- def py_make_scanner(context):
19
- parse_object = context.parse_object
20
- parse_array = context.parse_array
21
- parse_string = context.parse_string
22
- match_number = NUMBER_RE.match
23
- encoding = context.encoding
24
- strict = context.strict
25
- parse_float = context.parse_float
26
- parse_int = context.parse_int
27
- parse_constant = context.parse_constant
28
- object_hook = context.object_hook
29
- object_pairs_hook = context.object_pairs_hook
30
- memo = context.memo
31
-
32
- def _scan_once(string, idx):
33
- try:
34
- nextchar = string[idx]
35
- except IndexError:
36
- raise StopIteration
37
-
38
- if nextchar == '"':
39
- return parse_string(string, idx + 1, encoding, strict)
40
- elif nextchar == '{':
41
- return parse_object((string, idx + 1), encoding, strict,
42
- _scan_once, object_hook, object_pairs_hook, memo)
43
- elif nextchar == '[':
44
- return parse_array((string, idx + 1), _scan_once)
45
- elif nextchar == 'n' and string[idx:idx + 4] == 'null':
46
- return None, idx + 4
47
- elif nextchar == 't' and string[idx:idx + 4] == 'true':
48
- return True, idx + 4
49
- elif nextchar == 'f' and string[idx:idx + 5] == 'false':
50
- return False, idx + 5
51
-
52
- m = match_number(string, idx)
53
- if m is not None:
54
- integer, frac, exp = m.groups()
55
- if frac or exp:
56
- res = parse_float(integer + (frac or '') + (exp or ''))
57
- else:
58
- res = parse_int(integer)
59
- return res, m.end()
60
- elif nextchar == 'N' and string[idx:idx + 3] == 'NaN':
61
- return parse_constant('NaN'), idx + 3
62
- elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity':
63
- return parse_constant('Infinity'), idx + 8
64
- elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
65
- return parse_constant('-Infinity'), idx + 9
66
- else:
67
- raise StopIteration
68
-
69
- def scan_once(string, idx):
70
- try:
71
- return _scan_once(string, idx)
72
- finally:
73
- memo.clear()
74
-
75
- return scan_once
76
-
77
- make_scanner = c_make_scanner or py_make_scanner
@@ -1,39 +0,0 @@
1
- r"""Command-line tool to validate and pretty-print JSON
2
-
3
- Usage::
4
-
5
- $ echo '{"json":"obj"}' | python -m simplejson.tool
6
- {
7
- "json": "obj"
8
- }
9
- $ echo '{ 1.2:3.4}' | python -m simplejson.tool
10
- Expecting property name: line 1 column 2 (char 2)
11
-
12
- """
13
- import sys
14
- import simplejson as json
15
-
16
- def main():
17
- if len(sys.argv) == 1:
18
- infile = sys.stdin
19
- outfile = sys.stdout
20
- elif len(sys.argv) == 2:
21
- infile = open(sys.argv[1], 'rb')
22
- outfile = sys.stdout
23
- elif len(sys.argv) == 3:
24
- infile = open(sys.argv[1], 'rb')
25
- outfile = open(sys.argv[2], 'wb')
26
- else:
27
- raise SystemExit(sys.argv[0] + " [infile [outfile]]")
28
- try:
29
- obj = json.load(infile,
30
- object_pairs_hook=json.OrderedDict,
31
- use_decimal=True)
32
- except ValueError, e:
33
- raise SystemExit(e)
34
- json.dump(obj, outfile, sort_keys=True, indent=' ', use_decimal=True)
35
- outfile.write('\n')
36
-
37
-
38
- if __name__ == '__main__':
39
- main()
@@ -1,2565 +0,0 @@
1
- #!/usr/bin/env python
2
- # coding: utf-8
3
- #
4
- # Copyright 2007 Google Inc.
5
- #
6
- # Licensed under the Apache License, Version 2.0 (the "License");
7
- # you may not use this file except in compliance with the License.
8
- # You may obtain a copy of the License at
9
- #
10
- # http://www.apache.org/licenses/LICENSE-2.0
11
- #
12
- # Unless required by applicable law or agreed to in writing, software
13
- # distributed under the License is distributed on an "AS IS" BASIS,
14
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- # See the License for the specific language governing permissions and
16
- # limitations under the License.
17
-
18
- """Tool for uploading diffs from a version control system to the codereview app.
19
-
20
- Usage summary: upload.py [options] [-- diff_options] [path...]
21
-
22
- Diff options are passed to the diff command of the underlying system.
23
-
24
- Supported version control systems:
25
- Git
26
- Mercurial
27
- Subversion
28
- Perforce
29
- CVS
30
-
31
- It is important for Git/Mercurial users to specify a tree/node/branch to diff
32
- against by using the '--rev' option.
33
- """
34
- # This code is derived from appcfg.py in the App Engine SDK (open source),
35
- # and from ASPN recipe #146306.
36
-
37
- from __future__ import print_function
38
-
39
- import cookielib
40
- import errno
41
- import fnmatch
42
- import getpass
43
- import logging
44
- import marshal
45
- import mimetypes
46
- import optparse
47
- import os
48
- import re
49
- import socket
50
- import subprocess
51
- import sys
52
- import urllib
53
- import urllib2
54
- import urlparse
55
-
56
- from multiprocessing.pool import ThreadPool
57
-
58
- import appengine_mapper
59
-
60
- # The configparser module was renamed in Python 3.
61
- try:
62
- import configparser
63
- except ImportError:
64
- import ConfigParser as configparser
65
-
66
- # The md5 module was deprecated in Python 2.5.
67
- try:
68
- from hashlib import md5
69
- except ImportError:
70
- from md5 import md5
71
-
72
- try:
73
- import readline
74
- except ImportError:
75
- pass
76
-
77
- try:
78
- import keyring
79
- except:
80
- keyring = None
81
-
82
- # auth.py is a part of depot_tools.
83
- # TODO(vadimsh): Merge upload.py into depot_tools
84
- import auth
85
-
86
- # The logging verbosity:
87
- # 0: Errors only.
88
- # 1: Status messages.
89
- # 2: Info logs.
90
- # 3: Debug logs.
91
- verbosity = 1
92
- LOGGER = logging.getLogger('upload')
93
-
94
- # The account type used for authentication.
95
- # This line could be changed by the review server (see handler for
96
- # upload.py).
97
- AUTH_ACCOUNT_TYPE = "GOOGLE"
98
-
99
- # URL of the default review server. As for AUTH_ACCOUNT_TYPE, this line could be
100
- # changed by the review server (see handler for upload.py).
101
- DEFAULT_REVIEW_SERVER = "codereview.appspot.com"
102
-
103
- # Max size of patch or base file.
104
- MAX_UPLOAD_SIZE = 900 * 1024
105
-
106
-
107
- # Constants for version control names. Used by GuessVCSName.
108
- VCS_GIT = "Git"
109
- VCS_MERCURIAL = "Mercurial"
110
- VCS_SUBVERSION = "Subversion"
111
- VCS_PERFORCE = "Perforce"
112
- VCS_CVS = "CVS"
113
- VCS_UNKNOWN = "Unknown"
114
-
115
- VCS = [
116
- {'name': VCS_MERCURIAL,
117
- 'aliases': ['hg', 'mercurial']},
118
- {'name': VCS_SUBVERSION,
119
- 'aliases': ['svn', 'subversion'],},
120
- {'name': VCS_PERFORCE,
121
- 'aliases': ['p4', 'perforce']},
122
- {'name': VCS_GIT,
123
- 'aliases': ['git']},
124
- {'name': VCS_CVS,
125
- 'aliases': ['cvs']},
126
- ]
127
-
128
-
129
- VCS_SHORT_NAMES = [] # hg, svn, ...
130
- VCS_ABBREVIATIONS = {} # alias: name, ...
131
- for vcs in VCS:
132
- VCS_SHORT_NAMES.append(min(vcs['aliases'], key=len))
133
- VCS_ABBREVIATIONS.update((alias, vcs['name']) for alias in vcs['aliases'])
134
-
135
- UPLOAD_TIMEOUT = 120
136
- MAX_UPLOAD_ATTEMPTS = 3
137
-
138
-
139
- # The result of parsing Subversion's [auto-props] setting.
140
- svn_auto_props_map = None
141
-
142
- def GetEmail(prompt):
143
- """Prompts the user for their email address and returns it.
144
-
145
- The last used email address is saved to a file and offered up as a suggestion
146
- to the user. If the user presses enter without typing in anything the last
147
- used email address is used. If the user enters a new address, it is saved
148
- for next time we prompt.
149
-
150
- """
151
- last_email_file_name = os.path.expanduser("~/.last_codereview_email_address")
152
- last_email = ""
153
- if os.path.exists(last_email_file_name):
154
- try:
155
- last_email_file = open(last_email_file_name, "r")
156
- last_email = last_email_file.readline().strip("\n")
157
- last_email_file.close()
158
- prompt += " [%s]" % last_email
159
- except IOError as e:
160
- pass
161
- email = raw_input(prompt + ": ").strip()
162
- if email:
163
- try:
164
- last_email_file = open(last_email_file_name, "w")
165
- last_email_file.write(email)
166
- last_email_file.close()
167
- except IOError as e:
168
- pass
169
- else:
170
- email = last_email
171
- return email
172
-
173
-
174
- def StatusUpdate(msg):
175
- """Print a status message to stdout.
176
-
177
- If 'verbosity' is greater than 0, print the message.
178
-
179
- Args:
180
- msg: The string to print.
181
- """
182
- if verbosity > 0:
183
- print(msg)
184
-
185
-
186
- def ErrorExit(msg):
187
- """Print an error message to stderr and exit."""
188
- print(msg, file=sys.stderr)
189
- sys.exit(1)
190
-
191
-
192
- class ClientLoginError(urllib2.HTTPError):
193
- """Raised to indicate there was an error authenticating with ClientLogin."""
194
-
195
- def __init__(self, url, code, msg, headers, args):
196
- urllib2.HTTPError.__init__(self, url, code, msg, headers, None)
197
- self.args = args
198
- self._reason = args["Error"]
199
- self.info = args.get("Info", None)
200
-
201
- @property
202
- def reason(self):
203
- # reason is a property on python 2.7 but a member variable on <=2.6.
204
- # self.args is modified so it cannot be used as-is so save the value in
205
- # self._reason.
206
- return self._reason
207
-
208
-
209
- class AbstractRpcServer(object):
210
- """Provides a common interface for a simple RPC server."""
211
-
212
- def __init__(self, host, auth_function, host_override=None,
213
- request_path_prefix=None, extra_headers=None,
214
- save_cookies=False, account_type=AUTH_ACCOUNT_TYPE):
215
- """Creates a new AbstractRpcServer.
216
-
217
- Args:
218
- host: The host to send requests to.
219
- auth_function: A function that takes no arguments and returns an
220
- (email, password) tuple when called. Will be called if authentication
221
- is required.
222
- host_override: The host header to send to the server (defaults to host).
223
- request_path_prefix: A string to prefix all URL paths with (e.g. 'bots/').
224
- extra_headers: A dict of extra headers to append to every request.
225
- save_cookies: If True, save the authentication cookies to local disk.
226
- If False, use an in-memory cookiejar instead. Subclasses must
227
- implement this functionality. Defaults to False.
228
- account_type: Account type used for authentication. Defaults to
229
- AUTH_ACCOUNT_TYPE.
230
- """
231
- self.host = host
232
- if (not self.host.startswith("http://") and
233
- not self.host.startswith("https://")):
234
- self.host = "http://" + self.host
235
- self.host_override = host_override
236
- self.request_path_prefix = request_path_prefix or ''
237
- self.auth_function = auth_function
238
- self.authenticated = False
239
- self.extra_headers = extra_headers or {}
240
- self.save_cookies = save_cookies
241
- self.account_type = account_type
242
- self.opener = self._GetOpener()
243
- if self.host_override:
244
- LOGGER.info("Server: %s; Host: %s", self.host, self.host_override)
245
- else:
246
- LOGGER.info("Server: %s", self.host)
247
-
248
- def _GetOpener(self):
249
- """Returns an OpenerDirector for making HTTP requests.
250
-
251
- Returns:
252
- A urllib2.OpenerDirector object.
253
- """
254
- raise NotImplementedError()
255
-
256
- def _CreateRequest(self, url, data=None):
257
- """Creates a new urllib request."""
258
- LOGGER.debug("Creating request for: '%s' with payload:\n%s", url, data)
259
- req = urllib2.Request(url, data=data, headers={"Accept": "text/plain"})
260
- if self.host_override:
261
- req.add_header("Host", self.host_override)
262
- for key, value in self.extra_headers.iteritems():
263
- req.add_header(key, value)
264
- return req
265
-
266
- def _GetAuthToken(self, email, password, internal=False):
267
- """Uses ClientLogin to authenticate the user, returning an auth token.
268
-
269
- Args:
270
- email: The user's email address
271
- password: The user's password
272
-
273
- Raises:
274
- ClientLoginError: If there was an error authenticating with ClientLogin.
275
- HTTPError: If there was some other form of HTTP error.
276
-
277
- Returns:
278
- The authentication token returned by ClientLogin.
279
- """
280
- account_type = self.account_type
281
- if self.host.endswith(".google.com"):
282
- # Needed for use inside Google.
283
- account_type = "HOSTED"
284
- service = ('ClientLogin') if not internal else ('ClientAuth')
285
- req = self._CreateRequest(
286
- url="https://www.google.com/accounts/%s" % (service,),
287
- data=urllib.urlencode({
288
- "Email": email,
289
- "Passwd": password,
290
- "service": "ah",
291
- "source": "rietveld-codereview-upload",
292
- "accountType": account_type,
293
- }),
294
- )
295
- try:
296
- response = self.opener.open(req)
297
- response_body = response.read()
298
- response_dict = dict(x.split("=")
299
- for x in response_body.split("\n") if x)
300
- return response_dict["Auth"]
301
- except urllib2.HTTPError as e:
302
- if e.code == 403:
303
- body = e.read()
304
- response_dict = dict(x.split("=", 1) for x in body.split("\n") if x)
305
- raise ClientLoginError(req.get_full_url(), e.code, e.msg,
306
- e.headers, response_dict)
307
- else:
308
- raise
309
-
310
- def _GetAuthCookie(self, auth_token):
311
- """Fetches authentication cookies for an authentication token.
312
-
313
- Args:
314
- auth_token: The authentication token returned by ClientLogin.
315
-
316
- Raises:
317
- HTTPError: If there was an error fetching the authentication cookies.
318
- """
319
- # This is a dummy value to allow us to identify when we're successful.
320
- continue_location = "http://localhost/"
321
- args = {"continue": continue_location, "auth": auth_token}
322
- req = self._CreateRequest("%s/_ah/login?%s" %
323
- (self.host, urllib.urlencode(args)))
324
- try:
325
- response = self.opener.open(req)
326
- except urllib2.HTTPError as e:
327
- response = e
328
- if (response.code != 302 or
329
- response.info()["location"] != continue_location):
330
- raise urllib2.HTTPError(req.get_full_url(), response.code, response.msg,
331
- response.headers, response.fp)
332
- self.authenticated = True
333
-
334
- def _Authenticate(self, force_refresh):
335
- """Authenticates the user.
336
-
337
- The authentication process works as follows:
338
- 1) We get a username and password from the user
339
- 2) We use ClientLogin to obtain an AUTH token for the user
340
- (see http://code.google.com/apis/accounts/AuthForInstalledApps.html).
341
- 3) We pass the auth token to /_ah/login on the server to obtain an
342
- authentication cookie. If login was successful, it tries to redirect
343
- us to the URL we provided.
344
-
345
- If we attempt to access the upload API without first obtaining an
346
- authentication cookie, it returns a 401 response (or a 302) and
347
- directs us to authenticate ourselves with ClientLogin.
348
- """
349
- for i in range(3):
350
- credentials = self.auth_function()
351
-
352
- # Try external, then internal.
353
- e = None
354
- error_map = None
355
- try:
356
- auth_token = self._GetAuthToken(credentials[0], credentials[1])
357
- except urllib2.HTTPError:
358
- try:
359
- # Try internal endpoint.
360
- error_map = {
361
- "badauth": "BadAuthentication",
362
- "cr": "CaptchaRequired",
363
- "adel": "AccountDeleted",
364
- "adis": "AccountDisabled",
365
- "sdis": "ServiceDisabled",
366
- "ire": "ServiceUnavailable",
367
- }
368
- auth_token = self._GetAuthToken(credentials[0], credentials[1],
369
- internal=True)
370
- except ClientLoginError as exc:
371
- e = exc
372
- if e:
373
- print('', file=sys.stderr)
374
- error_message = e.reason
375
- if error_map:
376
- error_message = error_map.get(error_message, error_message)
377
- if error_message == "BadAuthentication":
378
- if e.info == "InvalidSecondFactor":
379
- print >> sys.stderr, (
380
- "Use an application-specific password instead "
381
- "of your regular account password.\n"
382
- "See http://www.google.com/"
383
- "support/accounts/bin/answer.py?answer=185833")
384
- else:
385
- print("Invalid username or password.", file=sys.stderr)
386
- elif error_message == "CaptchaRequired":
387
- print >> sys.stderr, (
388
- "Please go to\n"
389
- "https://www.google.com/accounts/DisplayUnlockCaptcha\n"
390
- "and verify you are a human. Then try again.\n"
391
- "If you are using a Google Apps account the URL is:\n"
392
- "https://www.google.com/a/yourdomain.com/UnlockCaptcha")
393
- elif error_message == "NotVerified":
394
- print("Account not verified.", file=sys.stderr)
395
- elif error_message == "TermsNotAgreed":
396
- print("User has not agreed to TOS.", file=sys.stderr)
397
- elif error_message == "AccountDeleted":
398
- print("The user account has been deleted.", file=sys.stderr)
399
- elif error_message == "AccountDisabled":
400
- print("The user account has been disabled.", file=sys.stderr)
401
- break
402
- elif error_message == "ServiceDisabled":
403
- print("The user's access to the service has been disabled.",
404
- file=sys.stderr)
405
- elif error_message == "ServiceUnavailable":
406
- print("The service is not available; try again later.",
407
- file=sys.stderr)
408
- else:
409
- # Unknown error.
410
- raise e
411
- print('', file=sys.stderr)
412
- continue
413
- self._GetAuthCookie(auth_token)
414
- return
415
-
416
- def Send(self, request_path, payload=None,
417
- content_type="application/octet-stream",
418
- timeout=None,
419
- extra_headers=None,
420
- **kwargs):
421
- """Sends an RPC and returns the response.
422
-
423
- Args:
424
- request_path: The path to send the request to, eg /api/appversion/create.
425
- payload: The body of the request, or None to send an empty request.
426
- content_type: The Content-Type header to use.
427
- timeout: timeout in seconds; default None i.e. no timeout.
428
- (Note: for large requests on OS X, the timeout doesn't work right.)
429
- extra_headers: Dict containing additional HTTP headers that should be
430
- included in the request (string header names mapped to their values),
431
- or None to not include any additional headers.
432
- kwargs: Any keyword arguments are converted into query string parameters.
433
-
434
- Returns:
435
- The response body, as a string.
436
- """
437
- # TODO: Don't require authentication. Let the server say
438
- # whether it is necessary.
439
- if not self.authenticated and self.auth_function:
440
- self._Authenticate(force_refresh=False)
441
-
442
- old_timeout = socket.getdefaulttimeout()
443
- socket.setdefaulttimeout(timeout)
444
- auth_attempted = False
445
- try:
446
- tries = 0
447
- while True:
448
- tries += 1
449
- args = dict(kwargs)
450
- url = "%s%s%s" % (self.host, self.request_path_prefix, request_path)
451
- url = appengine_mapper.MapUrl(url)
452
- if args:
453
- url += "?" + urllib.urlencode(args)
454
- req = self._CreateRequest(url=url, data=payload)
455
- req.add_header("Content-Type", content_type)
456
- if extra_headers:
457
- for header, value in extra_headers.items():
458
- req.add_header(header, value)
459
- try:
460
- f = self.opener.open(req, timeout=70)
461
- response = f.read()
462
- f.close()
463
- return response
464
- except urllib2.HTTPError as e:
465
- if tries > 3:
466
- raise
467
- elif e.code in (302, 401, 403):
468
- if not self.auth_function:
469
- raise
470
- # Already tried force refresh, didn't help -> give up with error.
471
- if auth_attempted:
472
- raise auth.AuthenticationError(
473
- 'Access to %s is denied (server returned HTTP %d).'
474
- % (self.host, e.code))
475
- self._Authenticate(force_refresh=True)
476
- auth_attempted = True
477
- elif e.code == 301:
478
- # Handle permanent redirect manually.
479
- url = e.info()["location"]
480
- url_loc = urlparse.urlparse(url)
481
- self.host = '%s://%s' % (url_loc[0], url_loc[1])
482
- elif e.code >= 500:
483
- # TODO: We should error out on a 500, but the server is too flaky
484
- # for that at the moment.
485
- StatusUpdate('Upload got a 500 response: %d' % e.code)
486
- else:
487
- raise
488
- finally:
489
- socket.setdefaulttimeout(old_timeout)
490
-
491
-
492
- class HttpRpcServer(AbstractRpcServer):
493
- """Provides a simplified RPC-style interface for HTTP requests."""
494
-
495
- def _Authenticate(self, force_refresh):
496
- """Save the cookie jar after authentication."""
497
- if isinstance(self.auth_function, auth.Authenticator):
498
- try:
499
- access_token = self.auth_function.get_access_token(force_refresh)
500
- except auth.LoginRequiredError:
501
- # Attempt to make unauthenticated request first if there's no cached
502
- # credentials. HttpRpcServer calls __Authenticate(force_refresh=True)
503
- # again if unauthenticated request doesn't work.
504
- if not force_refresh:
505
- return
506
- raise
507
- self.extra_headers['Authorization'] = 'Bearer %s' % (
508
- access_token.token,)
509
- else:
510
- super(HttpRpcServer, self)._Authenticate(force_refresh)
511
- if self.save_cookies:
512
- StatusUpdate("Saving authentication cookies to %s" % self.cookie_file)
513
- self.cookie_jar.save()
514
-
515
- def _GetOpener(self):
516
- """Returns an OpenerDirector that supports cookies and ignores redirects.
517
-
518
- Returns:
519
- A urllib2.OpenerDirector object.
520
- """
521
- opener = urllib2.OpenerDirector()
522
- opener.add_handler(urllib2.ProxyHandler())
523
- opener.add_handler(urllib2.UnknownHandler())
524
- opener.add_handler(urllib2.HTTPHandler())
525
- opener.add_handler(urllib2.HTTPDefaultErrorHandler())
526
- opener.add_handler(urllib2.HTTPSHandler())
527
- opener.add_handler(urllib2.HTTPErrorProcessor())
528
- if self.save_cookies:
529
- self.cookie_file = os.path.expanduser("~/.codereview_upload_cookies")
530
- self.cookie_jar = cookielib.MozillaCookieJar(self.cookie_file)
531
- if os.path.exists(self.cookie_file):
532
- try:
533
- self.cookie_jar.load()
534
- self.authenticated = True
535
- StatusUpdate("Loaded authentication cookies from %s" %
536
- self.cookie_file)
537
- except (cookielib.LoadError, IOError):
538
- # Failed to load cookies - just ignore them.
539
- pass
540
- else:
541
- # Create an empty cookie file with mode 600
542
- fd = os.open(self.cookie_file, os.O_CREAT, 0o600)
543
- os.close(fd)
544
- # Always chmod the cookie file
545
- os.chmod(self.cookie_file, 0o600)
546
- else:
547
- # Don't save cookies across runs of update.py.
548
- self.cookie_jar = cookielib.CookieJar()
549
- opener.add_handler(urllib2.HTTPCookieProcessor(self.cookie_jar))
550
- return opener
551
-
552
-
553
- class CondensedHelpFormatter(optparse.IndentedHelpFormatter):
554
- """Frees more horizontal space by removing indentation from group
555
- options and collapsing arguments between short and long, e.g.
556
- '-o ARG, --opt=ARG' to -o --opt ARG"""
557
-
558
- def format_heading(self, heading):
559
- return "%s:\n" % heading
560
-
561
- def format_option(self, option):
562
- self.dedent()
563
- res = optparse.HelpFormatter.format_option(self, option)
564
- self.indent()
565
- return res
566
-
567
- def format_option_strings(self, option):
568
- self.set_long_opt_delimiter(" ")
569
- optstr = optparse.HelpFormatter.format_option_strings(self, option)
570
- optlist = optstr.split(", ")
571
- if len(optlist) > 1:
572
- if option.takes_value():
573
- # strip METAVAR from all but the last option
574
- optlist = [x.split()[0] for x in optlist[:-1]] + optlist[-1:]
575
- optstr = " ".join(optlist)
576
- return optstr
577
-
578
-
579
- parser = optparse.OptionParser(
580
- usage=("%prog [options] [-- diff_options] [path...]\n"
581
- "See also: http://code.google.com/p/rietveld/wiki/UploadPyUsage"),
582
- add_help_option=False,
583
- formatter=CondensedHelpFormatter()
584
- )
585
- parser.add_option("-h", "--help", action="store_true",
586
- help="Show this help message and exit.")
587
- parser.add_option("-y", "--assume_yes", action="store_true",
588
- dest="assume_yes", default=False,
589
- help="Assume that the answer to yes/no questions is 'yes'.")
590
- # Logging
591
- group = parser.add_option_group("Logging options")
592
- group.add_option("-q", "--quiet", action="store_const", const=0,
593
- dest="verbose", help="Print errors only.")
594
- group.add_option("-v", "--verbose", action="store_const", const=2,
595
- dest="verbose", default=1,
596
- help="Print info level logs.")
597
- group.add_option("--noisy", action="store_const", const=3,
598
- dest="verbose", help="Print all logs.")
599
- group.add_option("--print_diffs", dest="print_diffs", action="store_true",
600
- help="Print full diffs.")
601
- # Review server
602
- group = parser.add_option_group("Review server options")
603
- group.add_option("-s", "--server", action="store", dest="server",
604
- default=DEFAULT_REVIEW_SERVER,
605
- metavar="SERVER",
606
- help=("The server to upload to. The format is host[:port]. "
607
- "Defaults to '%default'."))
608
- group.add_option("-e", "--email", action="store", dest="email",
609
- metavar="EMAIL", default=None,
610
- help="The username to use. Will prompt if omitted.")
611
- group.add_option("-j", "--number-parallel-uploads",
612
- dest="num_upload_threads", default=8,
613
- help="Number of uploads to do in parallel.")
614
- # Authentication
615
- auth.add_auth_options(parser)
616
- # Issue
617
- group = parser.add_option_group("Issue options")
618
- group.add_option("-t", "--title", action="store", dest="title",
619
- help="New issue subject or new patch set title")
620
- group.add_option("--project", action="store", dest="project",
621
- help="The project the issue belongs to")
622
- group.add_option("-m", "--message", action="store", dest="message",
623
- default=None,
624
- help="New issue description or new patch set message")
625
- group.add_option("-F", "--file", action="store", dest="file",
626
- default=None, help="Read the message above from file.")
627
- group.add_option("-r", "--reviewers", action="store", dest="reviewers",
628
- metavar="REVIEWERS", default=None,
629
- help="Add reviewers (comma separated email addresses).")
630
- group.add_option("--cc", action="store", dest="cc",
631
- metavar="CC", default=None,
632
- help="Add CC (comma separated email addresses).")
633
- group.add_option("--private", action="store_true", dest="private",
634
- default=False,
635
- help="Make the issue restricted to reviewers and those CCed")
636
- # Upload options
637
- group = parser.add_option_group("Patch options")
638
- group.add_option("-i", "--issue", type="int", action="store",
639
- metavar="ISSUE", default=None,
640
- help="Issue number to which to add. Defaults to new issue.")
641
- group.add_option("--target_ref", action="store", dest="target_ref",
642
- default=None,
643
- help="The target ref that is transitively tracked by the "
644
- "local branch this patch comes from.")
645
- parser.add_option("--cq_dry_run", action="store_true",
646
- help="Send the patchset to do a CQ dry run right after "
647
- "upload.")
648
- parser.add_option("--depends_on_patchset", action="store",
649
- dest="depends_on_patchset",
650
- help="The uploaded patchset this patchset depends on. The "
651
- "value will be in this format- issue_num:patchset_num")
652
- group.add_option("--download_base", action="store_true",
653
- dest="download_base", default=False,
654
- help="Base files will be downloaded by the server "
655
- "(side-by-side diffs may not work on files with CRs).")
656
- group.add_option("--rev", action="store", dest="revision",
657
- metavar="REV", default=None,
658
- help="Base revision/branch/tree to diff against. Use "
659
- "rev1:rev2 range to review already committed changeset.")
660
- group.add_option("--send_mail", action="store_true",
661
- dest="send_mail", default=False,
662
- help="Send notification email to reviewers.")
663
- group.add_option("-p", "--send_patch", action="store_true",
664
- dest="send_patch", default=False,
665
- help="Same as --send_mail, but include diff as an "
666
- "attachment, and prepend email subject with 'PATCH:'.")
667
- group.add_option("--vcs", action="store", dest="vcs",
668
- metavar="VCS", default=None,
669
- help=("Explicitly specify version control system (%s)"
670
- % ", ".join(VCS_SHORT_NAMES)))
671
- group.add_option("--emulate_svn_auto_props", action="store_true",
672
- dest="emulate_svn_auto_props", default=False,
673
- help=("Emulate Subversion's auto properties feature."))
674
- # Git-specific
675
- group = parser.add_option_group("Git-specific options")
676
- group.add_option("--git_similarity", action="store", dest="git_similarity",
677
- metavar="SIM", type="int", default=50,
678
- help=("Set the minimum similarity percentage for detecting "
679
- "renames and copies. See `git diff -C`. (default 50)."))
680
- group.add_option("--git_find_copies_harder", action="store_true", default=False,
681
- dest='git_find_copies_harder',
682
- help="Adds --find-copies-harder when seaching for copies")
683
- group.add_option("--git_no_find_copies", action="store_false", default=True,
684
- dest="git_find_copies",
685
- help=("Prevents git from looking for copies (default off)."))
686
- # Perforce-specific
687
- group = parser.add_option_group("Perforce-specific options "
688
- "(overrides P4 environment variables)")
689
- group.add_option("--p4_port", action="store", dest="p4_port",
690
- metavar="P4_PORT", default=None,
691
- help=("Perforce server and port (optional)"))
692
- group.add_option("--p4_changelist", action="store", dest="p4_changelist",
693
- metavar="P4_CHANGELIST", default=None,
694
- help=("Perforce changelist id"))
695
- group.add_option("--p4_client", action="store", dest="p4_client",
696
- metavar="P4_CLIENT", default=None,
697
- help=("Perforce client/workspace"))
698
- group.add_option("--p4_user", action="store", dest="p4_user",
699
- metavar="P4_USER", default=None,
700
- help=("Perforce user"))
701
-
702
-
703
- class KeyringCreds(object):
704
- def __init__(self, server, host, email):
705
- self.server = server
706
- # Explicitly cast host to str to work around bug in old versions of Keyring
707
- # (versions before 0.10). Even though newer versions of Keyring fix this,
708
- # some modern linuxes (such as Ubuntu 12.04) still bundle a version with
709
- # the bug.
710
- self.host = str(host)
711
- self.email = email
712
- self.accounts_seen = set()
713
-
714
- def GetUserCredentials(self):
715
- """Prompts the user for a username and password.
716
-
717
- Only use keyring on the initial call. If the keyring contains the wrong
718
- password, we want to give the user a chance to enter another one.
719
- """
720
- # Create a local alias to the email variable to avoid Python's crazy
721
- # scoping rules.
722
- global keyring
723
- email = self.email
724
- if email is None:
725
- email = GetEmail("Email (login for uploading to %s)" % self.server)
726
- password = None
727
- if keyring and not email in self.accounts_seen:
728
- try:
729
- password = keyring.get_password(self.host, email)
730
- except:
731
- # Sadly, we have to trap all errors here as
732
- # gnomekeyring.IOError inherits from object. :/
733
- print("Failed to get password from keyring")
734
- keyring = None
735
- if password is not None:
736
- print("Using password from system keyring.")
737
- self.accounts_seen.add(email)
738
- else:
739
- password = getpass.getpass("Password for %s: " % email)
740
- if keyring:
741
- answer = raw_input("Store password in system keyring?(y/N) ").strip()
742
- if answer == "y":
743
- keyring.set_password(self.host, email, password)
744
- self.accounts_seen.add(email)
745
- return (email, password)
746
-
747
-
748
- def GetRpcServer(server, auth_config=None, email=None):
749
- """Returns an instance of an AbstractRpcServer.
750
-
751
- Args:
752
- server: String containing the review server URL.
753
- auth_config: auth.AuthConfig tuple with OAuth2 configuration.
754
- email: String containing user's email address [deprecated].
755
-
756
- Returns:
757
- A new HttpRpcServer, on which RPC calls can be made.
758
- """
759
- # If email is given as an empty string or no auth config is passed, then
760
- # assume we want to make requests that do not need authentication. Bypass
761
- # authentication by setting the auth_function to None.
762
- if email == '' or not auth_config:
763
- return HttpRpcServer(server, None)
764
-
765
- # If this is the dev_appserver, use fake authentication.
766
- host = server.lower()
767
- if re.match(r'(http://)?localhost([:/]|$)', host):
768
- if email is None:
769
- email = "test@example.com"
770
- LOGGER.info("Using debug user %s. Override with --email" % email)
771
- server = HttpRpcServer(
772
- server,
773
- lambda: (email, "password"),
774
- extra_headers={"Cookie":
775
- 'dev_appserver_login="%s:False"' % email},
776
- save_cookies=auth_config.save_cookies,
777
- account_type=AUTH_ACCOUNT_TYPE)
778
- # Don't try to talk to ClientLogin.
779
- server.authenticated = True
780
- return server
781
-
782
- if auth_config.use_oauth2:
783
- auth_func = auth.get_authenticator_for_host(server, auth_config)
784
- else:
785
- auth_func = KeyringCreds(server, host, email).GetUserCredentials
786
-
787
- # HACK(crbug.com/476690): Internal Rietveld is configured to require cookie
788
- # auth for all paths except /bots/* (requests to /bots/* are authenticated
789
- # with OAuth). /bots/* paths expose exact same API as /* (at least enough of
790
- # it for depot_tools to work). So when using OAuth with internal Rietveld,
791
- # silently prefix all requests with '/bots'.
792
- request_path_prefix = ''
793
- if auth_config.use_oauth2:
794
- if not host.startswith(('http://', 'https://')):
795
- host = 'https://' + host
796
- parsed = urlparse.urlparse(host)
797
- if parsed.netloc.endswith('.googleplex.com'):
798
- request_path_prefix = '/bots'
799
-
800
- return HttpRpcServer(
801
- server,
802
- auth_func,
803
- request_path_prefix=request_path_prefix,
804
- save_cookies=auth_config.save_cookies,
805
- account_type=AUTH_ACCOUNT_TYPE)
806
-
807
-
808
- def EncodeMultipartFormData(fields, files):
809
- """Encode form fields for multipart/form-data.
810
-
811
- Args:
812
- fields: A sequence of (name, value) elements for regular form fields.
813
- files: A sequence of (name, filename, value) elements for data to be
814
- uploaded as files.
815
- Returns:
816
- (content_type, body) ready for httplib.HTTP instance.
817
-
818
- Source:
819
- http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306
820
- """
821
- BOUNDARY = '-M-A-G-I-C---B-O-U-N-D-A-R-Y-%s-' % sum(hash(f) for f in files)
822
- CRLF = '\r\n'
823
- lines = []
824
- for (key, value) in fields:
825
- lines.append('--' + BOUNDARY)
826
- lines.append('Content-Disposition: form-data; name="%s"' % key)
827
- lines.append('')
828
- if isinstance(value, unicode):
829
- value = value.encode('utf-8')
830
- lines.append(value)
831
- for (key, filename, value) in files:
832
- lines.append('--' + BOUNDARY)
833
- lines.append('Content-Disposition: form-data; name="%s"; filename="%s"' %
834
- (key, filename))
835
- lines.append('Content-Type: %s' % GetContentType(filename))
836
- lines.append('')
837
- if isinstance(value, unicode):
838
- value = value.encode('utf-8')
839
- lines.append(value)
840
- lines.append('--' + BOUNDARY + '--')
841
- lines.append('')
842
- body = CRLF.join(lines)
843
- content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
844
- return content_type, body
845
-
846
-
847
- def GetContentType(filename):
848
- """Helper to guess the content-type from the filename."""
849
- return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
850
-
851
-
852
- # Use a shell for subcommands on Windows to get a PATH search.
853
- use_shell = sys.platform.startswith("win")
854
-
855
- def RunShellWithReturnCodeAndStderr(command, universal_newlines=True,
856
- env=os.environ):
857
- """Run a command and return output from stdout, stderr and the return code.
858
-
859
- Args:
860
- command: Command to execute.
861
- universal_newlines: Use universal_newlines flag (default: True).
862
-
863
- Returns:
864
- Tuple (stdout, stderr, return code)
865
- """
866
- LOGGER.info("Running %s", command)
867
- env = env.copy()
868
- env['LC_MESSAGES'] = 'C'
869
- p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
870
- shell=use_shell, universal_newlines=universal_newlines,
871
- env=env)
872
- output, errout = p.communicate()
873
- p.stdout.close()
874
- p.stderr.close()
875
- return output, errout, p.returncode
876
-
877
- def RunShellWithReturnCode(command, universal_newlines=True, env=os.environ):
878
- """Run a command and return output from stdout and the return code."""
879
- out, err, retcode = RunShellWithReturnCodeAndStderr(command,
880
- universal_newlines, env)
881
- return out, retcode
882
-
883
- def RunShell(command, silent_ok=False, universal_newlines=True,
884
- env=os.environ):
885
- data, retcode = RunShellWithReturnCode(command, universal_newlines, env)
886
- if retcode:
887
- ErrorExit("Got error status from %s:\n%s" % (command, data))
888
- if not silent_ok and not data:
889
- ErrorExit("No output from %s" % command)
890
- return data
891
-
892
-
893
- class VersionControlSystem(object):
894
- """Abstract base class providing an interface to the VCS."""
895
-
896
- def __init__(self, options):
897
- """Constructor.
898
-
899
- Args:
900
- options: Command line options.
901
- """
902
- self.options = options
903
-
904
- def GetGUID(self):
905
- """Return string to distinguish the repository from others, for example to
906
- query all opened review issues for it"""
907
- raise NotImplementedError(
908
- "abstract method -- subclass %s must override" % self.__class__)
909
-
910
- def PostProcessDiff(self, diff):
911
- """Return the diff with any special post processing this VCS needs, e.g.
912
- to include an svn-style "Index:"."""
913
- return diff
914
-
915
- def GenerateDiff(self, args):
916
- """Return the current diff as a string.
917
-
918
- Args:
919
- args: Extra arguments to pass to the diff command.
920
- """
921
- raise NotImplementedError(
922
- "abstract method -- subclass %s must override" % self.__class__)
923
-
924
- def GetUnknownFiles(self):
925
- """Return a list of files unknown to the VCS."""
926
- raise NotImplementedError(
927
- "abstract method -- subclass %s must override" % self.__class__)
928
-
929
- def CheckForUnknownFiles(self):
930
- """Show an "are you sure?" prompt if there are unknown files."""
931
- unknown_files = self.GetUnknownFiles()
932
- if unknown_files:
933
- print("The following files are not added to version control:")
934
- for line in unknown_files:
935
- print(line)
936
- prompt = "Are you sure to continue?(y/N) "
937
- answer = raw_input(prompt).strip()
938
- if answer != "y":
939
- ErrorExit("User aborted")
940
-
941
- def GetBaseFile(self, filename):
942
- """Get the content of the upstream version of a file.
943
-
944
- Returns:
945
- A tuple (base_content, new_content, is_binary, status)
946
- base_content: The contents of the base file.
947
- new_content: For text files, this is empty. For binary files, this is
948
- the contents of the new file, since the diff output won't contain
949
- information to reconstruct the current file.
950
- is_binary: True iff the file is binary.
951
- status: The status of the file.
952
- """
953
-
954
- raise NotImplementedError(
955
- "abstract method -- subclass %s must override" % self.__class__)
956
-
957
- def GetBaseFiles(self, diff):
958
- """Helper that calls GetBase file for each file in the patch.
959
-
960
- Returns:
961
- A dictionary that maps from filename to GetBaseFile's tuple. Filenames
962
- are retrieved based on lines that start with "Index:" or
963
- "Property changes on:".
964
- """
965
- files = {}
966
- for line in diff.splitlines(True):
967
- if line.startswith('Index:') or line.startswith('Property changes on:'):
968
- unused, filename = line.split(':', 1)
969
- # On Windows if a file has property changes its filename uses '\'
970
- # instead of '/'.
971
- filename = filename.strip().replace('\\', '/')
972
- files[filename] = self.GetBaseFile(filename)
973
- return files
974
-
975
- def UploadBaseFiles(self, issue, rpc_server, patch_list, patchset, options,
976
- files):
977
- """Uploads the base files (and if necessary, the current ones as well)."""
978
-
979
- def UploadFile(filename, file_id, content, is_binary, status, is_base):
980
- """Uploads a file to the server."""
981
- file_too_large = False
982
- if is_base:
983
- type = "base"
984
- else:
985
- type = "current"
986
- if len(content) > MAX_UPLOAD_SIZE:
987
- result = ("Not uploading the %s file for %s because it's too large." %
988
- (type, filename))
989
- file_too_large = True
990
- content = ""
991
- elif options.verbose:
992
- result = "Uploading %s file for %s" % (type, filename)
993
- checksum = md5(content).hexdigest()
994
- url = "/%d/upload_content/%d/%d" % (int(issue), int(patchset), file_id)
995
- form_fields = [("filename", filename),
996
- ("status", status),
997
- ("checksum", checksum),
998
- ("is_binary", str(is_binary)),
999
- ("is_current", str(not is_base)),
1000
- ]
1001
- if file_too_large:
1002
- form_fields.append(("file_too_large", "1"))
1003
- if options.email:
1004
- form_fields.append(("user", options.email))
1005
- ctype, body = EncodeMultipartFormData(form_fields,
1006
- [("data", filename, content)])
1007
- try:
1008
- response_body = rpc_server.Send(url, body, content_type=ctype)
1009
- except urllib2.HTTPError as e:
1010
- response_body = ("Failed to upload file for %s. Got %d status code." %
1011
- (filename, e.code))
1012
-
1013
- if not response_body.startswith("OK"):
1014
- StatusUpdate(" --> %s" % response_body)
1015
- sys.exit(1)
1016
-
1017
- return result
1018
-
1019
- patches = dict()
1020
- [patches.setdefault(v, k) for k, v in patch_list]
1021
-
1022
- def uploadAttempt():
1023
- threads = []
1024
- thread_pool = ThreadPool(options.num_upload_threads)
1025
-
1026
- for filename in patches.keys():
1027
- base_content, new_content, is_binary, status = files[filename]
1028
- file_id_str = patches.get(filename)
1029
- if file_id_str.find("nobase") != -1:
1030
- base_content = None
1031
- file_id_str = file_id_str[file_id_str.rfind("_") + 1:]
1032
- file_id = int(file_id_str)
1033
- if base_content != None:
1034
- t = thread_pool.apply_async(UploadFile, args=(filename,
1035
- file_id, base_content, is_binary, status, True))
1036
- threads.append(t)
1037
- if new_content != None:
1038
- t = thread_pool.apply_async(UploadFile, args=(filename,
1039
- file_id, new_content, is_binary, status, False))
1040
- threads.append(t)
1041
-
1042
- for t in threads:
1043
- print(t.get(timeout=UPLOAD_TIMEOUT))
1044
-
1045
- success = False
1046
- for _ in range(MAX_UPLOAD_ATTEMPTS):
1047
- try:
1048
- uploadAttempt()
1049
- success = True
1050
- break
1051
- except multiprocessing.TimeoutError:
1052
- LOGGER.warning('Timeout error while uploading, retrying...')
1053
-
1054
- if not success:
1055
- raise IOError(
1056
- '%d consecutive timeout errors, aborting!' % MAX_UPLOAD_ATTEMPTS)
1057
-
1058
- def IsImage(self, filename):
1059
- """Returns true if the filename has an image extension."""
1060
- mimetype = mimetypes.guess_type(filename)[0]
1061
- if not mimetype:
1062
- return False
1063
- return (mimetype.startswith("image/") and
1064
- not mimetype.startswith("image/svg"))
1065
-
1066
- def IsBinaryData(self, data):
1067
- """Returns true if data contains a null byte."""
1068
- # Derived from how Mercurial's heuristic, see
1069
- # http://selenic.com/hg/file/848a6658069e/mercurial/util.py#l229
1070
- return bool(data and "\0" in data)
1071
-
1072
- def GetMostRecentCommitSummary(self):
1073
- """Returns a one line summary of the current commit."""
1074
- return ""
1075
-
1076
-
1077
- class SubversionVCS(VersionControlSystem):
1078
- """Implementation of the VersionControlSystem interface for Subversion."""
1079
-
1080
- def __init__(self, options):
1081
- super(SubversionVCS, self).__init__(options)
1082
- if self.options.revision:
1083
- match = re.match(r"(\d+)(:(\d+))?", self.options.revision)
1084
- if not match:
1085
- ErrorExit("Invalid Subversion revision %s." % self.options.revision)
1086
- self.rev_start = match.group(1)
1087
- self.rev_end = match.group(3)
1088
- else:
1089
- self.rev_start = self.rev_end = None
1090
- # Cache output from "svn list -r REVNO dirname".
1091
- # Keys: dirname, Values: 2-tuple (ouput for start rev and end rev).
1092
- self.svnls_cache = {}
1093
- # Base URL is required to fetch files deleted in an older revision.
1094
- # Result is cached to not guess it over and over again in GetBaseFile().
1095
- required = self.options.download_base or self.options.revision is not None
1096
- self.svn_base = self._GuessBase(required)
1097
-
1098
- def GetGUID(self):
1099
- return self._GetInfo("Repository UUID")
1100
-
1101
- def GuessBase(self, required):
1102
- """Wrapper for _GuessBase."""
1103
- return self.svn_base
1104
-
1105
- def _GuessBase(self, required):
1106
- """Returns base URL for current diff.
1107
-
1108
- Args:
1109
- required: If true, exits if the url can't be guessed, otherwise None is
1110
- returned.
1111
- """
1112
- url = self._GetInfo("URL")
1113
- if url:
1114
- scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
1115
- guess = ""
1116
- # TODO(anatoli) - repository specific hacks should be handled by server
1117
- if netloc == "svn.python.org" and scheme == "svn+ssh":
1118
- path = "projects" + path
1119
- scheme = "http"
1120
- guess = "Python "
1121
- elif netloc.endswith(".googlecode.com"):
1122
- scheme = "http"
1123
- guess = "Google Code "
1124
- path = path + "/"
1125
- base = urlparse.urlunparse((scheme, netloc, path, params,
1126
- query, fragment))
1127
- LOGGER.info("Guessed %sbase = %s", guess, base)
1128
- return base
1129
- if required:
1130
- ErrorExit("Can't find URL in output from svn info")
1131
- return None
1132
-
1133
- def _GetInfo(self, key):
1134
- """Parses 'svn info' for current dir. Returns value for key or None"""
1135
- for line in RunShell(["svn", "info"]).splitlines():
1136
- if line.startswith(key + ": "):
1137
- return line.split(":", 1)[1].strip()
1138
-
1139
- def _EscapeFilename(self, filename):
1140
- """Escapes filename for SVN commands."""
1141
- if "@" in filename and not filename.endswith("@"):
1142
- filename = "%s@" % filename
1143
- return filename
1144
-
1145
- def GenerateDiff(self, args):
1146
- cmd = ["svn", "diff"]
1147
- if self.options.revision:
1148
- cmd += ["-r", self.options.revision]
1149
- cmd.extend(args)
1150
- data = RunShell(cmd)
1151
- count = 0
1152
- for line in data.splitlines():
1153
- if line.startswith("Index:") or line.startswith("Property changes on:"):
1154
- count += 1
1155
- LOGGER.info(line)
1156
- if not count:
1157
- ErrorExit("No valid patches found in output from svn diff")
1158
- return data
1159
-
1160
- def _CollapseKeywords(self, content, keyword_str):
1161
- """Collapses SVN keywords."""
1162
- # svn cat translates keywords but svn diff doesn't. As a result of this
1163
- # behavior patching.PatchChunks() fails with a chunk mismatch error.
1164
- # This part was originally written by the Review Board development team
1165
- # who had the same problem (http://reviews.review-board.org/r/276/).
1166
- # Mapping of keywords to known aliases
1167
- svn_keywords = {
1168
- # Standard keywords
1169
- 'Date': ['Date', 'LastChangedDate'],
1170
- 'Revision': ['Revision', 'LastChangedRevision', 'Rev'],
1171
- 'Author': ['Author', 'LastChangedBy'],
1172
- 'HeadURL': ['HeadURL', 'URL'],
1173
- 'Id': ['Id'],
1174
-
1175
- # Aliases
1176
- 'LastChangedDate': ['LastChangedDate', 'Date'],
1177
- 'LastChangedRevision': ['LastChangedRevision', 'Rev', 'Revision'],
1178
- 'LastChangedBy': ['LastChangedBy', 'Author'],
1179
- 'URL': ['URL', 'HeadURL'],
1180
- }
1181
-
1182
- def repl(m):
1183
- if m.group(2):
1184
- return "$%s::%s$" % (m.group(1), " " * len(m.group(3)))
1185
- return "$%s$" % m.group(1)
1186
-
1187
- keywords = [keyword
1188
- for name in keyword_str.split(" ")
1189
- for keyword in svn_keywords.get(name, [])]
1190
- return re.sub(r"\$(%s):(:?)([^\$]+)\$" % '|'.join(keywords), repl, content)
1191
-
1192
- def GetUnknownFiles(self):
1193
- status = RunShell(["svn", "status", "--ignore-externals"], silent_ok=True)
1194
- unknown_files = []
1195
- for line in status.split("\n"):
1196
- if line and line[0] == "?":
1197
- unknown_files.append(line)
1198
- return unknown_files
1199
-
1200
- def ReadFile(self, filename):
1201
- """Returns the contents of a file."""
1202
- file = open(filename, 'rb')
1203
- result = ""
1204
- try:
1205
- result = file.read()
1206
- finally:
1207
- file.close()
1208
- return result
1209
-
1210
- def GetStatus(self, filename):
1211
- """Returns the status of a file."""
1212
- if not self.options.revision:
1213
- status = RunShell(["svn", "status", "--ignore-externals",
1214
- self._EscapeFilename(filename)])
1215
- if not status:
1216
- ErrorExit("svn status returned no output for %s" % filename)
1217
- status_lines = status.splitlines()
1218
- # If file is in a cl, the output will begin with
1219
- # "\n--- Changelist 'cl_name':\n". See
1220
- # http://svn.collab.net/repos/svn/trunk/notes/changelist-design.txt
1221
- if (len(status_lines) == 3 and
1222
- not status_lines[0] and
1223
- status_lines[1].startswith("--- Changelist")):
1224
- status = status_lines[2]
1225
- else:
1226
- status = status_lines[0]
1227
- # If we have a revision to diff against we need to run "svn list"
1228
- # for the old and the new revision and compare the results to get
1229
- # the correct status for a file.
1230
- else:
1231
- dirname, relfilename = os.path.split(filename)
1232
- if dirname not in self.svnls_cache:
1233
- cmd = ["svn", "list", "-r", self.rev_start,
1234
- self._EscapeFilename(dirname) or "."]
1235
- out, err, returncode = RunShellWithReturnCodeAndStderr(cmd)
1236
- if returncode:
1237
- # Directory might not yet exist at start revison
1238
- # svn: Unable to find repository location for 'abc' in revision nnn
1239
- if re.match('^svn: Unable to find repository location '
1240
- 'for .+ in revision \d+', err):
1241
- old_files = ()
1242
- else:
1243
- ErrorExit("Failed to get status for %s:\n%s" % (filename, err))
1244
- else:
1245
- old_files = out.splitlines()
1246
- args = ["svn", "list"]
1247
- if self.rev_end:
1248
- args += ["-r", self.rev_end]
1249
- cmd = args + [self._EscapeFilename(dirname) or "."]
1250
- out, returncode = RunShellWithReturnCode(cmd)
1251
- if returncode:
1252
- ErrorExit("Failed to run command %s" % cmd)
1253
- self.svnls_cache[dirname] = (old_files, out.splitlines())
1254
- old_files, new_files = self.svnls_cache[dirname]
1255
- if relfilename in old_files and relfilename not in new_files:
1256
- status = "D "
1257
- elif relfilename in old_files and relfilename in new_files:
1258
- status = "M "
1259
- else:
1260
- status = "A "
1261
- return status
1262
-
1263
- def GetBaseFile(self, filename):
1264
- status = self.GetStatus(filename)
1265
- base_content = None
1266
- new_content = None
1267
-
1268
- # If a file is copied its status will be "A +", which signifies
1269
- # "addition-with-history". See "svn st" for more information. We need to
1270
- # upload the original file or else diff parsing will fail if the file was
1271
- # edited.
1272
- if status[0] == "A" and status[3] != "+":
1273
- # We'll need to upload the new content if we're adding a binary file
1274
- # since diff's output won't contain it.
1275
- mimetype = RunShell(["svn", "propget", "svn:mime-type",
1276
- self._EscapeFilename(filename)], silent_ok=True)
1277
- base_content = ""
1278
- is_binary = bool(mimetype) and not mimetype.startswith("text/")
1279
- if is_binary:
1280
- new_content = self.ReadFile(filename)
1281
- elif (status[0] in ("M", "D", "R") or
1282
- (status[0] == "A" and status[3] == "+") or # Copied file.
1283
- (status[0] == " " and status[1] == "M")): # Property change.
1284
- args = []
1285
- if self.options.revision:
1286
- # filename must not be escaped. We already add an ampersand here.
1287
- url = "%s/%s@%s" % (self.svn_base, filename, self.rev_start)
1288
- else:
1289
- # Don't change filename, it's needed later.
1290
- url = filename
1291
- args += ["-r", "BASE"]
1292
- cmd = ["svn"] + args + ["propget", "svn:mime-type", url]
1293
- mimetype, returncode = RunShellWithReturnCode(cmd)
1294
- if returncode:
1295
- # File does not exist in the requested revision.
1296
- # Reset mimetype, it contains an error message.
1297
- mimetype = ""
1298
- else:
1299
- mimetype = mimetype.strip()
1300
- get_base = False
1301
- # this test for binary is exactly the test prescribed by the
1302
- # official SVN docs at
1303
- # http://subversion.apache.org/faq.html#binary-files
1304
- is_binary = (bool(mimetype) and
1305
- not mimetype.startswith("text/") and
1306
- mimetype not in ("image/x-xbitmap", "image/x-xpixmap"))
1307
- if status[0] == " ":
1308
- # Empty base content just to force an upload.
1309
- base_content = ""
1310
- elif is_binary:
1311
- get_base = True
1312
- if status[0] == "M":
1313
- if not self.rev_end:
1314
- new_content = self.ReadFile(filename)
1315
- else:
1316
- url = "%s/%s@%s" % (self.svn_base, filename, self.rev_end)
1317
- new_content = RunShell(["svn", "cat", url],
1318
- universal_newlines=True, silent_ok=True)
1319
- else:
1320
- get_base = True
1321
-
1322
- if get_base:
1323
- if is_binary:
1324
- universal_newlines = False
1325
- else:
1326
- universal_newlines = True
1327
- if self.rev_start:
1328
- # "svn cat -r REV delete_file.txt" doesn't work. cat requires
1329
- # the full URL with "@REV" appended instead of using "-r" option.
1330
- url = "%s/%s@%s" % (self.svn_base, filename, self.rev_start)
1331
- base_content = RunShell(["svn", "cat", url],
1332
- universal_newlines=universal_newlines,
1333
- silent_ok=True)
1334
- else:
1335
- base_content, ret_code = RunShellWithReturnCode(
1336
- ["svn", "cat", self._EscapeFilename(filename)],
1337
- universal_newlines=universal_newlines)
1338
- if ret_code and status[0] == "R":
1339
- # It's a replaced file without local history (see issue208).
1340
- # The base file needs to be fetched from the server.
1341
- url = "%s/%s" % (self.svn_base, filename)
1342
- base_content = RunShell(["svn", "cat", url],
1343
- universal_newlines=universal_newlines,
1344
- silent_ok=True)
1345
- elif ret_code:
1346
- ErrorExit("Got error status from 'svn cat %s'" % filename)
1347
- if not is_binary:
1348
- args = []
1349
- if self.rev_start:
1350
- url = "%s/%s@%s" % (self.svn_base, filename, self.rev_start)
1351
- else:
1352
- url = filename
1353
- args += ["-r", "BASE"]
1354
- cmd = ["svn"] + args + ["propget", "svn:keywords", url]
1355
- keywords, returncode = RunShellWithReturnCode(cmd)
1356
- if keywords and not returncode:
1357
- base_content = self._CollapseKeywords(base_content, keywords)
1358
- else:
1359
- StatusUpdate("svn status returned unexpected output: %s" % status)
1360
- sys.exit(1)
1361
- return base_content, new_content, is_binary, status[0:5]
1362
-
1363
-
1364
- class GitVCS(VersionControlSystem):
1365
- """Implementation of the VersionControlSystem interface for Git."""
1366
-
1367
- def __init__(self, options):
1368
- super(GitVCS, self).__init__(options)
1369
- # Map of filename -> (hash before, hash after) of base file.
1370
- # Hashes for "no such file" are represented as None.
1371
- self.hashes = {}
1372
- # Map of new filename -> old filename for renames.
1373
- self.renames = {}
1374
-
1375
- def GetGUID(self):
1376
- remote, retcode = RunShellWithReturnCode(
1377
- "git config remote.origin.url".split())
1378
- if not retcode:
1379
- return remote.strip()
1380
- revlist = RunShell("git rev-list --parents HEAD".split()).splitlines()
1381
- # M-A: Return the 1st root hash, there could be multiple when a
1382
- # subtree is merged. In that case, more analysis would need to
1383
- # be done to figure out which HEAD is the 'most representative'.
1384
- for r in revlist:
1385
- if ' ' not in r:
1386
- return r
1387
-
1388
- def PostProcessDiff(self, gitdiff):
1389
- """Converts the diff output to include an svn-style "Index:" line as well
1390
- as record the hashes of the files, so we can upload them along with our
1391
- diff."""
1392
- # Special used by git to indicate "no such content".
1393
- NULL_HASH = "0"*40
1394
-
1395
- def IsFileNew(filename):
1396
- return filename in self.hashes and self.hashes[filename][0] is None
1397
-
1398
- def AddSubversionPropertyChange(filename):
1399
- """Add svn's property change information into the patch if given file is
1400
- new file.
1401
-
1402
- We use Subversion's auto-props setting to retrieve its property.
1403
- See http://svnbook.red-bean.com/en/1.1/ch07.html#svn-ch-7-sect-1.3.2 for
1404
- Subversion's [auto-props] setting.
1405
- """
1406
- if self.options.emulate_svn_auto_props and IsFileNew(filename):
1407
- svnprops = GetSubversionPropertyChanges(filename)
1408
- if svnprops:
1409
- svndiff.append("\n" + svnprops + "\n")
1410
-
1411
- svndiff = []
1412
- filecount = 0
1413
- filename = None
1414
- for line in gitdiff.splitlines():
1415
- match = re.match(r"diff --git a/(.*) b/(.*)$", line)
1416
- if match:
1417
- # Add auto property here for previously seen file.
1418
- if filename is not None:
1419
- AddSubversionPropertyChange(filename)
1420
- filecount += 1
1421
- # Intentionally use the "after" filename so we can show renames.
1422
- filename = match.group(2)
1423
- svndiff.append("Index: %s\n" % filename)
1424
- if match.group(1) != match.group(2):
1425
- self.renames[match.group(2)] = match.group(1)
1426
- else:
1427
- # The "index" line in a git diff looks like this (long hashes elided):
1428
- # index 82c0d44..b2cee3f 100755
1429
- # We want to save the left hash, as that identifies the base file.
1430
- match = re.match(r"index (\w+)\.\.(\w+)", line)
1431
- if match:
1432
- before, after = (match.group(1), match.group(2))
1433
- if before == NULL_HASH:
1434
- before = None
1435
- if after == NULL_HASH:
1436
- after = None
1437
- self.hashes[filename] = (before, after)
1438
- svndiff.append(line + "\n")
1439
- if not filecount:
1440
- ErrorExit("No valid patches found in output from git diff")
1441
- # Add auto property for the last seen file.
1442
- assert filename is not None
1443
- AddSubversionPropertyChange(filename)
1444
- return "".join(svndiff)
1445
-
1446
- def GenerateDiff(self, extra_args):
1447
- extra_args = extra_args[:]
1448
- if self.options.revision:
1449
- if ":" in self.options.revision:
1450
- extra_args = self.options.revision.split(":", 1) + extra_args
1451
- else:
1452
- extra_args = [self.options.revision] + extra_args
1453
-
1454
- # --no-ext-diff is broken in some versions of Git, so try to work around
1455
- # this by overriding the environment (but there is still a problem if the
1456
- # git config key "diff.external" is used).
1457
- env = os.environ.copy()
1458
- if "GIT_EXTERNAL_DIFF" in env:
1459
- del env["GIT_EXTERNAL_DIFF"]
1460
- # 'cat' is a magical git string that disables pagers on all platforms.
1461
- env["GIT_PAGER"] = "cat"
1462
-
1463
- # -M/-C will not print the diff for the deleted file when a file is renamed.
1464
- # This is confusing because the original file will not be shown on the
1465
- # review when a file is renamed. So, get a diff with ONLY deletes, then
1466
- # append a diff (with rename detection), without deletes.
1467
- cmd = [
1468
- "git", "diff", "--no-color", "--no-ext-diff", "--full-index",
1469
- "--ignore-submodules", "--src-prefix=a/", "--dst-prefix=b/",
1470
- ]
1471
- diff = RunShell(
1472
- cmd + ["--no-renames", "--diff-filter=D"] + extra_args,
1473
- env=env, silent_ok=True)
1474
- assert 0 <= self.options.git_similarity <= 100
1475
- if self.options.git_find_copies:
1476
- similarity_options = ["-l100000", "-C%d%%" % self.options.git_similarity]
1477
- if self.options.git_find_copies_harder:
1478
- similarity_options.append("--find-copies-harder")
1479
- else:
1480
- similarity_options = ["-M%d%%" % self.options.git_similarity ]
1481
- diff += RunShell(
1482
- cmd + ["--diff-filter=AMCRT"] + similarity_options + extra_args,
1483
- env=env, silent_ok=True)
1484
-
1485
- # The CL could be only file deletion or not. So accept silent diff for both
1486
- # commands then check for an empty diff manually.
1487
- if not diff:
1488
- ErrorExit("No output from %s" % (cmd + extra_args))
1489
- return diff
1490
-
1491
- def GetUnknownFiles(self):
1492
- status = RunShell(["git", "ls-files", "--exclude-standard", "--others"],
1493
- silent_ok=True)
1494
- return status.splitlines()
1495
-
1496
- def GetFileContent(self, file_hash):
1497
- """Returns the content of a file identified by its git hash."""
1498
- data, retcode = RunShellWithReturnCode(["git", "show", file_hash],
1499
- universal_newlines=False)
1500
- if retcode:
1501
- ErrorExit("Got error status from 'git show %s'" % file_hash)
1502
- return data
1503
-
1504
- def GetBaseFile(self, filename):
1505
- hash_before, hash_after = self.hashes.get(filename, (None,None))
1506
- base_content = None
1507
- new_content = None
1508
- status = None
1509
-
1510
- if filename in self.renames:
1511
- status = "A +" # Match svn attribute name for renames.
1512
- if filename not in self.hashes:
1513
- # If a rename doesn't change the content, we never get a hash.
1514
- base_content = RunShell(
1515
- ["git", "show", "HEAD:" + filename], silent_ok=True,
1516
- universal_newlines=False)
1517
- elif not hash_before:
1518
- status = "A"
1519
- base_content = ""
1520
- elif not hash_after:
1521
- status = "D"
1522
- else:
1523
- status = "M"
1524
-
1525
- # Grab the before/after content if we need it.
1526
- # Grab the base content if we don't have it already.
1527
- if base_content is None and hash_before:
1528
- base_content = self.GetFileContent(hash_before)
1529
-
1530
- is_binary = self.IsImage(filename)
1531
- if base_content:
1532
- is_binary = is_binary or self.IsBinaryData(base_content)
1533
-
1534
- # Only include the "after" file if it's an image; otherwise it
1535
- # it is reconstructed from the diff.
1536
- if hash_after:
1537
- new_content = self.GetFileContent(hash_after)
1538
- is_binary = is_binary or self.IsBinaryData(new_content)
1539
- if not is_binary:
1540
- new_content = None
1541
- return (base_content, new_content, is_binary, status)
1542
-
1543
- def GetMostRecentCommitSummary(self):
1544
- return RunShell(["git", "log", "-1", "--format=%s"], silent_ok=True).strip()
1545
-
1546
-
1547
- class CVSVCS(VersionControlSystem):
1548
- """Implementation of the VersionControlSystem interface for CVS."""
1549
-
1550
- def __init__(self, options):
1551
- super(CVSVCS, self).__init__(options)
1552
-
1553
- def GetGUID(self):
1554
- """For now we don't know how to get repository ID for CVS"""
1555
- return
1556
-
1557
- def GetOriginalContent_(self, filename):
1558
- RunShell(["cvs", "up", filename], silent_ok=True)
1559
- # TODO need detect file content encoding
1560
- content = open(filename).read()
1561
- return content.replace("\r\n", "\n")
1562
-
1563
- def GetBaseFile(self, filename):
1564
- base_content = None
1565
- new_content = None
1566
- status = "A"
1567
-
1568
- output, retcode = RunShellWithReturnCode(["cvs", "status", filename])
1569
- if retcode:
1570
- ErrorExit("Got error status from 'cvs status %s'" % filename)
1571
-
1572
- if output.find("Status: Locally Modified") != -1:
1573
- status = "M"
1574
- temp_filename = "%s.tmp123" % filename
1575
- os.rename(filename, temp_filename)
1576
- base_content = self.GetOriginalContent_(filename)
1577
- os.rename(temp_filename, filename)
1578
- elif output.find("Status: Locally Added"):
1579
- status = "A"
1580
- base_content = ""
1581
- elif output.find("Status: Needs Checkout"):
1582
- status = "D"
1583
- base_content = self.GetOriginalContent_(filename)
1584
-
1585
- return (base_content, new_content, self.IsBinaryData(base_content), status)
1586
-
1587
- def GenerateDiff(self, extra_args):
1588
- cmd = ["cvs", "diff", "-u", "-N"]
1589
- if self.options.revision:
1590
- cmd += ["-r", self.options.revision]
1591
-
1592
- cmd.extend(extra_args)
1593
- data, retcode = RunShellWithReturnCode(cmd)
1594
- count = 0
1595
- if retcode in [0, 1]:
1596
- for line in data.splitlines():
1597
- if line.startswith("Index:"):
1598
- count += 1
1599
- LOGGER.info(line)
1600
-
1601
- if not count:
1602
- ErrorExit("No valid patches found in output from cvs diff")
1603
-
1604
- return data
1605
-
1606
- def GetUnknownFiles(self):
1607
- data, retcode = RunShellWithReturnCode(["cvs", "diff"])
1608
- if retcode not in [0, 1]:
1609
- ErrorExit("Got error status from 'cvs diff':\n%s" % (data,))
1610
- unknown_files = []
1611
- for line in data.split("\n"):
1612
- if line and line[0] == "?":
1613
- unknown_files.append(line)
1614
- return unknown_files
1615
-
1616
- class MercurialVCS(VersionControlSystem):
1617
- """Implementation of the VersionControlSystem interface for Mercurial."""
1618
-
1619
- def __init__(self, options, repo_dir):
1620
- super(MercurialVCS, self).__init__(options)
1621
- # Absolute path to repository (we can be in a subdir)
1622
- self.repo_dir = os.path.normpath(repo_dir)
1623
- # Compute the subdir
1624
- cwd = os.path.normpath(os.getcwd())
1625
- assert cwd.startswith(self.repo_dir)
1626
- self.subdir = cwd[len(self.repo_dir):].lstrip(r"\/")
1627
- if self.options.revision:
1628
- self.base_rev = self.options.revision
1629
- else:
1630
- self.base_rev = RunShell(["hg", "parent", "-q"]).split(':')[1].strip()
1631
-
1632
- def GetGUID(self):
1633
- # See chapter "Uniquely identifying a repository"
1634
- # http://hgbook.red-bean.com/read/customizing-the-output-of-mercurial.html
1635
- info = RunShell("hg log -r0 --template {node}".split())
1636
- return info.strip()
1637
-
1638
- def _GetRelPath(self, filename):
1639
- """Get relative path of a file according to the current directory,
1640
- given its logical path in the repo."""
1641
- absname = os.path.join(self.repo_dir, filename)
1642
- return os.path.relpath(absname)
1643
-
1644
- def GenerateDiff(self, extra_args):
1645
- cmd = ["hg", "diff", "--git", "-r", self.base_rev] + extra_args
1646
- data = RunShell(cmd, silent_ok=True)
1647
- svndiff = []
1648
- filecount = 0
1649
- for line in data.splitlines():
1650
- m = re.match("diff --git a/(\S+) b/(\S+)", line)
1651
- if m:
1652
- # Modify line to make it look like as it comes from svn diff.
1653
- # With this modification no changes on the server side are required
1654
- # to make upload.py work with Mercurial repos.
1655
- # NOTE: for proper handling of moved/copied files, we have to use
1656
- # the second filename.
1657
- filename = m.group(2)
1658
- svndiff.append("Index: %s" % filename)
1659
- svndiff.append("=" * 67)
1660
- filecount += 1
1661
- LOGGER.info(line)
1662
- else:
1663
- svndiff.append(line)
1664
- if not filecount:
1665
- ErrorExit("No valid patches found in output from hg diff")
1666
- return "\n".join(svndiff) + "\n"
1667
-
1668
- def GetUnknownFiles(self):
1669
- """Return a list of files unknown to the VCS."""
1670
- args = []
1671
- status = RunShell(["hg", "status", "--rev", self.base_rev, "-u", "."],
1672
- silent_ok=True)
1673
- unknown_files = []
1674
- for line in status.splitlines():
1675
- st, fn = line.split(" ", 1)
1676
- if st == "?":
1677
- unknown_files.append(fn)
1678
- return unknown_files
1679
-
1680
- def GetBaseFile(self, filename):
1681
- # "hg status" and "hg cat" both take a path relative to the current subdir,
1682
- # but "hg diff" has given us the path relative to the repo root.
1683
- base_content = ""
1684
- new_content = None
1685
- is_binary = False
1686
- oldrelpath = relpath = self._GetRelPath(filename)
1687
- # "hg status -C" returns two lines for moved/copied files, one otherwise
1688
- out = RunShell(["hg", "status", "-C", "--rev", self.base_rev, relpath])
1689
- out = out.splitlines()
1690
- # HACK: strip error message about missing file/directory if it isn't in
1691
- # the working copy
1692
- if out[0].startswith('%s: ' % relpath):
1693
- out = out[1:]
1694
- status, _ = out[0].split(' ', 1)
1695
- if len(out) > 1 and status == "A":
1696
- # Moved/copied => considered as modified, use old filename to
1697
- # retrieve base contents
1698
- oldrelpath = out[1].strip()
1699
- status = "M"
1700
- if ":" in self.base_rev:
1701
- base_rev = self.base_rev.split(":", 1)[0]
1702
- else:
1703
- base_rev = self.base_rev
1704
- if status != "A":
1705
- base_content = RunShell(["hg", "cat", "-r", base_rev, oldrelpath],
1706
- silent_ok=True)
1707
- is_binary = self.IsBinaryData(base_content)
1708
- if status != "R":
1709
- new_content = open(relpath, "rb").read()
1710
- is_binary = is_binary or self.IsBinaryData(new_content)
1711
- if is_binary and base_content:
1712
- # Fetch again without converting newlines
1713
- base_content = RunShell(["hg", "cat", "-r", base_rev, oldrelpath],
1714
- silent_ok=True, universal_newlines=False)
1715
- if not is_binary:
1716
- new_content = None
1717
- return base_content, new_content, is_binary, status
1718
-
1719
-
1720
- class PerforceVCS(VersionControlSystem):
1721
- """Implementation of the VersionControlSystem interface for Perforce."""
1722
-
1723
- def __init__(self, options):
1724
-
1725
- def ConfirmLogin():
1726
- # Make sure we have a valid perforce session
1727
- while True:
1728
- data, retcode = self.RunPerforceCommandWithReturnCode(
1729
- ["login", "-s"], marshal_output=True)
1730
- if not data:
1731
- ErrorExit("Error checking perforce login")
1732
- if not retcode and (not "code" in data or data["code"] != "error"):
1733
- break
1734
- print("Enter perforce password: ")
1735
- self.RunPerforceCommandWithReturnCode(["login"])
1736
-
1737
- super(PerforceVCS, self).__init__(options)
1738
-
1739
- self.p4_changelist = options.p4_changelist
1740
- if not self.p4_changelist:
1741
- ErrorExit("A changelist id is required")
1742
- if (options.revision):
1743
- ErrorExit("--rev is not supported for perforce")
1744
-
1745
- self.p4_port = options.p4_port
1746
- self.p4_client = options.p4_client
1747
- self.p4_user = options.p4_user
1748
-
1749
- ConfirmLogin()
1750
-
1751
- if not options.title:
1752
- description = self.RunPerforceCommand(["describe", self.p4_changelist],
1753
- marshal_output=True)
1754
- if description and "desc" in description:
1755
- # Rietveld doesn't support multi-line descriptions
1756
- raw_title = description["desc"].strip()
1757
- lines = raw_title.splitlines()
1758
- if len(lines):
1759
- options.title = lines[0]
1760
-
1761
- def GetGUID(self):
1762
- """For now we don't know how to get repository ID for Perforce"""
1763
- return
1764
-
1765
- def RunPerforceCommandWithReturnCode(self, extra_args, marshal_output=False,
1766
- universal_newlines=True):
1767
- args = ["p4"]
1768
- if marshal_output:
1769
- # -G makes perforce format its output as marshalled python objects
1770
- args.extend(["-G"])
1771
- if self.p4_port:
1772
- args.extend(["-p", self.p4_port])
1773
- if self.p4_client:
1774
- args.extend(["-c", self.p4_client])
1775
- if self.p4_user:
1776
- args.extend(["-u", self.p4_user])
1777
- args.extend(extra_args)
1778
-
1779
- data, retcode = RunShellWithReturnCode(
1780
- args, universal_newlines=universal_newlines)
1781
- if marshal_output and data:
1782
- data = marshal.loads(data)
1783
- return data, retcode
1784
-
1785
- def RunPerforceCommand(self, extra_args, marshal_output=False,
1786
- universal_newlines=True):
1787
- # This might be a good place to cache call results, since things like
1788
- # describe or fstat might get called repeatedly.
1789
- data, retcode = self.RunPerforceCommandWithReturnCode(
1790
- extra_args, marshal_output, universal_newlines)
1791
- if retcode:
1792
- ErrorExit("Got error status from %s:\n%s" % (extra_args, data))
1793
- return data
1794
-
1795
- def GetFileProperties(self, property_key_prefix = "", command = "describe"):
1796
- description = self.RunPerforceCommand(["describe", self.p4_changelist],
1797
- marshal_output=True)
1798
-
1799
- changed_files = {}
1800
- file_index = 0
1801
- # Try depotFile0, depotFile1, ... until we don't find a match
1802
- while True:
1803
- file_key = "depotFile%d" % file_index
1804
- if file_key in description:
1805
- filename = description[file_key]
1806
- change_type = description[property_key_prefix + str(file_index)]
1807
- changed_files[filename] = change_type
1808
- file_index += 1
1809
- else:
1810
- break
1811
- return changed_files
1812
-
1813
- def GetChangedFiles(self):
1814
- return self.GetFileProperties("action")
1815
-
1816
- def GetUnknownFiles(self):
1817
- # Perforce doesn't detect new files, they have to be explicitly added
1818
- return []
1819
-
1820
- def IsBaseBinary(self, filename):
1821
- base_filename = self.GetBaseFilename(filename)
1822
- return self.IsBinaryHelper(base_filename, "files")
1823
-
1824
- def IsPendingBinary(self, filename):
1825
- return self.IsBinaryHelper(filename, "describe")
1826
-
1827
- def IsBinaryHelper(self, filename, command):
1828
- file_types = self.GetFileProperties("type", command)
1829
- if not filename in file_types:
1830
- ErrorExit("Trying to check binary status of unknown file %s." % filename)
1831
- # This treats symlinks, macintosh resource files, temporary objects, and
1832
- # unicode as binary. See the Perforce docs for more details:
1833
- # http://www.perforce.com/perforce/doc.current/manuals/cmdref/o.ftypes.html
1834
- return not file_types[filename].endswith("text")
1835
-
1836
- def GetFileContent(self, filename, revision, is_binary):
1837
- file_arg = filename
1838
- if revision:
1839
- file_arg += "#" + revision
1840
- # -q suppresses the initial line that displays the filename and revision
1841
- return self.RunPerforceCommand(["print", "-q", file_arg],
1842
- universal_newlines=not is_binary)
1843
-
1844
- def GetBaseFilename(self, filename):
1845
- actionsWithDifferentBases = [
1846
- "move/add", # p4 move
1847
- "branch", # p4 integrate (to a new file), similar to hg "add"
1848
- "add", # p4 integrate (to a new file), after modifying the new file
1849
- ]
1850
-
1851
- # We only see a different base for "add" if this is a downgraded branch
1852
- # after a file was branched (integrated), then edited.
1853
- if self.GetAction(filename) in actionsWithDifferentBases:
1854
- # -Or shows information about pending integrations/moves
1855
- fstat_result = self.RunPerforceCommand(["fstat", "-Or", filename],
1856
- marshal_output=True)
1857
-
1858
- baseFileKey = "resolveFromFile0" # I think it's safe to use only file0
1859
- if baseFileKey in fstat_result:
1860
- return fstat_result[baseFileKey]
1861
-
1862
- return filename
1863
-
1864
- def GetBaseRevision(self, filename):
1865
- base_filename = self.GetBaseFilename(filename)
1866
-
1867
- have_result = self.RunPerforceCommand(["have", base_filename],
1868
- marshal_output=True)
1869
- if "haveRev" in have_result:
1870
- return have_result["haveRev"]
1871
-
1872
- def GetLocalFilename(self, filename):
1873
- where = self.RunPerforceCommand(["where", filename], marshal_output=True)
1874
- if "path" in where:
1875
- return where["path"]
1876
-
1877
- def GenerateDiff(self, args):
1878
- class DiffData:
1879
- def __init__(self, perforceVCS, filename, action):
1880
- self.perforceVCS = perforceVCS
1881
- self.filename = filename
1882
- self.action = action
1883
- self.base_filename = perforceVCS.GetBaseFilename(filename)
1884
-
1885
- self.file_body = None
1886
- self.base_rev = None
1887
- self.prefix = None
1888
- self.working_copy = True
1889
- self.change_summary = None
1890
-
1891
- def GenerateDiffHeader(diffData):
1892
- header = []
1893
- header.append("Index: %s" % diffData.filename)
1894
- header.append("=" * 67)
1895
-
1896
- if diffData.base_filename != diffData.filename:
1897
- if diffData.action.startswith("move"):
1898
- verb = "rename"
1899
- else:
1900
- verb = "copy"
1901
- header.append("%s from %s" % (verb, diffData.base_filename))
1902
- header.append("%s to %s" % (verb, diffData.filename))
1903
-
1904
- suffix = "\t(revision %s)" % diffData.base_rev
1905
- header.append("--- " + diffData.base_filename + suffix)
1906
- if diffData.working_copy:
1907
- suffix = "\t(working copy)"
1908
- header.append("+++ " + diffData.filename + suffix)
1909
- if diffData.change_summary:
1910
- header.append(diffData.change_summary)
1911
- return header
1912
-
1913
- def GenerateMergeDiff(diffData, args):
1914
- # -du generates a unified diff, which is nearly svn format
1915
- diffData.file_body = self.RunPerforceCommand(
1916
- ["diff", "-du", diffData.filename] + args)
1917
- diffData.base_rev = self.GetBaseRevision(diffData.filename)
1918
- diffData.prefix = ""
1919
-
1920
- # We have to replace p4's file status output (the lines starting
1921
- # with +++ or ---) to match svn's diff format
1922
- lines = diffData.file_body.splitlines()
1923
- first_good_line = 0
1924
- while (first_good_line < len(lines) and
1925
- not lines[first_good_line].startswith("@@")):
1926
- first_good_line += 1
1927
- diffData.file_body = "\n".join(lines[first_good_line:])
1928
- return diffData
1929
-
1930
- def GenerateAddDiff(diffData):
1931
- fstat = self.RunPerforceCommand(["fstat", diffData.filename],
1932
- marshal_output=True)
1933
- if "headRev" in fstat:
1934
- diffData.base_rev = fstat["headRev"] # Re-adding a deleted file
1935
- else:
1936
- diffData.base_rev = "0" # Brand new file
1937
- diffData.working_copy = False
1938
- rel_path = self.GetLocalFilename(diffData.filename)
1939
- diffData.file_body = open(rel_path, 'r').read()
1940
- # Replicate svn's list of changed lines
1941
- line_count = len(diffData.file_body.splitlines())
1942
- diffData.change_summary = "@@ -0,0 +1"
1943
- if line_count > 1:
1944
- diffData.change_summary += ",%d" % line_count
1945
- diffData.change_summary += " @@"
1946
- diffData.prefix = "+"
1947
- return diffData
1948
-
1949
- def GenerateDeleteDiff(diffData):
1950
- diffData.base_rev = self.GetBaseRevision(diffData.filename)
1951
- is_base_binary = self.IsBaseBinary(diffData.filename)
1952
- # For deletes, base_filename == filename
1953
- diffData.file_body = self.GetFileContent(diffData.base_filename,
1954
- None,
1955
- is_base_binary)
1956
- # Replicate svn's list of changed lines
1957
- line_count = len(diffData.file_body.splitlines())
1958
- diffData.change_summary = "@@ -1"
1959
- if line_count > 1:
1960
- diffData.change_summary += ",%d" % line_count
1961
- diffData.change_summary += " +0,0 @@"
1962
- diffData.prefix = "-"
1963
- return diffData
1964
-
1965
- changed_files = self.GetChangedFiles()
1966
-
1967
- svndiff = []
1968
- filecount = 0
1969
- for (filename, action) in changed_files.items():
1970
- svn_status = self.PerforceActionToSvnStatus(action)
1971
- if svn_status == "SKIP":
1972
- continue
1973
-
1974
- diffData = DiffData(self, filename, action)
1975
- # Is it possible to diff a branched file? Stackoverflow says no:
1976
- # http://stackoverflow.com/questions/1771314/in-perforce-command-line-how-to-diff-a-file-reopened-for-add
1977
- if svn_status == "M":
1978
- diffData = GenerateMergeDiff(diffData, args)
1979
- elif svn_status == "A":
1980
- diffData = GenerateAddDiff(diffData)
1981
- elif svn_status == "D":
1982
- diffData = GenerateDeleteDiff(diffData)
1983
- else:
1984
- ErrorExit("Unknown file action %s (svn action %s)." % \
1985
- (action, svn_status))
1986
-
1987
- svndiff += GenerateDiffHeader(diffData)
1988
-
1989
- for line in diffData.file_body.splitlines():
1990
- svndiff.append(diffData.prefix + line)
1991
- filecount += 1
1992
- if not filecount:
1993
- ErrorExit("No valid patches found in output from p4 diff")
1994
- return "\n".join(svndiff) + "\n"
1995
-
1996
- def PerforceActionToSvnStatus(self, status):
1997
- # Mirroring the list at http://permalink.gmane.org/gmane.comp.version-control.mercurial.devel/28717
1998
- # Is there something more official?
1999
- return {
2000
- "add" : "A",
2001
- "branch" : "A",
2002
- "delete" : "D",
2003
- "edit" : "M", # Also includes changing file types.
2004
- "integrate" : "M",
2005
- "move/add" : "M",
2006
- "move/delete": "SKIP",
2007
- "purge" : "D", # How does a file's status become "purge"?
2008
- }[status]
2009
-
2010
- def GetAction(self, filename):
2011
- changed_files = self.GetChangedFiles()
2012
- if not filename in changed_files:
2013
- ErrorExit("Trying to get base version of unknown file %s." % filename)
2014
-
2015
- return changed_files[filename]
2016
-
2017
- def GetBaseFile(self, filename):
2018
- base_filename = self.GetBaseFilename(filename)
2019
- base_content = ""
2020
- new_content = None
2021
-
2022
- status = self.PerforceActionToSvnStatus(self.GetAction(filename))
2023
-
2024
- if status != "A":
2025
- revision = self.GetBaseRevision(base_filename)
2026
- if not revision:
2027
- ErrorExit("Couldn't find base revision for file %s" % filename)
2028
- is_base_binary = self.IsBaseBinary(base_filename)
2029
- base_content = self.GetFileContent(base_filename,
2030
- revision,
2031
- is_base_binary)
2032
-
2033
- is_binary = self.IsPendingBinary(filename)
2034
- if status != "D" and status != "SKIP":
2035
- relpath = self.GetLocalFilename(filename)
2036
- if is_binary:
2037
- new_content = open(relpath, "rb").read()
2038
-
2039
- return base_content, new_content, is_binary, status
2040
-
2041
- # NOTE: The SplitPatch function is duplicated in engine.py, keep them in sync.
2042
- def SplitPatch(data):
2043
- """Splits a patch into separate pieces for each file.
2044
-
2045
- Args:
2046
- data: A string containing the output of svn diff.
2047
-
2048
- Returns:
2049
- A list of 2-tuple (filename, text) where text is the svn diff output
2050
- pertaining to filename.
2051
- """
2052
- patches = []
2053
- filename = None
2054
- diff = []
2055
- for line in data.splitlines(True):
2056
- new_filename = None
2057
- if line.startswith('Index:'):
2058
- unused, new_filename = line.split(':', 1)
2059
- new_filename = new_filename.strip()
2060
- elif line.startswith('Property changes on:'):
2061
- unused, temp_filename = line.split(':', 1)
2062
- # When a file is modified, paths use '/' between directories, however
2063
- # when a property is modified '\' is used on Windows. Make them the same
2064
- # otherwise the file shows up twice.
2065
- temp_filename = temp_filename.strip().replace('\\', '/')
2066
- if temp_filename != filename:
2067
- # File has property changes but no modifications, create a new diff.
2068
- new_filename = temp_filename
2069
- if new_filename:
2070
- if filename and diff:
2071
- patches.append((filename, ''.join(diff)))
2072
- filename = new_filename
2073
- diff = [line]
2074
- continue
2075
- if diff is not None:
2076
- diff.append(line)
2077
- if filename and diff:
2078
- patches.append((filename, ''.join(diff)))
2079
- return patches
2080
-
2081
-
2082
- def UploadSeparatePatches(issue, rpc_server, patchset, data, options):
2083
- """Uploads a separate patch for each file in the diff output.
2084
-
2085
- Returns a list of [patch_key, filename] for each file.
2086
- """
2087
- def UploadFile(filename, data):
2088
- form_fields = [("filename", filename)]
2089
- if not options.download_base:
2090
- form_fields.append(("content_upload", "1"))
2091
- files = [("data", "data.diff", data)]
2092
- ctype, body = EncodeMultipartFormData(form_fields, files)
2093
- url = "/%d/upload_patch/%d" % (int(issue), int(patchset))
2094
-
2095
- try:
2096
- response_body = rpc_server.Send(url, body, content_type=ctype)
2097
- except urllib2.HTTPError as e:
2098
- response_body = ("Failed to upload patch for %s. Got %d status code." %
2099
- (filename, e.code))
2100
-
2101
- lines = response_body.splitlines()
2102
- if not lines or lines[0] != "OK":
2103
- StatusUpdate(" --> %s" % response_body)
2104
- sys.exit(1)
2105
- return ("Uploaded patch for " + filename, [lines[1], filename])
2106
-
2107
- threads = []
2108
- thread_pool = ThreadPool(options.num_upload_threads)
2109
-
2110
- patches = SplitPatch(data)
2111
- rv = []
2112
- for patch in patches:
2113
- if len(patch[1]) > MAX_UPLOAD_SIZE:
2114
- print("Not uploading the patch for %s because the file is too large." %
2115
- (patch[0],))
2116
- continue
2117
-
2118
- filename = patch[0]
2119
- data = patch[1]
2120
-
2121
- t = thread_pool.apply_async(UploadFile, args=(filename, data))
2122
- threads.append(t)
2123
-
2124
- for t in threads:
2125
- result = t.get(timeout=UPLOAD_TIMEOUT)
2126
- print(result[0])
2127
- rv.append(result[1])
2128
-
2129
- return rv
2130
-
2131
-
2132
- def GuessVCSName(options):
2133
- """Helper to guess the version control system.
2134
-
2135
- This examines the current directory, guesses which VersionControlSystem
2136
- we're using, and returns an string indicating which VCS is detected.
2137
-
2138
- Returns:
2139
- A pair (vcs, output). vcs is a string indicating which VCS was detected
2140
- and is one of VCS_GIT, VCS_MERCURIAL, VCS_SUBVERSION, VCS_PERFORCE,
2141
- VCS_CVS, or VCS_UNKNOWN.
2142
- Since local perforce repositories can't be easily detected, this method
2143
- will only guess VCS_PERFORCE if any perforce options have been specified.
2144
- output is a string containing any interesting output from the vcs
2145
- detection routine, or None if there is nothing interesting.
2146
- """
2147
- for attribute, value in options.__dict__.iteritems():
2148
- if attribute.startswith("p4") and value != None:
2149
- return (VCS_PERFORCE, None)
2150
-
2151
- def RunDetectCommand(vcs_type, command):
2152
- """Helper to detect VCS by executing command.
2153
-
2154
- Returns:
2155
- A pair (vcs, output) or None. Throws exception on error.
2156
- """
2157
- try:
2158
- out, returncode = RunShellWithReturnCode(command)
2159
- if returncode == 0:
2160
- return (vcs_type, out.strip())
2161
- except OSError as e:
2162
- if e.errno != errno.ENOENT: # command not found code
2163
- raise
2164
- except ValueError as e:
2165
- # Workaround for https://bugs.python.org/issue26083
2166
- if e.message != "insecure string pickle":
2167
- raise
2168
-
2169
- # Mercurial has a command to get the base directory of a repository
2170
- # Try running it, but don't die if we don't have hg installed.
2171
- # NOTE: we try Mercurial first as it can sit on top of an SVN working copy.
2172
- res = RunDetectCommand(VCS_MERCURIAL, ["hg", "root"])
2173
- if res != None:
2174
- return res
2175
-
2176
- # Subversion from 1.7 has a single centralized .svn folder
2177
- # ( see http://subversion.apache.org/docs/release-notes/1.7.html#wc-ng )
2178
- # That's why we use 'svn info' instead of checking for .svn dir
2179
- res = RunDetectCommand(VCS_SUBVERSION, ["svn", "info"])
2180
- if res != None:
2181
- return res
2182
-
2183
- # Git has a command to test if you're in a git tree.
2184
- # Try running it, but don't die if we don't have git installed.
2185
- res = RunDetectCommand(VCS_GIT, ["git", "rev-parse",
2186
- "--is-inside-work-tree"])
2187
- if res != None:
2188
- return res
2189
-
2190
- # detect CVS repos use `cvs status && $? == 0` rules
2191
- res = RunDetectCommand(VCS_CVS, ["cvs", "status"])
2192
- if res != None:
2193
- return res
2194
-
2195
- return (VCS_UNKNOWN, None)
2196
-
2197
-
2198
- def GuessVCS(options):
2199
- """Helper to guess the version control system.
2200
-
2201
- This verifies any user-specified VersionControlSystem (by command line
2202
- or environment variable). If the user didn't specify one, this examines
2203
- the current directory, guesses which VersionControlSystem we're using,
2204
- and returns an instance of the appropriate class. Exit with an error
2205
- if we can't figure it out.
2206
-
2207
- Returns:
2208
- A VersionControlSystem instance. Exits if the VCS can't be guessed.
2209
- """
2210
- vcs = options.vcs
2211
- if not vcs:
2212
- vcs = os.environ.get("CODEREVIEW_VCS")
2213
- if vcs:
2214
- v = VCS_ABBREVIATIONS.get(vcs.lower())
2215
- if v is None:
2216
- ErrorExit("Unknown version control system %r specified." % vcs)
2217
- (vcs, extra_output) = (v, None)
2218
- else:
2219
- (vcs, extra_output) = GuessVCSName(options)
2220
-
2221
- if vcs == VCS_MERCURIAL:
2222
- if extra_output is None:
2223
- extra_output = RunShell(["hg", "root"]).strip()
2224
- return MercurialVCS(options, extra_output)
2225
- elif vcs == VCS_SUBVERSION:
2226
- return SubversionVCS(options)
2227
- elif vcs == VCS_PERFORCE:
2228
- return PerforceVCS(options)
2229
- elif vcs == VCS_GIT:
2230
- return GitVCS(options)
2231
- elif vcs == VCS_CVS:
2232
- return CVSVCS(options)
2233
-
2234
- ErrorExit(("Could not guess version control system. "
2235
- "Are you in a working copy directory?"))
2236
-
2237
-
2238
- def CheckReviewer(reviewer):
2239
- """Validate a reviewer -- either a nickname or an email addres.
2240
-
2241
- Args:
2242
- reviewer: A nickname or an email address.
2243
-
2244
- Calls ErrorExit() if it is an invalid email address.
2245
- """
2246
- if "@" not in reviewer:
2247
- return # Assume nickname
2248
- parts = reviewer.split("@")
2249
- if len(parts) > 2:
2250
- ErrorExit("Invalid email address: %r" % reviewer)
2251
- assert len(parts) == 2
2252
- if "." not in parts[1]:
2253
- ErrorExit("Invalid email address: %r" % reviewer)
2254
-
2255
-
2256
- def LoadSubversionAutoProperties():
2257
- """Returns the content of [auto-props] section of Subversion's config file as
2258
- a dictionary.
2259
-
2260
- Returns:
2261
- A dictionary whose key-value pair corresponds the [auto-props] section's
2262
- key-value pair.
2263
- In following cases, returns empty dictionary:
2264
- - config file doesn't exist, or
2265
- - 'enable-auto-props' is not set to 'true-like-value' in [miscellany].
2266
- """
2267
- if os.name == 'nt':
2268
- subversion_config = os.environ.get("APPDATA") + "\\Subversion\\config"
2269
- else:
2270
- subversion_config = os.path.expanduser("~/.subversion/config")
2271
- if not os.path.exists(subversion_config):
2272
- return {}
2273
- config = configparser.ConfigParser()
2274
- config.read(subversion_config)
2275
- if (config.has_section("miscellany") and
2276
- config.has_option("miscellany", "enable-auto-props") and
2277
- config.getboolean("miscellany", "enable-auto-props") and
2278
- config.has_section("auto-props")):
2279
- props = {}
2280
- for file_pattern in config.options("auto-props"):
2281
- props[file_pattern] = ParseSubversionPropertyValues(
2282
- config.get("auto-props", file_pattern))
2283
- return props
2284
- else:
2285
- return {}
2286
-
2287
- def ParseSubversionPropertyValues(props):
2288
- """Parse the given property value which comes from [auto-props] section and
2289
- returns a list whose element is a (svn_prop_key, svn_prop_value) pair.
2290
-
2291
- See the following doctest for example.
2292
-
2293
- >>> ParseSubversionPropertyValues('svn:eol-style=LF')
2294
- [('svn:eol-style', 'LF')]
2295
- >>> ParseSubversionPropertyValues('svn:mime-type=image/jpeg')
2296
- [('svn:mime-type', 'image/jpeg')]
2297
- >>> ParseSubversionPropertyValues('svn:eol-style=LF;svn:executable')
2298
- [('svn:eol-style', 'LF'), ('svn:executable', '*')]
2299
- """
2300
- key_value_pairs = []
2301
- for prop in props.split(";"):
2302
- key_value = prop.split("=")
2303
- assert len(key_value) <= 2
2304
- if len(key_value) == 1:
2305
- # If value is not given, use '*' as a Subversion's convention.
2306
- key_value_pairs.append((key_value[0], "*"))
2307
- else:
2308
- key_value_pairs.append((key_value[0], key_value[1]))
2309
- return key_value_pairs
2310
-
2311
-
2312
- def GetSubversionPropertyChanges(filename):
2313
- """Return a Subversion's 'Property changes on ...' string, which is used in
2314
- the patch file.
2315
-
2316
- Args:
2317
- filename: filename whose property might be set by [auto-props] config.
2318
-
2319
- Returns:
2320
- A string like 'Property changes on |filename| ...' if given |filename|
2321
- matches any entries in [auto-props] section. None, otherwise.
2322
- """
2323
- global svn_auto_props_map
2324
- if svn_auto_props_map is None:
2325
- svn_auto_props_map = LoadSubversionAutoProperties()
2326
-
2327
- all_props = []
2328
- for file_pattern, props in svn_auto_props_map.items():
2329
- if fnmatch.fnmatch(filename, file_pattern):
2330
- all_props.extend(props)
2331
- if all_props:
2332
- return FormatSubversionPropertyChanges(filename, all_props)
2333
- return None
2334
-
2335
-
2336
- def FormatSubversionPropertyChanges(filename, props):
2337
- """Returns Subversion's 'Property changes on ...' strings using given filename
2338
- and properties.
2339
-
2340
- Args:
2341
- filename: filename
2342
- props: A list whose element is a (svn_prop_key, svn_prop_value) pair.
2343
-
2344
- Returns:
2345
- A string which can be used in the patch file for Subversion.
2346
-
2347
- See the following doctest for example.
2348
-
2349
- >>> print FormatSubversionPropertyChanges('foo.cc', [('svn:eol-style', 'LF')])
2350
- Property changes on: foo.cc
2351
- ___________________________________________________________________
2352
- Added: svn:eol-style
2353
- + LF
2354
- <BLANKLINE>
2355
- """
2356
- prop_changes_lines = [
2357
- "Property changes on: %s" % filename,
2358
- "___________________________________________________________________"]
2359
- for key, value in props:
2360
- prop_changes_lines.append("Added: " + key)
2361
- prop_changes_lines.append(" + " + value)
2362
- return "\n".join(prop_changes_lines) + "\n"
2363
-
2364
-
2365
- def RealMain(argv, data=None):
2366
- """The real main function.
2367
-
2368
- Args:
2369
- argv: Command line arguments.
2370
- data: Diff contents. If None (default) the diff is generated by
2371
- the VersionControlSystem implementation returned by GuessVCS().
2372
-
2373
- Returns:
2374
- A 2-tuple (issue id, patchset id).
2375
- The patchset id is None if the base files are not uploaded by this
2376
- script (applies only to SVN checkouts).
2377
- """
2378
- options, args = parser.parse_args(argv[1:])
2379
- if options.help:
2380
- if options.verbose < 2:
2381
- # hide Perforce options
2382
- parser.epilog = (
2383
- "Use '--help -v' to show additional Perforce options. "
2384
- "For more help, see "
2385
- "http://code.google.com/p/rietveld/wiki/CodeReviewHelp"
2386
- )
2387
- parser.option_groups.remove(parser.get_option_group('--p4_port'))
2388
- parser.print_help()
2389
- sys.exit(0)
2390
-
2391
- global verbosity
2392
- verbosity = options.verbose
2393
- if verbosity >= 3:
2394
- LOGGER.setLevel(logging.DEBUG)
2395
- elif verbosity >= 2:
2396
- LOGGER.setLevel(logging.INFO)
2397
-
2398
- vcs = GuessVCS(options)
2399
-
2400
- if options.download_base:
2401
- options.download_base = True
2402
- LOGGER.info("Enabled upload of base file")
2403
- if not options.assume_yes:
2404
- vcs.CheckForUnknownFiles()
2405
- if data is None:
2406
- data = vcs.GenerateDiff(args)
2407
- data = vcs.PostProcessDiff(data)
2408
- if options.print_diffs:
2409
- print("Rietveld diff start:*****")
2410
- print(data)
2411
- print("Rietveld diff end:*****")
2412
- files = vcs.GetBaseFiles(data)
2413
- if verbosity >= 1:
2414
- print("Upload server:", options.server, "(change with -s/--server)")
2415
-
2416
- auth_config = auth.extract_auth_config_from_options(options)
2417
- rpc_server = GetRpcServer(options.server, auth_config, options.email)
2418
- form_fields = []
2419
-
2420
- repo_guid = vcs.GetGUID()
2421
- if repo_guid:
2422
- form_fields.append(("repo_guid", repo_guid))
2423
- if options.issue:
2424
- form_fields.append(("issue", str(options.issue)))
2425
- if options.email:
2426
- form_fields.append(("user", options.email))
2427
- if options.reviewers:
2428
- for reviewer in options.reviewers.split(','):
2429
- CheckReviewer(reviewer)
2430
- form_fields.append(("reviewers", options.reviewers))
2431
- if options.cc:
2432
- for cc in options.cc.split(','):
2433
- CheckReviewer(cc)
2434
- form_fields.append(("cc", options.cc))
2435
- if options.project:
2436
- form_fields.append(("project", options.project))
2437
- if options.target_ref:
2438
- form_fields.append(("target_ref", options.target_ref))
2439
- if options.cq_dry_run:
2440
- form_fields.append(("cq_dry_run", "1"))
2441
- form_fields.append(("commit", "1"))
2442
- if options.depends_on_patchset:
2443
- form_fields.append(("depends_on_patchset", options.depends_on_patchset))
2444
-
2445
- # Process --message, --title and --file.
2446
- message = options.message or ""
2447
- explicit_title = options.title is not None
2448
- title = options.title or ""
2449
- if options.file:
2450
- if options.message:
2451
- ErrorExit("Can't specify both message and message file options")
2452
- file = open(options.file, 'r')
2453
- message = file.read()
2454
- file.close()
2455
- title = title or message.split('\n', 1)[0].strip()
2456
- if not title and not explicit_title:
2457
- if options.issue:
2458
- prompt = "Title describing this patch set"
2459
- else:
2460
- prompt = "New issue subject"
2461
- title_default = vcs.GetMostRecentCommitSummary()
2462
- if title_default:
2463
- prompt += " [%s]" % title_default
2464
- title = raw_input(prompt + ": ").strip() or title_default
2465
- if not title and not options.issue:
2466
- ErrorExit("A non-empty title is required for a new issue")
2467
- # For existing issues, it's fine to give a patchset an empty name. Rietveld
2468
- # doesn't accept that so use a whitespace.
2469
- title = title or " "
2470
- if len(title) > 100:
2471
- title = title[:99] + '…'
2472
- if title and not options.issue:
2473
- message = message or title
2474
-
2475
- form_fields.append(("subject", title))
2476
- # If it's a new issue send message as description. Otherwise a new
2477
- # message is created below on upload_complete.
2478
- if message and not options.issue:
2479
- form_fields.append(("description", message))
2480
-
2481
- # Send a hash of all the base file so the server can determine if a copy
2482
- # already exists in an earlier patchset.
2483
- base_hashes = ""
2484
- for file, info in files.iteritems():
2485
- if not info[0] is None:
2486
- checksum = md5(info[0]).hexdigest()
2487
- if base_hashes:
2488
- base_hashes += "|"
2489
- base_hashes += checksum + ":" + file
2490
- form_fields.append(("base_hashes", base_hashes))
2491
- if options.private:
2492
- if options.issue:
2493
- print("Warning: Private flag ignored when updating an existing issue.")
2494
- else:
2495
- form_fields.append(("private", "1"))
2496
- if options.send_patch:
2497
- options.send_mail = True
2498
- if not options.download_base:
2499
- form_fields.append(("content_upload", "1"))
2500
- if len(data) > MAX_UPLOAD_SIZE:
2501
- print("Patch is large, so uploading file patches separately.")
2502
- uploaded_diff_file = []
2503
- form_fields.append(("separate_patches", "1"))
2504
- else:
2505
- uploaded_diff_file = [("data", "data.diff", data)]
2506
- ctype, body = EncodeMultipartFormData(form_fields, uploaded_diff_file)
2507
- response_body = rpc_server.Send("/upload", body, content_type=ctype)
2508
- issue, patchset = None, None
2509
- if not options.download_base or not uploaded_diff_file:
2510
- lines = response_body.splitlines()
2511
- if len(lines) >= 2:
2512
- # lines[0] is "Issue (created|updated): <url>".
2513
- issue = lines[0][lines[0].rfind("/")+1:]
2514
- # lines[1] is just patchset number.
2515
- patchset = lines[1].strip()
2516
- msg = '%s (patchset: %s)' % (lines[0], patchset)
2517
- patches = [x.split(" ", 1) for x in lines[2:]]
2518
- else:
2519
- msg = response_body
2520
- else:
2521
- msg = response_body
2522
- StatusUpdate(msg)
2523
- if not response_body.startswith("Issue created.") and \
2524
- not response_body.startswith("Issue updated."):
2525
- sys.exit(0)
2526
- assert issue
2527
-
2528
- if not uploaded_diff_file:
2529
- result = UploadSeparatePatches(issue, rpc_server, patchset, data, options)
2530
- if not options.download_base:
2531
- patches = result
2532
-
2533
- if not options.download_base:
2534
- vcs.UploadBaseFiles(issue, rpc_server, patches, patchset, options, files)
2535
-
2536
- payload = {} # payload for final request
2537
- if options.send_mail:
2538
- payload["send_mail"] = "yes"
2539
- if options.send_patch:
2540
- payload["attach_patch"] = "yes"
2541
- if options.issue and message:
2542
- payload["message"] = message
2543
- payload = urllib.urlencode(payload)
2544
- rpc_server.Send("/" + issue + "/upload_complete/" + (patchset or ""),
2545
- payload=payload)
2546
- return issue, patchset
2547
-
2548
-
2549
- def main():
2550
- try:
2551
- logging.basicConfig(format=("%(asctime).19s %(levelname)s %(filename)s:"
2552
- "%(lineno)s %(message)s "))
2553
- os.environ['LC_ALL'] = 'C'
2554
- RealMain(sys.argv)
2555
- except KeyboardInterrupt:
2556
- print
2557
- StatusUpdate("Interrupted.")
2558
- sys.exit(1)
2559
- except auth.AuthenticationError as e:
2560
- print(e, file=sys.stderr)
2561
- sys.exit(1)
2562
-
2563
-
2564
- if __name__ == "__main__":
2565
- main()