零、Git有什么用
1.一个小例子
假如你做了一个小游戏,现在把它命名为“游戏v0.1”,过了几周觉得角色技能设置不是很满意,改名为"游戏v0.2"。
你邀请了你的小伙伴来玩游戏,他提出了一些意见,你进行了修改,于是有了游戏"v0.3"。
同时Git还可以用于团队协作,假如你的朋友想和你一起做这个游戏,你负责功能A,他负责功能B,那你们可能要过段时间用QQ进行文件发送,来得知对方的最新更改。
最终做好了以后,可能还需要手动把功能A和功能B的代码复制粘贴合并一下。两个人况且这么麻烦,如果是稍微大一点的十几个人的团队,就更加麻烦了。于是这时候Git就可以来帮我们的忙了。
2.其他
最早的Git是Linus用C语言编写的,并且只花了休假的不到两周的时间。没错Linus就是那位Linux之父,给大神程序员跪了。
以下内容摘自廖雪峰的官方网站,是我个人觉得比较有用的部分
(1)首先,分布式版本控制系统根本没有“中央服务器”,每个人的电脑上都是一个完整的版本库,这样,你工作的时候,就不需要联网了,因为版本库就在你自己的电脑上。既然每个人电脑上都有一个完整的版本库,那多个人如何协作呢?比方说你在自己电脑上改了文件A,你的同事也在他的电脑上改了文件A,这时,你们俩之间只需把各自的修改推送给对方,就可以互相看到对方的修改了。
和集中式版本控制系统相比,分布式版本控制系统的安全性要高很多,因为每个人电脑里都有完整的版本库,某一个人的电脑坏掉了不要紧,随便从其他人那里复制一个就可以了。而集中式版本控制系统的中央服务器要是出了问题,所有人都没法干活了。
在实际使用分布式版本控制系统的时候,其实很少在两人之间的电脑上推送版本库的修改,因为可能你们俩不在一个局域网内,两台电脑互相访问不了,也可能今天你的同事病了,他的电脑压根没有开机。因此,分布式版本控制系统通常也有一台充当“中央服务器”的电脑,但这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已。
(2)所有的版本控制系统,其实只能跟踪文本文件的改动,比如TXT文件,网页,所有的程序代码等等,Git也不例外。版本控制系统可以告诉你每次的改动,比如在第5行加了一个单词“Linux”,在第8行删了一个单词“Windows”。而图片、视频这些二进制文件,虽然也能由版本控制系统管理,但没法跟踪文件的变化,只能把二进制文件每次改动串起来,也就是只知道图片从100KB改成了120KB,但到底改了啥,版本控制系统不知道,也没法知道。
不幸的是,Microsoft的Word格式是二进制格式,因此,版本控制系统是没法跟踪Word文件的改动的,如果要真正使用版本控制系统,就要以纯文本方式编写文件。
如果没有历史遗留问题,强烈建议使用标准的UTF-8编码,所有语言使用同一种编码,既没有冲突,又被所有平台所支持。
千万不要使用Windows自带的记事本编辑任何文本文件。原因是Microsoft开发记事本的团队使用了一个非常弱智的行为来保存UTF-8编码的文件,他们自作聪明地在每个文件开头添加了0xefbbbf(十六进制)的字符,你会遇到很多不可思议的问题,比如,网页第一行可能会显示一个“?”,明明正确的程序一编译就报语法错误,等等,都是由记事本的弱智行为带来的。建议你下载Notepad++代替记事本,不但功能强大,而且免费。
一、Git安装
详见https://www.runoob.com/git/git-install-setup.html
二、本地命令
1.简单提交
(1)初始化仓库 git init
首先可以创建一个简单的目录,cd到那个目录下,那个目录就是Git仓库所在目录
通过git init
命令把这个目录变成Git可以管理的仓库
当前目录下多了一个“.git”的文件夹,这个目录是Git来跟踪管理文件夹的,没事千万不要手动修改这个目录里面的文件,不然改乱了,就把Git仓库给破坏了。
如果你没有看到“.git”目录,那是因为这个目录默认是隐藏的,用ls -ah
命令就可以看见。如果是windows系统点击“查看”->“显示隐藏的项目”即可。
(2)简单提交 git add && git commit
我们可以在该目录下创建一个叫做reademe.txt的文件,尝试对它进行简单的提交。命令如下
git add readme.txt
git commit -m "wrote a readme file"
#-m后面是本次提交的注释,注释是必须要加的,否则不能commit
为什么Git添加文件需要add
,commit
一共两步呢?因为commit
可以一次提交很多文件,所以你可以多次add
不同的文件。
我平时使用时常常会使用git add .
和git commit -m "xxx"
两个命令,很少会单独一个个手动添加,因为太麻烦了。
“.”用来表示当前目录下的所有文件,因此git add .
可以把当前目录的所有文件都add进去。
(3)删除暂存区文件 git rm
git如何删除已经 add 的文件? 使用 git rm 命令即可,有两种选择,
一种是 git rm –cached “文件路径”,不删除物理文件,仅将该文件从暂存区中删除;
一种是 git rm –f “文件路径”,不仅将该文件从缓存中删除,还会将物理文件删除(不会回收到垃圾桶)。
(4)仓库状态 git status && git diff
我们可以使用git status
了解仓库当前的状态
git diff
顾名思义就是查看difference,显示的格式正是Unix通用的diff格式
2.版本
(1)提交日志 git log
git log
命令显示从最近到最远的提交日志。
你看到的一大串类似1094adb...
的是commit id
(版本号),和SVN不一样,Git的commit id
不是1,2,3……递增的数字,而是一个SHA1计算出来的一个非常大的数字,用十六进制表示。为什么commit id
需要用这么一大串数字表示呢?因为Git是分布式的版本控制系统,后面我们还要研究多人在同一个版本库里工作,如果大家都用1,2,3……作为版本号,那肯定就冲突了。
(2)版本回退 git reset
①git reset
命令用于进行版本回退。
或者说它用于版本跳跃,因为他还可以把回退后的版本变为回退前的版本。
在Git中,用HEAD
表示当前版本,也就是最新的提交1094adb...
(注意我的提交ID和你的肯定不一样),上一个版本就是HEAD^
,上上一个版本就是HEAD^^
,当然往上100个版本写100个^
比较容易数不过来,所以写成HEAD~100
。
其实^和~可以组合起来用,所以甚至你可以写git reset HEAD^~2
,不过这样容易把人弄晕,没什么必要。
git reset有个--hard参数。如果你加了--hard参数,--hard参数会把你当前文件夹中自己的文件也回退了,如果你没有在其他地方备份,那么你这次修改的心血也就没有了。
--hard参数会修改工作区的内容,不加--hard的时候只是操作了暂存区,不影响工作区,--hard一步到位,不加--hard需要分开执行,两步操作。加了--hard参数显然方便许多,但也许会因为粗心造成不必要的损失。
关于什么是工作区我们在之后讲解。
如果你没有commit你的本地修改(甚至于你都没有通过git add追踪过这些文件,当他们被删除,git reset --hard对于这些没有被commit过也没有git add过的修改来说就是具有毁灭性的)
②如果我们想取消回退,用git reflog
查看命令历史,以便确定要回到未来的哪个版本。
然后执行git reset --hard [版本号]
,这里加上--hard参数是有必要的,因为你显然不希望让自己当前文件夹里还是过去的内容,
git reset --hard 10fc
Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD
指针,Git只是在修改这个指针。
版本号没必要写全,前几位就可以了,Git会自动去找。当然也不能只写前一两位,因为Git可能会找到多个版本号,就无法确定是哪一个了。
③暂存区与工作区
工作区(Working Directory)就是你在电脑里能看到的目录。
版本库(Repository)工作区有一个隐藏目录.git
,这个不算工作区,而是Git的版本库。
Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master
,以及指向master
的一个指针叫HEAD
.
我们把文件往Git版本库里添加的时候,是分两步执行的:
第一步是用git add
把文件添加进去,实际上就是把文件修改添加到暂存区;
第二步是用git commit
提交更改,实际上就是把暂存区的所有内容提交到当前分支。
你可以简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。
一旦提交后,如果你又没有对工作区做任何修改,那么工作区就是“干净”的。
为什么Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件。
(3)丢弃工作区修改 git checkout -- [文件名]
git checkout -- [文件名]
可以丢弃工作区的修改,如git checkout -- readme.txt
这里有两种情况:
一种是"readme.txt"自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
一种是"readme.txt"已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。
3.分支
在没有创建新的分支前,我们的所有操作都是在master上进行的。有了新的分支,一个仓库里就可以存在多个版本,而不是由一个版本覆盖另一个版本。
(1)查看本地所有分支 git branch
git branch
命令可以用于查看本地的分支,当前所在的分支会用*标出来。
如果你的仓库关联了远程仓库,需要加上-a参数才能看见远程的分支,即git branch -a
(2)创建分支 git branch [分支名]
git branch [分支名]
用于创建新的分支,如git branch dev
(3)删除分支 git branch -d [分支名]
git branch -d [分支名]
用于删除某个分支。
(4)切换分支 git checkout [分支名]
git checkout [分支名]
可以用于切换到别的分支。
如果你加上-b参数,即git checkout -b [分支名],它会创建并切换到另一个分支。
如git checkout -b dev
相当于git branch dev
和git checkout dev
的缩写。
(5)合并分支 git merge [分支名]
git merge test
等于把当前分支和test分支的内容进行合并。
如果加上--rebase参数,将使用rebase的方法进行合并。两种合并的策略不同,rebase合并比较干净,merge合并比较简单。
关于如何合并,它有自己的算法,可以看看“六、推荐链接”里的“Git原理”和"Git分支合并"。
三.远程仓库相关
实际情况往往是这样,找一台电脑充当服务器的角色,每天24小时开机,其他每个人都从这个“服务器”仓库克隆一份到自己的电脑上,并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交。
1.起步
(1)SSH key
当你想要从远端拉取一个仓库,或者push本地的仓库到远端时,需要验证你的身份,否则什么人都可以修改仓库,是有危险的。
通常会要求你输入用户名和密码,如果你经常对一个仓库进行操作,每次都输入用户名和密码过于麻烦。
就需要设置SSH key来验证你的身份,相当于远端仓库给了你的电脑一个白名单。
关于如何设置SSH key可以自行搜索一下。
(2)克隆仓库 git clone
git clone是最简单的命令,git clone <仓库地址>
可以直接把远程仓库clone到本地,同时被clone的远程仓库会成为这个本地仓库的默认关联远程仓库。
git clone也会把远程分支复制到本地一份,这个分支是不会进行实时更新的,只是被复制到了你的本地而已。
远程分支命名规范通常是<remote name>/<branch name>
远程分支不能直接被修改,当你试图checkout到远程分支后,Git会变成分离HEAD状态(HEAD指向节点而不是分支),所以是无法修改的。
你需要在本地修改了本地的分支,再把本地的分支复制到远程分支上。
关于如何操作,在下文讲解。
(3)克隆仓库指定分支 git clone -b
如果某个仓库有很多条分支,但你只想要其中一条,就可以使用该命令:git clone -b <分支名> <仓库地址>
(4)查看所有分支 git branch -a
加上-a参数可以查看所有分支(包括远程分支)
(5)复制远程分支数据 git fetch
从远程仓库获取数据git fetch
-
从远程仓库下载本地仓库中缺失的提交记录
-
更新远程分支指针(如
o/master
)
git fetch
实际上将本地仓库中的远程分支更新成了远程仓库相应分支最新的状态。
git fetch
并不会改变你本地仓库的状态。它不会更新你的 master
分支,也不会修改你磁盘上的文件。
也就是说git fetch只改变自己本地的远程分支的状态。
(6)关联远程仓库 git remote
如果你使用了git clone命令来克隆远程仓库,会自动建立与被克隆远程仓库之间的关联,可以不进行这一步。
但是如果你自己有个文件夹,通过git init
命令将它变成了一个仓库,想和远程的某个仓库建立联系。
你可以使用如下命令来建立联系:
git remote add origin [仓库地址] #添加某个远程仓库为当前仓库的远程仓库
如果你想换个关联的远程仓库,你可以先删掉当前的。
git remote rm origin #删除与本地仓库关联的远程仓库
这样你就可以继续关联新的远程仓库了。
通过这种方式关联的远程仓库,当你试图进行push操作时,会对你发出警告,并且你无法push。
因为你本地的仓库是一个新仓库,与远程仓库的提交日志并不一致。
你需要先从远程pull一下,与远程仓库进行一个合并,这样你的本地仓库也会有和远程仓库类似的提交日志了,这时候push上去就不会有问题了。
当然如果你头铁(非常不建议),可以加上-f参数执行git push origin master -f
强行push过去。
2.push和pull
(1)拉取分支 git pull
git pull
是 git fetch
和 git merge
两个命令的缩写。
第一步:把远程仓库的最新修改同步到本地的远程分支;
第二步:把本地远程分支和当前分支(或者指定分支)进行合并。
一些常用命令:
git pull origin <远程分支名>:<本地分支名> #将远程指定分支 拉取到 本地指定分支上:
git pull origin <远程分支名> #将远程指定分支 拉取到 本地当前分支上:
git pull #将与本地当前分支同名的远程分支 拉取到 本地当前分支上(需先关联远程分支)
如果git pull origin <远程分支名>:<本地分支名>
的本地分支名留空,会创建一个本地分支。
关联远程分支的方法:git push --set-upstream origin <本地分支名>
(2)推送分支 git push
push操作会修改本地仓库的远程分支,并且会修改远程仓库的远程分支。
常用命令:
#(注意:pull是远程在前本地在后,push相反)
git push origin <本地分支名>:<远程分支名> #将本地当前分支 推送到 远程指定分支上
git push origin <本地分支名> #将本地当前分支 推送到 与本地当前分支同名的远程分支上
git push #将本地当前分支 推送到 与本地当前分支同名的远程分支上(需先关联远程分支)
如果git push origin <本地分支名>:<远程分支名>
的本地分支名留空,会删除这个远程分支。
3.补救措施
(1)回退远程分支版本
这个操作无法在远程分支上直接进行。
我们需要把本地分支回退到上一版本,然后再push到远程。
git reset --hard HEAD~1 #回滚本地分支到上一个版本
git add .
git commit -m "rollback"
git push -f origin <远程分支名> #必须加上-f参数
-f 参数是强制提交,因为reset之后本地库落后于远程库一个版本,因此需要强制提交。
也可以使用revert命令进行回退。revert是放弃指定提交的修改,但是会生成一次新的提交,需要填写提交注释,以前的历史记录都在,而reset是指将HEAD指针指到指定提交,历史记录中不会出现放弃的提交记录。
git revert是复制上一份的记录到某个分支。
具体使用revert还是reset要和团队负责人沟通。
(2)删除远程分支
我经常手贱创建一个名字错误的分支,每次这种时候我都想光速删除它......
git push origin --delete <远程分支名>
四、Git内网代理
暑假和小伙伴一起进行的开发,git仓库在校园网里,但自己的肉身和电脑在家里。于是使用了vmess代理来访问校园内网。
如果公司的仓库在内网里,或者需要对github的仓库加速的(前提是你已经做了前置工作),可以参考一下我的做法。
之前尽管使用了vmess代理,网页可以正常访问校园网,但还是无法拉取git仓库,后来怀疑是git没有走这个vmess代理的缘故。
在命令行输入如下命令即可解决:
#设置代理 git config --global http.proxy http://127.0.0.1:7890 git config --global https.proxy https://127.0.0.1:7890 #取消设置的命令 git config --global --unset http.proxy git config --global --unset https.proxy
请把7890改成自己的使用的软件(clash,v2rayn)进行代理的端口。
上述方法是挂了全局代理,即只要是git操作就通过代理,如果你挂了国外的代理,可能会拉取github很快,拉取国内仓库很慢,这时候最好只对github进行这个设置。
git config --global http.https://github.com.proxy http://127.0.0.1:7890 git config --global https.https://github.com.proxy https://127.0.0.1:7890
五、关于Git的其他问题
1.在自己一个人进行开发的时候,通常习惯性自己在主分支上进行修改。但团队进行开发的时候通常需要新建一个分支,分支名的命名要按照约定写明本次修改的目的,然后Merge过去,现在想想这样是非常合理的,但第一次自己没有注意这一点就闹了笑话。
2.但在团队中,我们使用的是Gitlab的仓库,每次合并到主分支前都需要发一个Merge Request给负责人进行审核。
六、推荐阅读
1.Git分支操作练习 https://learngitbranching.js.org/
2.Git原理https://www.lzane.com/tech/git-internal/
Git分支合并