From 261eeb5e93850c603981ce723051631130d7c255 Mon Sep 17 00:00:00 2001 From: "maruel@chromium.org" Date: Mon, 16 Nov 2009 18:25:45 +0000 Subject: [PATCH] Revert 32057, 32058, 32059, 32062 because they still have unwanted side-effects. Revert "Group SCM-specific functions in classes to simplify generalization of the interface." Revert "Fix 2 recently introduced errors." Revert "Fix a variable aliasing error." Revert "Fix typo." TBR=dpranke TEST=none BUG=none Review URL: http://codereview.chromium.org/391075 git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@32065 0039d316-1c4b-4281-b951-d872f2087c98 --- gcl.py | 58 ++- gclient.py | 8 +- gclient_scm.py | 109 +++--- gclient_utils.py | 22 +- git_cl_hooks.py | 5 +- presubmit_support.py | 34 +- revert.py | 2 +- scm.py | 724 ++++++++++++++++-------------------- tests/gcl_unittest.py | 47 ++- tests/gclient_scm_test.py | 76 ++-- tests/gclient_test.py | 17 +- tests/presubmit_unittest.py | 124 +++--- tests/revert_unittest.py | 3 +- tests/scm_unittest.py | 90 ++--- tests/super_mox.py | 2 +- tests/trychange_unittest.py | 6 +- trychange.py | 51 ++- 17 files changed, 679 insertions(+), 699 deletions(-) diff --git a/gcl.py b/gcl.py index 1a9ce9d495..16afdd7c42 100755 --- a/gcl.py +++ b/gcl.py @@ -19,10 +19,10 @@ import upload import urllib2 # gcl now depends on gclient. -from scm import SVN +import gclient_scm import gclient_utils -__version__ = '1.1.2' +__version__ = '1.1.1' CODEREVIEW_SETTINGS = { @@ -46,13 +46,43 @@ MISSING_TEST_MSG = "Change contains new or modified methods, but no new tests!" FILES_CACHE = {} +### SVN Functions + +def IsSVNMoved(filename): + """Determine if a file has been added through svn mv""" + info = gclient_scm.CaptureSVNInfo(filename) + return (info.get('Copied From URL') and + info.get('Copied From Rev') and + info.get('Schedule') == 'add') + + +def GetSVNFileProperty(file, property_name): + """Returns the value of an SVN property for the given file. + + Args: + file: The file to check + property_name: The name of the SVN property, e.g. "svn:mime-type" + + Returns: + The value of the property, which will be the empty string if the property + is not set on the file. If the file is not under version control, the + empty string is also returned. + """ + output = RunShell(["svn", "propget", property_name, file]) + if (output.startswith("svn: ") and + output.endswith("is not under version control")): + return "" + else: + return output + + def UnknownFiles(extra_args): """Runs svn status and prints unknown files. Any args in |extra_args| are passed to the tool to support giving alternate code locations. """ - return [item[1] for item in SVN.CaptureStatus(extra_args) + return [item[1] for item in gclient_scm.CaptureSVNStatus(extra_args) if item[0][0] == '?'] @@ -63,7 +93,7 @@ def GetRepositoryRoot(): """ global REPOSITORY_ROOT if not REPOSITORY_ROOT: - infos = SVN.CaptureInfo(os.getcwd(), print_error=False) + infos = gclient_scm.CaptureSVNInfo(os.getcwd(), print_error=False) cur_dir_repo_root = infos.get("Repository Root") if not cur_dir_repo_root: raise gclient_utils.Error("gcl run outside of repository") @@ -71,7 +101,7 @@ def GetRepositoryRoot(): REPOSITORY_ROOT = os.getcwd() while True: parent = os.path.dirname(REPOSITORY_ROOT) - if (SVN.CaptureInfo(parent, print_error=False).get( + if (gclient_scm.CaptureSVNInfo(parent, print_error=False).get( "Repository Root") != cur_dir_repo_root): break REPOSITORY_ROOT = parent @@ -116,7 +146,7 @@ def GetCachedFile(filename, max_age=60*60*24*3, use_root=False): os.stat(cached_file).st_mtime > max_age): local_dir = os.path.dirname(os.path.abspath(filename)) local_base = os.path.basename(filename) - dir_info = SVN.CaptureInfo(".") + dir_info = gclient_scm.CaptureSVNInfo(".") repo_root = dir_info["Repository Root"] if use_root: url_path = repo_root @@ -128,7 +158,7 @@ def GetCachedFile(filename, max_age=60*60*24*3, use_root=False): r = "" if not use_root: local_path = os.path.join(local_dir, local_base) - r = SVN.CaptureStatus((local_path,)) + r = gclient_scm.CaptureSVNStatus((local_path,)) rc = -1 if r: status = r[0][0] @@ -448,7 +478,7 @@ class ChangeInfo(object): if update_status: for item in files: filename = os.path.join(local_root, item[1]) - status_result = SVN.CaptureStatus(filename) + status_result = gclient_scm.CaptureSVNStatus(filename) if not status_result or not status_result[0][0]: # File has been reverted. save = True @@ -532,7 +562,7 @@ def GetModifiedFiles(): files_in_cl[filename] = change_info.name # Get all the modified files. - status_result = SVN.CaptureStatus(None) + status_result = gclient_scm.CaptureSVNStatus(None) for line in status_result: status = line[0] filename = line[1] @@ -719,10 +749,10 @@ def GenerateDiff(files, root=None): diff = [] for filename in files: - # TODO(maruel): Use SVN.DiffItem(). # Use svn info output instead of os.path.isdir because the latter fails # when the file is deleted. - if SVN.CaptureInfo(filename).get('Node Kind') == 'directory': + if gclient_scm.CaptureSVNInfo(filename).get("Node Kind") in ("dir", + "directory"): continue # If the user specified a custom diff command in their svn config file, # then it'll be used when we do svn diff, which we don't want to happen @@ -740,7 +770,7 @@ def GenerateDiff(files, root=None): output = RunShell(["svn", "diff", "--config-dir", bogus_dir, filename]) if output: diff.append(output) - elif SVN.IsMoved(filename): + elif IsSVNMoved(filename): # svn diff on a mv/cp'd file outputs nothing. # We put in an empty Index entry so upload.py knows about them. diff.append("\nIndex: %s\n" % filename) @@ -966,7 +996,7 @@ def Change(change_info, args): silent = FilterFlag(args, "--silent") # Verify the user is running the change command from a read-write checkout. - svn_info = SVN.CaptureInfo('.') + svn_info = gclient_scm.CaptureSVNInfo('.') if not svn_info: ErrorExit("Current checkout is unversioned. Please retry with a versioned " "directory.") @@ -978,7 +1008,7 @@ def Change(change_info, args): f.close() else: override_description = None - + if change_info.issue: try: description = GetIssueDescription(change_info.issue) diff --git a/gclient.py b/gclient.py index 9edadc2c86..86d10f4340 100755 --- a/gclient.py +++ b/gclient.py @@ -66,7 +66,7 @@ Hooks """ __author__ = "darinf@gmail.com (Darin Fisher)" -__version__ = "0.3.4" +__version__ = "0.3.3" import errno import logging @@ -747,9 +747,9 @@ class GClient(object): # Use entry and not entry_fixed there. if entry not in entries and os.path.exists(e_dir): modified_files = False - if isinstance(prev_entries, list): + if isinstance(prev_entries,list): # old .gclient_entries format was list, now dict - modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir) + modified_files = gclient_scm.CaptureSVNStatus(e_dir) else: file_list = [] scm = gclient_scm.CreateSCM(prev_entries[entry], self._root_dir, @@ -830,7 +830,7 @@ class GClient(object): (url, rev) = GetURLAndRev(name, solution["url"]) entries[name] = "%s@%s" % (url, rev) # TODO(aharper): SVN/SCMWrapper cleanup (non-local commandset) - entries_deps_content[name] = gclient_scm.scm.SVN.Capture( + entries_deps_content[name] = gclient_scm.CaptureSVN( ["cat", "%s/%s@%s" % (url, self._options.deps_file, diff --git a/gclient_scm.py b/gclient_scm.py index e2a0017ca6..a9c537b9c2 100644 --- a/gclient_scm.py +++ b/gclient_scm.py @@ -1,6 +1,16 @@ -# Copyright (c) 2009 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. +# Copyright 2009 Google Inc. All Rights Reserved. +# +# 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. """Gclient-specific SCM-specific operations.""" @@ -8,13 +18,19 @@ import logging import os import re import subprocess +import sys +import xml.dom.minidom -import scm import gclient_utils +# TODO(maruel): Temporary. +from scm import CaptureGit, CaptureGitStatus, CaptureSVN +from scm import CaptureSVNHeadRevision, CaptureSVNInfo, CaptureSVNStatus +from scm import RunSVN, RunSVNAndFilterOutput, RunSVNAndGetFileList ### SCM abstraction layer + # Factory Method for SCM wrapper creation def CreateSCM(url=None, root_dir=None, relpath=None, scm_name='svn'): @@ -77,20 +93,20 @@ class SCMWrapper(object): return getattr(self, command)(options, args, file_list) -class GitWrapper(SCMWrapper, scm.GIT): +class GitWrapper(SCMWrapper): """Wrapper for Git""" def cleanup(self, options, args, file_list): """Cleanup working copy.""" __pychecker__ = 'unusednames=args,file_list,options' - self._Run(['prune'], redirect_stdout=False) - self._Run(['fsck'], redirect_stdout=False) - self._Run(['gc'], redirect_stdout=False) + self._RunGit(['prune'], redirect_stdout=False) + self._RunGit(['fsck'], redirect_stdout=False) + self._RunGit(['gc'], redirect_stdout=False) def diff(self, options, args, file_list): __pychecker__ = 'unusednames=args,file_list,options' - merge_base = self._Run(['merge-base', 'HEAD', 'origin']) - self._Run(['diff', merge_base], redirect_stdout=False) + merge_base = self._RunGit(['merge-base', 'HEAD', 'origin']) + self._RunGit(['diff', merge_base], redirect_stdout=False) def export(self, options, args, file_list): __pychecker__ = 'unusednames=file_list,options' @@ -98,8 +114,8 @@ class GitWrapper(SCMWrapper, scm.GIT): export_path = os.path.abspath(os.path.join(args[0], self.relpath)) if not os.path.exists(export_path): os.makedirs(export_path) - self._Run(['checkout-index', '-a', '--prefix=%s/' % export_path], - redirect_stdout=False) + self._RunGit(['checkout-index', '-a', '--prefix=%s/' % export_path], + redirect_stdout=False) def update(self, options, args, file_list): """Runs git to update or transparently checkout the working copy. @@ -126,21 +142,21 @@ class GitWrapper(SCMWrapper, scm.GIT): print("\n_____ %s%s" % (self.relpath, rev_str)) if not os.path.exists(self.checkout_path): - self._Run(['clone', url, self.checkout_path], - cwd=self._root_dir, redirect_stdout=False) + self._RunGit(['clone', url, self.checkout_path], + cwd=self._root_dir, redirect_stdout=False) if revision: - self._Run(['reset', '--hard', revision], redirect_stdout=False) - files = self._Run(['ls-files']).split() + self._RunGit(['reset', '--hard', revision], redirect_stdout=False) + files = self._RunGit(['ls-files']).split() file_list.extend([os.path.join(self.checkout_path, f) for f in files]) return - self._Run(['remote', 'update'], redirect_stdout=False) + self._RunGit(['remote', 'update'], redirect_stdout=False) new_base = 'origin' if revision: new_base = revision - files = self._Run(['diff', new_base, '--name-only']).split() + files = self._RunGit(['diff', new_base, '--name-only']).split() file_list.extend([os.path.join(self.checkout_path, f) for f in files]) - self._Run(['rebase', '-v', new_base], redirect_stdout=False) + self._RunGit(['rebase', '-v', new_base], redirect_stdout=False) print "Checked out revision %s." % self.revinfo(options, (), None) def revert(self, options, args, file_list): @@ -156,15 +172,15 @@ class GitWrapper(SCMWrapper, scm.GIT): print("\n_____ %s is missing, synching instead" % self.relpath) # Don't reuse the args. return self.update(options, [], file_list) - merge_base = self._Run(['merge-base', 'HEAD', 'origin']) - files = self._Run(['diff', merge_base, '--name-only']).split() - self._Run(['reset', '--hard', merge_base], redirect_stdout=False) + merge_base = self._RunGit(['merge-base', 'HEAD', 'origin']) + files = self._RunGit(['diff', merge_base, '--name-only']).split() + self._RunGit(['reset', '--hard', merge_base], redirect_stdout=False) file_list.extend([os.path.join(self.checkout_path, f) for f in files]) def revinfo(self, options, args, file_list): """Display revision""" __pychecker__ = 'unusednames=args,file_list,options' - return self._Run(['rev-parse', 'HEAD']) + return self._RunGit(['rev-parse', 'HEAD']) def runhooks(self, options, args, file_list): self.status(options, args, file_list) @@ -176,30 +192,29 @@ class GitWrapper(SCMWrapper, scm.GIT): print('\n________ couldn\'t run status in %s:\nThe directory ' 'does not exist.' % self.checkout_path) else: - merge_base = self._Run(['merge-base', 'HEAD', 'origin']) - self._Run(['diff', '--name-status', merge_base], redirect_stdout=False) - files = self._Run(['diff', '--name-only', merge_base]).split() + merge_base = self._RunGit(['merge-base', 'HEAD', 'origin']) + self._RunGit(['diff', '--name-status', merge_base], redirect_stdout=False) + files = self._RunGit(['diff', '--name-only', merge_base]).split() file_list.extend([os.path.join(self.checkout_path, f) for f in files]) - def _Run(self, args, cwd=None, checkrc=True, redirect_stdout=True): - # TODO(maruel): Merge with Capture? + def _RunGit(self, args, cwd=None, checkrc=True, redirect_stdout=True): stdout=None if redirect_stdout: stdout=subprocess.PIPE if cwd == None: cwd = self.checkout_path - cmd = [self.COMMAND] + cmd = ['git'] cmd.extend(args) sp = subprocess.Popen(cmd, cwd=cwd, stdout=stdout) if checkrc and sp.returncode: raise gclient_utils.Error('git command %s returned %d' % (args[0], sp.returncode)) output = sp.communicate()[0] - if output is not None: + if output != None: return output.strip() -class SVNWrapper(SCMWrapper, scm.SVN): +class SVNWrapper(SCMWrapper): """ Wrapper for SVN """ def cleanup(self, options, args, file_list): @@ -207,14 +222,14 @@ class SVNWrapper(SCMWrapper, scm.SVN): __pychecker__ = 'unusednames=file_list,options' command = ['cleanup'] command.extend(args) - self.Run(command, os.path.join(self._root_dir, self.relpath)) + RunSVN(command, os.path.join(self._root_dir, self.relpath)) def diff(self, options, args, file_list): # NOTE: This function does not currently modify file_list. __pychecker__ = 'unusednames=file_list,options' command = ['diff'] command.extend(args) - self.Run(command, os.path.join(self._root_dir, self.relpath)) + RunSVN(command, os.path.join(self._root_dir, self.relpath)) def export(self, options, args, file_list): __pychecker__ = 'unusednames=file_list,options' @@ -227,7 +242,7 @@ class SVNWrapper(SCMWrapper, scm.SVN): assert os.path.exists(export_path) command = ['export', '--force', '.'] command.append(export_path) - self.Run(command, os.path.join(self._root_dir, self.relpath)) + RunSVN(command, os.path.join(self._root_dir, self.relpath)) def update(self, options, args, file_list): """Runs SCM to update or transparently checkout the working copy. @@ -264,11 +279,11 @@ class SVNWrapper(SCMWrapper, scm.SVN): command = ['checkout', url, checkout_path] if revision: command.extend(['--revision', str(revision)]) - self.RunAndGetFileList(options, command, self._root_dir, file_list) + RunSVNAndGetFileList(options, command, self._root_dir, file_list) return # Get the existing scm url and the revision number of the current checkout. - from_info = self.CaptureInfo(os.path.join(checkout_path, '.'), '.') + from_info = CaptureSVNInfo(os.path.join(checkout_path, '.'), '.') if not from_info: raise gclient_utils.Error("Can't update/checkout %r if an unversioned " "directory is present. Delete the directory " @@ -278,12 +293,12 @@ class SVNWrapper(SCMWrapper, scm.SVN): if options.manually_grab_svn_rev: # Retrieve the current HEAD version because svn is slow at null updates. if not revision: - from_info_live = self.CaptureInfo(from_info['URL'], '.') + from_info_live = CaptureSVNInfo(from_info['URL'], '.') revision = str(from_info_live['Revision']) rev_str = ' at %s' % revision if from_info['URL'] != base_url: - to_info = self.CaptureInfo(url, '.') + to_info = CaptureSVNInfo(url, '.') if not to_info.get('Repository Root') or not to_info.get('UUID'): # The url is invalid or the server is not accessible, it's safer to bail # out right now. @@ -305,12 +320,12 @@ class SVNWrapper(SCMWrapper, scm.SVN): from_info['Repository Root'], to_info['Repository Root'], self.relpath] - self.Run(command, self._root_dir) + RunSVN(command, self._root_dir) from_info['URL'] = from_info['URL'].replace( from_info['Repository Root'], to_info['Repository Root']) else: - if self.CaptureStatus(checkout_path): + if CaptureSVNStatus(checkout_path): raise gclient_utils.Error("Can't switch the checkout to %s; UUID " "don't match and there is local changes " "in %s. Delete the directory and " @@ -322,7 +337,7 @@ class SVNWrapper(SCMWrapper, scm.SVN): command = ['checkout', url, checkout_path] if revision: command.extend(['--revision', str(revision)]) - self.RunAndGetFileList(options, command, self._root_dir, file_list) + RunSVNAndGetFileList(options, command, self._root_dir, file_list) return @@ -336,7 +351,7 @@ class SVNWrapper(SCMWrapper, scm.SVN): command = ["update", checkout_path] if revision: command.extend(['--revision', str(revision)]) - self.RunAndGetFileList(options, command, self._root_dir, file_list) + RunSVNAndGetFileList(options, command, self._root_dir, file_list) def revert(self, options, args, file_list): """Reverts local modifications. Subversion specific. @@ -353,7 +368,7 @@ class SVNWrapper(SCMWrapper, scm.SVN): # Don't reuse the args. return self.update(options, [], file_list) - for file_status in self.CaptureStatus(path): + for file_status in CaptureSVNStatus(path): file_path = os.path.join(path, file_status[1]) if file_status[0][0] == 'X': # Ignore externals. @@ -388,7 +403,7 @@ class SVNWrapper(SCMWrapper, scm.SVN): try: # svn revert is so broken we don't even use it. Using # "svn up --revision BASE" achieve the same effect. - self.RunAndGetFileList(options, ['update', '--revision', 'BASE'], path, + RunSVNAndGetFileList(options, ['update', '--revision', 'BASE'], path, file_list) except OSError, e: # Maybe the directory disapeared meanwhile. We don't want it to throw an @@ -398,7 +413,7 @@ class SVNWrapper(SCMWrapper, scm.SVN): def revinfo(self, options, args, file_list): """Display revision""" __pychecker__ = 'unusednames=args,file_list,options' - return self.CaptureHeadRevision(self.url) + return CaptureSVNHeadRevision(self.url) def runhooks(self, options, args, file_list): self.status(options, args, file_list) @@ -415,7 +430,7 @@ class SVNWrapper(SCMWrapper, scm.SVN): % (' '.join(command), path)) # There's no file list to retrieve. else: - self.RunAndGetFileList(options, command, path, file_list) + RunSVNAndGetFileList(options, command, path, file_list) def pack(self, options, args, file_list): """Generates a patch file which can be applied to the root of the @@ -460,4 +475,4 @@ class SVNWrapper(SCMWrapper, scm.SVN): print line filterer = DiffFilterer(self.relpath) - self.RunAndFilterOutput(command, path, False, False, filterer.Filter) + RunSVNAndFilterOutput(command, path, False, False, filterer.Filter) diff --git a/gclient_utils.py b/gclient_utils.py index 4a5989c0cd..9016270d4d 100644 --- a/gclient_utils.py +++ b/gclient_utils.py @@ -12,8 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Generic utils.""" - import errno import os import re @@ -24,6 +22,8 @@ import time import xml.dom.minidom import xml.parsers.expat +## Generic utils + def SplitUrlRevision(url): """Splits url and returns a two-tuple: url, rev""" @@ -76,9 +76,9 @@ class PrintableObject(object): return output -def FileRead(filename, mode='rU'): +def FileRead(filename): content = None - f = open(filename, mode) + f = open(filename, "rU") try: content = f.read() finally: @@ -86,8 +86,8 @@ def FileRead(filename, mode='rU'): return content -def FileWrite(filename, content, mode='w'): - f = open(filename, mode) +def FileWrite(filename, content): + f = open(filename, "w") try: f.write(content) finally: @@ -201,9 +201,9 @@ def SubprocessCallAndFilter(command, only if we actually need to print something else as well, so you can get the context of the output. If print_messages is false and print_stdout is false, no output at all is generated. - - Also, if print_stdout is true, the command's stdout is also forwarded - to stdout. + + Also, if print_stdout is true, the command's stdout is also forwarded + to stdout. If a filter function is specified, it is expected to take a single string argument, and it will be called with each line of the @@ -223,7 +223,7 @@ def SubprocessCallAndFilter(command, # executable, but shell=True makes subprocess on Linux fail when it's called # with a list because it only tries to execute the first item in the list. kid = subprocess.Popen(command, bufsize=0, cwd=in_directory, - shell=(sys.platform == 'win32'), stdout=subprocess.PIPE, + shell=(sys.platform == 'win32'), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # Also, we need to forward stdout to prevent weird re-ordering of output. @@ -238,7 +238,7 @@ def SubprocessCallAndFilter(command, if not print_messages: print("\n________ running \'%s\' in \'%s\'" % (' '.join(command), in_directory)) - print_messages = True + print_messages = True sys.stdout.write(in_byte) if in_byte != "\n": in_line += in_byte diff --git a/git_cl_hooks.py b/git_cl_hooks.py index c63cbe268c..5203096f54 100644 --- a/git_cl_hooks.py +++ b/git_cl_hooks.py @@ -7,8 +7,9 @@ import re import subprocess import sys +# Imported from depot_tools. +import gclient_scm import presubmit_support -import scm def Backquote(cmd, cwd=None): """Like running `cmd` in a shell script.""" @@ -34,7 +35,7 @@ class ChangeOptions: raise Exception("Could not parse log message: %s" % log) name = m.group(1) description = m.group(2) - files = scm.GIT.CaptureStatus([root], upstream_branch) + files = gclient_scm.CaptureGitStatus([root], upstream_branch) issue = Backquote(['git', 'cl', 'status', '--field=id']) patchset = None self.change = presubmit_support.GitChange(name, description, root, files, diff --git a/presubmit_support.py b/presubmit_support.py index a68bdf0dd8..fabf7e946c 100755 --- a/presubmit_support.py +++ b/presubmit_support.py @@ -6,7 +6,7 @@ """Enables directory-specific presubmit checks to run at upload and/or commit. """ -__version__ = '1.3.4' +__version__ = '1.3.3' # TODO(joi) Add caching where appropriate/needed. The API is designed to allow # caching (between all different invocations of presubmit scripts for a given @@ -35,10 +35,11 @@ import urllib2 # Exposed through the API. import warnings # Local imports. +# TODO(joi) Would be cleaner to factor out utils in gcl to separate module, but +# for now it would only be a couple of functions so hardly worth it. import gcl -import gclient_utils +import gclient_scm import presubmit_canned_checks -import scm # Ask for feedback only once in program lifetime. @@ -240,7 +241,7 @@ class InputApi(object): Remember to check for the None case and show an appropriate error! """ - local_path = scm.SVN.CaptureInfo(depot_path).get('Path') + local_path = gclient_scm.CaptureSVNInfo(depot_path).get('Path') if local_path: return local_path @@ -253,7 +254,7 @@ class InputApi(object): Returns: The depot path (SVN URL) of the file if mapped, otherwise None. """ - depot_path = scm.SVN.CaptureInfo(local_path).get('URL') + depot_path = gclient_scm.CaptureSVNInfo(local_path).get('URL') if depot_path: return depot_path @@ -353,7 +354,7 @@ class InputApi(object): file_item = file_item.AbsoluteLocalPath() if not file_item.startswith(self.change.RepositoryRoot()): raise IOError('Access outside the repository root is denied.') - return gclient_utils.FileRead(file_item, mode) + return gcl.ReadFile(file_item, mode) @staticmethod def _RightHandSideLinesImpl(affected_files): @@ -431,8 +432,7 @@ class AffectedFile(object): if self.IsDirectory(): return [] else: - return gclient_utils.FileRead(self.AbsoluteLocalPath(), - 'rU').splitlines() + return gcl.ReadFile(self.AbsoluteLocalPath()).splitlines() def OldContents(self): """Returns an iterator over the lines in the old version of file. @@ -464,7 +464,7 @@ class SvnAffectedFile(AffectedFile): def ServerPath(self): if self._server_path is None: - self._server_path = scm.SVN.CaptureInfo( + self._server_path = gclient_scm.CaptureSVNInfo( self.AbsoluteLocalPath()).get('URL', '') return self._server_path @@ -476,13 +476,13 @@ class SvnAffectedFile(AffectedFile): # querying subversion, especially on Windows. self._is_directory = os.path.isdir(path) else: - self._is_directory = scm.SVN.CaptureInfo( + self._is_directory = gclient_scm.CaptureSVNInfo( path).get('Node Kind') in ('dir', 'directory') return self._is_directory def Property(self, property_name): if not property_name in self._properties: - self._properties[property_name] = scm.SVN.GetFileProperty( + self._properties[property_name] = gcl.GetSVNFileProperty( self.AbsoluteLocalPath(), property_name).rstrip() return self._properties[property_name] @@ -494,8 +494,8 @@ class SvnAffectedFile(AffectedFile): elif self.IsDirectory(): self._is_text_file = False else: - mime_type = scm.SVN.GetFileProperty(self.AbsoluteLocalPath(), - 'svn:mime-type') + mime_type = gcl.GetSVNFileProperty(self.AbsoluteLocalPath(), + 'svn:mime-type') self._is_text_file = (not mime_type or mime_type.startswith('text/')) return self._is_text_file @@ -809,7 +809,7 @@ def DoGetTrySlaves(changed_files, if verbose: output_stream.write("Running %s\n" % filename) # Accept CRLF presubmit script. - presubmit_script = gclient_utils.FileRead(filename, 'rU') + presubmit_script = gcl.ReadFile(filename, 'rU') results += executer.ExecPresubmitScript(presubmit_script) slaves = list(set(results)) @@ -925,7 +925,7 @@ def DoPresubmitChecks(change, if verbose: output_stream.write("Running %s\n" % filename) # Accept CRLF presubmit script. - presubmit_script = gclient_utils.FileRead(filename, 'rU') + presubmit_script = gcl.ReadFile(filename, 'rU') results += executer.ExecPresubmitScript(presubmit_script, filename) errors = [] @@ -1022,7 +1022,7 @@ def Main(argv): options.files = ParseFiles(args, options.recursive) else: # Grab modified files. - options.files = scm.GIT.CaptureStatus([options.root]) + options.files = gclient_scm.CaptureGitStatus([options.root]) elif os.path.isdir(os.path.join(options.root, '.svn')): change_class = SvnChange if not options.files: @@ -1030,7 +1030,7 @@ def Main(argv): options.files = ParseFiles(args, options.recursive) else: # Grab modified files. - options.files = scm.SVN.CaptureStatus([options.root]) + options.files = gclient_scm.CaptureSVNStatus([options.root]) else: # Doesn't seem under source control. change_class = Change diff --git a/revert.py b/revert.py index dc56c1f9c3..71016654f0 100755 --- a/revert.py +++ b/revert.py @@ -146,7 +146,7 @@ def Revert(revisions, force=False, commit=True, send_email=True, message=None, print "" # Make sure these files are unmodified with svn status. - status = gclient_scm.scm.SVN.CaptureStatus(files) + status = gclient_scm.CaptureSVNStatus(files) if status: if force: # TODO(maruel): Use the tool to correctly revert '?' files. diff --git a/scm.py b/scm.py index 9c12ba8361..12848152ab 100644 --- a/scm.py +++ b/scm.py @@ -2,415 +2,335 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -"""SCM-specific utility classes.""" +"""SCM-specific functions.""" import os import re import subprocess import sys -import tempfile import xml.dom.minidom import gclient_utils -class GIT(object): - COMMAND = "git" - - @staticmethod - def Capture(args, in_directory=None, print_error=True): - """Runs git, capturing output sent to stdout as a string. - - Args: - args: A sequence of command line parameters to be passed to git. - in_directory: The directory where git is to be run. - - Returns: - The output sent to stdout as a string. - """ - c = [GIT.COMMAND] - c.extend(args) - - # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for - # the git.exe executable, but shell=True makes subprocess on Linux fail - # when it's called with a list because it only tries to execute the - # first string ("git"). - stderr = None - if not print_error: - stderr = subprocess.PIPE - return subprocess.Popen(c, - cwd=in_directory, - shell=sys.platform.startswith('win'), - stdout=subprocess.PIPE, - stderr=stderr).communicate()[0] - - - @staticmethod - def CaptureStatus(files, upstream_branch='origin'): - """Returns git status. - - @files can be a string (one file) or a list of files. - - Returns an array of (status, file) tuples.""" - command = ["diff", "--name-status", "-r", "%s.." % upstream_branch] - if not files: - pass - elif isinstance(files, basestring): - command.append(files) - else: - command.extend(files) - - status = GIT.Capture(command).rstrip() - results = [] - if status: - for statusline in status.split('\n'): - m = re.match('^(\w)\t(.+)$', statusline) - if not m: - raise Exception("status currently unsupported: %s" % statusline) - results.append(('%s ' % m.group(1), m.group(2))) - return results - - -class SVN(object): - COMMAND = "svn" - - @staticmethod - def Run(args, in_directory): - """Runs svn, sending output to stdout. - - Args: - args: A sequence of command line parameters to be passed to svn. - in_directory: The directory where svn is to be run. - - Raises: - Error: An error occurred while running the svn command. - """ - c = [SVN.COMMAND] - c.extend(args) - - gclient_utils.SubprocessCall(c, in_directory) - - @staticmethod - def Capture(args, in_directory=None, print_error=True): - """Runs svn, capturing output sent to stdout as a string. - - Args: - args: A sequence of command line parameters to be passed to svn. - in_directory: The directory where svn is to be run. - - Returns: - The output sent to stdout as a string. - """ - c = [SVN.COMMAND] - c.extend(args) - - # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for - # the svn.exe executable, but shell=True makes subprocess on Linux fail - # when it's called with a list because it only tries to execute the - # first string ("svn"). - stderr = None - if not print_error: - stderr = subprocess.PIPE - return subprocess.Popen(c, - cwd=in_directory, - shell=(sys.platform == 'win32'), - stdout=subprocess.PIPE, - stderr=stderr).communicate()[0] - - @staticmethod - def RunAndGetFileList(options, args, in_directory, file_list): - """Runs svn checkout, update, or status, output to stdout. - - The first item in args must be either "checkout", "update", or "status". - - svn's stdout is parsed to collect a list of files checked out or updated. - These files are appended to file_list. svn's stdout is also printed to - sys.stdout as in Run. - - Args: - options: command line options to gclient - args: A sequence of command line parameters to be passed to svn. - in_directory: The directory where svn is to be run. - - Raises: - Error: An error occurred while running the svn command. - """ - command = [SVN.COMMAND] - command.extend(args) - - # svn update and svn checkout use the same pattern: the first three columns - # are for file status, property status, and lock status. This is followed - # by two spaces, and then the path to the file. - update_pattern = '^... (.*)$' - - # The first three columns of svn status are the same as for svn update and - # svn checkout. The next three columns indicate addition-with-history, - # switch, and remote lock status. This is followed by one space, and then - # the path to the file. - status_pattern = '^...... (.*)$' - - # args[0] must be a supported command. This will blow up if it's something - # else, which is good. Note that the patterns are only effective when - # these commands are used in their ordinary forms, the patterns are invalid - # for "svn status --show-updates", for example. - pattern = { - 'checkout': update_pattern, - 'status': status_pattern, - 'update': update_pattern, - }[args[0]] - - compiled_pattern = re.compile(pattern) - - def CaptureMatchingLines(line): - match = compiled_pattern.search(line) - if match: - file_list.append(match.group(1)) - - SVN.RunAndFilterOutput(args, - in_directory, - options.verbose, - True, - CaptureMatchingLines) - - @staticmethod - def RunAndFilterOutput(args, - in_directory, - print_messages, - print_stdout, - filter): - """Runs svn checkout, update, status, or diff, optionally outputting - to stdout. - - The first item in args must be either "checkout", "update", - "status", or "diff". - - svn's stdout is passed line-by-line to the given filter function. If - print_stdout is true, it is also printed to sys.stdout as in Run. - - Args: - args: A sequence of command line parameters to be passed to svn. - in_directory: The directory where svn is to be run. - print_messages: Whether to print status messages to stdout about - which Subversion commands are being run. - print_stdout: Whether to forward Subversion's output to stdout. - filter: A function taking one argument (a string) which will be - passed each line (with the ending newline character removed) of - Subversion's output for filtering. - - Raises: - Error: An error occurred while running the svn command. - """ - command = [SVN.COMMAND] - command.extend(args) - - gclient_utils.SubprocessCallAndFilter(command, - in_directory, - print_messages, - print_stdout, - filter=filter) - - @staticmethod - def CaptureInfo(relpath, in_directory=None, print_error=True): - """Returns a dictionary from the svn info output for the given file. - - Args: - relpath: The directory where the working copy resides relative to - the directory given by in_directory. - in_directory: The directory where svn is to be run. - """ - output = SVN.Capture(["info", "--xml", relpath], in_directory, print_error) - dom = gclient_utils.ParseXML(output) - result = {} - if dom: - GetNamedNodeText = gclient_utils.GetNamedNodeText - GetNodeNamedAttributeText = gclient_utils.GetNodeNamedAttributeText - def C(item, f): - if item is not None: return f(item) - # /info/entry/ - # url - # reposityory/(root|uuid) - # wc-info/(schedule|depth) - # commit/(author|date) - # str() the results because they may be returned as Unicode, which - # interferes with the higher layers matching up things in the deps - # dictionary. - # TODO(maruel): Fix at higher level instead (!) - result['Repository Root'] = C(GetNamedNodeText(dom, 'root'), str) - result['URL'] = C(GetNamedNodeText(dom, 'url'), str) - result['UUID'] = C(GetNamedNodeText(dom, 'uuid'), str) - result['Revision'] = C(GetNodeNamedAttributeText(dom, 'entry', - 'revision'), - int) - result['Node Kind'] = C(GetNodeNamedAttributeText(dom, 'entry', 'kind'), - str) - # Differs across versions. - if result['Node Kind'] == 'dir': - result['Node Kind'] = 'directory' - result['Schedule'] = C(GetNamedNodeText(dom, 'schedule'), str) - result['Path'] = C(GetNodeNamedAttributeText(dom, 'entry', 'path'), str) - result['Copied From URL'] = C(GetNamedNodeText(dom, 'copy-from-url'), str) - result['Copied From Rev'] = C(GetNamedNodeText(dom, 'copy-from-rev'), str) - return result - - @staticmethod - def CaptureHeadRevision(url): - """Get the head revision of a SVN repository. - - Returns: - Int head revision - """ - info = SVN.Capture(["info", "--xml", url], os.getcwd()) - dom = xml.dom.minidom.parseString(info) - return dom.getElementsByTagName('entry')[0].getAttribute('revision') - - @staticmethod - def CaptureStatus(files): - """Returns the svn 1.5 svn status emulated output. - - @files can be a string (one file) or a list of files. - - Returns an array of (status, file) tuples.""" - command = ["status", "--xml"] - if not files: - pass - elif isinstance(files, basestring): - command.append(files) - else: - command.extend(files) - - status_letter = { - None: ' ', - '': ' ', - 'added': 'A', - 'conflicted': 'C', - 'deleted': 'D', - 'external': 'X', - 'ignored': 'I', - 'incomplete': '!', - 'merged': 'G', - 'missing': '!', - 'modified': 'M', - 'none': ' ', - 'normal': ' ', - 'obstructed': '~', - 'replaced': 'R', - 'unversioned': '?', - } - dom = gclient_utils.ParseXML(SVN.Capture(command)) - results = [] - if dom: - # /status/target/entry/(wc-status|commit|author|date) - for target in dom.getElementsByTagName('target'): - #base_path = target.getAttribute('path') - for entry in target.getElementsByTagName('entry'): - file_path = entry.getAttribute('path') - wc_status = entry.getElementsByTagName('wc-status') - assert len(wc_status) == 1 - # Emulate svn 1.5 status ouput... - statuses = [' '] * 7 - # Col 0 - xml_item_status = wc_status[0].getAttribute('item') - if xml_item_status in status_letter: - statuses[0] = status_letter[xml_item_status] - else: - raise Exception('Unknown item status "%s"; please implement me!' % - xml_item_status) - # Col 1 - xml_props_status = wc_status[0].getAttribute('props') - if xml_props_status == 'modified': - statuses[1] = 'M' - elif xml_props_status == 'conflicted': - statuses[1] = 'C' - elif (not xml_props_status or xml_props_status == 'none' or - xml_props_status == 'normal'): - pass - else: - raise Exception('Unknown props status "%s"; please implement me!' % - xml_props_status) - # Col 2 - if wc_status[0].getAttribute('wc-locked') == 'true': - statuses[2] = 'L' - # Col 3 - if wc_status[0].getAttribute('copied') == 'true': - statuses[3] = '+' - # Col 4 - if wc_status[0].getAttribute('switched') == 'true': - statuses[4] = 'S' - # TODO(maruel): Col 5 and 6 - item = (''.join(statuses), file_path) - results.append(item) - return results - - @staticmethod - def IsMoved(filename): - """Determine if a file has been added through svn mv""" - info = SVN.CaptureInfo(filename) - return (info.get('Copied From URL') and - info.get('Copied From Rev') and - info.get('Schedule') == 'add') - - @staticmethod - def GetFileProperty(file, property_name): - """Returns the value of an SVN property for the given file. - - Args: - file: The file to check - property_name: The name of the SVN property, e.g. "svn:mime-type" - - Returns: - The value of the property, which will be the empty string if the property - is not set on the file. If the file is not under version control, the - empty string is also returned. - """ - output = SVN.Run(["propget", property_name, file], None) - if (output and - output.startswith("svn: ") and - output.endswith("is not under version control")): - return "" - else: - return output - - @staticmethod - def DiffItem(filename): - """Diff a single file""" - # Use svn info output instead of os.path.isdir because the latter fails - # when the file is deleted. - if SVN.CaptureInfo(filename).get("Node Kind") == "directory": - return None - # If the user specified a custom diff command in their svn config file, - # then it'll be used when we do svn diff, which we don't want to happen - # since we want the unified diff. Using --diff-cmd=diff doesn't always - # work, since they can have another diff executable in their path that - # gives different line endings. So we use a bogus temp directory as the - # config directory, which gets around these problems. - if sys.platform.startswith("win"): - parent_dir = tempfile.gettempdir() - else: - parent_dir = sys.path[0] # tempdir is not secure. - bogus_dir = os.path.join(parent_dir, "temp_svn_config") - if not os.path.exists(bogus_dir): - os.mkdir(bogus_dir) - # Grabs the diff data. - data = SVN.Capture(["diff", "--config-dir", bogus_dir, filename], None) - - # We know the diff will be incorrectly formatted. Fix it. - if SVN.IsMoved(filename): - # The file is "new" in the patch sense. Generate a homebrew diff. - # We can't use ReadFile() since it's not using binary mode. - file_handle = open(filename, 'rb') - file_content = file_handle.read() - file_handle.close() - # Prepend '+' to every lines. - file_content = ['+' + i for i in file_content.splitlines(True)] - nb_lines = len(file_content) - # We need to use / since patch on unix will fail otherwise. - filename = filename.replace('\\', '/') - data = "Index: %s\n" % filename - data += ("=============================================================" - "======\n") - # Note: Should we use /dev/null instead? - data += "--- %s\n" % filename - data += "+++ %s\n" % filename - data += "@@ -0,0 +1,%d @@\n" % nb_lines - data += ''.join(file_content) - return data +SVN_COMMAND = "svn" +GIT_COMMAND = "git" + +# ----------------------------------------------------------------------------- +# Git utils: + + +def CaptureGit(args, in_directory=None, print_error=True): + """Runs git, capturing output sent to stdout as a string. + + Args: + args: A sequence of command line parameters to be passed to git. + in_directory: The directory where git is to be run. + + Returns: + The output sent to stdout as a string. + """ + c = [GIT_COMMAND] + c.extend(args) + + # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for + # the git.exe executable, but shell=True makes subprocess on Linux fail + # when it's called with a list because it only tries to execute the + # first string ("git"). + stderr = None + if not print_error: + stderr = subprocess.PIPE + return subprocess.Popen(c, + cwd=in_directory, + shell=sys.platform.startswith('win'), + stdout=subprocess.PIPE, + stderr=stderr).communicate()[0] + + +def CaptureGitStatus(files, upstream_branch='origin'): + """Returns git status. + + @files can be a string (one file) or a list of files. + + Returns an array of (status, file) tuples.""" + command = ["diff", "--name-status", "-r", "%s.." % upstream_branch] + if not files: + pass + elif isinstance(files, basestring): + command.append(files) + else: + command.extend(files) + + status = CaptureGit(command).rstrip() + results = [] + if status: + for statusline in status.split('\n'): + m = re.match('^(\w)\t(.+)$', statusline) + if not m: + raise Exception("status currently unsupported: %s" % statusline) + results.append(('%s ' % m.group(1), m.group(2))) + return results + + +# ----------------------------------------------------------------------------- +# SVN utils: + + +def RunSVN(args, in_directory): + """Runs svn, sending output to stdout. + + Args: + args: A sequence of command line parameters to be passed to svn. + in_directory: The directory where svn is to be run. + + Raises: + Error: An error occurred while running the svn command. + """ + c = [SVN_COMMAND] + c.extend(args) + + gclient_utils.SubprocessCall(c, in_directory) + + +def CaptureSVN(args, in_directory=None, print_error=True): + """Runs svn, capturing output sent to stdout as a string. + + Args: + args: A sequence of command line parameters to be passed to svn. + in_directory: The directory where svn is to be run. + + Returns: + The output sent to stdout as a string. + """ + c = [SVN_COMMAND] + c.extend(args) + + # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for + # the svn.exe executable, but shell=True makes subprocess on Linux fail + # when it's called with a list because it only tries to execute the + # first string ("svn"). + stderr = None + if not print_error: + stderr = subprocess.PIPE + return subprocess.Popen(c, + cwd=in_directory, + shell=(sys.platform == 'win32'), + stdout=subprocess.PIPE, + stderr=stderr).communicate()[0] + + +def RunSVNAndGetFileList(options, args, in_directory, file_list): + """Runs svn checkout, update, or status, output to stdout. + + The first item in args must be either "checkout", "update", or "status". + + svn's stdout is parsed to collect a list of files checked out or updated. + These files are appended to file_list. svn's stdout is also printed to + sys.stdout as in RunSVN. + + Args: + options: command line options to gclient + args: A sequence of command line parameters to be passed to svn. + in_directory: The directory where svn is to be run. + + Raises: + Error: An error occurred while running the svn command. + """ + command = [SVN_COMMAND] + command.extend(args) + + # svn update and svn checkout use the same pattern: the first three columns + # are for file status, property status, and lock status. This is followed + # by two spaces, and then the path to the file. + update_pattern = '^... (.*)$' + + # The first three columns of svn status are the same as for svn update and + # svn checkout. The next three columns indicate addition-with-history, + # switch, and remote lock status. This is followed by one space, and then + # the path to the file. + status_pattern = '^...... (.*)$' + + # args[0] must be a supported command. This will blow up if it's something + # else, which is good. Note that the patterns are only effective when + # these commands are used in their ordinary forms, the patterns are invalid + # for "svn status --show-updates", for example. + pattern = { + 'checkout': update_pattern, + 'status': status_pattern, + 'update': update_pattern, + }[args[0]] + + compiled_pattern = re.compile(pattern) + + def CaptureMatchingLines(line): + match = compiled_pattern.search(line) + if match: + file_list.append(match.group(1)) + + RunSVNAndFilterOutput(args, + in_directory, + options.verbose, + True, + CaptureMatchingLines) + +def RunSVNAndFilterOutput(args, + in_directory, + print_messages, + print_stdout, + filter): + """Runs svn checkout, update, status, or diff, optionally outputting + to stdout. + + The first item in args must be either "checkout", "update", + "status", or "diff". + + svn's stdout is passed line-by-line to the given filter function. If + print_stdout is true, it is also printed to sys.stdout as in RunSVN. + + Args: + args: A sequence of command line parameters to be passed to svn. + in_directory: The directory where svn is to be run. + print_messages: Whether to print status messages to stdout about + which Subversion commands are being run. + print_stdout: Whether to forward Subversion's output to stdout. + filter: A function taking one argument (a string) which will be + passed each line (with the ending newline character removed) of + Subversion's output for filtering. + + Raises: + Error: An error occurred while running the svn command. + """ + command = [SVN_COMMAND] + command.extend(args) + + gclient_utils.SubprocessCallAndFilter(command, + in_directory, + print_messages, + print_stdout, + filter=filter) + +def CaptureSVNInfo(relpath, in_directory=None, print_error=True): + """Returns a dictionary from the svn info output for the given file. + + Args: + relpath: The directory where the working copy resides relative to + the directory given by in_directory. + in_directory: The directory where svn is to be run. + """ + output = CaptureSVN(["info", "--xml", relpath], in_directory, print_error) + dom = gclient_utils.ParseXML(output) + result = {} + if dom: + GetNamedNodeText = gclient_utils.GetNamedNodeText + GetNodeNamedAttributeText = gclient_utils.GetNodeNamedAttributeText + def C(item, f): + if item is not None: return f(item) + # /info/entry/ + # url + # reposityory/(root|uuid) + # wc-info/(schedule|depth) + # commit/(author|date) + # str() the results because they may be returned as Unicode, which + # interferes with the higher layers matching up things in the deps + # dictionary. + # TODO(maruel): Fix at higher level instead (!) + result['Repository Root'] = C(GetNamedNodeText(dom, 'root'), str) + result['URL'] = C(GetNamedNodeText(dom, 'url'), str) + result['UUID'] = C(GetNamedNodeText(dom, 'uuid'), str) + result['Revision'] = C(GetNodeNamedAttributeText(dom, 'entry', 'revision'), + int) + result['Node Kind'] = C(GetNodeNamedAttributeText(dom, 'entry', 'kind'), + str) + result['Schedule'] = C(GetNamedNodeText(dom, 'schedule'), str) + result['Path'] = C(GetNodeNamedAttributeText(dom, 'entry', 'path'), str) + result['Copied From URL'] = C(GetNamedNodeText(dom, 'copy-from-url'), str) + result['Copied From Rev'] = C(GetNamedNodeText(dom, 'copy-from-rev'), str) + return result + + +def CaptureSVNHeadRevision(url): + """Get the head revision of a SVN repository. + + Returns: + Int head revision + """ + info = CaptureSVN(["info", "--xml", url], os.getcwd()) + dom = xml.dom.minidom.parseString(info) + return dom.getElementsByTagName('entry')[0].getAttribute('revision') + + +def CaptureSVNStatus(files): + """Returns the svn 1.5 svn status emulated output. + + @files can be a string (one file) or a list of files. + + Returns an array of (status, file) tuples.""" + command = ["status", "--xml"] + if not files: + pass + elif isinstance(files, basestring): + command.append(files) + else: + command.extend(files) + + status_letter = { + None: ' ', + '': ' ', + 'added': 'A', + 'conflicted': 'C', + 'deleted': 'D', + 'external': 'X', + 'ignored': 'I', + 'incomplete': '!', + 'merged': 'G', + 'missing': '!', + 'modified': 'M', + 'none': ' ', + 'normal': ' ', + 'obstructed': '~', + 'replaced': 'R', + 'unversioned': '?', + } + dom = gclient_utils.ParseXML(CaptureSVN(command)) + results = [] + if dom: + # /status/target/entry/(wc-status|commit|author|date) + for target in dom.getElementsByTagName('target'): + for entry in target.getElementsByTagName('entry'): + file_path = entry.getAttribute('path') + wc_status = entry.getElementsByTagName('wc-status') + assert len(wc_status) == 1 + # Emulate svn 1.5 status ouput... + statuses = [' '] * 7 + # Col 0 + xml_item_status = wc_status[0].getAttribute('item') + if xml_item_status in status_letter: + statuses[0] = status_letter[xml_item_status] + else: + raise Exception('Unknown item status "%s"; please implement me!' % + xml_item_status) + # Col 1 + xml_props_status = wc_status[0].getAttribute('props') + if xml_props_status == 'modified': + statuses[1] = 'M' + elif xml_props_status == 'conflicted': + statuses[1] = 'C' + elif (not xml_props_status or xml_props_status == 'none' or + xml_props_status == 'normal'): + pass + else: + raise Exception('Unknown props status "%s"; please implement me!' % + xml_props_status) + # Col 2 + if wc_status[0].getAttribute('wc-locked') == 'true': + statuses[2] = 'L' + # Col 3 + if wc_status[0].getAttribute('copied') == 'true': + statuses[3] = '+' + # Col 4 + if wc_status[0].getAttribute('switched') == 'true': + statuses[4] = 'S' + # TODO(maruel): Col 5 and 6 + item = (''.join(statuses), file_path) + results.append(item) + return results diff --git a/tests/gcl_unittest.py b/tests/gcl_unittest.py index a3c5524fa7..739840bb71 100755 --- a/tests/gcl_unittest.py +++ b/tests/gcl_unittest.py @@ -16,7 +16,7 @@ class GclTestsBase(SuperMoxTestBase): SuperMoxTestBase.setUp(self) self.fake_root_dir = self.RootDir() self.mox.StubOutWithMock(gcl, 'RunShell') - self.mox.StubOutWithMock(gcl.SVN, 'CaptureInfo') + self.mox.StubOutWithMock(gcl.gclient_scm, 'CaptureSVNInfo') self.mox.StubOutWithMock(gcl, 'tempfile') self.mox.StubOutWithMock(gcl.upload, 'RealMain') # These are not tested. @@ -29,21 +29,25 @@ class GclUnittest(GclTestsBase): def testMembersChanged(self): self.mox.ReplayAll() members = [ - 'CODEREVIEW_SETTINGS', 'CODEREVIEW_SETTINGS_FILE', 'Change', - 'ChangeInfo', 'Changes', 'Commit', 'DEFAULT_LINT_IGNORE_REGEX', - 'DEFAULT_LINT_REGEX', 'DeleteEmptyChangeLists', 'DoPresubmitChecks', - 'ErrorExit', 'FILES_CACHE', 'FilterFlag', 'GenerateChangeName', - 'GenerateDiff', 'GetCLs', 'GetCacheDir', 'GetCachedFile', - 'GetChangelistInfoFile', 'GetChangesDir', 'GetCodeReviewSetting', - 'GetEditor', 'GetFilesNotInCL', 'GetInfoDir', 'GetIssueDescription', - 'GetModifiedFiles', 'GetRepositoryRoot', 'Help', 'Lint', - 'LoadChangelistInfoForMultiple', 'MISSING_TEST_MSG', 'Opened', - 'OptionallyDoPresubmitChecks', 'PresubmitCL', 'REPOSITORY_ROOT', - 'ReadFile', 'RunShell', 'RunShellWithReturnCode', 'SVN', - 'SendToRietveld', 'TryChange', 'UnknownFiles', 'UploadCL', 'Warn', - 'WriteFile', 'gclient_utils', 'getpass', 'main', 'os', 'random', 're', - 'shutil', 'string', 'subprocess', 'sys', 'tempfile', 'upload', - 'urllib2', + 'CODEREVIEW_SETTINGS', 'CODEREVIEW_SETTINGS_FILE', + 'Change', 'ChangeInfo', 'Changes', 'Commit', + 'DEFAULT_LINT_IGNORE_REGEX', 'DEFAULT_LINT_REGEX', + 'DeleteEmptyChangeLists', 'DoPresubmitChecks', + 'ErrorExit', 'FILES_CACHE', 'FilterFlag', 'GenerateChangeName', + 'GenerateDiff', + 'GetCacheDir', 'GetCachedFile', 'GetChangesDir', 'GetCLs', + 'GetChangelistInfoFile', 'GetCodeReviewSetting', 'GetEditor', + 'GetFilesNotInCL', 'GetInfoDir', 'GetIssueDescription', + 'GetModifiedFiles', 'GetRepositoryRoot', + 'GetSVNFileProperty', 'Help', 'IsSVNMoved', + 'Lint', 'LoadChangelistInfoForMultiple', + 'MISSING_TEST_MSG', 'Opened', 'OptionallyDoPresubmitChecks', + 'PresubmitCL', 'ReadFile', 'REPOSITORY_ROOT', 'RunShell', + 'RunShellWithReturnCode', 'SendToRietveld', 'TryChange', + 'UnknownFiles', 'UploadCL', 'Warn', 'WriteFile', + 'gclient_scm', 'gclient_utils', 'getpass', 'main', 'os', 'random', 're', + 'shutil', 'string', 'subprocess', 'sys', 'tempfile', + 'upload', 'urllib2', ] # If this test fails, you should add the relevant test. self.compareMembers(gcl, members) @@ -66,7 +70,8 @@ class GclUnittest(GclTestsBase): result = { "Repository Root": "" } - gcl.SVN.CaptureInfo("/bleh/prout", print_error=False).AndReturn(result) + gcl.gclient_scm.CaptureSVNInfo("/bleh/prout", print_error=False).AndReturn( + result) self.mox.ReplayAll() self.assertRaises(Exception, gcl.GetRepositoryRoot) @@ -75,11 +80,13 @@ class GclUnittest(GclTestsBase): root_path = gcl.os.path.join('bleh', 'prout', 'pouet') gcl.os.getcwd().AndReturn(root_path) result1 = { "Repository Root": "Some root" } - gcl.SVN.CaptureInfo(root_path, print_error=False).AndReturn(result1) + gcl.gclient_scm.CaptureSVNInfo(root_path, + print_error=False).AndReturn(result1) gcl.os.getcwd().AndReturn(root_path) results2 = { "Repository Root": "A different root" } - gcl.SVN.CaptureInfo(gcl.os.path.dirname(root_path), - print_error=False).AndReturn(results2) + gcl.gclient_scm.CaptureSVNInfo( + gcl.os.path.dirname(root_path), + print_error=False).AndReturn(results2) self.mox.ReplayAll() self.assertEquals(gcl.GetRepositoryRoot(), root_path) diff --git a/tests/gclient_scm_test.py b/tests/gclient_scm_test.py index e62711a975..11a2cd933c 100755 --- a/tests/gclient_scm_test.py +++ b/tests/gclient_scm_test.py @@ -33,12 +33,12 @@ class BaseTestCase(GCBaseTestCase): self.mox.StubOutWithMock(gclient_scm.gclient_utils, 'FileWrite') self.mox.StubOutWithMock(gclient_scm.gclient_utils, 'SubprocessCall') self.mox.StubOutWithMock(gclient_scm.gclient_utils, 'RemoveDirectory') - self._CaptureSVNInfo = gclient_scm.scm.SVN.CaptureInfo - self.mox.StubOutWithMock(gclient_scm.scm.SVN, 'Capture') - self.mox.StubOutWithMock(gclient_scm.scm.SVN, 'CaptureInfo') - self.mox.StubOutWithMock(gclient_scm.scm.SVN, 'CaptureStatus') - self.mox.StubOutWithMock(gclient_scm.scm.SVN, 'Run') - self.mox.StubOutWithMock(gclient_scm.scm.SVN, 'RunAndGetFileList') + self._CaptureSVNInfo = gclient_scm.CaptureSVNInfo + self.mox.StubOutWithMock(gclient_scm, 'CaptureSVN') + self.mox.StubOutWithMock(gclient_scm, 'CaptureSVNInfo') + self.mox.StubOutWithMock(gclient_scm, 'CaptureSVNStatus') + self.mox.StubOutWithMock(gclient_scm, 'RunSVN') + self.mox.StubOutWithMock(gclient_scm, 'RunSVNAndGetFileList') self._scm_wrapper = gclient_scm.CreateSCM @@ -64,11 +64,9 @@ class SVNWrapperTestCase(BaseTestCase): def testDir(self): members = [ - 'COMMAND', 'Capture', 'CaptureHeadRevision', 'CaptureInfo', - 'CaptureStatus', 'DiffItem', 'FullUrlForRelativeUrl', 'GetFileProperty', - 'IsMoved', 'Run', 'RunAndFilterOutput', 'RunAndGetFileList', - 'RunCommand', 'cleanup', 'diff', 'export', 'pack', 'relpath', 'revert', - 'revinfo', 'runhooks', 'scm_name', 'status', 'update', 'url', + 'FullUrlForRelativeUrl', 'RunCommand', 'cleanup', 'diff', 'export', + 'pack', 'relpath', 'revert', 'revinfo', 'runhooks', 'scm_name', 'status', + 'update', 'url', ] # If you add a member, be sure to add the relevant test! @@ -115,9 +113,8 @@ class SVNWrapperTestCase(BaseTestCase): # Checkout. gclient_scm.os.path.exists(base_path).AndReturn(False) files_list = self.mox.CreateMockAnything() - gclient_scm.scm.SVN.RunAndGetFileList(options, - ['checkout', self.url, base_path], - self.root_dir, files_list) + gclient_scm.RunSVNAndGetFileList(options, ['checkout', self.url, base_path], + self.root_dir, files_list) self.mox.ReplayAll() scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir, @@ -128,10 +125,9 @@ class SVNWrapperTestCase(BaseTestCase): options = self.Options(verbose=True) base_path = gclient_scm.os.path.join(self.root_dir, self.relpath) gclient_scm.os.path.isdir(base_path).AndReturn(True) - gclient_scm.scm.SVN.CaptureStatus(base_path).AndReturn([]) - gclient_scm.scm.SVN.RunAndGetFileList(options, - ['update', '--revision', 'BASE'], - base_path, mox.IgnoreArg()) + gclient_scm.CaptureSVNStatus(base_path).AndReturn([]) + gclient_scm.RunSVNAndGetFileList(options, ['update', '--revision', 'BASE'], + base_path, mox.IgnoreArg()) self.mox.ReplayAll() scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir, @@ -149,16 +145,15 @@ class SVNWrapperTestCase(BaseTestCase): ] file_path1 = gclient_scm.os.path.join(base_path, 'a') file_path2 = gclient_scm.os.path.join(base_path, 'b') - gclient_scm.scm.SVN.CaptureStatus(base_path).AndReturn(items) + gclient_scm.CaptureSVNStatus(base_path).AndReturn(items) gclient_scm.os.path.exists(file_path1).AndReturn(True) gclient_scm.os.path.isfile(file_path1).AndReturn(True) gclient_scm.os.remove(file_path1) gclient_scm.os.path.exists(file_path2).AndReturn(True) gclient_scm.os.path.isfile(file_path2).AndReturn(True) gclient_scm.os.remove(file_path2) - gclient_scm.scm.SVN.RunAndGetFileList(options, - ['update', '--revision', 'BASE'], - base_path, mox.IgnoreArg()) + gclient_scm.RunSVNAndGetFileList(options, ['update', '--revision', 'BASE'], + base_path, mox.IgnoreArg()) print(gclient_scm.os.path.join(base_path, 'a')) print(gclient_scm.os.path.join(base_path, 'b')) @@ -175,7 +170,7 @@ class SVNWrapperTestCase(BaseTestCase): items = [ ('~ ', 'a'), ] - gclient_scm.scm.SVN.CaptureStatus(base_path).AndReturn(items) + gclient_scm.CaptureSVNStatus(base_path).AndReturn(items) file_path = gclient_scm.os.path.join(base_path, 'a') print(file_path) gclient_scm.os.path.exists(file_path).AndReturn(True) @@ -183,9 +178,8 @@ class SVNWrapperTestCase(BaseTestCase): gclient_scm.os.path.isdir(file_path).AndReturn(True) gclient_scm.gclient_utils.RemoveDirectory(file_path) file_list1 = [] - gclient_scm.scm.SVN.RunAndGetFileList(options, - ['update', '--revision', 'BASE'], - base_path, mox.IgnoreArg()) + gclient_scm.RunSVNAndGetFileList(options, ['update', '--revision', 'BASE'], + base_path, mox.IgnoreArg()) self.mox.ReplayAll() scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir, @@ -197,9 +191,8 @@ class SVNWrapperTestCase(BaseTestCase): options = self.Options(verbose=True) base_path = gclient_scm.os.path.join(self.root_dir, self.relpath) gclient_scm.os.path.isdir(base_path).AndReturn(True) - gclient_scm.scm.SVN.RunAndGetFileList(options, - ['status'] + self.args, - base_path, []).AndReturn(None) + gclient_scm.RunSVNAndGetFileList(options, ['status'] + self.args, + base_path, []).AndReturn(None) self.mox.ReplayAll() scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir, @@ -223,9 +216,8 @@ class SVNWrapperTestCase(BaseTestCase): # Checkout. gclient_scm.os.path.exists(base_path).AndReturn(False) files_list = self.mox.CreateMockAnything() - gclient_scm.scm.SVN.RunAndGetFileList(options, - ['checkout', self.url, base_path], - self.root_dir, files_list) + gclient_scm.RunSVNAndGetFileList(options, ['checkout', self.url, + base_path], self.root_dir, files_list) self.mox.ReplayAll() scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir, relpath=self.relpath) @@ -246,19 +238,17 @@ class SVNWrapperTestCase(BaseTestCase): ).AndReturn(False) # Checkout or update. gclient_scm.os.path.exists(base_path).AndReturn(True) - gclient_scm.scm.SVN.CaptureInfo( - gclient_scm.os.path.join(base_path, "."), '.' - ).AndReturn(file_info) + gclient_scm.CaptureSVNInfo(gclient_scm.os.path.join(base_path, "."), '.' + ).AndReturn(file_info) # Cheat a bit here. - gclient_scm.scm.SVN.CaptureInfo(file_info['URL'], '.').AndReturn(file_info) + gclient_scm.CaptureSVNInfo(file_info['URL'], '.').AndReturn(file_info) additional_args = [] if options.manually_grab_svn_rev: additional_args = ['--revision', str(file_info['Revision'])] files_list = [] - gclient_scm.scm.SVN.RunAndGetFileList( - options, - ['update', base_path] + additional_args, - self.root_dir, files_list) + gclient_scm.RunSVNAndGetFileList(options, + ['update', base_path] + additional_args, + self.root_dir, files_list) self.mox.ReplayAll() scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir, @@ -366,9 +356,9 @@ from :3 def testDir(self): members = [ - 'COMMAND', 'Capture', 'CaptureStatus', 'FullUrlForRelativeUrl', - 'RunCommand', 'cleanup', 'diff', 'export', 'relpath', 'revert', - 'revinfo', 'runhooks', 'scm_name', 'status', 'update', 'url', + 'FullUrlForRelativeUrl', 'RunCommand', 'cleanup', 'diff', 'export', + 'relpath', 'revert', 'revinfo', 'runhooks', 'scm_name', 'status', + 'update', 'url', ] # If you add a member, be sure to add the relevant test! diff --git a/tests/gclient_test.py b/tests/gclient_test.py index e21c20ae7d..dc3dff4a5c 100755 --- a/tests/gclient_test.py +++ b/tests/gclient_test.py @@ -22,6 +22,8 @@ import __builtin__ import StringIO import gclient +# Temporary due to the "from scm import *" in gclient_scm. +import scm from super_mox import mox, IsOneOf, SuperMoxTestBase @@ -48,11 +50,16 @@ class GClientBaseTestCase(BaseTestCase): self.mox.StubOutWithMock(gclient.gclient_utils, 'SubprocessCall') self.mox.StubOutWithMock(gclient.gclient_utils, 'RemoveDirectory') # Mock them to be sure nothing bad happens. - self.mox.StubOutWithMock(gclient.gclient_scm.scm.SVN, 'Capture') - self.mox.StubOutWithMock(gclient.gclient_scm.scm.SVN, 'CaptureInfo') - self.mox.StubOutWithMock(gclient.gclient_scm.scm.SVN, 'CaptureStatus') - self.mox.StubOutWithMock(gclient.gclient_scm.scm.SVN, 'Run') - self.mox.StubOutWithMock(gclient.gclient_scm.scm.SVN, 'RunAndGetFileList') + self.mox.StubOutWithMock(gclient.gclient_scm, 'CaptureSVN') + self.mox.StubOutWithMock(gclient.gclient_scm, 'CaptureSVNInfo') + self.mox.StubOutWithMock(gclient.gclient_scm, 'CaptureSVNStatus') + self.mox.StubOutWithMock(gclient.gclient_scm, 'RunSVN') + self.mox.StubOutWithMock(gclient.gclient_scm, 'RunSVNAndGetFileList') + self.mox.StubOutWithMock(scm, 'CaptureSVN') + self.mox.StubOutWithMock(scm, 'CaptureSVNInfo') + self.mox.StubOutWithMock(scm, 'CaptureSVNStatus') + self.mox.StubOutWithMock(scm, 'RunSVN') + self.mox.StubOutWithMock(scm, 'RunSVNAndGetFileList') self._gclient_gclient = gclient.GClient gclient.GClient = self.mox.CreateMockAnything() self._scm_wrapper = gclient.gclient_scm.CreateSCM diff --git a/tests/presubmit_unittest.py b/tests/presubmit_unittest.py index b1fbbd4770..3149c9738d 100755 --- a/tests/presubmit_unittest.py +++ b/tests/presubmit_unittest.py @@ -7,9 +7,9 @@ import StringIO +# Local imports import presubmit_support as presubmit -# Shortcut. -from presubmit_support import presubmit_canned_checks +import presubmit_canned_checks from super_mox import mox, SuperMoxTestBase @@ -47,11 +47,9 @@ def GetPreferredTrySlaves(): presubmit.os.path.abspath = MockAbsPath presubmit.os.getcwd = self.RootDir presubmit.os.chdir = MockChdir - self.mox.StubOutWithMock(presubmit.scm.SVN, 'CaptureInfo') - self.mox.StubOutWithMock(presubmit.scm.SVN, 'GetFileProperty') - # TODO(maruel): Err, small duplication of code here. + self.mox.StubOutWithMock(presubmit.gclient_scm, 'CaptureSVNInfo') + self.mox.StubOutWithMock(presubmit.gcl, 'GetSVNFileProperty') self.mox.StubOutWithMock(presubmit.gcl, 'ReadFile') - self.mox.StubOutWithMock(presubmit.gclient_utils, 'FileRead') class PresubmitUnittest(PresubmitTestsBase): @@ -65,9 +63,9 @@ class PresubmitUnittest(PresubmitTestsBase): 'NotImplementedException', 'OutputApi', 'ParseFiles', 'PresubmitExecuter', 'PromptYesNo', 'ScanSubDirs', 'SvnAffectedFile', 'SvnChange', 'cPickle', 'cStringIO', - 'exceptions', 'fnmatch', 'gcl', 'gclient_utils', 'glob', + 'exceptions', 'fnmatch', 'gcl', 'gclient_scm', 'glob', 'logging', 'marshal', 'normpath', 'optparse', 'os', 'pickle', - 'presubmit_canned_checks', 'random', 're', 'scm', 'subprocess', 'sys', + 'presubmit_canned_checks', 'random', 're', 'subprocess', 'sys', 'tempfile', 'time', 'traceback', 'types', 'unittest', 'urllib2', 'warnings', ] @@ -142,22 +140,22 @@ class PresubmitUnittest(PresubmitTestsBase): presubmit.os.path.exists(notfound).AndReturn(True) presubmit.os.path.isdir(notfound).AndReturn(False) presubmit.os.path.exists(flap).AndReturn(False) - presubmit.scm.SVN.CaptureInfo(flap + presubmit.gclient_scm.CaptureSVNInfo(flap ).AndReturn({'Node Kind': 'file'}) - presubmit.scm.SVN.GetFileProperty(blat, 'svn:mime-type').AndReturn(None) - presubmit.scm.SVN.GetFileProperty( + presubmit.gcl.GetSVNFileProperty(blat, 'svn:mime-type').AndReturn(None) + presubmit.gcl.GetSVNFileProperty( binary, 'svn:mime-type').AndReturn('application/octet-stream') - presubmit.scm.SVN.GetFileProperty( + presubmit.gcl.GetSVNFileProperty( notfound, 'svn:mime-type').AndReturn('') - presubmit.scm.SVN.CaptureInfo(blat).AndReturn( + presubmit.gclient_scm.CaptureSVNInfo(blat).AndReturn( {'URL': 'svn:/foo/foo/blat.cc'}) - presubmit.scm.SVN.CaptureInfo(binary).AndReturn( + presubmit.gclient_scm.CaptureSVNInfo(binary).AndReturn( {'URL': 'svn:/foo/binary.dll'}) - presubmit.scm.SVN.CaptureInfo(notfound).AndReturn({}) - presubmit.scm.SVN.CaptureInfo(flap).AndReturn( + presubmit.gclient_scm.CaptureSVNInfo(notfound).AndReturn({}) + presubmit.gclient_scm.CaptureSVNInfo(flap).AndReturn( {'URL': 'svn:/foo/boo/flap.h'}) - presubmit.gclient_utils.FileRead(blat, 'rU').AndReturn('boo!\nahh?') - presubmit.gclient_utils.FileRead(notfound, 'rU').AndReturn('look!\nthere?') + presubmit.gcl.ReadFile(blat).AndReturn('boo!\nahh?') + presubmit.gcl.ReadFile(notfound).AndReturn('look!\nthere?') self.mox.ReplayAll() change = presubmit.SvnChange('mychange', '\n'.join(description_lines), @@ -287,10 +285,10 @@ class PresubmitUnittest(PresubmitTestsBase): root_path = join(self.fake_root_dir, 'PRESUBMIT.py') presubmit.os.path.isfile(root_path).AndReturn(True) presubmit.os.path.isfile(haspresubmit_path).AndReturn(True) - presubmit.gclient_utils.FileRead(root_path, - 'rU').AndReturn(self.presubmit_text) - presubmit.gclient_utils.FileRead(haspresubmit_path, - 'rU').AndReturn(self.presubmit_text) + presubmit.gcl.ReadFile(root_path, + 'rU').AndReturn(self.presubmit_text) + presubmit.gcl.ReadFile(haspresubmit_path, + 'rU').AndReturn(self.presubmit_text) presubmit.random.randint(0, 4).AndReturn(1) self.mox.ReplayAll() @@ -315,9 +313,9 @@ class PresubmitUnittest(PresubmitTestsBase): for i in range(2): presubmit.os.path.isfile(presubmit_path).AndReturn(True) presubmit.os.path.isfile(haspresubmit_path).AndReturn(True) - presubmit.gclient_utils.FileRead(presubmit_path, 'rU' + presubmit.gcl.ReadFile(presubmit_path, 'rU' ).AndReturn(self.presubmit_text) - presubmit.gclient_utils.FileRead(haspresubmit_path, 'rU' + presubmit.gcl.ReadFile(haspresubmit_path, 'rU' ).AndReturn(self.presubmit_text) presubmit.random.randint(0, 4).AndReturn(1) presubmit.random.randint(0, 4).AndReturn(1) @@ -351,9 +349,8 @@ class PresubmitUnittest(PresubmitTestsBase): 'PRESUBMIT.py') presubmit.os.path.isfile(presubmit_path).AndReturn(True) presubmit.os.path.isfile(haspresubmit_path).AndReturn(True) - presubmit.gclient_utils.FileRead(presubmit_path, 'rU' - ).AndReturn(self.presubmit_text) - presubmit.gclient_utils.FileRead(haspresubmit_path, 'rU').AndReturn( + presubmit.gcl.ReadFile(presubmit_path, 'rU').AndReturn(self.presubmit_text) + presubmit.gcl.ReadFile(haspresubmit_path, 'rU').AndReturn( self.presubmit_text) presubmit.random.randint(0, 4).AndReturn(1) self.mox.ReplayAll() @@ -505,14 +502,14 @@ def CheckChangeOnCommit(input_api, output_api): linux_presubmit = join(self.fake_root_dir, 'linux_only', 'PRESUBMIT.py') presubmit.os.path.isfile(root_presubmit).AndReturn(True) - presubmit.gclient_utils.FileRead(root_presubmit, 'rU').AndReturn( + presubmit.gcl.ReadFile(root_presubmit, 'rU').AndReturn( self.presubmit_tryslave % '["win"]') presubmit.os.path.isfile(root_presubmit).AndReturn(True) presubmit.os.path.isfile(linux_presubmit).AndReturn(True) - presubmit.gclient_utils.FileRead(root_presubmit, 'rU').AndReturn( + presubmit.gcl.ReadFile(root_presubmit, 'rU').AndReturn( self.presubmit_tryslave % '["win"]') - presubmit.gclient_utils.FileRead(linux_presubmit, 'rU').AndReturn( + presubmit.gcl.ReadFile(linux_presubmit, 'rU').AndReturn( self.presubmit_tryslave % '["linux"]') self.mox.ReplayAll() @@ -568,9 +565,9 @@ class InputApiUnittest(PresubmitTestsBase): self.compareMembers(presubmit.InputApi(None, './.', False), members) def testDepotToLocalPath(self): - presubmit.scm.SVN.CaptureInfo('svn://foo/smurf').AndReturn( + presubmit.gclient_scm.CaptureSVNInfo('svn://foo/smurf').AndReturn( {'Path': 'prout'}) - presubmit.scm.SVN.CaptureInfo('svn:/foo/notfound/burp').AndReturn({}) + presubmit.gclient_scm.CaptureSVNInfo('svn:/foo/notfound/burp').AndReturn({}) self.mox.ReplayAll() path = presubmit.InputApi(None, './p', False).DepotToLocalPath( @@ -581,9 +578,9 @@ class InputApiUnittest(PresubmitTestsBase): self.failUnless(path == None) def testLocalToDepotPath(self): - presubmit.scm.SVN.CaptureInfo('smurf').AndReturn({'URL': + presubmit.gclient_scm.CaptureSVNInfo('smurf').AndReturn({'URL': 'svn://foo'}) - presubmit.scm.SVN.CaptureInfo('notfound-food').AndReturn({}) + presubmit.gclient_scm.CaptureSVNInfo('notfound-food').AndReturn({}) self.mox.ReplayAll() path = presubmit.InputApi(None, './p', False).LocalToDepotPath('smurf') @@ -634,20 +631,18 @@ class InputApiUnittest(PresubmitTestsBase): presubmit.os.path.exists(notfound).AndReturn(False) presubmit.os.path.exists(flap).AndReturn(True) presubmit.os.path.isdir(flap).AndReturn(False) - presubmit.scm.SVN.CaptureInfo(beingdeleted).AndReturn({}) - presubmit.scm.SVN.CaptureInfo(notfound).AndReturn({}) - presubmit.scm.SVN.GetFileProperty(blat, 'svn:mime-type').AndReturn(None) - presubmit.scm.SVN.GetFileProperty(readme, 'svn:mime-type').AndReturn(None) - presubmit.scm.SVN.GetFileProperty(binary, 'svn:mime-type').AndReturn( + presubmit.gclient_scm.CaptureSVNInfo(beingdeleted).AndReturn({}) + presubmit.gclient_scm.CaptureSVNInfo(notfound).AndReturn({}) + presubmit.gcl.GetSVNFileProperty(blat, 'svn:mime-type').AndReturn(None) + presubmit.gcl.GetSVNFileProperty(readme, 'svn:mime-type').AndReturn(None) + presubmit.gcl.GetSVNFileProperty(binary, 'svn:mime-type').AndReturn( 'application/octet-stream') - presubmit.scm.SVN.GetFileProperty(weird, 'svn:mime-type').AndReturn(None) - presubmit.scm.SVN.GetFileProperty(another, 'svn:mime-type').AndReturn(None) - presubmit.scm.SVN.GetFileProperty(third_party, 'svn:mime-type' + presubmit.gcl.GetSVNFileProperty(weird, 'svn:mime-type').AndReturn(None) + presubmit.gcl.GetSVNFileProperty(another, 'svn:mime-type').AndReturn(None) + presubmit.gcl.GetSVNFileProperty(third_party, 'svn:mime-type' ).AndReturn(None) - presubmit.gclient_utils.FileRead(blat, 'rU' - ).AndReturn('whatever\ncookie') - presubmit.gclient_utils.FileRead(another, 'rU' - ).AndReturn('whatever\ncookie2') + presubmit.gcl.ReadFile(blat).AndReturn('whatever\ncookie') + presubmit.gcl.ReadFile(another).AndReturn('whatever\ncookie2') self.mox.ReplayAll() change = presubmit.SvnChange('mychange', '\n'.join(description_lines), @@ -759,7 +754,7 @@ class InputApiUnittest(PresubmitTestsBase): item = presubmit.os.path.join(self.fake_root_dir, item) presubmit.os.path.exists(item).AndReturn(True) presubmit.os.path.isdir(item).AndReturn(False) - presubmit.scm.SVN.GetFileProperty(item, 'svn:mime-type').AndReturn(None) + presubmit.gcl.GetSVNFileProperty(item, 'svn:mime-type').AndReturn(None) self.mox.ReplayAll() change = presubmit.SvnChange('mychange', '', self.fake_root_dir, files, 0, @@ -781,7 +776,7 @@ class InputApiUnittest(PresubmitTestsBase): item = presubmit.os.path.join(self.fake_root_dir, item) presubmit.os.path.exists(item).AndReturn(True) presubmit.os.path.isdir(item).AndReturn(False) - presubmit.scm.SVN.GetFileProperty(item, 'svn:mime-type').AndReturn(None) + presubmit.gcl.GetSVNFileProperty(item, 'svn:mime-type').AndReturn(None) self.mox.ReplayAll() change = presubmit.SvnChange('mychange', '', self.fake_root_dir, files, 0, @@ -857,7 +852,7 @@ class InputApiUnittest(PresubmitTestsBase): def testReadFileStringAccepted(self): path = presubmit.os.path.join(self.fake_root_dir, 'AA/boo') - presubmit.gclient_utils.FileRead(path, 'x').AndReturn(None) + presubmit.gcl.ReadFile(path, 'x').AndReturn(None) self.mox.ReplayAll() change = presubmit.Change('foo', 'foo', self.fake_root_dir, [('M', 'AA')], @@ -878,8 +873,7 @@ class InputApiUnittest(PresubmitTestsBase): def testReadFileAffectedFileAccepted(self): file = presubmit.AffectedFile('AA/boo', 'M', self.fake_root_dir) - presubmit.gclient_utils.FileRead(file.AbsoluteLocalPath(), 'x' - ).AndReturn(None) + presubmit.gcl.ReadFile(file.AbsoluteLocalPath(), 'x').AndReturn(None) self.mox.ReplayAll() change = presubmit.Change('foo', 'foo', self.fake_root_dir, [('M', 'AA')], @@ -961,8 +955,8 @@ class AffectedFileUnittest(PresubmitTestsBase): path = presubmit.os.path.join('foo', 'blat.cc') presubmit.os.path.exists(path).AndReturn(True) presubmit.os.path.isdir(path).AndReturn(False) - presubmit.gclient_utils.FileRead(path, 'rU').AndReturn('whatever\ncookie') - presubmit.scm.SVN.CaptureInfo(path).AndReturn( + presubmit.gcl.ReadFile(path).AndReturn('whatever\ncookie') + presubmit.gclient_scm.CaptureSVNInfo(path).AndReturn( {'URL': 'svn:/foo/foo/blat.cc'}) self.mox.ReplayAll() af = presubmit.SvnAffectedFile('foo/blat.cc', 'M') @@ -974,7 +968,7 @@ class AffectedFileUnittest(PresubmitTestsBase): self.failUnless(af.ServerPath() == '') def testProperty(self): - presubmit.scm.SVN.GetFileProperty('foo.cc', 'svn:secret-property' + presubmit.gcl.GetSVNFileProperty('foo.cc', 'svn:secret-property' ).AndReturn('secret-property-value') self.mox.ReplayAll() affected_file = presubmit.SvnAffectedFile('foo.cc', 'A') @@ -986,7 +980,7 @@ class AffectedFileUnittest(PresubmitTestsBase): def testIsDirectoryNotExists(self): presubmit.os.path.exists('foo.cc').AndReturn(False) - presubmit.scm.SVN.CaptureInfo('foo.cc').AndReturn({}) + presubmit.gclient_scm.CaptureSVNInfo('foo.cc').AndReturn({}) self.mox.ReplayAll() affected_file = presubmit.SvnAffectedFile('foo.cc', 'A') # Verify cache coherency. @@ -1012,8 +1006,8 @@ class AffectedFileUnittest(PresubmitTestsBase): presubmit.os.path.isdir(blat).AndReturn(False) presubmit.os.path.exists(blob).AndReturn(True) presubmit.os.path.isdir(blob).AndReturn(False) - presubmit.scm.SVN.GetFileProperty(blat, 'svn:mime-type').AndReturn(None) - presubmit.scm.SVN.GetFileProperty(blob, 'svn:mime-type' + presubmit.gcl.GetSVNFileProperty(blat, 'svn:mime-type').AndReturn(None) + presubmit.gcl.GetSVNFileProperty(blob, 'svn:mime-type' ).AndReturn('application/octet-stream') self.mox.ReplayAll() @@ -1157,10 +1151,10 @@ class CannedChecksUnittest(PresubmitTestsBase): input_api1.AffectedSourceFiles(None).AndReturn(files1) else: input_api1.AffectedFiles(include_deleted=False).AndReturn(files1) - presubmit.scm.SVN.GetFileProperty(presubmit.normpath('foo/bar.cc'), - property).AndReturn(value1) - presubmit.scm.SVN.GetFileProperty(presubmit.normpath('foo.cc'), - property).AndReturn(value1) + presubmit.gcl.GetSVNFileProperty(presubmit.normpath('foo/bar.cc'), + property).AndReturn(value1) + presubmit.gcl.GetSVNFileProperty(presubmit.normpath('foo.cc'), + property).AndReturn(value1) change2 = presubmit.SvnChange('mychange', '', self.fake_root_dir, [], 0, 0) input_api2 = self.MockInputApi(change2, committing) files2 = [ @@ -1172,10 +1166,10 @@ class CannedChecksUnittest(PresubmitTestsBase): else: input_api2.AffectedFiles(include_deleted=False).AndReturn(files2) - presubmit.scm.SVN.GetFileProperty(presubmit.normpath('foo/bar.cc'), - property).AndReturn(value2) - presubmit.scm.SVN.GetFileProperty(presubmit.normpath('foo.cc'), - property).AndReturn(value2) + presubmit.gcl.GetSVNFileProperty(presubmit.normpath('foo/bar.cc'), + property).AndReturn(value2) + presubmit.gcl.GetSVNFileProperty(presubmit.normpath('foo.cc'), + property).AndReturn(value2) self.mox.ReplayAll() results1 = check(input_api1, presubmit.OutputApi, None) diff --git a/tests/revert_unittest.py b/tests/revert_unittest.py index 8e872e630b..9296d85861 100644 --- a/tests/revert_unittest.py +++ b/tests/revert_unittest.py @@ -61,7 +61,6 @@ class RevertMainUnittest(RevertTestsBase): class RevertRevertUnittest(RevertTestsBase): def setUp(self): RevertTestsBase.setUp(self) - self.mox.StubOutWithMock(revert.gclient_scm.scm.SVN, 'CaptureStatus') def testRevert(self): revert.gcl.GetRepositoryRoot().AndReturn('foo') @@ -74,7 +73,7 @@ class RevertRevertUnittest(RevertTestsBase): }] revert.CaptureSVNLog(['-r', '42', '-v']).AndReturn(entries) revert.GetRepoBase().AndReturn('proto://fqdn/repo/') - revert.gclient_scm.scm.SVN.CaptureStatus(['random_file']).AndReturn([]) + revert.gclient_scm.CaptureSVNStatus(['random_file']).AndReturn([]) revert.gcl.RunShell(['svn', 'up', 'random_file']) revert.os.path.isdir('random_file').AndReturn(False) status = """--- Reverse-merging r42 into '.': diff --git a/tests/scm_unittest.py b/tests/scm_unittest.py index 4dbb929653..68846d71e1 100755 --- a/tests/scm_unittest.py +++ b/tests/scm_unittest.py @@ -5,12 +5,9 @@ """Unit tests for scm.py.""" -import shutil -import tempfile - from gclient_test import BaseTestCase import scm -from super_mox import mox, SuperMoxBaseTestBase +from super_mox import mox class BaseSCMTestCase(BaseTestCase): @@ -24,14 +21,17 @@ class RootTestCase(BaseSCMTestCase): def testMembersChanged(self): self.mox.ReplayAll() members = [ - 'GIT', 'SVN', - 'gclient_utils', 'os', 're', 'subprocess', 'sys', 'tempfile', 'xml', + 'CaptureGit', 'CaptureGitStatus', 'GIT_COMMAND', + 'CaptureSVN', 'CaptureSVNHeadRevision', 'CaptureSVNInfo', + 'CaptureSVNStatus', 'RunSVN', 'RunSVNAndFilterOutput', + 'RunSVNAndGetFileList', 'SVN_COMMAND', + 'gclient_utils', 'os', 're', 'subprocess', 'sys', 'xml', ] # If this test fails, you should add the relevant test. self.compareMembers(scm, members) -class GitWrapperTestCase(SuperMoxBaseTestBase): +class GitWrapperTestCase(BaseSCMTestCase): sample_git_import = """blob mark :1 data 6 @@ -80,44 +80,30 @@ from :3 def CreateGitRepo(self, git_import, path): try: - scm.subprocess.Popen(['git', 'init'], - stdout=scm.subprocess.PIPE, - stderr=scm.subprocess.STDOUT, - cwd=path).communicate() - except OSError: + subprocess.Popen(['git', 'init'], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, cwd=path).communicate() + except WindowsError: # git is not available, skip this test. return False - scm.subprocess.Popen(['git', 'fast-import'], - stdin=scm.subprocess.PIPE, - stdout=scm.subprocess.PIPE, - stderr=scm.subprocess.STDOUT, - cwd=path).communicate(input=git_import) - scm.subprocess.Popen(['git', 'checkout'], - stdout=scm.subprocess.PIPE, - stderr=scm.subprocess.STDOUT, - cwd=path).communicate() + subprocess.Popen(['git', 'fast-import'], stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + cwd=path).communicate(input=git_import) + subprocess.Popen(['git', 'checkout'], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, cwd=path).communicate() return True def setUp(self): - SuperMoxBaseTestBase.setUp(self) + BaseSCMTestCase.setUp(self) self.args = self.Args() self.url = 'git://foo' self.root_dir = tempfile.mkdtemp() self.relpath = '.' - self.base_path = scm.os.path.join(self.root_dir, self.relpath) + self.base_path = os.path.join(self.root_dir, self.relpath) self.enabled = self.CreateGitRepo(self.sample_git_import, self.base_path) def tearDown(self): shutil.rmtree(self.root_dir) - SuperMoxBaseTestBase.tearDown(self) - - def testMembersChanged(self): - self.mox.ReplayAll() - members = [ - 'COMMAND', 'Capture', 'CaptureStatus', - ] - # If this test fails, you should add the relevant test. - self.compareMembers(scm.GIT, members) + gclient_test.BaseTestCase.tearDown(self) class SVNTestCase(BaseSCMTestCase): @@ -128,17 +114,7 @@ class SVNTestCase(BaseSCMTestCase): self.url = self.Url() self.relpath = 'asf' - def testMembersChanged(self): - self.mox.ReplayAll() - members = [ - 'COMMAND', 'Capture', 'CaptureHeadRevision', 'CaptureInfo', - 'CaptureStatus', 'DiffItem', 'GetFileProperty', 'IsMoved', 'Run', - 'RunAndFilterOutput', 'RunAndGetFileList', - ] - # If this test fails, you should add the relevant test. - self.compareMembers(scm.SVN, members) - - def testGetFileInfo(self): + def testGetSVNFileInfo(self): xml_text = r""" @@ -154,8 +130,8 @@ class SVNTestCase(BaseSCMTestCase): """ % self.url - self.mox.StubOutWithMock(scm.SVN, 'Capture') - scm.SVN.Capture(['info', '--xml', self.url], '.', True).AndReturn(xml_text) + self.mox.StubOutWithMock(scm, 'CaptureSVN') + scm.CaptureSVN(['info', '--xml', self.url], '.', True).AndReturn(xml_text) expected = { 'URL': 'http://src.chromium.org/svn/trunk/src/chrome/app/d', 'UUID': None, @@ -169,10 +145,10 @@ class SVNTestCase(BaseSCMTestCase): 'Node Kind': 'file', } self.mox.ReplayAll() - file_info = scm.SVN.CaptureInfo(self.url, '.', True) + file_info = scm.CaptureSVNInfo(self.url, '.', True) self.assertEquals(sorted(file_info.items()), sorted(expected.items())) - def testCaptureInfo(self): + def testCaptureSvnInfo(self): xml_text = """ """ % (self.url, self.root_dir) - self.mox.StubOutWithMock(scm.SVN, 'Capture') - scm.SVN.Capture(['info', '--xml', self.url], '.', True).AndReturn(xml_text) + self.mox.StubOutWithMock(scm, 'CaptureSVN') + scm.CaptureSVN(['info', '--xml', self.url], '.', True).AndReturn(xml_text) self.mox.ReplayAll() - file_info = scm.SVN.CaptureInfo(self.url, '.', True) + file_info = scm.CaptureSVNInfo(self.url, '.', True) expected = { 'URL': self.url, 'UUID': '7b9385f5-0452-0410-af26-ad4892b7a1fb', @@ -209,11 +185,11 @@ class SVNTestCase(BaseSCMTestCase): 'Copied From URL': None, 'Copied From Rev': None, 'Path': '.', - 'Node Kind': 'directory', + 'Node Kind': 'dir', } self.assertEqual(file_info, expected) - def testCaptureStatus(self): + def testCaptureSVNStatus(self): text =r""" @@ -260,7 +236,7 @@ class SVNTestCase(BaseSCMTestCase): proc.communicate().AndReturn((text, 0)) self.mox.ReplayAll() - info = scm.SVN.CaptureStatus('.') + info = scm.CaptureSVNStatus('.') expected = [ ('? ', 'unversionned_file.txt'), ('M ', 'build\\internal\\essential.vsprops'), @@ -270,14 +246,14 @@ class SVNTestCase(BaseSCMTestCase): ] self.assertEquals(sorted(info), sorted(expected)) - def testRun(self): + def testRunSVN(self): param2 = 'bleh' scm.gclient_utils.SubprocessCall(['svn', 'foo', 'bar'], param2).AndReturn(None) self.mox.ReplayAll() - scm.SVN.Run(['foo', 'bar'], param2) + scm.RunSVN(['foo', 'bar'], param2) - def testCaptureStatusEmpty(self): + def testCaptureSVNStatusEmpty(self): text = r""" >sys.stderr, diff + print diff self.assertEqual(actual_members, expected_members) def UnMock(self, object, name): diff --git a/tests/trychange_unittest.py b/tests/trychange_unittest.py index 828d3ea4e8..24550dda31 100644 --- a/tests/trychange_unittest.py +++ b/tests/trychange_unittest.py @@ -25,9 +25,9 @@ class TryChangeUnittest(TryChangeTestsBase): 'GetTryServerSettings', 'GuessVCS', 'HELP_STRING', 'InvalidScript', 'NoTryServerAccess', 'PathDifference', 'RunCommand', 'SCM', 'SVN', 'TryChange', 'USAGE', - 'datetime', 'gcl', 'getpass', 'logging', - 'optparse', 'os', 'presubmit_support', 'scm', 'shutil', 'socket', - 'subprocess', 'sys', 'tempfile', 'upload', 'urllib', + 'datetime', 'gcl', 'gclient_scm', 'getpass', 'logging', + 'optparse', 'os', 'presubmit_support', 'shutil', 'socket', 'subprocess', + 'sys', 'tempfile', 'upload', 'urllib', ] # If this test fails, you should add the relevant test. self.compareMembers(trychange, members) diff --git a/trychange.py b/trychange.py index 65e935a08a..cbe49f93f9 100755 --- a/trychange.py +++ b/trychange.py @@ -21,11 +21,11 @@ import tempfile import urllib import gcl -import scm +import gclient_scm import presubmit_support import upload -__version__ = '1.1.2' +__version__ = '1.1.1' # Constants @@ -150,8 +150,50 @@ class SVN(SCM): else: os.chdir(root) - # Directories will return None so filter them out. - diff = filter(None, [scm.SVN.DiffItem(f) for f in files]) + diff = [] + for filename in files: + # Use svn info output instead of os.path.isdir because the latter fails + # when the file is deleted. + if gclient_scm.CaptureSVNInfo(filename).get("Node Kind") in ( + "dir", "directory"): + continue + # If the user specified a custom diff command in their svn config file, + # then it'll be used when we do svn diff, which we don't want to happen + # since we want the unified diff. Using --diff-cmd=diff doesn't always + # work, since they can have another diff executable in their path that + # gives different line endings. So we use a bogus temp directory as the + # config directory, which gets around these problems. + if sys.platform.startswith("win"): + parent_dir = tempfile.gettempdir() + else: + parent_dir = sys.path[0] # tempdir is not secure. + bogus_dir = os.path.join(parent_dir, "temp_svn_config") + if not os.path.exists(bogus_dir): + os.mkdir(bogus_dir) + # Grabs the diff data. + data = gcl.RunShell(["svn", "diff", "--config-dir", bogus_dir, filename]) + + # We know the diff will be incorrectly formatted. Fix it. + if gcl.IsSVNMoved(filename): + # The file is "new" in the patch sense. Generate a homebrew diff. + # We can't use ReadFile() since it's not using binary mode. + file_handle = open(filename, 'rb') + file_content = file_handle.read() + file_handle.close() + # Prepend '+' to every lines. + file_content = ['+' + i for i in file_content.splitlines(True)] + nb_lines = len(file_content) + # We need to use / since patch on unix will fail otherwise. + filename = filename.replace('\\', '/') + data = "Index: %s\n" % filename + data += ("=============================================================" + "======\n") + # Note: Should we use /dev/null instead? + data += "--- %s\n" % filename + data += "+++ %s\n" % filename + data += "@@ -0,0 +1,%d @@\n" % nb_lines + data += ''.join(file_content) + diff.append(data) os.chdir(previous_cwd) return "".join(diff) @@ -365,7 +407,6 @@ def GuessVCS(options): Returns: A SCM instance. Exits if the SCM can't be guessed. """ - __pychecker__ = 'no-returnvalues' # Subversion has a .svn in all working directories. if os.path.isdir('.svn'): logging.info("Guessed VCS = Subversion")