3
# Copyright (C) 2013 to 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 os, re, shutil, filecmp, json
24
from walker.copy_in import CopyInWalker
25
from walker.conflict import ConflictWalker
26
from walker.copy_out import CopyOutWalker
34
raise RuntimeError( 'logic error: Deployment initialised when '
36
self.load_deployment_state()
37
self.conflicts_checked = False
40
def load_deployment_state( self ):
41
"""Load any deployment state. If one is found then a deployment will be
42
considered to be ongoing.
45
# list of files that were copied-in (or at least given the opportunity
46
# to be) and updated through the vcs update. This means that, while
47
# there may have been conflicts during the update (which will be dealt
48
# with in the repo), any conflicts arising with these files in the home
49
# directory are no longer important and can be ignored. In short, this
50
# is a list of files that it is safe to deploy, regardless of their
51
# state in the home directory.
52
self.deploy_files = None
54
# list of files that were affected by a recent vcs update (so only these
55
# files need to be checked for deployment conflicts or copied-out)
56
self.affected_files = None
58
# the revno that the repo was as prior to a recent update
59
self.initial_revno = None
62
if not os.path.exists( the.repo.full_dir ): return
64
# if no file list exists, we're done
65
file = os.path.join( the.full_mddir, "deploy.%s" % the.repo.name )
66
if not os.path.isfile( file ):
70
if the.verbose: print "deployment state found; loading"
72
state = json.loads( f.read() )
74
# unpack deployment state
75
self.deploy_files = state['deploy_files'];
76
self.initial_revno = state['initial_revno'];
77
self.affected_files = state['affected_files'];
80
def save_deployment_state( self ):
81
"""Save the current deployment state (so there will be a deployment ongoing).
84
if the.verbose: print "saving deployment state"
86
# create metadata directory, as necessary
87
if not os.path.isdir( the.full_mddir ):
88
os.mkdir( the.full_mddir )
90
# pack deployment state
92
'deploy_files': self.deploy_files,
93
'initial_revno': self.initial_revno,
94
'affected_files': self.affected_files,
98
file = os.path.join( the.full_mddir, "deploy.%s" % the.repo.name )
100
f.write( json.dumps( state ) );
103
def remove_deployment_state( self ):
104
"""Remove the current deployment state (so no deployment will be ongoing).
108
file = os.path.join( the.full_mddir, "deploy.%s" % the.repo.name )
109
if( os.path.isfile( file ) ):
112
if the.verbose: print "removing deployment state"
116
def is_ongoing( self ):
117
"""Is there a deployment currently ongoing?
120
return False if self.deploy_files is None else True
123
def check_ongoing( self, ongoing = True ):
124
"""Check that a deployment either is or is not ongoing and raise an error if
129
if self.deploy_files is None:
130
raise self.DeploymentOngoing( False )
132
if self.deploy_files is not None:
133
raise self.DeploymentOngoing( True )
136
def get_initial_revno( self ):
137
"""Get the initial revision identifier from the deployment state.
140
return self.initial_revno
143
def copy_in( self, initial_revno = None ):
144
"""Copy-in changes from the home directory to the repository. When finished,
145
the state of deployment is saved, meaning that a deployment is ongoing.
148
# check we don't already have a file list
149
self.check_ongoing( False )
151
# if the repo doesn't exist, we have an empty file list
152
if not os.path.exists( the.repo.full_dir ):
153
self.deploy_files = list()
156
if the.verbose: print "importing files..."
157
walker = CopyInWalker()
159
if( walker.changed ):
160
raise self.CopyInConflicts( walker.changed )
161
self.deploy_files = walker.walk_list
163
# obtain initial revno
164
self.initial_revno = the.repo.vcs.get_revno()
167
self.save_deployment_state()
170
def get_conflicts( self, affected_files = None ):
171
"""Check to see if there are any delpoyment conflicts. If a list of affected
172
files is supplied, then only those files are checked (and they are also
173
saved with the deployment state). Otherwise, all files in the
174
repository are checked.
177
# check we have a file list
178
self.check_ongoing( True )
181
if affected_files is not None:
182
self.affected_files = affected_files
183
self.save_deployment_state()
185
# check for deployment conflicts
186
walker = ConflictWalker( self.deploy_files, self.affected_files )
189
self.conflicts_checked = True
190
return walker.changed + walker.obstructed
193
def copy_out( self ):
194
"""Copy-out changed files frmo the repository to the home directory. If the
195
deployment state incudes a list of affected files, then only those fiels
199
# check we have a file list
200
self.check_ongoing( True )
202
# check that deployment conflicts have been checked-for
203
if not self.conflicts_checked:
204
raise RuntimeError('logic error: deployment conflicts unchecked' )
207
if the.verbose: print "exporting files..."
208
walker = CopyOutWalker( self.affected_files )
212
self.remove_deployment_state()
215
class DeploymentOngoing( the.program.FatalError ):
217
def __init__( self, ongoing ):
219
self.msg = "there is an ongoing deployment"
221
self.msg = "there is no ongoing deployment"
224
class CopyInConflicts( the.program.FatalError ):
226
def __init__( self, conflicts ):
227
self.conflicts = conflicts