/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-28 17:51:41 UTC
  • Revision ID: tim@ed.am-20160228175141-paziccmndn03e3s8
don't attempt to add parent directories whena adding a file

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
 
 
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
 
39
55
        def init( self ):
40
56
                """Create a new, empty branch
41
57
                """
44
60
                os.mkdir( self.dir )
45
61
 
46
62
                # 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:
 
63
                try:
 
64
                        self.run( [ 'bzr', 'init', '.' ] )
 
65
                except self.VcsError as e:
51
66
 
52
67
                        # attempt to clean-up dir
53
68
                        try:
55
70
                        except OSError:
56
71
                                pass
57
72
 
58
 
                        raise the.program.FatalError( 'bzr init failed', output )
 
73
                        raise
59
74
 
60
75
 
61
76
        def checkout( self, url ):
68
83
                os.mkdir( self.dir )
69
84
 
70
85
                # 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:
 
86
                try:
 
87
                        self.run( [ 'bzr', 'checkout', url, '.' ] )
 
88
                except self.VcsError as e:
75
89
 
76
90
                        # attempt to clean-up dir
77
91
                        try:
79
93
                        except OSError:
80
94
                                pass
81
95
 
82
 
                        raise the.program.FatalError( 'bzr checkout failed', output )
83
 
 
84
 
 
85
 
        def revert( self ):
 
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 ):
86
112
                """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.
87
115
                """
88
116
 
 
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
 
89
140
                # 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 )
 
141
                self.run( [ 'bzr', 'revert', '--no-backup' ] )
95
142
 
96
143
                # 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 )
 
144
                output = self.run( [ 'bzr', 'status' ] )
102
145
                files = self.parse_file_blocks( output )
103
146
 
104
147
                # remove unknown files
105
148
                if 'unknown' in files:
106
149
                        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
107
156
                                full_file = os.path.join( self.dir, file )
108
 
                                if os.path.isfile( full_file ):
 
157
                                if os.path.isfile( full_file ) or os.path.islink( full_file ):
109
158
                                        os.unlink( full_file )
110
 
                                elif os.full_file.isdir( full_file ):
 
159
                                elif os.path.isdir( full_file ):
111
160
                                        shutil.rmtree( full_file )
112
161
                                else:
113
162
                                        raise RuntimeError( 'exotic file in repo: %s' % file )
114
163
 
 
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
 
115
170
 
116
171
        def update( self ):
117
 
                """Update the branch, pulling down any upstream changes and merging them.
 
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.
118
175
                """
119
176
 
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 )
 
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
126
249
 
127
250
 
128
251
        def has_changes( self ):
130
253
                """
131
254
 
132
255
                # 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 )
 
256
                output = self.run( [ 'bzr', 'status', '--no-pending' ] )
 
257
 
 
258
                # parse output
138
259
                files = self.parse_file_blocks( output )
139
260
                return True if len( files ) else False
140
261
 
144
265
                """
145
266
 
146
267
                # 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 )
 
268
                output = self.run( [ 'bzr', 'status', '--no-pending' ] )
 
269
 
 
270
                # parse output
152
271
                files = self.parse_file_blocks( output )
153
272
                return files['conflicts'] if 'conflicts' in files else None
154
273
 
155
274
 
 
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
 
156
306
        def parse_file_blocks( self, output ):
157
307
                res = dict()
158
308
                current = None
169
319
                                                res[ current ] = list()
170
320
                                        res[ current ].append( matches.group( 1 ) )
171
321
                                        continue
172
 
                        if re.search( '^[0-9]+ shelf exists', line ): continue
 
322
                        if re.search( '^[0-9]+ shel(?:f|ves) exists?', line ): continue
173
323
                        if re.search( '^working tree is out of date', line ): continue
174
324
                        raise self.ParseError( "unrecognised line: %s" % line )
175
325
                return res