diff options
Diffstat (limited to 'webapp/git_upload.py')
-rwxr-xr-x | webapp/git_upload.py | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/webapp/git_upload.py b/webapp/git_upload.py new file mode 100755 index 0000000000..a6fa76879d --- /dev/null +++ b/webapp/git_upload.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python2.5 +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import getpass +import logging +import optparse +import os +import subprocess +import sys +from tempfile import mkstemp + +from codereview.proto_client import HttpRpc, Proxy +from codereview.review_pb2 import ReviewService_Stub +from codereview.upload_bundle_pb2 import * + +try: + import readline +except ImportError: + pass + +MAX_SEGMENT_SIZE = 1020 * 1024 + +# The logging verbosity: +# 0: Errors only. +# 1: Status messages. +# 2: Info logs. +# 3: Debug logs. +verbosity = 1 + +def StatusUpdate(msg): + """Print a status message to stdout. + + If 'verbosity' is greater than 0, print the message. + + Args: + msg: The string to print. + """ + if verbosity > 0: + print msg + + +def ErrorExit(msg): + """Print an error message to stderr and exit.""" + print >>sys.stderr, msg + sys.exit(1) + + +def RunShell(command, args=(), silent_ok=False): + command = "%s %s" % (command, " ".join(args)) + logging.info("Running %s", command) + stream = os.popen(command, "r") + data = stream.read() + if stream.close(): + ErrorExit("Got error status from %s" % command) + if not silent_ok and not data: + ErrorExit("No output from %s" % command) + return data + + +def RunGit(*args): + argv = ["git"] + argv += args + retcode = subprocess.call(argv) + if retcode != 0: + raise OSError, retcode + +def GitVal(*args): + data = RunShell("git", args) + if data.rfind("\n") == len(data) - 1: + return data[0 : len(data) - 1] + return data + + +parser = optparse.OptionParser(usage="%prog [options] [-- diff_options]") + +# Logging +group = parser.add_option_group("Logging options") +group.add_option("-q", "--quiet", action="store_const", const=0, + dest="verbose", help="Print errors only.") +group.add_option("-v", "--verbose", action="store_const", const=2, + dest="verbose", default=1, + help="Print info level logs (default).") +group.add_option("--noisy", action="store_const", const=3, + dest="verbose", help="Print all logs.") + +# Review server +group = parser.add_option_group("Review server options") +group.add_option("-s", "--server", action="store", dest="server", + default="codereview.appspot.com", + metavar="SERVER", + help=("The server to upload to. The format is host[:port]. " + "Defaults to 'codereview.appspot.com'.")) +group.add_option("-e", "--email", action="store", dest="email", + metavar="EMAIL", default=None, + help="The username to use. Will prompt if omitted.") +group.add_option("-H", "--host", action="store", dest="host", + metavar="HOST", default=None, + help="Overrides the Host header sent with all RPCs.") +group.add_option("--no_cookies", action="store_false", + dest="save_cookies", default=True, + help="Do not save authentication cookies to local disk.") + +# Git +group = parser.add_option_group("Git options") +group.add_option("-p", "--project", action="store", dest="dest_project", + metavar="PROJECT", + help=("Name of the Git repository to submit into.")) +group.add_option("-b", "--branch", action="store", dest="dest_branch", + metavar="BRANCH", + help=("Name of the branch the changes are proposed for.")) +group.add_option("-B", "--base", action="store", dest="base_commit", + default="refs/remotes/origin/master", + metavar="COMMIT", + help=("Base commit for the bundle.")) + +def GetRpcServer(options): + """Returns an RpcServer. + + Returns: + A new RpcServer, on which RPC calls can be made. + """ + + def GetUserCredentials(): + """Prompts the user for a username and password.""" + email = options.email + if email is None: + email = raw_input("Email: ").strip() + password = getpass.getpass("Password for %s: " % email) + return (email, password) + + # If this is the dev_appserver, use fake authentication. + host = (options.host or options.server).lower() + if host == "localhost" or host.startswith("localhost:"): + email = options.email + if email is None: + email = "test@example.com" + logging.info("Using debug user %s. Override with --email" % email) + + server = HttpRpc( + options.server, + lambda: (email, "password"), + host_override=options.host, + extra_headers={"Cookie": + 'dev_appserver_login="%s:False"' % email}) + # Don't try to talk to ClientLogin. + server.authenticated = True + return server + + if options.save_cookies: + cookie_file = ".gerrit_cookies" + else: + cookie_file = None + + return HttpRpc(options.server, GetUserCredentials, + host_override=options.host, + cookie_file=cookie_file) + + +def RealMain(argv, data=None): + logging.basicConfig(format=("%(asctime).19s %(levelname)s %(filename)s:" + "%(lineno)s %(message)s ")) + os.environ['LC_ALL'] = 'C' + options, args = parser.parse_args(argv[1:]) + + global verbosity + verbosity = options.verbose + if verbosity >= 3: + logging.getLogger().setLevel(logging.DEBUG) + elif verbosity >= 2: + logging.getLogger().setLevel(logging.INFO) + + srv = GetRpcServer(options) + review = Proxy(ReviewService_Stub(srv)) + + git_dir = GitVal("rev-parse","--git-dir") + + revlist = GitVal("rev-list", + "^" + options.base_commit, + "HEAD").split("\n") + + tmp_fd, tmp_bundle = mkstemp(".bundle", ".gpq", git_dir) + os.close(tmp_fd) + + try: + RunGit("bundle", "create", + tmp_bundle, + "^" + options.base_commit, + "HEAD") + fd = open(tmp_bundle, "rb") + + bundle_id = None + segment_id = 0 + next_data = fd.read(MAX_SEGMENT_SIZE) + + while len(next_data) > 0: + this_data = next_data + next_data = fd.read(MAX_SEGMENT_SIZE) + segment_id += 1 + + if bundle_id is None: + req = UploadBundleRequest() + req.dest_project = options.dest_project + req.dest_branch = options.dest_branch + for c in revlist: + req.contained_object.append(c) + else: + req = UploadBundleContinue() + req.bundle_id = bundle_id + req.segment_id = segment_id + + req.bundle_data = this_data + if len(next_data) > 0: + req.partial_upload = True + else: + req.partial_upload = False + + if bundle_id is None: + rsp = review.UploadBundle(req) + else: + rsp = review.ContinueBundle(req) + + if rsp.status_code == UploadBundleResponse.CONTINUE: + bundle_id = rsp.bundle_id + else: + print rsp + break + finally: + os.unlink(tmp_bundle) + +def main(): + try: + RealMain(sys.argv) + except KeyboardInterrupt: + print + StatusUpdate("Interrupted.") + sys.exit(1) + + +if __name__ == "__main__": + main() |