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)
Другие вопросы по тегам