В Git, как я могу записать текущий хеш коммита в файл в том же коммите

131

Я пытаюсь сделать что-то необычное с помощью хитов Git, но я не знаю, как это сделать (или, если это возможно).

Что мне нужно сделать, так это: при каждом коммите я хочу взять его хеш, а затем обновить файл в коммите с помощью этого хэша.

Любые идеи?

Фелипе Камакура
источник
12
В основном у меня есть веб-приложение, и я хочу связать установленную версию этого приложения с точным коммитом, с которым связана эта версия. Моей первоначальной идеей было обновить что-то вроде файла about.html с помощью хеша коммита. Но после изучения модели объектов в git я понял, что это как-то невозможно = /
Фелипе Камакура
29
Это очень практичная проблема. Я тоже столкнулся с этим!
Ли Донг
7
Что касается меня, я бы хотел, чтобы моя программа записывала подобное сообщение в журналы: «myprog starting, v.56c6bb2». Таким образом, если кто-то регистрирует ошибку и отправляет мне файлы журнала, я могу точно узнать, какая версия моей программы была запущена.
Эдвард Фальк
5
@Jefromi, фактический случай использования на самом деле очень распространен, и очень легко поражает новичков. Наличие реальной версии, каким-то образом «отпечатанной» в базовых файлах, является основной потребностью, и далеко не очевидно, почему это было бы неверной идеей, например, потому что это в значительной степени ваш единственный вариант с ручными проверками ревизий. (Помните новичков.) Добавьте к этому, что многие проекты просто не имеют какого-либо шага сборки / установки / развертывания, который мог бы захватить и запечатать версию в живые файлы. В любом случае, вместо предварительной фиксации хук после проверки может помочь даже в этих случаях.
Sz.
Это невозможно! Если вы можете сделать это, вы нарушили алгоритм хеширования SHA-1 ... ericsink.com/vcbe/html/cryptographic_hashes.html
betontalpfa

Ответы:

82

Я бы рекомендовал сделать что-то похожее на то, что вы имеете в виду: поместить SHA1 в неотслеживаемый файл, сгенерированный как часть процесса сборки / установки / развертывания. Это, очевидно, легко сделать ( git rev-parse HEAD > filenameили, возможно, git describe [--tags] > filename), и это позволяет избежать каких-либо сумасшедших действий, таких как создание файла, отличного от того, что отслеживает git.

Ваш код может затем ссылаться на этот файл, когда ему нужен номер версии, или процесс сборки может включить информацию в конечный продукт. Последнее на самом деле - то, как сам git получает свои номера версий - процесс сборки извлекает номер версии из репозитория, а затем встраивает его в исполняемый файл.

Cascabel
источник
3
Может ли кто-то более подробно изложить шаг за шагом, как это сделать? Или хотя бы толчок в правильном направлении?
Джоэл Уоршам
1
@ Джоэль Как сделать что? Я упоминал, как поместить хеш в файл; остальное, вероятно, что-то в вашем процессе сборки? Может быть, новый вопрос, если вы пытаетесь задать об этой части.
Каскабель
1
В моем случае я добавил в свой Makefile правило, которое генерирует файл «gitversion.h» при каждой сборке. См. Stackoverflow.com/a/38087913/338479
Эдвард Фальк
1
Вы могли бы автоматизировать это с помощью git-checkout. Проблема в том, что крючки должны быть установлены вручную.
Эдвард Фальк
14

Невозможно записать текущий хеш коммита: если вам удастся предварительно рассчитать хэш будущего коммита - он изменится, как только вы измените какой-либо файл.

Однако есть три варианта:

  1. Используйте скрипт, чтобы увеличить «идентификатор фиксации» и включить его где-нибудь. уродливый
  2. .gitignore файл, в котором вы собираетесь хранить хеш. Не очень удобно
  3. В pre-commit, сохраните предыдущий хеш коммита :) Вы не модифицируете / вставляете коммиты в 99,99% случаев, поэтому это БУДЕТ работать В худшем случае вы все еще можете определить исходную ревизию.

Я работаю над хук-скриптом, выложу его здесь «когда это будет сделано», но все же - раньше, чем выйдет Duke Nukem Forever :))

Обновление : код для .git/hooks/pre-commit:

#!/usr/bin/env bash
set -e

#=== 'prev-commit' solution by o_O Tync
#commit_hash=$(git rev-parse --verify HEAD)
commit=$(git log -1 --pretty="%H%n%ci") # hash \n date
commit_hash=$(echo "$commit" | head -1)
commit_date=$(echo "$commit" | head -2 | tail -1) # 2010-12-28 05:16:23 +0300

branch_name=$(git symbolic-ref -q HEAD) # http://stackoverflow.com/questions/1593051/#1593487
branch_name=${branch_name##refs/heads/}
branch_name=${branch_name:-HEAD} # 'HEAD' indicates detached HEAD situation

# Write it
echo -e "prev_commit='$commit_hash'\ndate='$commit_date'\nbranch='$branch'\n" > gitcommit.py

Теперь единственное, что нам нужно, это инструмент, который конвертирует prev_commit,branchпару в реальный хеш коммита :)

Я не знаю, может ли этот подход отличить слияние коммитов. Скоро проверим

kolypto
источник
13

Кто-то указал мне на раздел "man gitattributes" на identif, который имеет это:

идент

Если для пути задан идентификатор атрибута, git заменяет $ Id $ в объекте BLOB-объекта на $ Id:, за которым следует 40-символьное имя объекта шестнадцатеричного двоичного объекта, за которым следует знак доллара $ при получении. Любая последовательность байтов, которая начинается с $ Id: и заканчивается $ в файле рабочего дерева, заменяется на $ Id $ при регистрации.

Если подумать, это то, что делают CVS, Subversion и т. Д. Если вы посмотрите на хранилище, то увидите, что файл в хранилище всегда содержит, например, $ Id $. Это никогда не содержит расширения этого. Только при оформлении заказа текст расширяется.

Барон шварц
источник
8
identэто хеш для самого файла, а не хеш из коммита. От git-scm.com/book/en/… : «Тем не менее, этот результат имеет ограниченное использование. Если вы использовали подстановку ключевых слов в CVS или Subversion, вы можете включить метку даты - SHA не так уж полезен, потому что это довольно случайно, и вы не можете сказать, является ли один SHA старше или новее другого ". filterтребует работы, но может получить информацию о коммите в файл (и из него).
Зак Янг
11

Это может быть достигнуто с помощью filterатрибута в gitattributes . Вам нужно будет предоставить smudgeкоманду, которая вставляет идентификатор фиксации, и cleanкоманду, которая удаляет его, чтобы файл, в который он вставлен, не изменялся только из-за идентификатора фиксации.

Таким образом, идентификатор фиксации никогда не сохраняется в двоичном объекте файла; это просто раскрыто в вашей рабочей копии. (На самом деле вставка идентификатора коммита в blob станет бесконечно рекурсивной задачей. ☺) Любой, кто клонирует это дерево, должен настроить атрибуты для себя.

legoscia
источник
7
Невозможная задача, а не рекурсивная задача. Хеш коммита зависит от хэша дерева, который зависит от хэша файла, который зависит от содержимого файла. Вы должны получить самосогласованность. Если только вы не найдете своего рода [обобщенную] фиксированную точку для хэша SHA-1.
Якуб Наребски
1
@Jakub, есть ли какая-то хитрость в git, которая позволит создавать отслеживаемые файлы, которые не изменяют полученный хеш? Может быть, есть какой-то способ переопределить его хэш. Это будет решением :)
Колыпто
@o_O Tync: невозможно. «Измененный файл» означает измененный хеш (файла) - это по замыслу и по определению хеш-функции.
Якуб Наребски
2
Это довольно хорошее решение, но имейте в виду, что оно включает в себя хуки, которые необходимо устанавливать вручную при клонировании репозитория.
Эдвард Фальк
7

Думайте за пределами коробки фиксации!

вставьте это в файл hooks / post-checkout

#!/bin/sh
git describe --all --long > config/git-commit-version.txt

Версия будет доступна везде, где вы ее используете.

Кит Патрик
источник
3

Я не думаю, что вы действительно хотите это сделать, потому что, когда файл в коммите изменяется, хэш коммита также изменяется.

midtiby
источник
1

Позвольте мне исследовать, почему это сложная проблема с использованием внутренностей git. Вы можете получить sha1 текущего коммита

#!/bin/bash
commit=$(git cat-file commit HEAD) #
sha1=($((printf "commit %s\0" $(echo "$commit" | wc -c); echo "$commit") | sha1sum))
echo ${sha1[0]}

По сути, вы запускаете контрольную сумму sha1 для сообщения, возвращаемого пользователем git cat-file commit HEAD. При рассмотрении этого сообщения сразу же возникают две проблемы. Одним из них является дерево sha1, а вторым - время фиксации.

Теперь время фиксации легко можно изменить, изменив сообщение и угадав, сколько времени потребуется для принятия или планирования фиксации в определенное время. Истинная проблема - это дерево sha1, которое вы можете получить git ls-tree $(git write-tree) | git mktree. По сути, вы делаете контрольную сумму sha1 для сообщения от ls-tree, которое представляет собой список всех файлов и их контрольную сумму sha1.

Поэтому ваша контрольная сумма коммитов sha1 зависит от вашей контрольной суммы sha1 дерева, которая напрямую зависит от контрольной суммы файлов sha1, которая завершает круг и зависит от коммитов sha1. Таким образом, у вас есть круговая проблема с методами, доступными для меня.

С менее безопасными контрольными суммами было показано, что можно записать контрольную сумму файла в сам файл с помощью грубой силы; однако я не знаю ни одной работы, которая бы выполнила эту задачу с помощью sha1. Это не невозможно, но почти невозможно с нашим текущим пониманием (но кто знает, может быть, через пару лет это будет тривиально). Тем не менее, перебор еще труднее, так как вы должны записать в файл контрольную сумму (commit) контрольной суммы (tree) контрольной суммы (blob).

Новичок C
источник
Есть ли способ, которым можно было бы зафиксировать файлы, а затем сделать проверку и поместить последний хеш коммита в качестве комментария в начале каждого файла исходного кода? Тогда строить и запускать из этого?
Джон Вутен