254 lines
7.2 KiB
Python
Executable File
254 lines
7.2 KiB
Python
Executable File
#!/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()
|