Index: scm.py |
=================================================================== |
--- scm.py (revision 195011) |
+++ scm.py (working copy) |
@@ -772,40 +772,109 @@ |
return '' |
@staticmethod |
- def DiffItem(filename, cwd, full_move, revision): |
- """Diffs a single file. |
+ def GenerateDiff(filenames, cwd, full_move, revision): |
+ """Returns a string containing the diff for the given file list. |
- Should be simple, eh? No it isn't. |
- Be sure to be in the appropriate directory before calling to have the |
- expected relative path. |
- full_move means that move or copy operations should completely recreate the |
- files, usually in the prospect to apply the patch for a try job.""" |
+ The files in the list should either be absolute paths or relative to the |
+ given root. If no root directory is provided, the repository root will be |
+ used. |
+ The diff will always use relative paths. |
+ """ |
+ assert isinstance(filenames, (list, tuple)) |
# 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. |
- bogus_dir = tempfile.mkdtemp() |
- try: |
- # Use "svn info" output instead of os.path.isdir because the latter fails |
- # when the file is deleted. |
- return SVN._DiffItemInternal( |
- filename, |
- cwd, |
- SVN.CaptureLocalInfo([filename], cwd), |
- bogus_dir, |
- full_move, |
- revision) |
- finally: |
- gclient_utils.RemoveDirectory(bogus_dir) |
+ # since we want the unified diff. On svn >= 1.7, the "--internal-diff" flag |
+ # will solve this. On svn < 1.7, however, this flag doesn't exist. Using |
+ # --diff-cmd=diff doesn't always work, since some users (e.g. Windows cmd |
+ # users) may have no diff executable in their paths at all, and even people |
+ # who do can have a diff executable in their path that uses unexpected line |
+ # endings. So we use a bogus temp directory as the config directory, which |
+ # bypasses any user settings for the diff-cmd. |
+ if SVN.AssertVersion("1.7")[0]: |
+ return SVN._GenerateDiffInternal(filenames, cwd, full_move, revision, |
+ ["diff", "--internal-diff"]) |
+ else: |
+ bogus_dir = tempfile.mkdtemp() |
+ try: |
+ return SVN._GenerateDiffInternal(filenames, cwd, full_move, revision, |
+ ["diff", "--config_dir", bogus_dir]) |
+ finally: |
+ gclient_utils.RemoveDirectory(bogus_dir) |
@staticmethod |
- def _DiffItemInternal(filename, cwd, info, bogus_dir, full_move, revision): |
+ def _GenerateDiffInternal(filenames, cwd, full_move, revision, diff_command): |
+ root = os.path.normcase(os.path.join(cwd, '')) |
+ def RelativePath(path, root): |
+ """We must use relative paths.""" |
+ if os.path.normcase(path).startswith(root): |
+ return path[len(root):] |
+ return path |
+ # Cleanup filenames |
+ filenames = [RelativePath(f, root) for f in filenames] |
+ # Get information about the modified items (files and directories) |
+ data = dict([(f, SVN.CaptureLocalInfo([f], root)) for f in filenames]) |
+ diffs = [] |
+ if full_move: |
+ # Eliminate modified files inside moved/copied directory. |
+ for (filename, info) in data.iteritems(): |
+ if SVN.IsMovedInfo(info) and info.get("Node Kind") == "directory": |
+ # Remove files inside the directory. |
+ filenames = [f for f in filenames |
+ if not f.startswith(filename + os.path.sep)] |
+ for filename in data.keys(): |
+ if not filename in filenames: |
+ # Remove filtered out items. |
+ del data[filename] |
+ else: |
+ metaheaders = [] |
+ for (filename, info) in data.iteritems(): |
+ if SVN.IsMovedInfo(info): |
+ # for now, the most common case is a head copy, |
+ # so let's just encode that as a straight up cp. |
+ srcurl = info.get('Copied From URL') |
+ file_root = info.get('Repository Root') |
+ rev = int(info.get('Copied From Rev')) |
+ assert srcurl.startswith(file_root) |
+ src = srcurl[len(file_root)+1:] |
+ try: |
+ srcinfo = SVN.CaptureRemoteInfo(srcurl) |
+ except subprocess2.CalledProcessError, e: |
+ if not 'Not a valid URL' in e.stderr: |
+ raise |
+ # Assume the file was deleted. No idea how to figure out at which |
+ # revision the file was deleted. |
+ srcinfo = {'Revision': rev} |
+ if (srcinfo.get('Revision') != rev and |
+ SVN.Capture(diff_command + ['-r', '%d:head' % rev, srcurl], cwd)): |
+ metaheaders.append("#$ svn cp -r %d %s %s " |
+ "### WARNING: note non-trunk copy\n" % |
+ (rev, src, filename)) |
+ else: |
+ metaheaders.append("#$ cp %s %s\n" % (src, |
+ filename)) |
+ if metaheaders: |
+ diffs.append("### BEGIN SVN COPY METADATA\n") |
+ diffs.extend(metaheaders) |
+ diffs.append("### END SVN COPY METADATA\n") |
+ # Now ready to do the actual diff. |
+ for filename in sorted(data.iterkeys()): |
+ diffs.append(SVN._DiffItemInternal( |
+ filename, cwd, data[filename], diff_command, full_move, revision)) |
+ # Use StringIO since it can be messy when diffing a directory move with |
+ # full_move=True. |
+ buf = cStringIO.StringIO() |
+ for d in filter(None, diffs): |
+ buf.write(d) |
+ result = buf.getvalue() |
+ buf.close() |
+ return result |
+ |
+ @staticmethod |
+ def _DiffItemInternal(filename, cwd, info, diff_command, full_move, revision): |
"""Grabs the diff data.""" |
- command = ["diff", "--config-dir", bogus_dir, filename] |
+ diff_command.append(filename) |
if revision: |
- command.extend(['--revision', revision]) |
+ diff_command.extend(['--revision', revision]) |
data = None |
if SVN.IsMovedInfo(info): |
if full_move: |
@@ -837,7 +906,7 @@ |
else: |
if info.get("Node Kind") != "directory": |
# svn diff on a mv/cp'd file outputs nothing if there was no change. |
- data = SVN.Capture(command, cwd) |
+ data = SVN.Capture(diff_command, cwd) |
if not data: |
# We put in an empty Index entry so upload.py knows about them. |
data = "Index: %s\n" % filename.replace(os.sep, '/') |
@@ -846,7 +915,7 @@ |
if info.get("Node Kind") != "directory": |
# Normal simple case. |
try: |
- data = SVN.Capture(command, cwd) |
+ data = SVN.Capture(diff_command, cwd) |
except subprocess2.CalledProcessError: |
if revision: |
data = GenFakeDiff(filename) |
@@ -856,93 +925,6 @@ |
return data |
@staticmethod |
- def GenerateDiff(filenames, cwd, full_move, revision): |
- """Returns a string containing the diff for the given file list. |
- |
- The files in the list should either be absolute paths or relative to the |
- given root. If no root directory is provided, the repository root will be |
- used. |
- The diff will always use relative paths. |
- """ |
- assert isinstance(filenames, (list, tuple)) |
- root = os.path.normcase(os.path.join(cwd, '')) |
- def RelativePath(path, root): |
- """We must use relative paths.""" |
- if os.path.normcase(path).startswith(root): |
- return path[len(root):] |
- return path |
- # 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. |
- bogus_dir = tempfile.mkdtemp() |
- try: |
- # Cleanup filenames |
- filenames = [RelativePath(f, root) for f in filenames] |
- # Get information about the modified items (files and directories) |
- data = dict([(f, SVN.CaptureLocalInfo([f], root)) for f in filenames]) |
- diffs = [] |
- if full_move: |
- # Eliminate modified files inside moved/copied directory. |
- for (filename, info) in data.iteritems(): |
- if SVN.IsMovedInfo(info) and info.get("Node Kind") == "directory": |
- # Remove files inside the directory. |
- filenames = [f for f in filenames |
- if not f.startswith(filename + os.path.sep)] |
- for filename in data.keys(): |
- if not filename in filenames: |
- # Remove filtered out items. |
- del data[filename] |
- else: |
- metaheaders = [] |
- for (filename, info) in data.iteritems(): |
- if SVN.IsMovedInfo(info): |
- # for now, the most common case is a head copy, |
- # so let's just encode that as a straight up cp. |
- srcurl = info.get('Copied From URL') |
- file_root = info.get('Repository Root') |
- rev = int(info.get('Copied From Rev')) |
- assert srcurl.startswith(file_root) |
- src = srcurl[len(file_root)+1:] |
- try: |
- srcinfo = SVN.CaptureRemoteInfo(srcurl) |
- except subprocess2.CalledProcessError, e: |
- if not 'Not a valid URL' in e.stderr: |
- raise |
- # Assume the file was deleted. No idea how to figure out at which |
- # revision the file was deleted. |
- srcinfo = {'Revision': rev} |
- if (srcinfo.get('Revision') != rev and |
- SVN.Capture(['diff', '-r', '%d:head' % rev, srcurl], cwd)): |
- metaheaders.append("#$ svn cp -r %d %s %s " |
- "### WARNING: note non-trunk copy\n" % |
- (rev, src, filename)) |
- else: |
- metaheaders.append("#$ cp %s %s\n" % (src, |
- filename)) |
- |
- if metaheaders: |
- diffs.append("### BEGIN SVN COPY METADATA\n") |
- diffs.extend(metaheaders) |
- diffs.append("### END SVN COPY METADATA\n") |
- # Now ready to do the actual diff. |
- for filename in sorted(data.iterkeys()): |
- diffs.append(SVN._DiffItemInternal( |
- filename, cwd, data[filename], bogus_dir, full_move, revision)) |
- # Use StringIO since it can be messy when diffing a directory move with |
- # full_move=True. |
- buf = cStringIO.StringIO() |
- for d in filter(None, diffs): |
- buf.write(d) |
- result = buf.getvalue() |
- buf.close() |
- return result |
- finally: |
- gclient_utils.RemoveDirectory(bogus_dir) |
- |
- @staticmethod |
def GetEmail(cwd): |
"""Retrieves the svn account which we assume is an email address.""" |
try: |