|
|
|
@ -16,6 +16,7 @@
|
|
|
|
|
|
|
|
|
|
import re
|
|
|
|
|
import sys
|
|
|
|
|
import os
|
|
|
|
|
from os import path
|
|
|
|
|
from calculate.lib.configparser import ConfigParser
|
|
|
|
|
import shutil
|
|
|
|
@ -24,6 +25,7 @@ from calculate.lib.utils.files import (getProgPath, STDOUT,
|
|
|
|
|
PercentProgress, process, readFile,
|
|
|
|
|
writeFile, listDirectory, removeDir)
|
|
|
|
|
from calculate.lib.utils.tools import AddonError
|
|
|
|
|
from calculate.lib.utils.ip import check_port
|
|
|
|
|
|
|
|
|
|
_ = lambda x: x
|
|
|
|
|
from calculate.lib.cl_lang import setLocalTranslate
|
|
|
|
@ -152,7 +154,27 @@ class Git:
|
|
|
|
|
return d
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
def cloneTagRepository(self, url, rpath, reference, cb_progress=None):
|
|
|
|
|
def _copy_index(self, git_dir, file_url):
|
|
|
|
|
"""
|
|
|
|
|
Переместить index файл из одного репозитория в другой
|
|
|
|
|
:param git_dir:
|
|
|
|
|
:param file_url:
|
|
|
|
|
:return:
|
|
|
|
|
"""
|
|
|
|
|
old_index = path.join(file_url[7:], "index")
|
|
|
|
|
new_index = path.join(git_dir, "index")
|
|
|
|
|
if path.exists(old_index):
|
|
|
|
|
try:
|
|
|
|
|
with open(old_index, 'r') as in_f:
|
|
|
|
|
with writeFile(new_index) as out_f:
|
|
|
|
|
out_f.write(in_f.read())
|
|
|
|
|
return True
|
|
|
|
|
except IOError:
|
|
|
|
|
pass
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def cloneTagRepository(self, url, rpath, reference, cb_progress=None,
|
|
|
|
|
copy_index=False):
|
|
|
|
|
"""
|
|
|
|
|
Сделать локальную копию репозитория для переключения по тэгам
|
|
|
|
|
|
|
|
|
@ -170,21 +192,49 @@ class Git:
|
|
|
|
|
error = []
|
|
|
|
|
|
|
|
|
|
command = self._git_command(git_dir, rpath, error, cb_progress)
|
|
|
|
|
# получить последние коммиты из удаленного репозитория
|
|
|
|
|
if command("init"):
|
|
|
|
|
if not command("fetch", "--depth=1", url, reference, part=4,
|
|
|
|
|
end=False):
|
|
|
|
|
|
|
|
|
|
def command_fetch(**kw):
|
|
|
|
|
"""
|
|
|
|
|
Получить данные репозитория (если shallow.lock - удалит и повторить)
|
|
|
|
|
"""
|
|
|
|
|
res = command("fetch", "--depth=1", url, reference, **kw)
|
|
|
|
|
if not res:
|
|
|
|
|
if self._clear_lock(git_dir, "shallow.lock", error):
|
|
|
|
|
res = command("fetch", "--depth=1", url, reference, **kw)
|
|
|
|
|
if not res:
|
|
|
|
|
raise GitError(
|
|
|
|
|
_("Reference {mark} not found in repository {url}"
|
|
|
|
|
).format(mark=reference, url=url))
|
|
|
|
|
if command("reset", "--hard", "FETCH_HEAD", part=4, startpart=3):
|
|
|
|
|
if self.saveFetchHead(git_dir) == self.Reference.Branch:
|
|
|
|
|
command("symbolic-ref", "HEAD", "refs/heads/%s" % reference)
|
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
|
# получить последние коммиты из удаленного репозитория
|
|
|
|
|
if command("init"):
|
|
|
|
|
if copy_index:
|
|
|
|
|
copy_index = self._copy_index(git_dir, url)
|
|
|
|
|
if copy_index:
|
|
|
|
|
command_fetch(part=3)
|
|
|
|
|
fetch_type = self.saveFetchHead(git_dir)
|
|
|
|
|
if fetch_type == self.Reference.Tag:
|
|
|
|
|
# создать master ветку с ссылкой на HEAD
|
|
|
|
|
self.saveFetchHead(
|
|
|
|
|
git_dir, force_type=self.Reference.Branch,
|
|
|
|
|
force_ref=self.Reference.Master)
|
|
|
|
|
reference = self.Reference.Master
|
|
|
|
|
command("symbolic-ref", "HEAD", "refs/heads/%s" % reference)
|
|
|
|
|
return True
|
|
|
|
|
else:
|
|
|
|
|
command_fetch(part=4, end=False)
|
|
|
|
|
if command("reset", "--hard", "FETCH_HEAD", part=4,
|
|
|
|
|
startpart=3):
|
|
|
|
|
fetch_type = self.saveFetchHead(git_dir)
|
|
|
|
|
if fetch_type == self.Reference.Branch:
|
|
|
|
|
command("symbolic-ref", "HEAD",
|
|
|
|
|
"refs/heads/%s" % reference)
|
|
|
|
|
return True
|
|
|
|
|
raise GitError(_("Failed to clone repository {url}").format(
|
|
|
|
|
url=url), error[-1])
|
|
|
|
|
# progressParam = {'fetch': {'part': 4, 'end': False},
|
|
|
|
|
# 'checkout': {'part': 4, 'startpart': 3}}
|
|
|
|
|
# 'checkout': {'part': 4, 'startpart': 3}}
|
|
|
|
|
|
|
|
|
|
def trimRepository(self, rpath, cb_progress=None):
|
|
|
|
|
"""
|
|
|
|
@ -196,7 +246,6 @@ class Git:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
reference = head_info['reference']
|
|
|
|
|
print reference
|
|
|
|
|
git_dir_old = "%s.old" % git_dir
|
|
|
|
|
try:
|
|
|
|
|
shutil.move(git_dir, git_dir_old)
|
|
|
|
@ -204,7 +253,8 @@ class Git:
|
|
|
|
|
raise GitError(_("Failed to optimize repository %s") % rpath)
|
|
|
|
|
|
|
|
|
|
self.cloneTagRepository("file://%s" % git_dir_old,
|
|
|
|
|
rpath, reference, cb_progress)
|
|
|
|
|
rpath, reference, cb_progress,
|
|
|
|
|
copy_index=True)
|
|
|
|
|
try:
|
|
|
|
|
removeDir(git_dir_old)
|
|
|
|
|
except Exception:
|
|
|
|
@ -245,7 +295,19 @@ class Git:
|
|
|
|
|
return self.Reference.Branch
|
|
|
|
|
return self.Reference.Unknown
|
|
|
|
|
|
|
|
|
|
def updateTagRepository(self, url, rpath, reference, cb_progress=None):
|
|
|
|
|
def _clear_lock(self, git_dir, lock_fn, error):
|
|
|
|
|
if lock_fn in error[-1]:
|
|
|
|
|
fn = path.join(git_dir, lock_fn)
|
|
|
|
|
if path.exists(fn):
|
|
|
|
|
try:
|
|
|
|
|
os.unlink(fn)
|
|
|
|
|
return True
|
|
|
|
|
except OSError:
|
|
|
|
|
pass
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def updateTagRepository(self, url, rpath, reference, cb_progress=None,
|
|
|
|
|
clean=False):
|
|
|
|
|
"""
|
|
|
|
|
Обновить данные до указанного тега репозитория
|
|
|
|
|
|
|
|
|
@ -264,19 +326,33 @@ class Git:
|
|
|
|
|
try:
|
|
|
|
|
if (self.reference_type(rpath, reference) == Git.Reference.Tag and
|
|
|
|
|
self.isTagRepository(rpath) and
|
|
|
|
|
self.getCurrentCommit(rpath) == self.getCommit(rpath,
|
|
|
|
|
reference)):
|
|
|
|
|
status = self.getStatusInfo(rpath)
|
|
|
|
|
if status and not status["files"]:
|
|
|
|
|
self.getCurrentCommit(rpath) == self.getCommit(
|
|
|
|
|
rpath, reference)):
|
|
|
|
|
if clean:
|
|
|
|
|
status = self.getStatusInfo(rpath)
|
|
|
|
|
if status and not status["files"]:
|
|
|
|
|
return False
|
|
|
|
|
else:
|
|
|
|
|
return False
|
|
|
|
|
except GitError:
|
|
|
|
|
pass
|
|
|
|
|
if not command("fetch", "--depth=1", url, reference, part=4, end=False):
|
|
|
|
|
res = command("fetch", "--depth=1", url, reference, part=4, end=False)
|
|
|
|
|
if not res:
|
|
|
|
|
if self._clear_lock(git_dir, "shallow.lock", error):
|
|
|
|
|
res = command("fetch", "--depth=1",
|
|
|
|
|
url, reference, part=4, end=False)
|
|
|
|
|
if not res:
|
|
|
|
|
raise GitError(
|
|
|
|
|
_("Reference {mark} not found in repository {url}"
|
|
|
|
|
).format(mark=reference, url=url))
|
|
|
|
|
if command("reset", "--hard", "FETCH_HEAD", part=4, startpart=3):
|
|
|
|
|
command("clean", "-df")
|
|
|
|
|
res = command("reset", "--hard", "FETCH_HEAD", part=4, startpart=3)
|
|
|
|
|
if not res:
|
|
|
|
|
if self._clear_lock(git_dir, "index.lock", error):
|
|
|
|
|
res = command("reset", "--hard", "FETCH_HEAD",
|
|
|
|
|
part=4, startpart=3)
|
|
|
|
|
if res:
|
|
|
|
|
if clean:
|
|
|
|
|
command("clean", "-df")
|
|
|
|
|
if self.saveFetchHead(git_dir) == self.Reference.Branch:
|
|
|
|
|
command("symbolic-ref", "HEAD", "refs/heads/%s" % reference)
|
|
|
|
|
return True
|
|
|
|
@ -285,13 +361,17 @@ class Git:
|
|
|
|
|
|
|
|
|
|
# TODO: метод удаление лишних объектов, оставляя только текущий коммит
|
|
|
|
|
|
|
|
|
|
def saveFetchHead(self, git_dir):
|
|
|
|
|
def saveFetchHead(self, git_dir, force_type=None, force_ref=None):
|
|
|
|
|
"""
|
|
|
|
|
Сохранить FEACH_HEAD как объект (tag или branch)
|
|
|
|
|
:param git_dir: каталог .git репозитория
|
|
|
|
|
:return: Reference
|
|
|
|
|
"""
|
|
|
|
|
fetch_info = self._getFetchHeadInfo(git_dir)
|
|
|
|
|
if force_type is not None:
|
|
|
|
|
fetch_info['type'] = force_type
|
|
|
|
|
if force_ref is not None:
|
|
|
|
|
fetch_info['reference'] = force_ref
|
|
|
|
|
tag_file = None
|
|
|
|
|
if fetch_info.get('type', '') == self.Reference.Tag:
|
|
|
|
|
tag_file = path.join(git_dir, "refs/tags/%s"
|
|
|
|
@ -305,6 +385,91 @@ class Git:
|
|
|
|
|
return fetch_info['type']
|
|
|
|
|
return self.Reference.Unknown
|
|
|
|
|
|
|
|
|
|
class GitProtocol(object):
|
|
|
|
|
SSH = 22
|
|
|
|
|
Git = 9418
|
|
|
|
|
HTTP = 80
|
|
|
|
|
HTTPS = 443
|
|
|
|
|
FTP = 21
|
|
|
|
|
FTPS = 990
|
|
|
|
|
Rsync = 873
|
|
|
|
|
Unknown = 0
|
|
|
|
|
|
|
|
|
|
def _parse_url(self, url):
|
|
|
|
|
"""
|
|
|
|
|
Разобрать url на составляющие: тип и хост
|
|
|
|
|
|
|
|
|
|
:param url: разбираемый URL
|
|
|
|
|
:return: (тип, хост)
|
|
|
|
|
"""
|
|
|
|
|
net_protocols = {
|
|
|
|
|
'ssh': self.GitProtocol.SSH,
|
|
|
|
|
'ssh+git': self.GitProtocol.SSH,
|
|
|
|
|
'git+ssh': self.GitProtocol.SSH,
|
|
|
|
|
'http': self.GitProtocol.HTTP,
|
|
|
|
|
'https': self.GitProtocol.HTTPS,
|
|
|
|
|
'ftp': self.GitProtocol.FTP,
|
|
|
|
|
'ftps': self.GitProtocol.FTPS,
|
|
|
|
|
'rsync': self.GitProtocol.Rsync,
|
|
|
|
|
'git': self.GitProtocol.Git}
|
|
|
|
|
re_host = re.compile(
|
|
|
|
|
"(%s)://(?:[^@]+@)?([^:/]+)" %
|
|
|
|
|
"|".join(x.replace("+", r"\+") for x in net_protocols.keys())
|
|
|
|
|
)
|
|
|
|
|
net_match = re_host.search(url)
|
|
|
|
|
if net_match:
|
|
|
|
|
return net_protocols[net_match.group(1)], net_match.group(2)
|
|
|
|
|
re_scp = re.compile("(?:[^@]+@)?([^:]+):(//)?")
|
|
|
|
|
scp_match = re_scp.search(url)
|
|
|
|
|
if scp_match and scp_match.group(2) is None:
|
|
|
|
|
return self.GitProtocol.SSH, scp_match.group(1)
|
|
|
|
|
return self.GitProtocol.Unknown, ""
|
|
|
|
|
|
|
|
|
|
def checkUrl(self, url):
|
|
|
|
|
"""
|
|
|
|
|
Проверить доступность указанного URL
|
|
|
|
|
|
|
|
|
|
ssh://[user@]host.xz[:port]/path/to/repo.git/
|
|
|
|
|
git://host.xz[:port]/path/to/repo.git/
|
|
|
|
|
http[s]://host.xz[:port]/path/to/repo.git/
|
|
|
|
|
ftp[s]://host.xz[:port]/path/to/repo.git/
|
|
|
|
|
rsync://host.xz/path/to/repo.git/
|
|
|
|
|
[user@]host.xz:path/to/repo.git/
|
|
|
|
|
|
|
|
|
|
/path/to/repo.git/
|
|
|
|
|
file:///path/to/repo.git/
|
|
|
|
|
|
|
|
|
|
:param url: проверяемый URL
|
|
|
|
|
:return:
|
|
|
|
|
"""
|
|
|
|
|
git_port, target = self._parse_url(url)
|
|
|
|
|
if git_port != self.GitProtocol.Unknown:
|
|
|
|
|
return check_port(target, git_port)
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def isNeedUnpack(self, rpath, fns=(".gitignore",
|
|
|
|
|
"metadata/.gitignore")):
|
|
|
|
|
"""
|
|
|
|
|
Проверить репозиторий на то, что он еще не распакован
|
|
|
|
|
:param rpath: путь до репозитория
|
|
|
|
|
:param fns: имя файла
|
|
|
|
|
:return:
|
|
|
|
|
"""
|
|
|
|
|
for fn in fns:
|
|
|
|
|
if path.exists(path.join(rpath, fn)):
|
|
|
|
|
return False
|
|
|
|
|
else:
|
|
|
|
|
git_dir = self._gitDir(rpath)
|
|
|
|
|
git_log = process(self._git, "--git-dir", git_dir, "log",
|
|
|
|
|
"-n1", "--format=format:%H",
|
|
|
|
|
"--quiet", "--", *fns, stderr=STDOUT)
|
|
|
|
|
if git_log.success() and git_log.read().strip():
|
|
|
|
|
return True
|
|
|
|
|
status = self.getStatusInfo(rpath)
|
|
|
|
|
if not status or status['files']:
|
|
|
|
|
return True
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def cloneRevRepository(self, url, rpath, branch, revision,
|
|
|
|
|
cb_progress=None):
|
|
|
|
|
"""
|
|
|
|
@ -586,3 +751,35 @@ class Git:
|
|
|
|
|
"repository").format(branch=branch,
|
|
|
|
|
rpath=rpath), error)
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MTimeKeeper(object):
|
|
|
|
|
"""
|
|
|
|
|
Сохранить и восстановить mtime на файлы
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, dn):
|
|
|
|
|
self.data = {}
|
|
|
|
|
self.dn = dn
|
|
|
|
|
|
|
|
|
|
def save(self):
|
|
|
|
|
for fn in listDirectory(self.dn, fullPath=True):
|
|
|
|
|
try:
|
|
|
|
|
stat_data = os.stat(fn)
|
|
|
|
|
self.data[fn] = {'size': stat_data.st_size,
|
|
|
|
|
'mtime': stat_data.st_mtime,
|
|
|
|
|
'atime': stat_data.st_atime,
|
|
|
|
|
}
|
|
|
|
|
except OSError:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def restore(self):
|
|
|
|
|
for fn in self.data.keys():
|
|
|
|
|
try:
|
|
|
|
|
if path.exists(fn):
|
|
|
|
|
stat_data = os.stat(fn)
|
|
|
|
|
if stat_data.st_size == self.data[fn]['size']:
|
|
|
|
|
os.utime(fn, (self.data[fn]['atime'],
|
|
|
|
|
self.data[fn]['mtime']))
|
|
|
|
|
except OSError:
|
|
|
|
|
pass
|
|
|
|
|