~abentley/bzrtools/bzrtools.dev

257.1.2 by Aaron Bentley
Updated GPL notices
1
# Copyright (C) 2005 Aaron Bentley
2
# <aaron.bentley@utoronto.ca>
3
#
4
#    This program is free software; you can redistribute it and/or modify
5
#    it under the terms of the GNU General Public License as published by
6
#    the Free Software Foundation; either version 2 of the License, or
7
#    (at your option) any later version.
8
#
9
#    This program is distributed in the hope that it will be useful,
10
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
11
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
#    GNU General Public License for more details.
13
#
14
#    You should have received a copy of the GNU General Public License
15
#    along with this program; if not, write to the Free Software
16
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
321.2.1 by ghigo
add support for bazaar diff
17
import sys, os
152 by Aaron Bentley
Added new bzr patch command
18
from subprocess import Popen, PIPE
259 by Aaron Bentley
Got patch working with urls
19
from bzrlib.transport import get_transport
20
from urlparse import urlsplit, urlunsplit
321.2.3 by ghigo
Inform bzr of a renaming
21
from bzrlib.workingtree import WorkingTree
22
import bzrlib.add
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
23
import bzrlib.transform
24
import bzrlib.branch
25
import bzrlib.bzrdir
321.2.1 by ghigo
add support for bazaar diff
26
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
27
class BzrPatchProc:
28
    """This class process the bzr patch
321.2.1 by ghigo
add support for bazaar diff
29
30
        TODO:
31
          error handling
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
32
          execute attribute support
321.2.1 by ghigo
add support for bazaar diff
33
    """
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
34
    def __init__(self, tt, tree, bzrdir):
321.2.1 by ghigo
add support for bazaar diff
35
        self.link = None
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
36
        self.changes = []
321.2.5 by ghigo
Now the patch command work.
37
        self.mode = None
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
38
        self.command = None
39
        self.target_id = None
40
        self.tt = tt
321.2.3 by ghigo
Inform bzr of a renaming
41
        self.tree = tree
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
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
321.2.1 by ghigo
add support for bazaar diff
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:
321.2.5 by ghigo
Now the patch command work.
67
                return s[1:i],i+1
321.2.1 by ghigo
add support for bazaar diff
68
69
            i += 1
70
71
        assert(False)
72
321.2.5 by ghigo
Now the patch command work.
73
321.2.1 by ghigo
add support for bazaar diff
74
    def extractname(self, s):
321.2.5 by ghigo
Now the patch command work.
75
        space=-1
76
        for i in range(0,3):
77
            space = s.find(" ",space+1)    # find the 2nd space
78
        assert(space>=0)
321.2.1 by ghigo
add support for bazaar diff
79
        return self._extractname(s[space+1:])[0]
80
321.2.5 by ghigo
Now the patch command work.
81
321.2.1 by ghigo
add support for bazaar diff
82
    def extractnames(self, s):
321.2.5 by ghigo
Now the patch command work.
83
        space=-1
84
        for i in range(0,3):
85
            space = s.find(" ",space+1)    # find the 2nd space
86
        assert(space>=0)
321.2.1 by ghigo
add support for bazaar diff
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
321.2.5 by ghigo
Now the patch command work.
94
321.2.1 by ghigo
add support for bazaar diff
95
    def flush(self):
96
        self.process( )
97
321.2.5 by ghigo
Now the patch command work.
98
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
99
    def do_patch(self, changes, targetid):
321.2.5 by ghigo
Now the patch command work.
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
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
112
        self.changes = []
321.2.3 by ghigo
Inform bzr of a renaming
113
321.2.1 by ghigo
add support for bazaar diff
114
    def process(self, cmd = None):
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
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)
321.2.5 by ghigo
Now the patch command work.
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
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
131
            self.target_id = None
132
            self.changes = []
321.2.5 by ghigo
Now the patch command work.
133
            self.mode = None
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
134
135
        if cmd == None: return
136
        if not cmd.startswith("==="):
137
            if self.skipchange: return
138
            assert(self.target_id)
321.2.5 by ghigo
Now the patch command work.
139
            assert(self.mode)
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
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
321.2.5 by ghigo
Now the patch command work.
148
        self.mode = None
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
149
150
        if ( cmd.startswith("=== removed file") or
151
             cmd.startswith("=== removed directory") or
152
             cmd.startswith("=== removed symlink") ):
153
321.2.5 by ghigo
Now the patch command work.
154
            target = self.extractname(cmd)[2:]
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
155
            tid = self.get_transid(target)
156
            self.tt.delete_versioned(tid)
157
            print "removing '%s'"%target
158
            self.skipchange = True
321.2.5 by ghigo
Now the patch command work.
159
            self.mode = "rm"
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
160
161
        elif cmd.startswith("=== added file"):
321.2.5 by ghigo
Now the patch command work.
162
            
163
            target = self.extractname(cmd)[2:]
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
164
            self.target_id = self.get_transid(target)
321.2.5 by ghigo
Now the patch command work.
165
            #self.tt.create_file([],self.target_id)
166
            print "adding '%s'"%target
167
            self.mode = "add"
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
168
169
        elif cmd.startswith("=== modified file"):
321.2.5 by ghigo
Now the patch command work.
170
            target = self.extractname(cmd)[2:]
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
171
            self.target_id = self.get_transid(target)
321.2.5 by ghigo
Now the patch command work.
172
            self.mode = "mod"
173
            print "patching '%s'"%target
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
174
175
        elif cmd.startswith("=== added directory"):
321.2.5 by ghigo
Now the patch command work.
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
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
180
181
        elif cmd.startswith("=== added symlink"):
321.2.1 by ghigo
add support for bazaar diff
182
            assert(not self.link)
321.2.5 by ghigo
Now the patch command work.
183
            self.link = self.extractname(cmd)[2:]
321.2.1 by ghigo
add support for bazaar diff
184
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
185
        elif cmd.startswith("=== target is"):
321.2.1 by ghigo
add support for bazaar diff
186
            assert(self.link)
321.2.3 by ghigo
Inform bzr of a renaming
187
            target = self.extractname(cmd)
321.2.5 by ghigo
Now the patch command work.
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)
321.2.1 by ghigo
add support for bazaar diff
192
            self.link = None
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
193
194
        elif ( cmd.startswith("=== renamed symlink") or
195
               cmd.startswith("=== renamed file") or
196
               cmd.startswith("=== renamed directory") ):
197
321.2.5 by ghigo
Now the patch command work.
198
            source,dest = self.extractnames(cmd)
199
            source = source[2:]
200
            dest = dest[2:]
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
201
            sourceid = self.get_transid(source)
321.2.5 by ghigo
Now the patch command work.
202
            pdir,pname = os.path.split(dest)
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
203
            pdir_id = self.get_transid(pdir)
204
            self.tt.adjust_path(pname, pdir_id, sourceid)
205
321.2.5 by ghigo
Now the patch command work.
206
            if cmd.startswith("=== renamed file"):
207
                self.target_id = sourceid
208
                self.mode = "mv"
209
321.2.1 by ghigo
add support for bazaar diff
210
            print "renaming '%s' => '%s'"%(source,dest)
211
212
213
        else:
214
            sys.stderr.write("Unsupported tag: '%s'\n"%cmd)
215
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
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( )
321.2.1 by ghigo
add support for bazaar diff
234
340 by Aaron Bentley
Fixed patch on checkouts
235
def patch(tree, location, strip, legacy):
259 by Aaron Bentley
Got patch working with urls
236
    """Apply a patch to a branch, using patch(1).  URLs may be used."""
237
    my_file = None
238
    if location is None:
152 by Aaron Bentley
Added new bzr patch command
239
        my_file = sys.stdin
240
    else:
259 by Aaron Bentley
Got patch working with urls
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')
340 by Aaron Bentley
Fixed patch on checkouts
249
    cmd = ['patch', '--directory', tree.basedir, '--strip', str(strip)]
321.2.2 by ghigo
Initializate r before the use
250
    r = 0
321.2.1 by ghigo
add support for bazaar diff
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()
257
    else:
321.2.5 by ghigo
Now the patch command work.
258
321.2.4 by ghigo
Use the TreeTransform class, on the basis of the suggest of
259
        do_patch(sys.stdin.readlines( ))
340 by Aaron Bentley
Fixed patch on checkouts
260
    return r