↓Перейти к содержанию
  1. Git: руководства и команды/

Дата файлов после git clone: как сохранить mtime коммита

·6 минут·

Проблема: почему git clone устанавливает текущее время #

Когда вы клонируете репозиторий командой git clone, все файлы получают текущую системную дату в качестве времени модификации (mtime). Это может показаться странным — ведь в Git хранится информация о том, когда файл был изменён в последний раз. Почему Git её не использует?

На самом деле это сделано намеренно, и на то есть вполне логичные причины:

1. Воспроизводимые сборки (Reproducible Builds)

Если бы Git устанавливал дату файлов на дату коммита, результат клонирования зависел бы от того, какой коммит вы клонируете. Это усложняло бы создание идентичных сборок разными людьми в разное время.

2. Избежание путаницы с make-системами

Утилита make и похожие системы сборки полагаются на mtime для определения, какие файлы нужно пересобрать. Если вы клонируете старый коммит, файлы с исторической датой могут вызвать неправильное поведение системы сборки.

3. Философия Git

Git хранит содержимое файлов и метаинформацию о коммитах (автор, дата коммита, сообщение), но не хранит и не отслеживает файловые метаданные вроде mtime. Это сделано намеренно, чтобы сосредоточиться на самом важном — версионировании кода.

Когда это реально важно #

Хотя это поведение обычно приемлемо, есть сценарии, где восстановление исходных дат файлов критично:

Make-based проекты

Если у вас есть Makefile с правилами типа target: dependency, и вы клонируете старый коммит, make может не пересобрать нужные файлы.

Статические сайты (Hugo, Jekyll)

Генератор статических сайтов может использовать дату файла как дату публикации или для сортировки контента. После клона все статьи будут иметь “сегодняшнюю” дату.

Системы деплоя

Deployment-скрипты часто сравнивают даты файлов: “был ли файл изменён после последнего деплоя?” Если все файлы имеют текущую дату, такая система не будет работать корректно.

rsync-based развёртывание

Утилита rsync с флагом --checksum или сравнением дат будет думать, что все файлы изменились, даже если содержимое осталось прежним.

Решение 1: git-restore-mtime #

Самый популярный и надёжный способ — использовать специализированный инструмент git-restore-mtime. Это Python-скрипт, который проходит по всем файлам в репозитории и устанавливает их mtime на дату последнего коммита, который изменил этот файл.

Установка:

# Через pip (универсально)
pip install git-restore-mtime

# Или через apt (на Debian/Ubuntu)
sudo apt install git-restore-mtime

Использование:

# Перейти в директорию репозитория
cd ~/my-project

# Запустить утилиту
git restore-mtime

# Вывод может выглядеть так:
# Updating mtime for 42 files...
# Done!

Как это работает:

Утилита запускает команду git log для каждого файла, вычисляет дату последнего коммита, и затем использует системный вызов touch для установки mtime:

# Примерно то же самое, что делает git-restore-mtime:
for file in $(git ls-tree -r --name-only HEAD); do
    date=$(git log -1 --format="%ai" -- "$file")
    touch -d "$date" "$file"
done

Этот подход гарантирует, что каждый файл будет иметь правильную дату, соответствующую его истории в Git.

Решение 2: post-checkout хук #

Если вы хотите автоматизировать процесс и не запоминать, чтобы запустить git restore-mtime после каждого клона, можно использовать Git-хук.

Git-хуки — это скрипты, которые выполняются автоматически при определённых событиях (смотрите нашу статью про git-hooks). Хук post-checkout запускается после любого изменения ветки или клонирования.

Как установить хук:

# Создать директорию для хуков (если её нет)
mkdir -p .git/hooks

# Создать файл хука
cat > .git/hooks/post-checkout << 'EOF'
#!/bin/bash

# Проверяем, установлена ли git-restore-mtime
if command -v git-restore-mtime &> /dev/null; then
    echo "Восстанавливаю даты файлов..."
    git restore-mtime
    echo "Готово!"
else
    echo "Подсказка: установите git-restore-mtime для автоматического восстановления дат"
fi
EOF

# Сделать хук исполняемым
chmod +x .git/hooks/post-checkout

Теперь после каждого git clone, git checkout или git pull хук будет автоматически восстанавливать даты файлов.

Более простой вариант (без проверки):

Если вы уверены, что git-restore-mtime установлена везде, где работает ваш код:

cat > .git/hooks/post-checkout << 'EOF'
#!/bin/bash
git restore-mtime
EOF

chmod +x .git/hooks/post-checkout

Решение 3: ручной подход через git log #

Если вам нужно восстановить дату для конкретного файла, или вы не хотите устанавливать дополнительные утилиты, можно использовать стандартные Git-команды.

Узнать дату последнего коммита для файла:

git log -1 --format="%ai" -- path/to/file.txt

Результат: 2024-12-15 14:32:18 +0300

Восстановить дату для одного файла:

touch -d "$(git log -1 --format='%ai' -- path/to/file.txt)" path/to/file.txt

Восстановить дату для всех файлов (однострочник):

git ls-tree -r --name-only HEAD | while read file; do touch -d "$(git log -1 --format='%ai' -- "$file")" "$file"; done

Это медленнее, чем git-restore-mtime, но не требует установки дополнительного ПО.

Решение 4: GitHub Actions / CI #

Если вы развёртываете код через CI/CD (например, GitHub Actions), вы можете восстановить даты файлов как часть вашего workflow.

Пример workflow для GitHub Actions:

name: Deploy

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0  # Получить полную историю

      - name: Восстановить даты файлов
        run: |
          pip install git-restore-mtime
          git restore-mtime

      - name: Собрать сайт
        run: hugo

      - name: Развернуть
        run: ./deploy.sh

Флаг fetch-depth: 0 важен — по умолчанию actions/checkout получает только последний коммит, чего недостаточно для работы git restore-mtime.

Альтернативный подход (встроенный скрипт):

Если вы не хотите зависеть от git-restore-mtime, можно использовать встроенную команду:

- name: Восстановить даты файлов
  run: |
    git ls-tree -r --name-only HEAD | while read file; do
      touch -d "$(git log -1 --format='%ai' -- "$file")" "$file"
    done

Проблемы и ограничения #

Производительность

На больших репозиториях (с десятками тысяч файлов) git restore-mtime может работать медленно, так как для каждого файла нужно запустить отдельную команду git log. На GitHub Actions это может добавить несколько минут к времени выполнения workflow.

Shallow clones

Если вы используете git clone --depth=1 (shallow clone для экономии времени), историю коммитов будет недостаточно. git-restore-mtime может работать неправильно или вообще не иметь нужной информации.

Новые файлы

Если файл был добавлен и никогда не менялся после этого, git-restore-mtime установит дату на дату добавления файла. Это обычно правильно, но иногда может быть неожиданным.

Файлы, не отслеживаемые Git

Файлы, которых нет в Git (например, сгенерированные файлы сборки), не будут обработаны. Это правильное поведение.

FAQ: часто задаваемые вопросы #

Как узнать дату последнего коммита для конкретного файла? #

# Подробная информация
git log -1 -- path/to/file.txt

# Только дата
git log -1 --format="%ai" -- path/to/file.txt

Влияет ли это на git status? #

Нет. git status основан на хешах содержимого файлов (SHA-1), а не на mtime. Поэтому восстановление дат не повлияет на то, как Git видит изменения.

Как сделать так, чтобы Hugo использовал дату коммита? #

Hugo может использовать дату из frontmatter или из файловой системы (mtime). Если вы хотите, чтобы Hugo всегда использовал дату коммита, у вас есть два варианта:

  1. Явно указывать дату в frontmatter (самый надёжный способ):
---
title: "Мой пост"
date: 2024-12-15
---
  1. Полагаться на mtime (нужно восстановить даты через git restore-mtime):
---
title: "Мой пост"
# Hugo автоматически использует файловую дату, если не указано иное
---

Во втором случае Hugo будет использовать mtime файла как дату публикации.

Работает ли это на Windows? #

Да, git-restore-mtime работает на Windows. Команда touch -d — это инструмент Unix, но git-restore-mtime использует кроссплатформенные Python-библиотеки для установки времени файлов.

Почему git-restore-mtime может быть медленным? #

Потому что он запускает git log для каждого файла в репозитории. На большом репозитории это означает тысячи вызовов отдельных процессов. Есть попытки оптимизировать это (например, через git log --follow или батч-обработку), но это остаётся узким местом.

Если производительность критична, рассмотрите:

  • Использование shallow clones (если хронология не важна)
  • Кэширование информации о датах в CI
  • Указание дат в frontmatter вручную

Связанные статьи #

Заключение #

Хотя Git по умолчанию не сохраняет даты файлов, восстановить их очень просто. Выберите способ в зависимости от вашей ситуации:

  • Простота: используйте git-restore-mtime после клонирования
  • Автоматизм: установите post-checkout хук
  • В CI/CD: добавьте шаг в ваш workflow
  • Минимальные зависимости: используйте однострочник с git log и touch

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

По теме #