/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/deployment.py

  • Committer: Tim Marston
  • Date: 2021-07-05 21:01:29 UTC
  • Revision ID: tim@ed.am-20210705210129-gc746mez1vhzobz7
add stage-status command; add common stage notice to helps

Show diffs side-by-side

added added

removed removed

1
1
# deployment.py
2
2
#
3
 
# Copyright (C) 2013 Tim Marston <tim@edm.am>
 
3
# Copyright (C) 2013 to 2014 Tim Marston <tim@edm.am>
4
4
#
5
5
# This file is part of stdhome (hereafter referred to as "this program").
6
6
# See http://ed.am/dev/stdhome for more information.
19
19
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
20
 
21
21
 
22
 
import os
23
 
import the
 
22
import os, re, shutil, filecmp, json
 
23
from . import the, util
 
24
from .walker.copy_in import CopyInWalker
 
25
from .walker.conflict import ConflictWalker
 
26
from .walker.copy_out import CopyOutWalker
24
27
 
25
28
 
26
29
class Deployment:
27
30
 
28
31
 
29
32
        def __init__( self ):
30
 
 
31
 
                # initialise the file list
32
 
                self.file_list = None
33
 
                if not os.path.exists( the.repo.expanded_dir ):
34
 
                        self.file_list = []
 
33
                if the.repo is None:
 
34
                        raise RuntimeError( 'logic error: Deployment initialised when '
 
35
                                                                'the.repo is unset' )
 
36
                self.load_deployment_state()
 
37
                self.conflicts_checked = False
 
38
 
 
39
 
 
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.
 
43
                """
 
44
 
 
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 the user will
 
48
                # have to have dealt with in the repo), any conflicts arising with these
 
49
                # files in the home directory are no longer important and can be
 
50
                # ignored.  In short, this is a list of files that can safely be
 
51
                # deployed, regardless of the state of the home directory.
 
52
                self.imported_files = None
 
53
 
 
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
 
57
 
 
58
                # the revno that the repo was as prior to a recent update
 
59
                self.initial_revno = None
 
60
 
 
61
                # do we have a repo?
 
62
                if not os.path.exists( the.repo.full_dir ): return
 
63
 
 
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 ):
 
67
                        return
 
68
 
 
69
                # read the file list
 
70
                if the.verbose >= 1: print("deployment state found; loading")
 
71
                f = open( file, 'r' )
 
72
                state = json.loads( f.read() )
 
73
 
 
74
                # unpack deployment state
 
75
                if 'imported_files' in state:
 
76
                        self.imported_files = state['imported_files'];
 
77
                if 'initial_revno' in state:
 
78
                        self.initial_revno = state['initial_revno'];
 
79
                if 'affected_files' in state:
 
80
                        self.affected_files = state['affected_files'];
 
81
 
 
82
 
 
83
        def save_deployment_state( self ):
 
84
                """Save the current deployment state (so there will be a deployment ongoing).
 
85
                """
 
86
 
 
87
                if the.verbose >= 1: print("saving deployment state")
 
88
 
 
89
                # create metadata directory, as necessary
 
90
                if not os.path.isdir( the.full_mddir ):
 
91
                        os.mkdir( the.full_mddir )
 
92
 
 
93
                # pack deployment state
 
94
                state = {
 
95
                        'imported_files': self.imported_files,
 
96
                        'initial_revno': self.initial_revno,
 
97
                        'affected_files': self.affected_files,
 
98
                }
 
99
 
 
100
                # create file
 
101
                file = os.path.join( the.full_mddir, "deploy.%s" % the.repo.name )
 
102
                f = open( file, 'w' )
 
103
                f.write( json.dumps( state ) );
 
104
 
 
105
 
 
106
        def remove_deployment_state( self ):
 
107
                """Remove the current deployment state (so no deployment will be ongoing).
 
108
                """
 
109
 
 
110
                # check it exists
 
111
                file = os.path.join( the.full_mddir, "deploy.%s" % the.repo.name )
 
112
                if( os.path.isfile( file ) ):
 
113
 
 
114
                        # delete it
 
115
                        if the.verbose >= 1: print("removing deployment state")
 
116
                        os.unlink( file )
 
117
 
 
118
 
 
119
        def is_ongoing( self ):
 
120
                """Is there a deployment currently ongoing?
 
121
                """
 
122
 
 
123
                return False if self.imported_files is None else True
 
124
 
 
125
 
 
126
        def check_ongoing( self, ongoing = True ):
 
127
                """Check that a deployment either is or is not ongoing and raise an error if
 
128
                not.
 
129
                """
 
130
 
 
131
                if( ongoing ):
 
132
                        if self.imported_files is None:
 
133
                                raise self.DeploymentOngoing( False )
35
134
                else:
36
 
                        self.load_file_list()
37
 
 
38
 
                assert False # not implemented
39
 
 
40
 
 
41
 
        def load_file_list( self ):
42
 
 
43
 
                # if no file list, don't load one
44
 
                if .....
45
 
                        return
46
 
 
47
 
                # load it
48
 
 
49
 
 
50
 
        def copy_in( self ):
 
135
                        if self.imported_files is not None:
 
136
                                raise self.DeploymentOngoing( True )
 
137
 
 
138
 
 
139
        def get_initial_revno( self ):
 
140
                """Get the initial revision identifier from the deployment state.
 
141
                """
 
142
 
 
143
                return self.initial_revno
 
144
 
 
145
 
 
146
        def copy_in( self, initial_revno = None ):
 
147
                """Copy-in changes from the home directory to the repository.  When finished,
 
148
                the state of deployment is saved, meaning that a deployment is ongoing.
 
149
                """
51
150
 
52
151
                # check we don't already have a file list
53
 
                if self.file_list is not None:
54
 
                        raise the.FatalError( 'deployment in progress; ' + \
55
 
                                'see "%s resolve --help" for information' % the.program.name )
56
 
                        
57
 
                assert False # not implemented
58
 
 
59
 
 
60
 
        def copy_out( self ):
61
 
                if self.pre_copy_out_list is None:
62
 
                        return
63
 
 
64
 
                print "deploying files"
65
 
 
66
 
                # walk the repo
67
 
                for ( repodir, directories, files ) in os.walk( the.repo.expanded_dir ):
68
 
                        assert path[ : len( the.repo.expanded_dir ) ] == \
69
 
                                the.repo.expanded_dir
70
 
                        relative_path = path[ len( the.repo.expanded_dir ) : ]
71
 
                        fsdir = the.expanded_fsdir + relative_path
72
 
 
73
 
                        print relative_path
74
 
 
75
 
                        # check directories
76
 
                        for dir in directories:
77
 
                                if os.path.exists( fsdir + dir ) and \
78
 
                                   not os.path.isdir( fsdir + dir ):
79
 
 
80
 
                                        #
81
 
 
82
 
                        # if exists in repo as directory and also in fs as non-directory
83
 
                        if os.path.isdir( the.repo.expnaded_dir + relative_path + 
 
152
                self.check_ongoing( False )
 
153
 
 
154
                # if the repo doesn't exist, we have an empty file list
 
155
                if not os.path.exists( the.repo.full_dir ):
 
156
                        self.imported_files = list()
 
157
                else:
 
158
                        # copy in
 
159
                        if the.verbose >= 1: print("importing files...")
 
160
                        walker = CopyInWalker()
 
161
                        walker.walk()
 
162
                        self.imported_files = walker.walk_list
 
163
 
 
164
                        # obtain initial revno
 
165
                        self.initial_revno = the.repo.vcs.get_revno()
 
166
 
 
167
                # save state
 
168
                self.save_deployment_state()
 
169
 
 
170
 
 
171
        def get_conflicts( self, affected_files = None ):
 
172
                """Check to see if there are any deployment conflicts.  If a list of affected
 
173
                files is supplied, then only those files are checked (and they are also
 
174
                saved with the deployment state).  Otherwise, all files in the
 
175
                repository are checked.
 
176
                """
 
177
 
 
178
                # check we have a file list
 
179
                self.check_ongoing( True )
 
180
 
 
181
                # set updated files
 
182
                if affected_files is not None:
 
183
                        self.affected_files = affected_files
 
184
                        self.save_deployment_state()
 
185
 
 
186
                # check for deployment conflicts
 
187
                walker = ConflictWalker( self.imported_files, self.affected_files )
 
188
                walker.walk()
 
189
 
 
190
                self.conflicts_checked = True
 
191
                return walker.changed + walker.obstructed
 
192
 
 
193
 
 
194
        def copy_out( self, quiet ):
 
195
                """Copy-out changed files from the repository to the home directory.  If the
 
196
                deployment state includes a list of affected files, then only those
 
197
                files are copied-out.
 
198
                """
 
199
 
 
200
                # check we have a file list
 
201
                self.check_ongoing( True )
 
202
 
 
203
                # check that deployment conflicts have been checked-for
 
204
                if not self.conflicts_checked:
 
205
                        raise RuntimeError('logic error: deployment conflicts unchecked' )
 
206
 
 
207
                # copy out
 
208
                if the.verbose >= 1: print("exporting files...")
 
209
                walker = CopyOutWalker( self.affected_files, not quiet )
 
210
                walker.walk()
 
211
 
 
212
                # clear state
 
213
                self.remove_deployment_state()
 
214
 
 
215
 
 
216
        class DeploymentOngoing( the.program.FatalError ):
 
217
 
 
218
                def __init__( self, ongoing ):
 
219
                        if( ongoing ):
 
220
                                self.msg = "there is an ongoing deployment"
 
221
                        else:
 
222
                                self.msg = "there is no ongoing deployment"