浅记录下最近才遇到要使用的git命令吧
worktree
之前开发节奏慢的时候,一般都是在同一个目录下哐当哐当干活,然后commit,完事了push,创PR,最多来个revert, rebase, merge都算复杂的了。可是,在有了AI之后,很多需求都是提的很急,开发节奏太快了,有时候甚至需要同时进行多个功能的开发,这种时候,AI在那边说“我太懂你的感受了”的时候,我一般会在旁边得闲饮茶先吗?nonono,当然是想着能不是同时开多个线程干活啦(后期请在此插入一头热爱工作的牛马.jpg)。但有时候啊有时候,会遇到两个功能需要在不同的环境/上下文下开发,也就是不同分支啦,那么怎么办呢,这个时候就要有请我们的git影分身之术——worktree登场啦。
worktree简单来说就是在本地创建了不同的目录,然后分别可以check到一个分支上,但worktree不能check到同一个分支,worktree是共享同一个.git目录的,新建worktree会一并携带所有被分支追踪的文件,也就是说像.env这样的文件不会一并本复制过去。.git文件存在的目录我叫它主worktree,至于你问我删除主worktree会怎样?这你还是问豆包吧,她比我懂。
然后下面是一些常用命令:
创建新鲜的worktree
git worktree add <目标目录> [分支名]
列出所有worktrees
git worktree list
删除worktree
git worktree remove <绝对路径>
不想让人知道的小秘密?试试这些!
我们从学走路的那天开始就被传授了.gitignore的使用方法,这个文件可以让我们忽略一些不必要提交的内容,但这有时候还是稍显乏力的,看看下面这几种情况:
已经追踪的文件,怎么忽略?
这里就得先泼一盆冷水了:.gitignore只能拦住还没被追踪的新文件,对于已经进过历史的文件,你把它加进.gitignore,Git只会翻个白眼继续追踪,毕竟人家都拿到编制了,哪能你说开就开。
既然不能直接封杀,那就先“劝退”再拉黑:
把文件从追踪列表里移除,但保留本地文件(--cached就是保命的关键):
git rm --cached <文件名>
提交之后它就恢复自由身了,这时再写进.gitignore就能正常拦截。
不想改.gitignore,要怎么忽略?
有时候你只是想在自己电脑上悄悄忽略几个东西——比如只会出现在你这台机器上的临时文件、个人的编辑器配置啥的——但这点私人恩怨实在不好意思写进团队的.gitignore里恶心大家。别急,Git偷偷留了个后门:
.git/info/exclude
打开一看,语法和.gitignore一模一样,写进去的规则只在本地生效,永远不会被提交到仓库里。它就是属于你一个人的.gitignore,低调、隐秘、不影响队友,简直是社恐福音。
废话开始(快进到废话结束)
哦,对了。(这一段有很多废话,请在家长陪同下阅读或酌情跳过)突然想到个事儿,就是吧,诶啧啧,咋说呢,嗨,好吧,我行我说会儿啊,嗯好,我呢,之前就那个啥,有个项目套娃嘛不是,哦更专业一点说应该是submodule,来,我吧,在这个submodule里边儿想要修改.git/info/exclude,嘿~您猜怎么着
答案揭晓:我兴冲冲地点开那个.git,发现它压根儿就不接待我——那根本不是一个目录,只是一个文件,里面孤零零躺着一行字:
gitdir: ../../.git/modules/my-submodule
一个平平无奇的路径,却像一张抄着远方地址的字条。得,本来只想安安静静改个文件,现在被迫开启寻址之旅了。既然都这样了,那就请各位系好安全带,本博客年度文学栏目正式开播:
《门后》
那年他接到任务:去修改一座城市的名册。地图上标得清清楚楚——就在城墙边上,门牌号.git。
可他到了地方才发现,那里根本没有门,只有一块薄薄的牌子,上面抄着一个远方的地址。
行吧,寻人启事变成了寻址启事。他按图索骥,穿过一层又一层的目录,像剥一颗不知道有没有芯的洋葱,每推开一扇门都在心里祈祷:这次总该是真的了吧?
终于,最后一扇门后面不再有字条,而是一座安静的地下城:objects是一座按内容寻址的巨型仓库,同样的记忆全宇宙只存一份;refs是一排指向不同时间线的路标;logs收着全城的监控录像带。而那本名册info/exclude,正安静地摊在角落的小桌上,扉页写着一行小字:
“本册只在本地生效,永不外传。”
改完名册离开时,他忍不住问守门人:为什么要把城的真实地址写在一张纸条上?
守门人说:孩子,城是活的呀。它哪天搬家了,也只需要换一张纸条而已。
——好,文学环节到此为止,观众请有序退场,接下来是人话总结时间。说人话就是:submodule里的.git不是目录而是个文件,里面用一行gitdir:记录了真身的位置,我的名册其实一直好好住在主仓库的.git/modules下面。跟这种“门后只有纸条”的迷宫斗智斗勇纯属浪费生命,不如直接让Git亲自带路:
cd $(git rev-parse --git-dir)
rev-parse --git-dir会现场算出当前仓库.git真身的所在位置,不管你在第几层子目录里摸鱼,是在worktree分身里打工,还是套娃套到怀疑人生的submodule里迷路,这条命令都能把你准确空降到你想去的那座城门口,从此告别对着一堆相对路径玩解谜的玄学生活。
废话结束
而如果连这么一个文件都不想留——毕竟它虽然不会被提交,但明晃晃躺在.git目录里,谁翻谁知道——那还有更阴险的:跳过一切配置文件,直接对着索引下手:
git update-index --skip-worktree <文件名>
这招等于给文件贴上“我的版本我说了算”的封条:不管你在本地怎么改,status和diff都会假装无事发生。不过远端哪天真更新了这个文件,pull的时候它会当场翻脸,到时候用--no-skip-worktree再把人家请回来就好。
比它还要再阴险一档的,是它的兄弟命令--assume-unchanged:
git update-index --assume-unchanged <文件名>
效果立竿见影:你随便折腾这个文件,Git全程睁一只眼闭一只眼。但这招本意是个性能优化工具,告诉Git“这种大文件就别费劲检查了”,所以承诺非常轻飘飘——切换分支、rebase之类的操作都可能让它悄悄失效,等哪天你发现“我明明改了代码怎么一点反应都没有”的时候,恭喜你解锁了排查半天的灵异事件。
顺手记个查案技巧,怀疑有文件在装死的时候:
git ls-files -v | grep '^[a-z]'
列出来的都是被开了装的选手,事后记得用--no-skip-worktree或--no-assume-unchanged把它们一个个捞回来,别让它们永远活在平行时空里。
发明天才的人真是个cherry-pick
回到开头的故事:多线程干活的时候,另一个分支上恰好有一颗commit是你当前分支急缺的灵丹妙药,merge会把人家一整桌菜都端过来,rebase又动静太大,可你只是想吃那一颗樱桃而已。这时候cherry-pick就像自助餐里的夹子:对不起,我只夹这一块。
它的作用简单粗暴:把指定的commit“复制”一份应用到当前分支,并生成一个崭新的commit。注意是复制不是搬家,原分支上的那颗樱桃还完好无损。
摘一颗尝尝:
git cherry-pick <commit-hash>
只想拿内容不要新的commit记录(应用后停在暂存区):
git cherry-pick -n <commit-hash>
一次夹一把(A^..B表示含A到B的连续区间):
git cherry-pick <hash1> <hash2> <hash3>
git cherry-pick A^..B
当然,天上不会掉好摘的樱桃,冲突该来还是会来,处理完之后:
git cherry-pick --continue # 解决了,继续摘下一颗
git cherry-pick --abort # 溜了溜了,当无事发生
所以说常用常新,以前觉得这辈子都用不上它,现在天天开口就是“这颗熟了我先摘走了啊?”