/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:49 UTC
  • Revision ID: tim@ed.am-20140212215149-msaxl7vo98il5i4a
added more commands

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
46
44
                os.mkdir( self.dir )
47
45
 
48
46
                # bzr init
49
 
                try:
50
 
                        self.run( [ 'bzr', 'init', '.' ] )
51
 
                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:
52
51
 
53
52
                        # attempt to clean-up dir
54
53
                        try:
56
55
                        except OSError:
57
56
                                pass
58
57
 
59
 
                        raise
 
58
                        raise the.program.FatalError( 'bzr init failed', output )
60
59
 
61
60
 
62
61
        def checkout( self, url ):
69
68
                os.mkdir( self.dir )
70
69
 
71
70
                # bzr co
72
 
                try:
73
 
                        self.run( [ 'bzr', 'checkout', url, '.' ] )
74
 
                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:
75
75
 
76
76
                        # attempt to clean-up dir
77
77
                        try:
79
79
                        except OSError:
80
80
                                pass
81
81
 
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 ):
 
82
                        raise the.program.FatalError( 'bzr checkout failed', output )
 
83
 
 
84
 
 
85
        def revert( self ):
98
86
                """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.
101
87
                """
102
88
 
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
 
 
126
89
                # bzr revert
127
 
                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 )
128
95
 
129
96
                # bzr st
130
 
                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 )
131
102
                files = self.parse_file_blocks( output )
132
103
 
133
104
                # remove unknown files
134
105
                if 'unknown' in files:
135
106
                        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
142
107
                                full_file = os.path.join( self.dir, file )
143
 
                                if os.path.isfile( full_file ) or os.path.islink( full_file ):
 
108
                                if os.path.isfile( full_file ):
144
109
                                        os.unlink( full_file )
145
 
                                elif os.path.isdir( full_file ):
 
110
                                elif os.full_file.isdir( full_file ):
146
111
                                        shutil.rmtree( full_file )
147
112
                                else:
148
113
                                        raise RuntimeError( 'exotic file in repo: %s' % file )
149
114
 
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
 
 
156
115
 
157
116
        def update( self ):
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.
 
117
                """Update the branch, pulling down any upstream changes and merging them.
161
118
                """
162
119
 
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
 
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 )
235
126
 
236
127
 
237
128
        def has_changes( self ):
239
130
                """
240
131
 
241
132
                # bzr status
242
 
                output = self.run( [ 'bzr', 'status', '--no-pending' ] )
243
 
 
244
 
                # 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 )
245
138
                files = self.parse_file_blocks( output )
246
139
                return True if len( files ) else False
247
140
 
251
144
                """
252
145
 
253
146
                # bzr status
254
 
                output = self.run( [ 'bzr', 'status', '--no-pending' ] )
255
 
 
256
 
                # 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 )
257
152
                files = self.parse_file_blocks( output )
258
153
                return files['conflicts'] if 'conflicts' in files else None
259
154
 
260
155
 
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
 
 
290
156
        def parse_file_blocks( self, output ):
291
157
                res = dict()
292
158
                current = None
303
169
                                                res[ current ] = list()
304
170
                                        res[ current ].append( matches.group( 1 ) )
305
171
                                        continue
306
 
                        if re.search( '^[0-9]+ shel(?:f|ves) exists?', line ): continue
 
172
                        if re.search( '^[0-9]+ shelf exists', line ): continue
307
173
                        if re.search( '^working tree is out of date', line ): continue
308
174
                        raise self.ParseError( "unrecognised line: %s" % line )
309
175
                return res