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