Вызов git в хайке pre-commit - PullRequest
       52

Вызов git в хайке pre-commit

0 голосов
/ 10 января 2020

Я получаю странные результаты запуска моего git pre-commit hook, например, когда я делаю git diff --name-only в терминале, он, кажется, дает другой результат, чем когда он выполняется в .git / hooks / pre-commit

Итак, мои вопросы:

  1. Могу ли я позвонить git внутри git hooks?
  2. Если 1. все в порядке : когда точно вызывается ловушка pre-commit, если я делаю git commit -am "bla"? В частности, git сначала делает постановку, а затем вызывает ловушку перед фиксацией или нет?

Я спрашиваю это, потому что я пытался это сделать 2 или 3 раза: я изменяю файл, я запускаю Сценарий вручную выводит

#! /bin/sh -xv
files=$(git diff --name-only)
+ git diff --name-only
+ files=path/to/file.h
echo $files
+ echo path/to/file.h
path/to/file.h
...

Когда я выполняю git commit -am "eh", вывод будет другим

#! /bin/sh -xv
files=$(git diff --name-only)
+ git diff --name-only
+ files=
echo $files
+ echo

Ответы [ 2 ]

3 голосов
/ 11 января 2020
  1. Могу ли я позвонить git внутри git хуков?

Да, но вы должны проявлять осторожность, так как есть ряд вещей установить в среде, и вы работаете с чем-то, что находится в середине выполнения:

  • GIT_DIR задан путь к каталогу Git.
  • GIT_WORKTREE может быть установлено в путь к рабочему дереву (из git --work-tree).
  • Другие переменные Git, такие как GIT_NO_REPLACE_OBJECTS, также могут быть установлены из командной строки.

(Вы должны оставить эти настройки, если продолжаете работать с текущим хранилищем, но очистить их, если вы работаете с другим хранилищем.)

Если 1. все в порядке: когда точно вызывается ловушка pre-commit, если я делаю git commit -am "bla"? В частности, git сначала делает постановку, а затем вызывает ловушку перед фиксацией или нет?

Это сложно.

Есть три "режима", которые git commit использует внутренне. (Об этом нет никаких обещаний, но вот как все это реализовано в течение многих лет, поэтому эта трехрежимная работа кажется довольно стабильной.) Режимы:

  • git commit без -a, --include, --only и / или любые имена файлов, указанные в командной строке. Это стандартный или нормальный режим. Детали базовой реализации не отображаются через.

  • git commit с -a или с указанными в командной строке именами файлов. Это делит на два подрежима:

    • такой коммит с --include, или
    • такой коммит с --only.


    На этом этапе базовая реализация просвечивает.

Основные детали реализации здесь включают в себя то, что Git вызывает, по-разному, index , область подготовки и (редко в настоящее время) кэш , который обычно реализуется в виде файла с именем $GIT_DIR/index (где $GIT_DIR - переменная среды из примечания о точке 1) , Обычно есть только один из них: индекс . Он содержит контент, который вы намереваетесь зафиксировать. 1 Когда вы запускаете git commit, Git будет упаковывать все, что является в индексом, как следующий коммит.

Но во время операции из git commit может быть до трех индексных файлов. Для обычного git commit есть только один индекс, и ваш хук перед фиксацией может использовать его и даже обновлять. (Я советую не обновлять его, по причинам, которые мы увидим через мгновение.)

Но, если вы сделаете git commit -a или git commit --include file.ext, теперь есть два индекса файлы. Есть контент, готовый для фиксации - обычный индекс - и один дополнительный индекс, который является исходным индексом плюс результат выполнения git add для file.ext или для все файлы (эквивалент git add -u). Итак, теперь есть два индексных файла.

В этом режиме Git оставляет обычный индексный файл как обычный индексный файл. Этот файл в $GIT_DIR/index как обычно. второй индексный файл с дополнительным добавленным содержимым находится в $GIT_DIR/index.lock, а переменная окружения GIT_INDEX_FILE установлена ​​для хранения этого пути. Если фиксация не удалась , Git удалит файл index.lock, и все будет так, как если бы вы вообще не запускали git commit. Если фиксация завершится успешно , Git переименует index.lock в index, сняв блокировку и обновив (стандартный, обычный) индекс все в одном движении.

Наконец, есть третий режим, который вы получаете, например, при запуске git commit --only file.ext. Здесь теперь есть три индексных файла:

  • $GIT_DIR/index: стандартный индекс, который содержит то, что он обычно делает.
  • $GIT_DIR/index.lock: A копия стандартного индекса, для которого file.ext был git add -ед.
  • $GIT_DIR/index<em>suffix</em>: копия HEAD commit 2 , для которого file.ext был git add -ed.

Переменная среды GIT_INDEX_PATH указывает на этот третий индекс. Если фиксация завершится успешно, Git переименует файл index.lock в index, чтобы он стал индексом . Если фиксация завершится неудачно, Git удалит файл index.lock, поэтому индекс вернется к состоянию, в котором он находился до того, как вы начали. (И в любом случае Git удаляет третий индекс, который теперь служит своей цели.)

Обратите внимание, что из ловушки перед фиксацией вы можете определить, является ли git commit стандартным коммитом (GIT_INDEX_FILE не установлено или установлено на $GIT_DIR/index) или один из двух специальных режимов. В стандартном режиме, если вы хотите обновить индекс , вы можете сделать это как обычно. В двух специальных режимах вы можете использовать git add для изменения файла с именами GIT_INDEX_FILE, что изменит то, что входит в коммит; и если вы используете коммит в стиле * 1163, это также изменяет то, что станет стандартным индексом успеха. Но если вы находитесь в режиме --only, изменение предложенного коммита не влияет на стандартные index, и index.lock, которые станут стандартный индекс.

Чтобы рассмотреть конкретный пример, предположим, что пользователь сделал:

git add file1 file2

, чтобы стандартный индекс совпадал с HEAD, за исключением file1 и file2 , Затем пользователь запускает:

git commit --only file3

, так что предлагаемый коммит является копией HEAD с добавлением file3, и , если этот коммит завершится успешно, Git заменит стандартный индекс с индексом, в который добавляются file1, file2 и file3 (но поскольку file3 будет соответствовать новому HEAD коммиту, только файлы 1 и 2 будут модифицированы в новом индексе).

Теперь предположим, что хук фиксации запускает git add file4 и процесс в целом завершается успешно (новый коммит выполнен успешно). Шаг git add скопирует версию рабочего дерева file4 во временный индекс, так что для фиксации будут обновлены file3 и file4. Затем Git переименует файл index.lock, чтобы file3 совпадал с новым HEAD коммитом. Но file4 в index.lock никогда не обновлялся, поэтому не будет соответствовать фиксации HEAD. Пользователю покажется, что как-то file4 вернулось! git status покажет ожидающие изменения в нем, подготовленные для фиксации, а git diff --cached покажет, что разница между HEAD и индексом заключается в том, что file4 был изменен обратно, чтобы соответствовать file4 в HEAD~1.

Вы могли бы пройти предварительную проверку хука для этого режима и отказаться от git add файлов в этом режиме, чтобы избежать проблемы. (Или вы могли бы даже незаметно добавить file4 к index.lock с помощью второй команды git add!) Но обычно лучше, чтобы ваш хук просто отклонял коммит, советуя пользователю делать любые git add с сами по себе, так что вам не нужно знать все эти секреты реализации о git commit в первую очередь.


1 Индекс содержит также некоторая дополнительная информация: данные кэша о рабочего дерева. Вот почему его иногда называют кешем. Эти дополнительные копии, которые я здесь описываю, создаются путем копирования исходного индекса, поэтому дополнительные копии также имеют те же данные кэша, за исключением случаев, когда они обновляются через git add.

2 * 1242. * Не указано, будет ли Git делать эту копию с помощью внутреннего эквивалента:

TMP=$GIT_DIR/index<digits>
cp $GIT_DIR/index $TMP
GIT_INDEX_FILE=$TMP git reset
GIT_INDEX_FILE=$TMP git add file3

или каким-либо другим способом (например, внутренним эквивалентом git read-tree), но так как эта конкретная копия всегда просто удалено в конце процесса, это не имеет значения: любая информация из кэша для рабочего дерева становится неактуальной.

1 голос
/ 10 января 2020

Да, изменения, похоже, уже кэшированы. Используйте git diff --cached --name-only для просмотра списка файлов, которые должны быть зафиксированы.

...