如何按日期自动化 git 历史压缩?

How to automate git history squash by date?

我有一个 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+).+/_ /'

如果您有想要返回的提交次数,那么您可以只使用 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" 

据我了解,您打算按照以下方式做某事:

#!/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 之前的提交,但保留 work-tree 和指数不变。
  3. 提交压缩的更改。

一个警告 但是...请注意,现在您实际上正在重写历史。你不能再只做 git pull 同步更改,因为如果客户端 repo 仍然具有原始提交历史,而服务器具有重写的历史, 你会得到类似的东西:

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

<编辑>

如果你想处理整个历史,一种方法是使用 git filter-branch 的一些变体。我在下面放了一个示例方法,但是这种方法有很多缺点,所以你可能想改进一下。

Weaknesses/characteristics:

  • 简单地忽略来自 git 原始时间戳的时区。 (如果在不同时区进行提交会出现奇怪的行为)
  • 通过其根树哈希识别您要处理的分支上的最新提交。 (如果多个提交具有相同的根树,则会出现奇怪的行为(例如,还原提交还原其父提交))
  • 假设一个线性分支历史。 (如果分支中有合并提交会出现奇怪的行为)
  • 没有专门每天创建一个提交。相反,对于每次提交,它会检查自上次提交以来是否至少过去了 24 小时。如果还没有,它就跳过那个提交。
  • 始终保留第一个和最后一个提交,无论它们是否及时接近 subsequent/previous 个提交。
  • 作品基于 GIT_COMMITER_DATEs 而不是 GIT_AUTHOR_DATEs。
  • 未经过充分测试。因此,如果您要尝试 运行 这个,请务必备份原始存储库。

示例命令:

LATEST_TREE=$(git rev-parse HEAD^{tree}) git filter-branch --commit-filter '
  #  = parent commit hash (if commit has at least one parent)
  if [ -z "" ] 
  then
    # First commit. Keep it.
    git commit-tree "$@"
  elif [ "" == "$LATEST_TREE" ]
  then
    # Latest commit. Keep it.
    git commit-tree "$@"
  else
    PREVIOUS_COMMIT_COMMITTER_DATE="$(git log -1 --date=raw --pretty=format:%cd )"
    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

如果你有一个命令来提取你想要保留的提交的提交哈希,也许你可以获得所有这些提交的根树哈希,并将它们存储到一个单独的文件中。然后您可以更改 commit-filter 条件以检查 "is the current root tree hash present in the file of desired root tree hashes?" 而不是 "has 24 hours elapsed since the previous commit?"。 (这会放大我上面提到的 "identify commits by root tree hash" 问题,因为它适用于所有提交,而不仅仅是最新的提交)

我根据 Alderath 的建议分享结果:我使用 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