2
#******************************************************************************\
3
#* $Source: /u/blais/cvsroot/xxdiff/bin/xxdiff-patch-UNFINISHED,v $
4
#* $Id: xxdiff-patch-UNFINISHED,v 1.8 2004/02/25 17:37:35 blais Exp $
5
#* $Date: 2004/02/25 17:37:35 $
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-patch [<options>] [<patchfile>]
27
This simple script splits the input patch for the individual files it
28
represents, applies the individual patches to temporary files and for each,
29
spawns an xxdiff for it. This effectively allows you merge the patch
30
interactively. You can accept or reject a patch for each file. You can also
31
merge the patch interactively with xxdiff, and select for the merged results to
32
be applied on the output file.
34
If no <patchfile> is specified, xxdiff-patch attempts to read the patch from
37
Important: the new (patched) file will always be shown on the right. The
38
patched file will display (patched) in its filename title. When a decision is
39
asked to the user, ACCEPT always means keep/save the file that appears on the
40
right. The script takes the following actions upon the answer:
42
- ACCEPT: keep/copy the file on the right side to the output file;
43
- REJECT: keep/copy the file on the left side to the output file;
44
- MERGED: copy the merged file on the output file;
46
If a patch for a file is accepted or merged, before being overwritten, a copy of
47
the original files are saved with the extension .orig, like patch itself.
49
Note: notice that the --reverse and --invert options below are implementing
50
orthogonal behaviour. It can be confusing to understand the differences
51
between these two. If the a reverse patch is indeed reverting changes (and
52
not constructed in the incorrect order), applying the patch will revert the
53
changes as well (the display order is not changed). This behaviour is
54
exactly like that of patch. Using invert, however, changes the logic of
55
what this script does upon accepting or rejecting a patch. In any case, you
56
will always find the 'new file if accepted' on the right side and the
57
patched file is indicated with a string in the filename title.
62
To apply a patch, simply cd into the root of the directory where the patch
63
should be applied, and invoke like this:
65
cat patchfile | xxdiff-patch
67
To preview the changes that have been made in a cvs checkout area, use the
70
cvs diff | xxdiff-patch --reverse --invert --preview
72
(For better cvs-specific behaviour about committing changes, see script
77
This script is unfinished, we still need to implement file addition/removal
82
__title__ = 'xxdiff-patch'
83
__version__ = "$Revision: 1.8 $"
84
__author__ = "Martin Blais <blais@furius.ca>"
85
__depends__ = ['xxdiff', 'Python-2.3', 'diffutils (patch)']
86
__copyright__ = """Copyright (C) 2003-2004 Martin Blais <blais@furius.ca>.
87
This code is distributed under the terms of the GNU General Public License."""
90
print >> sys.stderr, \
91
"Error: FIXME not finished have yet to deal with deleted and added files"
93
#===============================================================================
94
# EXTERNAL DECLARATIONS
95
#===============================================================================
97
import sys, os, os.path
99
import commands, shutil
100
from tempfile import NamedTemporaryFile
102
#===============================================================================
104
#===============================================================================
106
tmppfx = '%s.' % os.path.basename(sys.argv[0])
108
#-------------------------------------------------------------------------------
110
def splitpatch( text ):
112
"""Split output in chunks starting with ^Index. Returns a list of pairs
113
(tuples), each with (filename, patch) contents."""
115
## splitre = re.compile('^Index: (.*)$', re.M)
116
splitre = re.compile('^diff (.*)\s+(\S+)\s+(\S+)\s*$', re.M)
118
curbeg, curfn = None, None
119
for mo in splitre.finditer(text):
122
chunks.append( (curfn, text[curbeg:mo.start()]) )
124
curfn = mo.groups()[-1]
126
chunks.append( (curfn, text[curbeg:]) )
130
#-------------------------------------------------------------------------------
132
def complete( parser ):
133
"Programmable completion support. Script should work without it."
136
optcomplete.autocomplete(parser)
140
#===============================================================================
142
#===============================================================================
146
parser = optparse.OptionParser(__doc__.strip(), version=__version__)
148
parser.add_option('-x', '--xxdiff-options', action='store', metavar="OPTS",
150
help="specifies additional options to pass on to xxdiff.")
152
parser.add_option('-p', '--patch-options', action='store', metavar="OPTS",
154
help="specifies additional options to pass on to patch.")
155
parser.add_option('-s', '--strip', action='store', type='int',
156
help="strip option to patch, provided for convenience.")
158
parser.add_option('-R', '--reverse', action='store_true',
159
help="""apply the patch as a reverse patch (see
160
patch(1) option --reverse). The patched file will be
161
shown as the new file regardless.""")
163
parser.add_option('-I', '--invert', action='store_true',
164
help="""change the order of the displayed files, so that
165
the patched file shows as the old file. Accepting a
166
change will thus do nothing and rejecting a change
167
will copy the patched file over.""")
169
parser.add_option('-n', '--dry-run', '--preview', action='store_true',
170
help="""don't apply the patch, just show the
171
differences (you don't have to make any decisions).""")
173
parser.add_option('-B', '--no-backup', action='store_true',
174
help="disable backup filesa")
177
opts, args = parser.parse_args()
180
# read the input file.
196
"Error: reading patchfile %s: %s" % (fn, str(e)))
198
chunks += splitpatch(text)
201
# For each subpatch, apply it individually
203
for filename, patch in chunks:
208
# print patch contents for this file.
211
print "FILE:", filename
216
# feed diffs to patch, patch will do its deed and save the output to
218
tmpfp = NamedTemporaryFile(prefix=tmppfx)
220
popts = opts.patch_options
221
popts += ['--reverse', ''][ opts.reverse == None ]
223
popts += '--strip=%d' % opts.strip
224
cmd = 'patch %s --output "%s"' % (popts, tmpfp.name)
225
cin, cout = os.popen2(cmd, 'rw')
226
cin.write('Index: %s\n' % filename)
230
# read output from patch.
232
if cout.close() != None:
233
print >> sys.stderr, "Error: running patch."
239
# compute stripped filename
242
sfilename = os.sep.join(sfilename.split(os.sep)[opts.strip:])
245
leftfn, rightfn = sfilename, tmpfp.name
247
###FIXME not finished have to deal with deleted and added files
249
# swap files if requested
251
leftfn, rightfn = rightfn, leftfn
254
# calculate xxdiff options
257
# create temporary file to hold merged results.
258
tmpfm = NamedTemporaryFile('w', prefix=tmppfx)
259
dopts += '--decision --merged-filename "%s" ' % tmpfm.name
261
dopts += '--title%d="%s (patched)"' % (pno, sfilename)
262
dopts += opts.xxdiff_options
263
cmd = 'xxdiff %s "%s" "%s"' % (dopts, leftfn, rightfn)
266
s, o = commands.getstatusoutput(cmd)
267
except KeyboardInterrupt:
268
raise SystemExit("Interrupted.")
270
# print output of xxdiff command.
273
# perform the requested action
274
if o and not opts.dry_run:
278
if not opts.no_backup:
279
shutil.copyfile(sfilename, "%s.bak" % sfilename)
280
shutil.copyfile(rightfn, sfilename)
282
assert rightfn == sfilename
286
if not opts.no_backup:
287
shutil.copyfile(sfilename, "%s.bak" % sfilename)
288
shutil.copyfile(leftfn, sfilename)
290
assert leftfn == sfilename
293
if not opts.no_backup:
294
shutil.copyfile(sfilename, "%s.bak" % sfilename)
295
shutil.copyfile(tmpfm.name, sfilename)
297
elif o == 'NODECISION':
302
"Error: unexpected answer from xxdiff: %s" % o)
304
if __name__ == '__main__':