libv8 4.5.95.5 → 5.0.71.48.0beta2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +45 -19
- data/CHANGELOG.md +14 -0
- data/README.md +30 -15
- data/Rakefile +7 -6
- data/ext/libv8/arch.rb +5 -4
- data/ext/libv8/builder.rb +25 -19
- data/ext/libv8/compiler.rb +6 -33
- data/ext/libv8/location.rb +7 -8
- data/lib/libv8/version.rb +1 -1
- data/libv8.gemspec +1 -1
- data/patches/build-standalone-static-library.patch +14 -0
- data/patches/disable-building-tests.patch +48 -10
- data/patches/fPIC-for-static.patch +3 -3
- data/release/x86-linux/Vagrantfile +8 -4
- data/release/x86_64-freebsd10/Vagrantfile +86 -0
- data/release/x86_64-linux/Vagrantfile +8 -4
- data/spec/compiler_spec.rb +5 -29
- data/spec/support/compiler_helpers.rb +2 -4
- data/vendor/depot_tools/.gitignore +15 -3
- data/vendor/depot_tools/OWNERS +2 -2
- data/vendor/depot_tools/PRESUBMIT.py +4 -2
- data/vendor/depot_tools/WATCHLISTS +6 -0
- data/vendor/depot_tools/apply_issue.py +70 -38
- data/vendor/depot_tools/bootstrap/win/README.md +66 -0
- data/vendor/depot_tools/bootstrap/win/git-bash.template.sh +12 -0
- data/vendor/depot_tools/bootstrap/win/git.template.bat +5 -0
- data/vendor/depot_tools/bootstrap/win/profile.d.python.sh +20 -0
- data/vendor/depot_tools/bootstrap/win/win_tools.bat +96 -45
- data/vendor/depot_tools/breakpad.py +6 -141
- data/vendor/depot_tools/buildbucket.py +45 -31
- data/vendor/depot_tools/cbuildbot +1 -0
- data/vendor/depot_tools/checkout.py +2 -1
- data/vendor/depot_tools/chrome_set_ver +1 -0
- data/vendor/depot_tools/cit +8 -0
- data/vendor/depot_tools/cit.bat +11 -0
- data/vendor/depot_tools/cit.py +120 -0
- data/vendor/depot_tools/codereview.settings +0 -2
- data/vendor/depot_tools/commit_queue +1 -5
- data/vendor/depot_tools/commit_queue.bat +1 -4
- data/vendor/depot_tools/commit_queue.py +78 -29
- data/vendor/depot_tools/cpplint.py +22 -14
- data/vendor/depot_tools/cros +1 -0
- data/vendor/depot_tools/cros_sdk +1 -0
- data/vendor/depot_tools/depot-tools-auth.py +3 -3
- data/vendor/depot_tools/download_from_google_storage.py +101 -21
- data/vendor/depot_tools/drover.py +2 -3
- data/vendor/depot_tools/fetch.py +31 -27
- data/vendor/depot_tools/{recipes → fetch_configs}/android.py +4 -4
- data/vendor/depot_tools/fetch_configs/breakpad.py +45 -0
- data/vendor/depot_tools/{recipes → fetch_configs}/chromium.py +3 -3
- data/vendor/depot_tools/{recipes/recipe_util.py → fetch_configs/config_util.py} +3 -3
- data/vendor/depot_tools/fetch_configs/crashpad.py +41 -0
- data/vendor/depot_tools/{recipes → fetch_configs}/dart.py +3 -3
- data/vendor/depot_tools/{recipes/pdfium.py → fetch_configs/dartino.py} +14 -13
- data/vendor/depot_tools/{recipes → fetch_configs}/dartium.py +3 -3
- data/vendor/depot_tools/{recipes → fetch_configs}/depot_tools.py +3 -3
- data/vendor/depot_tools/fetch_configs/gyp.py +42 -0
- data/vendor/depot_tools/{recipes → fetch_configs}/infra.py +3 -3
- data/vendor/depot_tools/{recipes → fetch_configs}/infra_internal.py +3 -3
- data/vendor/depot_tools/{recipes → fetch_configs}/ios.py +4 -4
- data/vendor/depot_tools/{recipes → fetch_configs}/mojo.py +3 -3
- data/vendor/depot_tools/{recipes → fetch_configs}/nacl.py +3 -3
- data/vendor/depot_tools/{recipes → fetch_configs}/naclports.py +3 -3
- data/vendor/depot_tools/fetch_configs/pdfium.py +40 -0
- data/vendor/depot_tools/{recipes → fetch_configs}/skia.py +3 -3
- data/vendor/depot_tools/{recipes → fetch_configs}/skia_buildbot.py +3 -3
- data/vendor/depot_tools/fetch_configs/syzygy.py +41 -0
- data/vendor/depot_tools/{recipes → fetch_configs}/v8.py +3 -3
- data/vendor/depot_tools/{recipes → fetch_configs}/webrtc.py +3 -3
- data/vendor/depot_tools/{recipes → fetch_configs}/webrtc_android.py +4 -4
- data/vendor/depot_tools/{recipes → fetch_configs}/webrtc_ios.py +4 -4
- data/vendor/depot_tools/fix_encoding.py +6 -6
- data/vendor/depot_tools/gcl.py +11 -21
- data/vendor/depot_tools/gclient +10 -0
- data/vendor/depot_tools/gclient-new-workdir.py +7 -38
- data/vendor/depot_tools/gclient.bat +2 -2
- data/vendor/depot_tools/gclient.py +85 -65
- data/vendor/depot_tools/gclient_scm.py +83 -10
- data/vendor/depot_tools/gclient_utils.py +5 -1
- data/vendor/depot_tools/gerrit_util.py +243 -26
- data/vendor/depot_tools/git-auto-svn +1 -1
- data/vendor/depot_tools/git-cache +1 -1
- data/vendor/depot_tools/git-cherry-pick-upload +1 -1
- data/vendor/depot_tools/git-cl +1 -1
- data/vendor/depot_tools/git-drover +6 -0
- data/vendor/depot_tools/git-find-releases +6 -0
- data/vendor/depot_tools/git-footers +1 -1
- data/vendor/depot_tools/git-freeze +1 -1
- data/vendor/depot_tools/git-gs +1 -1
- data/vendor/depot_tools/git-hyper-blame +6 -0
- data/vendor/depot_tools/git-map +1 -1
- data/vendor/depot_tools/git-map-branches +1 -1
- data/vendor/depot_tools/git-mark-merge-base +1 -1
- data/vendor/depot_tools/git-nav-downstream +1 -1
- data/vendor/depot_tools/git-new-branch +1 -1
- data/vendor/depot_tools/git-number +1 -1
- data/vendor/depot_tools/git-rebase-update +1 -1
- data/vendor/depot_tools/git-rename-branch +1 -1
- data/vendor/depot_tools/git-reparent-branch +1 -1
- data/vendor/depot_tools/git-retry +1 -1
- data/vendor/depot_tools/git-squash-branch +1 -1
- data/vendor/depot_tools/git-thaw +1 -1
- data/vendor/depot_tools/git-try +1 -1
- data/vendor/depot_tools/git-upstream-diff +1 -1
- data/vendor/depot_tools/git_auto_svn.py +24 -6
- data/vendor/depot_tools/git_cache.py +74 -27
- data/vendor/depot_tools/git_cl.py +2118 -747
- data/vendor/depot_tools/git_common.py +100 -6
- data/vendor/depot_tools/git_dates.py +62 -0
- data/vendor/depot_tools/git_drover.py +424 -0
- data/vendor/depot_tools/git_find_releases.py +65 -0
- data/vendor/depot_tools/git_footers.py +42 -0
- data/vendor/depot_tools/git_hyper_blame.py +391 -0
- data/vendor/depot_tools/git_map_branches.py +8 -6
- data/vendor/depot_tools/git_new_branch.py +6 -1
- data/vendor/depot_tools/git_rebase_update.py +56 -16
- data/vendor/depot_tools/git_reparent_branch.py +13 -0
- data/vendor/depot_tools/git_try.py +0 -2
- data/vendor/depot_tools/gsutil.py +51 -20
- data/vendor/depot_tools/infra/config/OWNERS +3 -1
- data/vendor/depot_tools/infra/config/cq.cfg +7 -3
- data/vendor/depot_tools/infra/config/recipes.cfg +9 -0
- data/vendor/depot_tools/luci_hacks/README.md +35 -0
- data/vendor/depot_tools/{bootstrap/virtualenv/tests → luci_hacks}/__init__.py +0 -0
- data/vendor/depot_tools/luci_hacks/luci_recipe_run.isolate +12 -0
- data/vendor/depot_tools/luci_hacks/luci_recipe_run.py +81 -0
- data/vendor/depot_tools/luci_hacks/trigger_luci_job.py +128 -0
- data/vendor/depot_tools/man/html/depot_tools.html +9 -1
- data/vendor/depot_tools/man/html/depot_tools_tutorial.html +4 -4
- data/vendor/depot_tools/man/html/git-drover.html +191 -35
- data/vendor/depot_tools/man/html/git-hyper-blame.html +878 -0
- data/vendor/depot_tools/man/html/git-rebase-update.html +9 -4
- data/vendor/depot_tools/man/man1/git-drover.1 +189 -36
- data/vendor/depot_tools/man/man1/git-hyper-blame.1 +128 -0
- data/vendor/depot_tools/man/man1/git-rebase-update.1 +8 -6
- data/vendor/depot_tools/man/man7/depot_tools.7 +9 -4
- data/vendor/depot_tools/man/src/_git-hyper-blame_desc.helper.txt +1 -0
- data/vendor/depot_tools/man/src/common_demo_functions.sh +5 -0
- data/vendor/depot_tools/man/src/depot_tools_tutorial.txt +1 -1
- data/vendor/depot_tools/man/src/git-drover.demo.1.sh +11 -16
- data/vendor/depot_tools/man/src/git-drover.demo.3.sh +27 -0
- data/vendor/depot_tools/man/src/git-drover.demo.4.sh +39 -0
- data/vendor/depot_tools/man/src/git-drover.txt +49 -3
- data/vendor/depot_tools/man/src/git-hyper-blame.demo.1.sh +3 -0
- data/vendor/depot_tools/man/src/git-hyper-blame.demo.2.sh +4 -0
- data/vendor/depot_tools/man/src/git-hyper-blame.demo.common.sh +57 -0
- data/vendor/depot_tools/man/src/git-hyper-blame.txt +85 -0
- data/vendor/depot_tools/man/src/git-rebase-update.txt +5 -1
- data/vendor/depot_tools/my_activity.py +6 -21
- data/vendor/depot_tools/ninja +2 -2
- data/vendor/depot_tools/ninja-linux32 +0 -0
- data/vendor/depot_tools/ninja-linux64 +0 -0
- data/vendor/depot_tools/ninja-mac +0 -0
- data/vendor/depot_tools/ninja.exe +0 -0
- data/vendor/depot_tools/presubmit_canned_checks.py +83 -69
- data/vendor/depot_tools/presubmit_support.py +126 -42
- data/vendor/depot_tools/pylint.py +5 -1
- data/vendor/depot_tools/python_runner.sh +55 -0
- data/vendor/depot_tools/recipe_modules/bot_update/__init__.py +32 -0
- data/vendor/depot_tools/recipe_modules/bot_update/api.py +283 -0
- data/vendor/depot_tools/recipe_modules/bot_update/example.expected/basic.json +56 -0
- data/vendor/depot_tools/recipe_modules/bot_update/example.expected/basic_output_manifest.json +63 -0
- data/vendor/depot_tools/recipe_modules/bot_update/example.expected/basic_with_branch_heads.json +57 -0
- data/vendor/depot_tools/recipe_modules/bot_update/example.expected/clobber.json +44 -0
- data/vendor/depot_tools/recipe_modules/bot_update/example.expected/forced.json +57 -0
- data/vendor/depot_tools/recipe_modules/bot_update/example.expected/gerrit_no_reset.json +44 -0
- data/vendor/depot_tools/recipe_modules/bot_update/example.expected/no_shallow.json +44 -0
- data/vendor/depot_tools/recipe_modules/bot_update/example.expected/off.json +43 -0
- data/vendor/depot_tools/recipe_modules/bot_update/example.expected/reset_root_solution_revision.json +43 -0
- data/vendor/depot_tools/recipe_modules/bot_update/example.expected/svn_mode.json +59 -0
- data/vendor/depot_tools/recipe_modules/bot_update/example.expected/trychange.json +58 -0
- data/vendor/depot_tools/recipe_modules/bot_update/example.expected/trychange_oauth2.json +60 -0
- data/vendor/depot_tools/recipe_modules/bot_update/example.expected/tryjob.json +58 -0
- data/vendor/depot_tools/recipe_modules/bot_update/example.expected/tryjob_fail.json +60 -0
- data/vendor/depot_tools/recipe_modules/bot_update/example.expected/tryjob_fail_patch.json +81 -0
- data/vendor/depot_tools/recipe_modules/bot_update/example.expected/tryjob_fail_patch_download.json +81 -0
- data/vendor/depot_tools/recipe_modules/bot_update/example.expected/tryjob_gerrit_angle.json +49 -0
- data/vendor/depot_tools/recipe_modules/bot_update/example.expected/tryjob_v8.json +61 -0
- data/vendor/depot_tools/recipe_modules/bot_update/example.expected/tryjob_v8_head_by_default.json +51 -0
- data/vendor/depot_tools/recipe_modules/bot_update/example.py +172 -0
- data/vendor/depot_tools/{bootstrap/virtualenv/virtualenv_support → recipe_modules/bot_update/resources}/__init__.py +0 -0
- data/vendor/depot_tools/recipe_modules/bot_update/resources/bot_update.py +1764 -0
- data/vendor/depot_tools/recipe_modules/bot_update/test_api.py +86 -0
- data/vendor/depot_tools/recipe_modules/depot_tools/__init__.py +3 -0
- data/vendor/depot_tools/recipe_modules/depot_tools/api.py +27 -0
- data/vendor/depot_tools/recipe_modules/gclient/__init__.py +10 -0
- data/vendor/depot_tools/recipe_modules/gclient/api.py +378 -0
- data/vendor/depot_tools/recipe_modules/gclient/config.py +671 -0
- data/vendor/depot_tools/recipe_modules/gclient/example.expected/basic.json +172 -0
- data/vendor/depot_tools/recipe_modules/gclient/example.expected/revision.json +174 -0
- data/vendor/depot_tools/recipe_modules/gclient/example.expected/tryserver.json +185 -0
- data/vendor/depot_tools/recipe_modules/gclient/example.py +100 -0
- data/vendor/depot_tools/recipe_modules/gclient/test_api.py +37 -0
- data/vendor/depot_tools/recipe_modules/git/__init__.py +9 -0
- data/vendor/depot_tools/recipe_modules/git/api.py +377 -0
- data/vendor/depot_tools/recipe_modules/git/example.expected/basic.json +177 -0
- data/vendor/depot_tools/recipe_modules/git/example.expected/basic_branch.json +177 -0
- data/vendor/depot_tools/recipe_modules/git/example.expected/basic_file_name.json +179 -0
- data/vendor/depot_tools/recipe_modules/git/example.expected/basic_hash.json +176 -0
- data/vendor/depot_tools/recipe_modules/git/example.expected/basic_ref.json +177 -0
- data/vendor/depot_tools/recipe_modules/git/example.expected/basic_submodule_update_force.json +178 -0
- data/vendor/depot_tools/recipe_modules/git/example.expected/can_fail_build.json +153 -0
- data/vendor/depot_tools/recipe_modules/git/example.expected/cannot_fail_build.json +181 -0
- data/vendor/depot_tools/recipe_modules/git/example.expected/cat-file_test.json +199 -0
- data/vendor/depot_tools/recipe_modules/git/example.expected/count-objects_delta.json +250 -0
- data/vendor/depot_tools/recipe_modules/git/example.expected/count-objects_failed.json +181 -0
- data/vendor/depot_tools/recipe_modules/git/example.expected/count-objects_with_bad_output.json +182 -0
- data/vendor/depot_tools/recipe_modules/git/example.expected/count-objects_with_bad_output_fails_build.json +102 -0
- data/vendor/depot_tools/recipe_modules/git/example.expected/curl_trace_file.json +181 -0
- data/vendor/depot_tools/recipe_modules/git/example.expected/platform_win.json +186 -0
- data/vendor/depot_tools/recipe_modules/git/example.expected/rebase_failed.json +179 -0
- data/vendor/depot_tools/recipe_modules/git/example.expected/remote_not_origin.json +179 -0
- data/vendor/depot_tools/recipe_modules/git/example.expected/set_got_revision.json +178 -0
- data/vendor/depot_tools/recipe_modules/git/example.py +147 -0
- data/vendor/depot_tools/recipe_modules/git/resources/git_setup.py +61 -0
- data/vendor/depot_tools/recipe_modules/git/test_api.py +18 -0
- data/vendor/depot_tools/recipe_modules/git_cl/__init__.py +4 -0
- data/vendor/depot_tools/recipe_modules/git_cl/api.py +25 -0
- data/vendor/depot_tools/recipe_modules/git_cl/config.py +22 -0
- data/vendor/depot_tools/recipe_modules/git_cl/example.expected/basic.json +66 -0
- data/vendor/depot_tools/recipe_modules/git_cl/example.py +41 -0
- data/vendor/depot_tools/recipe_modules/infra_paths/__init__.py +4 -0
- data/vendor/depot_tools/recipe_modules/infra_paths/api.py +12 -0
- data/vendor/depot_tools/recipe_modules/infra_paths/example.expected/basic.json +14 -0
- data/vendor/depot_tools/recipe_modules/infra_paths/example.expected/paths_buildbot_linux.json +14 -0
- data/vendor/depot_tools/recipe_modules/infra_paths/example.expected/paths_buildbot_mac.json +14 -0
- data/vendor/depot_tools/recipe_modules/infra_paths/example.expected/paths_buildbot_win.json +14 -0
- data/vendor/depot_tools/recipe_modules/infra_paths/example.expected/paths_kitchen_linux.json +14 -0
- data/vendor/depot_tools/recipe_modules/infra_paths/example.expected/paths_kitchen_mac.json +14 -0
- data/vendor/depot_tools/recipe_modules/infra_paths/example.expected/paths_kitchen_win.json +14 -0
- data/vendor/depot_tools/recipe_modules/infra_paths/example.py +28 -0
- data/vendor/depot_tools/recipe_modules/infra_paths/path_config.py +45 -0
- data/vendor/depot_tools/recipe_modules/presubmit/__init__.py +4 -0
- data/vendor/depot_tools/recipe_modules/presubmit/api.py +20 -0
- data/vendor/depot_tools/recipe_modules/presubmit/example.expected/basic.json +18 -0
- data/vendor/depot_tools/recipe_modules/presubmit/example.py +15 -0
- data/vendor/depot_tools/recipe_modules/rietveld/__init__.py +5 -0
- data/vendor/depot_tools/recipe_modules/rietveld/api.py +94 -0
- data/vendor/depot_tools/recipe_modules/rietveld/example.expected/basic.json +30 -0
- data/vendor/depot_tools/recipe_modules/rietveld/example.py +24 -0
- data/vendor/depot_tools/recipe_modules/tryserver/__init__.py +15 -0
- data/vendor/depot_tools/recipe_modules/tryserver/api.py +280 -0
- data/vendor/depot_tools/recipe_modules/tryserver/example.expected/with_git_patch.json +104 -0
- data/vendor/depot_tools/recipe_modules/tryserver/example.expected/with_rietveld_patch.json +58 -0
- data/vendor/depot_tools/recipe_modules/tryserver/example.expected/with_rietveld_patch_new.json +58 -0
- data/vendor/depot_tools/recipe_modules/tryserver/example.expected/with_svn_patch.json +68 -0
- data/vendor/depot_tools/recipe_modules/tryserver/example.expected/with_wrong_patch.json +43 -0
- data/vendor/depot_tools/recipe_modules/tryserver/example.expected/with_wrong_patch_new.json +43 -0
- data/vendor/depot_tools/recipe_modules/tryserver/example.py +53 -0
- data/vendor/depot_tools/recipe_modules/tryserver/test_api.py +7 -0
- data/vendor/depot_tools/recipes.py +136 -0
- data/vendor/depot_tools/repo +1 -1
- data/vendor/depot_tools/rietveld.py +46 -15
- data/vendor/depot_tools/roll_dep.py +97 -36
- data/vendor/depot_tools/scm.py +3 -3
- data/vendor/depot_tools/setup_color.py +94 -0
- data/vendor/depot_tools/subprocess2.py +10 -1
- data/vendor/depot_tools/third_party/cq_client/OWNERS +0 -1
- data/vendor/depot_tools/third_party/cq_client/README.md +47 -9
- data/vendor/depot_tools/third_party/cq_client/cq.pb.go +617 -0
- data/vendor/depot_tools/third_party/cq_client/cq.proto +75 -17
- data/vendor/depot_tools/third_party/cq_client/cq_pb2.py +168 -41
- data/vendor/depot_tools/third_party/cq_client/testdata/cq_gerrit.cfg +55 -0
- data/vendor/depot_tools/third_party/cq_client/{test/cq_example.cfg → testdata/cq_rietveld.cfg} +14 -6
- data/vendor/depot_tools/third_party/fancy_urllib/README +5 -4
- data/vendor/depot_tools/third_party/fancy_urllib/__init__.py +114 -52
- data/vendor/depot_tools/third_party/protobuf26/README.chromium +9 -6
- data/vendor/depot_tools/third_party/upload.py +17 -31
- data/vendor/depot_tools/trychange.py +0 -2
- data/vendor/depot_tools/update_depot_tools +29 -11
- data/vendor/depot_tools/update_depot_tools.bat +4 -9
- data/vendor/depot_tools/upload_to_google_storage.py +42 -5
- data/vendor/depot_tools/win_toolchain/OWNERS +1 -0
- data/vendor/depot_tools/win_toolchain/get_toolchain_if_necessary.py +227 -52
- data/vendor/depot_tools/win_toolchain/package_from_installed.py +203 -88
- metadata +161 -81
- data/patches/arm/do-not-imply-vfp3-and-armv7.patch +0 -16
- data/patches/arm/do-not-use-vfp2.patch +0 -13
- data/patches/clang51/no-unused-variable.patch +0 -12
- data/vendor/depot_tools/bootstrap/.gitignore +0 -2
- data/vendor/depot_tools/bootstrap/bootstrap.py +0 -234
- data/vendor/depot_tools/bootstrap/deps.pyl +0 -15
- data/vendor/depot_tools/bootstrap/util.py +0 -87
- data/vendor/depot_tools/bootstrap/virtualenv/.gitignore +0 -10
- data/vendor/depot_tools/bootstrap/virtualenv/.travis.yml +0 -28
- data/vendor/depot_tools/bootstrap/virtualenv/AUTHORS.txt +0 -91
- data/vendor/depot_tools/bootstrap/virtualenv/CONTRIBUTING.rst +0 -21
- data/vendor/depot_tools/bootstrap/virtualenv/LICENSE.txt +0 -22
- data/vendor/depot_tools/bootstrap/virtualenv/MANIFEST.in +0 -11
- data/vendor/depot_tools/bootstrap/virtualenv/README.rst +0 -10
- data/vendor/depot_tools/bootstrap/virtualenv/bin/rebuild-script.py +0 -71
- data/vendor/depot_tools/bootstrap/virtualenv/docs/changes.rst +0 -747
- data/vendor/depot_tools/bootstrap/virtualenv/docs/conf.py +0 -149
- data/vendor/depot_tools/bootstrap/virtualenv/docs/development.rst +0 -61
- data/vendor/depot_tools/bootstrap/virtualenv/docs/index.rst +0 -137
- data/vendor/depot_tools/bootstrap/virtualenv/docs/installation.rst +0 -58
- data/vendor/depot_tools/bootstrap/virtualenv/docs/make.bat +0 -170
- data/vendor/depot_tools/bootstrap/virtualenv/docs/reference.rst +0 -256
- data/vendor/depot_tools/bootstrap/virtualenv/docs/userguide.rst +0 -249
- data/vendor/depot_tools/bootstrap/virtualenv/scripts/virtualenv +0 -3
- data/vendor/depot_tools/bootstrap/virtualenv/setup.py +0 -111
- data/vendor/depot_tools/bootstrap/virtualenv/tests/test_activate.sh +0 -94
- data/vendor/depot_tools/bootstrap/virtualenv/tests/test_activate_expected.output +0 -2
- data/vendor/depot_tools/bootstrap/virtualenv/tests/test_virtualenv.py +0 -139
- data/vendor/depot_tools/bootstrap/virtualenv/tests/tox.ini +0 -12
- data/vendor/depot_tools/bootstrap/virtualenv/tox.ini +0 -17
- data/vendor/depot_tools/bootstrap/virtualenv/virtualenv.py +0 -2367
- data/vendor/depot_tools/bootstrap/virtualenv/virtualenv_embedded/activate.bat +0 -26
- data/vendor/depot_tools/bootstrap/virtualenv/virtualenv_embedded/activate.csh +0 -42
- data/vendor/depot_tools/bootstrap/virtualenv/virtualenv_embedded/activate.fish +0 -74
- data/vendor/depot_tools/bootstrap/virtualenv/virtualenv_embedded/activate.ps1 +0 -150
- data/vendor/depot_tools/bootstrap/virtualenv/virtualenv_embedded/activate.sh +0 -80
- data/vendor/depot_tools/bootstrap/virtualenv/virtualenv_embedded/activate_this.py +0 -34
- data/vendor/depot_tools/bootstrap/virtualenv/virtualenv_embedded/deactivate.bat +0 -20
- data/vendor/depot_tools/bootstrap/virtualenv/virtualenv_embedded/distutils-init.py +0 -101
- data/vendor/depot_tools/bootstrap/virtualenv/virtualenv_embedded/distutils.cfg +0 -6
- data/vendor/depot_tools/bootstrap/virtualenv/virtualenv_embedded/site.py +0 -758
- data/vendor/depot_tools/bootstrap/virtualenv/virtualenv_support/pip-6.0-py2.py3-none-any.whl +0 -0
- data/vendor/depot_tools/bootstrap/virtualenv/virtualenv_support/setuptools-8.2.1-py2.py3-none-any.whl +0 -0
- data/vendor/depot_tools/bootstrap/win/README.google +0 -16
- data/vendor/depot_tools/cbuildbot +0 -96
- data/vendor/depot_tools/chrome_set_ver +0 -96
- data/vendor/depot_tools/cros +0 -96
- data/vendor/depot_tools/cros_sdk +0 -96
- data/vendor/depot_tools/git-cl-upload-hook +0 -52
- data/vendor/depot_tools/git-crup +0 -45
- data/vendor/depot_tools/python_git_runner.sh +0 -36
- data/vendor/depot_tools/recipes/blink.py +0 -59
- data/vendor/depot_tools/third_party/cq_client/test/validate_config_test.py +0 -52
- data/vendor/depot_tools/third_party/cq_client/validate_config.py +0 -108
- data/vendor/depot_tools/win_toolchain/toolchain2013.py +0 -494
data/vendor/depot_tools/git-cl
CHANGED
data/vendor/depot_tools/git-gs
CHANGED
@@ -6,4 +6,4 @@ git grep -n -e "$@" -- "*.h" "*.hpp" "*.cpp" "*.c" "*.cc" "*.cpp" "*.inl"\
|
|
6
6
|
"*.grd" "*.grdp" "*.idl" "*.m" "*.mm" "*.py" "*.sh" "*.cfg" "*.tac" "*.go"\
|
7
7
|
"*SConscript" "SConscript*" "*.scons" "*.vcproj" "*.vsprops" "*.make"\
|
8
8
|
"*.gyp" "*.gypi" "*.isolate" "*.java" "*.js" "*.html" "*.css" "*.ebuild" \
|
9
|
-
"*.pl" "*.pm" "*.yaml" "*.gn" "*.gni" "*.json"
|
9
|
+
"*.pl" "*.pm" "*.yaml" "*.gn" "*.gni" "*.json" "DEPS" "*/DEPS"
|
data/vendor/depot_tools/git-map
CHANGED
data/vendor/depot_tools/git-thaw
CHANGED
data/vendor/depot_tools/git-try
CHANGED
@@ -22,7 +22,8 @@ import subprocess2
|
|
22
22
|
|
23
23
|
from git_common import run as run_git
|
24
24
|
from git_common import run_stream_with_retcode as run_git_stream_with_retcode
|
25
|
-
from git_common import set_config, root, ROOT
|
25
|
+
from git_common import set_config, root, ROOT, current_branch
|
26
|
+
from git_common import upstream as get_upstream
|
26
27
|
from git_footers import get_footer_svn_id
|
27
28
|
|
28
29
|
|
@@ -57,7 +58,24 @@ def main(argv):
|
|
57
58
|
description='Automatically set up git-svn for a repo mirrored from svn.')
|
58
59
|
parser.parse_args(argv)
|
59
60
|
|
60
|
-
|
61
|
+
upstreams = []
|
62
|
+
# Always configure the upstream trunk.
|
63
|
+
upstreams.append(root())
|
64
|
+
# Optionally configure whatever upstream branch might be currently checked
|
65
|
+
# out. This is needed for work on svn-based branches, otherwise git-svn gets
|
66
|
+
# very confused and tries to relate branch commits back to trunk, making a big
|
67
|
+
# mess of the codereview patches, and generating all kinds of spurious errors
|
68
|
+
# about the repo being in some sort of bad state.
|
69
|
+
curr_upstream = get_upstream(current_branch())
|
70
|
+
# There will be no upstream if the checkout is in detached HEAD.
|
71
|
+
if curr_upstream:
|
72
|
+
upstreams.append(curr_upstream)
|
73
|
+
for upstream in upstreams:
|
74
|
+
config_svn(upstream)
|
75
|
+
return 0
|
76
|
+
|
77
|
+
|
78
|
+
def config_svn(upstream):
|
61
79
|
svn_id = get_footer_svn_id(upstream)
|
62
80
|
assert svn_id, 'No valid git-svn-id footer found on %s.' % upstream
|
63
81
|
print 'Found git-svn-id footer %s on %s' % (svn_id, upstream)
|
@@ -86,14 +104,14 @@ def main(argv):
|
|
86
104
|
assert svn_repo is not None, 'Unable to find svn repo for %s' % svn_id
|
87
105
|
print 'Found upstream svn repo %s and path %s' % (svn_repo, svn_path)
|
88
106
|
|
89
|
-
|
90
|
-
|
91
|
-
|
107
|
+
run_git('config', '--local', '--replace-all', 'svn-remote.svn.url', svn_repo)
|
108
|
+
run_git('config', '--local', '--replace-all', 'svn-remote.svn.fetch',
|
109
|
+
'%s:refs/remotes/%s' % (svn_path, upstream),
|
110
|
+
'refs/remotes/%s$' % upstream)
|
92
111
|
print 'Configured metadata, running "git svn fetch". This may take some time.'
|
93
112
|
with run_git_stream_with_retcode('svn', 'fetch') as stdout:
|
94
113
|
for line in stdout.xreadlines():
|
95
114
|
print line.strip()
|
96
|
-
return 0
|
97
115
|
|
98
116
|
|
99
117
|
if __name__ == '__main__':
|
@@ -44,8 +44,9 @@ class RefsHeadsFailedToFetch(Exception):
|
|
44
44
|
class Lockfile(object):
|
45
45
|
"""Class to represent a cross-platform process-specific lockfile."""
|
46
46
|
|
47
|
-
def __init__(self, path):
|
47
|
+
def __init__(self, path, timeout=0):
|
48
48
|
self.path = os.path.abspath(path)
|
49
|
+
self.timeout = timeout
|
49
50
|
self.lockfile = self.path + ".lock"
|
50
51
|
self.pid = os.getpid()
|
51
52
|
|
@@ -91,16 +92,25 @@ class Lockfile(object):
|
|
91
92
|
def lock(self):
|
92
93
|
"""Acquire the lock.
|
93
94
|
|
94
|
-
|
95
|
-
Do. Or do not. There is no try.
|
95
|
+
This will block with a deadline of self.timeout seconds.
|
96
96
|
"""
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
97
|
+
elapsed = 0
|
98
|
+
while True:
|
99
|
+
try:
|
100
|
+
self._make_lockfile()
|
101
|
+
return
|
102
|
+
except OSError as e:
|
103
|
+
if elapsed < self.timeout:
|
104
|
+
sleep_time = max(10, min(3, self.timeout - elapsed))
|
105
|
+
logging.info('Could not create git cache lockfile; '
|
106
|
+
'will retry after sleep(%d).', sleep_time);
|
107
|
+
elapsed += sleep_time
|
108
|
+
time.sleep(sleep_time)
|
109
|
+
continue
|
110
|
+
if e.errno == errno.EEXIST:
|
111
|
+
raise LockError("%s is already locked" % self.path)
|
112
|
+
else:
|
113
|
+
raise LockError("Failed to create %s (err %s)" % (self.path, e.errno))
|
104
114
|
|
105
115
|
def unlock(self):
|
106
116
|
"""Release the lock."""
|
@@ -145,9 +155,24 @@ class Mirror(object):
|
|
145
155
|
os.path.dirname(os.path.abspath(__file__)), 'gsutil.py')
|
146
156
|
cachepath_lock = threading.Lock()
|
147
157
|
|
158
|
+
@staticmethod
|
159
|
+
def parse_fetch_spec(spec):
|
160
|
+
"""Parses and canonicalizes a fetch spec.
|
161
|
+
|
162
|
+
Returns (fetchspec, value_regex), where value_regex can be used
|
163
|
+
with 'git config --replace-all'.
|
164
|
+
"""
|
165
|
+
parts = spec.split(':', 1)
|
166
|
+
src = parts[0].lstrip('+').rstrip('/')
|
167
|
+
if not src.startswith('refs/'):
|
168
|
+
src = 'refs/heads/%s' % src
|
169
|
+
dest = parts[1].rstrip('/') if len(parts) > 1 else src
|
170
|
+
regex = r'\+%s:.*' % src.replace('*', r'\*')
|
171
|
+
return ('+%s:%s' % (src, dest), regex)
|
172
|
+
|
148
173
|
def __init__(self, url, refs=None, print_func=None):
|
149
174
|
self.url = url
|
150
|
-
self.
|
175
|
+
self.fetch_specs = set([self.parse_fetch_spec(ref) for ref in (refs or [])])
|
151
176
|
self.basedir = self.UrlToCacheDir(url)
|
152
177
|
self.mirror_path = os.path.join(self.GetCachePath(), self.basedir)
|
153
178
|
if print_func:
|
@@ -236,16 +261,9 @@ class Mirror(object):
|
|
236
261
|
self.RunGit(['config', 'remote.origin.url', self.url], cwd=cwd)
|
237
262
|
self.RunGit(['config', '--replace-all', 'remote.origin.fetch',
|
238
263
|
'+refs/heads/*:refs/heads/*', r'\+refs/heads/\*:.*'], cwd=cwd)
|
239
|
-
for
|
240
|
-
ref = ref.lstrip('+').rstrip('/')
|
241
|
-
if ref.startswith('refs/'):
|
242
|
-
refspec = '+%s:%s' % (ref, ref)
|
243
|
-
regex = r'\+%s:.*' % ref.replace('*', r'\*')
|
244
|
-
else:
|
245
|
-
refspec = '+refs/%s/*:refs/%s/*' % (ref, ref)
|
246
|
-
regex = r'\+refs/heads/%s:.*' % ref.replace('*', r'\*')
|
264
|
+
for spec, value_regex in self.fetch_specs:
|
247
265
|
self.RunGit(
|
248
|
-
['config', '--replace-all', 'remote.origin.fetch',
|
266
|
+
['config', '--replace-all', 'remote.origin.fetch', spec, value_regex],
|
249
267
|
cwd=cwd)
|
250
268
|
|
251
269
|
def bootstrap_repo(self, directory):
|
@@ -314,9 +332,27 @@ class Mirror(object):
|
|
314
332
|
def exists(self):
|
315
333
|
return os.path.isfile(os.path.join(self.mirror_path, 'config'))
|
316
334
|
|
335
|
+
def _preserve_fetchspec(self):
|
336
|
+
"""Read and preserve remote.origin.fetch from an existing mirror.
|
337
|
+
|
338
|
+
This modifies self.fetch_specs.
|
339
|
+
"""
|
340
|
+
if not self.exists():
|
341
|
+
return
|
342
|
+
try:
|
343
|
+
config_fetchspecs = subprocess.check_output(
|
344
|
+
[self.git_exe, 'config', '--get-all', 'remote.origin.fetch'],
|
345
|
+
cwd=self.mirror_path)
|
346
|
+
for fetchspec in config_fetchspecs.splitlines():
|
347
|
+
self.fetch_specs.add(self.parse_fetch_spec(fetchspec))
|
348
|
+
except subprocess.CalledProcessError:
|
349
|
+
logging.warn('Tried and failed to preserve remote.origin.fetch from the '
|
350
|
+
'existing cache directory. You may need to manually edit '
|
351
|
+
'%s and "git cache fetch" again.'
|
352
|
+
% os.path.join(self.mirror_path, 'config'))
|
353
|
+
|
317
354
|
def _ensure_bootstrapped(self, depth, bootstrap, force=False):
|
318
355
|
tempdir = None
|
319
|
-
config_file = os.path.join(self.mirror_path, 'config')
|
320
356
|
pack_dir = os.path.join(self.mirror_path, 'objects', 'pack')
|
321
357
|
pack_files = []
|
322
358
|
|
@@ -324,16 +360,19 @@ class Mirror(object):
|
|
324
360
|
pack_files = [f for f in os.listdir(pack_dir) if f.endswith('.pack')]
|
325
361
|
|
326
362
|
should_bootstrap = (force or
|
327
|
-
not
|
363
|
+
not self.exists() or
|
328
364
|
len(pack_files) > GC_AUTOPACKLIMIT)
|
329
365
|
if should_bootstrap:
|
366
|
+
if self.exists():
|
367
|
+
# Re-bootstrapping an existing mirror; preserve existing fetch spec.
|
368
|
+
self._preserve_fetchspec()
|
330
369
|
tempdir = tempfile.mkdtemp(
|
331
370
|
prefix='_cache_tmp', suffix=self.basedir, dir=self.GetCachePath())
|
332
371
|
bootstrapped = not depth and bootstrap and self.bootstrap_repo(tempdir)
|
333
372
|
if bootstrapped:
|
334
373
|
# Bootstrap succeeded; delete previous cache, if any.
|
335
374
|
gclient_utils.rmtree(self.mirror_path)
|
336
|
-
elif not
|
375
|
+
elif not self.exists():
|
337
376
|
# Bootstrap failed, no previous cache; start with a bare git dir.
|
338
377
|
self.RunGit(['init', '--bare'], cwd=tempdir)
|
339
378
|
else:
|
@@ -372,13 +411,13 @@ class Mirror(object):
|
|
372
411
|
logging.warn('Fetch of %s failed' % spec)
|
373
412
|
|
374
413
|
def populate(self, depth=None, shallow=False, bootstrap=False,
|
375
|
-
verbose=False, ignore_lock=False):
|
414
|
+
verbose=False, ignore_lock=False, lock_timeout=0):
|
376
415
|
assert self.GetCachePath()
|
377
416
|
if shallow and not depth:
|
378
417
|
depth = 10000
|
379
418
|
gclient_utils.safe_makedirs(self.GetCachePath())
|
380
419
|
|
381
|
-
lockfile = Lockfile(self.mirror_path)
|
420
|
+
lockfile = Lockfile(self.mirror_path, lock_timeout)
|
382
421
|
if not ignore_lock:
|
383
422
|
lockfile.lock()
|
384
423
|
|
@@ -553,6 +592,7 @@ def CMDpopulate(parser, args):
|
|
553
592
|
'shallow': options.shallow,
|
554
593
|
'bootstrap': not options.no_bootstrap,
|
555
594
|
'ignore_lock': options.ignore_locks,
|
595
|
+
'lock_timeout': options.timeout,
|
556
596
|
}
|
557
597
|
if options.depth:
|
558
598
|
kwargs['depth'] = options.depth
|
@@ -563,6 +603,9 @@ def CMDpopulate(parser, args):
|
|
563
603
|
def CMDfetch(parser, args):
|
564
604
|
"""Update mirror, and fetch in cwd."""
|
565
605
|
parser.add_option('--all', action='store_true', help='Fetch all remotes')
|
606
|
+
parser.add_option('--no_bootstrap', '--no-bootstrap',
|
607
|
+
action='store_true',
|
608
|
+
help='Don\'t (re)bootstrap from Google Storage')
|
566
609
|
options, args = parser.parse_args(args)
|
567
610
|
|
568
611
|
# Figure out which remotes to fetch. This mimics the behavior of regular
|
@@ -593,7 +636,8 @@ def CMDfetch(parser, args):
|
|
593
636
|
git_dir = os.path.abspath(git_dir)
|
594
637
|
if git_dir.startswith(cachepath):
|
595
638
|
mirror = Mirror.FromPath(git_dir)
|
596
|
-
mirror.populate(
|
639
|
+
mirror.populate(
|
640
|
+
bootstrap=not options.no_bootstrap, lock_timeout=options.timeout)
|
597
641
|
return 0
|
598
642
|
for remote in remotes:
|
599
643
|
remote_url = subprocess.check_output(
|
@@ -602,7 +646,8 @@ def CMDfetch(parser, args):
|
|
602
646
|
mirror = Mirror.FromPath(remote_url)
|
603
647
|
mirror.print = lambda *args: None
|
604
648
|
print('Updating git cache...')
|
605
|
-
mirror.populate(
|
649
|
+
mirror.populate(
|
650
|
+
bootstrap=not options.no_bootstrap, lock_timeout=options.timeout)
|
606
651
|
subprocess.check_call([Mirror.git_exe, 'fetch', remote])
|
607
652
|
return 0
|
608
653
|
|
@@ -651,6 +696,8 @@ class OptionParser(optparse.OptionParser):
|
|
651
696
|
help='Increase verbosity (can be passed multiple times)')
|
652
697
|
self.add_option('-q', '--quiet', action='store_true',
|
653
698
|
help='Suppress all extraneous output')
|
699
|
+
self.add_option('--timeout', type='int', default=0,
|
700
|
+
help='Timeout for acquiring cache lock, in seconds')
|
654
701
|
|
655
702
|
def parse_args(self, args=None, values=None):
|
656
703
|
options, args = optparse.OptionParser.parse_args(self, args, values)
|
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
# Copyright (C) 2008 Evan Martin <martine@danga.com>
|
7
7
|
|
8
|
-
"""A git-command for integrating reviews on Rietveld."""
|
8
|
+
"""A git-command for integrating reviews on Rietveld and Gerrit."""
|
9
9
|
|
10
10
|
from distutils.version import LooseVersion
|
11
11
|
from multiprocessing.pool import ThreadPool
|
@@ -15,6 +15,7 @@ import glob
|
|
15
15
|
import httplib
|
16
16
|
import json
|
17
17
|
import logging
|
18
|
+
import multiprocessing
|
18
19
|
import optparse
|
19
20
|
import os
|
20
21
|
import Queue
|
@@ -25,8 +26,10 @@ import tempfile
|
|
25
26
|
import textwrap
|
26
27
|
import time
|
27
28
|
import traceback
|
29
|
+
import urllib
|
28
30
|
import urllib2
|
29
31
|
import urlparse
|
32
|
+
import uuid
|
30
33
|
import webbrowser
|
31
34
|
import zlib
|
32
35
|
|
@@ -39,13 +42,17 @@ from third_party import colorama
|
|
39
42
|
from third_party import httplib2
|
40
43
|
from third_party import upload
|
41
44
|
import auth
|
42
|
-
import
|
45
|
+
from luci_hacks import trigger_luci_job as luci_trigger
|
43
46
|
import clang_format
|
47
|
+
import commit_queue
|
44
48
|
import dart_format
|
49
|
+
import setup_color
|
45
50
|
import fix_encoding
|
46
51
|
import gclient_utils
|
52
|
+
import gerrit_util
|
53
|
+
import git_cache
|
47
54
|
import git_common
|
48
|
-
|
55
|
+
import git_footers
|
49
56
|
import owners
|
50
57
|
import owners_finder
|
51
58
|
import presubmit_support
|
@@ -61,15 +68,11 @@ DEFAULT_SERVER = 'https://codereview.appspot.com'
|
|
61
68
|
POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s'
|
62
69
|
DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup'
|
63
70
|
GIT_INSTRUCTIONS_URL = 'http://code.google.com/p/chromium/wiki/UsingGit'
|
64
|
-
CHANGE_ID = 'Change-Id:'
|
65
71
|
REFS_THAT_ALIAS_TO_OTHER_REFS = {
|
66
72
|
'refs/remotes/origin/lkgr': 'refs/remotes/origin/master',
|
67
73
|
'refs/remotes/origin/lkcr': 'refs/remotes/origin/master',
|
68
74
|
}
|
69
75
|
|
70
|
-
# Buildbucket-related constants
|
71
|
-
BUILDBUCKET_HOST = 'cr-buildbucket.appspot.com'
|
72
|
-
|
73
76
|
# Valid extensions for files we want to lint.
|
74
77
|
DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)"
|
75
78
|
DEFAULT_LINT_IGNORE_REGEX = r"$^"
|
@@ -93,9 +96,9 @@ def GetNoGitPagerEnv():
|
|
93
96
|
return env
|
94
97
|
|
95
98
|
|
96
|
-
def RunCommand(args, error_ok=False, error_message=None, **kwargs):
|
99
|
+
def RunCommand(args, error_ok=False, error_message=None, shell=False, **kwargs):
|
97
100
|
try:
|
98
|
-
return subprocess2.check_output(args, shell=
|
101
|
+
return subprocess2.check_output(args, shell=shell, **kwargs)
|
99
102
|
except subprocess2.CalledProcessError as e:
|
100
103
|
logging.debug('Failed running %s', args)
|
101
104
|
if not error_ok:
|
@@ -156,7 +159,7 @@ def ask_for_data(prompt):
|
|
156
159
|
|
157
160
|
|
158
161
|
def git_set_branch_value(key, value):
|
159
|
-
branch =
|
162
|
+
branch = GetCurrentBranch()
|
160
163
|
if not branch:
|
161
164
|
return
|
162
165
|
|
@@ -168,7 +171,7 @@ def git_set_branch_value(key, value):
|
|
168
171
|
|
169
172
|
|
170
173
|
def git_get_branch_default(key, default):
|
171
|
-
branch =
|
174
|
+
branch = GetCurrentBranch()
|
172
175
|
if branch:
|
173
176
|
git_key = 'branch.%s.%s' % (branch, key)
|
174
177
|
(_, stdout) = RunGitWithCode(['config', '--int', '--get', git_key])
|
@@ -217,6 +220,16 @@ def add_git_similarity(parser):
|
|
217
220
|
parser.parse_args = Parse
|
218
221
|
|
219
222
|
|
223
|
+
def _get_properties_from_options(options):
|
224
|
+
properties = dict(x.split('=', 1) for x in options.properties)
|
225
|
+
for key, val in properties.iteritems():
|
226
|
+
try:
|
227
|
+
properties[key] = json.loads(val)
|
228
|
+
except ValueError:
|
229
|
+
pass # If a value couldn't be evaluated, treat it as a string.
|
230
|
+
return properties
|
231
|
+
|
232
|
+
|
220
233
|
def _prefix_master(master):
|
221
234
|
"""Convert user-specified master name to full master name.
|
222
235
|
|
@@ -231,8 +244,57 @@ def _prefix_master(master):
|
|
231
244
|
return '%s%s' % (prefix, master)
|
232
245
|
|
233
246
|
|
234
|
-
def
|
235
|
-
|
247
|
+
def _buildbucket_retry(operation_name, http, *args, **kwargs):
|
248
|
+
"""Retries requests to buildbucket service and returns parsed json content."""
|
249
|
+
try_count = 0
|
250
|
+
while True:
|
251
|
+
response, content = http.request(*args, **kwargs)
|
252
|
+
try:
|
253
|
+
content_json = json.loads(content)
|
254
|
+
except ValueError:
|
255
|
+
content_json = None
|
256
|
+
|
257
|
+
# Buildbucket could return an error even if status==200.
|
258
|
+
if content_json and content_json.get('error'):
|
259
|
+
error = content_json.get('error')
|
260
|
+
if error.get('code') == 403:
|
261
|
+
raise BuildbucketResponseException(
|
262
|
+
'Access denied: %s' % error.get('message', ''))
|
263
|
+
msg = 'Error in response. Reason: %s. Message: %s.' % (
|
264
|
+
error.get('reason', ''), error.get('message', ''))
|
265
|
+
raise BuildbucketResponseException(msg)
|
266
|
+
|
267
|
+
if response.status == 200:
|
268
|
+
if not content_json:
|
269
|
+
raise BuildbucketResponseException(
|
270
|
+
'Buildbucket returns invalid json content: %s.\n'
|
271
|
+
'Please file bugs at http://crbug.com, label "Infra-BuildBucket".' %
|
272
|
+
content)
|
273
|
+
return content_json
|
274
|
+
if response.status < 500 or try_count >= 2:
|
275
|
+
raise httplib2.HttpLib2Error(content)
|
276
|
+
|
277
|
+
# status >= 500 means transient failures.
|
278
|
+
logging.debug('Transient errors when %s. Will retry.', operation_name)
|
279
|
+
time.sleep(0.5 + 1.5*try_count)
|
280
|
+
try_count += 1
|
281
|
+
assert False, 'unreachable'
|
282
|
+
|
283
|
+
|
284
|
+
def trigger_luci_job(changelist, masters, options):
|
285
|
+
"""Send a job to run on LUCI."""
|
286
|
+
issue_props = changelist.GetIssueProperties()
|
287
|
+
issue = changelist.GetIssue()
|
288
|
+
patchset = changelist.GetMostRecentPatchset()
|
289
|
+
for builders_and_tests in sorted(masters.itervalues()):
|
290
|
+
# TODO(hinoka et al): add support for other properties.
|
291
|
+
# Currently, this completely ignores testfilter and other properties.
|
292
|
+
for builder in sorted(builders_and_tests):
|
293
|
+
luci_trigger.trigger(
|
294
|
+
builder, 'HEAD', issue, patchset, issue_props['project'])
|
295
|
+
|
296
|
+
|
297
|
+
def trigger_try_jobs(auth_config, changelist, options, masters, category):
|
236
298
|
rietveld_url = settings.GetDefaultServerUrl()
|
237
299
|
rietveld_host = urlparse.urlparse(rietveld_url).hostname
|
238
300
|
authenticator = auth.get_authenticator_for_host(rietveld_host, auth_config)
|
@@ -241,10 +303,11 @@ def trigger_try_jobs(auth_config, changelist, options, masters, category,
|
|
241
303
|
issue_props = changelist.GetIssueProperties()
|
242
304
|
issue = changelist.GetIssue()
|
243
305
|
patchset = changelist.GetMostRecentPatchset()
|
306
|
+
properties = _get_properties_from_options(options)
|
244
307
|
|
245
308
|
buildbucket_put_url = (
|
246
309
|
'https://{hostname}/_ah/api/buildbucket/v1/builds/batch'.format(
|
247
|
-
hostname=
|
310
|
+
hostname=options.buildbucket_host))
|
248
311
|
buildset = 'patch/rietveld/{hostname}/{issue}/{patch}'.format(
|
249
312
|
hostname=rietveld_host,
|
250
313
|
issue=issue,
|
@@ -260,9 +323,10 @@ def trigger_try_jobs(auth_config, changelist, options, masters, category,
|
|
260
323
|
print_text.append(' %s: %s' % (builder, tests))
|
261
324
|
parameters = {
|
262
325
|
'builder_name': builder,
|
263
|
-
'changes': [
|
264
|
-
|
265
|
-
|
326
|
+
'changes': [{
|
327
|
+
'author': {'email': issue_props['owner_email']},
|
328
|
+
'revision': options.revision,
|
329
|
+
}],
|
266
330
|
'properties': {
|
267
331
|
'category': category,
|
268
332
|
'issue': issue,
|
@@ -271,19 +335,22 @@ def trigger_try_jobs(auth_config, changelist, options, masters, category,
|
|
271
335
|
'patch_storage': 'rietveld',
|
272
336
|
'patchset': patchset,
|
273
337
|
'reason': options.name,
|
274
|
-
'revision': options.revision,
|
275
338
|
'rietveld': rietveld_url,
|
276
|
-
'testfilter': tests,
|
277
339
|
},
|
278
340
|
}
|
279
|
-
if
|
280
|
-
parameters['properties']
|
341
|
+
if 'presubmit' in builder.lower():
|
342
|
+
parameters['properties']['dry_run'] = 'true'
|
343
|
+
if tests:
|
344
|
+
parameters['properties']['testfilter'] = tests
|
345
|
+
if properties:
|
346
|
+
parameters['properties'].update(properties)
|
281
347
|
if options.clobber:
|
282
348
|
parameters['properties']['clobber'] = True
|
283
349
|
batch_req_body['builds'].append(
|
284
350
|
{
|
285
351
|
'bucket': bucket,
|
286
352
|
'parameters_json': json.dumps(parameters),
|
353
|
+
'client_operation_id': str(uuid.uuid4()),
|
287
354
|
'tags': ['builder:%s' % builder,
|
288
355
|
'buildset:%s' % buildset,
|
289
356
|
'master:%s' % master,
|
@@ -291,42 +358,155 @@ def trigger_try_jobs(auth_config, changelist, options, masters, category,
|
|
291
358
|
}
|
292
359
|
)
|
293
360
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
pass
|
361
|
+
_buildbucket_retry(
|
362
|
+
'triggering tryjobs',
|
363
|
+
http,
|
364
|
+
buildbucket_put_url,
|
365
|
+
'PUT',
|
366
|
+
body=json.dumps(batch_req_body),
|
367
|
+
headers={'Content-Type': 'application/json'}
|
368
|
+
)
|
369
|
+
print_text.append('To see results here, run: git cl try-results')
|
370
|
+
print_text.append('To see results in browser, run: git cl web')
|
371
|
+
print '\n'.join(print_text)
|
306
372
|
|
307
|
-
# Buildbucket could return an error even if status==200.
|
308
|
-
if content_json and content_json.get('error'):
|
309
|
-
msg = 'Error in response. Code: %d. Reason: %s. Message: %s.' % (
|
310
|
-
content_json['error'].get('code', ''),
|
311
|
-
content_json['error'].get('reason', ''),
|
312
|
-
content_json['error'].get('message', ''))
|
313
|
-
raise BuildbucketResponseException(msg)
|
314
373
|
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
374
|
+
def fetch_try_jobs(auth_config, changelist, options):
|
375
|
+
"""Fetches tryjobs from buildbucket.
|
376
|
+
|
377
|
+
Returns a map from build id to build info as json dictionary.
|
378
|
+
"""
|
379
|
+
rietveld_url = settings.GetDefaultServerUrl()
|
380
|
+
rietveld_host = urlparse.urlparse(rietveld_url).hostname
|
381
|
+
authenticator = auth.get_authenticator_for_host(rietveld_host, auth_config)
|
382
|
+
if authenticator.has_cached_credentials():
|
383
|
+
http = authenticator.authorize(httplib2.Http())
|
384
|
+
else:
|
385
|
+
print ('Warning: Some results might be missing because %s' %
|
386
|
+
# Get the message on how to login.
|
387
|
+
auth.LoginRequiredError(rietveld_host).message)
|
388
|
+
http = httplib2.Http()
|
389
|
+
|
390
|
+
http.force_exception_to_status_code = True
|
391
|
+
|
392
|
+
buildset = 'patch/rietveld/{hostname}/{issue}/{patch}'.format(
|
393
|
+
hostname=rietveld_host,
|
394
|
+
issue=changelist.GetIssue(),
|
395
|
+
patch=options.patchset)
|
396
|
+
params = {'tag': 'buildset:%s' % buildset}
|
397
|
+
|
398
|
+
builds = {}
|
399
|
+
while True:
|
400
|
+
url = 'https://{hostname}/_ah/api/buildbucket/v1/search?{params}'.format(
|
401
|
+
hostname=options.buildbucket_host,
|
402
|
+
params=urllib.urlencode(params))
|
403
|
+
content = _buildbucket_retry('fetching tryjobs', http, url, 'GET')
|
404
|
+
for build in content.get('builds', []):
|
405
|
+
builds[build['id']] = build
|
406
|
+
if 'next_cursor' in content:
|
407
|
+
params['start_cursor'] = content['next_cursor']
|
408
|
+
else:
|
321
409
|
break
|
322
|
-
|
323
|
-
raise httplib2.HttpLib2Error(content)
|
410
|
+
return builds
|
324
411
|
|
325
|
-
# status >= 500 means transient failures.
|
326
|
-
logging.debug('Transient errors when triggering tryjobs. Will retry.')
|
327
|
-
time.sleep(0.5 + 1.5*try_count)
|
328
412
|
|
329
|
-
|
413
|
+
def print_tryjobs(options, builds):
|
414
|
+
"""Prints nicely result of fetch_try_jobs."""
|
415
|
+
if not builds:
|
416
|
+
print 'No tryjobs scheduled'
|
417
|
+
return
|
418
|
+
|
419
|
+
# Make a copy, because we'll be modifying builds dictionary.
|
420
|
+
builds = builds.copy()
|
421
|
+
builder_names_cache = {}
|
422
|
+
|
423
|
+
def get_builder(b):
|
424
|
+
try:
|
425
|
+
return builder_names_cache[b['id']]
|
426
|
+
except KeyError:
|
427
|
+
try:
|
428
|
+
parameters = json.loads(b['parameters_json'])
|
429
|
+
name = parameters['builder_name']
|
430
|
+
except (ValueError, KeyError) as error:
|
431
|
+
print 'WARNING: failed to get builder name for build %s: %s' % (
|
432
|
+
b['id'], error)
|
433
|
+
name = None
|
434
|
+
builder_names_cache[b['id']] = name
|
435
|
+
return name
|
436
|
+
|
437
|
+
def get_bucket(b):
|
438
|
+
bucket = b['bucket']
|
439
|
+
if bucket.startswith('master.'):
|
440
|
+
return bucket[len('master.'):]
|
441
|
+
return bucket
|
442
|
+
|
443
|
+
if options.print_master:
|
444
|
+
name_fmt = '%%-%ds %%-%ds' % (
|
445
|
+
max(len(str(get_bucket(b))) for b in builds.itervalues()),
|
446
|
+
max(len(str(get_builder(b))) for b in builds.itervalues()))
|
447
|
+
def get_name(b):
|
448
|
+
return name_fmt % (get_bucket(b), get_builder(b))
|
449
|
+
else:
|
450
|
+
name_fmt = '%%-%ds' % (
|
451
|
+
max(len(str(get_builder(b))) for b in builds.itervalues()))
|
452
|
+
def get_name(b):
|
453
|
+
return name_fmt % get_builder(b)
|
454
|
+
|
455
|
+
def sort_key(b):
|
456
|
+
return b['status'], b.get('result'), get_name(b), b.get('url')
|
457
|
+
|
458
|
+
def pop(title, f, color=None, **kwargs):
|
459
|
+
"""Pop matching builds from `builds` dict and print them."""
|
460
|
+
|
461
|
+
if not options.color or color is None:
|
462
|
+
colorize = str
|
463
|
+
else:
|
464
|
+
colorize = lambda x: '%s%s%s' % (color, x, Fore.RESET)
|
465
|
+
|
466
|
+
result = []
|
467
|
+
for b in builds.values():
|
468
|
+
if all(b.get(k) == v for k, v in kwargs.iteritems()):
|
469
|
+
builds.pop(b['id'])
|
470
|
+
result.append(b)
|
471
|
+
if result:
|
472
|
+
print colorize(title)
|
473
|
+
for b in sorted(result, key=sort_key):
|
474
|
+
print ' ', colorize('\t'.join(map(str, f(b))))
|
475
|
+
|
476
|
+
total = len(builds)
|
477
|
+
pop(status='COMPLETED', result='SUCCESS',
|
478
|
+
title='Successes:', color=Fore.GREEN,
|
479
|
+
f=lambda b: (get_name(b), b.get('url')))
|
480
|
+
pop(status='COMPLETED', result='FAILURE', failure_reason='INFRA_FAILURE',
|
481
|
+
title='Infra Failures:', color=Fore.MAGENTA,
|
482
|
+
f=lambda b: (get_name(b), b.get('url')))
|
483
|
+
pop(status='COMPLETED', result='FAILURE', failure_reason='BUILD_FAILURE',
|
484
|
+
title='Failures:', color=Fore.RED,
|
485
|
+
f=lambda b: (get_name(b), b.get('url')))
|
486
|
+
pop(status='COMPLETED', result='CANCELED',
|
487
|
+
title='Canceled:', color=Fore.MAGENTA,
|
488
|
+
f=lambda b: (get_name(b),))
|
489
|
+
pop(status='COMPLETED', result='FAILURE',
|
490
|
+
failure_reason='INVALID_BUILD_DEFINITION',
|
491
|
+
title='Wrong master/builder name:', color=Fore.MAGENTA,
|
492
|
+
f=lambda b: (get_name(b),))
|
493
|
+
pop(status='COMPLETED', result='FAILURE',
|
494
|
+
title='Other failures:',
|
495
|
+
f=lambda b: (get_name(b), b.get('failure_reason'), b.get('url')))
|
496
|
+
pop(status='COMPLETED',
|
497
|
+
title='Other finished:',
|
498
|
+
f=lambda b: (get_name(b), b.get('result'), b.get('url')))
|
499
|
+
pop(status='STARTED',
|
500
|
+
title='Started:', color=Fore.YELLOW,
|
501
|
+
f=lambda b: (get_name(b), b.get('url')))
|
502
|
+
pop(status='SCHEDULED',
|
503
|
+
title='Scheduled:',
|
504
|
+
f=lambda b: (get_name(b), 'id=%s' % b['id']))
|
505
|
+
# The last section is just in case buildbucket API changes OR there is a bug.
|
506
|
+
pop(title='Other:',
|
507
|
+
f=lambda b: (get_name(b), 'id=%s' % b['id']))
|
508
|
+
assert len(builds) == 0
|
509
|
+
print 'Total: %d tryjobs' % total
|
330
510
|
|
331
511
|
|
332
512
|
def MatchSvnGlob(url, base_url, glob_spec, allow_wildcards):
|
@@ -411,6 +591,8 @@ class Settings(object):
|
|
411
591
|
self.viewvc_url = None
|
412
592
|
self.updated = False
|
413
593
|
self.is_gerrit = None
|
594
|
+
self.squash_gerrit_uploads = None
|
595
|
+
self.gerrit_skip_ensure_authenticated = None
|
414
596
|
self.git_editor = None
|
415
597
|
self.project = None
|
416
598
|
self.force_https_commit_url = None
|
@@ -428,10 +610,6 @@ class Settings(object):
|
|
428
610
|
cr_settings_file = FindCodereviewSettingsFile()
|
429
611
|
if autoupdate != 'false' and cr_settings_file:
|
430
612
|
LoadCodereviewSettingsFromFile(cr_settings_file)
|
431
|
-
# set updated to True to avoid infinite calling loop
|
432
|
-
# through DownloadHooks
|
433
|
-
self.updated = True
|
434
|
-
DownloadHooks(False)
|
435
613
|
self.updated = True
|
436
614
|
|
437
615
|
def GetDefaultServerUrl(self, error_ok=False):
|
@@ -457,6 +635,19 @@ class Settings(object):
|
|
457
635
|
self.root = os.path.abspath(self.GetRelativeRoot())
|
458
636
|
return self.root
|
459
637
|
|
638
|
+
def GetGitMirror(self, remote='origin'):
|
639
|
+
"""If this checkout is from a local git mirror, return a Mirror object."""
|
640
|
+
local_url = RunGit(['config', '--get', 'remote.%s.url' % remote]).strip()
|
641
|
+
if not os.path.isdir(local_url):
|
642
|
+
return None
|
643
|
+
git_cache.Mirror.SetCachePath(os.path.dirname(local_url))
|
644
|
+
remote_url = git_cache.Mirror.CacheDirToUrl(local_url)
|
645
|
+
# Use the /dev/null print_func to avoid terminal spew in WaitForRealCommit.
|
646
|
+
mirror = git_cache.Mirror(remote_url, print_func = lambda *args: None)
|
647
|
+
if mirror.exists():
|
648
|
+
return mirror
|
649
|
+
return None
|
650
|
+
|
460
651
|
def GetIsGitSvn(self):
|
461
652
|
"""Return true if this repo looks like it's using git-svn."""
|
462
653
|
if self.is_git_svn is None:
|
@@ -554,6 +745,11 @@ class Settings(object):
|
|
554
745
|
def GetBugPrefix(self):
|
555
746
|
return self._GetRietveldConfig('bug-prefix', error_ok=True)
|
556
747
|
|
748
|
+
def GetIsSkipDependencyUpload(self, branch_name):
|
749
|
+
"""Returns true if specified branch should skip dep uploads."""
|
750
|
+
return self._GetBranchConfig(branch_name, 'skip-deps-uploads',
|
751
|
+
error_ok=True)
|
752
|
+
|
557
753
|
def GetRunPostUploadHook(self):
|
558
754
|
run_post_upload_hook = self._GetRietveldConfig(
|
559
755
|
'run-post-upload-hook', error_ok=True)
|
@@ -571,6 +767,23 @@ class Settings(object):
|
|
571
767
|
self.is_gerrit = self._GetConfig('gerrit.host', error_ok=True)
|
572
768
|
return self.is_gerrit
|
573
769
|
|
770
|
+
def GetSquashGerritUploads(self):
|
771
|
+
"""Return true if uploads to Gerrit should be squashed by default."""
|
772
|
+
if self.squash_gerrit_uploads is None:
|
773
|
+
self.squash_gerrit_uploads = (
|
774
|
+
RunGit(['config', '--bool', 'gerrit.squash-uploads'],
|
775
|
+
error_ok=True).strip() == 'true')
|
776
|
+
return self.squash_gerrit_uploads
|
777
|
+
|
778
|
+
def GetGerritSkipEnsureAuthenticated(self):
|
779
|
+
"""Return True if EnsureAuthenticated should not be done for Gerrit
|
780
|
+
uploads."""
|
781
|
+
if self.gerrit_skip_ensure_authenticated is None:
|
782
|
+
self.gerrit_skip_ensure_authenticated = (
|
783
|
+
RunGit(['config', '--bool', 'gerrit.skip-ensure-authenticated'],
|
784
|
+
error_ok=True).strip() == 'true')
|
785
|
+
return self.gerrit_skip_ensure_authenticated
|
786
|
+
|
574
787
|
def GetGitEditor(self):
|
575
788
|
"""Return the editor specified in the git config, or None if none is."""
|
576
789
|
if self.git_editor is None:
|
@@ -605,6 +818,9 @@ class Settings(object):
|
|
605
818
|
def _GetRietveldConfig(self, param, **kwargs):
|
606
819
|
return self._GetConfig('rietveld.' + param, **kwargs)
|
607
820
|
|
821
|
+
def _GetBranchConfig(self, branch_name, param, **kwargs):
|
822
|
+
return self._GetConfig('branch.' + branch_name + '.' + param, **kwargs)
|
823
|
+
|
608
824
|
def _GetConfig(self, param, **kwargs):
|
609
825
|
self.LazyUpdateIfNeeded()
|
610
826
|
return RunGit(['config', param], **kwargs).strip()
|
@@ -612,23 +828,110 @@ class Settings(object):
|
|
612
828
|
|
613
829
|
def ShortBranchName(branch):
|
614
830
|
"""Convert a name like 'refs/heads/foo' to just 'foo'."""
|
615
|
-
return branch.replace('refs/heads/', '')
|
831
|
+
return branch.replace('refs/heads/', '', 1)
|
832
|
+
|
833
|
+
|
834
|
+
def GetCurrentBranchRef():
|
835
|
+
"""Returns branch ref (e.g., refs/heads/master) or None."""
|
836
|
+
return RunGit(['symbolic-ref', 'HEAD'],
|
837
|
+
stderr=subprocess2.VOID, error_ok=True).strip() or None
|
838
|
+
|
839
|
+
|
840
|
+
def GetCurrentBranch():
|
841
|
+
"""Returns current branch or None.
|
842
|
+
|
843
|
+
For refs/heads/* branches, returns just last part. For others, full ref.
|
844
|
+
"""
|
845
|
+
branchref = GetCurrentBranchRef()
|
846
|
+
if branchref:
|
847
|
+
return ShortBranchName(branchref)
|
848
|
+
return None
|
849
|
+
|
850
|
+
|
851
|
+
class _CQState(object):
|
852
|
+
"""Enum for states of CL with respect to Commit Queue."""
|
853
|
+
NONE = 'none'
|
854
|
+
DRY_RUN = 'dry_run'
|
855
|
+
COMMIT = 'commit'
|
856
|
+
|
857
|
+
ALL_STATES = [NONE, DRY_RUN, COMMIT]
|
858
|
+
|
859
|
+
|
860
|
+
class _ParsedIssueNumberArgument(object):
|
861
|
+
def __init__(self, issue=None, patchset=None, hostname=None):
|
862
|
+
self.issue = issue
|
863
|
+
self.patchset = patchset
|
864
|
+
self.hostname = hostname
|
865
|
+
|
866
|
+
@property
|
867
|
+
def valid(self):
|
868
|
+
return self.issue is not None
|
869
|
+
|
870
|
+
|
871
|
+
class _RietveldParsedIssueNumberArgument(_ParsedIssueNumberArgument):
|
872
|
+
def __init__(self, *args, **kwargs):
|
873
|
+
self.patch_url = kwargs.pop('patch_url', None)
|
874
|
+
super(_RietveldParsedIssueNumberArgument, self).__init__(*args, **kwargs)
|
875
|
+
|
876
|
+
|
877
|
+
def ParseIssueNumberArgument(arg):
|
878
|
+
"""Parses the issue argument and returns _ParsedIssueNumberArgument."""
|
879
|
+
fail_result = _ParsedIssueNumberArgument()
|
880
|
+
|
881
|
+
if arg.isdigit():
|
882
|
+
return _ParsedIssueNumberArgument(issue=int(arg))
|
883
|
+
if not arg.startswith('http'):
|
884
|
+
return fail_result
|
885
|
+
url = gclient_utils.UpgradeToHttps(arg)
|
886
|
+
try:
|
887
|
+
parsed_url = urlparse.urlparse(url)
|
888
|
+
except ValueError:
|
889
|
+
return fail_result
|
890
|
+
for cls in _CODEREVIEW_IMPLEMENTATIONS.itervalues():
|
891
|
+
tmp = cls.ParseIssueURL(parsed_url)
|
892
|
+
if tmp is not None:
|
893
|
+
return tmp
|
894
|
+
return fail_result
|
616
895
|
|
617
896
|
|
618
897
|
class Changelist(object):
|
619
|
-
|
898
|
+
"""Changelist works with one changelist in local branch.
|
899
|
+
|
900
|
+
Supports two codereview backends: Rietveld or Gerrit, selected at object
|
901
|
+
creation.
|
902
|
+
|
903
|
+
Notes:
|
904
|
+
* Not safe for concurrent multi-{thread,process} use.
|
905
|
+
* Caches values from current branch. Therefore, re-use after branch change
|
906
|
+
with care.
|
907
|
+
"""
|
908
|
+
|
909
|
+
def __init__(self, branchref=None, issue=None, codereview=None, **kwargs):
|
910
|
+
"""Create a new ChangeList instance.
|
911
|
+
|
912
|
+
If issue is given, the codereview must be given too.
|
913
|
+
|
914
|
+
If `codereview` is given, it must be 'rietveld' or 'gerrit'.
|
915
|
+
Otherwise, it's decided based on current configuration of the local branch,
|
916
|
+
with default being 'rietveld' for backwards compatibility.
|
917
|
+
See _load_codereview_impl for more details.
|
918
|
+
|
919
|
+
**kwargs will be passed directly to codereview implementation.
|
920
|
+
"""
|
620
921
|
# Poke settings so we get the "configure your server" message if necessary.
|
621
922
|
global settings
|
622
923
|
if not settings:
|
623
924
|
# Happens when git_cl.py is used as a utility library.
|
624
925
|
settings = Settings()
|
625
|
-
|
926
|
+
|
927
|
+
if issue:
|
928
|
+
assert codereview, 'codereview must be known, if issue is known'
|
929
|
+
|
626
930
|
self.branchref = branchref
|
627
931
|
if self.branchref:
|
628
932
|
self.branch = ShortBranchName(self.branchref)
|
629
933
|
else:
|
630
934
|
self.branch = None
|
631
|
-
self.rietveld_server = None
|
632
935
|
self.upstream_branch = None
|
633
936
|
self.lookedup_issue = False
|
634
937
|
self.issue = issue or None
|
@@ -638,14 +941,43 @@ class Changelist(object):
|
|
638
941
|
self.patchset = None
|
639
942
|
self.cc = None
|
640
943
|
self.watchers = ()
|
641
|
-
self._auth_config = auth_config
|
642
|
-
self._props = None
|
643
944
|
self._remote = None
|
644
|
-
self._rpc_server = None
|
645
945
|
|
646
|
-
|
647
|
-
|
648
|
-
|
946
|
+
self._codereview_impl = None
|
947
|
+
self._codereview = None
|
948
|
+
self._load_codereview_impl(codereview, **kwargs)
|
949
|
+
assert self._codereview_impl
|
950
|
+
assert self._codereview in _CODEREVIEW_IMPLEMENTATIONS
|
951
|
+
|
952
|
+
def _load_codereview_impl(self, codereview=None, **kwargs):
|
953
|
+
if codereview:
|
954
|
+
assert codereview in _CODEREVIEW_IMPLEMENTATIONS
|
955
|
+
cls = _CODEREVIEW_IMPLEMENTATIONS[codereview]
|
956
|
+
self._codereview = codereview
|
957
|
+
self._codereview_impl = cls(self, **kwargs)
|
958
|
+
return
|
959
|
+
|
960
|
+
# Automatic selection based on issue number set for a current branch.
|
961
|
+
# Rietveld takes precedence over Gerrit.
|
962
|
+
assert not self.issue
|
963
|
+
# Whether we find issue or not, we are doing the lookup.
|
964
|
+
self.lookedup_issue = True
|
965
|
+
for codereview, cls in _CODEREVIEW_IMPLEMENTATIONS.iteritems():
|
966
|
+
setting = cls.IssueSetting(self.GetBranch())
|
967
|
+
issue = RunGit(['config', setting], error_ok=True).strip()
|
968
|
+
if issue:
|
969
|
+
self._codereview = codereview
|
970
|
+
self._codereview_impl = cls(self, **kwargs)
|
971
|
+
self.issue = int(issue)
|
972
|
+
return
|
973
|
+
|
974
|
+
# No issue is set for this branch, so decide based on repo-wide settings.
|
975
|
+
return self._load_codereview_impl(
|
976
|
+
codereview='gerrit' if settings.GetIsGerrit() else 'rietveld',
|
977
|
+
**kwargs)
|
978
|
+
|
979
|
+
def IsGerrit(self):
|
980
|
+
return self._codereview == 'gerrit'
|
649
981
|
|
650
982
|
def GetCCList(self):
|
651
983
|
"""Return the users cc'd on this CL.
|
@@ -673,8 +1005,7 @@ class Changelist(object):
|
|
673
1005
|
def GetBranch(self):
|
674
1006
|
"""Returns the short branch name, e.g. 'master'."""
|
675
1007
|
if not self.branch:
|
676
|
-
branchref =
|
677
|
-
stderr=subprocess2.VOID, error_ok=True).strip()
|
1008
|
+
branchref = GetCurrentBranchRef()
|
678
1009
|
if not branchref:
|
679
1010
|
return None
|
680
1011
|
self.branchref = branchref
|
@@ -686,6 +1017,10 @@ class Changelist(object):
|
|
686
1017
|
self.GetBranch() # Poke the lazy loader.
|
687
1018
|
return self.branchref
|
688
1019
|
|
1020
|
+
def ClearBranch(self):
|
1021
|
+
"""Clears cached branch data of this object."""
|
1022
|
+
self.branch = self.branchref = None
|
1023
|
+
|
689
1024
|
@staticmethod
|
690
1025
|
def FetchUpstreamTuple(branch):
|
691
1026
|
"""Returns a tuple containing remote and remote ref,
|
@@ -718,11 +1053,12 @@ class Changelist(object):
|
|
718
1053
|
remote = 'origin'
|
719
1054
|
upstream_branch = 'refs/heads/trunk'
|
720
1055
|
else:
|
721
|
-
DieWithError(
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
1056
|
+
DieWithError(
|
1057
|
+
'Unable to determine default branch to diff against.\n'
|
1058
|
+
'Either pass complete "git diff"-style arguments, like\n'
|
1059
|
+
' git cl upload origin/master\n'
|
1060
|
+
'or verify this branch is set up to track another \n'
|
1061
|
+
'(via the --track argument to "git checkout -b ...").')
|
726
1062
|
|
727
1063
|
return remote, upstream_branch
|
728
1064
|
|
@@ -787,7 +1123,7 @@ or verify this branch is set up to track another (via the --track argument to
|
|
787
1123
|
if upstream_git_obj is None:
|
788
1124
|
if self.GetBranch() is None:
|
789
1125
|
print >> sys.stderr, (
|
790
|
-
'ERROR: unable to
|
1126
|
+
'ERROR: unable to determine current branch (detached HEAD?)')
|
791
1127
|
else:
|
792
1128
|
print >> sys.stderr, (
|
793
1129
|
'ERROR: no upstream branch')
|
@@ -864,56 +1200,24 @@ or verify this branch is set up to track another (via the --track argument to
|
|
864
1200
|
def GetIssue(self):
|
865
1201
|
"""Returns the issue number as a int or None if not set."""
|
866
1202
|
if self.issue is None and not self.lookedup_issue:
|
867
|
-
issue = RunGit(['config',
|
1203
|
+
issue = RunGit(['config',
|
1204
|
+
self._codereview_impl.IssueSetting(self.GetBranch())],
|
1205
|
+
error_ok=True).strip()
|
868
1206
|
self.issue = int(issue) or None if issue else None
|
869
1207
|
self.lookedup_issue = True
|
870
1208
|
return self.issue
|
871
1209
|
|
872
|
-
def GetRietveldServer(self):
|
873
|
-
if not self.rietveld_server:
|
874
|
-
# If we're on a branch then get the server potentially associated
|
875
|
-
# with that branch.
|
876
|
-
if self.GetIssue():
|
877
|
-
rietveld_server_config = self._RietveldServer()
|
878
|
-
if rietveld_server_config:
|
879
|
-
self.rietveld_server = gclient_utils.UpgradeToHttps(RunGit(
|
880
|
-
['config', rietveld_server_config], error_ok=True).strip())
|
881
|
-
if not self.rietveld_server:
|
882
|
-
self.rietveld_server = settings.GetDefaultServerUrl()
|
883
|
-
return self.rietveld_server
|
884
|
-
|
885
1210
|
def GetIssueURL(self):
|
886
1211
|
"""Get the URL for a particular issue."""
|
887
|
-
|
1212
|
+
issue = self.GetIssue()
|
1213
|
+
if not issue:
|
888
1214
|
return None
|
889
|
-
return '%s/%s' % (self.
|
1215
|
+
return '%s/%s' % (self._codereview_impl.GetCodereviewServer(), issue)
|
890
1216
|
|
891
1217
|
def GetDescription(self, pretty=False):
|
892
1218
|
if not self.has_description:
|
893
1219
|
if self.GetIssue():
|
894
|
-
|
895
|
-
try:
|
896
|
-
self.description = self.RpcServer().get_description(issue).strip()
|
897
|
-
except urllib2.HTTPError as e:
|
898
|
-
if e.code == 404:
|
899
|
-
DieWithError(
|
900
|
-
('\nWhile fetching the description for issue %d, received a '
|
901
|
-
'404 (not found)\n'
|
902
|
-
'error. It is likely that you deleted this '
|
903
|
-
'issue on the server. If this is the\n'
|
904
|
-
'case, please run\n\n'
|
905
|
-
' git cl issue 0\n\n'
|
906
|
-
'to clear the association with the deleted issue. Then run '
|
907
|
-
'this command again.') % issue)
|
908
|
-
else:
|
909
|
-
DieWithError(
|
910
|
-
'\nFailed to fetch issue description. HTTP error %d' % e.code)
|
911
|
-
except urllib2.URLError as e:
|
912
|
-
print >> sys.stderr, (
|
913
|
-
'Warning: Failed to retrieve CL description due to network '
|
914
|
-
'failure.')
|
915
|
-
self.description = ''
|
916
|
-
|
1220
|
+
self.description = self._codereview_impl.FetchDescription()
|
917
1221
|
self.has_description = True
|
918
1222
|
if pretty:
|
919
1223
|
wrapper = textwrap.TextWrapper()
|
@@ -924,7 +1228,7 @@ or verify this branch is set up to track another (via the --track argument to
|
|
924
1228
|
def GetPatchset(self):
|
925
1229
|
"""Returns the patchset number as a int or None if not set."""
|
926
1230
|
if self.patchset is None and not self.lookedup_patchset:
|
927
|
-
patchset = RunGit(['config', self.
|
1231
|
+
patchset = RunGit(['config', self._codereview_impl.PatchsetSetting()],
|
928
1232
|
error_ok=True).strip()
|
929
1233
|
self.patchset = int(patchset) or None if patchset else None
|
930
1234
|
self.lookedup_patchset = True
|
@@ -932,47 +1236,29 @@ or verify this branch is set up to track another (via the --track argument to
|
|
932
1236
|
|
933
1237
|
def SetPatchset(self, patchset):
|
934
1238
|
"""Set this branch's patchset. If patchset=0, clears the patchset."""
|
1239
|
+
patchset_setting = self._codereview_impl.PatchsetSetting()
|
935
1240
|
if patchset:
|
936
|
-
RunGit(['config',
|
1241
|
+
RunGit(['config', patchset_setting, str(patchset)])
|
937
1242
|
self.patchset = patchset
|
938
1243
|
else:
|
939
|
-
RunGit(['config', '--unset',
|
1244
|
+
RunGit(['config', '--unset', patchset_setting],
|
940
1245
|
stderr=subprocess2.PIPE, error_ok=True)
|
941
1246
|
self.patchset = None
|
942
1247
|
|
943
|
-
def
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
return self.RpcServer().get(
|
948
|
-
'/download/issue%s_%s.diff' % (issue, patchset))
|
949
|
-
|
950
|
-
def GetIssueProperties(self):
|
951
|
-
if self._props is None:
|
952
|
-
issue = self.GetIssue()
|
953
|
-
if not issue:
|
954
|
-
self._props = {}
|
955
|
-
else:
|
956
|
-
self._props = self.RpcServer().get_issue_properties(issue, True)
|
957
|
-
return self._props
|
958
|
-
|
959
|
-
def GetApprovingReviewers(self):
|
960
|
-
return get_approving_reviewers(self.GetIssueProperties())
|
961
|
-
|
962
|
-
def AddComment(self, message):
|
963
|
-
return self.RpcServer().add_comment(self.GetIssue(), message)
|
964
|
-
|
965
|
-
def SetIssue(self, issue):
|
966
|
-
"""Set this branch's issue. If issue=0, clears the issue."""
|
1248
|
+
def SetIssue(self, issue=None):
|
1249
|
+
"""Set this branch's issue. If issue isn't given, clears the issue."""
|
1250
|
+
issue_setting = self._codereview_impl.IssueSetting(self.GetBranch())
|
1251
|
+
codereview_setting = self._codereview_impl.GetCodereviewServerSetting()
|
967
1252
|
if issue:
|
968
1253
|
self.issue = issue
|
969
|
-
RunGit(['config',
|
970
|
-
|
971
|
-
|
1254
|
+
RunGit(['config', issue_setting, str(issue)])
|
1255
|
+
codereview_server = self._codereview_impl.GetCodereviewServer()
|
1256
|
+
if codereview_server:
|
1257
|
+
RunGit(['config', codereview_setting, codereview_server])
|
972
1258
|
else:
|
973
1259
|
current_issue = self.GetIssue()
|
974
1260
|
if current_issue:
|
975
|
-
RunGit(['config', '--unset',
|
1261
|
+
RunGit(['config', '--unset', issue_setting])
|
976
1262
|
self.issue = None
|
977
1263
|
self.SetPatchset(None)
|
978
1264
|
|
@@ -1022,6 +1308,343 @@ or verify this branch is set up to track another (via the --track argument to
|
|
1022
1308
|
author,
|
1023
1309
|
upstream=upstream_branch)
|
1024
1310
|
|
1311
|
+
def UpdateDescription(self, description):
|
1312
|
+
self.description = description
|
1313
|
+
return self._codereview_impl.UpdateDescriptionRemote(description)
|
1314
|
+
|
1315
|
+
def RunHook(self, committing, may_prompt, verbose, change):
|
1316
|
+
"""Calls sys.exit() if the hook fails; returns a HookResults otherwise."""
|
1317
|
+
try:
|
1318
|
+
return presubmit_support.DoPresubmitChecks(change, committing,
|
1319
|
+
verbose=verbose, output_stream=sys.stdout, input_stream=sys.stdin,
|
1320
|
+
default_presubmit=None, may_prompt=may_prompt,
|
1321
|
+
rietveld_obj=self._codereview_impl.GetRieveldObjForPresubmit(),
|
1322
|
+
gerrit_obj=self._codereview_impl.GetGerritObjForPresubmit())
|
1323
|
+
except presubmit_support.PresubmitFailure, e:
|
1324
|
+
DieWithError(
|
1325
|
+
('%s\nMaybe your depot_tools is out of date?\n'
|
1326
|
+
'If all fails, contact maruel@') % e)
|
1327
|
+
|
1328
|
+
def CMDPatchIssue(self, issue_arg, reject, nocommit, directory):
|
1329
|
+
"""Fetches and applies the issue patch from codereview to local branch."""
|
1330
|
+
if isinstance(issue_arg, (int, long)) or issue_arg.isdigit():
|
1331
|
+
parsed_issue_arg = _ParsedIssueNumberArgument(int(issue_arg))
|
1332
|
+
else:
|
1333
|
+
# Assume url.
|
1334
|
+
parsed_issue_arg = self._codereview_impl.ParseIssueURL(
|
1335
|
+
urlparse.urlparse(issue_arg))
|
1336
|
+
if not parsed_issue_arg or not parsed_issue_arg.valid:
|
1337
|
+
DieWithError('Failed to parse issue argument "%s". '
|
1338
|
+
'Must be an issue number or a valid URL.' % issue_arg)
|
1339
|
+
return self._codereview_impl.CMDPatchWithParsedIssue(
|
1340
|
+
parsed_issue_arg, reject, nocommit, directory)
|
1341
|
+
|
1342
|
+
def CMDUpload(self, options, git_diff_args, orig_args):
|
1343
|
+
"""Uploads a change to codereview."""
|
1344
|
+
if git_diff_args:
|
1345
|
+
# TODO(ukai): is it ok for gerrit case?
|
1346
|
+
base_branch = git_diff_args[0]
|
1347
|
+
else:
|
1348
|
+
if self.GetBranch() is None:
|
1349
|
+
DieWithError('Can\'t upload from detached HEAD state. Get on a branch!')
|
1350
|
+
|
1351
|
+
# Default to diffing against common ancestor of upstream branch
|
1352
|
+
base_branch = self.GetCommonAncestorWithUpstream()
|
1353
|
+
git_diff_args = [base_branch, 'HEAD']
|
1354
|
+
|
1355
|
+
# Make sure authenticated to codereview before running potentially expensive
|
1356
|
+
# hooks. It is a fast, best efforts check. Codereview still can reject the
|
1357
|
+
# authentication during the actual upload.
|
1358
|
+
self._codereview_impl.EnsureAuthenticated(force=options.force)
|
1359
|
+
|
1360
|
+
# Apply watchlists on upload.
|
1361
|
+
change = self.GetChange(base_branch, None)
|
1362
|
+
watchlist = watchlists.Watchlists(change.RepositoryRoot())
|
1363
|
+
files = [f.LocalPath() for f in change.AffectedFiles()]
|
1364
|
+
if not options.bypass_watchlists:
|
1365
|
+
self.SetWatchers(watchlist.GetWatchersForPaths(files))
|
1366
|
+
|
1367
|
+
if not options.bypass_hooks:
|
1368
|
+
if options.reviewers or options.tbr_owners:
|
1369
|
+
# Set the reviewer list now so that presubmit checks can access it.
|
1370
|
+
change_description = ChangeDescription(change.FullDescriptionText())
|
1371
|
+
change_description.update_reviewers(options.reviewers,
|
1372
|
+
options.tbr_owners,
|
1373
|
+
change)
|
1374
|
+
change.SetDescriptionText(change_description.description)
|
1375
|
+
hook_results = self.RunHook(committing=False,
|
1376
|
+
may_prompt=not options.force,
|
1377
|
+
verbose=options.verbose,
|
1378
|
+
change=change)
|
1379
|
+
if not hook_results.should_continue():
|
1380
|
+
return 1
|
1381
|
+
if not options.reviewers and hook_results.reviewers:
|
1382
|
+
options.reviewers = hook_results.reviewers.split(',')
|
1383
|
+
|
1384
|
+
if self.GetIssue():
|
1385
|
+
latest_patchset = self.GetMostRecentPatchset()
|
1386
|
+
local_patchset = self.GetPatchset()
|
1387
|
+
if (latest_patchset and local_patchset and
|
1388
|
+
local_patchset != latest_patchset):
|
1389
|
+
print ('The last upload made from this repository was patchset #%d but '
|
1390
|
+
'the most recent patchset on the server is #%d.'
|
1391
|
+
% (local_patchset, latest_patchset))
|
1392
|
+
print ('Uploading will still work, but if you\'ve uploaded to this '
|
1393
|
+
'issue from another machine or branch the patch you\'re '
|
1394
|
+
'uploading now might not include those changes.')
|
1395
|
+
ask_for_data('About to upload; enter to confirm.')
|
1396
|
+
|
1397
|
+
print_stats(options.similarity, options.find_copies, git_diff_args)
|
1398
|
+
ret = self.CMDUploadChange(options, git_diff_args, change)
|
1399
|
+
if not ret:
|
1400
|
+
git_set_branch_value('last-upload-hash',
|
1401
|
+
RunGit(['rev-parse', 'HEAD']).strip())
|
1402
|
+
# Run post upload hooks, if specified.
|
1403
|
+
if settings.GetRunPostUploadHook():
|
1404
|
+
presubmit_support.DoPostUploadExecuter(
|
1405
|
+
change,
|
1406
|
+
self,
|
1407
|
+
settings.GetRoot(),
|
1408
|
+
options.verbose,
|
1409
|
+
sys.stdout)
|
1410
|
+
|
1411
|
+
# Upload all dependencies if specified.
|
1412
|
+
if options.dependencies:
|
1413
|
+
print
|
1414
|
+
print '--dependencies has been specified.'
|
1415
|
+
print 'All dependent local branches will be re-uploaded.'
|
1416
|
+
print
|
1417
|
+
# Remove the dependencies flag from args so that we do not end up in a
|
1418
|
+
# loop.
|
1419
|
+
orig_args.remove('--dependencies')
|
1420
|
+
ret = upload_branch_deps(self, orig_args)
|
1421
|
+
return ret
|
1422
|
+
|
1423
|
+
def SetCQState(self, new_state):
|
1424
|
+
"""Update the CQ state for latest patchset.
|
1425
|
+
|
1426
|
+
Issue must have been already uploaded and known.
|
1427
|
+
"""
|
1428
|
+
assert new_state in _CQState.ALL_STATES
|
1429
|
+
assert self.GetIssue()
|
1430
|
+
return self._codereview_impl.SetCQState(new_state)
|
1431
|
+
|
1432
|
+
# Forward methods to codereview specific implementation.
|
1433
|
+
|
1434
|
+
def CloseIssue(self):
|
1435
|
+
return self._codereview_impl.CloseIssue()
|
1436
|
+
|
1437
|
+
def GetStatus(self):
|
1438
|
+
return self._codereview_impl.GetStatus()
|
1439
|
+
|
1440
|
+
def GetCodereviewServer(self):
|
1441
|
+
return self._codereview_impl.GetCodereviewServer()
|
1442
|
+
|
1443
|
+
def GetApprovingReviewers(self):
|
1444
|
+
return self._codereview_impl.GetApprovingReviewers()
|
1445
|
+
|
1446
|
+
def GetMostRecentPatchset(self):
|
1447
|
+
return self._codereview_impl.GetMostRecentPatchset()
|
1448
|
+
|
1449
|
+
def __getattr__(self, attr):
|
1450
|
+
# This is because lots of untested code accesses Rietveld-specific stuff
|
1451
|
+
# directly, and it's hard to fix for sure. So, just let it work, and fix
|
1452
|
+
# on a cases by case basis.
|
1453
|
+
return getattr(self._codereview_impl, attr)
|
1454
|
+
|
1455
|
+
|
1456
|
+
class _ChangelistCodereviewBase(object):
|
1457
|
+
"""Abstract base class encapsulating codereview specifics of a changelist."""
|
1458
|
+
def __init__(self, changelist):
|
1459
|
+
self._changelist = changelist # instance of Changelist
|
1460
|
+
|
1461
|
+
def __getattr__(self, attr):
|
1462
|
+
# Forward methods to changelist.
|
1463
|
+
# TODO(tandrii): maybe clean up _GerritChangelistImpl and
|
1464
|
+
# _RietveldChangelistImpl to avoid this hack?
|
1465
|
+
return getattr(self._changelist, attr)
|
1466
|
+
|
1467
|
+
def GetStatus(self):
|
1468
|
+
"""Apply a rough heuristic to give a simple summary of an issue's review
|
1469
|
+
or CQ status, assuming adherence to a common workflow.
|
1470
|
+
|
1471
|
+
Returns None if no issue for this branch, or specific string keywords.
|
1472
|
+
"""
|
1473
|
+
raise NotImplementedError()
|
1474
|
+
|
1475
|
+
def GetCodereviewServer(self):
|
1476
|
+
"""Returns server URL without end slash, like "https://codereview.com"."""
|
1477
|
+
raise NotImplementedError()
|
1478
|
+
|
1479
|
+
def FetchDescription(self):
|
1480
|
+
"""Fetches and returns description from the codereview server."""
|
1481
|
+
raise NotImplementedError()
|
1482
|
+
|
1483
|
+
def GetCodereviewServerSetting(self):
|
1484
|
+
"""Returns git config setting for the codereview server."""
|
1485
|
+
raise NotImplementedError()
|
1486
|
+
|
1487
|
+
@classmethod
|
1488
|
+
def IssueSetting(cls, branch):
|
1489
|
+
return 'branch.%s.%s' % (branch, cls.IssueSettingSuffix())
|
1490
|
+
|
1491
|
+
@classmethod
|
1492
|
+
def IssueSettingSuffix(cls):
|
1493
|
+
"""Returns name of git config setting which stores issue number for a given
|
1494
|
+
branch."""
|
1495
|
+
raise NotImplementedError()
|
1496
|
+
|
1497
|
+
def PatchsetSetting(self):
|
1498
|
+
"""Returns name of git config setting which stores issue number."""
|
1499
|
+
raise NotImplementedError()
|
1500
|
+
|
1501
|
+
def GetRieveldObjForPresubmit(self):
|
1502
|
+
# This is an unfortunate Rietveld-embeddedness in presubmit.
|
1503
|
+
# For non-Rietveld codereviews, this probably should return a dummy object.
|
1504
|
+
raise NotImplementedError()
|
1505
|
+
|
1506
|
+
def GetGerritObjForPresubmit(self):
|
1507
|
+
# None is valid return value, otherwise presubmit_support.GerritAccessor.
|
1508
|
+
return None
|
1509
|
+
|
1510
|
+
def UpdateDescriptionRemote(self, description):
|
1511
|
+
"""Update the description on codereview site."""
|
1512
|
+
raise NotImplementedError()
|
1513
|
+
|
1514
|
+
def CloseIssue(self):
|
1515
|
+
"""Closes the issue."""
|
1516
|
+
raise NotImplementedError()
|
1517
|
+
|
1518
|
+
def GetApprovingReviewers(self):
|
1519
|
+
"""Returns a list of reviewers approving the change.
|
1520
|
+
|
1521
|
+
Note: not necessarily committers.
|
1522
|
+
"""
|
1523
|
+
raise NotImplementedError()
|
1524
|
+
|
1525
|
+
def GetMostRecentPatchset(self):
|
1526
|
+
"""Returns the most recent patchset number from the codereview site."""
|
1527
|
+
raise NotImplementedError()
|
1528
|
+
|
1529
|
+
def CMDPatchWithParsedIssue(self, parsed_issue_arg, reject, nocommit,
|
1530
|
+
directory):
|
1531
|
+
"""Fetches and applies the issue.
|
1532
|
+
|
1533
|
+
Arguments:
|
1534
|
+
parsed_issue_arg: instance of _ParsedIssueNumberArgument.
|
1535
|
+
reject: if True, reject the failed patch instead of switching to 3-way
|
1536
|
+
merge. Rietveld only.
|
1537
|
+
nocommit: do not commit the patch, thus leave the tree dirty. Rietveld
|
1538
|
+
only.
|
1539
|
+
directory: switch to directory before applying the patch. Rietveld only.
|
1540
|
+
"""
|
1541
|
+
raise NotImplementedError()
|
1542
|
+
|
1543
|
+
@staticmethod
|
1544
|
+
def ParseIssueURL(parsed_url):
|
1545
|
+
"""Parses url and returns instance of _ParsedIssueNumberArgument or None if
|
1546
|
+
failed."""
|
1547
|
+
raise NotImplementedError()
|
1548
|
+
|
1549
|
+
def EnsureAuthenticated(self, force):
|
1550
|
+
"""Best effort check that user is authenticated with codereview server.
|
1551
|
+
|
1552
|
+
Arguments:
|
1553
|
+
force: whether to skip confirmation questions.
|
1554
|
+
"""
|
1555
|
+
raise NotImplementedError()
|
1556
|
+
|
1557
|
+
def CMDUploadChange(self, options, args, change):
|
1558
|
+
"""Uploads a change to codereview."""
|
1559
|
+
raise NotImplementedError()
|
1560
|
+
|
1561
|
+
def SetCQState(self, new_state):
|
1562
|
+
"""Update the CQ state for latest patchset.
|
1563
|
+
|
1564
|
+
Issue must have been already uploaded and known.
|
1565
|
+
"""
|
1566
|
+
raise NotImplementedError()
|
1567
|
+
|
1568
|
+
|
1569
|
+
class _RietveldChangelistImpl(_ChangelistCodereviewBase):
|
1570
|
+
def __init__(self, changelist, auth_config=None, rietveld_server=None):
|
1571
|
+
super(_RietveldChangelistImpl, self).__init__(changelist)
|
1572
|
+
assert settings, 'must be initialized in _ChangelistCodereviewBase'
|
1573
|
+
settings.GetDefaultServerUrl()
|
1574
|
+
|
1575
|
+
self._rietveld_server = rietveld_server
|
1576
|
+
self._auth_config = auth_config
|
1577
|
+
self._props = None
|
1578
|
+
self._rpc_server = None
|
1579
|
+
|
1580
|
+
def GetCodereviewServer(self):
|
1581
|
+
if not self._rietveld_server:
|
1582
|
+
# If we're on a branch then get the server potentially associated
|
1583
|
+
# with that branch.
|
1584
|
+
if self.GetIssue():
|
1585
|
+
rietveld_server_setting = self.GetCodereviewServerSetting()
|
1586
|
+
if rietveld_server_setting:
|
1587
|
+
self._rietveld_server = gclient_utils.UpgradeToHttps(RunGit(
|
1588
|
+
['config', rietveld_server_setting], error_ok=True).strip())
|
1589
|
+
if not self._rietveld_server:
|
1590
|
+
self._rietveld_server = settings.GetDefaultServerUrl()
|
1591
|
+
return self._rietveld_server
|
1592
|
+
|
1593
|
+
def EnsureAuthenticated(self, force):
|
1594
|
+
"""Best effort check that user is authenticated with Rietveld server."""
|
1595
|
+
if self._auth_config.use_oauth2:
|
1596
|
+
authenticator = auth.get_authenticator_for_host(
|
1597
|
+
self.GetCodereviewServer(), self._auth_config)
|
1598
|
+
if not authenticator.has_cached_credentials():
|
1599
|
+
raise auth.LoginRequiredError(self.GetCodereviewServer())
|
1600
|
+
|
1601
|
+
def FetchDescription(self):
|
1602
|
+
issue = self.GetIssue()
|
1603
|
+
assert issue
|
1604
|
+
try:
|
1605
|
+
return self.RpcServer().get_description(issue).strip()
|
1606
|
+
except urllib2.HTTPError as e:
|
1607
|
+
if e.code == 404:
|
1608
|
+
DieWithError(
|
1609
|
+
('\nWhile fetching the description for issue %d, received a '
|
1610
|
+
'404 (not found)\n'
|
1611
|
+
'error. It is likely that you deleted this '
|
1612
|
+
'issue on the server. If this is the\n'
|
1613
|
+
'case, please run\n\n'
|
1614
|
+
' git cl issue 0\n\n'
|
1615
|
+
'to clear the association with the deleted issue. Then run '
|
1616
|
+
'this command again.') % issue)
|
1617
|
+
else:
|
1618
|
+
DieWithError(
|
1619
|
+
'\nFailed to fetch issue description. HTTP error %d' % e.code)
|
1620
|
+
except urllib2.URLError as e:
|
1621
|
+
print >> sys.stderr, (
|
1622
|
+
'Warning: Failed to retrieve CL description due to network '
|
1623
|
+
'failure.')
|
1624
|
+
return ''
|
1625
|
+
|
1626
|
+
def GetMostRecentPatchset(self):
|
1627
|
+
return self.GetIssueProperties()['patchsets'][-1]
|
1628
|
+
|
1629
|
+
def GetPatchSetDiff(self, issue, patchset):
|
1630
|
+
return self.RpcServer().get(
|
1631
|
+
'/download/issue%s_%s.diff' % (issue, patchset))
|
1632
|
+
|
1633
|
+
def GetIssueProperties(self):
|
1634
|
+
if self._props is None:
|
1635
|
+
issue = self.GetIssue()
|
1636
|
+
if not issue:
|
1637
|
+
self._props = {}
|
1638
|
+
else:
|
1639
|
+
self._props = self.RpcServer().get_issue_properties(issue, True)
|
1640
|
+
return self._props
|
1641
|
+
|
1642
|
+
def GetApprovingReviewers(self):
|
1643
|
+
return get_approving_reviewers(self.GetIssueProperties())
|
1644
|
+
|
1645
|
+
def AddComment(self, message):
|
1646
|
+
return self.RpcServer().add_comment(self.GetIssue(), message)
|
1647
|
+
|
1025
1648
|
def GetStatus(self):
|
1026
1649
|
"""Apply a rough heuristic to give a simple summary of an issue's review
|
1027
1650
|
or CQ status, assuming adherence to a common workflow.
|
@@ -1046,7 +1669,7 @@ or verify this branch is set up to track another (via the --track argument to
|
|
1046
1669
|
if props.get('closed'):
|
1047
1670
|
# Issue is closed.
|
1048
1671
|
return 'closed'
|
1049
|
-
if props.get('commit'):
|
1672
|
+
if props.get('commit') and not props.get('cq_dry_run', False):
|
1050
1673
|
# Issue is in the commit queue.
|
1051
1674
|
return 'commit'
|
1052
1675
|
|
@@ -1069,26 +1692,11 @@ or verify this branch is set up to track another (via the --track argument to
|
|
1069
1692
|
return 'reply'
|
1070
1693
|
return 'waiting'
|
1071
1694
|
|
1072
|
-
def
|
1073
|
-
"""Calls sys.exit() if the hook fails; returns a HookResults otherwise."""
|
1074
|
-
|
1075
|
-
try:
|
1076
|
-
return presubmit_support.DoPresubmitChecks(change, committing,
|
1077
|
-
verbose=verbose, output_stream=sys.stdout, input_stream=sys.stdin,
|
1078
|
-
default_presubmit=None, may_prompt=may_prompt,
|
1079
|
-
rietveld_obj=self.RpcServer())
|
1080
|
-
except presubmit_support.PresubmitFailure, e:
|
1081
|
-
DieWithError(
|
1082
|
-
('%s\nMaybe your depot_tools is out of date?\n'
|
1083
|
-
'If all fails, contact maruel@') % e)
|
1084
|
-
|
1085
|
-
def UpdateDescription(self, description):
|
1086
|
-
self.description = description
|
1695
|
+
def UpdateDescriptionRemote(self, description):
|
1087
1696
|
return self.RpcServer().update_description(
|
1088
1697
|
self.GetIssue(), self.description)
|
1089
1698
|
|
1090
1699
|
def CloseIssue(self):
|
1091
|
-
"""Updates the description and closes the issue."""
|
1092
1700
|
return self.RpcServer().close_issue(self.GetIssue())
|
1093
1701
|
|
1094
1702
|
def SetFlag(self, flag, value):
|
@@ -1112,65 +1720,851 @@ or verify this branch is set up to track another (via the --track argument to
|
|
1112
1720
|
"""
|
1113
1721
|
if not self._rpc_server:
|
1114
1722
|
self._rpc_server = rietveld.CachingRietveld(
|
1115
|
-
self.
|
1723
|
+
self.GetCodereviewServer(),
|
1116
1724
|
self._auth_config or auth.make_auth_config())
|
1117
1725
|
return self._rpc_server
|
1118
1726
|
|
1119
|
-
|
1120
|
-
|
1121
|
-
return '
|
1727
|
+
@classmethod
|
1728
|
+
def IssueSettingSuffix(cls):
|
1729
|
+
return 'rietveldissue'
|
1122
1730
|
|
1123
|
-
def
|
1731
|
+
def PatchsetSetting(self):
|
1124
1732
|
"""Return the git setting that stores this change's most recent patchset."""
|
1125
1733
|
return 'branch.%s.rietveldpatchset' % self.GetBranch()
|
1126
1734
|
|
1127
|
-
def
|
1735
|
+
def GetCodereviewServerSetting(self):
|
1128
1736
|
"""Returns the git setting that stores this change's rietveld server."""
|
1129
1737
|
branch = self.GetBranch()
|
1130
1738
|
if branch:
|
1131
1739
|
return 'branch.%s.rietveldserver' % branch
|
1132
1740
|
return None
|
1133
1741
|
|
1742
|
+
def GetRieveldObjForPresubmit(self):
|
1743
|
+
return self.RpcServer()
|
1134
1744
|
|
1135
|
-
def
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
prompt = 'Rietveld server (host[:port])'
|
1140
|
-
prompt += ' [%s]' % (server or DEFAULT_SERVER)
|
1141
|
-
newserver = ask_for_data(prompt + ':')
|
1142
|
-
if not server and not newserver:
|
1143
|
-
newserver = DEFAULT_SERVER
|
1144
|
-
if newserver:
|
1145
|
-
newserver = gclient_utils.UpgradeToHttps(newserver)
|
1146
|
-
if newserver != server:
|
1147
|
-
RunGit(['config', 'rietveld.server', newserver])
|
1745
|
+
def SetCQState(self, new_state):
|
1746
|
+
props = self.GetIssueProperties()
|
1747
|
+
if props.get('private'):
|
1748
|
+
DieWithError('Cannot set-commit on private issue')
|
1148
1749
|
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1750
|
+
if new_state == _CQState.COMMIT:
|
1751
|
+
self.SetFlag('commit', '1')
|
1752
|
+
elif new_state == _CQState.NONE:
|
1753
|
+
self.SetFlag('commit', '0')
|
1754
|
+
else:
|
1755
|
+
raise NotImplementedError()
|
1756
|
+
|
1757
|
+
|
1758
|
+
def CMDPatchWithParsedIssue(self, parsed_issue_arg, reject, nocommit,
|
1759
|
+
directory):
|
1760
|
+
# TODO(maruel): Use apply_issue.py
|
1761
|
+
|
1762
|
+
# PatchIssue should never be called with a dirty tree. It is up to the
|
1763
|
+
# caller to check this, but just in case we assert here since the
|
1764
|
+
# consequences of the caller not checking this could be dire.
|
1765
|
+
assert(not git_common.is_dirty_git_tree('apply'))
|
1766
|
+
assert(parsed_issue_arg.valid)
|
1767
|
+
self._changelist.issue = parsed_issue_arg.issue
|
1768
|
+
if parsed_issue_arg.hostname:
|
1769
|
+
self._rietveld_server = 'https://%s' % parsed_issue_arg.hostname
|
1770
|
+
|
1771
|
+
if (isinstance(parsed_issue_arg, _RietveldParsedIssueNumberArgument) and
|
1772
|
+
parsed_issue_arg.patch_url):
|
1773
|
+
assert parsed_issue_arg.patchset
|
1774
|
+
patchset = parsed_issue_arg.patchset
|
1775
|
+
patch_data = urllib2.urlopen(parsed_issue_arg.patch_url).read()
|
1776
|
+
else:
|
1777
|
+
patchset = parsed_issue_arg.patchset or self.GetMostRecentPatchset()
|
1778
|
+
patch_data = self.GetPatchSetDiff(self.GetIssue(), patchset)
|
1779
|
+
|
1780
|
+
# Switch up to the top-level directory, if necessary, in preparation for
|
1781
|
+
# applying the patch.
|
1782
|
+
top = settings.GetRelativeRoot()
|
1783
|
+
if top:
|
1784
|
+
os.chdir(top)
|
1785
|
+
|
1786
|
+
# Git patches have a/ at the beginning of source paths. We strip that out
|
1787
|
+
# with a sed script rather than the -p flag to patch so we can feed either
|
1788
|
+
# Git or svn-style patches into the same apply command.
|
1789
|
+
# re.sub() should be used but flags=re.MULTILINE is only in python 2.7.
|
1790
|
+
try:
|
1791
|
+
patch_data = subprocess2.check_output(
|
1792
|
+
['sed', '-e', 's|^--- a/|--- |; s|^+++ b/|+++ |'], stdin=patch_data)
|
1793
|
+
except subprocess2.CalledProcessError:
|
1794
|
+
DieWithError('Git patch mungling failed.')
|
1795
|
+
logging.info(patch_data)
|
1796
|
+
|
1797
|
+
# We use "git apply" to apply the patch instead of "patch" so that we can
|
1798
|
+
# pick up file adds.
|
1799
|
+
# The --index flag means: also insert into the index (so we catch adds).
|
1800
|
+
cmd = ['git', 'apply', '--index', '-p0']
|
1801
|
+
if directory:
|
1802
|
+
cmd.extend(('--directory', directory))
|
1803
|
+
if reject:
|
1804
|
+
cmd.append('--reject')
|
1805
|
+
elif IsGitVersionAtLeast('1.7.12'):
|
1806
|
+
cmd.append('--3way')
|
1807
|
+
try:
|
1808
|
+
subprocess2.check_call(cmd, env=GetNoGitPagerEnv(),
|
1809
|
+
stdin=patch_data, stdout=subprocess2.VOID)
|
1810
|
+
except subprocess2.CalledProcessError:
|
1811
|
+
print 'Failed to apply the patch'
|
1812
|
+
return 1
|
1161
1813
|
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1814
|
+
# If we had an issue, commit the current state and register the issue.
|
1815
|
+
if not nocommit:
|
1816
|
+
RunGit(['commit', '-m', (self.GetDescription() + '\n\n' +
|
1817
|
+
'patch from issue %(i)s at patchset '
|
1818
|
+
'%(p)s (http://crrev.com/%(i)s#ps%(p)s)'
|
1819
|
+
% {'i': self.GetIssue(), 'p': patchset})])
|
1820
|
+
self.SetIssue(self.GetIssue())
|
1821
|
+
self.SetPatchset(patchset)
|
1822
|
+
print "Committed patch locally."
|
1823
|
+
else:
|
1824
|
+
print "Patch applied to index."
|
1825
|
+
return 0
|
1826
|
+
|
1827
|
+
@staticmethod
|
1828
|
+
def ParseIssueURL(parsed_url):
|
1829
|
+
if not parsed_url.scheme or not parsed_url.scheme.startswith('http'):
|
1830
|
+
return None
|
1831
|
+
# Typical url: https://domain/<issue_number>[/[other]]
|
1832
|
+
match = re.match('/(\d+)(/.*)?$', parsed_url.path)
|
1833
|
+
if match:
|
1834
|
+
return _RietveldParsedIssueNumberArgument(
|
1835
|
+
issue=int(match.group(1)),
|
1836
|
+
hostname=parsed_url.netloc)
|
1837
|
+
# Rietveld patch: https://domain/download/issue<number>_<patchset>.diff
|
1838
|
+
match = re.match(r'/download/issue(\d+)_(\d+).diff$', parsed_url.path)
|
1839
|
+
if match:
|
1840
|
+
return _RietveldParsedIssueNumberArgument(
|
1841
|
+
issue=int(match.group(1)),
|
1842
|
+
patchset=int(match.group(2)),
|
1843
|
+
hostname=parsed_url.netloc,
|
1844
|
+
patch_url=gclient_utils.UpgradeToHttps(parsed_url.geturl()))
|
1845
|
+
return None
|
1846
|
+
|
1847
|
+
def CMDUploadChange(self, options, args, change):
|
1848
|
+
"""Upload the patch to Rietveld."""
|
1849
|
+
upload_args = ['--assume_yes'] # Don't ask about untracked files.
|
1850
|
+
upload_args.extend(['--server', self.GetCodereviewServer()])
|
1851
|
+
upload_args.extend(auth.auth_config_to_command_options(self._auth_config))
|
1852
|
+
if options.emulate_svn_auto_props:
|
1853
|
+
upload_args.append('--emulate_svn_auto_props')
|
1854
|
+
|
1855
|
+
change_desc = None
|
1856
|
+
|
1857
|
+
if options.email is not None:
|
1858
|
+
upload_args.extend(['--email', options.email])
|
1859
|
+
|
1860
|
+
if self.GetIssue():
|
1861
|
+
if options.title:
|
1862
|
+
upload_args.extend(['--title', options.title])
|
1863
|
+
if options.message:
|
1864
|
+
upload_args.extend(['--message', options.message])
|
1865
|
+
upload_args.extend(['--issue', str(self.GetIssue())])
|
1866
|
+
print ('This branch is associated with issue %s. '
|
1867
|
+
'Adding patch to that issue.' % self.GetIssue())
|
1868
|
+
else:
|
1869
|
+
if options.title:
|
1870
|
+
upload_args.extend(['--title', options.title])
|
1871
|
+
message = (options.title or options.message or
|
1872
|
+
CreateDescriptionFromLog(args))
|
1873
|
+
change_desc = ChangeDescription(message)
|
1874
|
+
if options.reviewers or options.tbr_owners:
|
1875
|
+
change_desc.update_reviewers(options.reviewers,
|
1876
|
+
options.tbr_owners,
|
1877
|
+
change)
|
1878
|
+
if not options.force:
|
1879
|
+
change_desc.prompt()
|
1880
|
+
|
1881
|
+
if not change_desc.description:
|
1882
|
+
print "Description is empty; aborting."
|
1883
|
+
return 1
|
1884
|
+
|
1885
|
+
upload_args.extend(['--message', change_desc.description])
|
1886
|
+
if change_desc.get_reviewers():
|
1887
|
+
upload_args.append('--reviewers=%s' % ','.join(
|
1888
|
+
change_desc.get_reviewers()))
|
1889
|
+
if options.send_mail:
|
1890
|
+
if not change_desc.get_reviewers():
|
1891
|
+
DieWithError("Must specify reviewers to send email.")
|
1892
|
+
upload_args.append('--send_mail')
|
1893
|
+
|
1894
|
+
# We check this before applying rietveld.private assuming that in
|
1895
|
+
# rietveld.cc only addresses which we can send private CLs to are listed
|
1896
|
+
# if rietveld.private is set, and so we should ignore rietveld.cc only
|
1897
|
+
# when --private is specified explicitly on the command line.
|
1898
|
+
if options.private:
|
1899
|
+
logging.warn('rietveld.cc is ignored since private flag is specified. '
|
1900
|
+
'You need to review and add them manually if necessary.')
|
1901
|
+
cc = self.GetCCListWithoutDefault()
|
1902
|
+
else:
|
1903
|
+
cc = self.GetCCList()
|
1904
|
+
cc = ','.join(filter(None, (cc, ','.join(options.cc))))
|
1905
|
+
if cc:
|
1906
|
+
upload_args.extend(['--cc', cc])
|
1907
|
+
|
1908
|
+
if options.private or settings.GetDefaultPrivateFlag() == "True":
|
1909
|
+
upload_args.append('--private')
|
1910
|
+
|
1911
|
+
upload_args.extend(['--git_similarity', str(options.similarity)])
|
1912
|
+
if not options.find_copies:
|
1913
|
+
upload_args.extend(['--git_no_find_copies'])
|
1914
|
+
|
1915
|
+
# Include the upstream repo's URL in the change -- this is useful for
|
1916
|
+
# projects that have their source spread across multiple repos.
|
1917
|
+
remote_url = self.GetGitBaseUrlFromConfig()
|
1918
|
+
if not remote_url:
|
1919
|
+
if settings.GetIsGitSvn():
|
1920
|
+
remote_url = self.GetGitSvnRemoteUrl()
|
1921
|
+
else:
|
1922
|
+
if self.GetRemoteUrl() and '/' in self.GetUpstreamBranch():
|
1923
|
+
remote_url = '%s@%s' % (self.GetRemoteUrl(),
|
1924
|
+
self.GetUpstreamBranch().split('/')[-1])
|
1925
|
+
if remote_url:
|
1926
|
+
upload_args.extend(['--base_url', remote_url])
|
1927
|
+
remote, remote_branch = self.GetRemoteBranch()
|
1928
|
+
target_ref = GetTargetRef(remote, remote_branch, options.target_branch,
|
1929
|
+
settings.GetPendingRefPrefix())
|
1930
|
+
if target_ref:
|
1931
|
+
upload_args.extend(['--target_ref', target_ref])
|
1932
|
+
|
1933
|
+
# Look for dependent patchsets. See crbug.com/480453 for more details.
|
1934
|
+
remote, upstream_branch = self.FetchUpstreamTuple(self.GetBranch())
|
1935
|
+
upstream_branch = ShortBranchName(upstream_branch)
|
1936
|
+
if remote is '.':
|
1937
|
+
# A local branch is being tracked.
|
1938
|
+
local_branch = ShortBranchName(upstream_branch)
|
1939
|
+
if settings.GetIsSkipDependencyUpload(local_branch):
|
1940
|
+
print
|
1941
|
+
print ('Skipping dependency patchset upload because git config '
|
1942
|
+
'branch.%s.skip-deps-uploads is set to True.' % local_branch)
|
1943
|
+
print
|
1944
|
+
else:
|
1945
|
+
auth_config = auth.extract_auth_config_from_options(options)
|
1946
|
+
branch_cl = Changelist(branchref=local_branch,
|
1947
|
+
auth_config=auth_config)
|
1948
|
+
branch_cl_issue_url = branch_cl.GetIssueURL()
|
1949
|
+
branch_cl_issue = branch_cl.GetIssue()
|
1950
|
+
branch_cl_patchset = branch_cl.GetPatchset()
|
1951
|
+
if branch_cl_issue_url and branch_cl_issue and branch_cl_patchset:
|
1952
|
+
upload_args.extend(
|
1953
|
+
['--depends_on_patchset', '%s:%s' % (
|
1954
|
+
branch_cl_issue, branch_cl_patchset)])
|
1955
|
+
print(
|
1956
|
+
'\n'
|
1957
|
+
'The current branch (%s) is tracking a local branch (%s) with '
|
1958
|
+
'an associated CL.\n'
|
1959
|
+
'Adding %s/#ps%s as a dependency patchset.\n'
|
1960
|
+
'\n' % (self.GetBranch(), local_branch, branch_cl_issue_url,
|
1961
|
+
branch_cl_patchset))
|
1962
|
+
|
1963
|
+
project = settings.GetProject()
|
1964
|
+
if project:
|
1965
|
+
upload_args.extend(['--project', project])
|
1966
|
+
|
1967
|
+
if options.cq_dry_run:
|
1968
|
+
upload_args.extend(['--cq_dry_run'])
|
1969
|
+
|
1970
|
+
try:
|
1971
|
+
upload_args = ['upload'] + upload_args + args
|
1972
|
+
logging.info('upload.RealMain(%s)', upload_args)
|
1973
|
+
issue, patchset = upload.RealMain(upload_args)
|
1974
|
+
issue = int(issue)
|
1975
|
+
patchset = int(patchset)
|
1976
|
+
except KeyboardInterrupt:
|
1977
|
+
sys.exit(1)
|
1978
|
+
except:
|
1979
|
+
# If we got an exception after the user typed a description for their
|
1980
|
+
# change, back up the description before re-raising.
|
1981
|
+
if change_desc:
|
1982
|
+
backup_path = os.path.expanduser(DESCRIPTION_BACKUP_FILE)
|
1983
|
+
print('\nGot exception while uploading -- saving description to %s\n' %
|
1984
|
+
backup_path)
|
1985
|
+
backup_file = open(backup_path, 'w')
|
1986
|
+
backup_file.write(change_desc.description)
|
1987
|
+
backup_file.close()
|
1988
|
+
raise
|
1989
|
+
|
1990
|
+
if not self.GetIssue():
|
1991
|
+
self.SetIssue(issue)
|
1992
|
+
self.SetPatchset(patchset)
|
1993
|
+
|
1994
|
+
if options.use_commit_queue:
|
1995
|
+
self.SetCQState(_CQState.COMMIT)
|
1996
|
+
return 0
|
1997
|
+
|
1998
|
+
|
1999
|
+
class _GerritChangelistImpl(_ChangelistCodereviewBase):
|
2000
|
+
def __init__(self, changelist, auth_config=None):
|
2001
|
+
# auth_config is Rietveld thing, kept here to preserve interface only.
|
2002
|
+
super(_GerritChangelistImpl, self).__init__(changelist)
|
2003
|
+
self._change_id = None
|
2004
|
+
# Lazily cached values.
|
2005
|
+
self._gerrit_server = None # e.g. https://chromium-review.googlesource.com
|
2006
|
+
self._gerrit_host = None # e.g. chromium-review.googlesource.com
|
2007
|
+
|
2008
|
+
def _GetGerritHost(self):
|
2009
|
+
# Lazy load of configs.
|
2010
|
+
self.GetCodereviewServer()
|
2011
|
+
return self._gerrit_host
|
2012
|
+
|
2013
|
+
def _GetGitHost(self):
|
2014
|
+
"""Returns git host to be used when uploading change to Gerrit."""
|
2015
|
+
return urlparse.urlparse(self.GetRemoteUrl()).netloc
|
2016
|
+
|
2017
|
+
def GetCodereviewServer(self):
|
2018
|
+
if not self._gerrit_server:
|
2019
|
+
# If we're on a branch then get the server potentially associated
|
2020
|
+
# with that branch.
|
2021
|
+
if self.GetIssue():
|
2022
|
+
gerrit_server_setting = self.GetCodereviewServerSetting()
|
2023
|
+
if gerrit_server_setting:
|
2024
|
+
self._gerrit_server = RunGit(['config', gerrit_server_setting],
|
2025
|
+
error_ok=True).strip()
|
2026
|
+
if self._gerrit_server:
|
2027
|
+
self._gerrit_host = urlparse.urlparse(self._gerrit_server).netloc
|
2028
|
+
if not self._gerrit_server:
|
2029
|
+
# We assume repo to be hosted on Gerrit, and hence Gerrit server
|
2030
|
+
# has "-review" suffix for lowest level subdomain.
|
2031
|
+
parts = self._GetGitHost().split('.')
|
2032
|
+
parts[0] = parts[0] + '-review'
|
2033
|
+
self._gerrit_host = '.'.join(parts)
|
2034
|
+
self._gerrit_server = 'https://%s' % self._gerrit_host
|
2035
|
+
return self._gerrit_server
|
2036
|
+
|
2037
|
+
@classmethod
|
2038
|
+
def IssueSettingSuffix(cls):
|
2039
|
+
return 'gerritissue'
|
2040
|
+
|
2041
|
+
def EnsureAuthenticated(self, force):
|
2042
|
+
"""Best effort check that user is authenticated with Gerrit server."""
|
2043
|
+
if settings.GetGerritSkipEnsureAuthenticated():
|
2044
|
+
# For projects with unusual authentication schemes.
|
2045
|
+
# See http://crbug.com/603378.
|
2046
|
+
return
|
2047
|
+
# Lazy-loader to identify Gerrit and Git hosts.
|
2048
|
+
if gerrit_util.GceAuthenticator.is_gce():
|
2049
|
+
return
|
2050
|
+
self.GetCodereviewServer()
|
2051
|
+
git_host = self._GetGitHost()
|
2052
|
+
assert self._gerrit_server and self._gerrit_host
|
2053
|
+
cookie_auth = gerrit_util.CookiesAuthenticator()
|
2054
|
+
|
2055
|
+
gerrit_auth = cookie_auth.get_auth_header(self._gerrit_host)
|
2056
|
+
git_auth = cookie_auth.get_auth_header(git_host)
|
2057
|
+
if gerrit_auth and git_auth:
|
2058
|
+
if gerrit_auth == git_auth:
|
2059
|
+
return
|
2060
|
+
print((
|
2061
|
+
'WARNING: you have different credentials for Gerrit and git hosts.\n'
|
2062
|
+
' Check your %s or %s file for credentials of hosts:\n'
|
2063
|
+
' %s\n'
|
2064
|
+
' %s\n'
|
2065
|
+
' %s') %
|
2066
|
+
(cookie_auth.get_gitcookies_path(), cookie_auth.get_netrc_path(),
|
2067
|
+
git_host, self._gerrit_host,
|
2068
|
+
cookie_auth.get_new_password_message(git_host)))
|
2069
|
+
if not force:
|
2070
|
+
ask_for_data('If you know what you are doing, press Enter to continue, '
|
2071
|
+
'Ctrl+C to abort.')
|
2072
|
+
return
|
2073
|
+
else:
|
2074
|
+
missing = (
|
2075
|
+
[] if gerrit_auth else [self._gerrit_host] +
|
2076
|
+
[] if git_auth else [git_host])
|
2077
|
+
DieWithError('Credentials for the following hosts are required:\n'
|
2078
|
+
' %s\n'
|
2079
|
+
'These are read from %s (or legacy %s)\n'
|
2080
|
+
'%s' % (
|
2081
|
+
'\n '.join(missing),
|
2082
|
+
cookie_auth.get_gitcookies_path(),
|
2083
|
+
cookie_auth.get_netrc_path(),
|
2084
|
+
cookie_auth.get_new_password_message(git_host)))
|
2085
|
+
|
2086
|
+
|
2087
|
+
def PatchsetSetting(self):
|
2088
|
+
"""Return the git setting that stores this change's most recent patchset."""
|
2089
|
+
return 'branch.%s.gerritpatchset' % self.GetBranch()
|
2090
|
+
|
2091
|
+
def GetCodereviewServerSetting(self):
|
2092
|
+
"""Returns the git setting that stores this change's Gerrit server."""
|
2093
|
+
branch = self.GetBranch()
|
2094
|
+
if branch:
|
2095
|
+
return 'branch.%s.gerritserver' % branch
|
2096
|
+
return None
|
2097
|
+
|
2098
|
+
def GetRieveldObjForPresubmit(self):
|
2099
|
+
class ThisIsNotRietveldIssue(object):
|
2100
|
+
def __nonzero__(self):
|
2101
|
+
# This is a hack to make presubmit_support think that rietveld is not
|
2102
|
+
# defined, yet still ensure that calls directly result in a decent
|
2103
|
+
# exception message below.
|
2104
|
+
return False
|
2105
|
+
|
2106
|
+
def __getattr__(self, attr):
|
2107
|
+
print(
|
2108
|
+
'You aren\'t using Rietveld at the moment, but Gerrit.\n'
|
2109
|
+
'Using Rietveld in your PRESUBMIT scripts won\'t work.\n'
|
2110
|
+
'Please, either change your PRESUBIT to not use rietveld_obj.%s,\n'
|
2111
|
+
'or use Rietveld for codereview.\n'
|
2112
|
+
'See also http://crbug.com/579160.' % attr)
|
2113
|
+
raise NotImplementedError()
|
2114
|
+
return ThisIsNotRietveldIssue()
|
2115
|
+
|
2116
|
+
def GetGerritObjForPresubmit(self):
|
2117
|
+
return presubmit_support.GerritAccessor(self._GetGerritHost())
|
2118
|
+
|
2119
|
+
def GetStatus(self):
|
2120
|
+
"""Apply a rough heuristic to give a simple summary of an issue's review
|
2121
|
+
or CQ status, assuming adherence to a common workflow.
|
2122
|
+
|
2123
|
+
Returns None if no issue for this branch, or one of the following keywords:
|
2124
|
+
* 'error' - error from review tool (including deleted issues)
|
2125
|
+
* 'unsent' - no reviewers added
|
2126
|
+
* 'waiting' - waiting for review
|
2127
|
+
* 'reply' - waiting for owner to reply to review
|
2128
|
+
* 'not lgtm' - Code-Review -2 from at least one approved reviewer
|
2129
|
+
* 'lgtm' - Code-Review +2 from at least one approved reviewer
|
2130
|
+
* 'commit' - in the commit queue
|
2131
|
+
* 'closed' - abandoned
|
2132
|
+
"""
|
2133
|
+
if not self.GetIssue():
|
2134
|
+
return None
|
2135
|
+
|
2136
|
+
try:
|
2137
|
+
data = self._GetChangeDetail(['DETAILED_LABELS', 'CURRENT_REVISION'])
|
2138
|
+
except httplib.HTTPException:
|
2139
|
+
return 'error'
|
2140
|
+
|
2141
|
+
if data['status'] == 'ABANDONED':
|
2142
|
+
return 'closed'
|
2143
|
+
|
2144
|
+
cq_label = data['labels'].get('Commit-Queue', {})
|
2145
|
+
if cq_label:
|
2146
|
+
# Vote value is a stringified integer, which we expect from 0 to 2.
|
2147
|
+
vote_value = cq_label.get('value', '0')
|
2148
|
+
vote_text = cq_label.get('values', {}).get(vote_value, '')
|
2149
|
+
if vote_text.lower() == 'commit':
|
2150
|
+
return 'commit'
|
2151
|
+
|
2152
|
+
lgtm_label = data['labels'].get('Code-Review', {})
|
2153
|
+
if lgtm_label:
|
2154
|
+
if 'rejected' in lgtm_label:
|
2155
|
+
return 'not lgtm'
|
2156
|
+
if 'approved' in lgtm_label:
|
2157
|
+
return 'lgtm'
|
2158
|
+
|
2159
|
+
if not data.get('reviewers', {}).get('REVIEWER', []):
|
2160
|
+
return 'unsent'
|
2161
|
+
|
2162
|
+
messages = data.get('messages', [])
|
2163
|
+
if messages:
|
2164
|
+
owner = data['owner'].get('_account_id')
|
2165
|
+
last_message_author = messages[-1].get('author', {}).get('_account_id')
|
2166
|
+
if owner != last_message_author:
|
2167
|
+
# Some reply from non-owner.
|
2168
|
+
return 'reply'
|
2169
|
+
|
2170
|
+
return 'waiting'
|
2171
|
+
|
2172
|
+
def GetMostRecentPatchset(self):
|
2173
|
+
data = self._GetChangeDetail(['CURRENT_REVISION'])
|
2174
|
+
return data['revisions'][data['current_revision']]['_number']
|
2175
|
+
|
2176
|
+
def FetchDescription(self):
|
2177
|
+
data = self._GetChangeDetail(['CURRENT_REVISION'])
|
2178
|
+
current_rev = data['current_revision']
|
2179
|
+
url = data['revisions'][current_rev]['fetch']['http']['url']
|
2180
|
+
return gerrit_util.GetChangeDescriptionFromGitiles(url, current_rev)
|
2181
|
+
|
2182
|
+
def UpdateDescriptionRemote(self, description):
|
2183
|
+
gerrit_util.SetCommitMessage(self._GetGerritHost(), self.GetIssue(),
|
2184
|
+
description)
|
2185
|
+
|
2186
|
+
def CloseIssue(self):
|
2187
|
+
gerrit_util.AbandonChange(self._GetGerritHost(), self.GetIssue(), msg='')
|
2188
|
+
|
2189
|
+
def GetApprovingReviewers(self):
|
2190
|
+
"""Returns a list of reviewers approving the change.
|
2191
|
+
|
2192
|
+
Note: not necessarily committers.
|
2193
|
+
"""
|
2194
|
+
raise NotImplementedError()
|
2195
|
+
|
2196
|
+
def SubmitIssue(self, wait_for_merge=True):
|
2197
|
+
gerrit_util.SubmitChange(self._GetGerritHost(), self.GetIssue(),
|
2198
|
+
wait_for_merge=wait_for_merge)
|
2199
|
+
|
2200
|
+
def _GetChangeDetail(self, options=None, issue=None):
|
2201
|
+
options = options or []
|
2202
|
+
issue = issue or self.GetIssue()
|
2203
|
+
assert issue, 'issue required to query Gerrit'
|
2204
|
+
return gerrit_util.GetChangeDetail(self._GetGerritHost(), str(issue),
|
2205
|
+
options)
|
2206
|
+
|
2207
|
+
def CMDLand(self, force, bypass_hooks, verbose):
|
2208
|
+
if git_common.is_dirty_git_tree('land'):
|
2209
|
+
return 1
|
2210
|
+
differs = True
|
2211
|
+
last_upload = RunGit(['config',
|
2212
|
+
'branch.%s.gerritsquashhash' % self.GetBranch()],
|
2213
|
+
error_ok=True).strip()
|
2214
|
+
# Note: git diff outputs nothing if there is no diff.
|
2215
|
+
if not last_upload or RunGit(['diff', last_upload]).strip():
|
2216
|
+
print('WARNING: some changes from local branch haven\'t been uploaded')
|
2217
|
+
else:
|
2218
|
+
detail = self._GetChangeDetail(['CURRENT_REVISION'])
|
2219
|
+
if detail['current_revision'] == last_upload:
|
2220
|
+
differs = False
|
2221
|
+
else:
|
2222
|
+
print('WARNING: local branch contents differ from latest uploaded '
|
2223
|
+
'patchset')
|
2224
|
+
if differs:
|
2225
|
+
if not force:
|
2226
|
+
ask_for_data(
|
2227
|
+
'Do you want to submit latest Gerrit patchset and bypass hooks?')
|
2228
|
+
print('WARNING: bypassing hooks and submitting latest uploaded patchset')
|
2229
|
+
elif not bypass_hooks:
|
2230
|
+
hook_results = self.RunHook(
|
2231
|
+
committing=True,
|
2232
|
+
may_prompt=not force,
|
2233
|
+
verbose=verbose,
|
2234
|
+
change=self.GetChange(self.GetCommonAncestorWithUpstream(), None))
|
2235
|
+
if not hook_results.should_continue():
|
2236
|
+
return 1
|
2237
|
+
|
2238
|
+
self.SubmitIssue(wait_for_merge=True)
|
2239
|
+
print('Issue %s has been submitted.' % self.GetIssueURL())
|
2240
|
+
return 0
|
2241
|
+
|
2242
|
+
def CMDPatchWithParsedIssue(self, parsed_issue_arg, reject, nocommit,
|
2243
|
+
directory):
|
2244
|
+
assert not reject
|
2245
|
+
assert not nocommit
|
2246
|
+
assert not directory
|
2247
|
+
assert parsed_issue_arg.valid
|
2248
|
+
|
2249
|
+
self._changelist.issue = parsed_issue_arg.issue
|
2250
|
+
|
2251
|
+
if parsed_issue_arg.hostname:
|
2252
|
+
self._gerrit_host = parsed_issue_arg.hostname
|
2253
|
+
self._gerrit_server = 'https://%s' % self._gerrit_host
|
2254
|
+
|
2255
|
+
detail = self._GetChangeDetail(['ALL_REVISIONS'])
|
2256
|
+
|
2257
|
+
if not parsed_issue_arg.patchset:
|
2258
|
+
# Use current revision by default.
|
2259
|
+
revision_info = detail['revisions'][detail['current_revision']]
|
2260
|
+
patchset = int(revision_info['_number'])
|
2261
|
+
else:
|
2262
|
+
patchset = parsed_issue_arg.patchset
|
2263
|
+
for revision_info in detail['revisions'].itervalues():
|
2264
|
+
if int(revision_info['_number']) == parsed_issue_arg.patchset:
|
2265
|
+
break
|
2266
|
+
else:
|
2267
|
+
DieWithError('Couldn\'t find patchset %i in issue %i' %
|
2268
|
+
(parsed_issue_arg.patchset, self.GetIssue()))
|
2269
|
+
|
2270
|
+
fetch_info = revision_info['fetch']['http']
|
2271
|
+
RunGit(['fetch', fetch_info['url'], fetch_info['ref']])
|
2272
|
+
RunGit(['cherry-pick', 'FETCH_HEAD'])
|
2273
|
+
self.SetIssue(self.GetIssue())
|
2274
|
+
self.SetPatchset(patchset)
|
2275
|
+
print('Committed patch for issue %i pathset %i locally' %
|
2276
|
+
(self.GetIssue(), self.GetPatchset()))
|
2277
|
+
return 0
|
2278
|
+
|
2279
|
+
@staticmethod
|
2280
|
+
def ParseIssueURL(parsed_url):
|
2281
|
+
if not parsed_url.scheme or not parsed_url.scheme.startswith('http'):
|
2282
|
+
return None
|
2283
|
+
# Gerrit's new UI is https://domain/c/<issue_number>[/[patchset]]
|
2284
|
+
# But current GWT UI is https://domain/#/c/<issue_number>[/[patchset]]
|
2285
|
+
# Short urls like https://domain/<issue_number> can be used, but don't allow
|
2286
|
+
# specifying the patchset (you'd 404), but we allow that here.
|
2287
|
+
if parsed_url.path == '/':
|
2288
|
+
part = parsed_url.fragment
|
2289
|
+
else:
|
2290
|
+
part = parsed_url.path
|
2291
|
+
match = re.match('(/c)?/(\d+)(/(\d+)?/?)?$', part)
|
2292
|
+
if match:
|
2293
|
+
return _ParsedIssueNumberArgument(
|
2294
|
+
issue=int(match.group(2)),
|
2295
|
+
patchset=int(match.group(4)) if match.group(4) else None,
|
2296
|
+
hostname=parsed_url.netloc)
|
2297
|
+
return None
|
2298
|
+
|
2299
|
+
def CMDUploadChange(self, options, args, change):
|
2300
|
+
"""Upload the current branch to Gerrit."""
|
2301
|
+
if options.squash and options.no_squash:
|
2302
|
+
DieWithError('Can only use one of --squash or --no-squash')
|
2303
|
+
options.squash = ((settings.GetSquashGerritUploads() or options.squash) and
|
2304
|
+
not options.no_squash)
|
2305
|
+
# We assume the remote called "origin" is the one we want.
|
2306
|
+
# It is probably not worthwhile to support different workflows.
|
2307
|
+
gerrit_remote = 'origin'
|
2308
|
+
|
2309
|
+
remote, remote_branch = self.GetRemoteBranch()
|
2310
|
+
branch = GetTargetRef(remote, remote_branch, options.target_branch,
|
2311
|
+
pending_prefix='')
|
2312
|
+
|
2313
|
+
if options.squash:
|
2314
|
+
if not self.GetIssue():
|
2315
|
+
# TODO(tandrii): deperecate this after 2016Q2. Backwards compatibility
|
2316
|
+
# with shadow branch, which used to contain change-id for a given
|
2317
|
+
# branch, using which we can fetch actual issue number and set it as the
|
2318
|
+
# property of the branch, which is the new way.
|
2319
|
+
message = RunGitSilent([
|
2320
|
+
'show', '--format=%B', '-s',
|
2321
|
+
'refs/heads/git_cl_uploads/%s' % self.GetBranch()])
|
2322
|
+
if message:
|
2323
|
+
change_ids = git_footers.get_footer_change_id(message.strip())
|
2324
|
+
if change_ids and len(change_ids) == 1:
|
2325
|
+
details = self._GetChangeDetail(issue=change_ids[0])
|
2326
|
+
if details:
|
2327
|
+
print('WARNING: found old upload in branch git_cl_uploads/%s '
|
2328
|
+
'corresponding to issue %s' %
|
2329
|
+
(self.GetBranch(), details['_number']))
|
2330
|
+
self.SetIssue(details['_number'])
|
2331
|
+
if not self.GetIssue():
|
2332
|
+
DieWithError(
|
2333
|
+
'\n' # For readability of the blob below.
|
2334
|
+
'Found old upload in branch git_cl_uploads/%s, '
|
2335
|
+
'but failed to find corresponding Gerrit issue.\n'
|
2336
|
+
'If you know the issue number, set it manually first:\n'
|
2337
|
+
' git cl issue 123456\n'
|
2338
|
+
'If you intended to upload this CL as new issue, '
|
2339
|
+
'just delete or rename the old upload branch:\n'
|
2340
|
+
' git rename-branch git_cl_uploads/%s old_upload-%s\n'
|
2341
|
+
'After that, please run git cl upload again.' %
|
2342
|
+
tuple([self.GetBranch()] * 3))
|
2343
|
+
# End of backwards compatability.
|
2344
|
+
|
2345
|
+
if self.GetIssue():
|
2346
|
+
# Try to get the message from a previous upload.
|
2347
|
+
message = self.GetDescription()
|
2348
|
+
if not message:
|
2349
|
+
DieWithError(
|
2350
|
+
'failed to fetch description from current Gerrit issue %d\n'
|
2351
|
+
'%s' % (self.GetIssue(), self.GetIssueURL()))
|
2352
|
+
change_id = self._GetChangeDetail()['change_id']
|
2353
|
+
while True:
|
2354
|
+
footer_change_ids = git_footers.get_footer_change_id(message)
|
2355
|
+
if footer_change_ids == [change_id]:
|
2356
|
+
break
|
2357
|
+
if not footer_change_ids:
|
2358
|
+
message = git_footers.add_footer_change_id(message, change_id)
|
2359
|
+
print('WARNING: appended missing Change-Id to issue description')
|
2360
|
+
continue
|
2361
|
+
# There is already a valid footer but with different or several ids.
|
2362
|
+
# Doing this automatically is non-trivial as we don't want to lose
|
2363
|
+
# existing other footers, yet we want to append just 1 desired
|
2364
|
+
# Change-Id. Thus, just create a new footer, but let user verify the
|
2365
|
+
# new description.
|
2366
|
+
message = '%s\n\nChange-Id: %s' % (message, change_id)
|
2367
|
+
print(
|
2368
|
+
'WARNING: issue %s has Change-Id footer(s):\n'
|
2369
|
+
' %s\n'
|
2370
|
+
'but issue has Change-Id %s, according to Gerrit.\n'
|
2371
|
+
'Please, check the proposed correction to the description, '
|
2372
|
+
'and edit it if necessary but keep the "Change-Id: %s" footer\n'
|
2373
|
+
% (self.GetIssue(), '\n '.join(footer_change_ids), change_id,
|
2374
|
+
change_id))
|
2375
|
+
ask_for_data('Press enter to edit now, Ctrl+C to abort')
|
2376
|
+
if not options.force:
|
2377
|
+
change_desc = ChangeDescription(message)
|
2378
|
+
change_desc.prompt()
|
2379
|
+
message = change_desc.description
|
2380
|
+
if not message:
|
2381
|
+
DieWithError("Description is empty. Aborting...")
|
2382
|
+
# Continue the while loop.
|
2383
|
+
# Sanity check of this code - we should end up with proper message
|
2384
|
+
# footer.
|
2385
|
+
assert [change_id] == git_footers.get_footer_change_id(message)
|
2386
|
+
change_desc = ChangeDescription(message)
|
2387
|
+
else:
|
2388
|
+
change_desc = ChangeDescription(
|
2389
|
+
options.message or CreateDescriptionFromLog(args))
|
2390
|
+
if not options.force:
|
2391
|
+
change_desc.prompt()
|
2392
|
+
if not change_desc.description:
|
2393
|
+
DieWithError("Description is empty. Aborting...")
|
2394
|
+
message = change_desc.description
|
2395
|
+
change_ids = git_footers.get_footer_change_id(message)
|
2396
|
+
if len(change_ids) > 1:
|
2397
|
+
DieWithError('too many Change-Id footers, at most 1 allowed.')
|
2398
|
+
if not change_ids:
|
2399
|
+
# Generate the Change-Id automatically.
|
2400
|
+
message = git_footers.add_footer_change_id(
|
2401
|
+
message, GenerateGerritChangeId(message))
|
2402
|
+
change_desc.set_description(message)
|
2403
|
+
change_ids = git_footers.get_footer_change_id(message)
|
2404
|
+
assert len(change_ids) == 1
|
2405
|
+
change_id = change_ids[0]
|
2406
|
+
|
2407
|
+
remote, upstream_branch = self.FetchUpstreamTuple(self.GetBranch())
|
2408
|
+
if remote is '.':
|
2409
|
+
# If our upstream branch is local, we base our squashed commit on its
|
2410
|
+
# squashed version.
|
2411
|
+
upstream_branch_name = scm.GIT.ShortBranchName(upstream_branch)
|
2412
|
+
# Check the squashed hash of the parent.
|
2413
|
+
parent = RunGit(['config',
|
2414
|
+
'branch.%s.gerritsquashhash' % upstream_branch_name],
|
2415
|
+
error_ok=True).strip()
|
2416
|
+
# Verify that the upstream branch has been uploaded too, otherwise
|
2417
|
+
# Gerrit will create additional CLs when uploading.
|
2418
|
+
if not parent or (RunGitSilent(['rev-parse', upstream_branch + ':']) !=
|
2419
|
+
RunGitSilent(['rev-parse', parent + ':'])):
|
2420
|
+
# TODO(tandrii): remove "old depot_tools" part on April 12, 2016.
|
2421
|
+
DieWithError(
|
2422
|
+
'Upload upstream branch %s first.\n'
|
2423
|
+
'Note: maybe you\'ve uploaded it with --no-squash or with an old '
|
2424
|
+
'version of depot_tools. If so, then re-upload it with:\n'
|
2425
|
+
' git cl upload --squash\n' % upstream_branch_name)
|
2426
|
+
else:
|
2427
|
+
parent = self.GetCommonAncestorWithUpstream()
|
2428
|
+
|
2429
|
+
tree = RunGit(['rev-parse', 'HEAD:']).strip()
|
2430
|
+
ref_to_push = RunGit(['commit-tree', tree, '-p', parent,
|
2431
|
+
'-m', message]).strip()
|
2432
|
+
else:
|
2433
|
+
change_desc = ChangeDescription(
|
2434
|
+
options.message or CreateDescriptionFromLog(args))
|
2435
|
+
if not change_desc.description:
|
2436
|
+
DieWithError("Description is empty. Aborting...")
|
2437
|
+
|
2438
|
+
if not git_footers.get_footer_change_id(change_desc.description):
|
2439
|
+
DownloadGerritHook(False)
|
2440
|
+
change_desc.set_description(self._AddChangeIdToCommitMessage(options,
|
2441
|
+
args))
|
2442
|
+
ref_to_push = 'HEAD'
|
2443
|
+
parent = '%s/%s' % (gerrit_remote, branch)
|
2444
|
+
change_id = git_footers.get_footer_change_id(change_desc.description)[0]
|
2445
|
+
|
2446
|
+
assert change_desc
|
2447
|
+
commits = RunGitSilent(['rev-list', '%s..%s' % (parent,
|
2448
|
+
ref_to_push)]).splitlines()
|
2449
|
+
if len(commits) > 1:
|
2450
|
+
print('WARNING: This will upload %d commits. Run the following command '
|
2451
|
+
'to see which commits will be uploaded: ' % len(commits))
|
2452
|
+
print('git log %s..%s' % (parent, ref_to_push))
|
2453
|
+
print('You can also use `git squash-branch` to squash these into a '
|
2454
|
+
'single commit.')
|
2455
|
+
ask_for_data('About to upload; enter to confirm.')
|
2456
|
+
|
2457
|
+
if options.reviewers or options.tbr_owners:
|
2458
|
+
change_desc.update_reviewers(options.reviewers, options.tbr_owners,
|
2459
|
+
change)
|
2460
|
+
|
2461
|
+
# Extra options that can be specified at push time. Doc:
|
2462
|
+
# https://gerrit-review.googlesource.com/Documentation/user-upload.html
|
2463
|
+
refspec_opts = []
|
2464
|
+
if options.title:
|
2465
|
+
# Per doc, spaces must be converted to underscores, and Gerrit will do the
|
2466
|
+
# reverse on its side.
|
2467
|
+
if '_' in options.title:
|
2468
|
+
print('WARNING: underscores in title will be converted to spaces.')
|
2469
|
+
refspec_opts.append('m=' + options.title.replace(' ', '_'))
|
2470
|
+
|
2471
|
+
cc = self.GetCCList().split(',')
|
2472
|
+
if options.cc:
|
2473
|
+
cc.extend(options.cc)
|
2474
|
+
cc = filter(None, cc)
|
2475
|
+
if cc:
|
2476
|
+
# refspec_opts.extend('cc=' + email.strip() for email in cc)
|
2477
|
+
# TODO(tandrii): enable this back. http://crbug.com/604377
|
2478
|
+
print('WARNING: Gerrit doesn\'t yet support cc-ing arbitrary emails.\n'
|
2479
|
+
' Ignoring cc-ed emails. See http://crbug.com/604377.')
|
2480
|
+
|
2481
|
+
if change_desc.get_reviewers():
|
2482
|
+
refspec_opts.extend('r=' + email.strip()
|
2483
|
+
for email in change_desc.get_reviewers())
|
2484
|
+
|
2485
|
+
|
2486
|
+
refspec_suffix = ''
|
2487
|
+
if refspec_opts:
|
2488
|
+
refspec_suffix = '%' + ','.join(refspec_opts)
|
2489
|
+
assert ' ' not in refspec_suffix, (
|
2490
|
+
'spaces not allowed in refspec: "%s"' % refspec_suffix)
|
2491
|
+
refspec = '%s:refs/for/%s%s' % (ref_to_push, branch, refspec_suffix)
|
2492
|
+
|
2493
|
+
push_stdout = gclient_utils.CheckCallAndFilter(
|
2494
|
+
['git', 'push', gerrit_remote, refspec],
|
2495
|
+
print_stdout=True,
|
2496
|
+
# Flush after every line: useful for seeing progress when running as
|
2497
|
+
# recipe.
|
2498
|
+
filter_fn=lambda _: sys.stdout.flush())
|
2499
|
+
|
2500
|
+
if options.squash:
|
2501
|
+
regex = re.compile(r'remote:\s+https?://[\w\-\.\/]*/(\d+)\s.*')
|
2502
|
+
change_numbers = [m.group(1)
|
2503
|
+
for m in map(regex.match, push_stdout.splitlines())
|
2504
|
+
if m]
|
2505
|
+
if len(change_numbers) != 1:
|
2506
|
+
DieWithError(
|
2507
|
+
('Created|Updated %d issues on Gerrit, but only 1 expected.\n'
|
2508
|
+
'Change-Id: %s') % (len(change_numbers), change_id))
|
2509
|
+
self.SetIssue(change_numbers[0])
|
2510
|
+
RunGit(['config', 'branch.%s.gerritsquashhash' % self.GetBranch(),
|
2511
|
+
ref_to_push])
|
2512
|
+
return 0
|
2513
|
+
|
2514
|
+
def _AddChangeIdToCommitMessage(self, options, args):
|
2515
|
+
"""Re-commits using the current message, assumes the commit hook is in
|
2516
|
+
place.
|
2517
|
+
"""
|
2518
|
+
log_desc = options.message or CreateDescriptionFromLog(args)
|
2519
|
+
git_command = ['commit', '--amend', '-m', log_desc]
|
2520
|
+
RunGit(git_command)
|
2521
|
+
new_log_desc = CreateDescriptionFromLog(args)
|
2522
|
+
if git_footers.get_footer_change_id(new_log_desc):
|
2523
|
+
print 'git-cl: Added Change-Id to commit message.'
|
2524
|
+
return new_log_desc
|
2525
|
+
else:
|
2526
|
+
print >> sys.stderr, 'ERROR: Gerrit commit-msg hook not available.'
|
2527
|
+
|
2528
|
+
def SetCQState(self, new_state):
|
2529
|
+
"""Sets the Commit-Queue label assuming canonical CQ config for Gerrit."""
|
2530
|
+
# TODO(tandrii): maybe allow configurability in codereview.settings or by
|
2531
|
+
# self-discovery of label config for this CL using REST API.
|
2532
|
+
vote_map = {
|
2533
|
+
_CQState.NONE: 0,
|
2534
|
+
_CQState.DRY_RUN: 1,
|
2535
|
+
_CQState.COMMIT : 2,
|
2536
|
+
}
|
2537
|
+
gerrit_util.SetReview(self._GetGerritHost(), self.GetIssue(),
|
2538
|
+
labels={'Commit-Queue': vote_map[new_state]})
|
2539
|
+
|
2540
|
+
|
2541
|
+
_CODEREVIEW_IMPLEMENTATIONS = {
|
2542
|
+
'rietveld': _RietveldChangelistImpl,
|
2543
|
+
'gerrit': _GerritChangelistImpl,
|
2544
|
+
}
|
2545
|
+
|
2546
|
+
|
2547
|
+
def _add_codereview_select_options(parser):
|
2548
|
+
"""Appends --gerrit and --rietveld options to force specific codereview."""
|
2549
|
+
parser.codereview_group = optparse.OptionGroup(
|
2550
|
+
parser, 'EXPERIMENTAL! Codereview override options')
|
2551
|
+
parser.add_option_group(parser.codereview_group)
|
2552
|
+
parser.codereview_group.add_option(
|
2553
|
+
'--gerrit', action='store_true',
|
2554
|
+
help='Force the use of Gerrit for codereview')
|
2555
|
+
parser.codereview_group.add_option(
|
2556
|
+
'--rietveld', action='store_true',
|
2557
|
+
help='Force the use of Rietveld for codereview')
|
1171
2558
|
|
1172
|
-
|
1173
|
-
|
2559
|
+
|
2560
|
+
def _process_codereview_select_options(parser, options):
|
2561
|
+
if options.gerrit and options.rietveld:
|
2562
|
+
parser.error('Options --gerrit and --rietveld are mutually exclusive')
|
2563
|
+
options.forced_codereview = None
|
2564
|
+
if options.gerrit:
|
2565
|
+
options.forced_codereview = 'gerrit'
|
2566
|
+
elif options.rietveld:
|
2567
|
+
options.forced_codereview = 'rietveld'
|
1174
2568
|
|
1175
2569
|
|
1176
2570
|
class ChangeDescription(object):
|
@@ -1356,6 +2750,14 @@ def LoadCodereviewSettingsFromFile(fileobj):
|
|
1356
2750
|
if 'GERRIT_HOST' in keyvals:
|
1357
2751
|
RunGit(['config', 'gerrit.host', keyvals['GERRIT_HOST']])
|
1358
2752
|
|
2753
|
+
if 'GERRIT_SQUASH_UPLOADS' in keyvals:
|
2754
|
+
RunGit(['config', 'gerrit.squash-uploads',
|
2755
|
+
keyvals['GERRIT_SQUASH_UPLOADS']])
|
2756
|
+
|
2757
|
+
if 'GERRIT_SKIP_ENSURE_AUTHENTICATED' in keyvals:
|
2758
|
+
RunGit(['config', 'gerrit.skip-ensure-authenticated',
|
2759
|
+
keyvals['GERRIT_SKIP_ENSURE_AUTHENTICATED']])
|
2760
|
+
|
1359
2761
|
if 'PUSH_URL_CONFIG' in keyvals and 'ORIGIN_URL_CONFIG' in keyvals:
|
1360
2762
|
#should be of the form
|
1361
2763
|
#PUSH_URL_CONFIG: url.ssh://gitrw.chromium.org.pushinsteadof
|
@@ -1377,8 +2779,13 @@ def hasSheBang(fname):
|
|
1377
2779
|
return f.read(2).startswith('#!')
|
1378
2780
|
|
1379
2781
|
|
1380
|
-
|
1381
|
-
|
2782
|
+
# TODO(bpastene) Remove once a cleaner fix to crbug.com/600473 presents itself.
|
2783
|
+
def DownloadHooks(*args, **kwargs):
|
2784
|
+
pass
|
2785
|
+
|
2786
|
+
|
2787
|
+
def DownloadGerritHook(force):
|
2788
|
+
"""Download and install Gerrit commit-msg hook.
|
1382
2789
|
|
1383
2790
|
Args:
|
1384
2791
|
force: True to update hooks. False to install hooks if not present.
|
@@ -1392,6 +2799,10 @@ def DownloadHooks(force):
|
|
1392
2799
|
if not force:
|
1393
2800
|
return
|
1394
2801
|
try:
|
2802
|
+
print(
|
2803
|
+
'WARNING: installing Gerrit commit-msg hook.\n'
|
2804
|
+
' This behavior of git cl will soon be disabled.\n'
|
2805
|
+
' See bug http://crbug.com/579176.')
|
1395
2806
|
urlretrieve(src, dst)
|
1396
2807
|
if not hasSheBang(dst):
|
1397
2808
|
DieWithError('Not a script: %s\n'
|
@@ -1408,10 +2819,50 @@ def DownloadHooks(force):
|
|
1408
2819
|
'chmod +x .git/hooks/commit-msg' % src)
|
1409
2820
|
|
1410
2821
|
|
2822
|
+
|
2823
|
+
def GetRietveldCodereviewSettingsInteractively():
|
2824
|
+
"""Prompt the user for settings."""
|
2825
|
+
server = settings.GetDefaultServerUrl(error_ok=True)
|
2826
|
+
prompt = 'Rietveld server (host[:port])'
|
2827
|
+
prompt += ' [%s]' % (server or DEFAULT_SERVER)
|
2828
|
+
newserver = ask_for_data(prompt + ':')
|
2829
|
+
if not server and not newserver:
|
2830
|
+
newserver = DEFAULT_SERVER
|
2831
|
+
if newserver:
|
2832
|
+
newserver = gclient_utils.UpgradeToHttps(newserver)
|
2833
|
+
if newserver != server:
|
2834
|
+
RunGit(['config', 'rietveld.server', newserver])
|
2835
|
+
|
2836
|
+
def SetProperty(initial, caption, name, is_url):
|
2837
|
+
prompt = caption
|
2838
|
+
if initial:
|
2839
|
+
prompt += ' ("x" to clear) [%s]' % initial
|
2840
|
+
new_val = ask_for_data(prompt + ':')
|
2841
|
+
if new_val == 'x':
|
2842
|
+
RunGit(['config', '--unset-all', 'rietveld.' + name], error_ok=True)
|
2843
|
+
elif new_val:
|
2844
|
+
if is_url:
|
2845
|
+
new_val = gclient_utils.UpgradeToHttps(new_val)
|
2846
|
+
if new_val != initial:
|
2847
|
+
RunGit(['config', 'rietveld.' + name, new_val])
|
2848
|
+
|
2849
|
+
SetProperty(settings.GetDefaultCCList(), 'CC list', 'cc', False)
|
2850
|
+
SetProperty(settings.GetDefaultPrivateFlag(),
|
2851
|
+
'Private flag (rietveld only)', 'private', False)
|
2852
|
+
SetProperty(settings.GetTreeStatusUrl(error_ok=True), 'Tree status URL',
|
2853
|
+
'tree-status-url', False)
|
2854
|
+
SetProperty(settings.GetViewVCUrl(), 'ViewVC URL', 'viewvc-url', True)
|
2855
|
+
SetProperty(settings.GetBugPrefix(), 'Bug Prefix', 'bug-prefix', False)
|
2856
|
+
SetProperty(settings.GetRunPostUploadHook(), 'Run Post Upload Hook',
|
2857
|
+
'run-post-upload-hook', False)
|
2858
|
+
|
1411
2859
|
@subcommand.usage('[repo root containing codereview.settings]')
|
1412
2860
|
def CMDconfig(parser, args):
|
1413
2861
|
"""Edits configuration for this tree."""
|
1414
2862
|
|
2863
|
+
print('WARNING: git cl config works for Rietveld only.\n'
|
2864
|
+
'For Gerrit, see http://crbug.com/603116.')
|
2865
|
+
# TODO(tandrii): add Gerrit support as part of http://crbug.com/603116.
|
1415
2866
|
parser.add_option('--activate-update', action='store_true',
|
1416
2867
|
help='activate auto-updating [rietveld] section in '
|
1417
2868
|
'.git/config')
|
@@ -1429,8 +2880,7 @@ def CMDconfig(parser, args):
|
|
1429
2880
|
return
|
1430
2881
|
|
1431
2882
|
if len(args) == 0:
|
1432
|
-
|
1433
|
-
DownloadHooks(True)
|
2883
|
+
GetRietveldCodereviewSettingsInteractively()
|
1434
2884
|
return 0
|
1435
2885
|
|
1436
2886
|
url = args[0]
|
@@ -1439,7 +2889,6 @@ def CMDconfig(parser, args):
|
|
1439
2889
|
|
1440
2890
|
# Load code review settings and download hooks (if available).
|
1441
2891
|
LoadCodereviewSettingsFromFile(urllib2.urlopen(url))
|
1442
|
-
DownloadHooks(True)
|
1443
2892
|
return 0
|
1444
2893
|
|
1445
2894
|
|
@@ -1484,7 +2933,7 @@ def fetch_cl_status(branch, auth_config=None):
|
|
1484
2933
|
|
1485
2934
|
def get_cl_statuses(
|
1486
2935
|
branches, fine_grained, max_processes=None, auth_config=None):
|
1487
|
-
"""Returns a blocking iterable of (branch, issue,
|
2936
|
+
"""Returns a blocking iterable of (branch, issue, status) for given branches.
|
1488
2937
|
|
1489
2938
|
If fine_grained is true, this will fetch CL statuses from the server.
|
1490
2939
|
Otherwise, simply indicate if there's a matching url for the given branches.
|
@@ -1492,7 +2941,15 @@ def get_cl_statuses(
|
|
1492
2941
|
If max_processes is specified, it is used as the maximum number of processes
|
1493
2942
|
to spawn to fetch CL status from the server. Otherwise 1 process per branch is
|
1494
2943
|
spawned.
|
2944
|
+
|
2945
|
+
See GetStatus() for a list of possible statuses.
|
1495
2946
|
"""
|
2947
|
+
def fetch(branch):
|
2948
|
+
if not branch:
|
2949
|
+
return None
|
2950
|
+
|
2951
|
+
return fetch_cl_status(branch, auth_config=auth_config)
|
2952
|
+
|
1496
2953
|
# Silence upload.py otherwise it becomes unwieldly.
|
1497
2954
|
upload.verbosity = 0
|
1498
2955
|
|
@@ -1500,7 +2957,7 @@ def get_cl_statuses(
|
|
1500
2957
|
# Process one branch synchronously to work through authentication, then
|
1501
2958
|
# spawn processes to process all the other branches in parallel.
|
1502
2959
|
if branches:
|
1503
|
-
|
2960
|
+
|
1504
2961
|
yield fetch(branches[0])
|
1505
2962
|
|
1506
2963
|
branches_to_fetch = branches[1:]
|
@@ -1508,13 +2965,28 @@ def get_cl_statuses(
|
|
1508
2965
|
min(max_processes, len(branches_to_fetch))
|
1509
2966
|
if max_processes is not None
|
1510
2967
|
else len(branches_to_fetch))
|
1511
|
-
|
1512
|
-
|
2968
|
+
|
2969
|
+
fetched_branches = set()
|
2970
|
+
it = pool.imap_unordered(fetch, branches_to_fetch).__iter__()
|
2971
|
+
while True:
|
2972
|
+
try:
|
2973
|
+
row = it.next(timeout=5)
|
2974
|
+
except multiprocessing.TimeoutError:
|
2975
|
+
break
|
2976
|
+
|
2977
|
+
fetched_branches.add(row[0])
|
2978
|
+
yield row
|
2979
|
+
|
2980
|
+
# Add any branches that failed to fetch.
|
2981
|
+
for b in set(branches_to_fetch) - fetched_branches:
|
2982
|
+
cl = Changelist(branchref=b, auth_config=auth_config)
|
2983
|
+
yield (b, cl.GetIssueURL() if b else None, 'error')
|
2984
|
+
|
1513
2985
|
else:
|
1514
2986
|
# Do not use GetApprovingReviewers(), since it requires an HTTP request.
|
1515
2987
|
for b in branches:
|
1516
2988
|
cl = Changelist(branchref=b, auth_config=auth_config)
|
1517
|
-
url = cl.GetIssueURL()
|
2989
|
+
url = cl.GetIssueURL() if b else None
|
1518
2990
|
yield (b, url, 'waiting' if url else 'error')
|
1519
2991
|
|
1520
2992
|
|
@@ -1583,8 +3055,10 @@ def upload_branch_deps(cl, args):
|
|
1583
3055
|
'"git cl upload".')
|
1584
3056
|
ask_for_data('[Press enter to continue or ctrl-C to quit]')
|
1585
3057
|
|
1586
|
-
# Add a default patchset title to all upload calls.
|
1587
|
-
|
3058
|
+
# Add a default patchset title to all upload calls in Rietveld.
|
3059
|
+
if not cl.IsGerrit():
|
3060
|
+
args.extend(['-t', 'Updated patchset dependency'])
|
3061
|
+
|
1588
3062
|
# Record all dependents that failed to upload.
|
1589
3063
|
failures = {}
|
1590
3064
|
# Go through all dependents, checkout the branch and upload.
|
@@ -1669,6 +3143,7 @@ def CMDstatus(parser, args):
|
|
1669
3143
|
changes = (
|
1670
3144
|
Changelist(branchref=b, auth_config=auth_config)
|
1671
3145
|
for b in branches.splitlines())
|
3146
|
+
# TODO(tandrii): refactor to use CLs list instead of branches list.
|
1672
3147
|
branches = [c.GetBranch() for c in changes]
|
1673
3148
|
alignment = max(5, max(len(b) for b in branches))
|
1674
3149
|
print 'Branches associated with reviews:'
|
@@ -1686,7 +3161,7 @@ def CMDstatus(parser, args):
|
|
1686
3161
|
issue_url, status = branch_statuses.pop(branch)
|
1687
3162
|
color = color_for_status(status)
|
1688
3163
|
reset = Fore.RESET
|
1689
|
-
if not
|
3164
|
+
if not setup_color.IS_TTY:
|
1690
3165
|
color = ''
|
1691
3166
|
reset = ''
|
1692
3167
|
status_str = '(%s)' % status if status else ''
|
@@ -1697,10 +3172,10 @@ def CMDstatus(parser, args):
|
|
1697
3172
|
cl = Changelist(auth_config=auth_config)
|
1698
3173
|
print
|
1699
3174
|
print 'Current branch:',
|
3175
|
+
print cl.GetBranch()
|
1700
3176
|
if not cl.GetIssue():
|
1701
|
-
print '
|
3177
|
+
print 'No issue assigned.'
|
1702
3178
|
return 0
|
1703
|
-
print cl.GetBranch()
|
1704
3179
|
print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL())
|
1705
3180
|
if not options.fast:
|
1706
3181
|
print 'Issue description:'
|
@@ -1734,7 +3209,9 @@ def CMDissue(parser, args):
|
|
1734
3209
|
help='Lookup the branch(es) for the specified issues. If '
|
1735
3210
|
'no issues are specified, all branches with mapped '
|
1736
3211
|
'issues will be listed.')
|
3212
|
+
_add_codereview_select_options(parser)
|
1737
3213
|
options, args = parser.parse_args(args)
|
3214
|
+
_process_codereview_select_options(parser, options)
|
1738
3215
|
|
1739
3216
|
if options.reverse:
|
1740
3217
|
branches = RunGit(['for-each-ref', 'refs/heads',
|
@@ -1753,13 +3230,13 @@ def CMDissue(parser, args):
|
|
1753
3230
|
print 'Branch for issue number %s: %s' % (
|
1754
3231
|
issue, ', '.join(issue_branch_map.get(int(issue)) or ('None',)))
|
1755
3232
|
else:
|
1756
|
-
cl = Changelist()
|
3233
|
+
cl = Changelist(codereview=options.forced_codereview)
|
1757
3234
|
if len(args) > 0:
|
1758
3235
|
try:
|
1759
3236
|
issue = int(args[0])
|
1760
3237
|
except ValueError:
|
1761
3238
|
DieWithError('Pass a number to set the issue or none to list it.\n'
|
1762
|
-
|
3239
|
+
'Maybe you want to run git cl status?')
|
1763
3240
|
cl.SetIssue(issue)
|
1764
3241
|
print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL())
|
1765
3242
|
return 0
|
@@ -1771,6 +3248,8 @@ def CMDcomments(parser, args):
|
|
1771
3248
|
help='comment to add to an issue')
|
1772
3249
|
parser.add_option('-i', dest='issue',
|
1773
3250
|
help="review issue id (defaults to current issue)")
|
3251
|
+
parser.add_option('-j', '--json-file',
|
3252
|
+
help='File to write JSON summary to')
|
1774
3253
|
auth.add_auth_options(parser)
|
1775
3254
|
options, args = parser.parse_args(args)
|
1776
3255
|
auth_config = auth.extract_auth_config_from_options(options)
|
@@ -1782,18 +3261,28 @@ def CMDcomments(parser, args):
|
|
1782
3261
|
except ValueError:
|
1783
3262
|
DieWithError('A review issue id is expected to be a number')
|
1784
3263
|
|
1785
|
-
cl = Changelist(issue=issue, auth_config=auth_config)
|
3264
|
+
cl = Changelist(issue=issue, codereview='rietveld', auth_config=auth_config)
|
1786
3265
|
|
1787
3266
|
if options.comment:
|
1788
3267
|
cl.AddComment(options.comment)
|
1789
3268
|
return 0
|
1790
3269
|
|
1791
3270
|
data = cl.GetIssueProperties()
|
3271
|
+
summary = []
|
1792
3272
|
for message in sorted(data.get('messages', []), key=lambda x: x['date']):
|
3273
|
+
summary.append({
|
3274
|
+
'date': message['date'],
|
3275
|
+
'lgtm': False,
|
3276
|
+
'message': message['text'],
|
3277
|
+
'not_lgtm': False,
|
3278
|
+
'sender': message['sender'],
|
3279
|
+
})
|
1793
3280
|
if message['disapproval']:
|
1794
3281
|
color = Fore.RED
|
3282
|
+
summary[-1]['not lgtm'] = True
|
1795
3283
|
elif message['approval']:
|
1796
3284
|
color = Fore.GREEN
|
3285
|
+
summary[-1]['lgtm'] = True
|
1797
3286
|
elif message['sender'] == data['owner_email']:
|
1798
3287
|
color = Fore.MAGENTA
|
1799
3288
|
else:
|
@@ -1803,19 +3292,56 @@ def CMDcomments(parser, args):
|
|
1803
3292
|
Fore.RESET)
|
1804
3293
|
if message['text'].strip():
|
1805
3294
|
print '\n'.join(' ' + l for l in message['text'].splitlines())
|
3295
|
+
if options.json_file:
|
3296
|
+
with open(options.json_file, 'wb') as f:
|
3297
|
+
json.dump(summary, f)
|
1806
3298
|
return 0
|
1807
3299
|
|
1808
3300
|
|
3301
|
+
@subcommand.usage('[codereview url or issue id]')
|
1809
3302
|
def CMDdescription(parser, args):
|
1810
3303
|
"""Brings up the editor for the current CL's description."""
|
3304
|
+
parser.add_option('-d', '--display', action='store_true',
|
3305
|
+
help='Display the description instead of opening an editor')
|
3306
|
+
parser.add_option('-n', '--new-description',
|
3307
|
+
help='New description to set for this issue (- for stdin)')
|
3308
|
+
|
3309
|
+
_add_codereview_select_options(parser)
|
1811
3310
|
auth.add_auth_options(parser)
|
1812
|
-
options,
|
3311
|
+
options, args = parser.parse_args(args)
|
3312
|
+
_process_codereview_select_options(parser, options)
|
3313
|
+
|
3314
|
+
target_issue = None
|
3315
|
+
if len(args) > 0:
|
3316
|
+
issue_arg = ParseIssueNumberArgument(args[0])
|
3317
|
+
if not issue_arg.valid:
|
3318
|
+
parser.print_help()
|
3319
|
+
return 1
|
3320
|
+
target_issue = issue_arg.issue
|
3321
|
+
|
1813
3322
|
auth_config = auth.extract_auth_config_from_options(options)
|
1814
|
-
|
3323
|
+
|
3324
|
+
cl = Changelist(
|
3325
|
+
auth_config=auth_config, issue=target_issue,
|
3326
|
+
codereview=options.forced_codereview)
|
3327
|
+
|
1815
3328
|
if not cl.GetIssue():
|
1816
3329
|
DieWithError('This branch has no associated changelist.')
|
1817
3330
|
description = ChangeDescription(cl.GetDescription())
|
1818
|
-
|
3331
|
+
|
3332
|
+
if options.display:
|
3333
|
+
print description.description
|
3334
|
+
return 0
|
3335
|
+
|
3336
|
+
if options.new_description:
|
3337
|
+
text = options.new_description
|
3338
|
+
if text == '-':
|
3339
|
+
text = '\n'.join(l.rstrip() for l in sys.stdin)
|
3340
|
+
|
3341
|
+
description.set_description(text)
|
3342
|
+
else:
|
3343
|
+
description.prompt()
|
3344
|
+
|
1819
3345
|
if cl.GetDescription() != description.description:
|
1820
3346
|
cl.UpdateDescription(description.description)
|
1821
3347
|
return 0
|
@@ -1919,131 +3445,34 @@ def CMDpresubmit(parser, args):
|
|
1919
3445
|
return 0
|
1920
3446
|
|
1921
3447
|
|
1922
|
-
def
|
1923
|
-
"""
|
1924
|
-
place.
|
1925
|
-
"""
|
1926
|
-
log_desc = options.message or CreateDescriptionFromLog(args)
|
1927
|
-
git_command = ['commit', '--amend', '-m', log_desc]
|
1928
|
-
RunGit(git_command)
|
1929
|
-
new_log_desc = CreateDescriptionFromLog(args)
|
1930
|
-
if CHANGE_ID in new_log_desc:
|
1931
|
-
print 'git-cl: Added Change-Id to commit message.'
|
1932
|
-
else:
|
1933
|
-
print >> sys.stderr, 'ERROR: Gerrit commit-msg hook not available.'
|
1934
|
-
|
1935
|
-
|
1936
|
-
def GerritUpload(options, args, cl, change):
|
1937
|
-
"""upload the current branch to gerrit."""
|
1938
|
-
# We assume the remote called "origin" is the one we want.
|
1939
|
-
# It is probably not worthwhile to support different workflows.
|
1940
|
-
gerrit_remote = 'origin'
|
1941
|
-
|
1942
|
-
remote, remote_branch = cl.GetRemoteBranch()
|
1943
|
-
branch = GetTargetRef(remote, remote_branch, options.target_branch,
|
1944
|
-
pending_prefix='')
|
1945
|
-
|
1946
|
-
change_desc = ChangeDescription(
|
1947
|
-
options.message or CreateDescriptionFromLog(args))
|
1948
|
-
if not change_desc.description:
|
1949
|
-
print "Description is empty; aborting."
|
1950
|
-
return 1
|
1951
|
-
|
1952
|
-
if options.squash:
|
1953
|
-
# Try to get the message from a previous upload.
|
1954
|
-
shadow_branch = 'refs/heads/git_cl_uploads/' + cl.GetBranch()
|
1955
|
-
message = RunGitSilent(['show', '--format=%s\n\n%b', '-s', shadow_branch])
|
1956
|
-
if not message:
|
1957
|
-
if not options.force:
|
1958
|
-
change_desc.prompt()
|
1959
|
-
|
1960
|
-
if CHANGE_ID not in change_desc.description:
|
1961
|
-
# Run the commit-msg hook without modifying the head commit by writing
|
1962
|
-
# the commit message to a temporary file and running the hook over it,
|
1963
|
-
# then reading the file back in.
|
1964
|
-
commit_msg_hook = os.path.join(settings.GetRoot(), '.git', 'hooks',
|
1965
|
-
'commit-msg')
|
1966
|
-
file_handle, msg_file = tempfile.mkstemp(text=True,
|
1967
|
-
prefix='commit_msg')
|
1968
|
-
try:
|
1969
|
-
try:
|
1970
|
-
with os.fdopen(file_handle, 'w') as fileobj:
|
1971
|
-
fileobj.write(change_desc.description)
|
1972
|
-
finally:
|
1973
|
-
os.close(file_handle)
|
1974
|
-
RunCommand([commit_msg_hook, msg_file])
|
1975
|
-
change_desc.set_description(gclient_utils.FileRead(msg_file))
|
1976
|
-
finally:
|
1977
|
-
os.remove(msg_file)
|
1978
|
-
|
1979
|
-
if not change_desc.description:
|
1980
|
-
print "Description is empty; aborting."
|
1981
|
-
return 1
|
1982
|
-
|
1983
|
-
message = change_desc.description
|
1984
|
-
|
1985
|
-
remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch())
|
1986
|
-
if remote is '.':
|
1987
|
-
# If our upstream branch is local, we base our squashed commit on its
|
1988
|
-
# squashed version.
|
1989
|
-
parent = ('refs/heads/git_cl_uploads/' +
|
1990
|
-
scm.GIT.ShortBranchName(upstream_branch))
|
3448
|
+
def GenerateGerritChangeId(message):
|
3449
|
+
"""Returns Ixxxxxx...xxx change id.
|
1991
3450
|
|
1992
|
-
|
1993
|
-
|
1994
|
-
|
1995
|
-
RunGitSilent(['rev-parse', parent + ':'])):
|
1996
|
-
print 'Upload upstream branch ' + upstream_branch + ' first.'
|
1997
|
-
return 1
|
1998
|
-
else:
|
1999
|
-
parent = cl.GetCommonAncestorWithUpstream()
|
3451
|
+
Works the same way as
|
3452
|
+
https://gerrit-review.googlesource.com/tools/hooks/commit-msg
|
3453
|
+
but can be called on demand on all platforms.
|
2000
3454
|
|
2001
|
-
|
2002
|
-
|
2003
|
-
|
2004
|
-
|
2005
|
-
|
2006
|
-
|
2007
|
-
|
2008
|
-
|
2009
|
-
|
2010
|
-
|
2011
|
-
|
2012
|
-
|
2013
|
-
|
2014
|
-
|
2015
|
-
|
2016
|
-
|
2017
|
-
|
2018
|
-
|
2019
|
-
|
2020
|
-
|
2021
|
-
|
2022
|
-
|
2023
|
-
receive_options = []
|
2024
|
-
cc = cl.GetCCList().split(',')
|
2025
|
-
if options.cc:
|
2026
|
-
cc.extend(options.cc)
|
2027
|
-
cc = filter(None, cc)
|
2028
|
-
if cc:
|
2029
|
-
receive_options += ['--cc=' + email for email in cc]
|
2030
|
-
if change_desc.get_reviewers():
|
2031
|
-
receive_options.extend(
|
2032
|
-
'--reviewer=' + email for email in change_desc.get_reviewers())
|
2033
|
-
|
2034
|
-
git_command = ['push']
|
2035
|
-
if receive_options:
|
2036
|
-
git_command.append('--receive-pack=git receive-pack %s' %
|
2037
|
-
' '.join(receive_options))
|
2038
|
-
git_command += [gerrit_remote, ref_to_push + ':refs/for/' + branch]
|
2039
|
-
RunGit(git_command)
|
2040
|
-
|
2041
|
-
if options.squash:
|
2042
|
-
head = RunGit(['rev-parse', 'HEAD']).strip()
|
2043
|
-
RunGit(['update-ref', '-m', 'Uploaded ' + head, shadow_branch, ref_to_push])
|
2044
|
-
|
2045
|
-
# TODO(ukai): parse Change-Id: and set issue number?
|
2046
|
-
return 0
|
3455
|
+
The basic idea is to generate git hash of a state of the tree, original commit
|
3456
|
+
message, author/committer info and timestamps.
|
3457
|
+
"""
|
3458
|
+
lines = []
|
3459
|
+
tree_hash = RunGitSilent(['write-tree'])
|
3460
|
+
lines.append('tree %s' % tree_hash.strip())
|
3461
|
+
code, parent = RunGitWithCode(['rev-parse', 'HEAD~0'], suppress_stderr=False)
|
3462
|
+
if code == 0:
|
3463
|
+
lines.append('parent %s' % parent.strip())
|
3464
|
+
author = RunGitSilent(['var', 'GIT_AUTHOR_IDENT'])
|
3465
|
+
lines.append('author %s' % author.strip())
|
3466
|
+
committer = RunGitSilent(['var', 'GIT_COMMITTER_IDENT'])
|
3467
|
+
lines.append('committer %s' % committer.strip())
|
3468
|
+
lines.append('')
|
3469
|
+
# Note: Gerrit's commit-hook actually cleans message of some lines and
|
3470
|
+
# whitespace. This code is not doing this, but it clearly won't decrease
|
3471
|
+
# entropy.
|
3472
|
+
lines.append(message)
|
3473
|
+
change_hash = RunCommand(['git', 'hash-object', '-t', 'commit', '--stdin'],
|
3474
|
+
stdin='\n'.join(lines))
|
3475
|
+
return 'I%s' % change_hash.strip()
|
2047
3476
|
|
2048
3477
|
|
2049
3478
|
def GetTargetRef(remote, remote_branch, target_branch, pending_prefix):
|
@@ -2101,148 +3530,6 @@ def GetTargetRef(remote, remote_branch, target_branch, pending_prefix):
|
|
2101
3530
|
return remote_branch
|
2102
3531
|
|
2103
3532
|
|
2104
|
-
def RietveldUpload(options, args, cl, change):
|
2105
|
-
"""upload the patch to rietveld."""
|
2106
|
-
upload_args = ['--assume_yes'] # Don't ask about untracked files.
|
2107
|
-
upload_args.extend(['--server', cl.GetRietveldServer()])
|
2108
|
-
upload_args.extend(auth.auth_config_to_command_options(cl.auth_config))
|
2109
|
-
if options.emulate_svn_auto_props:
|
2110
|
-
upload_args.append('--emulate_svn_auto_props')
|
2111
|
-
|
2112
|
-
change_desc = None
|
2113
|
-
|
2114
|
-
if options.email is not None:
|
2115
|
-
upload_args.extend(['--email', options.email])
|
2116
|
-
|
2117
|
-
if cl.GetIssue():
|
2118
|
-
if options.title:
|
2119
|
-
upload_args.extend(['--title', options.title])
|
2120
|
-
if options.message:
|
2121
|
-
upload_args.extend(['--message', options.message])
|
2122
|
-
upload_args.extend(['--issue', str(cl.GetIssue())])
|
2123
|
-
print ("This branch is associated with issue %s. "
|
2124
|
-
"Adding patch to that issue." % cl.GetIssue())
|
2125
|
-
else:
|
2126
|
-
if options.title:
|
2127
|
-
upload_args.extend(['--title', options.title])
|
2128
|
-
message = options.title or options.message or CreateDescriptionFromLog(args)
|
2129
|
-
change_desc = ChangeDescription(message)
|
2130
|
-
if options.reviewers or options.tbr_owners:
|
2131
|
-
change_desc.update_reviewers(options.reviewers,
|
2132
|
-
options.tbr_owners,
|
2133
|
-
change)
|
2134
|
-
if not options.force:
|
2135
|
-
change_desc.prompt()
|
2136
|
-
|
2137
|
-
if not change_desc.description:
|
2138
|
-
print "Description is empty; aborting."
|
2139
|
-
return 1
|
2140
|
-
|
2141
|
-
upload_args.extend(['--message', change_desc.description])
|
2142
|
-
if change_desc.get_reviewers():
|
2143
|
-
upload_args.append('--reviewers=' + ','.join(change_desc.get_reviewers()))
|
2144
|
-
if options.send_mail:
|
2145
|
-
if not change_desc.get_reviewers():
|
2146
|
-
DieWithError("Must specify reviewers to send email.")
|
2147
|
-
upload_args.append('--send_mail')
|
2148
|
-
|
2149
|
-
# We check this before applying rietveld.private assuming that in
|
2150
|
-
# rietveld.cc only addresses which we can send private CLs to are listed
|
2151
|
-
# if rietveld.private is set, and so we should ignore rietveld.cc only when
|
2152
|
-
# --private is specified explicitly on the command line.
|
2153
|
-
if options.private:
|
2154
|
-
logging.warn('rietveld.cc is ignored since private flag is specified. '
|
2155
|
-
'You need to review and add them manually if necessary.')
|
2156
|
-
cc = cl.GetCCListWithoutDefault()
|
2157
|
-
else:
|
2158
|
-
cc = cl.GetCCList()
|
2159
|
-
cc = ','.join(filter(None, (cc, ','.join(options.cc))))
|
2160
|
-
if cc:
|
2161
|
-
upload_args.extend(['--cc', cc])
|
2162
|
-
|
2163
|
-
if options.private or settings.GetDefaultPrivateFlag() == "True":
|
2164
|
-
upload_args.append('--private')
|
2165
|
-
|
2166
|
-
upload_args.extend(['--git_similarity', str(options.similarity)])
|
2167
|
-
if not options.find_copies:
|
2168
|
-
upload_args.extend(['--git_no_find_copies'])
|
2169
|
-
|
2170
|
-
# Include the upstream repo's URL in the change -- this is useful for
|
2171
|
-
# projects that have their source spread across multiple repos.
|
2172
|
-
remote_url = cl.GetGitBaseUrlFromConfig()
|
2173
|
-
if not remote_url:
|
2174
|
-
if settings.GetIsGitSvn():
|
2175
|
-
remote_url = cl.GetGitSvnRemoteUrl()
|
2176
|
-
else:
|
2177
|
-
if cl.GetRemoteUrl() and '/' in cl.GetUpstreamBranch():
|
2178
|
-
remote_url = (cl.GetRemoteUrl() + '@'
|
2179
|
-
+ cl.GetUpstreamBranch().split('/')[-1])
|
2180
|
-
if remote_url:
|
2181
|
-
upload_args.extend(['--base_url', remote_url])
|
2182
|
-
remote, remote_branch = cl.GetRemoteBranch()
|
2183
|
-
target_ref = GetTargetRef(remote, remote_branch, options.target_branch,
|
2184
|
-
settings.GetPendingRefPrefix())
|
2185
|
-
if target_ref:
|
2186
|
-
upload_args.extend(['--target_ref', target_ref])
|
2187
|
-
|
2188
|
-
# Look for dependent patchsets. See crbug.com/480453 for more details.
|
2189
|
-
remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch())
|
2190
|
-
upstream_branch = ShortBranchName(upstream_branch)
|
2191
|
-
if remote is '.':
|
2192
|
-
# A local branch is being tracked.
|
2193
|
-
local_branch = ShortBranchName(upstream_branch)
|
2194
|
-
auth_config = auth.extract_auth_config_from_options(options)
|
2195
|
-
branch_cl = Changelist(branchref=local_branch, auth_config=auth_config)
|
2196
|
-
branch_cl_issue_url = branch_cl.GetIssueURL()
|
2197
|
-
branch_cl_issue = branch_cl.GetIssue()
|
2198
|
-
branch_cl_patchset = branch_cl.GetPatchset()
|
2199
|
-
if branch_cl_issue_url and branch_cl_issue and branch_cl_patchset:
|
2200
|
-
upload_args.extend(
|
2201
|
-
['--depends_on_patchset', '%s:%s' % (
|
2202
|
-
branch_cl_issue, branch_cl_patchset)])
|
2203
|
-
print
|
2204
|
-
print ('The current branch (%s) is tracking a local branch (%s) with '
|
2205
|
-
'an associated CL.') % (cl.GetBranch(), local_branch)
|
2206
|
-
print 'Adding %s/#ps%s as a dependency patchset.' % (
|
2207
|
-
branch_cl_issue_url, branch_cl_patchset)
|
2208
|
-
print
|
2209
|
-
|
2210
|
-
project = settings.GetProject()
|
2211
|
-
if project:
|
2212
|
-
upload_args.extend(['--project', project])
|
2213
|
-
|
2214
|
-
if options.cq_dry_run:
|
2215
|
-
upload_args.extend(['--cq_dry_run'])
|
2216
|
-
|
2217
|
-
try:
|
2218
|
-
upload_args = ['upload'] + upload_args + args
|
2219
|
-
logging.info('upload.RealMain(%s)', upload_args)
|
2220
|
-
issue, patchset = upload.RealMain(upload_args)
|
2221
|
-
issue = int(issue)
|
2222
|
-
patchset = int(patchset)
|
2223
|
-
except KeyboardInterrupt:
|
2224
|
-
sys.exit(1)
|
2225
|
-
except:
|
2226
|
-
# If we got an exception after the user typed a description for their
|
2227
|
-
# change, back up the description before re-raising.
|
2228
|
-
if change_desc:
|
2229
|
-
backup_path = os.path.expanduser(DESCRIPTION_BACKUP_FILE)
|
2230
|
-
print '\nGot exception while uploading -- saving description to %s\n' \
|
2231
|
-
% backup_path
|
2232
|
-
backup_file = open(backup_path, 'w')
|
2233
|
-
backup_file.write(change_desc.description)
|
2234
|
-
backup_file.close()
|
2235
|
-
raise
|
2236
|
-
|
2237
|
-
if not cl.GetIssue():
|
2238
|
-
cl.SetIssue(issue)
|
2239
|
-
cl.SetPatchset(patchset)
|
2240
|
-
|
2241
|
-
if options.use_commit_queue:
|
2242
|
-
cl.SetFlag('commit', '1')
|
2243
|
-
return 0
|
2244
|
-
|
2245
|
-
|
2246
3533
|
def cleanup_list(l):
|
2247
3534
|
"""Fixes a list so that comma separated items are put as individual items.
|
2248
3535
|
|
@@ -2256,7 +3543,14 @@ def cleanup_list(l):
|
|
2256
3543
|
|
2257
3544
|
@subcommand.usage('[args to "git diff"]')
|
2258
3545
|
def CMDupload(parser, args):
|
2259
|
-
"""Uploads the current changelist to codereview.
|
3546
|
+
"""Uploads the current changelist to codereview.
|
3547
|
+
|
3548
|
+
Can skip dependency patchset uploads for a branch by running:
|
3549
|
+
git config branch.branch_name.skip-deps-uploads True
|
3550
|
+
To unset run:
|
3551
|
+
git config --unset branch.branch_name.skip-deps-uploads
|
3552
|
+
Can also set the above globally by using the --global flag.
|
3553
|
+
"""
|
2260
3554
|
parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks',
|
2261
3555
|
help='bypass upload presubmit hook')
|
2262
3556
|
parser.add_option('--bypass-watchlists', action='store_true',
|
@@ -2265,7 +3559,8 @@ def CMDupload(parser, args):
|
|
2265
3559
|
parser.add_option('-f', action='store_true', dest='force',
|
2266
3560
|
help="force yes to questions (don't prompt)")
|
2267
3561
|
parser.add_option('-m', dest='message', help='message for patchset')
|
2268
|
-
parser.add_option('-t', dest='title',
|
3562
|
+
parser.add_option('-t', dest='title',
|
3563
|
+
help='title for patchset (Rietveld only)')
|
2269
3564
|
parser.add_option('-r', '--reviewers',
|
2270
3565
|
action='append', default=[],
|
2271
3566
|
help='reviewer email addresses')
|
@@ -2290,11 +3585,15 @@ def CMDupload(parser, args):
|
|
2290
3585
|
'Default: remote branch head, or master')
|
2291
3586
|
parser.add_option('--squash', action='store_true',
|
2292
3587
|
help='Squash multiple commits into one (Gerrit only)')
|
3588
|
+
parser.add_option('--no-squash', action='store_true',
|
3589
|
+
help='Don\'t squash multiple commits into one ' +
|
3590
|
+
'(Gerrit only)')
|
2293
3591
|
parser.add_option('--email', default=None,
|
2294
3592
|
help='email address to use to connect to Rietveld')
|
2295
3593
|
parser.add_option('--tbr-owners', dest='tbr_owners', action='store_true',
|
2296
3594
|
help='add a set of OWNERS to TBR')
|
2297
|
-
parser.add_option('--cq-dry-run', dest='cq_dry_run',
|
3595
|
+
parser.add_option('-d', '--cq-dry-run', dest='cq_dry_run',
|
3596
|
+
action='store_true',
|
2298
3597
|
help='Send the patchset to do a CQ dry run right after '
|
2299
3598
|
'upload.')
|
2300
3599
|
parser.add_option('--dependencies', action='store_true',
|
@@ -2304,7 +3603,9 @@ def CMDupload(parser, args):
|
|
2304
3603
|
orig_args = args
|
2305
3604
|
add_git_similarity(parser)
|
2306
3605
|
auth.add_auth_options(parser)
|
3606
|
+
_add_codereview_select_options(parser)
|
2307
3607
|
(options, args) = parser.parse_args(args)
|
3608
|
+
_process_codereview_select_options(parser, options)
|
2308
3609
|
auth_config = auth.extract_auth_config_from_options(options)
|
2309
3610
|
|
2310
3611
|
if git_common.is_dirty_git_tree('upload'):
|
@@ -2313,90 +3614,11 @@ def CMDupload(parser, args):
|
|
2313
3614
|
options.reviewers = cleanup_list(options.reviewers)
|
2314
3615
|
options.cc = cleanup_list(options.cc)
|
2315
3616
|
|
2316
|
-
|
2317
|
-
|
2318
|
-
# TODO(ukai): is it ok for gerrit case?
|
2319
|
-
base_branch = args[0]
|
2320
|
-
else:
|
2321
|
-
if cl.GetBranch() is None:
|
2322
|
-
DieWithError('Can\'t upload from detached HEAD state. Get on a branch!')
|
2323
|
-
|
2324
|
-
# Default to diffing against common ancestor of upstream branch
|
2325
|
-
base_branch = cl.GetCommonAncestorWithUpstream()
|
2326
|
-
args = [base_branch, 'HEAD']
|
2327
|
-
|
2328
|
-
# Make sure authenticated to Rietveld before running expensive hooks. It is
|
2329
|
-
# a fast, best efforts check. Rietveld still can reject the authentication
|
2330
|
-
# during the actual upload.
|
2331
|
-
if not settings.GetIsGerrit() and auth_config.use_oauth2:
|
2332
|
-
authenticator = auth.get_authenticator_for_host(
|
2333
|
-
cl.GetRietveldServer(), auth_config)
|
2334
|
-
if not authenticator.has_cached_credentials():
|
2335
|
-
raise auth.LoginRequiredError(cl.GetRietveldServer())
|
2336
|
-
|
2337
|
-
# Apply watchlists on upload.
|
2338
|
-
change = cl.GetChange(base_branch, None)
|
2339
|
-
watchlist = watchlists.Watchlists(change.RepositoryRoot())
|
2340
|
-
files = [f.LocalPath() for f in change.AffectedFiles()]
|
2341
|
-
if not options.bypass_watchlists:
|
2342
|
-
cl.SetWatchers(watchlist.GetWatchersForPaths(files))
|
2343
|
-
|
2344
|
-
if not options.bypass_hooks:
|
2345
|
-
if options.reviewers or options.tbr_owners:
|
2346
|
-
# Set the reviewer list now so that presubmit checks can access it.
|
2347
|
-
change_description = ChangeDescription(change.FullDescriptionText())
|
2348
|
-
change_description.update_reviewers(options.reviewers,
|
2349
|
-
options.tbr_owners,
|
2350
|
-
change)
|
2351
|
-
change.SetDescriptionText(change_description.description)
|
2352
|
-
hook_results = cl.RunHook(committing=False,
|
2353
|
-
may_prompt=not options.force,
|
2354
|
-
verbose=options.verbose,
|
2355
|
-
change=change)
|
2356
|
-
if not hook_results.should_continue():
|
2357
|
-
return 1
|
2358
|
-
if not options.reviewers and hook_results.reviewers:
|
2359
|
-
options.reviewers = hook_results.reviewers.split(',')
|
2360
|
-
|
2361
|
-
if cl.GetIssue():
|
2362
|
-
latest_patchset = cl.GetMostRecentPatchset()
|
2363
|
-
local_patchset = cl.GetPatchset()
|
2364
|
-
if latest_patchset and local_patchset and local_patchset != latest_patchset:
|
2365
|
-
print ('The last upload made from this repository was patchset #%d but '
|
2366
|
-
'the most recent patchset on the server is #%d.'
|
2367
|
-
% (local_patchset, latest_patchset))
|
2368
|
-
print ('Uploading will still work, but if you\'ve uploaded to this issue '
|
2369
|
-
'from another machine or branch the patch you\'re uploading now '
|
2370
|
-
'might not include those changes.')
|
2371
|
-
ask_for_data('About to upload; enter to confirm.')
|
2372
|
-
|
2373
|
-
print_stats(options.similarity, options.find_copies, args)
|
2374
|
-
if settings.GetIsGerrit():
|
2375
|
-
return GerritUpload(options, args, cl, change)
|
2376
|
-
ret = RietveldUpload(options, args, cl, change)
|
2377
|
-
if not ret:
|
2378
|
-
git_set_branch_value('last-upload-hash',
|
2379
|
-
RunGit(['rev-parse', 'HEAD']).strip())
|
2380
|
-
# Run post upload hooks, if specified.
|
2381
|
-
if settings.GetRunPostUploadHook():
|
2382
|
-
presubmit_support.DoPostUploadExecuter(
|
2383
|
-
change,
|
2384
|
-
cl,
|
2385
|
-
settings.GetRoot(),
|
2386
|
-
options.verbose,
|
2387
|
-
sys.stdout)
|
3617
|
+
# For sanity of test expectations, do this otherwise lazy-loading *now*.
|
3618
|
+
settings.GetIsGerrit()
|
2388
3619
|
|
2389
|
-
|
2390
|
-
|
2391
|
-
print
|
2392
|
-
print '--dependencies has been specified.'
|
2393
|
-
print 'All dependent local branches will be re-uploaded.'
|
2394
|
-
print
|
2395
|
-
# Remove the dependencies flag from args so that we do not end up in a
|
2396
|
-
# loop.
|
2397
|
-
orig_args.remove('--dependencies')
|
2398
|
-
upload_branch_deps(cl, orig_args)
|
2399
|
-
return ret
|
3620
|
+
cl = Changelist(auth_config=auth_config, codereview=options.forced_codereview)
|
3621
|
+
return cl.CMDUpload(options, args, orig_args)
|
2400
3622
|
|
2401
3623
|
|
2402
3624
|
def IsSubmoduleMergeCommit(ref):
|
@@ -2410,10 +3632,14 @@ def IsSubmoduleMergeCommit(ref):
|
|
2410
3632
|
def SendUpstream(parser, args, cmd):
|
2411
3633
|
"""Common code for CMDland and CmdDCommit
|
2412
3634
|
|
2413
|
-
|
2414
|
-
|
2415
|
-
|
2416
|
-
|
3635
|
+
In case of Gerrit, uses Gerrit REST api to "submit" the issue, which pushes
|
3636
|
+
upstream and closes the issue automatically and atomically.
|
3637
|
+
|
3638
|
+
Otherwise (in case of Rietveld):
|
3639
|
+
Squashes branch into a single commit.
|
3640
|
+
Updates changelog with metadata (e.g. pointer to review).
|
3641
|
+
Pushes/dcommits the code upstream.
|
3642
|
+
Updates review and closes.
|
2417
3643
|
"""
|
2418
3644
|
parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks',
|
2419
3645
|
help='bypass upload presubmit hook')
|
@@ -2432,6 +3658,24 @@ def SendUpstream(parser, args, cmd):
|
|
2432
3658
|
|
2433
3659
|
cl = Changelist(auth_config=auth_config)
|
2434
3660
|
|
3661
|
+
# TODO(tandrii): refactor this into _RietveldChangelistImpl method.
|
3662
|
+
if cl.IsGerrit():
|
3663
|
+
if options.message:
|
3664
|
+
# This could be implemented, but it requires sending a new patch to
|
3665
|
+
# Gerrit, as Gerrit unlike Rietveld versions messages with patchsets.
|
3666
|
+
# Besides, Gerrit has the ability to change the commit message on submit
|
3667
|
+
# automatically, thus there is no need to support this option (so far?).
|
3668
|
+
parser.error('-m MESSAGE option is not supported for Gerrit.')
|
3669
|
+
if options.contributor:
|
3670
|
+
parser.error(
|
3671
|
+
'-c CONTRIBUTOR option is not supported for Gerrit.\n'
|
3672
|
+
'Before uploading a commit to Gerrit, ensure it\'s author field is '
|
3673
|
+
'the contributor\'s "name <email>". If you can\'t upload such a '
|
3674
|
+
'commit for review, contact your repository admin and request'
|
3675
|
+
'"Forge-Author" permission.')
|
3676
|
+
return cl._codereview_impl.CMDLand(options.force, options.bypass_hooks,
|
3677
|
+
options.verbose)
|
3678
|
+
|
2435
3679
|
current = cl.GetBranch()
|
2436
3680
|
remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch())
|
2437
3681
|
if not settings.GetIsGitSvn() and remote == '.':
|
@@ -2516,12 +3760,6 @@ def SendUpstream(parser, args, cmd):
|
|
2516
3760
|
print('Unable to determine tree status. Please verify manually and '
|
2517
3761
|
'use "git cl %s --bypass-hooks" to commit on a closed tree.' % cmd)
|
2518
3762
|
return 1
|
2519
|
-
else:
|
2520
|
-
breakpad.SendStack(
|
2521
|
-
'GitClHooksBypassedCommit',
|
2522
|
-
'Issue %s/%s bypassed hook when committing (tree status was "%s")' %
|
2523
|
-
(cl.GetRietveldServer(), cl.GetIssue(), GetTreeStatus()),
|
2524
|
-
verbose=False)
|
2525
3763
|
|
2526
3764
|
change_desc = ChangeDescription(options.message)
|
2527
3765
|
if not change_desc.description and cl.GetIssue():
|
@@ -2545,8 +3783,10 @@ def SendUpstream(parser, args, cmd):
|
|
2545
3783
|
commit_desc = ChangeDescription(change_desc.description)
|
2546
3784
|
if cl.GetIssue():
|
2547
3785
|
# Xcode won't linkify this URL unless there is a non-whitespace character
|
2548
|
-
# after it. Add a period on a new line to circumvent this.
|
2549
|
-
|
3786
|
+
# after it. Add a period on a new line to circumvent this. Also add a space
|
3787
|
+
# before the period to make sure that Gitiles continues to correctly resolve
|
3788
|
+
# the URL.
|
3789
|
+
commit_desc.append_footer('Review URL: %s .' % cl.GetIssueURL())
|
2550
3790
|
if options.contributor:
|
2551
3791
|
commit_desc.append_footer('Patch from %s.' % options.contributor)
|
2552
3792
|
|
@@ -2603,19 +3843,21 @@ def SendUpstream(parser, args, cmd):
|
|
2603
3843
|
RunGit(['cherry-pick', cherry_pick_commit])
|
2604
3844
|
if cmd == 'land':
|
2605
3845
|
remote, branch = cl.FetchUpstreamTuple(cl.GetBranch())
|
3846
|
+
mirror = settings.GetGitMirror(remote)
|
3847
|
+
pushurl = mirror.url if mirror else remote
|
2606
3848
|
pending_prefix = settings.GetPendingRefPrefix()
|
2607
3849
|
if not pending_prefix or branch.startswith(pending_prefix):
|
2608
3850
|
# If not using refs/pending/heads/* at all, or target ref is already set
|
2609
3851
|
# to pending, then push to the target ref directly.
|
2610
3852
|
retcode, output = RunGitWithCode(
|
2611
|
-
['push', '--porcelain',
|
3853
|
+
['push', '--porcelain', pushurl, 'HEAD:%s' % branch])
|
2612
3854
|
pushed_to_pending = pending_prefix and branch.startswith(pending_prefix)
|
2613
3855
|
else:
|
2614
3856
|
# Cherry-pick the change on top of pending ref and then push it.
|
2615
3857
|
assert branch.startswith('refs/'), branch
|
2616
3858
|
assert pending_prefix[-1] == '/', pending_prefix
|
2617
3859
|
pending_ref = pending_prefix + branch[len('refs/'):]
|
2618
|
-
retcode, output = PushToGitPending(
|
3860
|
+
retcode, output = PushToGitPending(pushurl, pending_ref, branch)
|
2619
3861
|
pushed_to_pending = (retcode == 0)
|
2620
3862
|
if retcode == 0:
|
2621
3863
|
revision = RunGit(['rev-parse', 'HEAD']).strip()
|
@@ -2703,6 +3945,7 @@ def WaitForRealCommit(remote, pushed_commit, local_base_ref, real_ref):
|
|
2703
3945
|
print '(If you are impatient, you may Ctrl-C once without harm)'
|
2704
3946
|
target_tree = RunGit(['rev-parse', '%s:' % pushed_commit]).strip()
|
2705
3947
|
current_rev = RunGit(['rev-parse', local_base_ref]).strip()
|
3948
|
+
mirror = settings.GetGitMirror(remote)
|
2706
3949
|
|
2707
3950
|
loop = 0
|
2708
3951
|
while True:
|
@@ -2710,6 +3953,8 @@ def WaitForRealCommit(remote, pushed_commit, local_base_ref, real_ref):
|
|
2710
3953
|
sys.stdout.flush()
|
2711
3954
|
loop += 1
|
2712
3955
|
|
3956
|
+
if mirror:
|
3957
|
+
mirror.populate()
|
2713
3958
|
RunGit(['retry', 'fetch', remote, real_ref], stderr=subprocess2.VOID)
|
2714
3959
|
to_rev = RunGit(['rev-parse', 'FETCH_HEAD']).strip()
|
2715
3960
|
commits = RunGit(['rev-list', '%s..%s' % (current_rev, to_rev)])
|
@@ -2793,7 +4038,7 @@ def IsFatalPushFailure(push_stdout):
|
|
2793
4038
|
def CMDdcommit(parser, args):
|
2794
4039
|
"""Commits the current changelist via git-svn."""
|
2795
4040
|
if not settings.GetIsGitSvn():
|
2796
|
-
if get_footer_svn_id():
|
4041
|
+
if git_footers.get_footer_svn_id():
|
2797
4042
|
# If it looks like previous commits were mirrored with git-svn.
|
2798
4043
|
message = """This repository appears to be a git-svn mirror, but no
|
2799
4044
|
upstream SVN master is set. You probably need to run 'git auto-svn' once."""
|
@@ -2815,7 +4060,7 @@ proceed, please verify that the commit lands upstream as expected."""
|
|
2815
4060
|
@subcommand.usage('[upstream branch to apply against]')
|
2816
4061
|
def CMDland(parser, args):
|
2817
4062
|
"""Commits the current changelist via git."""
|
2818
|
-
if settings.GetIsGitSvn() or get_footer_svn_id():
|
4063
|
+
if settings.GetIsGitSvn() or git_footers.get_footer_svn_id():
|
2819
4064
|
print('This appears to be an SVN repository.')
|
2820
4065
|
print('Are you sure you didn\'t mean \'git cl dcommit\'?')
|
2821
4066
|
print('(Ignore if this is the first commit after migrating from svn->git)')
|
@@ -2823,7 +4068,7 @@ def CMDland(parser, args):
|
|
2823
4068
|
return SendUpstream(parser, args, 'land')
|
2824
4069
|
|
2825
4070
|
|
2826
|
-
@subcommand.usage('<patch url or issue id>')
|
4071
|
+
@subcommand.usage('<patch url or issue id or issue url>')
|
2827
4072
|
def CMDpatch(parser, args):
|
2828
4073
|
"""Patches in a code review."""
|
2829
4074
|
parser.add_option('-b', dest='newbranch',
|
@@ -2832,111 +4077,82 @@ def CMDpatch(parser, args):
|
|
2832
4077
|
help='with -b, clobber any existing branch')
|
2833
4078
|
parser.add_option('-d', '--directory', action='store', metavar='DIR',
|
2834
4079
|
help='Change to the directory DIR immediately, '
|
2835
|
-
'before doing anything else.')
|
4080
|
+
'before doing anything else. Rietveld only.')
|
2836
4081
|
parser.add_option('--reject', action='store_true',
|
2837
4082
|
help='failed patches spew .rej files rather than '
|
2838
|
-
'attempting a 3-way merge')
|
4083
|
+
'attempting a 3-way merge. Rietveld only.')
|
2839
4084
|
parser.add_option('-n', '--no-commit', action='store_true', dest='nocommit',
|
2840
|
-
help=
|
4085
|
+
help='don\'t commit after patch applies. Rietveld only.')
|
4086
|
+
|
4087
|
+
|
4088
|
+
group = optparse.OptionGroup(
|
4089
|
+
parser,
|
4090
|
+
'Options for continuing work on the current issue uploaded from a '
|
4091
|
+
'different clone (e.g. different machine). Must be used independently '
|
4092
|
+
'from the other options. No issue number should be specified, and the '
|
4093
|
+
'branch must have an issue number associated with it')
|
4094
|
+
group.add_option('--reapply', action='store_true', dest='reapply',
|
4095
|
+
help='Reset the branch and reapply the issue.\n'
|
4096
|
+
'CAUTION: This will undo any local changes in this '
|
4097
|
+
'branch')
|
4098
|
+
|
4099
|
+
group.add_option('--pull', action='store_true', dest='pull',
|
4100
|
+
help='Performs a pull before reapplying.')
|
4101
|
+
parser.add_option_group(group)
|
4102
|
+
|
2841
4103
|
auth.add_auth_options(parser)
|
4104
|
+
_add_codereview_select_options(parser)
|
2842
4105
|
(options, args) = parser.parse_args(args)
|
4106
|
+
_process_codereview_select_options(parser, options)
|
2843
4107
|
auth_config = auth.extract_auth_config_from_options(options)
|
2844
4108
|
|
2845
|
-
|
4109
|
+
cl = Changelist(auth_config=auth_config, codereview=options.forced_codereview)
|
4110
|
+
|
4111
|
+
issue_arg = None
|
4112
|
+
if options.reapply :
|
4113
|
+
if len(args) > 0:
|
4114
|
+
parser.error('--reapply implies no additional arguments.')
|
4115
|
+
|
4116
|
+
issue_arg = cl.GetIssue()
|
4117
|
+
upstream = cl.GetUpstreamBranch()
|
4118
|
+
if upstream == None:
|
4119
|
+
parser.error('No upstream branch specified. Cannot reset branch')
|
4120
|
+
|
4121
|
+
RunGit(['reset', '--hard', upstream])
|
4122
|
+
if options.pull:
|
4123
|
+
RunGit(['pull'])
|
4124
|
+
else:
|
4125
|
+
if len(args) != 1:
|
4126
|
+
parser.error('Must specify issue number or url')
|
4127
|
+
issue_arg = args[0]
|
4128
|
+
|
4129
|
+
if not issue_arg:
|
2846
4130
|
parser.print_help()
|
2847
4131
|
return 1
|
2848
|
-
|
4132
|
+
|
4133
|
+
if cl.IsGerrit():
|
4134
|
+
if options.reject:
|
4135
|
+
parser.error('--reject is not supported with Gerrit codereview.')
|
4136
|
+
if options.nocommit:
|
4137
|
+
parser.error('--nocommit is not supported with Gerrit codereview.')
|
4138
|
+
if options.directory:
|
4139
|
+
parser.error('--directory is not supported with Gerrit codereview.')
|
2849
4140
|
|
2850
4141
|
# We don't want uncommitted changes mixed up with the patch.
|
2851
4142
|
if git_common.is_dirty_git_tree('patch'):
|
2852
4143
|
return 1
|
2853
4144
|
|
2854
|
-
# TODO(maruel): Use apply_issue.py
|
2855
|
-
# TODO(ukai): use gerrit-cherry-pick for gerrit repository?
|
2856
|
-
|
2857
4145
|
if options.newbranch:
|
4146
|
+
if options.reapply:
|
4147
|
+
parser.error("--reapply excludes any option other than --pull")
|
2858
4148
|
if options.force:
|
2859
4149
|
RunGit(['branch', '-D', options.newbranch],
|
2860
4150
|
stderr=subprocess2.PIPE, error_ok=True)
|
2861
4151
|
RunGit(['checkout', '-b', options.newbranch,
|
2862
4152
|
Changelist().GetUpstreamBranch()])
|
2863
4153
|
|
2864
|
-
return
|
2865
|
-
|
2866
|
-
|
2867
|
-
|
2868
|
-
def PatchIssue(issue_arg, reject, nocommit, directory, auth_config):
|
2869
|
-
# PatchIssue should never be called with a dirty tree. It is up to the
|
2870
|
-
# caller to check this, but just in case we assert here since the
|
2871
|
-
# consequences of the caller not checking this could be dire.
|
2872
|
-
assert(not git_common.is_dirty_git_tree('apply'))
|
2873
|
-
|
2874
|
-
if type(issue_arg) is int or issue_arg.isdigit():
|
2875
|
-
# Input is an issue id. Figure out the URL.
|
2876
|
-
issue = int(issue_arg)
|
2877
|
-
cl = Changelist(issue=issue, auth_config=auth_config)
|
2878
|
-
patchset = cl.GetMostRecentPatchset()
|
2879
|
-
patch_data = cl.GetPatchSetDiff(issue, patchset)
|
2880
|
-
else:
|
2881
|
-
# Assume it's a URL to the patch. Default to https.
|
2882
|
-
issue_url = gclient_utils.UpgradeToHttps(issue_arg)
|
2883
|
-
match = re.match(r'(.*?)/download/issue(\d+)_(\d+).diff', issue_url)
|
2884
|
-
if not match:
|
2885
|
-
DieWithError('Must pass an issue ID or full URL for '
|
2886
|
-
'\'Download raw patch set\'')
|
2887
|
-
issue = int(match.group(2))
|
2888
|
-
cl = Changelist(issue=issue, auth_config=auth_config)
|
2889
|
-
cl.rietveld_server = match.group(1)
|
2890
|
-
patchset = int(match.group(3))
|
2891
|
-
patch_data = urllib2.urlopen(issue_arg).read()
|
2892
|
-
|
2893
|
-
# Switch up to the top-level directory, if necessary, in preparation for
|
2894
|
-
# applying the patch.
|
2895
|
-
top = settings.GetRelativeRoot()
|
2896
|
-
if top:
|
2897
|
-
os.chdir(top)
|
2898
|
-
|
2899
|
-
# Git patches have a/ at the beginning of source paths. We strip that out
|
2900
|
-
# with a sed script rather than the -p flag to patch so we can feed either
|
2901
|
-
# Git or svn-style patches into the same apply command.
|
2902
|
-
# re.sub() should be used but flags=re.MULTILINE is only in python 2.7.
|
2903
|
-
try:
|
2904
|
-
patch_data = subprocess2.check_output(
|
2905
|
-
['sed', '-e', 's|^--- a/|--- |; s|^+++ b/|+++ |'], stdin=patch_data)
|
2906
|
-
except subprocess2.CalledProcessError:
|
2907
|
-
DieWithError('Git patch mungling failed.')
|
2908
|
-
logging.info(patch_data)
|
2909
|
-
|
2910
|
-
# We use "git apply" to apply the patch instead of "patch" so that we can
|
2911
|
-
# pick up file adds.
|
2912
|
-
# The --index flag means: also insert into the index (so we catch adds).
|
2913
|
-
cmd = ['git', 'apply', '--index', '-p0']
|
2914
|
-
if directory:
|
2915
|
-
cmd.extend(('--directory', directory))
|
2916
|
-
if reject:
|
2917
|
-
cmd.append('--reject')
|
2918
|
-
elif IsGitVersionAtLeast('1.7.12'):
|
2919
|
-
cmd.append('--3way')
|
2920
|
-
try:
|
2921
|
-
subprocess2.check_call(cmd, env=GetNoGitPagerEnv(),
|
2922
|
-
stdin=patch_data, stdout=subprocess2.VOID)
|
2923
|
-
except subprocess2.CalledProcessError:
|
2924
|
-
print 'Failed to apply the patch'
|
2925
|
-
return 1
|
2926
|
-
|
2927
|
-
# If we had an issue, commit the current state and register the issue.
|
2928
|
-
if not nocommit:
|
2929
|
-
RunGit(['commit', '-m', (cl.GetDescription() + '\n\n' +
|
2930
|
-
'patch from issue %(i)s at patchset '
|
2931
|
-
'%(p)s (http://crrev.com/%(i)s#ps%(p)s)'
|
2932
|
-
% {'i': issue, 'p': patchset})])
|
2933
|
-
cl = Changelist(auth_config=auth_config)
|
2934
|
-
cl.SetIssue(issue)
|
2935
|
-
cl.SetPatchset(patchset)
|
2936
|
-
print "Committed patch locally."
|
2937
|
-
else:
|
2938
|
-
print "Patch applied to index."
|
2939
|
-
return 0
|
4154
|
+
return cl.CMDPatchIssue(issue_arg, options.reject, options.nocommit,
|
4155
|
+
options.directory)
|
2940
4156
|
|
2941
4157
|
|
2942
4158
|
def CMDrebase(parser, args):
|
@@ -3022,7 +4238,7 @@ def CMDtree(parser, args):
|
|
3022
4238
|
|
3023
4239
|
|
3024
4240
|
def CMDtry(parser, args):
|
3025
|
-
"""Triggers
|
4241
|
+
"""Triggers try jobs through BuildBucket."""
|
3026
4242
|
group = optparse.OptionGroup(parser, "Try job options")
|
3027
4243
|
group.add_option(
|
3028
4244
|
"-b", "--bot", action="append",
|
@@ -3034,6 +4250,7 @@ def CMDtry(parser, args):
|
|
3034
4250
|
group.add_option(
|
3035
4251
|
"-m", "--master", default='',
|
3036
4252
|
help=("Specify a try master where to run the tries."))
|
4253
|
+
group.add_option( "--luci", action='store_true')
|
3037
4254
|
group.add_option(
|
3038
4255
|
"-r", "--revision",
|
3039
4256
|
help="Revision to use for the try job; default: the "
|
@@ -3047,16 +4264,32 @@ def CMDtry(parser, args):
|
|
3047
4264
|
"--project",
|
3048
4265
|
help="Override which project to use. Projects are defined "
|
3049
4266
|
"server-side to define what default bot set to use")
|
4267
|
+
group.add_option(
|
4268
|
+
"-p", "--property", dest="properties", action="append", default=[],
|
4269
|
+
help="Specify generic properties in the form -p key1=value1 -p "
|
4270
|
+
"key2=value2 etc (buildbucket only). The value will be treated as "
|
4271
|
+
"json if decodable, or as string otherwise.")
|
3050
4272
|
group.add_option(
|
3051
4273
|
"-n", "--name", help="Try job name; default to current branch name")
|
3052
4274
|
group.add_option(
|
3053
|
-
"--use-
|
3054
|
-
help="Use
|
4275
|
+
"--use-rietveld", action="store_true", default=False,
|
4276
|
+
help="Use Rietveld to trigger try jobs.")
|
4277
|
+
group.add_option(
|
4278
|
+
"--buildbucket-host", default='cr-buildbucket.appspot.com',
|
4279
|
+
help="Host of buildbucket. The default host is %default.")
|
3055
4280
|
parser.add_option_group(group)
|
3056
4281
|
auth.add_auth_options(parser)
|
3057
4282
|
options, args = parser.parse_args(args)
|
3058
4283
|
auth_config = auth.extract_auth_config_from_options(options)
|
3059
4284
|
|
4285
|
+
if options.use_rietveld and options.properties:
|
4286
|
+
parser.error('Properties can only be specified with buildbucket')
|
4287
|
+
|
4288
|
+
# Make sure that all properties are prop=value pairs.
|
4289
|
+
bad_params = [x for x in options.properties if '=' not in x]
|
4290
|
+
if bad_params:
|
4291
|
+
parser.error('Got properties with missing "=": %s' % bad_params)
|
4292
|
+
|
3060
4293
|
if args:
|
3061
4294
|
parser.error('Unknown arguments: %s' % args)
|
3062
4295
|
|
@@ -3064,6 +4297,14 @@ def CMDtry(parser, args):
|
|
3064
4297
|
if not cl.GetIssue():
|
3065
4298
|
parser.error('Need to upload first')
|
3066
4299
|
|
4300
|
+
if cl.IsGerrit():
|
4301
|
+
parser.error(
|
4302
|
+
'Not yet supported for Gerrit (http://crbug.com/599931).\n'
|
4303
|
+
'If your project has Commit Queue, dry run is a workaround:\n'
|
4304
|
+
' git cl set-commit --dry-run')
|
4305
|
+
# Code below assumes Rietveld issue.
|
4306
|
+
# TODO(tandrii): actually implement for Gerrit http://crbug.com/599931.
|
4307
|
+
|
3067
4308
|
props = cl.GetIssueProperties()
|
3068
4309
|
if props.get('closed'):
|
3069
4310
|
parser.error('Cannot send tryjobs for a closed CL')
|
@@ -3107,6 +4348,24 @@ def CMDtry(parser, args):
|
|
3107
4348
|
None,
|
3108
4349
|
options.verbose,
|
3109
4350
|
sys.stdout)
|
4351
|
+
|
4352
|
+
if not options.bot:
|
4353
|
+
# Get try masters from cq.cfg if any.
|
4354
|
+
# TODO(tandrii): some (but very few) projects store cq.cfg in different
|
4355
|
+
# location.
|
4356
|
+
cq_cfg = os.path.join(change.RepositoryRoot(),
|
4357
|
+
'infra', 'config', 'cq.cfg')
|
4358
|
+
if os.path.exists(cq_cfg):
|
4359
|
+
masters = {}
|
4360
|
+
cq_masters = commit_queue.get_master_builder_map(
|
4361
|
+
cq_cfg, include_experimental=False, include_triggered=False)
|
4362
|
+
for master, builders in cq_masters.iteritems():
|
4363
|
+
for builder in builders:
|
4364
|
+
# Skip presubmit builders, because these will fail without LGTM.
|
4365
|
+
masters.setdefault(master, {})[builder] = ['defaulttests']
|
4366
|
+
if masters:
|
4367
|
+
return masters
|
4368
|
+
|
3110
4369
|
if not options.bot:
|
3111
4370
|
parser.error('No default try builder to try, use --bot')
|
3112
4371
|
|
@@ -3122,7 +4381,7 @@ def CMDtry(parser, args):
|
|
3122
4381
|
elif ',' in bot:
|
3123
4382
|
parser.error('Specify one bot per --bot flag')
|
3124
4383
|
else:
|
3125
|
-
builders_and_tests.setdefault(bot, [])
|
4384
|
+
builders_and_tests.setdefault(bot, [])
|
3126
4385
|
|
3127
4386
|
for bot, tests in new_style:
|
3128
4387
|
builders_and_tests.setdefault(bot, []).extend(tests)
|
@@ -3149,7 +4408,9 @@ def CMDtry(parser, args):
|
|
3149
4408
|
'\nWARNING Mismatch between local config and server. Did a previous '
|
3150
4409
|
'upload fail?\ngit-cl try always uses latest patchset from rietveld. '
|
3151
4410
|
'Continuing using\npatchset %s.\n' % patchset)
|
3152
|
-
if options.
|
4411
|
+
if options.luci:
|
4412
|
+
trigger_luci_job(cl, masters, options)
|
4413
|
+
elif not options.use_rietveld:
|
3153
4414
|
try:
|
3154
4415
|
trigger_try_jobs(auth_config, cl, options, masters, 'git_cl_try')
|
3155
4416
|
except BuildbucketResponseException as ex:
|
@@ -3181,6 +4442,50 @@ def CMDtry(parser, args):
|
|
3181
4442
|
return 0
|
3182
4443
|
|
3183
4444
|
|
4445
|
+
def CMDtry_results(parser, args):
|
4446
|
+
group = optparse.OptionGroup(parser, "Try job results options")
|
4447
|
+
group.add_option(
|
4448
|
+
"-p", "--patchset", type=int, help="patchset number if not current.")
|
4449
|
+
group.add_option(
|
4450
|
+
"--print-master", action='store_true', help="print master name as well.")
|
4451
|
+
group.add_option(
|
4452
|
+
"--color", action='store_true', default=setup_color.IS_TTY,
|
4453
|
+
help="force color output, useful when piping output.")
|
4454
|
+
group.add_option(
|
4455
|
+
"--buildbucket-host", default='cr-buildbucket.appspot.com',
|
4456
|
+
help="Host of buildbucket. The default host is %default.")
|
4457
|
+
parser.add_option_group(group)
|
4458
|
+
auth.add_auth_options(parser)
|
4459
|
+
options, args = parser.parse_args(args)
|
4460
|
+
if args:
|
4461
|
+
parser.error('Unrecognized args: %s' % ' '.join(args))
|
4462
|
+
|
4463
|
+
auth_config = auth.extract_auth_config_from_options(options)
|
4464
|
+
cl = Changelist(auth_config=auth_config)
|
4465
|
+
if not cl.GetIssue():
|
4466
|
+
parser.error('Need to upload first')
|
4467
|
+
|
4468
|
+
if not options.patchset:
|
4469
|
+
options.patchset = cl.GetMostRecentPatchset()
|
4470
|
+
if options.patchset and options.patchset != cl.GetPatchset():
|
4471
|
+
print(
|
4472
|
+
'\nWARNING Mismatch between local config and server. Did a previous '
|
4473
|
+
'upload fail?\ngit-cl try always uses latest patchset from rietveld. '
|
4474
|
+
'Continuing using\npatchset %s.\n' % options.patchset)
|
4475
|
+
try:
|
4476
|
+
jobs = fetch_try_jobs(auth_config, cl, options)
|
4477
|
+
except BuildbucketResponseException as ex:
|
4478
|
+
print 'Buildbucket error: %s' % ex
|
4479
|
+
return 1
|
4480
|
+
except Exception as e:
|
4481
|
+
stacktrace = (''.join(traceback.format_stack()) + traceback.format_exc())
|
4482
|
+
print 'ERROR: Exception when trying to fetch tryjobs: %s\n%s' % (
|
4483
|
+
e, stacktrace)
|
4484
|
+
return 1
|
4485
|
+
print_tryjobs(options, jobs)
|
4486
|
+
return 0
|
4487
|
+
|
4488
|
+
|
3184
4489
|
@subcommand.usage('[new upstream branch]')
|
3185
4490
|
def CMDupstream(parser, args):
|
3186
4491
|
"""Prints or sets the name of the upstream branch, if any."""
|
@@ -3220,16 +4525,28 @@ def CMDweb(parser, args):
|
|
3220
4525
|
|
3221
4526
|
def CMDset_commit(parser, args):
|
3222
4527
|
"""Sets the commit bit to trigger the Commit Queue."""
|
4528
|
+
parser.add_option('-d', '--dry-run', action='store_true',
|
4529
|
+
help='trigger in dry run mode')
|
4530
|
+
parser.add_option('-c', '--clear', action='store_true',
|
4531
|
+
help='stop CQ run, if any')
|
3223
4532
|
auth.add_auth_options(parser)
|
3224
4533
|
options, args = parser.parse_args(args)
|
3225
4534
|
auth_config = auth.extract_auth_config_from_options(options)
|
3226
4535
|
if args:
|
3227
4536
|
parser.error('Unrecognized args: %s' % ' '.join(args))
|
4537
|
+
if options.dry_run and options.clear:
|
4538
|
+
parser.error('Make up your mind: both --dry-run and --clear not allowed')
|
4539
|
+
|
3228
4540
|
cl = Changelist(auth_config=auth_config)
|
3229
|
-
|
3230
|
-
|
3231
|
-
|
3232
|
-
|
4541
|
+
if options.clear:
|
4542
|
+
state = _CQState.CLEAR
|
4543
|
+
elif options.dry_run:
|
4544
|
+
state = _CQState.DRY_RUN
|
4545
|
+
else:
|
4546
|
+
state = _CQState.COMMIT
|
4547
|
+
if not cl.GetIssue():
|
4548
|
+
parser.error('Must upload the issue first')
|
4549
|
+
cl.SetCQState(state)
|
3233
4550
|
return 0
|
3234
4551
|
|
3235
4552
|
|
@@ -3256,7 +4573,7 @@ def CMDdiff(parser, args):
|
|
3256
4573
|
parser.error('Unrecognized args: %s' % ' '.join(args))
|
3257
4574
|
|
3258
4575
|
# Uncommitted (staged and unstaged) changes will be destroyed by
|
3259
|
-
# "git reset --hard" if there are merging conflicts in
|
4576
|
+
# "git reset --hard" if there are merging conflicts in CMDPatchIssue().
|
3260
4577
|
# Staged changes would be committed along with the patch from last
|
3261
4578
|
# upload, hence counted toward the "last upload" side in the final
|
3262
4579
|
# diff output, and this is not what we want.
|
@@ -3273,9 +4590,11 @@ def CMDdiff(parser, args):
|
|
3273
4590
|
|
3274
4591
|
# Create a new branch based on the merge-base
|
3275
4592
|
RunGit(['checkout', '-q', '-b', TMP_BRANCH, base_branch])
|
4593
|
+
# Clear cached branch in cl object, to avoid overwriting original CL branch
|
4594
|
+
# properties.
|
4595
|
+
cl.ClearBranch()
|
3276
4596
|
try:
|
3277
|
-
|
3278
|
-
rtn = PatchIssue(issue, False, False, None, auth_config)
|
4597
|
+
rtn = cl.CMDPatchIssue(issue, reject=False, nocommit=False, directory=None)
|
3279
4598
|
if rtn != 0:
|
3280
4599
|
RunGit(['reset', '--hard'])
|
3281
4600
|
return rtn
|
@@ -3321,7 +4640,7 @@ def CMDowners(parser, args):
|
|
3321
4640
|
disable_color=options.no_color).run()
|
3322
4641
|
|
3323
4642
|
|
3324
|
-
def BuildGitDiffCmd(diff_type, upstream_commit, args
|
4643
|
+
def BuildGitDiffCmd(diff_type, upstream_commit, args):
|
3325
4644
|
"""Generates a diff command."""
|
3326
4645
|
# Generate diff for the current branch's changes.
|
3327
4646
|
diff_cmd = ['diff', '--no-ext-diff', '--no-prefix', diff_type,
|
@@ -3329,22 +4648,22 @@ def BuildGitDiffCmd(diff_type, upstream_commit, args, extensions):
|
|
3329
4648
|
|
3330
4649
|
if args:
|
3331
4650
|
for arg in args:
|
3332
|
-
if os.path.isdir(arg):
|
3333
|
-
diff_cmd.extend(os.path.join(arg, '*' + ext) for ext in extensions)
|
3334
|
-
elif os.path.isfile(arg):
|
4651
|
+
if os.path.isdir(arg) or os.path.isfile(arg):
|
3335
4652
|
diff_cmd.append(arg)
|
3336
4653
|
else:
|
3337
4654
|
DieWithError('Argument "%s" is not a file or a directory' % arg)
|
3338
|
-
else:
|
3339
|
-
diff_cmd.extend('*' + ext for ext in extensions)
|
3340
4655
|
|
3341
4656
|
return diff_cmd
|
3342
4657
|
|
4658
|
+
def MatchingFileType(file_name, extensions):
|
4659
|
+
"""Returns true if the file name ends with one of the given extensions."""
|
4660
|
+
return bool([ext for ext in extensions if file_name.lower().endswith(ext)])
|
3343
4661
|
|
3344
4662
|
@subcommand.usage('[files or directories to diff]')
|
3345
4663
|
def CMDformat(parser, args):
|
3346
4664
|
"""Runs auto-formatting tools (clang-format etc.) on the diff."""
|
3347
4665
|
CLANG_EXTS = ['.cc', '.cpp', '.h', '.mm', '.proto', '.java']
|
4666
|
+
GN_EXTS = ['.gn', '.gni']
|
3348
4667
|
parser.add_option('--full', action='store_true',
|
3349
4668
|
help='Reformat the full content of all touched files')
|
3350
4669
|
parser.add_option('--dry-run', action='store_true',
|
@@ -3376,75 +4695,73 @@ def CMDformat(parser, args):
|
|
3376
4695
|
DieWithError('Could not find base commit for this branch. '
|
3377
4696
|
'Are you in detached state?')
|
3378
4697
|
|
3379
|
-
|
3380
|
-
|
3381
|
-
|
3382
|
-
|
3383
|
-
|
3384
|
-
diff_type = '-U0'
|
4698
|
+
changed_files_cmd = BuildGitDiffCmd('--name-only', upstream_commit, args)
|
4699
|
+
diff_output = RunGit(changed_files_cmd)
|
4700
|
+
diff_files = diff_output.splitlines()
|
4701
|
+
# Filter out files deleted by this CL
|
4702
|
+
diff_files = [x for x in diff_files if os.path.isfile(x)]
|
3385
4703
|
|
3386
|
-
|
3387
|
-
|
4704
|
+
clang_diff_files = [x for x in diff_files if MatchingFileType(x, CLANG_EXTS)]
|
4705
|
+
python_diff_files = [x for x in diff_files if MatchingFileType(x, ['.py'])]
|
4706
|
+
dart_diff_files = [x for x in diff_files if MatchingFileType(x, ['.dart'])]
|
4707
|
+
gn_diff_files = [x for x in diff_files if MatchingFileType(x, GN_EXTS)]
|
3388
4708
|
|
3389
4709
|
top_dir = os.path.normpath(
|
3390
4710
|
RunGit(["rev-parse", "--show-toplevel"]).rstrip('\n'))
|
3391
4711
|
|
3392
|
-
# Locate the clang-format binary in the checkout
|
3393
|
-
try:
|
3394
|
-
clang_format_tool = clang_format.FindClangFormatToolInChromiumTree()
|
3395
|
-
except clang_format.NotFoundError, e:
|
3396
|
-
DieWithError(e)
|
3397
|
-
|
3398
4712
|
# Set to 2 to signal to CheckPatchFormatted() that this patch isn't
|
3399
4713
|
# formatted. This is used to block during the presubmit.
|
3400
4714
|
return_value = 0
|
3401
4715
|
|
3402
|
-
if
|
3403
|
-
#
|
3404
|
-
|
3405
|
-
|
4716
|
+
if clang_diff_files:
|
4717
|
+
# Locate the clang-format binary in the checkout
|
4718
|
+
try:
|
4719
|
+
clang_format_tool = clang_format.FindClangFormatToolInChromiumTree()
|
4720
|
+
except clang_format.NotFoundError, e:
|
4721
|
+
DieWithError(e)
|
4722
|
+
|
4723
|
+
if opts.full:
|
3406
4724
|
cmd = [clang_format_tool]
|
3407
4725
|
if not opts.dry_run and not opts.diff:
|
3408
4726
|
cmd.append('-i')
|
3409
|
-
stdout = RunCommand(cmd +
|
4727
|
+
stdout = RunCommand(cmd + clang_diff_files, cwd=top_dir)
|
3410
4728
|
if opts.diff:
|
3411
4729
|
sys.stdout.write(stdout)
|
3412
|
-
|
3413
|
-
|
3414
|
-
|
3415
|
-
|
3416
|
-
|
3417
|
-
|
3418
|
-
|
3419
|
-
|
3420
|
-
DieWithError(e)
|
4730
|
+
else:
|
4731
|
+
env = os.environ.copy()
|
4732
|
+
env['PATH'] = str(os.path.dirname(clang_format_tool))
|
4733
|
+
try:
|
4734
|
+
script = clang_format.FindClangFormatScriptInChromiumTree(
|
4735
|
+
'clang-format-diff.py')
|
4736
|
+
except clang_format.NotFoundError, e:
|
4737
|
+
DieWithError(e)
|
3421
4738
|
|
3422
|
-
|
3423
|
-
|
3424
|
-
|
4739
|
+
cmd = [sys.executable, script, '-p0']
|
4740
|
+
if not opts.dry_run and not opts.diff:
|
4741
|
+
cmd.append('-i')
|
3425
4742
|
|
3426
|
-
|
3427
|
-
|
3428
|
-
|
3429
|
-
|
3430
|
-
|
4743
|
+
diff_cmd = BuildGitDiffCmd('-U0', upstream_commit, clang_diff_files)
|
4744
|
+
diff_output = RunGit(diff_cmd)
|
4745
|
+
|
4746
|
+
stdout = RunCommand(cmd, stdin=diff_output, cwd=top_dir, env=env)
|
4747
|
+
if opts.diff:
|
4748
|
+
sys.stdout.write(stdout)
|
4749
|
+
if opts.dry_run and len(stdout) > 0:
|
4750
|
+
return_value = 2
|
3431
4751
|
|
3432
4752
|
# Similar code to above, but using yapf on .py files rather than clang-format
|
3433
4753
|
# on C/C++ files
|
3434
4754
|
if opts.python:
|
3435
|
-
diff_cmd = BuildGitDiffCmd(diff_type, upstream_commit, args, ['.py'])
|
3436
|
-
diff_output = RunGit(diff_cmd)
|
3437
4755
|
yapf_tool = gclient_utils.FindExecutable('yapf')
|
3438
4756
|
if yapf_tool is None:
|
3439
4757
|
DieWithError('yapf not found in PATH')
|
3440
4758
|
|
3441
4759
|
if opts.full:
|
3442
|
-
|
3443
|
-
if files:
|
4760
|
+
if python_diff_files:
|
3444
4761
|
cmd = [yapf_tool]
|
3445
4762
|
if not opts.dry_run and not opts.diff:
|
3446
4763
|
cmd.append('-i')
|
3447
|
-
stdout = RunCommand(cmd +
|
4764
|
+
stdout = RunCommand(cmd + python_diff_files, cwd=top_dir)
|
3448
4765
|
if opts.diff:
|
3449
4766
|
sys.stdout.write(stdout)
|
3450
4767
|
else:
|
@@ -3452,29 +4769,83 @@ def CMDformat(parser, args):
|
|
3452
4769
|
# https://github.com/google/yapf/issues/154
|
3453
4770
|
DieWithError('--python currently only works with --full')
|
3454
4771
|
|
3455
|
-
#
|
3456
|
-
#
|
3457
|
-
|
3458
|
-
dart_diff_cmd = BuildGitDiffCmd('--name-only', upstream_commit,
|
3459
|
-
args, ['.dart'])
|
3460
|
-
dart_diff_output = RunGit(dart_diff_cmd)
|
3461
|
-
if dart_diff_output:
|
4772
|
+
# Dart's formatter does not have the nice property of only operating on
|
4773
|
+
# modified chunks, so hard code full.
|
4774
|
+
if dart_diff_files:
|
3462
4775
|
try:
|
3463
4776
|
command = [dart_format.FindDartFmtToolInChromiumTree()]
|
3464
4777
|
if not opts.dry_run and not opts.diff:
|
3465
4778
|
command.append('-w')
|
3466
|
-
command.extend(
|
4779
|
+
command.extend(dart_diff_files)
|
3467
4780
|
|
3468
|
-
stdout = RunCommand(command, cwd=top_dir
|
4781
|
+
stdout = RunCommand(command, cwd=top_dir)
|
3469
4782
|
if opts.dry_run and stdout:
|
3470
4783
|
return_value = 2
|
3471
4784
|
except dart_format.NotFoundError as e:
|
3472
|
-
print ('Unable to check
|
3473
|
-
'this checkout.'
|
4785
|
+
print ('Warning: Unable to check Dart code formatting. Dart SDK not ' +
|
4786
|
+
'found in this checkout. Files in other languages are still ' +
|
4787
|
+
'formatted.')
|
4788
|
+
|
4789
|
+
# Format GN build files. Always run on full build files for canonical form.
|
4790
|
+
if gn_diff_files:
|
4791
|
+
cmd = ['gn', 'format']
|
4792
|
+
if not opts.dry_run and not opts.diff:
|
4793
|
+
cmd.append('--in-place')
|
4794
|
+
for gn_diff_file in gn_diff_files:
|
4795
|
+
stdout = RunCommand(cmd + [gn_diff_file],
|
4796
|
+
shell=sys.platform == 'win32',
|
4797
|
+
cwd=top_dir)
|
4798
|
+
if opts.diff:
|
4799
|
+
sys.stdout.write(stdout)
|
3474
4800
|
|
3475
4801
|
return return_value
|
3476
4802
|
|
3477
4803
|
|
4804
|
+
@subcommand.usage('<codereview url or issue id>')
|
4805
|
+
def CMDcheckout(parser, args):
|
4806
|
+
"""Checks out a branch associated with a given Rietveld or Gerrit issue."""
|
4807
|
+
_, args = parser.parse_args(args)
|
4808
|
+
|
4809
|
+
if len(args) != 1:
|
4810
|
+
parser.print_help()
|
4811
|
+
return 1
|
4812
|
+
|
4813
|
+
issue_arg = ParseIssueNumberArgument(args[0])
|
4814
|
+
if not issue_arg.valid:
|
4815
|
+
parser.print_help()
|
4816
|
+
return 1
|
4817
|
+
target_issue = str(issue_arg.issue)
|
4818
|
+
|
4819
|
+
def find_issues(issueprefix):
|
4820
|
+
output = RunGit(['config', '--local', '--get-regexp',
|
4821
|
+
r'branch\..*\.%s' % issueprefix],
|
4822
|
+
error_ok=True)
|
4823
|
+
for key, issue in [x.split() for x in output.splitlines()]:
|
4824
|
+
if issue == target_issue:
|
4825
|
+
yield re.sub(r'branch\.(.*)\.%s' % issueprefix, r'\1', key)
|
4826
|
+
|
4827
|
+
branches = []
|
4828
|
+
for cls in _CODEREVIEW_IMPLEMENTATIONS.values():
|
4829
|
+
branches.extend(find_issues(cls.IssueSettingSuffix()))
|
4830
|
+
if len(branches) == 0:
|
4831
|
+
print 'No branch found for issue %s.' % target_issue
|
4832
|
+
return 1
|
4833
|
+
if len(branches) == 1:
|
4834
|
+
RunGit(['checkout', branches[0]])
|
4835
|
+
else:
|
4836
|
+
print 'Multiple branches match issue %s:' % target_issue
|
4837
|
+
for i in range(len(branches)):
|
4838
|
+
print '%d: %s' % (i, branches[i])
|
4839
|
+
which = raw_input('Choose by index: ')
|
4840
|
+
try:
|
4841
|
+
RunGit(['checkout', branches[int(which)]])
|
4842
|
+
except (IndexError, ValueError):
|
4843
|
+
print 'Invalid selection, not checking out any branch.'
|
4844
|
+
return 1
|
4845
|
+
|
4846
|
+
return 0
|
4847
|
+
|
4848
|
+
|
3478
4849
|
def CMDlol(parser, args):
|
3479
4850
|
# This command is intentionally undocumented.
|
3480
4851
|
print zlib.decompress(base64.b64decode(
|
@@ -3531,7 +4902,7 @@ if __name__ == '__main__':
|
|
3531
4902
|
# These affect sys.stdout so do it outside of main() to simplify mocks in
|
3532
4903
|
# unit testing.
|
3533
4904
|
fix_encoding.fix_encoding()
|
3534
|
-
|
4905
|
+
setup_color.init()
|
3535
4906
|
try:
|
3536
4907
|
sys.exit(main(sys.argv[1:]))
|
3537
4908
|
except KeyboardInterrupt:
|