~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to patch.py

  • Committer: Aaron Bentley
  • Date: 2006-06-14 18:09:53 UTC
  • mto: This revision was merged to the branch mainline in revision 395.
  • Revision ID: abentley@panoramicfeedback.com-20060614180953-4671478534fd8823
Update NEWS

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2008 Aaron Bentley, 2006 Michael Ellerman
2
 
# <aaron@aaronbentley.com>
 
1
# Copyright (C) 2005 Aaron Bentley
 
2
# <aaron.bentley@utoronto.ca>
3
3
#
4
4
#    This program is free software; you can redistribute it and/or modify
5
5
#    it under the terms of the GNU General Public License as published by
14
14
#    You should have received a copy of the GNU General Public License
15
15
#    along with this program; if not, write to the Free Software
16
16
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
 
import sys
18
 
import subprocess
19
 
 
 
17
import sys, os
 
18
from subprocess import Popen, PIPE
 
19
from bzrlib.transport import get_transport
 
20
from urlparse import urlsplit, urlunsplit
 
21
from bzrlib.workingtree import WorkingTree
20
22
import bzrlib.add
21
 
 
22
 
from bzrlib.plugins.bzrtools.bzrtools import open_from_url
23
 
from errors import PatchFailed, PatchInvokeError
24
 
 
25
 
def patch(tree, location, strip, quiet=False):
 
23
import bzrlib.transform
 
24
import bzrlib.branch
 
25
import bzrlib.bzrdir
 
26
 
 
27
class BzrPatchProc:
 
28
    """This class process the bzr patch
 
29
 
 
30
        TODO:
 
31
          error handling
 
32
          execute attribute support
 
33
    """
 
34
    def __init__(self, tt, tree, bzrdir):
 
35
        self.link = None
 
36
        self.changes = []
 
37
        self.mode = None
 
38
        self.command = None
 
39
        self.target_id = None
 
40
        self.tt = tt
 
41
        self.tree = tree
 
42
        self.skipchange = True
 
43
 
 
44
        self.bzrdir = bzrdir
 
45
        self.branch =  self.bzrdir.open_branch( )
 
46
        self.repo = self.bzrdir.find_repository( )
 
47
        h = self.branch.revision_history( )
 
48
        self.inv = self.repo.get_inventory(h[-1])
 
49
 
 
50
 
 
51
    def get_transid(self, path):
 
52
        return self.tt.trans_id_tree_path(path)
 
53
 
 
54
 
 
55
    def _extractname(self, s):
 
56
        x = s[0]
 
57
        i = 1
 
58
        ls = len(s)
 
59
 
 
60
        while i < ls:
 
61
            if s[i] == '\\':
 
62
                assert(i+1 < ls )
 
63
                i += 2
 
64
                continue
 
65
 
 
66
            if s[i] == x:
 
67
                return s[1:i],i+1
 
68
 
 
69
            i += 1
 
70
 
 
71
        assert(False)
 
72
 
 
73
 
 
74
    def extractname(self, s):
 
75
        space=-1
 
76
        for i in range(0,3):
 
77
            space = s.find(" ",space+1)    # find the 2nd space
 
78
        assert(space>=0)
 
79
        return self._extractname(s[space+1:])[0]
 
80
 
 
81
 
 
82
    def extractnames(self, s):
 
83
        space=-1
 
84
        for i in range(0,3):
 
85
            space = s.find(" ",space+1)    # find the 2nd space
 
86
        assert(space>=0)
 
87
        s=s[space+1:]
 
88
        source, pos = self._extractname(s)
 
89
        assert( pos +4 < len(s) )
 
90
        dest,  dummy = self._extractname(s[pos+4:])
 
91
 
 
92
        return source,dest
 
93
 
 
94
 
 
95
    def flush(self):
 
96
        self.process( )
 
97
 
 
98
 
 
99
    def do_patch(self, changes, targetid):
 
100
        # apply change to target_id
 
101
        if 1:
 
102
            cmd = ['patch', '-o', self.tt._limbo_name(targetid),
 
103
                '--directory', self.branch.base, '-p1']
 
104
            #sys.stdout.write("# %r\n"%cmd)
 
105
            child_proc = Popen(cmd, stdin=PIPE)
 
106
            for line in changes:
 
107
                #sys.stdout.write("# %s\n"%line)
 
108
                child_proc.stdin.write(line+'\n')
 
109
            child_proc.stdin.close()
 
110
            r = child_proc.wait()
 
111
 
 
112
        self.changes = []
 
113
 
 
114
    def process(self, cmd = None):
 
115
 
 
116
        while cmd and len(cmd) and cmd[-1] in "\n\r":
 
117
            cmd = cmd[:-1]
 
118
 
 
119
        if ( cmd == None or cmd.startswith("===") ) and self.changes:
 
120
            assert(self.target_id)
 
121
 
 
122
            #print "********** mode=",self.mode
 
123
 
 
124
            if self.mode == "mv" or self.mode == "mod":
 
125
                self.tt.delete_contents(self.target_id)
 
126
 
 
127
            if self.mode == "add" or self.mode == "mod" or self.mode == "mv":
 
128
                self.tt.create_file([],self.target_id)
 
129
                self.do_patch(self.changes, self.target_id)
 
130
 
 
131
            self.target_id = None
 
132
            self.changes = []
 
133
            self.mode = None
 
134
 
 
135
        if cmd == None: return
 
136
        if not cmd.startswith("==="):
 
137
            if self.skipchange: return
 
138
            assert(self.target_id)
 
139
            assert(self.mode)
 
140
            self.changes.append(cmd)
 
141
            return
 
142
 
 
143
        # thr target_id is set during a rename; but there is possibility
 
144
        # that after a rename, is not to do a change
 
145
        self.target_id = None
 
146
        # skip change
 
147
        self.skipchange = False
 
148
        self.mode = None
 
149
 
 
150
        if ( cmd.startswith("=== removed file") or
 
151
             cmd.startswith("=== removed directory") or
 
152
             cmd.startswith("=== removed symlink") ):
 
153
 
 
154
            target = self.extractname(cmd)[2:]
 
155
            tid = self.get_transid(target)
 
156
            self.tt.delete_versioned(tid)
 
157
            print "removing '%s'"%target
 
158
            self.skipchange = True
 
159
            self.mode = "rm"
 
160
 
 
161
        elif cmd.startswith("=== added file"):
 
162
            
 
163
            target = self.extractname(cmd)[2:]
 
164
            self.target_id = self.get_transid(target)
 
165
            #self.tt.create_file([],self.target_id)
 
166
            print "adding '%s'"%target
 
167
            self.mode = "add"
 
168
 
 
169
        elif cmd.startswith("=== modified file"):
 
170
            target = self.extractname(cmd)[2:]
 
171
            self.target_id = self.get_transid(target)
 
172
            self.mode = "mod"
 
173
            print "patching '%s'"%target
 
174
 
 
175
        elif cmd.startswith("=== added directory"):
 
176
            target = self.extractname(cmd)[2:]
 
177
            target_id = self.get_transid(target)
 
178
            self.tt.create_directory(target_id)
 
179
            print "adding directory '%s'"%target
 
180
 
 
181
        elif cmd.startswith("=== added symlink"):
 
182
            assert(not self.link)
 
183
            self.link = self.extractname(cmd)[2:]
 
184
 
 
185
        elif cmd.startswith("=== target is"):
 
186
            assert(self.link)
 
187
            target = self.extractname(cmd)
 
188
            link_id = self.get_transid(self.link)
 
189
            
 
190
            print "symlinking '%s' => '%s'\n"%(target, link_id)
 
191
            self.tt.create_symlink(target, link_id)
 
192
            self.link = None
 
193
 
 
194
        elif ( cmd.startswith("=== renamed symlink") or
 
195
               cmd.startswith("=== renamed file") or
 
196
               cmd.startswith("=== renamed directory") ):
 
197
 
 
198
            source,dest = self.extractnames(cmd)
 
199
            source = source[2:]
 
200
            dest = dest[2:]
 
201
            sourceid = self.get_transid(source)
 
202
            pdir,pname = os.path.split(dest)
 
203
            pdir_id = self.get_transid(pdir)
 
204
            self.tt.adjust_path(pname, pdir_id, sourceid)
 
205
 
 
206
            if cmd.startswith("=== renamed file"):
 
207
                self.target_id = sourceid
 
208
                self.mode = "mv"
 
209
 
 
210
            print "renaming '%s' => '%s'"%(source,dest)
 
211
 
 
212
 
 
213
        else:
 
214
            sys.stderr.write("Unsupported tag: '%s'\n"%cmd)
 
215
 
 
216
    def apply(self):
 
217
        self.tt.apply( )
 
218
 
 
219
 
 
220
 
 
221
def do_patch(p):
 
222
 
 
223
    tree = WorkingTree.open_containing(u'.')[0]
 
224
    tt = bzrlib.transform.TreeTransform(tree)
 
225
    bzrdir = bzrlib.bzrdir.BzrDir.open(".")
 
226
    bzr_tags_proc = BzrPatchProc(tt, tree, bzrdir)
 
227
 
 
228
    for l in p:
 
229
        # we have to remove the \n\r
 
230
        bzr_tags_proc.process(l)
 
231
 
 
232
    bzr_tags_proc.flush( )
 
233
    bzr_tags_proc.apply( )
 
234
 
 
235
def patch(tree, location, strip, legacy):
26
236
    """Apply a patch to a branch, using patch(1).  URLs may be used."""
27
237
    my_file = None
28
238
    if location is None:
29
239
        my_file = sys.stdin
30
240
    else:
31
 
        my_file = open_from_url(location)
32
 
    patches = [my_file.read()]
33
 
    return run_patch(tree.basedir, patches, strip, quiet=quiet)
34
 
 
35
 
 
36
 
def run_patch(directory, patches, strip=0, reverse=False, dry_run=False,
37
 
              quiet=False, _patch_cmd='patch', target_file=None):
38
 
    args = [_patch_cmd, '-d', directory, '-s', '-p%d' % strip, '-f']
39
 
    if quiet:
40
 
        args.append('--quiet')
41
 
 
42
 
    if sys.platform == "win32":
43
 
        args.append('--binary')
44
 
 
45
 
    if reverse:
46
 
        args.append('-R')
47
 
    if dry_run:
48
 
        if sys.platform.startswith('freebsd'):
49
 
            args.append('--check')
50
 
        else:
51
 
            args.append('--dry-run')
52
 
        stderr = subprocess.PIPE
 
241
        for prefix in ('http://', 'sftp://', 'file://'):
 
242
            if not location.startswith(prefix):
 
243
                continue
 
244
            (scheme, loc, path, query, fragment) = urlsplit(location)
 
245
            loc_start = urlunsplit((scheme, loc, '/', '', ''))
 
246
            my_file = get_transport(loc_start).get(path[1:])
 
247
        if my_file is None:
 
248
            my_file = file(location, 'rb')
 
249
    cmd = ['patch', '--directory', tree.basedir, '--strip', str(strip)]
 
250
    r = 0
 
251
    if legacy:
 
252
        child_proc = Popen(cmd, stdin=PIPE)
 
253
        for line in my_file:
 
254
            child_proc.stdin.write(line)
 
255
        child_proc.stdin.close()
 
256
        r = child_proc.wait()
53
257
    else:
54
 
        stderr = None
55
 
    if target_file is not None:
56
 
        args.append(target_file)
57
 
 
58
 
    try:
59
 
        process = subprocess.Popen(args, stdin=subprocess.PIPE,
60
 
                                   stdout=subprocess.PIPE, stderr=stderr)
61
 
    except OSError, e:
62
 
        raise PatchInvokeError(e)
63
 
    try:
64
 
        for patch in patches:
65
 
            process.stdin.write(str(patch))
66
 
        process.stdin.close()
67
 
 
68
 
    except IOError, e:
69
 
        raise PatchInvokeError(e, process.stderr.read())
70
 
 
71
 
    result = process.wait()
72
 
    if not dry_run:
73
 
        sys.stdout.write(process.stdout.read())
74
 
    if result != 0:
75
 
        raise PatchFailed()
76
 
 
77
 
    return result
 
258
 
 
259
        do_patch(sys.stdin.readlines( ))
 
260
    return r