Добавлена синхронизация репозиторием.

Добавлены переменные для репозиториев. Добавлены методы взаимодействия с
git.
master3.3
Mike khiretskiy 10 years ago
parent 80526d57ff
commit fd2f069396

@ -0,0 +1,330 @@
#-*- coding: utf-8 -*-
# Copyright 2014 Calculate Ltd. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import re
import sys
import time
from os import path
from calculate.lib.utils.files import process,getProgPath,STDOUT,removeDir
class UpdateError(Exception):
"""Update Error"""
class CloneRepError(Exception):
"""Clone repository error"""
from calculate.lib.cl_lang import setLocalTranslate,getLazyLocalTranslate
setLocalTranslate('cl_update3',sys.modules[__name__])
__ = getLazyLocalTranslate(_)
class Update:
"""Основной объект для выполнения действий связанных с обновлением системы
"""
def _checkExistsRep(self,rpath):
"""
Проверить путь на наличие репозитория
"""
if path.exists(rpath):
if not path.isdir(rpath):
raise UpdateError(
_("Repository {path} is not directory").format(
path=rpath))
if not path.isdir(self.__gitDir(rpath)):
raise UpdateError(
_("Repository {path} is not git").format(
path=rpath))
return True
return False
def __getGit(self):
"""
Получить утилиту git
"""
git = getProgPath("/usr/bin/git")
if not git:
raise UpdateError(_("Git utility is not found"))
return git
def __gitDir(self,rpath):
return path.join(rpath,".git")
def _cloneRepository(self, url, rpath, branch):
"""
Сделать локальную копию репозитория
Args:
url: откуда качать репозиторий
rpath: куда сохранять репозиторий
branch: ветка на которую необходимо переключиться
"""
git = self.__getGit()
gitClone = process(git,"clone","-q","--no-single-branch",
"--depth=1","-b",branch,url,rpath,stderr=STDOUT)
if gitClone.failed():
error = gitClone.read()
if "Remote branch %s not found"%branch in error:
raise UpdateError(
_("Branch {branch} not found in {url} repository").format(
branch=branch,url=url))
self.printERROR(error)
raise UpdateError(_("Failed to clone {url} repository").format(
url=url))
return True
def _cloneRevRepository(self, url, rpath, branch,revision):
"""
Сделать локальную копию репозитория с указанной ревизией
Args:
url: откуда качать репозиторий
rpath: куда сохранять репозиторий
branch: ветка на которую необходимо переключиться
revision: если указана - сделать ревизию текущей
"""
git = self.__getGit()
git_dir = self.__gitDir(rpath)
for cmd in (["init",rpath],
["remote","add","origin",url],
["fetch","--depth=1"],
["checkout","-b",branch,revision],
["branch",branch,"-u","origin/%s"%branch]):
if cmd[0] == "init":
gitCmd = process(*[git]+cmd,stderr=STDOUT)
else:
gitCmd = process(*[git,"--git-dir",git_dir,
"--work-tree",rpath]+cmd,stderr=STDOUT)
if gitCmd.failed():
error = gitCmd.read()
if "reference is not a tree" in error:
raise UpdateError(
_("Commit {revision} not found in {url} repository"
).format(revision=revision,url=url))
elif "upstream branch '%s' does not exist"%("origin/%s"%branch):
raise UpdateError(
_("Branch {branch} not found in {url} repository"
).format(branch=branch,url=url))
else:
self.printERROR(error)
raise UpdateError(_("Failed to clone {url} repository").format(
url=url))
return True
def _pullRepository(self,rpath,quiet_error=False):
"""
Обновить репозиторий до последней версии
"""
git = self.__getGit()
gitPull = process(git,"--git-dir",self.__gitDir(rpath),
"pull","--ff-only",stderr=STDOUT)
if gitPull.failed():
if not quiet_error:
self.printERROR(gitPull.read())
raise UpdateError(
_("Failed to update repository in {rpath}").format(
rpath=rpath))
return False
return True
def _fetchRepository(self,rpath):
"""
Получить изменения из удаленно репозитория
"""
git = self.__getGit()
gitFetch = process(git,"--git-dir",self.__gitDir(rpath),
"fetch",stderr=STDOUT)
if gitFetch.failed():
self.printERROR(gitFetch.read())
raise UpdateError(
_("Failed to update repository in {rpath}").format(
rpath=rpath))
return True
def _checkChanges(self,rpath):
"""
Проверить наличие изменений пользователем файлов в репозитории
"""
git = self.__getGit()
git_dir = self.__gitDir(rpath)
git_status = process(git,"--git-dir",git_dir,"--work-tree",rpath,
"status","--porcelain",stderr=STDOUT)
if git_status.success():
for i in git_status:
if i.strip():
return False
else:
return True
else:
raise UpdateError(
_("Wrong repository in {rpath} directory").format(
rpath=rpath))
def _parseStatusInfo(self,data):
"""
Разобрать информацию полученную через git status -b --porcelain
Returns:
Словарь
# есть ли измененные файлы пользователем
{'files':True/False,
# есть коммиты после текущего
'ahead':True/False,
# есть коммиты перед текущим (означает, что обновление
# с основной веткой не осуществляется через fast-forward
'behind':True/False,
# текущая ветка
'branch':'',
# оригинальная ветка
'origin':'origin/master'}
"""
reStatus = re.compile("^## (\w+)(?:\.\.\.(\S+)\s+\[(ahead \d+)?"
"(?:, )?(behind \d+)?\])?\n?(.*|$)",re.S)
match = reStatus.search(data)
if not match:
return {}
return {'files':True if match.group(5) else False,
'ahead':True if match.group(3) else False,
'behind':True if match.group(4) else False,
'origin':match.group(2) or "",
'branch':match.group(1)}
def _getCurrentCommit(self,rpath):
"""
Получить текущий коммит в репозитории
"""
git = self.__getGit()
git_dir = self.__gitDir(rpath)
git_show = process(git,"--git-dir",git_dir,"show","--format=format:%H",
"--quiet",stderr=STDOUT)
if git_show.success():
return git_show.read().strip()
else:
raise UpdateError(
_("Failed to get status of repository in "
"{rpath} directory").format(
rpath=rpath))
def _getStatusInfo(self,rpath):
"""
Получить информацию об изменениях в репозитории
Returns:
Словарь выдаваемый функцией _parseStatusInfo
"""
git = self.__getGit()
git_dir = self.__gitDir(rpath)
git_status = process(git,"--git-dir",git_dir,"--work-tree",rpath,
"status","-b","--porcelain",stderr=STDOUT)
if git_status.success():
retDict = self._parseStatusInfo(git_status.read())
if not retDict:
raise UpdateError(
_("Failed to get status of repository in "
"{rpath} directory").format(
rpath=rpath))
return retDict
else:
raise UpdateError(
_("Wrong repository in {rpath} directory").format(
rpath=rpath))
def _resetRepository(self, rpath, to_origin=False, to_rev=None, info=None):
"""
Удалить неиндексированные изменения в репозитории
Args:
to_origin: откатить все изменения до удаленного репозитория
to_rev: откатить все изменения до определенной ревизии
info: использовать уже полученную информация об изменения в репозитории
"""
git = self.__getGit()
git_dir = self.__gitDir(rpath)
if to_origin and not info:
info = self._getStatusInfo(rpath)
commit = (info['origin'] if to_origin else to_rev) or "HEAD"
git_reset = process(git,"--git-dir",git_dir,"--work-tree",rpath,
"reset","--hard", commit, stderr=STDOUT)
git_clean = process(git,"--git-dir",git_dir,"--work-tree",rpath,
"clean","-fd",stderr=STDOUT)
if git_reset.failed() or git_clean.failed():
raise UpdateError(_("Failed to clean {rpath} repository").format(
rpath=rpath))
def _getBranch(self,rpath):
"""
Получить текущую ветку
"""
return self._getStatusInfo(rpath)['branch']
def _checkoutBranch(self,rpath,branch):
"""
Сменить ветку
"""
git = self.__getGit()
git_dir = self.__gitDir(rpath)
git_checkout = process(git,"--git-dir",git_dir,
"--work-tree", rpath,
"checkout","-f",branch, stderr=STDOUT)
if git_checkout.failed():
error = git_checkout.read()
if "pathspec '%s' did not match"%branch in error:
raise UpdateError(
_("Branch {branch} not found in {rpath} repository").format(
branch=branch,rpath=rpath))
self.printERROR(error)
raise UpdateError(
_("Failed to change branch to {branch} in "
"{rpath} repository").format(branch=branch,
rpath=rpath))
return True
def syncRepositories(self,repositories):
"""
Синхронизировать репозитории
"""
# TODO: удалять не git репозиторий
# TODO: прогресс скачивания
dv = self.clVars
for name, url, rpath, revision, branch in (reversed(
filter(lambda x:x[0] in repositories,
dv.Get('cl_update_rep_data')))):
if not self._checkExistsRep(rpath):
if revision == "last":
self._cloneRepository(url, rpath, branch)
else:
self._cloneRevRepository(url, rpath, branch, revision)
else:
# если нужно обновиться до конкретной ревизии
if revision != "last":
if revision == self._getCurrentCommit(rpath):
if self._getBranch(rpath) == branch:
continue
# получить изменения из удаленного репозитория
self._fetchRepository(rpath)
# если текущая ветка не соответствует нужной
repInfo = self._getStatusInfo(rpath)
if repInfo['branch'] != branch:
# меняем ветку
self._checkoutBranch(rpath,branch)
if revision == "last":
self._resetRepository(rpath, to_origin=True,
info=repInfo)
else:
self._resetRepository(rpath, to_rev=revision,
info=repInfo)
return True

@ -20,6 +20,7 @@ from calculate.lib.cl_lang import setLocalTranslate,getLazyLocalTranslate
from calculate.lib.utils.files import FilesError from calculate.lib.utils.files import FilesError
from calculate.install.install import (MigrationError, TemplatesError, from calculate.install.install import (MigrationError, TemplatesError,
InstallError) InstallError)
from calculate.update.update import UpdateError
setLocalTranslate('cl_update3',sys.modules[__name__]) setLocalTranslate('cl_update3',sys.modules[__name__])
__ = getLazyLocalTranslate(_) __ = getLazyLocalTranslate(_)
@ -29,17 +30,24 @@ class ClUpdateAction(Action):
Действие обновление конфигурационных файлов Действие обновление конфигурационных файлов
""" """
# ошибки, которые отображаются без подробностей # ошибки, которые отображаются без подробностей
native_error = (FilesError,) native_error = (FilesError,UpdateError)
successMessage = None successMessage = None
failedMessage = __("Revision update failed") failedMessage = __("Update failed")
interruptMessage = __("Update manually interrupted") interruptMessage = __("Update manually interrupted")
# список задач для дейсвия # список задач для дейсвия
tasks = [ tasks = [
{'name':'sync_reps',
'message' : __("Syncing {cl_update_sync_rep} repositories"),
'method':'Update.syncRepositories(update.cl_update_sync_rep)',
'condition':lambda Get:Get('cl_update_sync_rep')
},
{'name':'dispatch', {'name':'dispatch',
'method':'Install.applyTemplates(install.cl_source,cl_template_clt_set,'\ 'method':'Update.applyTemplates(install.cl_source,cl_template_clt_set,'\
'True,None)', 'True,None)',
'condition':lambda Get:(Get('cl_update_rev_set') == 'on' or
Get('cl_rebuild_world_set') == 'on')
}, },
# сообщение удачного завершения при обновлении ревизии # сообщение удачного завершения при обновлении ревизии
{'name':'success_rev', {'name':'success_rev',

@ -16,8 +16,12 @@
import os import os
import sys import sys
import re
from os import path from os import path
from calculate.lib.datavars import Variable,VariableError,ReadonlyVariable from calculate.lib.datavars import (Variable,VariableError,ReadonlyVariable,
ReadonlyTableVariable,TableVariable)
from calculate.lib.utils.portage import searchProfile
from calculate.lib.utils.files import readLinesFile, readFile
from calculate.lib.cl_lang import setLocalTranslate from calculate.lib.cl_lang import setLocalTranslate
setLocalTranslate('cl_update3',sys.modules[__name__]) setLocalTranslate('cl_update3',sys.modules[__name__])
@ -62,11 +66,179 @@ class VariableClUpdateRevSet(Variable):
opt = ["--update-rev"] opt = ["--update-rev"]
untrusted = True untrusted = True
value = "off" value = "off"
check_after = ["cl_update_sync_rep"]
def init(self): def init(self):
self.help = _("revision update") self.help = _("revision update")
self.label = _("Revision update") self.label = _("Revision update")
def check(self,value): def check(self,value):
if value != "on" and self.Get('cl_rebuild_world_set') != 'on': if ( value == "off" and self.Get('cl_rebuild_world_set') != 'on' and
not self.Get('cl_update_sync_rep')):
raise VariableError(_("Select at least one update action")) raise VariableError(_("Select at least one update action"))
class VariableClUpdateRep(Variable):
"""
Обновлять репозитории до конкретной ревизии или до последней
"""
type = "choice"
value = "rev"
def choice(self):
return ["last","rev"]
class VariableClUpdateRepData(ReadonlyTableVariable):
"""
Информация о репозиториях
"""
source = ['cl_update_rep_name',
'cl_update_rep_url',
'cl_update_rep_path',
'cl_update_rep_rev',
'cl_update_branch_name']
class VariableClUpdateRepName(Variable):
"""
Список имен используемых репозиториев
"""
type = "list"
value = []
class VariableClUpdateRepUrl(Variable):
"""
Список путей до репозиториев
"""
type = "list"
value = []
class VariableClUpdateSystemProfile(ReadonlyVariable):
"""
Профиль системы (симлинк /etc/make.profile')
"""
def get(self):
try:
return path.normpath(
path.join('/etc',os.readlink('/etc/make.profile')))
except:
raise VariableError(_("Failed to determine system profile"))
class VariableClUpdateLaymanStorage(ReadonlyVariable):
"""
Путь к репозиториям layman
"""
def get(self):
laymanConf = "/etc/layman/layman.cfg"
reStorage = re.compile("^storage\s*:\s*(\S+)")
if path.exists(laymanConf):
for line in readLinesFile(laymanConf):
match = reStorage.search(line)
if match:
return match.group(1)
return "/var/lib/layman"
class VariableClUpdateRepPath(ReadonlyVariable):
"""
Пути до репозиториев
"""
type = "list"
mapPath = {'portage':'/usr/portage'}
def get(self):
repPath = self.Get('cl_update_layman_storage')
def generatePaths(names):
for name in names:
if name in self.mapPath:
yield self.mapPath[name]
else:
yield path.join(repPath,name)
return list(generatePaths(self.Get('cl_update_rep_name')))
class VariableClUpdateRepRev(Variable):
"""
Ревизии до которых необходимо обновить репозитории
"""
type = "list"
def get(self):
if self.Get('cl_update_rep') == 'rev':
revPaths = searchProfile(self.Get('cl_update_system_profile'),
"rev")
if revPaths:
revPath = revPaths[-1]
dictNamesRevs = dict(map(lambda x:x.strip().partition('=')[::2],
readLinesFile(revPath)))
return map(lambda x:dictNamesRevs.get(x,"last"),
self.Get('cl_update_rep_name'))
return ["last"]*len(self.Get('cl_update_rep_name'))
class VariableClUpdateBranch(TableVariable):
"""
Выбор веток репозиториев до которых необходимо обновиться
"""
opt = ["--branch"]
metavalue = 'BRANCHES'
untrusted = True
source = ["cl_update_branch_rep",
"cl_update_branch_name"]
def init(self):
self.help = _("set branches for repository (REPOSITORY:BRANCH)")
self.label = _("Repository branch")
def raiseReadonlyIndexError(self,fieldname="",variablename="",
value=""):
"""
Неизвестный оврелей
"""
raise VariableError(_("Repository %s not found")%value)
class VariableClUpdateBranchRep(ReadonlyVariable):
"""
Список доступных репозиторием
"""
type = "list"
def init(self):
self.label = _("Repository")
def get(self):
return self.Get('cl_update_rep_name')
class VariableClUpdateBranchName(Variable):
"""
Список доступных репозиторием
"""
type = "choiceedit-list"
def init(self):
self.label = _("Branch")
def choice(self):
return ["master","develop","update"]
def get(self):
def generateBranch():
for reppath in self.Get('cl_update_rep_path'):
headPath = path.join(reppath,".git/HEAD")
yield readFile(headPath).rpartition('/')[2].strip() or "master"
return list(generateBranch())
class VariableClUpdateSyncRep(Variable):
"""
Обновляемый репозиторий
"""
type = "choice-list"
element = "selecttable"
opt = ["cl_update_sync_rep"]
metavalue = "REPOSITORIES"
untrusted = True
def init(self):
self.help = _("synchronize repositories (all by default)")
self.label = _("Synchronize repositories")
def get(self):
return self.Get('cl_update_rep_name')
def choice(self):
return self.Get('cl_update_rep_name')

@ -19,7 +19,8 @@ import sys, time, os
from calculate.lib.datavars import VariableError,DataVarsError,DataVars from calculate.lib.datavars import VariableError,DataVarsError,DataVars
from calculate.core.server.func import WsdlBase from calculate.core.server.func import WsdlBase
from calculate.install.install import InstallError,Install from calculate.install.install import InstallError
from calculate.update.update import Update,UpdateError
from utils.cl_update import ClUpdateAction from utils.cl_update import ClUpdateAction
from calculate.lib.cl_lang import setLocalTranslate,getLazyLocalTranslate from calculate.lib.cl_lang import setLocalTranslate,getLazyLocalTranslate
setLocalTranslate('cl_update3',sys.modules[__name__]) setLocalTranslate('cl_update3',sys.modules[__name__])
@ -37,7 +38,7 @@ class Wsdl(WsdlBase):
# категория метода # категория метода
'category':__('Update'), 'category':__('Update'),
# заголовок метода # заголовок метода
'title':__("Update configuration"), 'title':__("Update system"),
# иконка для графической консоли # иконка для графической консоли
'image':'software-properties,preferences-desktop', 'image':'software-properties,preferences-desktop',
# метод присутствует в графической консоли # метод присутствует в графической консоли
@ -47,7 +48,7 @@ class Wsdl(WsdlBase):
# права для запуска метода # права для запуска метода
'rights':['update'], 'rights':['update'],
# объект содержащий модули для действия # объект содержащий модули для действия
'logic':{'Install':Install}, 'logic':{'Update':Update},
# описание действия # описание действия
'action':ClUpdateAction, 'action':ClUpdateAction,
# объект переменных # объект переменных
@ -58,9 +59,10 @@ class Wsdl(WsdlBase):
'setvars':{'cl_action!':'sync'}, 'setvars':{'cl_action!':'sync'},
# описание груп (список лямбда функций) # описание груп (список лямбда функций)
'groups':[ 'groups':[
lambda group:group(_("Update configuration"), lambda group:group(_("Update system"),
normal=('cl_rebuild_world_set','cl_update_rev_set'), normal=('cl_rebuild_world_set','cl_update_rev_set'),
expert=('cl_templates_locate', expert=('cl_update_sync_rep', 'cl_update_branch',
'cl_templates_locate',
'cl_verbose_set','cl_dispatch_conf'), 'cl_verbose_set','cl_dispatch_conf'),
next_label=_("Update"))]}, next_label=_("Update"))]},
] ]

Loading…
Cancel
Save