Git:如何忘记非常古老的提交

Posted

技术标签:

【中文标题】Git:如何忘记非常古老的提交【英文标题】:Git: How to forget very old commits 【发布时间】:2016-03-10 14:21:02 【问题描述】:

场景:我有一个不时更改的目录结构。我想备份它曾经所处的所有状态。为此,我只需将其设置为 git 存储库并让 cron 作业每天执行一次git commit -m 'croncommit'。这很好用,使我能够查看历史中目录结构的任何状态。

但 git 存储库会增长,即使目录结构没有增长。如果我曾经有一个巨大的文件在那里短暂的时间,它会一直留在存储库中。当然,从 git 的角度来看,这很好且正确,但由于对我来说这只是一个备份工具,因此希望只保留最近的状态,比如上个月。

我正在寻找一种方法来从给定存储库中删除早于特定持续时间(例如一个月)的状态(提交)。我认为这可以通过将所有超过特定年龄的提交合并为一个来完成。

但我找不到此任务的正确命令和语法。

我该怎么做?

【问题讨论】:

【参考方案1】:

使用git log--since 选项来查找历史的新起点,并使用git commit-tree 创建一个新的无父提交,以重用其树状态。之后,将任何子节点重新定位到新根并将您的分支引用移动到新的 HEAD。

#! /usr/bin/env perl

use strict;
use warnings;

my $MAX_AGE = 30;
my $BRANCH  = "master";

# assumes linear history
my($new_start,$rebase) = `git log --reverse --since="$MAX_AGE days ago" --format=%H`;
die "$0: failed to determine new root commit"
  unless defined($new_start) && $? == 0;

chomp $new_start;

my $new_base = `echo Forget old commits | git commit-tree "$new_start^tree"`;
die "$0: failed to orphan $new_start" unless $? == 0;
chomp $new_base;

# don't assume multiple commits more recent than $MAX_AGE
if (defined $rebase) 
  system("git rebase --onto $new_base $new_start HEAD") == 0
    or die "$0: git rebase failed";


system("git branch -f $BRANCH HEAD") == 0
  or die "$0: failed to move $BRANCH";

system("git reflog expire --expire=now --all && git gc --prune=now") == 0
  or die "$0: cleanup failed";

例如:

$ git lol --name-status
* 186d2e5 (HEAD, master) C
| A     new-data
* 66b4a19 B
| D     huge-file
* 5e89273 A
  A     huge-file

$ git lol --since='30 days ago'
* 186d2e5 (HEAD, master) C
* 66b4a19 B

$ ../forget-old 
First, rewinding head to replay your work on top of it...
Applying: C
Counting objects: 5, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (5/5), done.
Total 5 (delta 1), reused 0 (delta 0)

$ git lol --name-status
* b882852 (HEAD, master) C
| A     new-data
* 63bb958 Forget old commits

请注意,git lol 是非标准的,但 highly useful alias 等效于

git log --graph --decorate --pretty=oneline --abbrev-commit

OP 的补充:这是上面 Perl 脚本的 bash 版本:

#!/bin/bash -xe

MAX_AGE=$MAX_AGE:-30
BRANCH=$BRANCH:-master

# assumes linear history

  read new_start
  read rebase
 < <(git log --reverse --since="$MAX_AGE days ago" --format=%H)
[ -n "$new_start" ]  # assertion

read new_base < <(
  echo "Forget old commits" | git commit-tree "$new_start^tree"
)

# don't assume multiple commits more recent than $MAX_AGE
[ -n "$rebase" ] && git rebase --onto $new_base $new_start HEAD

git branch -f "$BRANCH" HEAD

git reflog expire --expire=now --all
git gc --prune=now

git checkout "$BRANCH"  # avoid ending on "no branch"

【讨论】:

这就是我想要的,非常感谢。我冒昧地将您的 Perl 脚本重写为 Bash 脚本(并将其添加到您的答案中以确保完整性)。我希望你不介意:) 还有一个问题:当您测试 $rebase 是否被定义时,您会检查哪种情况?什么时候可以未定义(或在 bash 版本中为空)? @Alfe 不客气。干杯!检查是针对不太可能的情况,即您的分支上的单个单独提交的提交日期(git log --since=... 使用的日期)比 30 天新。 还有一件事:我发现在你的脚本运行后我最终进入了“无分支”,所以git status 等不再有帮助了。因此,我在 bash 版本中添加了 git checkout "$BRANCH"。如果这不是预期的,请纠正我。 糟糕。我错误地认为更新分支会让你脱离分离的头部状态。请用您的更改更新答案。

以上是关于Git:如何忘记非常古老的提交的主要内容,如果未能解决你的问题,请参考以下文章

我忘记在初始提交中添加文件。我如何返回并添加它? [复制]

git没有提交的代码如何迁移到新建分支

SVN 我刚刚用SVN覆盖更新了我本地的代码,悲催的是,我有一些新写的代码忘记提交了,直接被覆盖了!!!!

在 git 中提交消息前缀

sh 如何让Git“忘记”一个被跟踪但现在位于.gitignore的文件?

确定性追溯 .gitignore(如何让 Git 完全/追溯地忘记现在在 .gitignore 中的文件)