2
#******************************************************************************\
3
#* $Source: /u/blais/cvsroot/xxdiff/bin/xxdiff-cond-replace,v $
4
#* $Id: xxdiff-cond-replace,v 1.9 2004/11/03 22:53:39 blais Exp $
5
#* $Date: 2004/11/03 22:53:39 $
7
#* Copyright (C) 2003-2004 Martin Blais <blais@furius.ca>
9
#* This program is free software; you can redistribute it and/or modify
10
#* it under the terms of the GNU General Public License as published by
11
#* the Free Software Foundation; either version 2 of the License, or
12
#* (at your option) any later version.
14
#* This program is distributed in the hope that it will be useful,
15
#* but WITHOUT ANY WARRANTY; without even the implied warranty of
16
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
#* GNU General Public License for more details.
19
#* You should have received a copy of the GNU General Public License
20
#* along with this program; if not, write to the Free Software
21
#* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23
#*****************************************************************************/
25
"""xxdiff-cond-replace [<options>] <orig-file> <modified-file>
27
Useful script to conditionally replace a original by a generated file.
29
Basically, you invoke some script or pipeline of scripts to perform
30
modifications on an original text file and store the results in a temporary file
31
(the modified-file), and then invoke this script to conditionally overwrite the
32
contents of the orig-file with those of the modified file.
34
This works like copy (``cp``), except that xxdiff is shown to ask the user
35
whether to copy the file or not. This is useful to run in a loop, for example::
37
for i in `*/*.xml` ; do
38
cat $i | ...pipeline of cmds that modifies the file... > /tmp/out.xml
39
# copy only if the user accepts or merges.
40
xxdiff-cond-replace $i /tmp/out.xml
43
**IMPORTANT**: Notice well that the original file which will be overwritten is
44
on the LEFT side. The syntax is reversed that of the UNIX cp command.
49
This program exits with status 0 if the copy operation was accepted or merged,
55
- the script automatically creates backup files.
56
- the script automatically generates a detailed log of its actions and
57
a text summary of all the differences beteween the original and new files.
58
- the script can optionally checkout the file with ClearCase before performing
63
__version__ = "$Revision: 1.9 $"
64
__author__ = "Martin Blais <blais@furius.ca>"
65
__depends__ = ['xxdiff', 'Python-2.3']
66
__copyright__ = """Copyright (C) 2003-2004 Martin Blais <blais@furius.ca>.
67
This code is distributed under the terms of the GNU General Public License."""
69
#===============================================================================
70
# EXTERNAL DECLARATIONS
71
#===============================================================================
75
import commands, tempfile, shutil
77
#===============================================================================
79
#===============================================================================
81
tmpprefix = '%s.' % basename(sys.argv[0])
83
#-------------------------------------------------------------------------------
85
def backup( fn, opts ):
86
"""Compute backup filename and copy backup file."""
88
if opts.backup_type == 'parallel':
92
backupfn = fmt % (fn, ii)
93
if not exists(backupfn):
97
elif opts.backup_type == 'other':
99
backupfn = normpath(join(opts.backup_dir, fn))
105
print 'Backup:', backupfn
106
ddn = dirname(backupfn)
107
if ddn and not exists(ddn):
109
shutil.copy2(fn, backupfn)
111
#-------------------------------------------------------------------------------
113
def cc_checkout(fn, opts):
115
print 'Checking out the file'
116
os.system('cleartool co -nc "%s"' % fn)
118
#-------------------------------------------------------------------------------
120
def cond_replace( origfile, modfile, opts ):
122
"""Spawn xxdiff and perform the replacement if confirmed."""
126
print 'File: ', origfile
127
print 'Absolute:', abspath(origfile)
131
difffmt = 'diff -y --suppress-common-lines "%s" "%s" 2>&1'
132
diffcmd = difffmt % (origfile, modfile)
133
s, o = commands.getstatusoutput(diffcmd)
135
rv = os.WEXITSTATUS(s)
138
print >> sys.stderr, "Warning: no differences."
150
tmpf2 = tempfile.NamedTemporaryFile('w', prefix=tmpprefix)
152
if not opts.no_confirm:
153
cmd = ('%s --decision --merged-filename "%s" ' + \
154
'--title2 "NEW FILE" "%s" "%s" ') % \
155
(opts.command, tmpf2.name, origfile, modfile)
156
s, o = commands.getstatusoutput(cmd)
159
s, o = 0, 'NO_CONFIRM'
166
if o == 'ACCEPT' or o == 'NO_CONFIRM':
167
backup(origfile, opts)
168
if opts.checkout_clearcase:
169
cc_checkout(origfile, opts)
171
shutil.copyfile(modfile, origfile)
172
elif o == 'REJECT' or o == 'NODECISION':
176
# run diff again to show the changes that have actually been
177
# merged in the output log.
178
diffcmd = difffmt % (origfile, tmpf2.name)
179
s, o = commands.getstatusoutput(diffcmd)
180
print 'Actual merged changes:'
185
backup(origfile, opts)
186
if opts.checkout_clearcase:
187
cc_checkout(origfile, opts)
189
shutil.copyfile(tmpf2.name, origfile)
191
raise SystemExit("Error: unexpected answer from xxdiff: %s" % o)
197
raise SystemExit("Error: deleting modified file (%s)" % str(e))
201
#-------------------------------------------------------------------------------
203
def complete( parser ):
204
"Programmable completion support. Script should work without it."
207
optcomplete.autocomplete(parser)
211
#===============================================================================
213
#===============================================================================
217
parser = optparse.OptionParser(__doc__.strip(), version=__version__)
218
parser.add_option('--command', action='store', default='xxdiff',
219
help="xxdiff command prefix to use.")
220
parser.add_option('-b', '--backup-type', action='store', type='choice',
221
choices=['parallel', 'other', 'none'], metavar="CHOICE",
223
help="selects the backup type "
224
"('parallel', 'other', 'none')")
225
parser.add_option('--backup-dir', action='store',
226
help="specify backup directory for type 'other'")
227
parser.add_option('-C', '--checkout-clearcase', action='store_true',
228
help="checkout files with clearcase before storing.")
229
parser.add_option('-n', '--dry-run', action='store_true',
230
help="print the commands that would be executed " +
231
"but don't really run them.")
232
parser.add_option('-q', '--silent', '--quiet', action='store_true',
233
help="Do not output anything. Normally the decision "
234
"status and backup file location is output.")
235
parser.add_option('-x', '--diff', action='store_true',
236
help="Run a diff and log the differences on stdout.")
237
parser.add_option('-d', '--delete', action='store_true',
238
help="Instead of copying the temporary file, move it "
239
"(delete it after copying).")
240
parser.add_option('-X', '--no-confirm', action='store_true',
241
help="do not ask for confirmation with graphical "
242
"diff viewer. This essentially generates a diff log and "
243
"copies the file over with backups.")
245
opts, args = parser.parse_args()
248
if not args or len(args) > 2:
249
raise parser.error("you must specify exactly two files.")
252
raise parser.error("no need to use --delete on file from stdin.")
255
intmpf = tempfile.NamedTemporaryFile('w', prefix=tmpprefix)
257
intmpf.write(sys.stdin.read())
262
"Error: saving stdin to temporary file (%s)" % str(e))
263
modfile = intmpf.name
265
origfile, modfile = args
267
if opts.silent and opts.diff:
268
raise parser.error("you cannot ask for a diff output and for silent at "
271
if opts.backup_type == 'other':
272
if not opts.backup_dir:
273
opts.backup_dir = tempfile.mkdtemp(prefix=tmpprefix)
274
print "Storing backup files under:", opts.backup_dir
277
raise SystemExit("Error: backup-dir is only valid for backups of "
280
# call xxdiff and perform the conditional replacement.
281
rval = cond_replace(origfile, modfile, opts)
283
# repeat message at the end for convenience.
284
if opts.backup_type == 'other' and opts.backup_dir:
286
print "Storing backup files under:", opts.backup_dir
291
if __name__ == '__main__':
294
except KeyboardInterrupt:
295
print >> sys.stderr, 'Interrupted.'