/stdhome

To get this branch, use:
bzr branch http://bzr.ed.am/stdhome

« back to all changes in this revision

Viewing changes to lib/stdhome/vcs/bzr.py

  • Committer: Tim Marston
  • Date: 2016-02-23 19:35:21 UTC
  • Revision ID: tim@ed.am-20160223193521-2vgtxbfos50rrpku
renamed version -> VERSION

Show diffs side-by-side

added added

removed removed

21
21
 
22
22
import subprocess, os, re, shutil
23
23
from subprocess import Popen
24
 
import stdhome.the as the
25
24
import StringIO
26
 
 
27
 
 
28
 
class VcsBzr:
 
25
from vcs import Vcs
 
26
from stdhome import the
 
27
 
 
28
 
 
29
class BzrVcs( Vcs ):
29
30
 
30
31
 
31
32
        def __init__( self, dir ):
33
34
 
34
35
                @param dir the fully-qualified directory to work in.
35
36
                """
 
37
 
36
38
                self.dir = dir
37
39
 
38
40
 
44
46
                os.mkdir( self.dir )
45
47
 
46
48
                # bzr init
47
 
                p = Popen( [ 'bzr', 'init', '.' ], cwd = self.dir,
48
 
                                   stdout = subprocess.PIPE, stderr = subprocess.STDOUT )
49
 
                output = p.communicate()[ 0 ]
50
 
                if p.returncode > 0:
 
49
                try:
 
50
                        self.run( [ 'bzr', 'init', '.' ] )
 
51
                except self.VcsError as e:
51
52
 
52
53
                        # attempt to clean-up dir
53
54
                        try:
55
56
                        except OSError:
56
57
                                pass
57
58
 
58
 
                        raise the.program.FatalError( 'bzr init failed', output )
 
59
                        raise
59
60
 
60
61
 
61
62
        def checkout( self, url ):
68
69
                os.mkdir( self.dir )
69
70
 
70
71
                # bzr co
71
 
                p = Popen( [ 'bzr', 'co', url, '.' ], cwd = self.dir,
72
 
                                   stdout = subprocess.PIPE, stderr = subprocess.STDOUT )
73
 
                output = p.communicate()[ 0 ]
74
 
                if p.returncode > 0:
 
72
                try:
 
73
                        self.run( [ 'bzr', 'checkout', url, '.' ] )
 
74
                except self.VcsError as e:
75
75
 
76
76
                        # attempt to clean-up dir
77
77
                        try:
79
79
                        except OSError:
80
80
                                pass
81
81
 
82
 
                        raise the.program.FatalError( 'bzr checkout failed', output )
83
 
 
84
 
 
85
 
        def revert( self ):
 
82
                        raise
 
83
 
 
84
 
 
85
        def get_revno( self ):
 
86
                """Obtain some sort of revision identifier
 
87
                """
 
88
 
 
89
                # bzr revno
 
90
                output = self.run( [ 'bzr', 'revno', '--tree' ] )
 
91
 
 
92
                # parse revno
 
93
                buf = StringIO.StringIO( output )
 
94
                return buf.readline().rstrip()
 
95
 
 
96
 
 
97
        def revert( self, revno = None ):
86
98
                """Revert the branch so that there are no outstanding changes or unknown files.
 
99
                If a revno is supplied, then the repository is reverted to that
 
100
                revision.
87
101
                """
88
102
 
 
103
                # bzr st
 
104
                output = self.run( [ 'bzr', 'status' ] )
 
105
                files = self.parse_file_blocks( output )
 
106
 
 
107
                # remove kind changed files (or they can cause `bzr revert` to break in
 
108
                # strange situations, like when a directory has been replaced with a
 
109
                # symlink to a non-existant file)
 
110
                if 'kind changed' in files:
 
111
                        for file in files[ 'kind changed' ]:
 
112
                                matches = re.search( '^(.+?)[/@+]? \([^)]+\)$', file )
 
113
                                if not matches:
 
114
                                        raise RunTimeError(
 
115
                                                'failed to parse bzr kind change: %s' % file )
 
116
                                file = matches.group( 1 )
 
117
                                if the.verbose >= 2: print "removing (kind changed): " + file
 
118
                                full_file = os.path.join( self.dir, file )
 
119
                                if os.path.isfile( full_file ) or os.path.islink( full_file ):
 
120
                                        os.unlink( full_file )
 
121
                                elif os.path.isdir( full_file ):
 
122
                                        shutil.rmtree( full_file )
 
123
                                else:
 
124
                                        raise RuntimeError( 'exotic file in repo: %s' % file )
 
125
 
89
126
                # bzr revert
90
 
                p = Popen( [ 'bzr', 'revert', '--no-backup' ], cwd = self.dir,
91
 
                                   stdout = subprocess.PIPE, stderr = subprocess.STDOUT )
92
 
                output = p.communicate()[ 0 ]
93
 
                if p.returncode > 0:
94
 
                        raise the.program.FatalError( 'bzr revert failed', output )
 
127
                self.run( [ 'bzr', 'revert', '--no-backup' ] )
95
128
 
96
129
                # bzr st
97
 
                p = Popen( [ 'bzr', 'st' ], cwd = self.dir,
98
 
                                   stdout = subprocess.PIPE, stderr = subprocess.STDOUT )
99
 
                output = p.communicate()[ 0 ]
100
 
                if p.returncode > 0:
101
 
                        raise the.program.FatalError( 'bzr status failed', output )
 
130
                output = self.run( [ 'bzr', 'status' ] )
102
131
                files = self.parse_file_blocks( output )
103
132
 
104
133
                # remove unknown files
105
134
                if 'unknown' in files:
106
135
                        for file in files[ 'unknown' ]:
 
136
                                matches = re.search( r'^(.+?)[/@+]?$', file )
 
137
                                if not matches:
 
138
                                        raise RunTimeError(
 
139
                                                'failed to parse bzr unknowns: %s' % file )
 
140
                                file = matches.group( 1 )
 
141
                                if the.verbose >= 2: print "removing (unknown): " + file
107
142
                                full_file = os.path.join( self.dir, file )
108
 
                                if os.path.isfile( full_file ):
 
143
                                if os.path.isfile( full_file ) or os.path.islink( full_file ):
109
144
                                        os.unlink( full_file )
110
 
                                elif os.full_file.isdir( full_file ):
 
145
                                elif os.path.isdir( full_file ):
111
146
                                        shutil.rmtree( full_file )
112
147
                                else:
113
148
                                        raise RuntimeError( 'exotic file in repo: %s' % file )
114
149
 
 
150
                # if a revision identifier has been given, ensure we're updated to that
 
151
                if revno is not None and self.get_revno() != revno:
 
152
 
 
153
                        # bzr update
 
154
                        self.run( [ 'bzr', 'update', '-r', revno ] )
 
155
 
115
156
 
116
157
        def update( self ):
117
 
                """Update the branch, pulling down any upstream changes and merging them.
 
158
                """Update the branch, pulling down any upstream changes and merging them.  This
 
159
                method returns a list of the files that were modified as part of this
 
160
                operation.
118
161
                """
119
162
 
120
 
                # bzr update
121
 
                p = Popen( [ 'bzr', 'update' ], cwd = self.dir,
122
 
                                   stdout = subprocess.PIPE, stderr = subprocess.STDOUT )
123
 
                output = p.communicate()[ 0 ]
124
 
                if p.returncode > 0:
125
 
                        raise the.program.FatalError( 'bzr update failed', output )
 
163
#               WARNING: the following might cause bzr to ask for your ssh password more than
 
164
#               once during an update!!!
 
165
#
 
166
#               # get revno
 
167
#               revno = self.get_revno()
 
168
#
 
169
#               # update to current revision (pull in history without updating tree)
 
170
#               self.run( [ 'bzr', 'update', '-r', revno ] )
 
171
#
 
172
#               # get log output
 
173
#               next_revno = str( int( revno ) + 1 )
 
174
#               output = self.run( [ 'bzr', 'log', '-r', next_revno + '..' ] )
 
175
#
 
176
#               # parse output
 
177
#               keep_files = list()
 
178
#               buf = StringIO.StringIO( output )
 
179
#               in_message = False
 
180
#               for line in buf:
 
181
#                       line = line.rstrip( '\n' )
 
182
#                       if line.lower() == 'message:':
 
183
#                               in_message = True
 
184
#                       elif in_message:
 
185
#                               if line[ : 2 ] != '  ':
 
186
#                                       in_message = False
 
187
#                               else:
 
188
#                                       line = line[ 2 : ]
 
189
#
 
190
#                                       # process directives
 
191
#                                       if line[ : 6 ].lower() == 'keep: ':
 
192
#                                               file = line[ 6 : ]
 
193
#                                               if file in rename_files: file = rename_files[ file ]
 
194
#                                               keep_files.append( file )
 
195
#                                       elif line[ : 8 ].lower() == 'rename: ':
 
196
#                                               rename_from = line[ 8 : ]
 
197
#                                       elif line[ : 4 ].lower() == 'to: ':
 
198
#                                               if rename_from in rename_files:
 
199
#                                                       rename_from = rename_files[ rename_from ]
 
200
#                                               rename_files[ line[ 4 : ] ] = rename_from
 
201
 
 
202
                # bzr update properly
 
203
                output = self.run( [ 'bzr', 'update' ] )
 
204
 
 
205
                # parse output (see logic in report() in bzrlib/delta.py)
 
206
                files = list()
 
207
                buf = StringIO.StringIO( output )
 
208
                for line in buf:
 
209
                        if not re.search( '^[-R+ ?][K NMD!][* ] ', line ): continue
 
210
                        line = line.rstrip()
 
211
 
 
212
                        # renames show before and after file names
 
213
                        matches = re.search( '^R.. (.*?)[/@+]? => (.*?)[/@+]?$', line )
 
214
                        if matches:
 
215
                                files.append( matches.group( 1 ) )
 
216
                                files.append( matches.group( 2 ) )
 
217
                                continue
 
218
 
 
219
                        # kind changes shows the same name twice
 
220
                        matches = re.search( '^.K. (.*?)[/@+]? => (.*?)[/@+]?$', line )
 
221
                        if matches:
 
222
                                files.append( matches.group( 1 ) )
 
223
                                continue
 
224
 
 
225
                        # other entries have only one filename
 
226
                        matches = re.search( '^... (.*?)[/@+]?$', line )
 
227
                        if matches:
 
228
                                files.append( matches.group( 1 ) )
 
229
                                continue
 
230
 
 
231
                        raise RuntimeError(
 
232
                                'failed to parse bzr update output line:\n%s' % line )
 
233
 
 
234
                return files
126
235
 
127
236
 
128
237
        def has_changes( self ):
130
239
                """
131
240
 
132
241
                # bzr status
133
 
                p = Popen( [ 'bzr', 'status', '--no-pending' ], cwd = self.dir,
134
 
                                   stdout = subprocess.PIPE, stderr = subprocess.STDOUT )
135
 
                output = p.communicate()[ 0 ]
136
 
                if p.returncode > 0:
137
 
                        raise the.program.FatalError( 'bzr status failed', output )
 
242
                output = self.run( [ 'bzr', 'status', '--no-pending' ] )
 
243
 
 
244
                # parse output
138
245
                files = self.parse_file_blocks( output )
139
246
                return True if len( files ) else False
140
247
 
144
251
                """
145
252
 
146
253
                # bzr status
147
 
                p = Popen( [ 'bzr', 'status', '--no-pending' ], cwd = self.dir,
148
 
                                   stdout = subprocess.PIPE, stderr = subprocess.STDOUT )
149
 
                output = p.communicate()[ 0 ]
150
 
                if p.returncode > 0:
151
 
                        raise the.program.FatalError( 'bzr status failed', output )
 
254
                output = self.run( [ 'bzr', 'status', '--no-pending' ] )
 
255
 
 
256
                # parse output
152
257
                files = self.parse_file_blocks( output )
153
258
                return files['conflicts'] if 'conflicts' in files else None
154
259
 
155
260
 
 
261
        def add( self, files ):
 
262
                """Make sure files are added to version control.
 
263
                @param files a list of relative filenames
 
264
                """
 
265
 
 
266
                # bzr add
 
267
                self.run( [ 'bzr', 'add', '-N' ] + files )
 
268
 
 
269
 
 
270
        def commit( self ):
 
271
                """Commit changes to the repo.
 
272
                """
 
273
 
 
274
                # bzr commit
 
275
                self.run( [ 'bzr', 'commit', '-m', '' ] )
 
276
 
 
277
 
 
278
        def run( self, cmd ):
 
279
                if the.verbose >= 2: print 'exec: %s' % ' '.join( cmd )
 
280
                p = Popen( cmd, cwd = self.dir,
 
281
                                   stdout = subprocess.PIPE, stderr = subprocess.STDOUT )
 
282
                output = p.communicate()[ 0 ]
 
283
                if p.returncode > 0:
 
284
                        raise self.VcsError( ' '.join( cmd[ : 2 ] ), output )
 
285
                if the.verbose >= 2:
 
286
                        print re.sub( '(^|\n)', '\\1  > ', output.rstrip() )
 
287
                return output
 
288
 
 
289
 
156
290
        def parse_file_blocks( self, output ):
157
291
                res = dict()
158
292
                current = None
169
303
                                                res[ current ] = list()
170
304
                                        res[ current ].append( matches.group( 1 ) )
171
305
                                        continue
172
 
                        if re.search( '^[0-9]+ shelf exists', line ): continue
 
306
                        if re.search( '^[0-9]+ shel(?:f|ves) exists?', line ): continue
173
307
                        if re.search( '^working tree is out of date', line ): continue
174
308
                        raise self.ParseError( "unrecognised line: %s" % line )
175
309
                return res