00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033 from __future__ import print_function
00034 import os
00035 import shutil
00036 import datetime
00037
00038 from vcstools import VcsClient
00039 from vcstools.vcs_base import VcsError
00040
00041 from common import samefile, MultiProjectException
00042 from config_yaml import PathSpec
00043 import ui
00044
00045
00046
00047 class PreparationReport:
00048 """Specifies after user interaction of how to perform install / update"""
00049 def __init__(self, element):
00050 self.config_element = element
00051 self.abort = False
00052 self.skip = False
00053 self.error = None
00054 self.verbose = False
00055 self.checkout = True
00056 self.backup = False
00057 self.backup_path = None
00058
00059
00060
00061 class ConfigElement:
00062 """ Base class for Config provides methods with not implemented
00063 exceptions. Also a few shared methods."""
00064 def __init__(self, path, local_name, properties = None):
00065 self.path = path
00066 if path is None:
00067 raise MultiProjectException("Invalid empty path")
00068 self.local_name = local_name
00069 self.properties = properties
00070 def get_path(self):
00071 """A normalized absolute path"""
00072 return self.path
00073 def get_local_name(self):
00074 """What the user specified in his config"""
00075 return self.local_name
00076 def prepare_install(self, backup_path = None, arg_mode = 'abort', robust = False):
00077 """
00078 Check whether install can be performed, asking user for decision if necessary.
00079
00080 :param arg_mode: one of prompt, backup, delete, skip. Determines how to handle error cases
00081 :param backup_path: if arg_mode==backup, determines where to backup to
00082 :param robust: if true, operation will be aborted without changes to the filesystem and without user interaction
00083 :returns: A preparation_report instance, telling whether to checkout or to update, how to deal with existing tree, and where to backup to.
00084 """
00085 preparation_report = PreparationReport(self)
00086 preparation_report.skip = True
00087 return preparation_report
00088
00089 def install(self, checkout = True, backup = False, backup_path = None, verbose = False):
00090 """
00091 Attempt to make it so that self.path is the result of checking out / updating from remote repo.
00092 No user Interaction allowed here (for concurrent mode).
00093
00094 :param checkout: whether to checkout or update
00095 :param backup: if checking out, what to do if path exists. If true, backup_path must be set.
00096 """
00097 raise NotImplementedError, "ConfigElement install unimplemented"
00098
00099 def get_path_spec(self):
00100 """PathSpec object with values as specified in file"""
00101 raise NotImplementedError, "ConfigElement get_path_spec unimplemented"
00102 def get_properties(self):
00103 """Any meta information attached"""
00104 return self.properties
00105 def get_versioned_path_spec(self):
00106 """PathSpec where VCS elements have the version looked up"""
00107 raise NotImplementedError, "ConfigElement get_versioned_path_spec unimplemented"
00108 def is_vcs_element(self):
00109
00110 return False
00111 def get_diff(self, basepath = None):
00112 raise NotImplementedError, "ConfigElement get_diff unimplemented"
00113 def get_status(self, basepath = None, untracked = False):
00114 raise NotImplementedError, "ConfigElement get_status unimplemented"
00115 def backup(self, backup_path):
00116 if not backup_path:
00117 raise MultiProjectException("[%s] Cannot install %s. backup disabled."%(self.get_local_name(), self.get_path()))
00118 backup_path = os.path.join(backup_path, os.path.basename(self.path)+"_%s"%datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S"))
00119 print("[%s] Backing up %s to %s"%(self.get_local_name(), self.get_path(), backup_path))
00120 shutil.move(self.path, backup_path)
00121 def __str__(self):
00122 return str(self.get_path_spec().get_legacy_yaml());
00123 def __eq__(self, other):
00124 if isinstance(other, self.__class__):
00125 return self.get_path_spec() == other.get_path_spec()
00126 else:
00127 return False
00128
00129
00130 class OtherConfigElement(ConfigElement):
00131
00132 def __init__(self, path, local_name, uri=None, version='', properties = None):
00133 ConfigElement.__init__(self, path, local_name, properties)
00134 self.uri = uri
00135 self.version = version
00136
00137
00138 def install(self, checkout = True, backup = False, backup_path = None, verbose = False):
00139 return True
00140
00141 def get_versioned_path_spec(self):
00142 raise MultiProjectException("Cannot generate versioned outputs with non source types")
00143
00144 def get_path_spec(self):
00145 "yaml as from source"
00146 version = self.version
00147 if version == '': version = None
00148 return PathSpec(local_name = self.get_local_name(),
00149 path = self.get_path(),
00150 scmtype = None,
00151 uri = self.uri,
00152 version = version,
00153 tags = self.get_properties())
00154
00155
00156 class SetupConfigElement(ConfigElement):
00157 """A setup config element specifies a single file containing configuration data for a config."""
00158
00159 def install(self, checkout = True, backup = False, backup_path = None, verbose = False):
00160 return True
00161
00162 def get_versioned_path_spec(self):
00163 raise MultiProjectException("Cannot generate versioned outputs with non source types")
00164
00165 def get_path_spec(self):
00166 return PathSpec(local_name = self.get_local_name(), path = self.get_path(), tags=['setup-file'] + (self.get_properties() or []))
00167
00168
00169 class VCSConfigElement(ConfigElement):
00170
00171 def __init__(self, path, local_name, uri, version='', properties = None):
00172 """
00173 Creates a config element for a VCS repository.
00174
00175 :param path: absolute or relative path, str
00176 :param vcs_client: Object compatible with vcstools.VcsClientBase
00177 :param local_name: display name for the element, str
00178 :param uri: VCS uri to checkout/pull from, str
00179 :param version: optional revision spec (tagname, SHAID, ..., str)
00180 """
00181 ConfigElement.__init__(self, path, local_name, properties)
00182 if uri is None:
00183 raise MultiProjectException("Invalid scm entry having no uri attribute for path %s"%path)
00184 self.uri = uri.rstrip('/')
00185 self.version = version
00186
00187 def _get_vcsc(self):
00188 raise NotImplementedError, "VCSConfigElement _get_vcsc() unimplemented"
00189
00190 def is_vcs_element(self):
00191 return True
00192
00193 def get_vcs_type_name(self):
00194
00195 return self._get_vcsc().get_vcs_type_name()
00196
00197 def detect_presence(self):
00198
00199 return self._get_vcsc().detect_presence()
00200
00201 def path_exists(self):
00202
00203
00204 return os.path.isdir(self.path)
00205
00206 def prepare_install(self, backup_path = None, arg_mode = 'abort', robust = False):
00207 preparation_report = PreparationReport(self)
00208 present = self.detect_presence()
00209 if present or self.path_exists():
00210
00211 error_message = None
00212
00213 if not present:
00214 error_message = "Failed to detect %s presence at %s."%(self.get_vcs_type_name(), self.path)
00215 else:
00216 cur_url = self._get_vcsc().get_url()
00217 if cur_url is not None:
00218 cur_url = cur_url.rstrip('/')
00219 if not cur_url or cur_url != self.uri.rstrip('/'):
00220
00221 if not (os.path.isdir(self.uri) and os.path.isdir(cur_url) and samefile(cur_url, self.uri)):
00222 error_message = "Url %s does not match %s requested."%(cur_url, self.uri)
00223 if error_message is None:
00224
00225 preparation_report.checkout = False
00226 else:
00227
00228 if robust:
00229 raise MultiProjectException("Update Failed of %s: %s"%(self.path, error_message))
00230
00231 if arg_mode == 'prompt':
00232 print("Prepare updating %s (%s) to %s"%(self.uri, self.version, self.path))
00233 mode = ui.Ui.get_ui().prompt_del_abort_retry(error_message, allow_skip = True)
00234 else:
00235 mode = arg_mode
00236 if mode == 'backup':
00237 preparation_report.backup = True
00238 if backup_path == None:
00239 print("Prepare updating %s (%s) to %s"%(self.uri, self.version, self.path))
00240 preparation_report.backup_path = ui.Ui.get_ui().get_backup_path()
00241 else:
00242 preparation_report.backup_path = backup_path
00243 if mode == 'abort':
00244 preparation_report.abort = True
00245 preparation_report.error = error_message
00246 if mode == 'skip':
00247 preparation_report.skip = True
00248 preparation_report.error = error_message
00249 if mode == 'delete':
00250 preparation_report.backup = False
00251 return preparation_report
00252
00253 def install(self, checkout = True, backup = True, backup_path = None, verbose = False):
00254 """
00255 Runs the equivalent of SCM checkout for new local repos or update for existing.
00256
00257 :param checkout: whether to use an update command or a checkout/clone command
00258 :param backup: if checkout is True and folder exists, if backup is false folder will be DELETED.
00259 :param backup_path: if checkout is true and backup is true, move folder to this location
00260 """
00261 if checkout is True:
00262 print("[%s] Installing %s (%s) to %s"%(self.get_local_name(), self.uri, self.version, self.get_path()))
00263 if self.path_exists():
00264 if (backup is False):
00265 shutil.rmtree(self.path)
00266 else:
00267 self.backup(backup_path)
00268 if not self._get_vcsc().checkout(self.uri, self.version, verbose = verbose):
00269 raise MultiProjectException("[%s] Checkout of %s version %s into %s failed."%(self.get_local_name(), self.uri, self.version, self.get_path()))
00270 else:
00271 print("[%s] Updating %s"%(self.get_local_name(), self.get_path()))
00272 if not self._get_vcsc().update(self.version, verbose = verbose):
00273 raise MultiProjectException("[%s] Update Failed of %s"%(self.get_local_name(), self.get_path()))
00274 print("[%s] Done."%self.get_local_name())
00275
00276 def get_path_spec(self):
00277 "yaml as from source"
00278 version = self.version
00279 if version == '': version = None
00280 return PathSpec(local_name = self.get_local_name(),
00281 path = self.get_path(),
00282 scmtype = self.get_vcs_type_name(),
00283 uri = self.uri,
00284 version = version,
00285 tags = self.get_properties())
00286
00287 def get_versioned_path_spec(self):
00288 "yaml looking up current version"
00289 version = self.version
00290 if version == '': version = None
00291 revision = None
00292 if version is not None:
00293
00294 revision = self._get_vcsc().get_version(self.version)
00295 currevision = self._get_vcsc().get_version()
00296 return PathSpec(local_name = self.get_local_name(),
00297 path = self.get_path(),
00298 scmtype = self.get_vcs_type_name(),
00299 uri = self.uri,
00300 version = version,
00301 revision = revision,
00302 currevision = currevision,
00303 curr_uri = self._get_vcsc().get_url(),
00304 tags = self.get_properties())
00305
00306
00307 def get_diff(self, basepath=None):
00308 return self._get_vcsc().get_diff(basepath)
00309
00310 def get_status(self, basepath=None, untracked=False):
00311 return self._get_vcsc().get_status(basepath, untracked)
00312
00313
00314
00315 class AVCSConfigElement(VCSConfigElement):
00316 """
00317 Implementation using vcstools vcsclient, works for types svn, git, hg, bzr, tar
00318
00319 :raises: Lookup Exception for unknown types
00320 """
00321 def __init__(self, scmtype, path, local_name, uri, version = '', vcsc = None, properties = None):
00322 VCSConfigElement.__init__(self, path, local_name = local_name, uri = uri, version = version, properties = properties)
00323 self.vcsc = vcsc
00324 self._scmtype = scmtype
00325
00326 def get_vcs_type_name(self):
00327 return self._scmtype
00328
00329 def _get_vcsc(self):
00330
00331 if self.vcsc == None:
00332 try:
00333 self.vcsc = VcsClient(self._scmtype, self.get_path())
00334 except VcsError as e:
00335 raise MultiProjectException("Unable to create vcs client of type %s for %s: %s"%(self._scmtype, self.get_path(), e))
00336 return self.vcsc
00337
00338 def detect_presence(self):
00339
00340
00341 if self.get_vcs_type_name() == 'git':
00342 return os.path.isdir(os.path.join(self.path, '.git'))
00343 elif self.get_vcs_type_name() == 'svn':
00344 return os.path.isdir(os.path.join(self.path, '.svn'))
00345 elif self.get_vcs_type_name() == 'hg':
00346 return os.path.isdir(os.path.join(self.path, '.hg'))
00347 elif self.get_vcs_type_name() == 'bzr':
00348 return os.path.isdir(os.path.join(self.path, '.bzr'))
00349 else:
00350 return self._get_vcsc().detect_presence()