Fari

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指针。他们分别指向版本树中的某个版本

注:一般也会将暂存区和工作区也比作两棵树,我更乐意把他们看作两个代码缓冲区,因为他们没有版本的概念。就像两个大水池,你往里面放什么它就装什么,你往里面放哪个版本的代码,他就算是哪个版本。

例如:当你在暂存区存放了一些改动文件后,没有commit而切换到其他分支(git checkout branch),此时暂存区和工作区都会保持不变,只有HEAD指针会移动到新分支的branch指针所指向的版本上,你再commit的话就是将暂存区的代码提交到新分支上了

reset

git reset VERSION

用于同时修改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功能比较混乱,后面又开发出 switchrestore 两个命令分别用来切换分支和恢复文件状态

git checkout VERSION 和 git reset –hard VERSION 命令很像,他们都会移动HEAD指针,修改暂存区和工作区,但它们又有很大不同

git checkout VERSION file 也和 git reset –hard VERSION file相似,只不过也是不移动HEAD指针,暂存区和工作区还是会变的(不管暂存区和工作区是否有未提交的代码)

git拉取远程分支基本过程

Step 1: 首先查看当前分支

git branch

Step 2: 拉取远程分支代码到本地的一个临时分支

git fetch origin main:tmp

注:

file

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参数即为删除

例:

file

(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