/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: 2014-02-12 21:51:08 UTC
  • Revision ID: tim@ed.am-20140212215108-stk5z0nlvgpi4oa8
added bzr as a vcs backend; finished init command; implemented deployment

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