Git
gitattributes
简介
该配置文件可以控制git各种命令的行为。例如执行git diff时是否对比二进制文件内部差异,亦或者使用自定义的diff方法去对比文件,例如控制git如何去merge两个文件,还有个最常用的应用场景就是对换行符的处理,下面会提到。
.gitattributes
文件的配置形式为:
# 要匹配的文件 属性1 属性2 ...
pattern attr1 attr2 ...
例如:
* text=auto
# 文件的行尾自动转换。如果是文本文件,则在文件入Git库时,行尾自动转换为LF。如果已经在入Git库中的文件的行尾是GRLF,则文件在入Git库时,不再转换为LF。
*.txt text
# 对于.txt文件,标记为文本文件,并进行行尾规范化。
*.jpg -text
# 对于`.jpg`文件,标记为非文本文件
*.vcproj text eol=crlf
# 对于.vcproj文件,标记为文本文件,在文件入Git库时进行规范化,行尾转换为LF。在检测到出工作目录时,行尾自动转换为CRLF。
*.sh text eol=lf
# 对于sh文件,标记为文本文件,在文件入Git库时进行规范化,即行尾为LF。在检出到工作目录时,行尾也不会转换为CRLF(即保持LF)。
*.py eol=lf
# 对于py文件,只针对工作目录中的文件,行尾为LF。
*.bat text eol=crlf
# 无格式的文本文件,保证 Windows 的批处理文件在 checkout 至工作区时,始终被转换为 CRLF 风格的换行符;
*.bin binary
# bin文件为二进制文件处理
同gitignore一样,第一列为需要匹配的文件,可以为目录,当为目录时就是匹配该目录下所有的文件(不会递归匹配二级目录下的文件,这点和gitignore不同)
右边则为属性,例如上面配置文件中,text、eol、binary 均为属性,所有的属性都有4个状态:set、unset、unspecified、set a value
当为set状态时,则会设置该属性的值为true,例如上文中的 text 或 binary,该属性为true则会将匹配的文件视为text/binary文件处理。与之相反的状态为unset(视为text类型就能处理换行符,也就是下面的eol属性会生效,如果为binary则eol就不会生效)
当为set a value 状态时,则会使用该值,例如上文中的 eol,它用来配置如何处理文本文件的换行符,当它设置为lf时,它就会保证在git仓库中的文件结尾都为lf(如果原本时crlf则在提交到版本库时也会自动转成lf)
gitattributes提供了很多属性可以设置,其中就包含例如 text、diff、merge等,具体参见官方文档
git删除误提交文件
问题描述
今天向github提交代码时提示有大于100M的文件,无法完成push(github规定单文件大于100M无法提交)
错误信息指出,项目中存在一个 xxx.psd 的文件是127.33M,大于100M了。该文件是不应该放在项目中的,于是我删除了该文件,再次push还是不行,原因是该文件已经加入到了版本库中,单纯从工作区中删除是不行的。
解决方法
删除 .git 文件,基于现有代码重新创建版本库
该方法比较简单,适用于不会有任何历史包袱的项目。
rm -rf .git
git init
使用 git filter-banch 重写版本历史
简单介绍
该命令允许你修改git的提交历史,你可以删除已经提交而又不想提交的文件(例如误提交的,或者私钥等信息),也可以运行 perl 脚本进行历史提交信息的修改。修改后的历史版本将不再和之前的版本兼容,这意味着你只能强行push(-f 参数),若同时别人也在远程分支的基上进行开发,就会造成严重冲突。慎用
官方文档:https://git-scm.com/docs/git-filter-branch
官方不建议使用该命令,因为它可能会带来一些不可预知的问题,更建议使用 git filter-repo
删除误提交部分
git filter-branch --tree-filter 'git rm -f --ignore-unmatch {filename}' HEAD
若某些版本中不包含要删除的文件,则应该使用 -f 和 –ignore-unmatch 参数,否则会报错
最后需强制push
git push origin main -f
git rebase修改提交历史
rebase命令有很多功能,常用的就是通过变基合并代码,即让某个版本中的commit插入其他版本的commit。
使用 git rebase -i {v}
可以使用交互模式修改历史提交,例如在某次提交中,我错误地提交了某个大文件
可以看到该commit是倒数第二次提交,所以我可以如下方法删除该次提交
# HEAD~3 指的是最近三次提交
git rebase -i HEAD~3
输入该命令后就会进入一个交互页面,一般是一个vim编辑器页面
其上方非注释部分就是我近3次提交的内容,根据下面的注释,我可以选择对历史提交进行各种操作,在此,我选择删除第二次提交,将其 pick 修改为 d 即可,然后保存退出
开启clash代理git访问github还是很慢
如何开启
可能是因为git没有走代理,可做如下设置
# 若希望只对单个项目生效则不要 --global
git config --global http.proxy "socks5://127.0.0.1:7890"
git config --global https.proxy "socks5://127.0.0.1:7890"
取消代理
git config --global --unset http.proxy
git config --global --unset https.proxy
其中 7890
是clash设置的代理端口
或者只针对github单独配置(因为也可能使用其他的git远程仓库,如gitee)
# 添加代理
git config --global http.https://github.com.proxy socks5://127.0.0.1:7890
# 取消代理
git config --global --unset http.https://github.com.proxy
如果想对单个项目使用代理,则去除上述命令中的 --global
即可
如何验证
关闭clash,再进行提交或拉取代码,若设置了代理则会提示拒绝连接
开启clash后再次提交发现没问题了就说明代理设置成功了
还是没效果?
不知道你们什么情况,我根据上述方法测试后发现,即使确定使用了代理也经常会遇到dns解析或超时等问题,新的解决方案是使用 socks5h
代理,这里的 h 即 host,表示域名解析也走该代理(socks5a
也是一样),否则就是使用本地配置的dns进行解析。
git config --global http.proxy socks5h://127.0.0.1:7890
git config --global https.proxy socks5h://127.0.0.1:7890
或
git config --global http.https://github.com.proxy socks5h://127.0.0.1:7890
CMD怎么配置
在需要设置代理的cmd窗口输入命令
git rebase VS merge
代码从commit init处分为了两个分支,branch1和branch2,它们上面分别有各自的commit,c1、c2 和 c3、c4
此时切换到 branch1 处,并执行 git rebase branch2
就会这样:
大概就是:将要rebase分支上的提交作为当前分支的基,然后将当前分支上的提交都生成一个新的提交建立在新的基上。所以rebase被称为 变基,改变了当前分支所有commit的基础代码
而merge则是
看到并没有创建新的commit(即 commit ID 是对应一样的),并且是基于当前分支的commit所做的修改
试想一下,生产环境中的main分支应该使用merge还是rebase来合并bugfix分支上的代码呢?
个人感觉应该使用merge,因为它更符合人的直观感觉,它是一种线性的、追加形式的。代码出了问题或者有新的功能添加,它应该就是以追加的形式添加代码的
至于说能不能用rebase,当然也是可以的,merge和rabase都是代码合并的手段,其最终效果还是一样的
rebase不仅可以用来合并代码,还能加上 -i 参数可视化修改提交,是一个功能非常强大的命令
git回滚操作
图来源:https://blog.osteele.com/2008/05/my-git-workflow/
git中有一棵树、两个区、两类指针
一棵树:版本树,它将代码的每个版本打包串起来,某些版本可能会有分支,他们就共同构成一棵树
两个区:工作区和暂存区
-
工作区:就是你当前项目目录
-
暂存区:版本树是一个大仓库,但我并不希望每次代码改动都做成一个版本提交到版本树中,而是希望按照实现的功能将多次改动共同作为一个版本提交,这个暂存区就是用来存储你的多次改动的地方
两类指针:HEAD指针和branch指针。他们分别指向版本树中的某个版本
-
HEAD指针:你对代码的所有操作总是依赖于版本树中的一个版本的,而HEAD指针指的就是你当前工作的那个版本
-
branch指针:你的代码有多个分支,每个分支总会有一个版本状态,branch指针就是说当前版本树的哪个节点表示这个分支
注:一般也会将暂存区和工作区也比作两棵树,我更乐意把他们看作两个代码缓冲区,因为他们没有版本的概念。就像两个大水池,你往里面放什么它就装什么,你往里面放哪个版本的代码,他就算是哪个版本。
例如:当你在暂存区存放了一些改动文件后,没有commit而切换到其他分支(git checkout branch),此时暂存区和工作区都会保持不变,只有HEAD指针会移动到新分支的branch指针所指向的版本上,你再commit的话就是将暂存区的代码提交到新分支上了
reset
git reset VERSION
用于同时修改HEAD指针和branch指针指向代码树中的版本(只要树中有的版本都可以指),你可以使用参数设置它是否要级联修改暂存区和工作区的代码
-
--soft:只修改HEAD和branch指针。
此时暂存区和工作区保持不变。就是回滚到add之后,commit之前的状态
注:暂存区在commit之后并不会清空,而是保留commit时的代码快照 -
--mix(默认):修改HEAD&branch指针和暂存区代码。
即将暂存区的代码修改为HEAD新指向的版本代码。即回滚到add之前的状态 -
--hard:修改HEAD&branch指针和两个区的代码
使用该命令要注意,因为它会修改工作区的代码
git reset VERSION files
你可以选择只回滚部分文件到某个版本,但如此一来一个项目中的文件就可能属于不同版本了,而一个HEAD指针显然不可能指向多个版本
故当你只回滚部分文件时,HEAD指针并不会改变
除了HEAD指针不变外,其他还是一样的(即暂存区和工作区的级联改变)
git revert
git revert commitid
reset是将HEAD指针和branch指针重新指回之前的某个commit,而在该commit之后的commit都不要了
revert是指定回滚某一个commit,它只会回滚这一个commit中的代码,并且提交一个新的commit到版本库
reset的指针是往后走的,而revert的指针是往前走的
如果你要revert的某个commit与其后面的一些commit修改了相同的文件则会产生冲突,此时执行revert命令后,这些有冲突的文件就会有两个版本的代码,需要你手动进行处理,你可以使用 git revert –abort来撤销revert
checkout
git checkout 有两个作用,一是切换分支(checkout [ version|branch ]),二是恢复工作区文件状态(checkout files)
注:由于checkout功能比较混乱,后面又开发出 switch
和 restore
两个命令分别用来切换分支和恢复文件状态
git checkout VERSION 和 git reset –hard VERSION 命令很像,他们都会移动HEAD指针,修改暂存区和工作区,但它们又有很大不同
- 它只移动了HEAD指针而没有移动branch指针(若checkout的是某个版本而不是分支,则会出现branch指针和HEAD指针指向两个不同的版本,这就是指针分离)。你可以在指针分离的情况下修改代码创建新的branch,防止污染了原来的branch
git checkout VERSION file
也和 git reset –hard VERSION file相似,只不过也是不移动HEAD指针,暂存区和工作区还是会变的(不管暂存区和工作区是否有未提交的代码)
git拉取远程分支基本过程
Step 1: 首先查看当前分支
git branch
Step 2: 拉取远程分支代码到本地的一个临时分支
git fetch origin main:tmp
注:
-
git中的 origin 只是远程仓库地址的别名,也可替换为其真正的地址。使用 git remote -v 查看。
-
一般来说 对于不会用的命令,在其后面加上 –help 即可打开文档,翻到文档最下面一般有详细示例。
Step 3: 查看不同
git diff --stat tmp
# --stat 参数只显示不同文件名,否则会将修改的代码都显示出来
git diff
默认显示工作区和暂存区的不同
git diff --staged
显示暂存区和HEAD的不同
git diff commit1 commit2
显示commit2相对与commit1的变动,也能将commitID替换成branch
git diff commitID
显示当前工作区对比指定版本的变动,也能指定分支
Step 4: 合并代码
git merge tmp
此时如果有冲突会有提示,执行 git merge --abort
即可回退合并,该命令只有在合并完成之前有效,即如果没有冲突,则直接自动合并完成。
Step 5: 删除临时分支
git branch -d tmp
# -d参数即为删除
例:
(git) origin main VS origin/main
首先要知道的是,本地实际上有两个版本库,一个是你本地正在使用的版本库,另一个是远程版本库的副本(默认使用origin作为远程仓库的别名,可以使用 git remote -v
查看)
所以 git fetch
的作用就是同步远程版本库,否则,即使远程版本库修改了,你不主动fetch的话,本地远程版本库的副本还是原来的样子
而本地的远程版本库副本的分支名就是 origin/main
(如果远程分支中有一个分支名为 main
)
所以,对于合并远程代码的操作可以是:
# 更新本地的远程版本库副本
git fetch origin main
# 合并副本中的main分支(此时还是本地的两个版本库进行合并)
git merge origin/main
所以,总的来说,如果是对远程仓库的操作,例如 push pull fetch 等就是用 origin main,如果是对远程仓库分支的操作,例如 merge rebase 等就是用 origin/main