/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-13 13:25:21 UTC
  • Revision ID: tim@ed.am-20160213132521-46v5774k6ql7tdfn
fixed uninitialised variable

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 revert
 
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.match( r'(.+?)[/@+]? \([^)]+\)', 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
                                if the.verbose >= 2: print "removing (unknown): " + file
107
137
                                full_file = os.path.join( self.dir, file )
108
138
                                if os.path.isfile( full_file ):
109
139
                                        os.unlink( full_file )
110
 
                                elif os.full_file.isdir( full_file ):
 
140
                                elif os.path.isdir( full_file ):
111
141
                                        shutil.rmtree( full_file )
112
142
                                else:
113
143
                                        raise RuntimeError( 'exotic file in repo: %s' % file )
114
144
 
 
145
                # if a revision identifier has been given, ensure we're updated to that
 
146
                if revno is not None and self.get_revno() != revno:
 
147
 
 
148
                        # bzr update
 
149
                        self.run( [ 'bzr', 'update', '-r', revno ] )
 
150
 
115
151
 
116
152
        def update( self ):
117
 
                """Update the branch, pulling down any upstream changes and merging them.
 
153
                """Update the branch, pulling down any upstream changes and merging them.  This
 
154
                method returns a list of the files that were modified as part of this
 
155
                operation.
118
156
                """
119
157
 
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 )
 
158
#               WARNING: the following might cause bzr to ask for your ssh password more than
 
159
#               once during an update!!!
 
160
#
 
161
#               # get revno
 
162
#               revno = self.get_revno()
 
163
#
 
164
#               # update to current revision (pull in history without updating tree)
 
165
#               self.run( [ 'bzr', 'update', '-r', revno ] )
 
166
#
 
167
#               # get log output
 
168
#               next_revno = str( int( revno ) + 1 )
 
169
#               output = self.run( [ 'bzr', 'log', '-r', next_revno + '..' ] )
 
170
#
 
171
#               # parse output
 
172
#               keep_files = list()
 
173
#               buf = StringIO.StringIO( output )
 
174
#               in_message = False
 
175
#               for line in buf:
 
176
#                       line = line.rstrip( '\n' )
 
177
#                       if line.lower() == 'message:':
 
178
#                               in_message = True
 
179
#                       elif in_message:
 
180
#                               if line[ : 2 ] != '  ':
 
181
#                                       in_message = False
 
182
#                               else:
 
183
#                                       line = line[ 2 : ]
 
184
#
 
185
#                                       # process directives
 
186
#                                       if line[ : 6 ].lower() == 'keep: ':
 
187
#                                               file = line[ 6 : ]
 
188
#                                               if file in rename_files: file = rename_files[ file ]
 
189
#                                               keep_files.append( file )
 
190
#                                       elif line[ : 8 ].lower() == 'rename: ':
 
191
#                                               rename_from = line[ 8 : ]
 
192
#                                       elif line[ : 4 ].lower() == 'to: ':
 
193
#                                               if rename_from in rename_files:
 
194
#                                                       rename_from = rename_files[ rename_from ]
 
195
#                                               rename_files[ line[ 4 : ] ] = rename_from
 
196
 
 
197
                # bzr update properly
 
198
                output = self.run( [ 'bzr', 'update' ] )
 
199
 
 
200
                # parse output (see logic in report() in bzrlib/delta.py)
 
201
                files = list()
 
202
                buf = StringIO.StringIO( output )
 
203
                for line in buf:
 
204
                        if not re.search( '^[-R+ ?][K NMD!][* ] ', line ): continue
 
205
                        line = line.rstrip()
 
206
                        if the.verbose >= 2: print '  %s' % line
 
207
 
 
208
                        # renames show before and after file names
 
209
                        matches = re.search( '^R.. (.*?)[/@+]? => (.*?)[/@+]?$', line )
 
210
                        if matches:
 
211
                                files.append( matches.group( 1 ) )
 
212
                                files.append( matches.group( 2 ) )
 
213
                                continue
 
214
 
 
215
                        # kind changes shows the same name twice
 
216
                        matches = re.search( '^.K. (.*?)[/@+]? => (.*?)[/@+]?$', line )
 
217
                        if matches:
 
218
                                files.append( matches.group( 1 ) )
 
219
                                continue
 
220
 
 
221
                        # other entries have only one filename
 
222
                        matches = re.search( '^... (.*?)[/@+]?$', line )
 
223
                        if matches:
 
224
                                files.append( matches.group( 1 ) )
 
225
                                continue
 
226
 
 
227
                        raise RuntimeError(
 
228
                                'failed to parse bzr update output line:\n%s' % line )
 
229
 
 
230
                return files
126
231
 
127
232
 
128
233
        def has_changes( self ):
130
235
                """
131
236
 
132
237
                # 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 )
 
238
                output = self.run( [ 'bzr', 'status', '--no-pending' ] )
 
239
 
 
240
                # parse output
138
241
                files = self.parse_file_blocks( output )
139
242
                return True if len( files ) else False
140
243
 
144
247
                """
145
248
 
146
249
                # 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 )
 
250
                output = self.run( [ 'bzr', 'status', '--no-pending' ] )
 
251
 
 
252
                # parse output
152
253
                files = self.parse_file_blocks( output )
153
254
                return files['conflicts'] if 'conflicts' in files else None
154
255
 
155
256
 
 
257
        def add( self, files ):
 
258
                """Make sure files are added to version control.
 
259
                @param files a list of relative filenames
 
260
                """
 
261
 
 
262
                # bzr add
 
263
                self.run( [ 'bzr', 'add', '-N' ] + files )
 
264
 
 
265
 
 
266
        def commit( self ):
 
267
                """Commit changes to the repo.
 
268
                """
 
269
 
 
270
                # bzr commit
 
271
                self.run( [ 'bzr', 'commit', '-m', '' ] )
 
272
 
 
273
 
 
274
        def run( self, cmd ):
 
275
                if the.verbose >= 2: print 'exec: %s' % ' '.join( cmd )
 
276
                p = Popen( cmd, cwd = self.dir,
 
277
                                   stdout = subprocess.PIPE, stderr = subprocess.STDOUT )
 
278
                output = p.communicate()[ 0 ]
 
279
                if p.returncode > 0:
 
280
                        raise self.VcsError( ' '.join( cmd[ : 2 ] ), output )
 
281
                return output
 
282
 
 
283
 
156
284
        def parse_file_blocks( self, output ):
157
285
                res = dict()
158
286
                current = None
169
297
                                                res[ current ] = list()
170
298
                                        res[ current ].append( matches.group( 1 ) )
171
299
                                        continue
172
 
                        if re.search( '^[0-9]+ shelf exists', line ): continue
 
300
                        if re.search( '^[0-9]+ shel(?:f|ves) exists?', line ): continue
173
301
                        if re.search( '^working tree is out of date', line ): continue
174
302
                        raise self.ParseError( "unrecognised line: %s" % line )
175
303
                return res