Как автоматизировать Git History Squash по дате? - PullRequest
1 голос
/ 02 июля 2019

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

Это приводит к большому количеству коммитов, например, 50 в день. Я хотел бы написать сценарий bash cron, чтобы автоматизировать сжатие истории, имея один коммит в день, без комментариев, но с сохранением даты.

Я попытался git-rebase -i SHA~count, но я не могу понять, как автоматизировать процесс, то есть выбрать первый коммит и сдавить остальные коммиты.

Есть предложения?

У меня нет проблем с написанием bash, который находит первый SHA даты и подсчитывает коммиты для слияния, некоторый цикл по этому может помочь:

git log --reverse|grep -E -A3 ^commit| \
  grep -E -v 'Merge|Author:|--|^$'|paste - -| \
  perl -pe 's/commit (\w+)\s+Date:\s+\w+\s+(\w+)\s+(\d+).+/\2_\3 \1/'

Ответы [ 3 ]

2 голосов
/ 02 июля 2019

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

#!/bin/bash
FIRST_COMMIT_HASH_TODAY="$(git log --since="1 days ago" --pretty=format:%H | tail -n 1)"
git reset --soft ${FIRST_COMMIT_HASH_TODAY}^
git commit -m "Squashed changes for $(date +%F)"

Т.е..

  1. Перечислите хеши коммитов для всех коммитов, которые произошли в течение последнего дня, и извлеките первый из этих хэшей коммитов.
    (подразумевается, что в текущей форме по крайней мере один коммит каждый день)
  2. Переместите указатель репо HEAD на фиксацию до $ FIRST_COMMIT_HASH_OF_THE_DAY , но оставьте рабочее дерево и индекс без изменений.
  3. Зафиксируйте раздавленные изменения.

A предостережение хотя ... Обратите внимание, что теперь вы фактически переписываете историю. Вы больше не можете просто сделать git pull, чтобы синхронизировать изменения, потому что, если клиентское репо все еще имеет исходную историю коммитов, в то время как сервер имеет переписанную историю, вы получите что-то вроде:

Your branch and 'origin/master' have diverged,                                                                                                                                                                                                                                  
and have 50 and 1 different commit(s) each, respectively.

Если вы хотите обработать всю историю, одним из подходов будет использование некоторого варианта git filter-branch . Ниже приведен один примерный подход, но у этого подхода есть много недостатков, поэтому вы можете его немного улучшить.

Слабые стороны / характеристики:

  • Просто игнорирует часовые пояса из временных меток git raw. (странное поведение, если коммиты сделаны в разных часовых поясах)
  • Идентифицирует последний коммит в ветви, которую вы хотите обработать, по хешу корневого дерева. (странное поведение, если несколько коммитов имеют одно и то же корневое дерево (например, возвратный коммит, возвращающий родительский коммит))
  • Предполагает линейную ветвь истории. (странное поведение, если в ветке есть коммиты слияния)
  • Специально не создает один коммит в день. Вместо этого для каждого коммита он проверяет, прошло ли как минимум 24 часа с момента предыдущего коммита. Если его нет, он просто пропускает этот коммит.
  • Всегда сохраняет первый и последний коммит, независимо от того, близки ли они по времени к последующим / предыдущим коммитам.
  • Работает на основе GIT_COMMITER_DATE, а не GIT_AUTHOR_DATE.
  • Не очень хорошо протестирован. Поэтому обязательно сделайте резервную копию исходного репо, если вы собираетесь попробовать это запустить.

Пример команды:

LATEST_TREE=$(git rev-parse HEAD^{tree}) git filter-branch --commit-filter '
  # $3 = parent commit hash (if commit has at least one parent)
  if [ -z "$3" ] 
  then
    # First commit. Keep it.
    git commit-tree "$@"
  elif [ "$1" == "$LATEST_TREE" ]
  then
    # Latest commit. Keep it.
    git commit-tree "$@"
  else
    PREVIOUS_COMMIT_COMMITTER_DATE="$(git log -1 --date=raw --pretty=format:%cd $3)"
    PREVIOUS_COMMIT_COMMITTER_DATE_NO_TIMEZONE="$(echo $PREVIOUS_COMMIT_COMMITTER_DATE | egrep -o "[0-9]{5,10}")"
    GIT_COMMITTER_DATE_NO_TIMEZONE="$(echo $GIT_COMMITTER_DATE | egrep -o "[0-9]{5,10}")"
    SECONDS_PER_DAY="86400"

    if [ $(expr $GIT_COMMITTER_DATE_NO_TIMEZONE - $PREVIOUS_COMMIT_COMMITTER_DATE_NO_TIMEZONE) -gt $SECONDS_PER_DAY ]
    then
      # 24 hours elapsed since previous commit. Keep this commit.
      git commit-tree "$@"
    else
      skip_commit "$@"
    fi
  fi' HEAD

Если у вас была команда для извлечения хэшей коммитов коммитов, которые вы хотите сохранить, возможно, вы могли бы получить хэш корневого дерева для всех этих коммитов и сохранить их в отдельном файле. Затем вы можете изменить условие коммит-фильтра, чтобы проверить, «присутствует ли текущий хеш корневого дерева в файле желаемых хешей корневого дерева?» вместо «прошло 24 часа с момента предыдущего коммита?». (Это усилило бы проблему «идентификации коммитов по хешу корневого дерева», о которой я упоминал выше, поскольку она будет применяться ко всем коммитам, а не только к последнему коммиту)

0 голосов
/ 04 июля 2019

Я делюсь результатами на основе предположений Альдерата: я использовал git filter-branch, чтобы проанализировать историю и сохранить только последний коммит дня. Первый цикл в git log запишет временные метки фиксации, которые должны быть сохранены (последние в день) во временном файле; затем с git filter-branch я сохраняю только коммит с временной меткой, присутствующей в файле.

#!/bin/bash

# extracts the timestamps of the commits to keep (the last of the day)
export TOKEEP=`mktemp`
DATE=
for time in `git log --date=raw --pretty=format:%cd|cut -d\  -f1` ; do
   CDATE=`date -d @$time +%Y%m%d`
   if [ "$DATE" != "$CDATE" ] ; then
       echo @$time >> $TOKEEP
       DATE=$CDATE
   fi
done

# scan the repository keeping only selected commits
git filter-branch -f --commit-filter '
    if grep -q ${GIT_COMMITTER_DATE% *} $TOKEEP ; then
        git commit-tree "$@"
    else
        skip_commit "$@"
    fi' HEAD
rm -f $TOKEEP
0 голосов
/ 02 июля 2019

Если у вас есть количество коммитов, к которым вы хотите вернуться, вы можете просто использовать git reset --soft и затем сделать новый коммит, например

COMMIT_COUNT=$(git log --pretty=oneline --since="1 days" | wc -l) 
git reset --soft HEAD~$COMMIT_COUNT
git commit -m "Today's work" 
...