

























这是一个创建于 388 天前的主题,其中的信息可能已经有所发展或是发生改变。
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
2 benjim 2025 年 5 月 23 日你这需求就很妖 |
3 way2create 2025 年 5 月 23 日什么应用场景这是,我只试过批量修改提交历史的用户名邮箱,因为搞错了 |
4 xiangyuecn 2025 年 5 月 23 日@skankhunt42 @cookygg 场景二:a.txt 里面有 2000 行代码,现在要把里面后半部分 1000 行拆分到 b.txt 里,拆分出来的代码新文件要有原来的历史记录 |
5 darklinden 2025 年 5 月 23 日我这儿有过需求是 mono repo 中两个子项目共用一个配置 |
6 pagxir 2025 年 5 月 23 日 via Android你确实这是用 git 来做版本管理的么,你确定不是想建立一个分支啥的。非要 copy 的话,你可以考虑用 SVN 。 |
8 Iakihsoug 2025 年 5 月 23 日啊这 不知道 OP 听没听说过继承重写 |
9 magggia 2025 年 5 月 23 日没戏,跟 git 自身的原理相悖了,不支持 |
10 mercury233 2025 年 5 月 23 日假如 a 文件涉及 100 个 commit ,你这新建 b 文件的同时也得新建 100 个 commit ,不能去修改历史记录,我猜 git 自身没有这个功能 |
11 unused 2025 年 5 月 23 日 via Androidfilter-branch 每个 commit 复制一遍 |
12 whoosy 2025 年 5 月 23 日git 本事就是保持和维护 commit 历史记录的,你这直接伪造可还行 |
14 ningxing 2025 年 5 月 23 日新建分支就行了啊,不同分支不同记录,只是你只关心 a 和 b 文件,其他文件不用关心 |
15 xiangyuecn 2025 年 5 月 23 日@magggia “没戏,跟 git 自身的原理相悖了,不支持” |
17 BruceXu 2025 年 5 月 23 日我算是看明白了. OP 想的是,根据 a 文件复制出一个 b 文件来.同时在 git 中可以看到 b 文件也是 1 年前创建的,也有这 100 条修改记录. 这不是造假么... |
18 C02TobNClov1Dz56 2025 年 5 月 23 日简单, a 文件保留, 然后 b 文件开头加一行注释, 历史见 a 文件( |
19 mangoDB 2025 年 5 月 23 日你想让「文件 b 」和「文件 a 」具备一样的历史记录,不仅仅是内容的拷贝。我觉得`git`应该无法满足你这个奇怪的需求,这相当于是“伪造”了「文件 b 」的历史记录(仿佛开启了月报宝盒)。 如楼上的几个答案所说,这和“版本控制”的理念是矛盾的。 |
20 xiangyuecn 2025 年 5 月 23 日 |
21 Trim21 2025 年 5 月 23 日 via Android做不到,这个要看对应的 git 客户端能不能识别出他是文件改名。 |
22 geelaw 2025 年 5 月 23 日https://devblogs.microsoft.com/oldnewthing/20190919-00/?p=102904 基本原理是: - blame 在 merge commit 处会追踪文件来自各个 parents 的部份。 我的做法和 Raymond Chen 不一样,假设当前在 branch "current": 1. 重命名当前文件 git checkout -b copy 2. 把当前文件在第一个分支上恢复为原来的名字 git checkout -b copy1 3. 把当前文件在另一个分支上重命名为副本的名字 git checkout -b copy2 copy 4. 合并两个修改 git checkout copy 5. 回归原分支 git checkout current 6. 删除中间分支 git branch -d copy copy1 copy2 |
23 sunbeams001 2025 年 5 月 23 日把 a.txt 相关的所有记录导出成文本,贴在创建 b.txt 的 commit message 里 |
24 geelaw 2025 年 5 月 23 日@magggia #9 @xubingok #17 这当然不是造假,考虑各种变种需求:文件拆分成两个,希望各自保留历史;两个文件合并,希望保留各自的历史。 @Trim21 #21 模糊识别重命名确实有这个问题,但是 Git 的原理保证:如果一个 commit 里面只发生不同内容文件的重命名(没有内容修改、没有多个同内容文件重命名),那么历史可以正确用 blame 追溯。(不满足此条件则会有模糊匹配的问题,所以我给文件改名的时候都是一个 commit 只做改名一件事的。) |
25 gesse 2025 年 5 月 23 日本身 git 的哲学就是“保持历史真实性”,你这个需求就是“篡改历史” 相违背了。 |
30 94 2025 年 5 月 23 日@xiangyuecn #15 ,mv 是重命名啊,怎么到你这边就变成删文件+新建文件了…… git-mv - Move or rename a file, a directory, or a symlink 如果按照 OP 你的意思,其实复制出来一个 b 文件。但对于你来说是知道这个文件是基于 a 文件复制出来的,但是对于 git 来说 b 文件单纯只是没有被追踪过的一个新文件。 |
33 loon98 2025 年 5 月 23 日这种需求应该做成子仓库, txt 文件保留两份 blame 指向同一个记录, 必须由分支实现. 主仓库分别引用子仓库的两个分支. |
34 codehz 2025 年 5 月 23 日 via Android@dfkjgklfdjg git mv 等价于 mv+git rm+git add ,git 识别文件移动完全靠猜测,并没有特别记录这个信息 |
35 feedcode 2025 年 5 月 23 日11 楼老哥都给你答案了,再给你个详细的 |
36 94 2025 年 5 月 23 日@dfkjgklfdjg #30 ,不管是使用分支冲突的方式,还是通过 filter-branch/repo 去创造 b 文件的历史,都会去修改仓库的提交历史。在多人协作的项目里面操作 git 历史一个非常危险的行为。 |
37 xiangyuecn 2025 年 5 月 23 日@geelaw #22 感谢,前几天 deepseek 也给出了类似的方案,我就是嫌操作起来太复杂了,要是有 git cp 一条命令解决,这个世界就安静了,这记录如果没有简单操作实现,其实不要历史也没什么关系,提交的时候 commit 里面手动备注是从 a.txt 复制来的,到了这个位置就手动切换到 a.txt 查看历史 |
38 94 2025 年 5 月 23 日@codehz #34 ,识别到 rename 是猜。但是如果猜到了,那么提交历史里面是会有标注是 rename ,而不是 add 。 ``` |
39 geelaw 2025 年 5 月 23 日@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 算出来的。 |
40 94 2025 年 5 月 23 日@dfkjgklfdjg #38 ,随便建了一个 test 文件,rename 的时候顺手加了个 TT 。回复的时候感觉好像改成 test2 更合适。就手动改成了 2 ,但直接发出来了,后面的 to 还没改掉。 😂 将就看吧。 |
41 codehz 2025 年 5 月 23 日 via Android@dfkjgklfdjg 这个 diff 完全是根据两个状态对比实时计算出来的,git 并没有记录任何 diff 信息 |
42 rb6221 2025 年 5 月 23 日git 原生的概念是基于行操作的,甚至没有整个源文件 mv 的识别。 |
43 geelaw 2025 年 5 月 23 日@dfkjgklfdjg #30 git mv 和删除文件再新建没有任何区别。你在 #38 也说了,Git 是猜的,但 >但是如果猜到了,那么提交历史里面是会有标注是 rename 这是错误的,commits 里不会标注为 rename ,只有计算 diff/blame 的时候 Git 才会去(根据 commits 的内容)识别重命名关系。 |
44 codehz 2025 年 5 月 23 日 via Android@janus77 并没有基于行,是单纯整个文件对比,之所以可以提交 hunk ,也是前端的处理方式,内部生成虚拟文件提交到树里,这个意义上二进制文件和文本文件的唯一区别只在前端展示方面(后端影响的只有可压缩性,二进制一般不太容易压缩) |
45 dcsuibian 2025 年 5 月 23 日啊?这个很简单啊 |
46 TsubasaHanekaw 2025 年 5 月 23 日git mv 重命名文件 ,提交之后,回滚,到上一条记录,再重新添加旧的文件, merge 两个分支 |
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 |
49 ykrank 2025 年 5 月 23 日@xiangyuecn #29 并不会因为你不在乎重力,你就可以直接飞起来。现在的问题是其他人推荐让你可以飞的工具,你说我一定要靠肉身,然后别人说重力是这个世界的规则,你说我不需要“规则”,只需要飞起来。 |
50 skiy 2025 年 5 月 23 日跟修改邮箱一个样。历史记录是不变,但是,所有的 commit id 都变了。 |
52 mangoDB 2025 年 5 月 23 日@xiangyuecn #29 |
53 B1ankCat 2025 年 5 月 23 日个人朴素的看法,如果没有固定的命令的话,那么 commit 也是一个 git 对象,它的第一行是 tree ,第二行是 parent ,tree 就是这个文件在目录树中的内容,因为每次 commit 都和 a 一样,所以只需要复制每次 commit 里用到 tree 改下名字,sha1 都一样就行,然后把复制这个 commit ,把第一行改成复制出来的 tree 的 sha1 ,然后把 parent 也这样递归操作,就伪造好一个一模一样的历史了,浅见哈,有错请指出 |
54 gesse 2025 年 5 月 23 日@xiangyuecn #29 |
55 passive 2025 年 5 月 23 日 via Androidgit 的优点就是能改历史。但是楼主这题有地触碰到 git 的短板了。 (爬了楼,什么狗屁哲学和理念,就是 Torvalds 随手写的一个工具) |
56 moudy 2025 年 5 月 23 日 via iPhone@mangoDB 旁观者说一句,你们上来的用词才是傲慢。又是理念 又是伪造的,好像 lz 要对 git 下黑手一样。lz 只不过是问如何看历史的时候能知道 b 文件是在某个时候从 a copy 出来的,这不是一个很清楚也完全可以理解的需求么,怎么就跟伪造历史扯上了。 |
57 94 2025 年 5 月 23 日@geelaw #43 ,好的,修正了认知上的错误。 |
58 mangoDB 2025 年 5 月 23 日@moudy 你甚至都没有理解楼主的需求,就在这里大言不惭的教育别人。楼主需要让「文件 b 」具备和「文件 a 」一样的历史记录,并不是关注「文件 b 」是什么时间点被“拷贝”出来的。 |
59 evada 2025 年 5 月 23 日说 git blame 的意思就是,你使用 git mv a.txt b.txt 然后 commit 。 |
60 j869716 2025 年 5 月 23 日谢谢, 已 block |
61 xiangyuecn 2025 年 5 月 23 日@mangoDB #58 主题里面 [写的是] “并且复制前的历史和 a.txt 保持一致?”,复制前的记录两个文件保持一致。 [没说] 没说复制之后去修改 b.txt 的记录,也要去两个文件记录保持一致 [没说] 。难绷😐 |
64 xiangyuecn 2025 年 5 月 23 日 |
65 kneo 2025 年 5 月 23 日没记错的话 hg 和 svn 都是会复制文件历史。这是个非常实用的功能。 没想到某些 git 用户也有饭圈行为啊,一遇到自家没有的功能,就开始扯什么“理念”“造假”。不过能说出这些话的我估计也不是什么正儿八经的用户。 |
66 msg7086 2025 年 5 月 23 日文件更名就是删除+添加。git 能追溯,是在看到 diff 并发现删除+添加这样的行为以后,去手动关联起两个文件的历史轨迹。至于为什么复制不能追溯,这个不是 git 本身的局限,而是 git 客户端(也就是 git 软件)的局限,这些软件只会在删除+添加的时候匹配文件,不能在纯添加的时候匹配文件。 至于为什么大多数(或者所有)客户端不实现这个功能,多半是因为计算量太大了。原本是在删除和添加的文件里做文字匹配,现在变成了所有文件和添加的文件去做匹配,假如你 git 库里有 10 万个文件,现在添加了 1 个新文件,不就要做 10 万次匹配?性能就跟不上了。 |
67 dandel1on 2025 年 5 月 23 日感觉这是悖论,假设有这个功能,那么你把 b.txt 回退到某个历史节点(这个历史节点来自 a.txt ),结果却没有 b.txt 这个文件,因为那个时候 b.txt 还不存在。 |
68 lnbiuc 2025 年 5 月 23 日@xiangyuecn #64 |
69 lnbiuc 2025 年 5 月 23 日@kneo 这很常见啊,看看 linux 系对 systemd 的争议就知道了,你总不能说坚持 linux 一次做好一件事理念的这群人,linux 内核的开发者,不是正儿八经的用户吧 |
70 mayli 2025 年 5 月 23 日感觉技术上是可行的,因为 git 本身其实对于改名这个操作不是太在意。 |
71 FinnBai 2025 年 5 月 23 日其实技术上是可行的,无论是修改历史 commit ,还是 merge ,但楼主“嫌操作起来太复杂”,就是要一个 “git cp” 命令,别人说啥都不听,git 没有就是 git 的问题,这不是巨婴吗 |
72 yyj08070631 2025 年 5 月 23 日@baiyi 哈哈哈哈我也是想表达类似的观点 我估计 op 把楼上所说的“造假”理解成了一种批评,但这里说的造假只是出于 git 版本控制的出发点,历史记录是不应该被伪造的,所以 git 当然不会提供这种便捷的操作来让你伪造提交记录 但既然这些东西都是存在于文件系统,那当然会有解法,只是会麻烦一点 |
75 justplaymore 2025 年 5 月 23 日git log --follow 新文件 显示新文件的提交记录,包括新文件重命名之前的提交记录。 |
76 wangyzj 2025 年 5 月 23 日git 本身就是记录所有操作历史 |
77 moudy 2025 年 5 月 23 日@dfkjgklfdjg #57 所以要是 git 客户端能直接猜出 b 文件是 a 文件的 copy ,lz 可以直接无脑 cp 完事。而且似乎 git log --follow newfile.txt 真可以让 git 主动猜。 |
79 zhuisui 2025 年 5 月 23 日相比于 --follow 你还可以用 --follow --follow ,甚至是显式地使用 --find-copies 甚至是 --find-copies-harder ,此时你可能还需要考虑使用 -l 和 --combined-all-paths 等等 显然你自己不会去翻文档的,也不会理解 git 如何工作,对吗 |
80 chimission 2025 年 5 月 23 日haha 明明是一个很有意思的技术需求, 结果一堆人在那扯哲学不要这么做, 就像工作了好多年但是技术没什么没长进的老油条找借口推脱不会做的需求一样(狗头) |
81 xiangyuecn 2025 年 5 月 23 日@zhuisui #79 翻文档也得找到到地方,时间成本太高,前几天问 deepseek ,给的解决方案太复杂 上面还有个让我 fork git 源码,自己实现这个功能😂 |
83 lnbiuc 2025 年 5 月 23 日@xiangyuecn #81 这不就是巨婴吗 |
84 qping 2025 年 5 月 23 日请忽略我的 @,眼神不好,楼主的是复制,不是重命名 |
86 geelaw 2025 年 5 月 23 日@xiangyuecn #78 我不确定为什么你认为 TortoiseGit 能识别 = 这是一个 Git 的基本操作。TortoiseGit 并不是 Git 。如果你期待的是存在一个基于 Git 的工具可以识别,提问的时候应该说清楚,你的答案是 TortoiseGit 的基本操作,而非 Git 的基本操作。 使用 merge 反复重命名的方法可以确保 Git 本身可以识别,这一点在你需要远程查看 git blame 的时候十分重要——毕竟没法要求 GitHub 远程帮你运行 TortoiseGit 。 |
87 xiangyuecn 2025 年 5 月 23 日@geelaw #86 测试结果来看,git 本身是支持类似 git cp 的命令的,就是提交的时候 人为的保证复制的新文件和老文件内容一致,提交上去,外行人来看他们就自动的关联到了一起了。修改新文件再提交就没有这个效果 跟用的 git 命令、还是 TortoiseGit 图形界面,在这个功能上是没有区别的 cp mv 这种应该是属于同一类基本操作,要是 git 以前能明确显式的支持 cp 命令,后面这个修改复制的文件后再提交导致的不能关联的问题,也就会不存在了 |
88 zhuisui 2025 年 5 月 23 日@xiangyuecn |
89 zhuisui 2025 年 5 月 23 日> 跟用的 git 命令、还是 TortoiseGit 图形界面,在这个功能上是没有区别的 > cp mv 这种应该是属于同一类基本操作,要是 git 以前能明确显式的支持 cp 命令,后面这个修改复制的文件后再提交导致的不能关联的问题,也就会不存在了 纯粹瞎猜,显然没看我贴得几个选项的描述,所以认为这就是 git 设计出来能很容易支持的东西。 |
90 quqiu 2025 年 5 月 23 日没看懂,楼主能不能好心,重新整理一下操作说明。 |
91 quqiu 2025 年 5 月 23 日“注意不要改复制的文件就行” 指的是不要改 a.TXT? |
92 xingheng 2025 年 5 月 23 日Steps: 第一次 commit 让 git 知道你只做了 rename ,然后历史就接上了,第二次 commit 做你认为的差异内容,历史就连上了。试试 |
93 xiangyuecn 2025 年 5 月 23 日@quqiu #91 复制的新文件 b.txt 第一次提交前,不要修改内容,提交前要保持和老文件 a.txt 一致,提交后就能关联到 a.txt 的历史记录。如果修改了不一致就会关联不上,目前测试结果是这样的 老文件 a.txt 也可以修改,修改后再复制出 b.txt ,只要提交时,两个文件内容一致,提交后就能关联上 |
94 xiangyuecn 2025 年 5 月 23 日@zhuisui #88 我也不知道是哪里的问题 “比如已有 a.txt ,我现在要个 b.txt 。 如何复制出 b.txt 这个文件,并且复制前的历史和 a.txt 保持一致?” 这句是主题里面的原文,这段话不清楚是不是有歧义,还是需求描述不够准确,也没人提这句话有没有毛病😂 |
95 geelaw 2025 年 5 月 23 日@xiangyuecn #87 您似乎没有理解“TortoiseGit 不是 Git”这句话。 你可以自己试试,在 Git 2.42 上 git init 你会看到 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 解读没有任何关系。 |
96 geelaw 2025 年 5 月 23 日我在 #86 的主要观点有两个: 1. 如果你希望用一个工具自动检查文件复制的情况,应该问“有什么工具能追踪 Git 历史中复制的文件”,而不是“Git 基操:怎么样复制一个文件,能保持历史记录”,因为后者的自然解读是“保持 Git 所认为的历史记录”,即 git blame 的结果。 |
97 wynemo 2025 年 5 月 23 日这还不简单,ln -s 🤡 |
98 fpk5 2025 年 5 月 24 日这不是本来就支持的吗?不用--follow 啊 update b.txt diff --git a/b.txt b/b.txt commit e30f877d34497b41feac93d56d77d5282f8bb257 add b.txt diff --git a/a.txt b/b.txt commit fb7f7cf2ccbd5e488bc21a684414d4c7373f82ab first commit diff --git a/a.txt b/a.txt |
99 way2create 2025 年 5 月 24 日@qping 这种需求可以理解,那你们应该也适用 up 的这种情况了,但说实话我是没想到 up 要的是这个,毕竟我以为他提问前就算不会完整文档看一遍,但多多少少起码有检索看看文档的,而--follow 参数就在 git log -文档 OPTIONS 中的第一个选项,列出重命名前的历史记录,所以我以为他要的是 git log 什么参数都不加就能显示一样的历史 |
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。