/usr/lib/python2.7/dist-packages/xxdiff/scripts/svndiff.py is in xxdiff-scripts 1:4.0.1+hg487+dfsg-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 | # This file is part of the xxdiff package. See xxdiff for license and details.
"""xx-svn-diff [<options>] <file> <file> [<file>]
Script that invokes xxdiff for all modified files in the given Subversion
checkout area. Optionally replace the output file by decision mode.
"""
__author__ = "Martin Blais <blais@furius.ca>"
__depends__ = ['xxdiff', 'Python-2.4', 'Subversion']
# stdlib imports.
import sys, os, tempfile, datetime
from os.path import *
# xxdiff imports.
import xxdiff.scripts
import xxdiff.invoke
from xxdiff.invoke import title_opts
import xxdiff.editor
import xxdiff.resilient
import xxdiff.utils
from xxdiff.utils import makedirs
import xxdiff.history
import xxdiff.condrepl
from xxdiff.scripts import tmpprefix, script_name
from xxdiff.scm import subversion
from xxdiff.scripts.svnforeign import query_unregistered_svn_files
def review_file(sobj, opts):
"""
Check the given status object and if necessary, spawn xxdiff on it.
Return a pair of ((file type description, action) waiter-object).
"""
msg = ('normal', 'display')
dopts = []
merged = sobj.filename
try:
# Ignore unmodified files if there are any.
if sobj.status in (' ', '?'):
msg = ('unmodified', 'ignored')
return msg, None
# Diff modified files
if sobj.status in ('M', 'C'):
tmpf = subversion.cat_revision_temp(sobj.filename, 'BASE')
left, right = tmpf.name, sobj.filename
dopts.extend(title_opts('%s (BASE)' % sobj.filename))
# Diff added files
elif sobj.status == 'A':
# Check if it is a directory.
if not isfile(sobj.filename):
msg = ('directory', 'skip')
return msg, None
if sobj.withhist == '+':
# Get the source filename from the history.
info = subversion.getinfo(sobj.filename)
from_url, from_rev = [info.get('Copied From %s' % x, None)
for x in 'URL', 'Rev']
tmpf = subversion.cat_revision_temp(sobj.filename, 'BASE')
dopts.extend(title_opts('%s (%s)' % (from_url, from_rev)))
else:
tmpf = tempfile.NamedTemporaryFile('w', prefix=tmpprefix)
dopts.extend(title_opts('(NON-EXISTING)'))
left, right = tmpf.name, sobj.filename
# Diff deleted files
elif sobj.status == 'D':
tmpf = subversion.cat_revision_temp(sobj.filename, 'BASE')
tmpf_empty = tempfile.NamedTemporaryFile('w', prefix=tmpprefix)
dopts.extend(title_opts('%s (BASE)' % sobj.filename,
'(DELETED)'))
left, right = tmpf.name, tmpf_empty.name
# We don't know what to do with the rest yet.
else:
msg = ('unknown', 'ignored')
print >> sys.stderr, (
"Error: Action for status '%s' on file '%s' "
"is not implemented yet") % (sobj.status, sobj.filename)
return msg, None
finally:
pass
# Check for non-text files.
if not xxdiff.utils.istextfile(left) or not xxdiff.utils.istextfile(right):
return ('non-text', 'skip'), None
# Run xxdiff on the files.
if opts.review:
dopts.extend(['--decision'])
opts.no_confirm = False
opts.dry_run = False
decision = xxdiff.condrepl.cond_replace(left, right, opts, sys.stdout,
False, replfn=right)
waiter = None
else:
waiter = xxdiff.invoke.xxdiff_display(opts, left, right, nowait=1, *dopts)
return msg, waiter
def parse_options():
"""
Parse the options.
"""
import optparse
parser = optparse.OptionParser(__doc__.strip())
parser.add_option('-r', '--review', '--replace', action='store_true',
help="Review/replace mode: diff the file and replace"
"the target file by the output of the diff. Runs in "
"decision mode. This is useful in order to remove "
"selected changes before committing in.")
parser.add_option('-c', '--commit', '--commit-with-comments',
action='store_true',
help="Spawns an editor window for adding comments and "
"starts the review, then commits the files with the "
"given comment. NOTE: you editor is taken from the "
"environment variables XXDIFF_EDITOR, SVN_EDITOR, and "
"then EDITOR. This program *MUST* open a new window "
"(for example, you cannot use just 'vi' but rather "
"something like: xterm -e 'vi %s').")
o = parser.add_option('-C', '--comments-file', action='store',
default=None,
help="Specified the comments file to use. This file "
"is left behind and its contents reused if it is "
"specified.")
parser.add_option('-f', '--foreign', '--consider-foreign-files',
action='store_true',
help="Before starting the review/diffs, check all the "
"unregistered files and ask the user one by one about "
"what to do with them.")
for mod in xxdiff.invoke, xxdiff.history:
mod.options_graft(parser)
xxdiff.backup.options_graft(parser,
"These options affect automatic backup of "
"deleted files, if enabled.")
xxdiff.scripts.install_autocomplete(parser)
opts, args = parser.parse_args()
for mod in xxdiff.backup, xxdiff.invoke, xxdiff.history:
mod.options_validate(opts, parser)
if opts.comments_file and not opts.commit:
print >> sys.stderr, "(Option '%s' ignored.) " % o.dest
# Add a verbose option for svnforeign to use.
opts.verbose = True
return opts, args
def svndiff_main():
"""
Main program for svn-diff script.
"""
opts, args = parse_options()
# Compute the location of the resilient directory for the comments and
# history files (and maybe more stuff later on).
resildir = xxdiff.resilient.resilient_for_paths(args)
hist = xxdiff.history.History(opts, resildir)
# Compute a list of files to ignore (e.g. if the comments or history file is
# located in the checkout, we want to ignore them and then associated
# swap/temp files).
ignofiles = []
if opts.commit and opts.comments_file:
comfn = abspath(opts.comments_file)
ignofiles.extend([comfn, '%s.swp' % comfn])
if opts.history and opts.history_file:
histfn = abspath(opts.history_file)
ignofiles.extend([histfn, '%s.swp' % histfn])
if opts.foreign:
# Consider the unregistered files.
if query_unregistered_svn_files(
args, opts, sys.stdout, ignore=ignofiles) is not True:
# The user has quit, don't continue.
sys.exit(0)
print
print
# Get the status of the working copy.
statii = subversion.status(args)
# Ignore the comments file from the svn status output.
statii = [s for s in statii if abspath(s.filename) not in ignofiles]
if not statii:
print '(Nothing to do, exiting.)'
hist.delete()
return
# First print out the status to a string.
renstatus = os.linesep.join(x.parsed_line for x in statii)
if opts.commit:
# File to delete after a successful commit.
if opts.comments_file:
comfn = abspath(opts.comments_file)
else:
# Select a comments file and make sure that it exists.
comfn = join(resildir, 'comments')
makedirs(resildir, False)
if not exists(comfn):
open(comfn, 'w')
# Spawn an editor if requested before starting the review.
if opts.commit:
m = {'date': datetime.datetime.now()}
comments = None # renstatus
edit_waiter = xxdiff.editor.spawn_editor(comments, filename=comfn)
# First print out the status to the user.
print 'Status Of Files To Be Diffed'
print '----------------------------'
print renstatus
# Then we start printing each file and the associated decision.
msgfmt = ' %-10s | %-10s | %s'
print
print msgfmt % ('Type', 'Action', 'Status')
print msgfmt % ('-'*10, '-'*10, '-'*40)
# Main loop for graphical diffs, over each of the files reported by status.
for s in statii:
kind, action = 'unknown', 'exception' # Initialize for in case of an
# exception.
try:
# Skip directories.
if isdir(s.filename):
kind, action = 'directory', 'skip'
continue
elif islink(s.filename):
kind, action = 'symlink', 'skip'
continue
# Compute unique string for history recorder. We use the size, last
# modification time, and status info to hash on this.
if exists(s.filename):
fstat = os.lstat(s.filename)
sz, mtime = fstat.st_size, fstat.st_mtime
else:
# Deal with files that have been deleted.
sz, mtime = 0, 0
histitem = ' '.join((str(sz), str(mtime), s.parsed_line))
# If the file has already been reviewed in the history, skip it.
if hist.check(histitem):
kind, action = 'seen', 'skip'
continue
# Review the file.
(kind, action), waiter = review_file(s, opts)
finally:
print msgfmt % (kind, action, s.parsed_line)
if waiter is not None:
waiter()
# We have succesfully finished viewing the file, add it to the history.
hist.append(histitem)
# Commit the files if requested.
if opts.commit:
print "\nWaiting for editor '%s' to complete..." % edit_waiter.command,
sys.stdout.flush()
comments = edit_waiter()
print 'Done.\n'
print 'Recorded Merge Comments: ',
if comments == '':
print '(None)'
comments = None
else:
print
print '-' * 70
print comments
print '-' * 70
subversion.commit(args, comments=comments)
# Delete temporary comments flie if we created it.
if not opts.comments_file:
xxdiff.resilient.resilient_remove(comfn)
# The entire list of files has been reviewed (and possibly committed), clear
# the history file.
if opts.history:
print
print '(Review complete, history cleared)'
hist.delete()
def main():
xxdiff.scripts.interruptible_main(svndiff_main)
if __name__ == '__main__':
main()
|