这里是带你过一遍第一期,我来带你过一遍很出名的Git(廖雪峰版)教程,想要查看前面的笔记请翻阅我的CSDN博客,作者码字不易,喜欢的话点赞,加个关注吧,后期还有很多干货等着你!
初识Git
Git的诞生
2005的时候,linus使用2周时间用C语言写出来了一个Git,供全世界Linux开发者使用
集中式VS分布式
集中式版本控制:版本库是集中存放在中央服务器的,而干活的时候,用的都是自己的电脑,所以要先从中央服务器取得最新的版本,然后开始干活,干完活了,再把自己的活推送给中央服务器。中央服务器就好比是一个图书馆,你要改一本书,必须先从图书馆借出来,然后回到家自己改,改完了,再放回图书馆。
分布式版本控制:是Git使用的版本管理方式;每个人都各自管理版本库,当双方都有修改的时候,就会将自己的修改推送给对方就行了
安装Git
Git官网:https://git-scm.com/downloads
按照对应方式下载就行:Git下载
创建版本库
- 创建一个文件夹,不管你是Linux,Windows,Mac 请使用你新建文件夹的方式,建立一个文件夹(注意:避免莫名其妙的问题,请不要使用中文路径)
- 请打开你系统对应的控制台,(如:Windows请右键选择Git Bash Here)输入:git init(这时候,目录下,会出现一个隐藏文件夹:.git文件夹)
- 这时候,你已经建立了一个空的版本库了
把文件添加到版本库
这里有一个特别注意:
使用Windows的童鞋要特别注意:
千万不要使用Windows自带的记事本编辑任何文本文件。原因是Microsoft开发记事本的团队使用了一个非常弱智的行为来保存UTF-8编码的文件,他们自作聪明地在每个文件开头添加了0xefbbbf(十六进制)的字符,你会遇到很多不可思议的问题,比如,网页第一行可能会显示一个“?”,明明正确的程序一编译就报语法错误,等等,都是由记事本的弱智行为带来的。建议你下载Notepad++代替记事本,不但功能强大,而且免费!记得把Notepad++的默认编码设置为UTF-8 without BOM即可:
- 建立一个文件在刚刚你创建的文件夹中:
- 使用git add 文件名 告诉git,将这个文件加入仓库(这里廖老师说两句搞笑的话:执行上面的命令,没有任何显示,这就对了,Unix的哲学是“没有消息就是好消息”,说明添加成功。)
- 使用git commit -m ‘第一次提交’ 将文件提交到仓库(git commit命令执行成功后会告诉你,1 file changed:1个文件被改动(我们新添加的readme.txt文件);2 insertions:插入了两行内容(readme.txt有两行内容)。)
小贴士:可以一次性多次添加(也就是git add)后,使用commit一次性全部提交到仓库
疑难解答:
Q:输入git add readme.txt,得到错误:fatal: not a git repository (or any of the parent directories)。
A:Git命令必须在Git仓库目录内执行(git init除外),在仓库目录外执行是没有意义的。
Q:输入git add readme.txt,得到错误fatal: pathspec ‘readme.txt’ did not match any files。
A:添加某个文件时,该文件必须在当前目录下存在,用ls或者dir命令查看当前目录的文件,看看文件是否存在,或者是否写错了文件名。
时光穿梭机
版本退回
这里廖老师举了个例子,关于打游戏的:我拿精灵宝可梦红宝石(就是我们看得:宠物小精灵)来举例吧,当你想获得想要的道具,或者想抓捕你想要的小精灵的时候,我们就会不停的存档,读档,一直到获得自己想要的才停止;git也有相对应的方式保存你每一次提交到仓库的状态,git将你每一次commit都保存一个状态,你可以恢复到任意一个commit节点;
游戏中你不需要自己记下来上一次保存的时间,而是直接去存档列表中挑选你回到的位置;
git中也有这么个存档列表
- git log 查看你提交(commit)的历史(如果嫌输出信息太多,看得眼花缭乱的,可以试试加上–pretty=oneline参数:)
这里进一步解释一下,git生成的版本号(commit id)
需要友情提示的是,你看到的一大串类似1094adb…的是commit id(版本号),和SVN不一样,Git的commit id不是1,2,3……递增的数字,而是一个SHA1计算出来的一个非常大的数字,用十六进制表示,而且你看到的commit id和我的肯定不一样,以你自己的为准。为什么commit id需要用这么一大串数字表示呢?因为Git是分布式的版本控制系统,后面我们还要研究多人在同一个版本库里工作,如果大家都用1,2,3……作为版本号,那肯定就冲突了。
在git中,使用HEAD来表示当前版本,也就是最新提交的版本,那么上一个版本就是HEAD^ ,上上个版本是HEAD^ ^ ,那么一百个版本以前是什么 ,肯定不 是HEAD ^ 后面跟100^,而是使用HEAD~100的方式退回到100个版本以前
- 那么现在我们针对这个文件夹进行回退
git reset --hard HEAD^
将版本库返回上一个版本,这时候,我们使用git log来查看版本库的状态;
你会发现,回退的版本不见了,你的存档删除了,当然你现在还没有关掉你的窗口,你还可以用使用
git reset --hard 版本号(commit id 进行回退)
注意这里commit id 可以不用写全 git会自动去寻找
追寻版本回退原理
其实你可以把HEAD想象成一个指针:
┌────┐
│HEAD│
└────┘
│
└──> ○ append GPL
│
○ add distributed
│
○ wrote a readme file
他仅仅是从上一个位置,移动到你指定的位置
┌────┐
│HEAD│
└────┘
│
│ ○ append GPL
│ │
└──> ○ add distributed
│
○ wrote a readme file
然后顺便将你的文件更新了~
那么当你后悔了,但是你已经将窗口关闭了,这时候你已经找不到前面的commit id了你该怎么办:
也是有办法的;可以使用git reflog命令,这个命令记录了你所有的命令,这样你就可以找到对应的commit id了
工作区与暂存区
工作区
也就是你的使用 git init 的文件夹
版本库
你使用git init 后会在文件夹中产生一个.git的文件夹,这个就是版本库,其中最为重要的就是称之为“stage”的暂存区;git创建版本库后,会自动创建master分支,以及指向master的指针HEAD
暂存区
其实是版本库中的一个重要区域,如上图所示,文件会先git add到暂存区
文件提交的步骤
前面讲了我们把文件往Git版本库里添加的时候,是分两步执行的:
- 第一步是用git add把文件添加进去,实际上就是把文件修改添加到暂存区;
- 第二步是用git commit提交更改,实际上就是把暂存区的所有内容提交到当前分支。
因为我们创建Git版本库时,Git自动为我们创建了唯一一个master分支,所以,现在,git commit就是往master分支上提交更改。
你可以简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。
我们可以使用git status 来查看当前工作区中文件的状态
当我们将暂存区的文件进行commit 提交到分支上的时候,再使用git status 的时候 就会是空的;你会看到:
On branch master
nothing to commit, working tree clean
管理修改
这里就是一个需要理解的小概念:git管理的是修改,而非文件
当你这样执行修改一个文件后:第一次修改 -> git add -> 第二次修改 -> git commit
git只会将第一次修改去进行提交到版本库,git只会去跟踪你的修改,文件变化是没有监视的,也就是说没有提交到暂存区的文件,可能会与真实文件不一致,这大大提升了协同工作;
撤销修改
这里就是针对你犯错误的时候,请认真观看
我这边分为以下几种情况:
- 当你写错了东西,但是你没有git add也没有git commit 的情况下:
那么恭喜你了!你发现的很早,你现在只需要把错误修改了,神不知鬼不觉的提交就好了,
或者使用git checkout – 文件名 就可以回到和版本库中一样的文件了(这个文件,全部会覆盖嗷)
- 当你写错了东西,但是你使用git add已经提交到暂存区了,但是没有使用git commit提交到版本库中
git reset HEAD 以把暂存区的修改撤销掉(unstage),重新放回工作区:
- 当你写错了东西,上一次的暂存区里有你这个文件,你还没 git add 也没有 git commit
使用git checkout – 文件名 就可以回到和暂存库中一样的文件了(这个文件,全部会覆盖嗷)
- 当你写错了东西,但是你使用git add已经提交到暂存区了,也使用git commit提交到版本库中了
那么直接使用 git reset --hard HEAD^撤回到上一个commit就行了(这个文件,全部会覆盖嗷)
- 当你提交到远程了
你就真的惨了……
特别注意
git checkout – file命令中的–很重要,没有–,就变成了“切换到另一个分支”的命令,我们在后面的分支管理中会再次遇到git checkout命令。
删除文件
使用git rm 删除暂存区的文件
- 如果你是真的想删除,请使用git rm 或者git add 后使用git commit删除版本库
- 如果是误删,请使用git checkout – test.txt将文件夹恢复到与版本库一致
远程仓库
先有本地仓库,再创建远程仓库
创建远程仓库:不管你是什么平台(GitHub,GitLab,Gitee)
创建完成后你会获得一个连接:git@*******/.git**
这时候,你在你的版本库中执行
git remote add origin git@*******/**.git
这时候相当于连接上了远程仓库:这时候你的远程仓库的名字就是origin,这是git默认对于远程版本库的叫法
下一步就是将你的本地库推送到远程库上:
git push -u origin master
由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
这里贴上一个小问题;以便大家以后遇到排查
我也遇到了fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists.的问题
廖老师是通过 [email protected]:michaelliao/learngit.git 关联的,我在win7和win10上测试推送时都不能成功,https的才可以。如果已经用git@关联,则可以在.git目录下的config文件中,把 url = 后面的内容改为https类型的即可。 https类型的格式为: $ git remote add origin https://github.com/daoke0818/testGit2.git
另外我发现:
如果在第一步中创建时已经初始化过项目,则这时会提醒 error: failed to push some refs to ‘https://github.com/daoke0818/testGit2.git’ hint: Updates were rejected because the remote contains work that you do hint: not have locally. This is usually caused by another repository pushing hint: to the same ref. You may want to first integrate the remote changes hint: (e.g., ‘git pull …’) before pushing again. hint: See the ‘Note about fast-forwards’ in ‘git push --help’ for details.
因为远程库中已经存在readme文件了,所以需要先pull下来。命令如下: $ git pull origin master
这时又会报错: From https://github.com/daoke0818/testGit
branch master -> FETCH_HEAD fatal: refusing to merge unrelated histories 说这两个库有不相干的历史记录而无法合并,这时我们可以加上一个参数 --allow-unrelated-histories 即可成功pull: $ git pull origin master --allow-unrelated-histories
但是这时会可能会提示必须输入提交的信息,默认会打开vim编辑器,先按 i 切换到插入模式,写完后 Esc→:→wq 即可保存退出编辑器。如果不进入vim编辑器,则会自动生成一个合并代码的commit。然后再使用前面的命令push将本地提交推送到远程仓库。后面如果本地还有commit,就可以直接用 git push origin master 推送。
先有远程仓库,再创建本地仓库
使用git clone git@*******/**.git 可以直接从远程仓库克隆一个本地库
如果有多个人协作开发,那么每个人各自从远程克隆一份就可以了。
你也许还注意到,GitHub给出的地址不止一个,还可以用https://github.com/michaelliao/gitskills.git这样的地址。实际上,Git支持多种协议,默认的git://使用ssh,但也可以使用https等其他协议。
使用https除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用ssh协议而只能用https。
分支管理
创建与合并分支
先解释一下分支的概念
在版本回退里,你已经知道,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master分支。HEAD严格来说不是指向提交,而是指向master,master才是指向提交的,所以,HEAD指向的就是当前分支。
一开始的时候,master分支是一条线,Git用master指向最新的提交,再用HEAD指向master,就能确定当前分支,以及当前分支的提交点:
每次提交,master分支都会向前移动一步,这样,随着你不断提交,master分支的线也越来越长。
当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上:
你看,Git创建一个分支很快,因为除了增加一个dev指针,改改HEAD的指向,工作区的文件都没有任何变化!
不过,从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变:
假如我们在dev上的工作完成了,就可以把dev合并到master上。Git怎么合并呢?最简单的方法,就是直接把master指向dev的当前提交,就完成了合并:
所以Git合并分支也很快!就改改指针,工作区内容也不变!
合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下了一条master分支:
下面我使用git命令进行操作
- 首先,我们创建dev分支,然后切换到dev分支:
git checkout -b dev - git checkout命令加上-b参数表示创建并切换,相当于以下两条命令:
git branch dev
git checkout dev - 然后,用git branch命令查看当前分支:
git branch - 修改文件并添加到暂存库
git add 文件 - 添加到版本库库
git commit -m ‘提交文件’ - 切换回到master
git checkout master - 合并master和dev
git merge dev - 完成合并master和dev后,删除dev分支
git branch -d dev
注意这些点
对于所有分支而言, 工作区和暂存区是公共的。
分支修改后没有提交是无法切换到另一分支的
$ git checkout master
error: Your local changes to the following files would be overwritten by checkout:
README.md
Please commit your changes or stash them before you switch branches.
但是我看到有些人是可以违规切换到其他分支的,不太清楚这是怎么操作的
解决冲突分支
当你处理分支冲突的时候,合并后使用git会告诉你哪些文件冲突了,你可以打开这些冲突文件,你会看到git已经帮你预处理了一下
比如:
Git tracks changes of files.
<<<<<<<HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>>dev
这时候你就明白上面的是现在指向的分支内容,下面是你提交的分支内容
你只需要将这个文件处理一下
比如你想保留你在dev分支的内容:
Git tracks changes of files.
Creating a new branch is quick AND simple.
这时候你再提交,冲突已经被你解决了
以下为流程图:
如上图,你解决后的文件被提交到下一个commit节点上去了
分支管理策略
这里提到一个概念:
git会默认使用Fast forward模式,这种模式会丢掉分支信息
我的理解:在你合并后,查看分支历史git log,你只会看到你当前分支的信息
但是当你merge的时候添加–no-ff参数,就会保留你分支的信息
比如:git merge --no-ff -m “merge with no-ff” dev
这时候git log;你还可以在这个分支上,看到dev的分支历史
在实际开发中,我们应该按照几个基本原则进行分支管理:
- 首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
- 那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
- 你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
- 所以,团队合作的分支看起来就像这样:
Bug分支
这一节点主要讲述的内容是,当你正在开发dev分支,但是突然需要你修改一个bug,但是你目前的改动还不能提交(commit),怎么办?
可以使用git stash将现在工作区的操作全部保存下来
这时候你修改完成bug后:回到dev分支,可以使用git stash list查看你的保存的状态
工作现场还在,Git把stash内容存在某个地方了,但是需要恢复一下,有两个办法:
- 一是用git stash apply恢复,但是恢复后,stash内容并不删除,你需要用git stash drop来删除;
- 另一种方式是用git stash pop,恢复的同时把stash内容也删了:
当你多次使用git stash想要恢复到指定的状态时
可以使用git stash apply { {stash id}}
也可以回到你开发时的状态
注意,当你在其他分支进行提交,但你又不想将整个分支都合并到你现在发分支:可以使用git cherry-pick 命令
真实场景:在主分支上修改bug,提交后,你的dev上分支肯定没有修改好这个bug,但你又不想将主分支上的代码全部都移交过来,这时候你就可以使用git cherry-pick 只将主分支的这次提交合并过来;注意,这里的commit id指的是你想要合并过来的操作的commit id
未来分支
这一节点主要讲:如果要丢弃一个没有被合并过的分支,可以通过git branch -D 强行删除。
多人协作
这里,本人使用idea系列产品和vscode并没有遇到特别多的困难
我阐述一下多人协作的模式:
因此,多人协作的工作模式通常是这样:
-
首先,可以试图用git push origin 推送自己的修改;
-
如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;
-
如果合并有冲突,则解决冲突,并在本地提交;
-
没有冲突或者解决掉冲突后,再用git push origin 推送就能成功!
如果git pull提示no tracking information,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to origin/。
这就是多人协作的工作模式,一旦熟悉了,就非常简单。
标签管理
使用git tag 就可以打一个新标签:
可以用命令git tag查看所有标签:
用命令git show 可以看到说明文字:
命令git push origin 可以推送一个本地标签;
命令git push origin --tags可以推送全部未推送过的本地标签;
命令git tag -d 可以删除一个本地标签;
命令git push origin :refs/tags/可以删除一个远程标签。
命令git push origin --delete 也可以删除一个远程标签。
我对于标签的理解:其实就是针对特定节点的一个记录,类似于app的版本号,标签创建完成后,使用git show tagname就可以看到对应的commit id还有相关提交人信息,其实可以理解成时标签绑定到一个commit id之上