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

问个 Git 基操:怎么样复制一个文件,能保持历史记录?

  •  
  •   xiangyuecn · 2025 年 5 月 23 日 · 9084 次点击
    这是一个创建于 239 天前的主题,其中的信息可能已经有所发展或是发生改变。
    git cp:没这号命令😂
    git mv:这是改名
    

    比如已有 a.txt ,我现在要个 b.txt 。 如何复制出 b.txt 这个文件,并且复制前的历史和 a.txt 保持一致?

    还是说 和空文件夹 一样,git 不在乎你死活?

    操作尽量简单,要是涉及的非基础知识点太多,还是算了,这历史不要也罢😂

    第 1 条附言  ·  2025 年 5 月 23 日
    好了,测试了一下,直接复制文件 b.txt ,不要修改内容,直接先提交 b.txt 个新文件,然后再去修改后提交

    再使用 git log --follow b.txt 就能看到所有历史记录了。TortoiseSVN 中使用“遍历方法”下拉选项“跟踪重命名”,也可以看到所有记录。

    - 如果复制了马上修改文件,再提交,就关联不上历史记录
    - 只要新文件和当前版本中已有的文件相同,就可以稳定追踪历史记录
    - 已有文件修改后,再复制出新文件,只要提交时两个文件内容相同,一起提交后,同样可以稳定追踪历史记录

    -----------

    结论:不需要任何操作,直接复制文件,提交,完事,注意不要改复制的文件就行

    参考:@dcsuibian #45 @B1ankCat #73 @justplaymore #75 @mayli #70
    第 2 条附言  ·  2025 年 5 月 23 日
    修正 "TortoiseSVN" -> "TortoiseGit"
    修正 @dcsuibian #45
    99 条回复    2025-05-24 09:19:28 +08:00
    skankhunt42
        1
    skankhunt42  
       2025 年 5 月 23 日 via iPhone
    没明白你的需求
    benjim
        2
    benjim  
       2025 年 5 月 23 日
    你这需求就很妖
    way2create
        3
    way2create  
       2025 年 5 月 23 日
    什么应用场景这是,我只试过批量修改提交历史的用户名邮箱,因为搞错了
    xiangyuecn
        4
    xiangyuecn  
    OP
       2025 年 5 月 23 日
    @skankhunt42 @cookygg
    场景一:a.txt 里面有 500 行代码,现在有个新功能 b.txt ,和 a.txt 功能完全一致,就只用改某一行的几个字

    场景二:a.txt 里面有 2000 行代码,现在要把里面后半部分 1000 行拆分到 b.txt 里,拆分出来的代码新文件要有原来的历史记录
    darklinden
        5
    darklinden  
       2025 年 5 月 23 日
    我这儿有过需求是 mono repo 中两个子项目共用一个配置
    解决方式是单独提出来到最外层,然后如果需要放到子项目内就在文件夹内丢硬链接
    pagxir
        6
    pagxir  
       2025 年 5 月 23 日 via Android
    你确实这是用 git 来做版本管理的么,你确定不是想建立一个分支啥的。非要 copy 的话,你可以考虑用 SVN 。
    BrowerDriver
        7
    BrowerDriver  
       2025 年 5 月 23 日
    Iakihsoug
        8
    Iakihsoug  
       2025 年 5 月 23 日
    啊这 不知道 OP 听没听说过继承重写
    magggia
        9
    magggia  
       2025 年 5 月 23 日
    没戏,跟 git 自身的原理相悖了,不支持
    mercury233
        10
    mercury233  
       2025 年 5 月 23 日
    假如 a 文件涉及 100 个 commit ,你这新建 b 文件的同时也得新建 100 个 commit ,不能去修改历史记录,我猜 git 自身没有这个功能
    unused
        11
    unused  
       2025 年 5 月 23 日 via Android   ❤️ 2
    filter-branch 每个 commit 复制一遍
    whoosy
        12
    whoosy  
       2025 年 5 月 23 日   ❤️ 1
    git 本事就是保持和维护 commit 历史记录的,你这直接伪造可还行
    xiangyuecn
        13
    xiangyuecn  
    OP
       2025 年 5 月 23 日
    @BrowerDriver @Iakihsoug
    有没有基础可以用的操作,太复杂的不是说不可以学,要用的时候肯得去翻文档,时间成本太高了,前提是知道翻什么地方,不常用的命令普通人也不大会去记 现用现翻文档
    ningxing
        14
    ningxing  
       2025 年 5 月 23 日
    新建分支就行了啊,不同分支不同记录,只是你只关心 a 和 b 文件,其他文件不用关心
    xiangyuecn
        15
    xiangyuecn  
    OP
       2025 年 5 月 23 日
    @magggia “没戏,跟 git 自身的原理相悖了,不支持”
    但又存在 git mv 这个命令,mv 本身按字面意思就是删文件+新建文件,要实现复制只是不需要删文件+新建文件
    xiangyuecn
        16
    xiangyuecn  
    OP
       2025 年 5 月 23 日
    @pagxir @ningxing 不需要提分支功能,跟这个复制毫无干系
    BruceXu
        17
    BruceXu  
       2025 年 5 月 23 日   ❤️ 3
    我算是看明白了.
    假设 a 文件是 1 年前创建的,有 100 条修改记录.

    OP 想的是,根据 a 文件复制出一个 b 文件来.同时在 git 中可以看到 b 文件也是 1 年前创建的,也有这 100 条修改记录.

    这不是造假么...
    C02TobNClov1Dz56
        18
    C02TobNClov1Dz56  
       2025 年 5 月 23 日   ❤️ 4
    简单, a 文件保留, 然后 b 文件开头加一行注释, 历史见 a 文件(
    mangoDB
        19
    mangoDB  
       2025 年 5 月 23 日
    你想让「文件 b 」和「文件 a 」具备一样的历史记录,不仅仅是内容的拷贝。我觉得`git`应该无法满足你这个奇怪的需求,这相当于是“伪造”了「文件 b 」的历史记录(仿佛开启了月报宝盒)。

    如楼上的几个答案所说,这和“版本控制”的理念是矛盾的。
    xiangyuecn
        20
    xiangyuecn  
    OP
       2025 年 5 月 23 日
    @whoosy @xubingok “伪造” “造假”
    比如:新文件 b.txt 提交的时候会有新建文件的记录,显示是从 a.txt 复制。就像 mv 一样 显示从 a.txt 改名 新文件名可以看到以前的老文件名记录

    维护一下这个记录很难的嘛🙏
    Trim21
        21
    Trim21  
       2025 年 5 月 23 日 via Android
    做不到,这个要看对应的 git 客户端能不能识别出他是文件改名。
    geelaw
        22
    geelaw  
       2025 年 5 月 23 日   ❤️ 6
    https://devblogs.microsoft.com/oldnewthing/20190919-00/?p=102904

    基本原理是:

    - blame 在 merge commit 处会追踪文件来自各个 parents 的部份。
    - 让文件 a 和 b 来自 merge 的两个 parents 。
    - 让文件 a 来自更早的 a 。
    - 仍文件 b 来自更早的 a 。

    我的做法和 Raymond Chen 不一样,假设当前在 branch "current":

    1. 重命名当前文件

    git checkout -b copy
    git mv a _
    git commit -m 'Prepare to copy file.'

    2. 把当前文件在第一个分支上恢复为原来的名字

    git checkout -b copy1
    git mv _ a
    git commit -m 'Restore name of file.'

    3. 把当前文件在另一个分支上重命名为副本的名字

    git checkout -b copy2 copy
    git mv _ b
    git commit -m 'Copy file.'

    4. 合并两个修改

    git checkout copy
    git merge --no-ff -m 'Finish copying file.' copy1 copy2

    5. 回归原分支

    git checkout current
    git merge --no-ff -m 'Copy `a` to `b`.' copy

    6. 删除中间分支

    git branch -d copy copy1 copy2
    sunbeams001
        23
    sunbeams001  
       2025 年 5 月 23 日   ❤️ 1
    把 a.txt 相关的所有记录导出成文本,贴在创建 b.txt 的 commit message 里
    geelaw
        24
    geelaw  
       2025 年 5 月 23 日   ❤️ 4
    @magggia #9
    @mercury233 #10
    @mangoDB #19
    见 #22

    @xubingok #17 这当然不是造假,考虑各种变种需求:文件拆分成两个,希望各自保留历史;两个文件合并,希望保留各自的历史。

    @Trim21 #21 模糊识别重命名确实有这个问题,但是 Git 的原理保证:如果一个 commit 里面只发生不同内容文件的重命名(没有内容修改、没有多个同内容文件重命名),那么历史可以正确用 blame 追溯。(不满足此条件则会有模糊匹配的问题,所以我给文件改名的时候都是一个 commit 只做改名一件事的。)
    gesse
        25
    gesse  
       2025 年 5 月 23 日
    本身 git 的哲学就是“保持历史真实性”,你这个需求就是“篡改历史”

    相违背了。
    dsggnbsp
        26
    dsggnbsp  
       2025 年 5 月 23 日
    WhiskerSpark
        27
    WhiskerSpark  
       2025 年 5 月 23 日
    想看历史记录就去看 a.txt 就好了
    suhu
        28
    suhu  
    PRO
       2025 年 5 月 23 日
    你需要这个东西: https://github.com/newren/git-filter-repo
    筛选出某一个目录/文件的提交记录 和 filter-branch 不一样,更好用
    xiangyuecn
        29
    xiangyuecn  
    OP
       2025 年 5 月 23 日
    @mangoDB 我不需要“理念”
    @gesse 我不需要“哲学”

    我只想要我复制出的文件能简单的做到历史记录追踪😐
    94
        30
    94  
       2025 年 5 月 23 日
    @xiangyuecn #15 ,mv 是重命名啊,怎么到你这边就变成删文件+新建文件了……
    基于“文件”进行操作,给这个 mv 操作添加到对应文件的 commit 历史中。

    git-mv - Move or rename a file, a directory, or a symlink
    [Git - git-mv Documentation]( https://git-scm.com/docs/git-mv)

    如果按照 OP 你的意思,其实复制出来一个 b 文件。但对于你来说是知道这个文件是基于 a 文件复制出来的,但是对于 git 来说 b 文件单纯只是没有被追踪过的一个新文件。
    所以你的实际操作是新增一个 b 文件,同时去按照 a 文件的编辑历史造 b 文件的整个历史,而不是共享整个历史。
    xiangyuecn
        31
    xiangyuecn  
    OP
       2025 年 5 月 23 日
    @WhiskerSpark “想看历史记录就去看 a.txt 就好了”,目前就是这样看历史记录的,总感觉差点意思
    lzgshsj
        32
    lzgshsj  
       2025 年 5 月 23 日   ❤️ 7
    @xiangyuecn #29 所以你也不需要"git"
    loon98
        33
    loon98  
       2025 年 5 月 23 日
    这种需求应该做成子仓库, txt 文件保留两份 blame 指向同一个记录, 必须由分支实现. 主仓库分别引用子仓库的两个分支.
    你要求的这个用法就不符合 git 思想和哲学, 要么就用 24l 的方案, 要么就用最符合 git 哲学的办法达成近似效果.
    codehz
        34
    codehz  
       2025 年 5 月 23 日 via Android
    @dfkjgklfdjg git mv 等价于 mv+git rm+git add ,git 识别文件移动完全靠猜测,并没有特别记录这个信息
    feedcode
        35
    feedcode  
       2025 年 5 月 23 日
    11 楼老哥都给你答案了,再给你个详细的
    git filter-branch --tree-filter 'cp -f dir/a1 dir/a3' --tag-name-filter cat --prune-empty -- --all
    94
        36
    94  
       2025 年 5 月 23 日
    @dfkjgklfdjg #30 ,不管是使用分支冲突的方式,还是通过 filter-branch/repo 去创造 b 文件的历史,都会去修改仓库的提交历史。在多人协作的项目里面操作 git 历史一个非常危险的行为。
    xiangyuecn
        37
    xiangyuecn  
    OP
       2025 年 5 月 23 日
    @geelaw #22 感谢,前几天 deepseek 也给出了类似的方案,我就是嫌操作起来太复杂了,要是有 git cp 一条命令解决,这个世界就安静了,这记录如果没有简单操作实现,其实不要历史也没什么关系,提交的时候 commit 里面手动备注是从 a.txt 复制来的,到了这个位置就手动切换到 a.txt 查看历史
    94
        38
    94  
       2025 年 5 月 23 日
    @codehz #34 ,识别到 rename 是猜。但是如果猜到了,那么提交历史里面是会有标注是 rename ,而不是 add 。

    ```
    diff --git a/test.xt b/test2.xt
    similarity index 100%
    rename from test.xt
    rename to testTT.xt
    ```
    geelaw
        39
    geelaw  
       2025 年 5 月 23 日   ❤️ 1
    @xiangyuecn #37 git cp 很难成立,原因:git mv 和 git rm 都不产生 commit ,而是修改 index 等,因此基于一致性,git cp 也应该不产生 commit 而只是修改 index ;但是 git 数据库里面每个 commit 存的是状态,而不是“怎么来的”,因此要让 git blame 认识到一个文件是另一个的副本,要么 git blame 自动检测(见下一段),要么这个信息必须存在于 commits 的历史中,用多分支的做法就是把这个信息存放在历史中,这必然要创建多个 commits ,因此 git cp 不会这样做。

    目前的实现里,如果一个 commit 的惟一变化是有一个新文件 B ,并且新文件 B 和某个已经存在的文件 A 内容一样,那么 Git 会认为新文件是“手工重新写出来的”,不会认为 B 来自于 A 。我觉得这样设计的原因是很多配置文件可能确实会是内容上一样的,但更可能是基于不同的原因分别写出来的,所以不宜合并历史。

    git mv 也并不保证 git blame 会认为移动前后的文件有关联性,这种信息依然是 Git 通过对比两个 commits 算出来的。
    94
        40
    94  
       2025 年 5 月 23 日
    @dfkjgklfdjg #38 ,随便建了一个 test 文件,rename 的时候顺手加了个 TT 。回复的时候感觉好像改成 test2 更合适。就手动改成了 2 ,但直接发出来了,后面的 to 还没改掉。

    😂 将就看吧。
    codehz
        41
    codehz  
       2025 年 5 月 23 日 via Android
    @dfkjgklfdjg 这个 diff 完全是根据两个状态对比实时计算出来的,git 并没有记录任何 diff 信息
    rb6221
        42
    rb6221  
       2025 年 5 月 23 日   ❤️ 1
    git 原生的概念是基于行操作的,甚至没有整个源文件 mv 的识别。
    mv 的识别都是各大 git 第三方客户端自己实现的,为了用户的交互友好。
    我曾经就遇到过,同一个提交,在某个客户端上被识别为 mv ,在另一个客户端上被识别为 del+add 。没有任何理由。
    整个原理基本上是基于识别同名文件,对比文件中的内容。
    你这样,文件名变了,内容也变了,那鬼才知道你的意图。
    geelaw
        43
    geelaw  
       2025 年 5 月 23 日
    @dfkjgklfdjg #30 git mv 和删除文件再新建没有任何区别。你在 #38 也说了,Git 是猜的,但

    >但是如果猜到了,那么提交历史里面是会有标注是 rename

    这是错误的,commits 里不会标注为 rename ,只有计算 diff/blame 的时候 Git 才会去(根据 commits 的内容)识别重命名关系。
    codehz
        44
    codehz  
       2025 年 5 月 23 日 via Android
    @janus77 并没有基于行,是单纯整个文件对比,之所以可以提交 hunk ,也是前端的处理方式,内部生成虚拟文件提交到树里,这个意义上二进制文件和文本文件的唯一区别只在前端展示方面(后端影响的只有可压缩性,二进制一般不太容易压缩)
    dcsuibian
        45
    dcsuibian  
       2025 年 5 月 23 日
    啊?这个很简单啊
    首先你复制一下文件,复制以后立马就 add 并 commit (这其中不能修改,否则 git 会认为是另一个文件)
    然后就只要
    git log --follow -- 对应的文件
    就好了呀
    TsubasaHanekaw
        46
    TsubasaHanekaw  
       2025 年 5 月 23 日
    git mv 重命名文件 ,提交之后,回滚,到上一条记录,再重新添加旧的文件, merge 两个分支
    mercury233
        47
    mercury233  
       2025 年 5 月 23 日
    GIT_AUTHOR_DATE=$(date -d'2012-12-12 01:00:01') GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" git commit -m '2012 init'

    git 本身倒是确实支持随意设置提交时间(和提交人),但 git log 的顺序还是按实际顺序的,除非 merge
    ykrank
        49
    ykrank  
       2025 年 5 月 23 日   ❤️ 1
    @xiangyuecn #29 并不会因为你不在乎重力,你就可以直接飞起来。现在的问题是其他人推荐让你可以飞的工具,你说我一定要靠肉身,然后别人说重力是这个世界的规则,你说我不需要“规则”,只需要飞起来。
    那别人就只能建议你睡一觉或者去天台了
    skiy
        50
    skiy  
       2025 年 5 月 23 日
    跟修改邮箱一个样。历史记录是不变,但是,所有的 commit id 都变了。
    yinheli
        51
    yinheli  
       2025 年 5 月 23 日
    你需要 `git subtree` 具体来说你要把一个文件的 commit 历史都拿出来,`git subtree split`, https://tldr.inbrowser.app/pages/common/git-subtree
    mangoDB
        52
    mangoDB  
       2025 年 5 月 23 日   ❤️ 2
    @xiangyuecn #29 非常震惊你会以这种傲慢的态度进行回复。我后悔自己浪费时间码字来回复你(包括这条)。
    B1ankCat
        53
    B1ankCat  
       2025 年 5 月 23 日
    个人朴素的看法,如果没有固定的命令的话,那么 commit 也是一个 git 对象,它的第一行是 tree ,第二行是 parent ,tree 就是这个文件在目录树中的内容,因为每次 commit 都和 a 一样,所以只需要复制每次 commit 里用到 tree 改下名字,sha1 都一样就行,然后把复制这个 commit ,把第一行改成复制出来的 tree 的 sha1 ,然后把 parent 也这样递归操作,就伪造好一个一模一样的历史了,浅见哈,有错请指出
    gesse
        54
    gesse  
       2025 年 5 月 23 日   ❤️ 1
    @xiangyuecn #29 非常震惊你会以这种傲慢的态度进行回复。我后悔自己浪费时间码字来回复你(包括这条)。
    passive
        55
    passive  
       2025 年 5 月 23 日 via Android   ❤️ 1
    git 的优点就是能改历史。但是楼主这题有地触碰到 git 的短板了。

    (爬了楼,什么狗屁哲学和理念,就是 Torvalds 随手写的一个工具)
    moudy
        56
    moudy  
       2025 年 5 月 23 日 via iPhone   ❤️ 1
    @mangoDB 旁观者说一句,你们上来的用词才是傲慢。又是理念 又是伪造的,好像 lz 要对 git 下黑手一样。lz 只不过是问如何看历史的时候能知道 b 文件是在某个时候从 a copy 出来的,这不是一个很清楚也完全可以理解的需求么,怎么就跟伪造历史扯上了。
    94
        57
    94  
       2025 年 5 月 23 日
    @geelaw #43 ,好的,修正了认知上的错误。
    所以实际上 git 只会存储变更的文件快照,而不会额外存储变更信息。每次在拉取 log 或者分析的时候实时去对比计算。
    一直以为 git log 输出的内容是直接输出的 commit 中的记录,而不是实时算的。怪不得要加上 --stat 。
    mangoDB
        58
    mangoDB  
       2025 年 5 月 23 日   ❤️ 1
    @moudy 你甚至都没有理解楼主的需求,就在这里大言不惭的教育别人。楼主需要让「文件 b 」具备和「文件 a 」一样的历史记录,并不是关注「文件 b 」是什么时间点被“拷贝”出来的。
    evada
        59
    evada  
       2025 年 5 月 23 日
    说 git blame 的意思就是,你使用 git mv a.txt b.txt 然后 commit 。
    然后可以通过 git log --graph --decorate --oneline --name-status --follow b.txt 查看包括 a.txt 的详细记录。
    或者 git blame -C -C -w b.txt 查看每一行的变更记录(包括来自 a.txt)的
    j869716
        60
    j869716  
       2025 年 5 月 23 日
    谢谢, 已 block
    xiangyuecn
        61
    xiangyuecn  
    OP
       2025 年 5 月 23 日
    @mangoDB #58 主题里面 [写的是] “并且复制前的历史和 a.txt 保持一致?”,复制前的记录两个文件保持一致。 [没说] 没说复制之后去修改 b.txt 的记录,也要去两个文件记录保持一致 [没说] 。难绷😐
    xiangyuecn
        62
    xiangyuecn  
    OP
       2025 年 5 月 23 日
    @j869716 #60 “谢谢, 已 block”,不谢😘
    lnbiuc
        63
    lnbiuc  
       2025 年 5 月 23 日
    https://github.com/git/git

    欢迎 fork 此仓库,就叫 xiangyuecn-git 如何
    xiangyuecn
        64
    xiangyuecn  
    OP
       2025 年 5 月 23 日
    @lnbiuc @63 “欢迎 fork 此仓库,就叫 xiangyuecn-git 如何” 不感兴趣,欢迎提点有建设性的意见。

    对于源码管理,一个复制的文件,文件本身是有来源的也有历史记录,但 git 没法简单做到这个复制,这本身就是少了点什么

    在本地文件目录里面,直接复制一个新文件改改提交,使用场景中规中矩,可能很多时候不会去在意这个历史记录而已
    kneo
        65
    kneo  
       2025 年 5 月 23 日
    没记错的话 hg 和 svn 都是会复制文件历史。这是个非常实用的功能。

    没想到某些 git 用户也有饭圈行为啊,一遇到自家没有的功能,就开始扯什么“理念”“造假”。不过能说出这些话的我估计也不是什么正儿八经的用户。
    msg7086
        66
    msg7086  
       2025 年 5 月 23 日
    文件更名就是删除+添加。git 能追溯,是在看到 diff 并发现删除+添加这样的行为以后,去手动关联起两个文件的历史轨迹。至于为什么复制不能追溯,这个不是 git 本身的局限,而是 git 客户端(也就是 git 软件)的局限,这些软件只会在删除+添加的时候匹配文件,不能在纯添加的时候匹配文件。
    要实现这个功能,不是改 git 协议/数据库,而是要改 git 客户端。你用什么客户端,你就得改什么客户端,或者另外自己去实现这个功能。而且这个更改只有用改过的客户端才能看到,别人用他们自己的客户端照样看不到。

    至于为什么大多数(或者所有)客户端不实现这个功能,多半是因为计算量太大了。原本是在删除和添加的文件里做文字匹配,现在变成了所有文件和添加的文件去做匹配,假如你 git 库里有 10 万个文件,现在添加了 1 个新文件,不就要做 10 万次匹配?性能就跟不上了。
    dandeli0n
        67
    dandeli0n  
       2025 年 5 月 23 日
    感觉这是悖论,假设有这个功能,那么你把 b.txt 回退到某个历史节点(这个历史节点来自 a.txt ),结果却没有 b.txt 这个文件,因为那个时候 b.txt 还不存在。
    lnbiuc
        68
    lnbiuc  
       2025 年 5 月 23 日
    @xiangyuecn #64
    你觉得产品不能满足你的需求,你可以自己选择开发,有开源精神欢迎提 pr ,不想开源自己用也 OK
    别人不可能为你的场景专门进行开发,没有义务也没有必要
    git 作为 linus 的开源项目,并没有禁止 fork ,你觉得有用那就自己做这个功能
    《你不需要理念,你不需要哲学》用别人的东西就得顺着别人来,不服就自己 fork 开发
    lnbiuc
        69
    lnbiuc  
       2025 年 5 月 23 日
    @kneo 这很常见啊,看看 linux 系对 systemd 的争议就知道了,你总不能说坚持 linux 一次做好一件事理念的这群人,linux 内核的开发者,不是正儿八经的用户吧
    没有 linus 的理念,linux 都不知道成啥样子了
    mayli
        70
    mayli  
       2025 年 5 月 23 日
    感觉技术上是可行的,因为 git 本身其实对于改名这个操作不是太在意。
    可以这样想
    你直接做两个 branch ,一个 git cp a.txt b.txt, 一个 git mv a.txt c.txt, 然后两个 branch merge ,这样你就有个完整
    的 history 了
    git 是有概率可以侦测到的,但是 git log --follow 也只能是靠猜,毕竟 git log 是跟着路径走
    你 git log a.txt b.txt c.txt 就能看到三个文件的 log 了
    baiyi
        71
    baiyi  
       2025 年 5 月 23 日   ❤️ 2
    其实技术上是可行的,无论是修改历史 commit ,还是 merge ,但楼主“嫌操作起来太复杂”,就是要一个 “git cp” 命令,别人说啥都不听,git 没有就是 git 的问题,这不是巨婴吗
    yyj08070631
        72
    yyj08070631  
       2025 年 5 月 23 日
    @baiyi 哈哈哈哈我也是想表达类似的观点

    我估计 op 把楼上所说的“造假”理解成了一种批评,但这里说的造假只是出于 git 版本控制的出发点,历史记录是不应该被伪造的,所以 git 当然不会提供这种便捷的操作来让你伪造提交记录

    但既然这些东西都是存在于文件系统,那当然会有解法,只是会麻烦一点
    B1ankCat
        73
    B1ankCat  
       2025 年 5 月 23 日
    moudy
        74
    moudy  
       2025 年 5 月 23 日
    @xiangyuecn #37 可以写个脚本,起名叫 git-cp
    justplaymore
        75
    justplaymore  
       2025 年 5 月 23 日
    git log --follow 新文件

    显示新文件的提交记录,包括新文件重命名之前的提交记录。
    wangyzj
        76
    wangyzj  
       2025 年 5 月 23 日
    git 本身就是记录所有操作历史
    包括你的操作
    历史不动那违背了 git 的初衷
    moudy
        77
    moudy  
       2025 年 5 月 23 日
    @dfkjgklfdjg #57 所以要是 git 客户端能直接猜出 b 文件是 a 文件的 copy ,lz 可以直接无脑 cp 完事。而且似乎 git log --follow newfile.txt 真可以让 git 主动猜。
    xiangyuecn
        78
    xiangyuecn  
    OP
       2025 年 5 月 23 日
    zhuisui
        79
    zhuisui  
       2025 年 5 月 23 日
    相比于 --follow 你还可以用 --follow --follow ,甚至是显式地使用 --find-copies 甚至是 --find-copies-harder ,此时你可能还需要考虑使用 -l 和 --combined-all-paths 等等

    显然你自己不会去翻文档的,也不会理解 git 如何工作,对吗
    chimission
        80
    chimission  
       2025 年 5 月 23 日   ❤️ 1
    haha 明明是一个很有意思的技术需求, 结果一堆人在那扯哲学不要这么做, 就像工作了好多年但是技术没什么没长进的老油条找借口推脱不会做的需求一样(狗头)
    xiangyuecn
        81
    xiangyuecn  
    OP
       2025 年 5 月 23 日
    @zhuisui #79 翻文档也得找到到地方,时间成本太高,前几天问 deepseek ,给的解决方案太复杂
    实际压根什么都不需要做就能解决,前提是要知道怎么规避会产生影响的地方,完事

    上面还有个让我 fork git 源码,自己实现这个功能😂
    qping
        82
    qping  
       2025 年 5 月 23 日
    @skankhunt42
    @cookygg
    @way2create
    我们有类似的需求,必须 js 迁移到 ts ,想保留之前的修改记录
    lnbiuc
        83
    lnbiuc  
       2025 年 5 月 23 日
    @xiangyuecn #81 这不就是巨婴吗
    显然你自己不会去翻文档的,也不会理解 git 如何工作,对吗
    qping
        84
    qping  
       2025 年 5 月 23 日
    请忽略我的 @,眼神不好,楼主的是复制,不是重命名
    xiangyuecn
        85
    xiangyuecn  
    OP
       2025 年 5 月 23 日
    @lnbiuc #83 是的,您说的对,谢谢,已经解决了 简单有效
    geelaw
        86
    geelaw  
       2025 年 5 月 23 日
    @xiangyuecn #78 我不确定为什么你认为 TortoiseGit 能识别 = 这是一个 Git 的基本操作。TortoiseGit 并不是 Git 。如果你期待的是存在一个基于 Git 的工具可以识别,提问的时候应该说清楚,你的答案是 TortoiseGit 的基本操作,而非 Git 的基本操作。

    使用 merge 反复重命名的方法可以确保 Git 本身可以识别,这一点在你需要远程查看 git blame 的时候十分重要——毕竟没法要求 GitHub 远程帮你运行 TortoiseGit 。
    xiangyuecn
        87
    xiangyuecn  
    OP
       2025 年 5 月 23 日
    @geelaw #86 测试结果来看,git 本身是支持类似 git cp 的命令的,就是提交的时候 人为的保证复制的新文件和老文件内容一致,提交上去,外行人来看他们就自动的关联到了一起了。修改新文件再提交就没有这个效果

    跟用的 git 命令、还是 TortoiseGit 图形界面,在这个功能上是没有区别的

    cp mv 这种应该是属于同一类基本操作,要是 git 以前能明确显式的支持 cp 命令,后面这个修改复制的文件后再提交导致的不能关联的问题,也就会不存在了
    zhuisui
        88
    zhuisui  
       2025 年 5 月 23 日
    @xiangyuecn
    如果不是看到最后你觉得什么是对的,我一开始也不知道你到底要什么
    这反映了一个事实,你自己也不会描述你得需求。不管是对于使用 git 工具还是使用 AI 工具,清楚自己的需求知道工具的能力和限制,才能达到目的。
    是的,我就是来批判你的。你就是想不劳而获,也不想靠自己来知道工具如何工作,希望大家告诉你,然后能用就行了。
    这整篇帖子的交流,就是一个探寻用户真正需求的过程,你就是那个 xx 用户。
    zhuisui
        89
    zhuisui  
       2025 年 5 月 23 日
    > 跟用的 git 命令、还是 TortoiseGit 图形界面,在这个功能上是没有区别的

    > cp mv 这种应该是属于同一类基本操作,要是 git 以前能明确显式的支持 cp 命令,后面这个修改复制的文件后再提交导致的不能关联的问题,也就会不存在了

    纯粹瞎猜,显然没看我贴得几个选项的描述,所以认为这就是 git 设计出来能很容易支持的东西。
    其实这个需求 git 支持是很麻烦的,而且有很多问题的,包括可靠性和性能。
    根本就不是所谓 git 基础的能力,既高级,又需要谨慎使用,又有很多限制。
    quqiu
        90
    quqiu  
       2025 年 5 月 23 日
    没看懂,楼主能不能好心,重新整理一下操作说明。
    quqiu
        91
    quqiu  
       2025 年 5 月 23 日
    “注意不要改复制的文件就行”

    指的是不要改 a.TXT?
    xingheng
        92
    xingheng  
       2025 年 5 月 23 日
    Steps:
    1. mv a.txt b.txt
    2. git commit -am
    3. vim b.txt
    4. git commit -am

    第一次 commit 让 git 知道你只做了 rename ,然后历史就接上了,第二次 commit 做你认为的差异内容,历史就连上了。试试
    xiangyuecn
        93
    xiangyuecn  
    OP
       2025 年 5 月 23 日
    @quqiu #91 复制的新文件 b.txt 第一次提交前,不要修改内容,提交前要保持和老文件 a.txt 一致,提交后就能关联到 a.txt 的历史记录。如果修改了不一致就会关联不上,目前测试结果是这样的

    老文件 a.txt 也可以修改,修改后再复制出 b.txt ,只要提交时,两个文件内容一致,提交后就能关联上
    xiangyuecn
        94
    xiangyuecn  
    OP
       2025 年 5 月 23 日
    @zhuisui #88 我也不知道是哪里的问题

    “比如已有 a.txt ,我现在要个 b.txt 。 如何复制出 b.txt 这个文件,并且复制前的历史和 a.txt 保持一致?”

    这句是主题里面的原文,这段话不清楚是不是有歧义,还是需求描述不够准确,也没人提这句话有没有毛病😂
    geelaw
        95
    geelaw  
       2025 年 5 月 23 日
    @xiangyuecn #87 您似乎没有理解“TortoiseGit 不是 Git”这句话。

    你可以自己试试,在 Git 2.42 上

    git init
    >a.txt echo 1
    git add a.txt
    git commit -m first
    >>a.txt echo 2
    git commit -am second
    cp a.txt b.txt
    git add b.txt
    git commit -m copy
    git blame b.txt

    你会看到 b.txt 的每一行都是 copy 导致的,而不是 first, second 导致的。

    现在,为什么 TortoiseGit 会显示 b.txt 来自 a.txt (按照你的说法是这样的,我自己不用 TortoiseGit 所以不知道)。答案是因为 TortoiseGit 会自己读取 Git 的 commit 对象并自己追踪文件,和 git blame 没有任何关系。

    Git 不会认为 b.txt 和 a.txt 有相同的历史,至于 TortoiseGit 有自己的解读方式,那是 TortoiseGit 的事情。本质上来说,你自己写一个追踪复制的程序,等价于你用 TortoiseGit ,都和 Git 自带的 blame 解读没有任何关系。
    geelaw
        96
    geelaw  
       2025 年 5 月 23 日
    我在 #86 的主要观点有两个:

    1. 如果你希望用一个工具自动检查文件复制的情况,应该问“有什么工具能追踪 Git 历史中复制的文件”,而不是“Git 基操:怎么样复制一个文件,能保持历史记录”,因为后者的自然解读是“保持 Git 所认为的历史记录”,即 git blame 的结果。
    2. 如果你希望确保 git blame 总是得到你希望的历史(这和其他第三方工具没有任何关系),那么单纯复制 + git add + git commit 是不行的。
    wynemo
        97
    wynemo  
       2025 年 5 月 23 日
    这还不简单,ln -s 🤡
    fpk5
        98
    fpk5  
       2025 年 5 月 24 日
    这不是本来就支持的吗?不用--follow 啊
    ```
    $ git log -p -- b.txt
    commit a7d5f10cfba6f13a2eb09388bd2a941e95bfcb99 (HEAD -> main)
    Date:

    update b.txt

    diff --git a/b.txt b/b.txt
    index ce01362..3b18e51 100644
    --- a/b.txt
    +++ b/b.txt
    @@ -1 +1 @@
    -hello
    +hello world

    commit e30f877d34497b41feac93d56d77d5282f8bb257
    Date:

    add b.txt

    diff --git a/a.txt b/b.txt
    similarity index 100%
    copy from a.txt
    copy to b.txt

    commit fb7f7cf2ccbd5e488bc21a684414d4c7373f82ab
    Date:

    first commit

    diff --git a/a.txt b/a.txt
    new file mode 100644
    index 0000000..ce01362
    --- /dev/null
    +++ b/a.txt
    @@ -0,0 +1 @@
    +hello
    ```
    重点是`similarity index 100%`。
    way2create
        99
    way2create  
       2025 年 5 月 24 日
    @qping 这种需求可以理解,那你们应该也适用 up 的这种情况了,但说实话我是没想到 up 要的是这个,毕竟我以为他提问前就算不会完整文档看一遍,但多多少少起码有检索看看文档的,而--follow 参数就在 git log -文档 OPTIONS 中的第一个选项,列出重命名前的历史记录,所以我以为他要的是 git log 什么参数都不加就能显示一样的历史
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   Solana   ·   2614 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 44ms · UTC 10:58 · PVG 18:58 · LAX 02:58 · JFK 05:58
    ♥ Do have faith in what you're doing.