Git checkout в рабочий каталог с символическими ссылками
В этом случае репозиторий не имеет символических ссылок, но рабочий каталог, в который я проверяю, имеет. Зачем? У меня есть существующее хранилище для веб-сайта. Для упрощения, скажем, структура каталогов для этого:
бункер WWW
Но теперь, чтобы сэкономить деньги, я припарковал этот сайт под существующей учетной записью провайдера, так что каталог www теперь является подкаталогом www с именем parked-domain. Итак, фактическая структура каталогов теперь:
бункер WWW припаркованный домен
Я решил создать каталог с именем workdir с двумя символическими ссылками с именами bin и www, которые ссылались на каталоги bin и parked-domain в предыдущей структуре каталогов. Затем я выполняю команду checkout с параметром --work-tree, указывающим этот каталог workdir, и надеюсь, что, поскольку каталоги bin и www в workdir уже существуют, команда checkout не будет их воссоздавать. Но увы, символические ссылки удаляются и создаются обычные каталоги. Есть ли решение этой проблемы, кроме копирования всей структуры каталогов?
1 ответ
Не похоже, что это выполнимо. Что бы вы хотели: (1) возможность извлекать только один подкаталог и (2) иметь возможность переопределить имя каталога для использования в рабочем каталоге при извлечении этого подкаталога. Что-то вроде следующего:
git --work-tree=$HOME/www checkout-tree master www parked-domain
Кстати, CVS, который в наши дни любят стучать, может это сделать.
Решение, которое я нашел, которое я выбрасываю для комментариев, заключается в следующем:
Я создал рабочий каталог work_dir, в котором я создал символические ссылки на "настоящие" каталоги. Например,
ln -s ~/www/parked-domain www
В моем удаленном репозитории (инициализирован с помощью опции --bare) я создал хук после получения. Мы знаем, что этот хук не может использовать команду git checkout, потому что символические ссылки будут наложены. Вместо этого выполняется команда git diff, чтобы увидеть, какие файлы были добавлены / изменены, и какие файлы были удалены текущим нажатием. Затем ловушка выполняет команду удаления для каждого файла, который был указан как удаленный нажатием. Затем ловушка выполняет команду git archive, чтобы выгрузить каждый файл, который был либо добавлен, либо изменен с помощью push-запроса, сопровождаемого командой tar для извлечения этих файлов в рабочий каталог. Для самого первого нажатия "из ревизии хэш" равен 40 нулям, и в этом случае весь репозиторий выгружается и извлекается через архив git. Вот реализация в Python:
#!/usr/bin/env python
import sys, os, subprocess, re, string
SEP = os.sep
def log(s):
sys.stdout.write(s);
sys.stdout.flush();
def runCmd(cmd):
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
output = p.communicate()
return output[0]
def runPipe(cmd1, cmd2):
p1 = subprocess.Popen(cmd1, stdout=subprocess.PIPE)
p2 = subprocess.Popen(cmd2, stdin=p1.stdout, stdout=subprocess.PIPE)
output = p2.communicate()
return output[0]
def getBranch(ref, gitDir):
gitDirParam = '--git-dir=' + gitDir
return runCmd(['git', gitDirParam, 'rev-parse', '--symbolic', '--abbrev-ref', ref]).strip()
def isRenamedDirectory(fromDir, renameDict):
# simple case:
if fromDir in renameDict:
return True, renameDict[fromDir]
fDir = fromDir.split(SEP)
n = len(fDir)
if n != 1:
for i in range(n - 1, 0, -1):
fromDirNew = SEP.join(fDir[0:i])
if fromDirNew in renameDict:
newDir = renameDict[fromDirNew] + SEP + SEP.join(fDir[i:])
renameDict[fromDir] = newDir
return True, newDir
return False, fromDir
def process(fromCommit, toCommit, gitDir):
log('Processing %s - %s\n' % (fromCommit, toCommit))
gitDirParam = '--git-dir=' + gitDir
if fromCommit == 40*'0':
log('"Checking out" everything.\n')
archiveCmd = ['git', gitDirParam, 'archive', '--format=tar.gz', toCommit]
tarCmd = ['tar', '--warning=no-timestamp', '-zxf', '-']
out = runPipe(archiveCmd, tarCmd)
log(out)
return
diffs = runCmd(['git', gitDirParam, 'diff', '--name-status', '--find-renames=100', fromCommit, toCommit]).split('\n')
updateList = []
renameDict = dict()
renameSet = set()
defferedDiffs = []
for this_pass in range(1, 3):
for diff in diffs if this_pass == 1 else defferedDiffs:
m = re.match(r'^R100\t(.*)\t(.*)$', diff)
if m:
# rename:
fromPath = m.group(1)
toPath = m.group(2)
(fromDir, fromFilename) = os.path.split(fromPath)
(toDir, toFilename) = os.path.split(toPath)
isRenamed, newDir = isRenamedDirectory(fromDir, renameDict)
if isRenamed:
# it's been renamed to newDir
fromPath = newDir + SEP + fromFilename
elif fromDir != toDir and fromDir != '' and toDir != '' and not os.path.isdir(toDir):
# a directory is being renamed or moved
# has directory already been renamed or moved?
fDir = fromDir.split(SEP)
tDir = toDir.split(SEP)
l = min(len(fDir), len(tDir))
i = 0;
fDirLast = []
while i < l and fDir[-1] == tDir[-1]:
fDirLast.append(fDir.pop(-1))
tDir.pop(-1)
i += 1
subDirOld = SEP.join(fDir)
subDirNew = SEP.join(tDir)
while subDirOld not in renameSet and os.path.isdir(subDirNew):
assert len(fDirLast)
lastDir = fDirLast.pop(-1)
subDirOld += SEP + lastDir
subDirNew += SEP + lastDir
if subDirOld not in renameSet:
log("Renaming directory %s to %s\n" % (subDirOld, subDirNew))
runCmd(['mv', subDirOld, subDirNew])
renameSet.add(subDirOld)
renameDict[fromDir] = toDir
# this is the name of the file in the renamed or removed path
fromPath = toDir + SEP + fromFilename
# fall through and see if filename has also changed
if fromPath != toPath:
if os.path.isdir(toDir):
log("Renaming file %s to %s\n" % (fromPath, toPath))
runCmd(['mv', fromPath, toPath])
else:
# we have to defer this to a second pass
if this_pass == 1:
defferedDiffs.append(diff)
else:
log("Can't rename file %s to %s -- directory %s does not exist. % (fromPath, toPath, toDir)")
continue
m = re.match(r'^([ACDMTUXB])\t(.*)$', diff)
if m is None:
continue
action = m.group(1)
path = m.group(2)
if action != 'D':
updateList.append(path)
else:
(fileDir, fileName) = os.path.split(path)
if fileDir != '' and fileDir in renameDict:
path = renameDict[fileDir] + SEP + fileName
if os.path.isfile(path):
try:
os.remove(path)
except Exception as e:
log('Could not delete %s: %s\n' % (path, str(e)))
else:
log('Deleted %s\n' % path)
if len(updateList) == 0:
return
archiveCmd = ['git', gitDirParam, 'archive', '--format=tar.gz', toCommit]
archiveCmd.extend(updateList);
tarCmd = ['tar', '--warning=no-timestamp', '-zxvf', '-']
out = runPipe(archiveCmd, tarCmd)
log(out)
def process_revisions(gitDir, workDir):
log('Using Git directory %s, work directory %s\n' % (gitDir, workDir))
curDir = os.getcwd()
try:
os.chdir(workDir)
lines = sys.stdin.readlines()
for line in lines:
tokens = string.split(line.strip())
fromCommit = tokens[0]
toCommit = tokens[1]
branch = getBranch(tokens[2], gitDir)
if branch != 'master':
log('Not on master branch -- skipping.\n')
else:
process(fromCommit, toCommit, gitDir)
except Exception as e:
log(str(e))
os.chdir(curDir)
GIT_DIR = '%s%s%s' % (os.environ['HOME'], SEP, 'repository.git')
WORK_DIR = '%s%s%s' % (os.environ['HOME'], SEP, 'work_dir')
process_revisions(GIT_DIR, WORK_DIR)