V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
git
Pro Git
Atlassian Git Tutorial
Pro Git 简体中文翻译
GitX
Yancey
V2EX  ›  git

[V 站有 git 大神吗? ]删除仓库的某个时间点之前的历史记录,减少.git 目录大小。 救命啊啊。

  •  2
     
  •   Yancey · 2016-08-08 09:48:05 +08:00 · 12264 次点击
    这是一个创建于 2815 天前的主题,其中的信息可能已经有所发展或是发生改变。
    安卓仓库。有很多 commits 和分支。想删除某个时间点之前的所有 commits 减少.git 目录大小,找了很多办法。感觉只有 grafts+ filter-brnach 靠谱。不过还是遇到很多问题、
    1.要截断的 commits 之前有很多分支。。用 filter-branch 不会删除这些分支,怎么解决?
    2.这个仓库是公开仓库。很多人已经 clone 在本地。所以截断操作应该是在服务器还是本地?我试过在本地更改,但是推不到服务器上。如果在服务器操作,怎么将本地的改动变为最小?

    示意图如下:
    56 条回复    2016-08-09 11:14:58 +08:00
    jason0916
        1
    jason0916  
       2016-08-08 09:55:00 +08:00   ❤️ 1
    新开一个仓库?(手动滑稽)坐等楼下大神给出答案
    Yancey
        2
    Yancey  
    OP
       2016-08-08 09:56:25 +08:00
    @jason0916 能新建...也不会这么麻烦了
    vitovan
        3
    vitovan  
       2016-08-08 09:56:31 +08:00
    rebase ?
    vitovan
        4
    vitovan  
       2016-08-08 09:57:07 +08:00
    https://git-scm.com/docs/git-rebase

    git-rebase - Reapply commits on top of another base tip
    Yancey
        5
    Yancey  
    OP
       2016-08-08 09:57:38 +08:00
    @vitovan rebase 不行。。 rebase 是当前的回到某个时间点或 commits 。 现在是要把某个时间点之前的丢掉
    Yancey
        6
    Yancey  
    OP
       2016-08-08 10:00:04 +08:00
    @vitovan sorry 看错,以为你说的 reset 。 rebase 是可以。但是一般服务器的仓库是一个裸仓库(没有工作目录), git rebase , git branch 命令都没法使用。。如果在本地操作,强推到服务端。所有分支都会混乱、、
    lijianying10
        7
    lijianying10  
       2016-08-08 10:02:59 +08:00
    删掉本地仓库然后
    git clone --depth=6 https://xxxxxxx.git
    看看这种行不行?
    mengzhuo
        8
    mengzhuo  
       2016-08-08 10:07:08 +08:00 via iPhone
    本地 gc 一次 减少空间
    而且删 commit 很不可取啊
    relaxgo
        9
    relaxgo  
       2016-08-08 10:12:29 +08:00 via Android
    试试这个方案 [git 替换]( https://git-scm.com/book/zh/v2/Git-工具-替换)
    Yancey
        10
    Yancey  
    OP
       2016-08-08 10:13:08 +08:00
    @lijianying10 这种治标不治本啊。。
    vitovan
        11
    vitovan  
       2016-08-08 10:13:59 +08:00
    r#6 @Yancey 「如果在本地操作,强推到服务端。所有分支都会混乱」,为啥(扣鼻状)?
    我是 Git 小白,求教...
    shenqi
        12
    shenqi  
       2016-08-08 10:14:52 +08:00
    既然都打算这样了,就直接那个新的仓库,旧的仓库随时能查看就行了。
    Yancey
        13
    Yancey  
    OP
       2016-08-08 10:15:04 +08:00
    @mengzhuo commits 是确定不会再用的, 所以要删掉。。 gc 一次 200M 顶多到 160M 。
    forcecharlie
        14
    forcecharlie  
       2016-08-08 10:18:41 +08:00
    git clone --depth 会从最新的 commit 迭代回去,并不是完整的 repository ,一些额外的操作需要还原成完整的仓库, 在服务器上进行 git gc ---prune=now 可以一定程度的减小仓库体积. 一个思路是,扫描出 仓库中大文件提交时的 父 commit, 然后将分支强制设置为此 commit,即修改 .git/refs/heads/xxxx 的 commitid ,工作目录删除此大文件,然后创建提交,运行 git gc, 强制推送到服务器,服务器运行 git gc . 这样仓库的体积就可以减小了,这个实际上和 git rebase 类似,但不会丢失当前的 commit. 对于你的需求, git commit 的创建由父 commit 根 tree,提交者 提交信息等通过 sha1 创建,是链式的,牵一发而动全身. 所以你的不太好实现.
    Yancey
        15
    Yancey  
    OP
       2016-08-08 10:19:43 +08:00
    @vitovan 因为丢掉历史,会让从截断的地方到现在最新的 commit 的 ID 都改变,也就是相当于把所有的留下的 commits 都重新提交了一遍。 这个时候服务端和本地 commits 没有一个是能对应上的。强行 push 我们期望的效果是 服务端的 commits 和分支记录 都完全被更改过的本地 commits 替换掉。。但是 git 并不会这样。
    Yancey
        16
    Yancey  
    OP
       2016-08-08 10:22:22 +08:00
    @forcecharlie 对啊。截断的效果就是从截断到现在最新的 commits 所有的 SHA
    Yancey
        17
    Yancey  
    OP
       2016-08-08 10:24:26 +08:00
    @Yancey 对啊。截断导致结果就是从截断到现在最新的 commits 所有的 SHA1 值都会改变。。因为是公开的项目。很多人已经克隆了。。对本地的影响很大。
    @forcecharlie
    kukat
        18
    kukat  
       2016-08-08 10:27:22 +08:00
    ~ du -sh .git
    758M .git
    Yancey
        20
    Yancey  
    OP
       2016-08-08 10:29:04 +08:00
    @kukat 你这还不减? 我们一个工程就这么大。总共几十个工程。。。源码编译的时候硬盘受不了
    zzn
        21
    zzn  
       2016-08-08 10:30:15 +08:00
    公开项目不应该修改已提交的 commit ,除非让全部人重新 clone

    其实很好奇,究竟.git 是有多大? 怎么会有这种需求。。。
    Yancey
        22
    Yancey  
    OP
       2016-08-08 10:32:23 +08:00
    @bjzhou1990 我就是参考这个。如图的 git 结构,遇到问题是,因为截断点之前还有 commit 。会导致截断后除了 之后想要的 commit 线外。 branch1 branch2 不会被操作。保持原样。 分支混乱。。
    Yancey
        23
    Yancey  
    OP
       2016-08-08 10:35:06 +08:00
    @zzn 背景是这样。 最开始一个主工程。然后 clone 了好几份工程作为不同的库(这个点我们暂定位 date 点)。后来这些库往不同业务发展。所以想将 date 点之前的所有 commit 删除。毕竟后来分成不同库了, date 之前和这些库完全没有啥关系。。
    vitovan
        24
    vitovan  
       2016-08-08 10:35:52 +08:00
    r#15 @Yancey 嗯,刚才想说 --force 来着,如果不能让别人都接受 rebase ,好像确实有些难办:
    http://stackoverflow.com/questions/8939977/git-push-rejected-after-feature-branch-rebase

    `rebasing feature branches on master and force-pushing them back to remote repository is OK as long as you're the only one who works on that branch.`
    jianyunet
        25
    jianyunet  
       2016-08-08 10:36:55 +08:00   ❤️ 1
    既然都已经是公开项目了,在服务器端删掉也必须强推到各客户端啊。最简单的办法还是开一个新仓库做个 rebase
    mengzhuo
        26
    mengzhuo  
       2016-08-08 10:40:48 +08:00   ❤️ 1
    @Yancey 公开项目只能另开,或者删了再来了。要不然其他人会拉不到原始 commit 各种报错的
    bjzhou1990
        27
    bjzhou1990  
       2016-08-08 10:45:27 +08:00   ❤️ 1
    @Yancey 用 git checkout --orphan 创建新分支,然后在新分支上开发,丢弃原始分支?
    Yancey
        28
    Yancey  
    OP
       2016-08-08 10:59:12 +08:00
    @bjzhou1990 恩。是个好办法。
    我测试还是遇到问题
    1. 在服务端。我自己测试的时候是通过 git init --bare 来建仓库的,所以服务端 git checkout --orphan 命令没法使用。。
    2. 服务端进行操作后,所有的本地仓库都要克隆一遍。。


    我考虑的办法。
    假设我们要截断的点 hash 值为 1234abc
    在服务端:
    echo 1234abc > info/grafts
    git filter-branch -- --all
    以及后续删除 grafts 和 gc 操作

    写个脚本让所有本地克隆的都执行。内容大概是
    echo 1234abc > .git/info/grafts
    git filter-branch -- --all
    删除 grafts
    git fetch --all

    目的是让本地和服务端改变一模一样。这样本地的分支。没有 push 的 commits 都可以保留


    还是遇到问题

    在服务端;
    执行 git filter-branch -- --all 之前要将所有的 1234abc 之前的分支都删除。

    在本地
    git filter-branch -- --all 执行的时候 origin/xx 这种分支也会被操作。总之结果很混乱。不是想要的效果


    看来真的是无解啊。。
    jason19659
        29
    jason19659  
       2016-08-08 10:59:25 +08:00
    删除之后别人的项目跟你的肯定不是一个项目了。
    subpo
        30
    subpo  
       2016-08-08 11:08:53 +08:00
    迷之需求
    bjzhou1990
        31
    bjzhou1990  
       2016-08-08 11:21:33 +08:00
    @Yancey 所有 git 操作都应该是本地修改然后提交服务器,不要直接在服务器端修改啊。另外 git filter-branch 里的--all 意思是修改所有分支和 tag ,可以单独指定分支的吧?建议看看 git filter-branch --help ,文档很详细
    kukat
        32
    kukat  
       2016-08-08 11:35:49 +08:00
    @Yancey 编译跟.git 有什么关系?
    SpicyCat
        33
    SpicyCat  
       2016-08-08 11:45:20 +08:00
    我觉得在做这个操作之前,先要反思下你们对 git 的使用。单纯的 git 提交历史长不不会显著增加 git repo 的大小,一般发现 git repo 突然增大,都是误添加了大文件。
    因为 git history 太长觉得影响效率是误解。有些大项目,上万的 commit ,太稀松平常了。

    具体到你的需求,你想删掉某个时间点以前的 commit ,是肯定能做到的, git rebase -i 就可以。但是那样做以后,我估计你的项目就出错了,因为必然有些文件是在某个 commit 被加进来的,然后你把那个 commit 删除了,那以后某个 commit 要修改那个文件,会出什么现象我也不知道。总之这样做隐患很大。
    更不用说你想改的是服务器,会影响所有人,真是要慎重。

    另外推荐一款清理 git repo 的工具 https://rtyley.github.io/bfg-repo-cleaner/
    不过我一般是用它删除特定文件,可能不太符合你的需求。
    Yancey
        34
    Yancey  
    OP
       2016-08-08 12:05:03 +08:00
    @SpicyCat 因为是 android 项目。我查了下大文件, 基本是图片或 jar 包。这些图片或 jar 包经过迭代现在很可能已经不再使用了,但是还在仓库历史里面。所以.git folder 比较大。

    看来这问题是无解了。
    Yancey
        35
    Yancey  
    OP
       2016-08-08 12:05:51 +08:00
    @kukat 编译没关系。。这不是编译服务器磁盘小么。装不下所有带.git 文件夹的工程了
    9hills
        36
    9hills  
       2016-08-08 12:07:00 +08:00
    rebase 可以只合并某个范围内的 commit 。但是修改后需要约定下所有人,同时重新拉取
    mrcode
        37
    mrcode  
       2016-08-08 12:20:07 +08:00
    可以试试 revert 他会生成一个新的提交, 来抵消掉你指定的提交的更改. 然后 git 垃圾回收就回收掉没用的那部分了
    SpicyCat
        38
    SpicyCat  
       2016-08-08 12:22:43 +08:00
    @Yancey 那么解决问题的方法就是确定哪些图片文件或者 jar 不需要了,利用 bfg(就是我上个回复提到的清理工具)清理一下。 bfg 是把目标文件彻底从 git repo 里删除。
    SpicyCat
        39
    SpicyCat  
       2016-08-08 12:23:23 +08:00
    当然,最好还是新建一个 repo ,保留原有 repo 。
    fy
        40
    fy  
       2016-08-08 13:38:57 +08:00
    翻出一篇笔记:

    git clone [email protected]:jfinal/jfinal.git
    git filter-branch --tree-filter 'rm -f WebRoot/WEB-INF/lib/*.jar' --tag-name-filter cat -- --all
    git push origin --tags --force
    git push origin --all --force
    skydiver
        41
    skydiver  
       2016-08-08 14:13:37 +08:00   ❤️ 1
    如果误添加了大文件导致,可以用楼上 @fy 这种方法

    如果要彻底修改历史, rebase -i 就行了,保留第一个,保留现在的,中间的都 fixup
    AnyOfYou
        42
    AnyOfYou  
       2016-08-08 14:23:06 +08:00
    官方的用来管理 Android 源码下众多 git 仓库的 repo 工具, init 的时候可以指定 branch 。
    这个 repo 实际就是一堆 python 脚本。 init 后到会把 subcmds 下载到本地。你可以看其 sync 的实现并修改。
    AnyOfYou
        43
    AnyOfYou  
       2016-08-08 14:25:13 +08:00
    另外, init 的时候可以直接指定 --depth 。
    Yancey
        44
    Yancey  
    OP
       2016-08-08 14:28:23 +08:00
    @skydiver 几百个 branch 几千个 commits 怎么 rebase -i 不行吧
    SourceMan
        45
    SourceMan  
       2016-08-08 15:21:30 +08:00
    楼主没说规模,我这边几个 G 的算什么程度?
    Yancey
        46
    Yancey  
    OP
       2016-08-08 15:35:12 +08:00
    @SourceMan 我这边磁盘小。。编译的时候拉十几个工程也是几个 G
    skydiver
        47
    skydiver  
       2016-08-08 15:35:26 +08:00
    @Yancey 为什么不行?麻烦一点儿而已
    402124773
        48
    402124773  
       2016-08-08 17:05:38 +08:00
    7.2G .git
    也没见过提出这个需求。
    哈哈
    wweir
        49
    wweir  
       2016-08-08 18:50:42 +08:00
    不知道是否有更直接的操作方式
    只知道 squash 命令可以压缩大量 commit 为一个

    结合楼主的需求,也许结合 rebase 可以实现相应功能。

    我们这边的做法是,在下一次大重构之前,拷出所有有用的代码为一个新的 repo 。老代码就自己慢慢滚下去,直到成为一个历史档案
    jimages
        50
    jimages  
       2016-08-08 22:56:46 +08:00
    这笔记本太熟悉了。渡边本。
    msg7086
        51
    msg7086  
       2016-08-08 23:10:17 +08:00
    修改历史一定会导致 commit hash 变动,亲你不会连这点知识都没有吧……
    sodatea
        52
    sodatea  
       2016-08-09 00:47:15 +08:00
    @Yancey git clone --single-branch 的体积也仍然无法接受么?
    tinyproxy
        53
    tinyproxy  
       2016-08-09 08:44:22 +08:00 via iPhone
    @Yancey 一个建议,资源文件等大体积的不要入库,传到某云存储去,通过一个 configure 脚本下载回来就好了
    yuankui
        54
    yuankui  
       2016-08-09 08:51:46 +08:00
    @lijianying10 这个方法不错~
    mrsatangel
        55
    mrsatangel  
       2016-08-09 09:40:21 +08:00   ❤️ 1
    `root -> A -> B -> C -> newRoot -> D `
    也就是想要将 newRoot 之前的 commit 对象全部删除。

    在`.git/info/`下建立`grafts`文件,在其中输入`newRoot`对象的 SHA1 值,保存。此时使用`git log`命令应该只能看到`newRoot`的 commit 记录。
    之后使用`git filter-branch`使这个新建的 root 生效.

    注意,这个 chop 操作可能会导致出现一个新的 detached 的 branch ,为当前的这个 branch 新建一个临时的 branch:`git branch tmp`,然后将 tmp branch 推送到远程的 repo 里想要更新的 branch (如 master ):`git push <remoteRepoName> tmp:<remoteBranchName>`,此时远程 repo 里面的**&lt;remoteBranchName&gt;**已经将**newRoot**之前的所有 commit 对象删除。
    Yancey
        56
    Yancey  
    OP
       2016-08-09 11:14:58 +08:00
    @mrsatangel 感谢。你的方案可行。

    我原本以为本地截断后,不能强行推送到远端。原来是可以的。

    不过你提到
    “为当前的这个 branch 新建一个临时的 branch:`git branch tmp`,然后将 tmp branch 推送到远程的 repo 里想要更新的 branch (如 master ):`git push <remoteRepoName> tmp:<remoteBranchName>`,此时远程 repo 里面的**&lt;remoteBranchName&gt;**已经将**newRoot**之前的所有 commit 对象删除。”

    感觉这个 tem branch 是多余的吧。直接强推截断的 master 就可以的。


    另外对你的答案补充下;
    假如你有如下的分支结构图



    现在想从"57dd13f even"这个点截断。保留这个点上面的所有分支和 commit
    首先需要 git push origin :test 将 test 分支删除。也就是说截断点以下的所有都删除。
    再者 将截断点以上的所有分支都在本地有追踪分支 git checkout -b new origin/new

    之后就执行

    “在`.git/info/`下建立`grafts`文件,在其中输入`newRoot`对象的 SHA1 值,保存。此时使用`git log`命令应该只能看到`newRoot`的 commit 记录。
    之后使用`git filter-branch`使这个新建的 root 生效. ”


    最后执行
    将本地所有分支都强推到远端一遍。
    本例就是 git push --force origin master:master 和 git push --force origin new:new


    哦对了,还要删除 original 文件夹保存的临时记录
    rm -r .git/refs/original/


    最后效果:





    其实这样的好处还有一个就是,所有 clone 这个仓库的本地仓库都可以执行一个相同的脚本。这样就不用大家都重新 clone 了,本地的 branch 和没有推送的 commit 也可以保留。


    不知道理解对不对。可以再讨论。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3642 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 00:48 · PVG 08:48 · LAX 17:48 · JFK 20:48
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.