目录
初始化一个Git仓库
添加文件到Git仓库,分为两步
小结
查看状态和文件的修改信息
小结
版本回退
git log查看修改和提交的日志信息
版本回退这里可以使用命令
小结
工作区和暂存区
小结
管理修改
小结
撤销修改
小结
删除文件
小结
添加远程库
小结
从远程库克隆
小结
分支管理
创建与合并分支
命令
合并分支
删除分支
switch
小结
解决冲突
小结
分支管理策略
合并时禁止快速合并模式
小结
bug分支
描述和说明
小结
参考
初始化一个Git仓库
使用Git init命令
先创建一个目录用来作为一个仓库,使用git init把目录变成Git可以管理得仓库
$git init
Initialized empty Git repository in /Users/michael/learngit/.git/
添加文件到Git仓库,分为两步
第一步,用git add告诉Git,把文件添加到仓库,可以一次性添加多个文件
$git add readme.txt
第二步,用命令git commit告诉Git,把文件提交到仓库
$ git commit -m "wrote a readme file"
[master (root-commit) eaadf4e] wrote a readme file1 file changed, 2 insertions(+)create mode 100644 readme.txt
简单解释一下git commit
命令,-m
后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义的,这样你就能从历史记录里方便地找到改动记录。
首先这里再明确一下,所有的版本控制系统,其实只能跟踪文本文件的改动,比如TXT文件,网页,所有的程序代码等等,Git也不例外。版本控制系统可以告诉你每次的改动,比如在第5行加了一个单词“Linux”,在第8行删了一个单词“Windows”。而图片、视频这些二进制文件,虽然也能由版本控制系统管理,但没法跟踪文件的变化,只能把二进制文件每次改动串起来,也就是只知道图片从100KB改成了120KB,但到底改了啥,版本控制系统不知道,也没法知道。
不幸的是,Microsoft的Word格式是二进制格式,因此,版本控制系统是没法跟踪Word文件的改动的,前面我们举的例子只是为了演示,如果要真正使用版本控制系统,就要以纯文本方式编写文件。
使用Windows的童鞋要特别注意:
千万不要使用Windows自带的记事本编辑任何文本文件。原因是Microsoft开发记事本的团队使用了一个非常弱智的行为来保存UTF-8编码的文件,他们自作聪明地在每个文件开头添加了0xefbbbf(十六进制)的字符,你会遇到很多不可思议的问题,比如,网页第一行可能会显示一个“?”,明明正确的程序一编译就报语法错误,等等,都是由记事本的弱智行为带来的。建议你下载Visual Studio Code代替记事本,不但功能强大,而且免费!
为什么Git添加文件需要add
,commit
一共两步呢?因为commit
可以一次提交很多文件,所以你可以多次add
不同的文件,比如:
$ git add file1.txt
$ git add file2.txt file3.txt
$ git commit -m "add 3 files."
小结
初始化一个Git仓库,使用git init命令
添加文件到Git仓库,分两步:
1、使用命令git add <file1> <file2>可以添加多个文件一次性
2、使用命令git commit -m <message>完成,其中message是指提交时的描述信息,帮助你知道这次提交做了什么修改
查看状态和文件的修改信息
如果你想要随时掌握工作区的状态,使用git status命令
如果git status告诉你有文件被修改过,用git diff可以查看修改内容
提交修改和提交新文件是一样的步骤,也是分为两步,第一步是把文件add进仓库中,第二步是commit提交
这里上面输出告诉我们,当前没有需要提交的修改,而且工作目录是干净的。
小结
要随时掌握工作区的状态,使用git status命令
如果git status告诉你有文件被修改过,用git diff可以查看修改的内容
版本回退
前面的命令是和修改文件后进行添加和提交的,如果不断对文件进行修改,就好比玩游戏存档一样,当你觉得文件修改到一定程度之后,可以“保存一个快照“,这个快照在Git中被称为commit。一旦把文件改乱了,或者误删了文件,可以从最近的一个commit恢复,然后继续工作。
git log查看修改和提交的日志信息
git log
命令显示从最近到最远的提交日志,我们可以看到3次提交,最近的一次是append GPL
,上一次是add distributed
,最早的一次是wrote a readme file
。
如果嫌输出信息太多,看得眼花缭乱的,可以试试加上--pretty=oneline
参数:
上面的很长一串的数字序列是commit id(版本号)
其实也可以用git GUI可以明显的看到一些相关的Git版本信息
其中parent其实就是当前版本的上一个版本,它的child其实就是当前版本的下一个新版本。
版本回退这里可以使用命令
git reset --hard HEAD^//这里HEAD表示当前版本,也就是最新的版本,上一个版本就是HEAD^,
上上一个版本就是HEAD^^,如果你想回到上一100个版本命令不可能输入
HEAD^^^.....,这时候使用的是HEAD~100
这里的--hard其实就是会把暂存区的恢复到工作区中,git reset HEAD^
只是把版本库回退到了暂存区,但是工作区的文件修改还是保留着,这时
使用git status会提示你未暂存提交的更改
注意:这里需要注意的是,回到上一个版本容易但是要想再回来就比较难了,因为这里其实可以和C语言的指针作为理解,Git的版本回退速度非常快,因为在Git内部有个指向当前版本的HEAD指针(头指针(个人理解)),当你回退版本的时候,Git仅仅是把HEAD从指向当前版本指向到上一个版本号,但是新版本的没有保存,相当于指针丢失了(野指针)。
这时候要回到最新版本时,怎么解决呢?好比你从21世纪坐时光机来到了19世纪,想再回去已经回不去了怎么办?
//办法就是只要上面的命令行窗口没有关掉,并且你之前使用过git log
显示版本号信息,这时候你可以找到你要恢复的最新版本的号,这里你可以
使用类似的回退命令git reset --hard 94ad(这里只需要输入前几位即可,不需要完全输入,
git会自动去找,也不要只写1,2位,怕找到多个一样的,不知道返回哪个)
这时候,git就回到了最新的版本,其实根据这个原理,只需要找到对应的版本号,你可以回退到任何一个版本,回退到对应版本其实顺便把工作区相应的文件也是更新了。
现在你回退到了某个版本,电脑关机,第二天想要回到最新版本这时候怎么办,在Git中,还有后悔药可以吃,总之原理就是找到对应版本的版本号commit id
Git提供了一个命令,用来记录你的每一次命令:
git reflog
查找到对应的commit id,你就可以回到对应的版本了。
小结
- HEAD指向的版本就是当前版本,HEAD^表示上一个版本,HEAD^^上上个版本,HEAD~100上100个版本,使用命令git reset --hard commit_id可以回退到任何版本,原理都是要找到对应的commit_id
- 所以要查看commit_id就要使用命令git log查看各个版本的日志信息
- 要重返未来,且命令行工具关闭了,可以使用git reflog查看命令历史,以便查找commit_id,回退到某个版本
工作区和暂存区
工作区(Working Directory)
就是你电脑里可以看到的目录,比如我的是My_git文件夹就是一个工作区
版本库(Repository)
工作区有一个隐藏目录.git,这个不算工作区,而是Git的版本库
Git的版本库里存了很多东西,其中最重要的就是stage(或者叫作index)的暂存区,还有Git为我们自动创建了第一个分支master,以及指向master的一个指针叫作HEAD
前面讲到过,把文件往Git版本库里添加的时候,需要两步;
第一步用git add命令把文件添加进去,实际上就是把文件修改添加到暂存区
第二步是用git commit提交更改,实际上就是把暂存区的所有内容提交到当前分支
因为我们创建git仓库时,git自动为我们创建了唯一一个master分支,所以,git commit就是往master分支上提交更改。
简单理解:需要提交的文件修改通通放到暂存区,然后一次性提交暂存区的所有修改
实践了,新建一个LICENSE.txt文件,但是没有提交,使用git status查看状态,它显示的是untracked file未追踪文件
使用git add添加后,其实就是下面的状态
所以git add命令实际上就是把要提交的所有修改放到暂存区(Stage),然后执行git commit就可以一次性把暂存区的所有修改提交到分支
一旦,提交之后,如果你没有对工作区做任何修改,那么工作区就是”干净的“:
这张图好像有点错误,暂存区的内容其实应该还在,不是没有了,只是和HEAD版本无差别了,无差别则不需要提交,所以nothing to commit并不是说暂存区没有内容了
小结
工作区>>>>>>>>>>>>>暂存区>>>>>>>>>>>>>>>>仓库
git add
命令用于将文件从工作区添加到暂存区。git commit
命令用于将暂存区中的文件提交到仓库。git diff
命令用于查看工作区和暂存区之间的差异。git diff --cached
命令用于查看暂存区和仓库之间的差异。git diff HEAD
命令用于查看工作区和仓库之间的差异。git add的反向命令git checkout
命令用于撤销工作区中的修改,即将暂存区中的最新版本转移到工作区。git commit的反向命令git reset HEAD
命令用于将仓库中的最新版本转移到暂存区。就是把暂存区的修改取消
管理修改
Git跟踪并管理的是修改,而非文件
什么是修改,比如你新增了一行,这就是一个修改,删除一行,这也是一个修改,更改了某些字符,这也是一个修改,删了一些又增加了一些,也是一个修改,甚至创建一个新文件也算一个修改。
这里你可以做一个实验,修改一个文件,然后添加到暂存区,这时候如下:
如果你再修改之后,不先添加到暂存区,直接提交,这里第二次的修改是没有添加到仓库中的。
就是这样一个流程:第一次修改->git add-> 第二次修改->git commit
最后结果就是第二次的修改没有更新到仓库中,第二次的修改没有放到暂存区中,所以git commit只负责把暂存区的修改提交了,也就是第一次的修改被提交了,第二次的修改不会被提交。
使用git diff HEAD -- readme.txt可以查看版本库和工作区的区别:
结果就是第二次修改确实没有被提交。
小结
- Git跟踪并修改的不是文件,而是修改
- 在commit之前,务必保证add所有修改的文件,可以修改一次就add和commit一次,也可以先修改全部后add再commit,或者修改一次add一次,最后统一使用commit提交所有修改。
- 确保在commit之前,add所有文件。
- git diff HEAD -- readme.txt查看工作区和版本库的区别,--用来分隔选项和参数,这里只要文件名和选项不会相同,其实可以没有--,也可以用./readme.txt代替上面的-- readme.txt
撤销修改
在工作中很难遇到不犯错的情况,如果你的文件你添加了一些错误的修改,比如stupid boss写到了文档中,如果错误很小,你可以直接手动修改文件,如果此时你用git status查看一下
上面git已经说明了,你可以使用git restore <file>可以丢弃工作区的修改:
git restore readme.txt
其实也可以使用
git checkout -- readme.txt
但是git好像推荐restore指令。
命令git checkout -- readme.txt意思就是,把readme.txt文件在工作区的修改全部撤销,这里有两种情况(命令中的--很重要,缺少这个是一个分支命令)
一种是readme.txt自修改后还没有被放到暂存区,现在撤销修改就回到和版本库一模一样的状态
一种是readme.txt已经添加文件到暂存区,又做了修改,现在撤销修改就回到添加到暂存区后的状态。
总之,就是让文件回到最近一次git commit或git add时的状态
上面的是修改还未提交到暂存区,如果你修改了文件并且添加到了暂存区,这时候git status一下可以发现
git同样会提示我们git restore --stage <file>可以把暂存区的修改撤销掉(unstage),重新放回工作区
执行上面的撤销修改命令,输出如下,如果要把工作区的修改也撤销就是上面的内容,使用git restore <file>
小结
- 注意Git其实你每次git status它都会给出一些你可能用到的操作,要学会查看它的输出,上面就是通过使用git输出的信息里的命令进行撤销修改操作的
- 当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,使用git restore <file>或者git checkout -- file
- 当你不但改乱了工作区某个文件的内容,还添加到了暂存区,想丢弃修改,分为两步,第一步使用命令git restore --staged <file>或者git checkout -- file就回到了上面的场景,第二步就是重复上面的操作情况,撤销工作区的修改
- 已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退内容,不过前提是没有推送到远程库
- 从暂存区恢复工作区,
- git resotre --worktree readme.txt
- 从master恢复暂存区
- git restore --staged readme.txt
- 从master同时恢复工作区和暂存区
- git restore --source=HEAD --staged --worktree readme.txt
删除文件
在Git中,删除也是一个修改操作,我们可以先添加一个新文件test.txt到Git中并且提交:
一般情况下,你通常直接在文件管理器中把没用的文件删了,或者用rm命令删除
rm test.txt
这个时候,Git知道你删除了文件,因此,工作区和版本库就不一致了,git status 命令会立刻告诉你哪些文件被删除了:
现在你有两个选择,一是确实要从版本库中删除该文件,那就用git rm删掉,并且git commit
这时文件从版本库中删除了
小提示:git rm等价于先手动删除文件,再把修改添加到暂存区
注意:从来没有被添加到版本库就被删除的文件,是无法恢复的!
小结
- 命令git rm用于删除一个文件,如果一个文件已经被提交到版本库,那么你永远不用担心误删,但是要小心,你只能恢复文件到最新版本,你会丢失最近一次提交后你修改的内容。
- Git status是个很常用的命令,可以提示你可能用到的命令
添加远程库
1、登录Github添加自己的仓库,创建完之后,它会提示我们可以从这个仓库克隆出新的仓库,也可以把一个已有的本地仓库与之关联,然后把本地仓库的内容推送到Github仓库
2、所以我们可以根据它的提示,如果你是在本地已经有一个仓库了,只需要运行下面的命令
git remote add origin git@github.com:OldLiao/learngit.git
其中origin是你为远程库起的别名,因为它的名字其实是git@github.com:OldLiao/learngit.git,后面推送就可以不用打这么长一段,直接用origin代替。OldLiao是自己的GitHub账户名。
添加远程库之后,远程库的名字就是origin,这是Git默认的叫法,也可以改成别的,但是origin一眼知道这是远程库。
3、下一步就是把本地库的所有内容推送到远程库上:
git push -u origin master
把本地库的内容推送到远程,用git push命令,实际上就是把当前分支master推送到远程。
如果第一次远程库是空的,第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送到远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
推送成功以后,github远程库和本地库是一样的。
从现在起,只要本地做了提交,就可以通过命令:
git push origin master
把本地master分支的最新修改推送至GitHub,现在就相当于拥有了真正的分布式版本库!以后也不用担心你的硬盘了!!!
删除远程库
如果你添加远程库,输入错了,或者就是想删除远程库,可以先用以下命令
git remote -v //查看远程库信息
git remote rm <name> //删除远程库
直接输入git remote rm origin就可以删除
小结
- 要关联一个远程库,使用命令
-
git remote add origin git@server-name:path/repo-name.git
- 关联一个远程库时必须给远程库起一个名字,origin是习惯默认名
- 关联后第一次使用git push -u origin master第一次推送master分支所有内容
- 此后每次提交,只需要用git push origin master推送最新修改
- git remote show origin就是查看远程库是否有更新,如果有,我们可以使用git pull origin从服务器下载更新到本地仓库,本人经过实验确实如此
- git push origin master和git pull origin是一对互相作用的命令,一个是推送本地修改到服务端,一个如果在github上做修改后,使用pull下载到本地仓库
从远程库克隆
简单的说就是从远程库克隆下载到本地仓库,使用命令
git clone git@github.com:OldLiao/gitskills.git
其中,git@github.com:OldLiao/gitskills.git是使用的ssh协议,GitHub它支持多种协议,还有https和cli等
小结
- 要克隆一个仓库,首先必须知道仓库的地址,然后使用git clone命令克隆
- git支持多种协议,包括https、但ssh协议速度最快
分支管理
创建与合并分支
在版本回退里,知道每次提交,git都把它们串成一条时间线,这条时间线就是一个分支,一开始git init创建了一个主分支叫作master,HEAD严格来说不是指向提交,而是指向master,master才是指向提交的。所以HEAD指向的就是当前分支
一开始的时候,master
分支是一条线,Git用master
指向最新的提交,再用HEAD
指向master
,就能确定当前分支,以及当前分支的提交点:
每次提交,master
分支都会向前移动一步,这样,随着你不断提交,master
分支的线也越来越长。
当我们创建新的分支,例如dev
时,Git新建了一个指针叫dev
,指向master
相同的提交,再把HEAD
指向dev
,就表示当前分支在dev
上:
证明了我们说的HEAD指向的就是当前分支
可以看到Git创建一个分支很快,只需增加一个指针,改改HEAD的指向,工作区的文件没有任何变化。
不过,从现在开始,对工作区的修改和提交就是针对dev
分支了,比如新提交一次后,dev
指针往前移动一步,而master
指针不变:
假如我们在dev
上的工作完成了,就可以把dev
合并到master
上。Git怎么合并呢?最简单的方法,就是直接把master
指向dev
的当前提交,就完成了合并:
所以git合并分支也简单,就是改改指针,工作区内容也不变。
合并完分支之后,甚至可以删除dev分支,删除dev分支就是把dev指针删掉,删掉后我们就剩下一条master分支:
命令
//创建分支并且切换到新分支
git checkout -b dev
git switch -c dev //两条是一样的作用,switch更直观点//查看当前分支
git branch //该命令列出所有分支,带*号的是当前分支//假如在dev分支上做了修改并且添加和提交,master查看文件还是没有修改的
//这就是上面说的,master指向的是之前的提交,所以没有变
合并分支
//切回master分支,git merge用于合并指定分支到当前分支
git merge dev //这时就合并了分支
注意到上面的Fast-forward
信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master
指向dev
的当前提交,所以合并速度非常快。
当然,也不是每次合并都能Fast-forward
,还有其他方式的合并。
合并完成后,就可以放心地删除dev
分支了:
删除分支
git branch -d dev//删除dev分支
因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master
分支上工作效果是一样的,但过程更安全。
switch
我们注意到切换分支使用git checkout <branch>
,而前面讲过的撤销修改则是git checkout -- <file>
,同一个命令,有两种作用,确实有点令人迷惑。
实际上,切换分支这个动作,用switch
更科学。因此,最新版本的Git提供了新的git switch
命令来切换分支:
创建并切换到新的dev
分支,可以使用:
$ git switch -c dev
直接切换到已有的master
分支,可以使用:
$ git switch master
使用新的git switch
命令,比git checkout
要更容易理解。
小结
Git鼓励大量使用分支:
- 查看分支:
git branch
- 创建分支:
git branch <name>
- 切换分支:
git checkout <name>
或者git switch <name>
- 创建+切换分支:
git checkout -b <name>
或者git switch -c <name>
- 合并某分支到当前分支:
git merge <name>
- 删除分支:
git branch -d <name>
解决冲突
如果你准备一个新的分支,这时候,修改文件比如readme.txt,修改最后一行为任意内容修改,在新分支比如feature1提交
git add readme.txtgit commit -m "and simple"
切换到master分支,git switch master
会提示如下
$ git switch master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.(use "git push" to publish your local commits)
Git还会自动提示我们当前master
分支比远程的master
分支要超前1个提交。
在master
分支上把readme.txt
文件的最后一行改为:
Creating a new branch is quick & simple.
提交:
$ git add readme.txt
$ git commit -m "& simple"
[master 5dc6824] & simple1 file changed, 1 insertion(+), 1 deletion(-)
现在,master
分支和feature1
分支各自都分别有新的提交,变成了这样:
这种情况下,Git无法“快速合并” ,只能试图把各自的修改合起来,但这种合并就可能会有冲突,我们试试看:
$ git merge feature1 Auto-merging readme.txt CONFLICT (content): Merge conflict in readme.txt Automatic merge failed; fix conflicts and then commit the result.
上面提示合并冲突了,git告诉我们,readme.txt文件存在冲突,使用git status命令查看,必须手动解决冲突后再提交。
$ git status On branch master Your branch is ahead of 'origin/master' by 2 commits.(use "git push" to publish your local commits)You have unmerged paths.(fix conflicts and run "git commit")(use "git merge --abort" to abort the merge)Unmerged paths:(use "git add <file>..." to mark resolution)both modified: readme.txtno changes added to commit (use "git add" and/or "git commit -a")
我们可以直接查看readme.txt的内容:然后修改冲突文件的内容,修改成和feature1分支一样的修改。再提交git add和git commit
结果如下:
用带参数的git log也可以看到分支的合并情况:$ git log --graph --pretty=oneline --abbrev-commit
最后删除feature1分支git branch -d feature1
小结
- 当Git无法自动合并文件时,就必须先解决冲突,解决冲突后,再提交,合并完成
- 解决冲突就是把Git合并失败的文件手动编辑为我们希望的内容,再提交。
- 用git log --graph命令可以看到分支合并图。
分支管理策略
通常,合并分支时,如果没有冲突,并且分支是单向一条线路继承下来的,git会使用 fast forword 模式,但是有些快速合并不能成功,但是又没有冲突时,就会触发分支管理策略,git会自动做一次新的提交。
当两个分支对工作区都进行了修改,但是修改的并不是同一个文件,而是两个不同的文件,也就是不会产生冲突。此时合并的时候,不能使用**“快速合并”**,就会弹框需要你输入一段合并说明。使用快捷键 ctrl+x 退出
合并时禁止快速合并模式
# 合并dev到master,禁止快速合并模式,同时添加说明
git merge --no-ff -m '添加描述' dev
小结
- Git分支十分强大,在团队开发中应该充分应用。
- 合并分支时,加上
--no-ff
参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward
合并就看不出来曾经做过合并。
bug分支
描述和说明
使用场景:当在某个分支上工作,突然有个紧急的BUG需要修复,此时可以使用stash功能,将当前正在工作的现场存储起来,等bug修复之后,在返回继续工作。
对当前现场进行存储
git stash
切换到bug出现的分支上,比如bug出现在 master分支。如果bug就是在当前分支,可以操作此步骤
git checkout master
新添加一个bug临时分支
git checkout -b bug001
对代码进行修复。
切换回master分支
git checkout master
合并bug分支到主master上
git merge --no-ff -m '合并bug分支到master' bug001
删除bug001分支
git branch -d bug001
回到之前的工作现场所在的分支
git checkout dev
查看当前分支保存那些工作现场(之前封冻存储的工作现场)
git stash list
恢复存储的现场
git stash pop
你可以多次stash,恢复的时候,先用git stash list查看,然后恢复指定的stash,用命令:
$ git stash apply stash@{0}
小结
- 修复bug时,我们会通过创建新的bug分支进行修复,然后合并,最后删除。
- 当手头工作没有完成时,先把工作现场
git stash
一下,然后去修复bug,修复后,再git stash pop
,回到工作现场; - 在master分支上修复的bug,想要合并到当前dev分支,可以用
git cherry-pick <commit>
命令,把bug提交的修改“复制”到当前分支,避免重复劳动。
参考
创建标签 - 廖雪峰的官方网站 (liaoxuefeng.com)
geeeeeeeeek/git-recipes: 🥡 Git recipes in Chinese by Zhongyi Tong. 高质量的Git中文教程. (github.com)