Добавлены переменные для репозиториев. Добавлены методы взаимодействия с git.master3.3
Родитель
80526d57ff
Сommit
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
|
Загрузка…
Ссылка в новой задаче