Index: git_cl/upload.py |
diff --git a/git_cl/upload.py b/git_cl/upload.py |
index ff7d3f33e750518f3bcf0dd2ae7a5ef67401d3a8..ee2b4c2b4c4432bf84bee80226abc32bc27c2305 100644 |
--- a/git_cl/upload.py |
+++ b/git_cl/upload.py |
@@ -24,6 +24,8 @@ Supported version control systems: |
Git |
Mercurial |
Subversion |
+ Perforce |
+ CVS |
It is important for Git/Mercurial users to specify a tree/node/branch to diff |
against by using the '--rev' option. |
@@ -36,6 +38,7 @@ import cookielib |
import fnmatch |
import getpass |
import logging |
+import marshal |
import mimetypes |
import optparse |
import os |
@@ -86,6 +89,8 @@ MAX_UPLOAD_SIZE = 900 * 1024 |
VCS_GIT = "Git" |
VCS_MERCURIAL = "Mercurial" |
VCS_SUBVERSION = "Subversion" |
+VCS_PERFORCE = "Perforce" |
+VCS_CVS = "CVS" |
VCS_UNKNOWN = "Unknown" |
# whitelist for non-binary filetypes which do not start with "text/" |
@@ -99,7 +104,10 @@ VCS_ABBREVIATIONS = { |
"hg": VCS_MERCURIAL, |
VCS_SUBVERSION.lower(): VCS_SUBVERSION, |
"svn": VCS_SUBVERSION, |
+ VCS_PERFORCE.lower(): VCS_PERFORCE, |
+ "p4": VCS_PERFORCE, |
VCS_GIT.lower(): VCS_GIT, |
+ VCS_CVS.lower(): VCS_CVS, |
} |
# The result of parsing Subversion's [auto-props] setting. |
@@ -188,8 +196,6 @@ class AbstractRpcServer(object): |
if (not self.host.startswith("http://") and |
not self.host.startswith("https://")): |
self.host = "http://" + self.host |
- assert re.match(r'^[a-z]+://[a-z0-9\.-_]+(|:[0-9]+)$', self.host), ( |
- '%s is malformed' % host) |
self.host_override = host_override |
self.auth_function = auth_function |
self.authenticated = False |
@@ -220,11 +226,10 @@ class AbstractRpcServer(object): |
req.add_header(key, value) |
return req |
- def _GetAuthToken(self, host, email, password): |
+ def _GetAuthToken(self, email, password): |
"""Uses ClientLogin to authenticate the user, returning an auth token. |
Args: |
- host: Host to get a token against. |
email: The user's email address |
password: The user's password |
@@ -236,7 +241,7 @@ class AbstractRpcServer(object): |
The authentication token returned by ClientLogin. |
""" |
account_type = self.account_type |
- if host.endswith(".google.com"): |
+ if self.host.endswith(".google.com"): |
# Needed for use inside Google. |
account_type = "HOSTED" |
req = self._CreateRequest( |
@@ -264,12 +269,10 @@ class AbstractRpcServer(object): |
else: |
raise |
- def _GetAuthCookie(self, host, auth_token): |
+ def _GetAuthCookie(self, auth_token): |
"""Fetches authentication cookies for an authentication token. |
Args: |
- host: The host to get a cookie against. Because of 301, it may be a |
- different host than self.host. |
auth_token: The authentication token returned by ClientLogin. |
Raises: |
@@ -278,33 +281,21 @@ class AbstractRpcServer(object): |
# This is a dummy value to allow us to identify when we're successful. |
continue_location = "http://localhost/" |
args = {"continue": continue_location, "auth": auth_token} |
- tries = 0 |
- url = "%s/_ah/login?%s" % (host, urllib.urlencode(args)) |
- while tries < 3: |
- tries += 1 |
- req = self._CreateRequest(url) |
- try: |
- response = self.opener.open(req) |
- except urllib2.HTTPError, e: |
- response = e |
- if e.code == 301: |
- # Handle permanent redirect manually. |
- url = e.info()["location"] |
- continue |
- break |
+ req = self._CreateRequest("%s/_ah/login?%s" % |
+ (self.host, urllib.urlencode(args))) |
+ try: |
+ response = self.opener.open(req) |
+ except urllib2.HTTPError, e: |
+ response = e |
if (response.code != 302 or |
response.info()["location"] != continue_location): |
raise urllib2.HTTPError(req.get_full_url(), response.code, response.msg, |
response.headers, response.fp) |
self.authenticated = True |
- def _Authenticate(self, host): |
+ def _Authenticate(self): |
"""Authenticates the user. |
- Args: |
- host: The host to get a cookie against. Because of 301, it may be a |
- different host than self.host. |
- |
The authentication process works as follows: |
1) We get a username and password from the user |
2) We use ClientLogin to obtain an AUTH token for the user |
@@ -320,7 +311,7 @@ class AbstractRpcServer(object): |
for i in range(3): |
credentials = self.auth_function() |
try: |
- auth_token = self._GetAuthToken(host, credentials[0], credentials[1]) |
+ auth_token = self._GetAuthToken(credentials[0], credentials[1]) |
except ClientLoginError, e: |
if e.reason == "BadAuthentication": |
print >>sys.stderr, "Invalid username or password." |
@@ -353,7 +344,7 @@ class AbstractRpcServer(object): |
print >>sys.stderr, "The service is not available; try again later." |
break |
raise |
- self._GetAuthCookie(host, auth_token) |
+ self._GetAuthCookie(auth_token) |
return |
def Send(self, request_path, payload=None, |
@@ -380,18 +371,18 @@ class AbstractRpcServer(object): |
# TODO: Don't require authentication. Let the server say |
# whether it is necessary. |
if not self.authenticated: |
- self._Authenticate(self.host) |
+ self._Authenticate() |
old_timeout = socket.getdefaulttimeout() |
socket.setdefaulttimeout(timeout) |
try: |
tries = 0 |
- args = dict(kwargs) |
- url = "%s%s" % (self.host, request_path) |
- if args: |
- url += "?" + urllib.urlencode(args) |
while True: |
tries += 1 |
+ args = dict(kwargs) |
+ url = "%s%s" % (self.host, request_path) |
+ if args: |
+ url += "?" + urllib.urlencode(args) |
req = self._CreateRequest(url=url, data=payload) |
req.add_header("Content-Type", content_type) |
if extra_headers: |
@@ -406,24 +397,17 @@ class AbstractRpcServer(object): |
if tries > 3: |
raise |
elif e.code == 401 or e.code == 302: |
- url_loc = urlparse.urlparse(url) |
- self._Authenticate('%s://%s' % (url_loc[0], url_loc[1])) |
+ self._Authenticate() |
## elif e.code >= 500 and e.code < 600: |
## # Server Error - try again. |
## continue |
elif e.code == 301: |
# Handle permanent redirect manually. |
url = e.info()["location"] |
+ url_loc = urlparse.urlparse(url) |
+ self.host = '%s://%s' % (url_loc[0], url_loc[1]) |
else: |
raise |
- except urllib2.URLError, e: |
- reason = getattr(e, 'reason', None) |
- if isinstance(reason, str) and reason.find("110") != -1: |
- # Connection timeout error. |
- if tries <= 3: |
- # Try again. |
- continue |
- raise |
finally: |
socket.setdefaulttimeout(old_timeout) |
@@ -431,9 +415,9 @@ class AbstractRpcServer(object): |
class HttpRpcServer(AbstractRpcServer): |
"""Provides a simplified RPC-style interface for HTTP requests.""" |
- def _Authenticate(self, *args): |
+ def _Authenticate(self): |
"""Save the cookie jar after authentication.""" |
- super(HttpRpcServer, self)._Authenticate(*args) |
+ super(HttpRpcServer, self)._Authenticate() |
if self.save_cookies: |
StatusUpdate("Saving authentication cookies to %s" % self.cookie_file) |
self.cookie_jar.save() |
@@ -490,6 +474,8 @@ group.add_option("-v", "--verbose", action="store_const", const=2, |
help="Print info level logs.") |
group.add_option("--noisy", action="store_const", const=3, |
dest="verbose", help="Print all logs.") |
+group.add_option("--print_diffs", dest="print_diffs", action="store_true", |
+ help="Print full diffs.") |
# Review server |
group = parser.add_option_group("Review server options") |
group.add_option("-s", "--server", action="store", dest="server", |
@@ -562,7 +548,21 @@ group.add_option("--vcs", action="store", dest="vcs", |
group.add_option("--emulate_svn_auto_props", action="store_true", |
dest="emulate_svn_auto_props", default=False, |
help=("Emulate Subversion's auto properties feature.")) |
- |
+# Perforce-specific |
+group = parser.add_option_group("Perforce-specific options " |
+ "(overrides P4 environment variables)") |
+group.add_option("--p4_port", action="store", dest="p4_port", |
+ metavar="P4_PORT", default=None, |
+ help=("Perforce server and port (optional)")) |
+group.add_option("--p4_changelist", action="store", dest="p4_changelist", |
+ metavar="P4_CHANGELIST", default=None, |
+ help=("Perforce changelist id")) |
+group.add_option("--p4_client", action="store", dest="p4_client", |
+ metavar="P4_CLIENT", default=None, |
+ help=("Perforce client/workspace")) |
+group.add_option("--p4_user", action="store", dest="p4_user", |
+ metavar="P4_USER", default=None, |
+ help=("Perforce user")) |
def GetRpcServer(server, email=None, host_override=None, save_cookies=True, |
account_type=AUTH_ACCOUNT_TYPE): |
@@ -908,9 +908,6 @@ class SubversionVCS(VersionControlSystem): |
if line.startswith("URL: "): |
url = line.split()[1] |
scheme, netloc, path, params, query, fragment = urlparse.urlparse(url) |
- username, netloc = urllib.splituser(netloc) |
- if username: |
- logging.info("Removed username from base URL") |
guess = "" |
if netloc == "svn.python.org" and scheme == "svn+ssh": |
path = "projects" + path |
@@ -1070,8 +1067,12 @@ class SubversionVCS(VersionControlSystem): |
# File does not exist in the requested revision. |
# Reset mimetype, it contains an error message. |
mimetype = "" |
+ else: |
+ mimetype = mimetype.strip() |
get_base = False |
- is_binary = bool(mimetype) and not mimetype.startswith("text/") |
+ is_binary = (bool(mimetype) and |
+ not mimetype.startswith("text/") and |
+ not mimetype in TEXT_MIMETYPES) |
if status[0] == " ": |
# Empty base content just to force an upload. |
base_content = "" |
@@ -1265,6 +1266,71 @@ class GitVCS(VersionControlSystem): |
return (base_content, new_content, is_binary, status) |
+class CVSVCS(VersionControlSystem): |
+ """Implementation of the VersionControlSystem interface for CVS.""" |
+ |
+ def __init__(self, options): |
+ super(CVSVCS, self).__init__(options) |
+ |
+ def GetOriginalContent_(self, filename): |
+ RunShell(["cvs", "up", filename], silent_ok=True) |
+ # TODO need detect file content encoding |
+ content = open(filename).read() |
+ return content.replace("\r\n", "\n") |
+ |
+ def GetBaseFile(self, filename): |
+ base_content = None |
+ new_content = None |
+ is_binary = False |
+ status = "A" |
+ |
+ output, retcode = RunShellWithReturnCode(["cvs", "status", filename]) |
+ if retcode: |
+ ErrorExit("Got error status from 'cvs status %s'" % filename) |
+ |
+ if output.find("Status: Locally Modified") != -1: |
+ status = "M" |
+ temp_filename = "%s.tmp123" % filename |
+ os.rename(filename, temp_filename) |
+ base_content = self.GetOriginalContent_(filename) |
+ os.rename(temp_filename, filename) |
+ elif output.find("Status: Locally Added"): |
+ status = "A" |
+ base_content = "" |
+ elif output.find("Status: Needs Checkout"): |
+ status = "D" |
+ base_content = self.GetOriginalContent_(filename) |
+ |
+ return (base_content, new_content, is_binary, status) |
+ |
+ def GenerateDiff(self, extra_args): |
+ cmd = ["cvs", "diff", "-u", "-N"] |
+ if self.options.revision: |
+ cmd += ["-r", self.options.revision] |
+ |
+ cmd.extend(extra_args) |
+ data, retcode = RunShellWithReturnCode(cmd) |
+ count = 0 |
+ if retcode == 0: |
+ for line in data.splitlines(): |
+ if line.startswith("Index:"): |
+ count += 1 |
+ logging.info(line) |
+ |
+ if not count: |
+ ErrorExit("No valid patches found in output from cvs diff") |
+ |
+ return data |
+ |
+ def GetUnknownFiles(self): |
+ status = RunShell(["cvs", "diff"], |
+ silent_ok=True) |
+ unknown_files = [] |
+ for line in status.split("\n"): |
+ if line and line[0] == "?": |
+ unknown_files.append(line) |
+ return unknown_files |
+ |
class MercurialVCS(VersionControlSystem): |
"""Implementation of the VersionControlSystem interface for Mercurial.""" |
@@ -1364,6 +1430,326 @@ class MercurialVCS(VersionControlSystem): |
return base_content, new_content, is_binary, status |
+class PerforceVCS(VersionControlSystem): |
+ """Implementation of the VersionControlSystem interface for Perforce.""" |
+ |
+ def __init__(self, options): |
+ |
+ def ConfirmLogin(): |
+ # Make sure we have a valid perforce session |
+ while True: |
+ data, retcode = self.RunPerforceCommandWithReturnCode( |
+ ["login", "-s"], marshal_output=True) |
+ if not data: |
+ ErrorExit("Error checking perforce login") |
+ if not retcode and (not "code" in data or data["code"] != "error"): |
+ break |
+ print "Enter perforce password: " |
+ self.RunPerforceCommandWithReturnCode(["login"]) |
+ |
+ super(PerforceVCS, self).__init__(options) |
+ |
+ self.p4_changelist = options.p4_changelist |
+ if not self.p4_changelist: |
+ ErrorExit("A changelist id is required") |
+ if (options.revision): |
+ ErrorExit("--rev is not supported for perforce") |
+ |
+ self.p4_port = options.p4_port |
+ self.p4_client = options.p4_client |
+ self.p4_user = options.p4_user |
+ |
+ ConfirmLogin() |
+ |
+ if not options.message: |
+ description = self.RunPerforceCommand(["describe", self.p4_changelist], |
+ marshal_output=True) |
+ if description and "desc" in description: |
+ # Rietveld doesn't support multi-line descriptions |
+ raw_message = description["desc"].strip() |
+ lines = raw_message.splitlines() |
+ if len(lines): |
+ options.message = lines[0] |
+ |
+ def RunPerforceCommandWithReturnCode(self, extra_args, marshal_output=False, |
+ universal_newlines=True): |
+ args = ["p4"] |
+ if marshal_output: |
+ # -G makes perforce format its output as marshalled python objects |
+ args.extend(["-G"]) |
+ if self.p4_port: |
+ args.extend(["-p", self.p4_port]) |
+ if self.p4_client: |
+ args.extend(["-c", self.p4_client]) |
+ if self.p4_user: |
+ args.extend(["-u", self.p4_user]) |
+ args.extend(extra_args) |
+ |
+ data, retcode = RunShellWithReturnCode( |
+ args, print_output=False, universal_newlines=universal_newlines) |
+ if marshal_output and data: |
+ data = marshal.loads(data) |
+ return data, retcode |
+ |
+ def RunPerforceCommand(self, extra_args, marshal_output=False, |
+ universal_newlines=True): |
+ # This might be a good place to cache call results, since things like |
+ # describe or fstat might get called repeatedly. |
+ data, retcode = self.RunPerforceCommandWithReturnCode( |
+ extra_args, marshal_output, universal_newlines) |
+ if retcode: |
+ ErrorExit("Got error status from %s:\n%s" % (extra_args, data)) |
+ return data |
+ |
+ def GetFileProperties(self, property_key_prefix = "", command = "describe"): |
+ description = self.RunPerforceCommand(["describe", self.p4_changelist], |
+ marshal_output=True) |
+ |
+ changed_files = {} |
+ file_index = 0 |
+ # Try depotFile0, depotFile1, ... until we don't find a match |
+ while True: |
+ file_key = "depotFile%d" % file_index |
+ if file_key in description: |
+ filename = description[file_key] |
+ change_type = description[property_key_prefix + str(file_index)] |
+ changed_files[filename] = change_type |
+ file_index += 1 |
+ else: |
+ break |
+ return changed_files |
+ |
+ def GetChangedFiles(self): |
+ return self.GetFileProperties("action") |
+ |
+ def GetUnknownFiles(self): |
+ # Perforce doesn't detect new files, they have to be explicitly added |
+ return [] |
+ |
+ def IsBaseBinary(self, filename): |
+ base_filename = self.GetBaseFilename(filename) |
+ return self.IsBinaryHelper(base_filename, "files") |
+ |
+ def IsPendingBinary(self, filename): |
+ return self.IsBinaryHelper(filename, "describe") |
+ |
+ def IsBinary(self, filename): |
+ ErrorExit("IsBinary is not safe: call IsBaseBinary or IsPendingBinary") |
+ |
+ def IsBinaryHelper(self, filename, command): |
+ file_types = self.GetFileProperties("type", command) |
+ if not filename in file_types: |
+ ErrorExit("Trying to check binary status of unknown file %s." % filename) |
+ # This treats symlinks, macintosh resource files, temporary objects, and |
+ # unicode as binary. See the Perforce docs for more details: |
+ # http://www.perforce.com/perforce/doc.current/manuals/cmdref/o.ftypes.html |
+ return not file_types[filename].endswith("text") |
+ |
+ def GetFileContent(self, filename, revision, is_binary): |
+ file_arg = filename |
+ if revision: |
+ file_arg += "#" + revision |
+ # -q suppresses the initial line that displays the filename and revision |
+ return self.RunPerforceCommand(["print", "-q", file_arg], |
+ universal_newlines=not is_binary) |
+ |
+ def GetBaseFilename(self, filename): |
+ actionsWithDifferentBases = [ |
+ "move/add", # p4 move |
+ "branch", # p4 integrate (to a new file), similar to hg "add" |
+ "add", # p4 integrate (to a new file), after modifying the new file |
+ ] |
+ |
+ # We only see a different base for "add" if this is a downgraded branch |
+ # after a file was branched (integrated), then edited. |
+ if self.GetAction(filename) in actionsWithDifferentBases: |
+ # -Or shows information about pending integrations/moves |
+ fstat_result = self.RunPerforceCommand(["fstat", "-Or", filename], |
+ marshal_output=True) |
+ |
+ baseFileKey = "resolveFromFile0" # I think it's safe to use only file0 |
+ if baseFileKey in fstat_result: |
+ return fstat_result[baseFileKey] |
+ |
+ return filename |
+ |
+ def GetBaseRevision(self, filename): |
+ base_filename = self.GetBaseFilename(filename) |
+ |
+ have_result = self.RunPerforceCommand(["have", base_filename], |
+ marshal_output=True) |
+ if "haveRev" in have_result: |
+ return have_result["haveRev"] |
+ |
+ def GetLocalFilename(self, filename): |
+ where = self.RunPerforceCommand(["where", filename], marshal_output=True) |
+ if "path" in where: |
+ return where["path"] |
+ |
+ def GenerateDiff(self, args): |
+ class DiffData: |
+ def __init__(self, perforceVCS, filename, action): |
+ self.perforceVCS = perforceVCS |
+ self.filename = filename |
+ self.action = action |
+ self.base_filename = perforceVCS.GetBaseFilename(filename) |
+ |
+ self.file_body = None |
+ self.base_rev = None |
+ self.prefix = None |
+ self.working_copy = True |
+ self.change_summary = None |
+ |
+ def GenerateDiffHeader(diffData): |
+ header = [] |
+ header.append("Index: %s" % diffData.filename) |
+ header.append("=" * 67) |
+ |
+ if diffData.base_filename != diffData.filename: |
+ if diffData.action.startswith("move"): |
+ verb = "rename" |
+ else: |
+ verb = "copy" |
+ header.append("%s from %s" % (verb, diffData.base_filename)) |
+ header.append("%s to %s" % (verb, diffData.filename)) |
+ |
+ suffix = "\t(revision %s)" % diffData.base_rev |
+ header.append("--- " + diffData.base_filename + suffix) |
+ if diffData.working_copy: |
+ suffix = "\t(working copy)" |
+ header.append("+++ " + diffData.filename + suffix) |
+ if diffData.change_summary: |
+ header.append(diffData.change_summary) |
+ return header |
+ |
+ def GenerateMergeDiff(diffData, args): |
+ # -du generates a unified diff, which is nearly svn format |
+ diffData.file_body = self.RunPerforceCommand( |
+ ["diff", "-du", diffData.filename] + args) |
+ diffData.base_rev = self.GetBaseRevision(diffData.filename) |
+ diffData.prefix = "" |
+ |
+ # We have to replace p4's file status output (the lines starting |
+ # with +++ or ---) to match svn's diff format |
+ lines = diffData.file_body.splitlines() |
+ first_good_line = 0 |
+ while (first_good_line < len(lines) and |
+ not lines[first_good_line].startswith("@@")): |
+ first_good_line += 1 |
+ diffData.file_body = "\n".join(lines[first_good_line:]) |
+ return diffData |
+ |
+ def GenerateAddDiff(diffData): |
+ fstat = self.RunPerforceCommand(["fstat", diffData.filename], |
+ marshal_output=True) |
+ if "headRev" in fstat: |
+ diffData.base_rev = fstat["headRev"] # Re-adding a deleted file |
+ else: |
+ diffData.base_rev = "0" # Brand new file |
+ diffData.working_copy = False |
+ rel_path = self.GetLocalFilename(diffData.filename) |
+ diffData.file_body = open(rel_path, 'r').read() |
+ # Replicate svn's list of changed lines |
+ line_count = len(diffData.file_body.splitlines()) |
+ diffData.change_summary = "@@ -0,0 +1" |
+ if line_count > 1: |
+ diffData.change_summary += ",%d" % line_count |
+ diffData.change_summary += " @@" |
+ diffData.prefix = "+" |
+ return diffData |
+ |
+ def GenerateDeleteDiff(diffData): |
+ diffData.base_rev = self.GetBaseRevision(diffData.filename) |
+ is_base_binary = self.IsBaseBinary(diffData.filename) |
+ # For deletes, base_filename == filename |
+ diffData.file_body = self.GetFileContent(diffData.base_filename, |
+ None, |
+ is_base_binary) |
+ # Replicate svn's list of changed lines |
+ line_count = len(diffData.file_body.splitlines()) |
+ diffData.change_summary = "@@ -1" |
+ if line_count > 1: |
+ diffData.change_summary += ",%d" % line_count |
+ diffData.change_summary += " +0,0 @@" |
+ diffData.prefix = "-" |
+ return diffData |
+ |
+ changed_files = self.GetChangedFiles() |
+ |
+ svndiff = [] |
+ filecount = 0 |
+ for (filename, action) in changed_files.items(): |
+ svn_status = self.PerforceActionToSvnStatus(action) |
+ if svn_status == "SKIP": |
+ continue |
+ |
+ diffData = DiffData(self, filename, action) |
+ # Is it possible to diff a branched file? Stackoverflow says no: |
+ # http://stackoverflow.com/questions/1771314/in-perforce-command-line-how-to-diff-a-file-reopened-for-add |
+ if svn_status == "M": |
+ diffData = GenerateMergeDiff(diffData, args) |
+ elif svn_status == "A": |
+ diffData = GenerateAddDiff(diffData) |
+ elif svn_status == "D": |
+ diffData = GenerateDeleteDiff(diffData) |
+ else: |
+ ErrorExit("Unknown file action %s (svn action %s)." % \ |
+ (action, svn_status)) |
+ |
+ svndiff += GenerateDiffHeader(diffData) |
+ |
+ for line in diffData.file_body.splitlines(): |
+ svndiff.append(diffData.prefix + line) |
+ filecount += 1 |
+ if not filecount: |
+ ErrorExit("No valid patches found in output from p4 diff") |
+ return "\n".join(svndiff) + "\n" |
+ |
+ def PerforceActionToSvnStatus(self, status): |
+ # Mirroring the list at http://permalink.gmane.org/gmane.comp.version-control.mercurial.devel/28717 |
+ # Is there something more official? |
+ return { |
+ "add" : "A", |
+ "branch" : "A", |
+ "delete" : "D", |
+ "edit" : "M", # Also includes changing file types. |
+ "integrate" : "M", |
+ "move/add" : "M", |
+ "move/delete": "SKIP", |
+ "purge" : "D", # How does a file's status become "purge"? |
+ }[status] |
+ |
+ def GetAction(self, filename): |
+ changed_files = self.GetChangedFiles() |
+ if not filename in changed_files: |
+ ErrorExit("Trying to get base version of unknown file %s." % filename) |
+ |
+ return changed_files[filename] |
+ |
+ def GetBaseFile(self, filename): |
+ base_filename = self.GetBaseFilename(filename) |
+ base_content = "" |
+ new_content = None |
+ |
+ status = self.PerforceActionToSvnStatus(self.GetAction(filename)) |
+ |
+ if status != "A": |
+ revision = self.GetBaseRevision(base_filename) |
+ if not revision: |
+ ErrorExit("Couldn't find base revision for file %s" % filename) |
+ is_base_binary = self.IsBaseBinary(base_filename) |
+ base_content = self.GetFileContent(base_filename, |
+ revision, |
+ is_base_binary) |
+ |
+ is_binary = self.IsPendingBinary(filename) |
+ if status != "D" and status != "SKIP": |
+ relpath = self.GetLocalFilename(filename) |
+ if is_binary and self.IsImage(relpath): |
+ new_content = open(relpath, "rb").read() |
+ |
+ return base_content, new_content, is_binary, status |
+ |
# NOTE: The SplitPatch function is duplicated in engine.py, keep them in sync. |
def SplitPatch(data): |
"""Splits a patch into separate pieces for each file. |
@@ -1433,7 +1819,7 @@ def UploadSeparatePatches(issue, rpc_server, patchset, data, options): |
return rv |
-def GuessVCSName(): |
+def GuessVCSName(options): |
"""Helper to guess the version control system. |
This examines the current directory, guesses which VersionControlSystem |
@@ -1441,10 +1827,17 @@ def GuessVCSName(): |
Returns: |
A pair (vcs, output). vcs is a string indicating which VCS was detected |
- and is one of VCS_GIT, VCS_MERCURIAL, VCS_SUBVERSION, or VCS_UNKNOWN. |
+ and is one of VCS_GIT, VCS_MERCURIAL, VCS_SUBVERSION, VCS_PERFORCE, |
+ VCS_CVS, or VCS_UNKNOWN. |
+ Since local perforce repositories can't be easily detected, this method |
+ will only guess VCS_PERFORCE if any perforce options have been specified. |
output is a string containing any interesting output from the vcs |
detection routine, or None if there is nothing interesting. |
""" |
+ for attribute, value in options.__dict__.iteritems(): |
+ if attribute.startswith("p4") and value != None: |
+ return (VCS_PERFORCE, None) |
+ |
# Mercurial has a command to get the base directory of a repository |
# Try running it, but don't die if we don't have hg installed. |
# NOTE: we try Mercurial first as it can sit on top of an SVN working copy. |
@@ -1472,6 +1865,15 @@ def GuessVCSName(): |
if errno != 2: # ENOENT -- they don't have git installed. |
raise |
+ # detect CVS repos use `cvs status && $? == 0` rules |
+ try: |
+ out, returncode = RunShellWithReturnCode(["cvs", "status"]) |
+ if returncode == 0: |
+ return (VCS_CVS, None) |
+ except OSError, (errno, message): |
+ if error != 2: |
+ raise |
+ |
return (VCS_UNKNOWN, None) |
@@ -1496,7 +1898,7 @@ def GuessVCS(options): |
ErrorExit("Unknown version control system %r specified." % vcs) |
(vcs, extra_output) = (v, None) |
else: |
- (vcs, extra_output) = GuessVCSName() |
+ (vcs, extra_output) = GuessVCSName(options) |
if vcs == VCS_MERCURIAL: |
if extra_output is None: |
@@ -1504,8 +1906,12 @@ def GuessVCS(options): |
return MercurialVCS(options, extra_output) |
elif vcs == VCS_SUBVERSION: |
return SubversionVCS(options) |
+ elif vcs == VCS_PERFORCE: |
+ return PerforceVCS(options) |
elif vcs == VCS_GIT: |
return GitVCS(options) |
+ elif vcs == VCS_CVS: |
+ return CVSVCS(options) |
ErrorExit(("Could not guess version control system. " |
"Are you in a working copy directory?")) |
@@ -1684,6 +2090,10 @@ def RealMain(argv, data=None): |
if data is None: |
data = vcs.GenerateDiff(args) |
data = vcs.PostProcessDiff(data) |
+ if options.print_diffs: |
+ print "Rietveld diff start:*****" |
+ print data |
+ print "Rietveld diff end:*****" |
files = vcs.GetBaseFiles(data) |
if verbosity >= 1: |
print "Upload server:", options.server, "(change with -s/--server)" |
@@ -1701,6 +2111,12 @@ def RealMain(argv, data=None): |
options.account_type) |
form_fields = [("subject", message)] |
if base: |
+ b = urlparse.urlparse(base) |
+ username, netloc = urllib.splituser(b.netloc) |
+ if username: |
+ logging.info("Removed username from base URL") |
+ base = urlparse.urlunparse((b.scheme, netloc, b.path, b.params, |
+ b.query, b.fragment)) |
form_fields.append(("base", base)) |
if options.issue: |
form_fields.append(("issue", str(options.issue))) |