/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-04-12 12:53:54 UTC
  • Revision ID: tim@ed.am-20140412125354-64aysxvaz7nnk8hm
make verbose levels clearer

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 ):
44
45
                os.mkdir( self.dir )
45
46
 
46
47
                # 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:
 
48
                try:
 
49
                        self.run( [ 'bzr', 'init', '.' ] )
 
50
                except self.VcsError as e:
51
51
 
52
52
                        # attempt to clean-up dir
53
53
                        try:
55
55
                        except OSError:
56
56
                                pass
57
57
 
58
 
                        raise the.program.FatalError( 'bzr init failed', output )
 
58
                        raise
59
59
 
60
60
 
61
61
        def checkout( self, url ):
68
68
                os.mkdir( self.dir )
69
69
 
70
70
                # 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:
 
71
                try:
 
72
                        self.run( [ 'bzr', 'checkout', url, '.' ] )
 
73
                except self.VcsError as e:
75
74
 
76
75
                        # attempt to clean-up dir
77
76
                        try:
79
78
                        except OSError:
80
79
                                pass
81
80
 
82
 
                        raise the.program.FatalError( 'bzr checkout failed', output )
83
 
 
84
 
 
85
 
        def revert( self ):
 
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 ):
86
97
                """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.
87
100
                """
88
101
 
 
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
 
89
125
                # 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 )
 
126
                self.run( [ 'bzr', 'revert', '--no-backup' ] )
95
127
 
96
128
                # 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 )
 
129
                output = self.run( [ 'bzr', 'status' ] )
102
130
                files = self.parse_file_blocks( output )
103
131
 
104
132
                # remove unknown files
105
133
                if 'unknown' in files:
106
134
                        for file in files[ 'unknown' ]:
 
135
                                if the.verbose >= 2: print "removing (unknown): " + file
107
136
                                full_file = os.path.join( self.dir, file )
108
137
                                if os.path.isfile( full_file ):
109
138
                                        os.unlink( full_file )
110
 
                                elif os.full_file.isdir( full_file ):
 
139
                                elif os.path.isdir( full_file ):
111
140
                                        shutil.rmtree( full_file )
112
141
                                else:
113
142
                                        raise RuntimeError( 'exotic file in repo: %s' % file )
114
143
 
 
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
 
115
150
 
116
151
        def update( self ):
117
 
                """Update the branch, pulling down any upstream changes and merging them.
 
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.
118
155
                """
119
156
 
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 )
 
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
126
230
 
127
231
 
128
232
        def has_changes( self ):
130
234
                """
131
235
 
132
236
                # 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 )
 
237
                output = self.run( [ 'bzr', 'status', '--no-pending' ] )
 
238
 
 
239
                # parse output
138
240
                files = self.parse_file_blocks( output )
139
241
                return True if len( files ) else False
140
242
 
144
246
                """
145
247
 
146
248
                # 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 )
 
249
                output = self.run( [ 'bzr', 'status', '--no-pending' ] )
 
250
 
 
251
                # parse output
152
252
                files = self.parse_file_blocks( output )
153
253
                return files['conflicts'] if 'conflicts' in files else None
154
254
 
155
255
 
 
256
        def run( self, cmd ):
 
257
                if the.verbose >= 2: print 'exec: %s' % ' '.join( cmd )
 
258
                p = Popen( cmd, cwd = self.dir,
 
259
                                   stdout = subprocess.PIPE, stderr = subprocess.STDOUT )
 
260
                output = p.communicate()[ 0 ]
 
261
                if p.returncode > 0:
 
262
                        raise self.VcsError( ' '.join( cmd[ : 2 ] ), output )
 
263
                return output
 
264
 
 
265
 
156
266
        def parse_file_blocks( self, output ):
157
267
                res = dict()
158
268
                current = None
169
279
                                                res[ current ] = list()
170
280
                                        res[ current ].append( matches.group( 1 ) )
171
281
                                        continue
172
 
                        if re.search( '^[0-9]+ shelf exists', line ): continue
 
282
                        if re.search( '^[0-9]+ shel(?:f|ves) exists?', line ): continue
173
283
                        if re.search( '^working tree is out of date', line ): continue
174
284
                        raise self.ParseError( "unrecognised line: %s" % line )
175
285
                return res