~ubuntu-branches/ubuntu/hardy/xxdiff/hardy

« back to all changes in this revision

Viewing changes to bin/xxdiff-patch-UNFINISHED

  • Committer: Bazaar Package Importer
  • Author(s): Tomas Pospisek
  • Date: 2005-03-29 08:43:56 UTC
  • mfrom: (1.2.1 upstream) (2.1.2 hoary)
  • Revision ID: james.westby@ubuntu.com-20050329084356-nkwv8jf18nc7u5qf
Tags: 1:3.1-3
re-upload

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
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 $
 
6
#*
 
7
#* Copyright (C) 2003-2004 Martin Blais <blais@furius.ca>
 
8
#*
 
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.
 
13
#*
 
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.
 
18
#*
 
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.
 
22
#*
 
23
#*****************************************************************************/
 
24
 
 
25
"""xxdiff-patch [<options>] [<patchfile>]
 
26
 
 
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.
 
33
 
 
34
If no <patchfile> is specified, xxdiff-patch attempts to read the patch from
 
35
stdin.
 
36
 
 
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:
 
41
 
 
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;
 
45
 
 
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.
 
48
 
 
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.
 
58
 
 
59
Examples
 
60
--------
 
61
 
 
62
  To apply a patch, simply cd into the root of the directory where the patch
 
63
  should be applied, and invoke like this:
 
64
 
 
65
     cat patchfile | xxdiff-patch
 
66
 
 
67
  To preview the changes that have been made in a cvs checkout area, use the
 
68
  following command::
 
69
 
 
70
     cvs diff | xxdiff-patch --reverse --invert --preview
 
71
 
 
72
  (For better cvs-specific behaviour about committing changes, see script
 
73
  xxdiff-cvs-diff.)
 
74
 
 
75
.. important::
 
76
 
 
77
   This script is unfinished, we still need to implement file addition/removal
 
78
   support.
 
79
 
 
80
"""
 
81
 
 
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."""
 
88
 
 
89
import sys
 
90
print >> sys.stderr, \
 
91
    "Error: FIXME not finished have yet to deal with deleted and added files"
 
92
 
 
93
#===============================================================================
 
94
# EXTERNAL DECLARATIONS
 
95
#===============================================================================
 
96
 
 
97
import sys, os, os.path
 
98
import re
 
99
import commands, shutil
 
100
from tempfile import NamedTemporaryFile
 
101
 
 
102
#===============================================================================
 
103
# LOCAL DECLARATIONS
 
104
#===============================================================================
 
105
 
 
106
tmppfx = '%s.' % os.path.basename(sys.argv[0])
 
107
 
 
108
#-------------------------------------------------------------------------------
 
109
#
 
110
def splitpatch( text ):
 
111
 
 
112
    """Split output in chunks starting with ^Index.  Returns a list of pairs
 
113
    (tuples), each with (filename, patch) contents."""
 
114
 
 
115
    ## splitre = re.compile('^Index: (.*)$', re.M)
 
116
    splitre = re.compile('^diff (.*)\s+(\S+)\s+(\S+)\s*$', re.M)
 
117
    chunks = []
 
118
    curbeg, curfn = None, None
 
119
    for mo in splitre.finditer(text):
 
120
        if curbeg != None:
 
121
            assert curfn
 
122
            chunks.append( (curfn, text[curbeg:mo.start()]) )
 
123
        curbeg = mo.start()
 
124
        curfn = mo.groups()[-1]
 
125
    if curbeg != None:
 
126
        chunks.append( (curfn, text[curbeg:]) )
 
127
 
 
128
    return chunks
 
129
 
 
130
#-------------------------------------------------------------------------------
 
131
#
 
132
def complete( parser ):
 
133
    "Programmable completion support. Script should work without it."
 
134
    try:
 
135
        import optcomplete
 
136
        optcomplete.autocomplete(parser)
 
137
    except ImportError:
 
138
        pass
 
139
 
 
140
#===============================================================================
 
141
# MAIN
 
142
#===============================================================================
 
143
 
 
144
def main():
 
145
    import optparse
 
146
    parser = optparse.OptionParser(__doc__.strip(), version=__version__)
 
147
 
 
148
    parser.add_option('-x', '--xxdiff-options', action='store', metavar="OPTS",
 
149
                      default='',
 
150
                      help="specifies additional options to pass on to xxdiff.")
 
151
 
 
152
    parser.add_option('-p', '--patch-options', action='store', metavar="OPTS",
 
153
                      default='',
 
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.")
 
157
 
 
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.""")
 
162
 
 
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.""")
 
168
 
 
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).""")
 
172
 
 
173
    parser.add_option('-B', '--no-backup', action='store_true',
 
174
                      help="disable backup filesa")
 
175
 
 
176
    complete(parser)
 
177
    opts, args = parser.parse_args()
 
178
 
 
179
    #
 
180
    # read the input file.
 
181
    #
 
182
    if not args:
 
183
        args = ['-']
 
184
 
 
185
    chunks = []
 
186
    for fn in args:
 
187
        try:
 
188
            if fn == '-':
 
189
                iif = sys.stdin
 
190
            else:
 
191
                iif = open(fn, 'r')
 
192
            text = iif.read()
 
193
            iif.close()
 
194
        except IOError, e:
 
195
            raise SystemExit(
 
196
                "Error: reading patchfile %s: %s" % (fn, str(e)))
 
197
 
 
198
        chunks += splitpatch(text)
 
199
 
 
200
    #
 
201
    # For each subpatch, apply it individually
 
202
    #
 
203
    for filename, patch in chunks:
 
204
        ## print '*' * 80
 
205
        ## print fn
 
206
        ## print text
 
207
 
 
208
        # print patch contents for this file.
 
209
        print
 
210
        print
 
211
        print "FILE:", filename
 
212
        print '*' * 40
 
213
        print patch
 
214
        print '*' * 40
 
215
 
 
216
        # feed diffs to patch, patch will do its deed and save the output to
 
217
        # a temporary file.
 
218
        tmpfp = NamedTemporaryFile(prefix=tmppfx)
 
219
 
 
220
        popts = opts.patch_options
 
221
        popts += ['--reverse', ''][ opts.reverse == None ]
 
222
        if opts.strip:
 
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)
 
227
        cin.write(patch)
 
228
        cin.close()
 
229
 
 
230
        # read output from patch.
 
231
        print cout.read()
 
232
        if cout.close() != None:
 
233
            print >> sys.stderr, "Error: running patch."
 
234
 
 
235
        #
 
236
        # spawn xxdiff
 
237
        #
 
238
 
 
239
        # compute stripped filename
 
240
        sfilename = filename
 
241
        if opts.strip:
 
242
            sfilename = os.sep.join(sfilename.split(os.sep)[opts.strip:])
 
243
 
 
244
        pno = 2
 
245
        leftfn, rightfn = sfilename, tmpfp.name
 
246
 
 
247
###FIXME not finished have to deal with deleted and added files
 
248
 
 
249
        # swap files if requested
 
250
        if opts.invert:
 
251
            leftfn, rightfn = rightfn, leftfn
 
252
            pno = 1
 
253
 
 
254
        # calculate xxdiff options
 
255
        dopts = ''
 
256
        if not opts.dry_run:
 
257
            # create temporary file to hold merged results.
 
258
            tmpfm = NamedTemporaryFile('w', prefix=tmppfx)
 
259
            dopts += '--decision --merged-filename "%s" ' % tmpfm.name
 
260
 
 
261
        dopts += '--title%d="%s (patched)"' % (pno, sfilename)
 
262
        dopts += opts.xxdiff_options
 
263
        cmd = 'xxdiff %s "%s" "%s"' % (dopts, leftfn, rightfn)
 
264
 
 
265
        try:
 
266
            s, o = commands.getstatusoutput(cmd)
 
267
        except KeyboardInterrupt:
 
268
            raise SystemExit("Interrupted.")
 
269
 
 
270
        # print output of xxdiff command.
 
271
        if o: print o
 
272
 
 
273
        # perform the requested action
 
274
        if o and not opts.dry_run:
 
275
 
 
276
            if o == 'ACCEPT':
 
277
                if not opts.invert:
 
278
                    if not opts.no_backup:
 
279
                        shutil.copyfile(sfilename, "%s.bak" % sfilename)
 
280
                    shutil.copyfile(rightfn, sfilename)
 
281
                else:
 
282
                    assert rightfn == sfilename
 
283
 
 
284
            elif o == 'REJECT':
 
285
                if not opts.invert:
 
286
                    if not opts.no_backup:
 
287
                        shutil.copyfile(sfilename, "%s.bak" % sfilename)
 
288
                    shutil.copyfile(leftfn, sfilename)
 
289
                else:
 
290
                    assert leftfn == sfilename
 
291
 
 
292
            elif o == 'MERGED':
 
293
                if not opts.no_backup:
 
294
                    shutil.copyfile(sfilename, "%s.bak" % sfilename)
 
295
                shutil.copyfile(tmpfm.name, sfilename)
 
296
 
 
297
            elif o == 'NODECISION':
 
298
                pass # do nothing
 
299
 
 
300
            else:
 
301
                raise SystemExit(
 
302
                        "Error: unexpected answer from xxdiff: %s" % o)
 
303
 
 
304
if __name__ == '__main__':
 
305
    main()