Как эффективно синхронизировать несколько подмодулей Git - PullRequest
0 голосов
/ 23 мая 2018

Я работаю над многоуровневым приложением, в котором я назначил папку проекта для каждой службы, которую я создаю.Схема установки выглядит примерно так:

.
├── ProjectA
│   ├── .git
│   ├── _framework
│   ├── backend
│   ├── config
│   ├── frontend
│   └── info
├── ProjectB
│   ├── .git
│   ├── _framework
│   ├── backend
│   ├── config
│   ├── frontend
│   └── info
└── ProjectC
    ├── .git
    ├── _framework
    ├── backend
    ├── config
    ├── frontend
    └── info

В каждой папке проекта у меня есть настроенный субмодуль в папке _framework.Поскольку я активно развиваюсь в каждой из папок, я также часто делаю изменения в папке _framework.Я обнаружил, что синхронизация подмодуля _framework между всеми проектами занимает много времени.

Например: когда я занимаюсь разработкой в ​​ProjectB и внесу изменения в подмодуль, я будузафиксировать и отправить мои изменения в удаленный репозиторий.Затем, когда я переключаюсь на ProjectC, мне сначала нужно вытащить подмодуль _framework, зафиксировать изменения в основном репозитории GIT, прежде чем я смогу снова начать работать.

Я понимаю, что есть хорошиепричины, по которым субмодули GIT настроены таким образом, но есть ли способ автоматизировать процесс?Так что, когда я работаю в ProjectB и нажимаю субмодуль - что в других проектах этот же субмодуль автоматически извлекается и фиксируется в основном локальном репозитории GIT?

Ответы [ 2 ]

0 голосов
/ 30 мая 2018

Используя предложение @jingx, я решил сделать pre-push hook в репозиториях git ProjectA, ProjectB, ProjectC.Хук pre-push казался логичным событием, которое нужно использовать, поскольку вы все равно должны выдвигать новое состояние подмодулей в главном репозитории, когда вы изменили и успешно протестировали код в подмодуле Git.

Как ссылкана ловушках Git я использовал это превосходное объяснение от Atlassian: https://www.atlassian.com/git/tutorials/git-hooks

Поскольку я более знаком с JavaScript, чем со сценариями Bash, я решил создать сценарий NodeJS, который будет выполняться как ловушка перед нажатием:

#!/usr/bin/env node


const fs = require('fs');
const path = require('path');
const util = require('util');


// Logging something to the console so that we can verify if the hook is triggered or not
console.log('Invoking pre-push hook v1.0');

// Global variables
const exec = util.promisify(require('child_process').exec);
const gitRepositoryPathOs = process.argv[1].replace(/(.*)\/\.git\/.*/, '$1');
const gitRepositoryParentFolder = path.dirname(gitRepositoryPathOs);
const gitRepositoryFolderName = path.basename(gitRepositoryPathOs);
const scriptFilePathOs = __filename;
const scriptLogFolderPathOs = `${path.dirname(scriptFilePathOs)}/logs`;

const pushSiblingProjects = false;
const debugRoutine = true;
let debugInfo = "";
let stdIn = [];

// Defines all the project folder names where this hook should be working with
const projectFolderNames = ['ProjectA', 'ProjectB', 'ProjectC'];
// Defines the submodules that this routine should be checking for in each project folder
const submodulePaths = ['_framework'];



/**
 * Executes a shell command
 * 
 * @param {string} cmd Shell command to execute
 * @param {string} [workingDirectory=gitRepositoryParentFolder] Directory to execute the shell command in
 * @returns The result of the shell command
 */
const executeShellCommand = async (cmd, workingDirectory = gitRepositoryPathOs) => {
    // if (debugRoutine) debugInfo += `- executeShellCommand('${cmd}', '${workingDirectory}')\n`;
    const {stdout, stderr} = await exec(cmd, {
        cwd: workingDirectory
    });

    return stdout;
}

/**
 * Starts the logic of this GIT hook routine
 * 
 * @returns Exit code to indicate to the GIT process if it should continue or not
 */
const initGitHookRoutine = async () => {
    // Global variables
    let shellCommand = '';
    let shellResult = '';
    let siblingGitRepositoryUpdateExecuted = false;

    // Catch all parameters passed to the process.argv
    debugInfo += "Passed arguments:\n\n";
    process.argv.forEach(function (val, index, array) {
        debugInfo += `${index}: ${val}\n`;
    });

    debugInfo += `Standard In:\n${stdIn.join()}`;

    debugInfo += "\n\n";
    debugInfo += `- gitRepositoryPathOs: '${gitRepositoryPathOs}'\n`;
    debugInfo += `- gitRepositoryParentFolder: '${gitRepositoryParentFolder}'\n`;
    debugInfo += `- gitRepositoryFolderName: '${gitRepositoryFolderName}'\n`;
    debugInfo += `- scriptFilePathOs: '${scriptFilePathOs}'\n`;


    try {
        // Retrieve a list of objects that we are about to push to the remote repository
        shellCommand = `git diff --stat --cached origin/master`;
        shellResult = await executeShellCommand(shellCommand);
    } catch (err) {
        shellResult = `ERROR: could not execute '${shellCommand}', error details: ${JSON.stringify(err)}`;
    }
    debugInfo += `- shellCommand: '${shellCommand}', shellResult: '${shellResult}'\n`;

    // Mark which submodules we need to process
    let submodulePathsToProcess = [];
    submodulePaths.forEach((submodulePath) => {
        if (shellResult.indexOf(submodulePath) > -1) {
            submodulePathsToProcess.push(submodulePath);
        }
    })
    debugInfo += `- submodulePathsToProcess: ${submodulePathsToProcess.join()}\n`;


    if (submodulePathsToProcess.length > 0) {
        let submodulePath = '';

        // Now loop through the other projects and update the submodules there 
        // Using "old fashioned loop style" here as it seems to work better with async function calls...
        for (let i = 0; i < projectFolderNames.length; i++) {
            const projectFolderName = projectFolderNames[i];

            if (projectFolderName !== gitRepositoryFolderName) {
                const siblingGitRepositoryPathOs = `${gitRepositoryParentFolder}/${projectFolderName}`;
                debugInfo += `- processing GIT repository '${siblingGitRepositoryPathOs}'\n`;

                // Loop through the submodules that we need to update
                for (let j = 0; j < submodulePathsToProcess.length; j++) {
                    submodulePath = submodulePathsToProcess[j];

                    const siblingGitRepositorySubmodulePathOs = `${siblingGitRepositoryPathOs}/${submodulePath}`;
                    debugInfo += `- processing GIT submodule '${siblingGitRepositorySubmodulePathOs}'\n`;

                    try {
                        // Pull the latest version of the submodule from the remote repository
                        shellCommand = `git pull origin master`;
                        shellResult = await executeShellCommand(shellCommand, siblingGitRepositorySubmodulePathOs);
                    } catch (err) {
                        shellResult = `ERROR: could not execute '${shellCommand}', error details: ${JSON.stringify(err)}`;
                    }
                    debugInfo += `- shellCommand: '${shellCommand}', shellResult: '${shellResult}'\n`;
                }

                // Use git status to check which submodules need to be committed
                try {
                    shellCommand = `git status`;
                    shellResult = await executeShellCommand(shellCommand, siblingGitRepositoryPathOs);
                } catch (err) {
                    shellResult = `ERROR: could not execute '${shellCommand}', error details: ${JSON.stringify(err)}`;
                }
                debugInfo += `- shellCommand: '${shellCommand}', shellResult: '${shellResult}'\n`;

                // Now check for each submodule if it needs to be committed to the sibling project repository
                for (let j = 0; j < submodulePathsToProcess.length; j++) {
                    submodulePath = submodulePathsToProcess[j];

                    if (shellResult.indexOf(`${submodulePath} (new commits)`) > -1) {
                        // 1) Add the submodule reference to the local git staging area
                        try {
                            shellCommand = `git add ${submodulePath}`;
                            shellResult = await executeShellCommand(shellCommand, siblingGitRepositoryPathOs);
                        } catch (err) {
                            shellResult = `ERROR: could not execute '${shellCommand}', error details: ${JSON.stringify(err)}`;
                        }
                        debugInfo += `- shellCommand: '${shellCommand}', shellResult: '${shellResult}'\n`;

                        // 2) Commit the submodule reference to the local git repository
                        if (shellResult.indexOf('ERROR') === -1) {
                            siblingGitRepositoryUpdateExecuted = true;
                            try {
                                shellCommand = `git commit -m "Submodule ${path.basename(submodulePath)} updated by GIT hook utility"`;
                                shellResult = await executeShellCommand(shellCommand, siblingGitRepositoryPathOs);
                            } catch (err) {
                                shellResult = `ERROR: could not execute '${shellCommand}', error details: ${JSON.stringify(err)}`;
                            }
                            debugInfo += `- shellCommand: '${shellCommand}', shellResult: '${shellResult}'\n`;
                        }

                        // 3) Optionally push this to the remote repository (not recommended)
                        if (pushSiblingProjects) {
                            if (shellResult.indexOf('ERROR') === -1) {
                                try {
                                    shellCommand = `git push origin master`;
                                    shellResult = await executeShellCommand(shellCommand, siblingGitRepositoryPathOs);
                                } catch (err) {
                                    shellResult = `ERROR: could not execute '${shellCommand}', error details: ${JSON.stringify(err)}`;
                                }
                                debugInfo += `- shellCommand: '${shellCommand}', shellResult: '${shellResult}'\n`;
                            }
                        }

                    }

                }
            }
        }

        // Check if we need to execute anything after we have modified the sibling repositories
        if (siblingGitRepositoryUpdateExecuted) {
            // Put your logic in here
        }

    }

    // Dump the debug information to the console
    if (debugRoutine) console.log(`* debugInfo: ${debugInfo}`);

    // Dump the debug information in a log file so that we can inspect it later on
    try {
        fs.writeFileSync(`${scriptLogFolderPathOs}/pre-push.log`, debugInfo);
    } catch (err) {
        console.log(`ERROR: write the log file in folder '${scriptLogFolderPathOs}', error details: ${JSON.stringify(err)}`);
    }

    // To avoid push from taking place exit a code > 0
    return process.exit(0);
};


/** 
 * This is where the execution starts
 * First we capture the content from the shell standard in and then we start the logic itself
*/

// NOTE: use 'npm install split --save-dev' to install the split module
process.stdin.pipe(require('split')()).on('data', (line) => {
    // Push the line into the stdIn array so we can use it later on
    if (line.trim() !== '') stdIn.push(line);
}).on('end', initGitHookRoutine)

Для сценария NodeJS требуется один внешний модуль ('split') для синтаксического анализа стандартного входа из команды оболочки, которую хук Git добавит при вызове сценария.Код все еще немного грубоват, но я подумал, что этого будет достаточно для расширения.

Чтобы заставить скрипт NodeJS работать, вам нужно установить права доступа к файлу в скрипте NodeJS, чтобы он включал в себя команду execute.(см. страницу Atlassian).

На моем Mac скрипт не запускался, потому что хэшбанг #!/usr/bin/env node отказывался выполняться.Мне удалось решить эту проблему, создав символическую ссылку на исполняемый файл узла с помощью sudo ln -s /usr/bin/node /usr/local/bin/node

. На моем компьютере мне сначала пришлось отключить защиту целостности системы, прежде чем я смог создать символическую ссылку.Больше информации здесь: https://www.imore.com/el-capitan-system-integrity-protection-helps-keep-malware-away

0 голосов
/ 24 мая 2018

В качестве альтернативы можно указать _framework:

  • в качестве подмодуля в вашем основном проекте (на том же уровне, что и проекты A, B и C)
  • в качествесимволическая ссылка в каждом подпроекте (-> ../framework_)

Таким образом, только основной проект должен отслеживать framework_ версию.

...