3
# Copyright (C) 2014 Tim Marston <tim@edm.am>
5
# This file is part of stdhome (hereafter referred to as "this program").
6
# See http://ed.am/dev/stdhome for more information.
8
# This program is free software: you can redistribute it and/or modify
9
# it under the terms of the GNU General Public License as published by
10
# the Free Software Foundation, either version 3 of the License, or
11
# (at your option) any later version.
13
# This program is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
# GNU General Public License for more details.
18
# You should have received a copy of the GNU General Public License
19
# along with this program. If not, see <http://www.gnu.org/licenses/>.
22
import subprocess, os, re, shutil
23
from subprocess import Popen
24
import stdhome.the as the
31
def __init__( self, dir ):
34
@param dir the fully-qualified directory to work in.
40
"""Create a new, empty branch
43
# the directory shouldn't exist
47
p = Popen( [ 'bzr', 'init', '.' ], cwd = self.dir,
48
stdout = subprocess.PIPE, stderr = subprocess.STDOUT )
49
output = p.communicate()[ 0 ]
52
# attempt to clean-up dir
54
shutil.rmtree( self.dir )
58
raise the.program.FatalError( 'bzr init failed', output )
61
def checkout( self, url ):
62
"""Checkout a new copy of a remote branch.
64
@param url the remote repository URL
67
# the directory shouldn't exist
71
p = Popen( [ 'bzr', 'co', url, '.' ], cwd = self.dir,
72
stdout = subprocess.PIPE, stderr = subprocess.STDOUT )
73
output = p.communicate()[ 0 ]
76
# attempt to clean-up dir
78
shutil.rmtree( self.dir )
82
raise the.program.FatalError( 'bzr checkout failed', output )
86
"""Revert the branch so that there are no outstanding changes or unknown files.
90
p = Popen( [ 'bzr', 'revert', '--no-backup' ], cwd = self.dir,
91
stdout = subprocess.PIPE, stderr = subprocess.STDOUT )
92
output = p.communicate()[ 0 ]
94
raise the.program.FatalError( 'bzr revert failed', output )
97
p = Popen( [ 'bzr', 'st' ], cwd = self.dir,
98
stdout = subprocess.PIPE, stderr = subprocess.STDOUT )
99
output = p.communicate()[ 0 ]
101
raise the.program.FatalError( 'bzr status failed', output )
102
files = self.parse_file_blocks( output )
104
# remove unknown files
105
if 'unknown' in files:
106
for file in files[ 'unknown' ]:
107
full_file = os.path.join( self.dir, file )
108
if os.path.isfile( full_file ):
109
os.unlink( full_file )
110
elif os.full_file.isdir( full_file ):
111
shutil.rmtree( full_file )
113
raise RuntimeError( 'exotic file in repo: %s' % file )
117
"""Update the branch, pulling down any upstream changes and merging them.
121
p = Popen( [ 'bzr', 'update' ], cwd = self.dir,
122
stdout = subprocess.PIPE, stderr = subprocess.STDOUT )
123
output = p.communicate()[ 0 ]
125
raise the.program.FatalError( 'bzr update failed', output )
128
def has_changes( self ):
129
"""Check if the branch has any local modifications.
133
p = Popen( [ 'bzr', 'status', '--no-pending' ], cwd = self.dir,
134
stdout = subprocess.PIPE, stderr = subprocess.STDOUT )
135
output = p.communicate()[ 0 ]
137
raise the.program.FatalError( 'bzr status failed', output )
138
files = self.parse_file_blocks( output )
139
return True if len( files ) else False
142
def get_conflicts( self ):
143
"""Return a list of files that have conflicts.
147
p = Popen( [ 'bzr', 'status', '--no-pending' ], cwd = self.dir,
148
stdout = subprocess.PIPE, stderr = subprocess.STDOUT )
149
output = p.communicate()[ 0 ]
151
raise the.program.FatalError( 'bzr status failed', output )
152
files = self.parse_file_blocks( output )
153
return files['conflicts'] if 'conflicts' in files else None
156
def parse_file_blocks( self, output ):
159
buf = StringIO.StringIO( output )
161
matches = re.search( '^([a-z ]+):$', line, re.I )
163
current = matches.group( 1 )
166
matches = re.search( '^ ([^ ].*)$', line )
168
if not current in res:
169
res[ current ] = list()
170
res[ current ].append( matches.group( 1 ) )
172
if re.search( '^[0-9]+ shelf exists', line ): continue
173
if re.search( '^working tree is out of date', line ): continue
174
raise self.ParseError( "unrecognised line: %s" % line )
178
class ParseError( Exception ):