再看廖雪峰git系列
git简介
集中式与分布式
集中式
- 版本库集中存放在中央服务器
- 比如要联网才能使用
分布式
- 没有“中央服务器”,每个人的电脑上都是一个完整的版本库
- 因为版本库就在你自己的电脑上,也就无需联网
- 分布式版本控制系统通常也有一台充当“中央服务器”的电脑,但这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已。
- Git的优势不单是不必联网这么简单,后面我们还会看到Git极其强大的分支管理,把SVN等远远抛在了后面。
和集中式版本控制系统相比,分布式版本控制系统的安全性要高很多,因为每个人电脑里都有完整的版本库,某一个人的电脑坏掉了不要紧,随便从其他人那里复制一个就可以了。而集中式版本控制系统的中央服务器要是出了问题,所有人都没法干活了。
创建版本库
版本库又名仓库,英文名repository,你可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
直接创建文件夹,运行git init
即可,会在当前目录下多了一个.git
目录,这个目录是Git来跟踪管理版本库的,如果你没有看到.git
目录,那是因为这个目录默认是隐藏的,用ls -ah
命令就可以看见。
所有的版本控制系统,其实只能跟踪文本文件的改动,比如TXT文件,网页,所有的程序代码等等,Git也不例外。
添加文件到Git仓库,分两步:
- 使用命令
git add <file>
,注意,可反复多次使用,添加多个文件; - 使用命令
git commit -m <message>
,完成。
时光穿梭机
版本回退
git status
:查看当前仓库状态,比如
- 本地修改了哪些文件(Changes not staged for commit)
- 哪些文件待提交(Changes to be commited)
- 哪些文件是新增的(untracked file)
- 未合并文件(unmerged paths)
git diff <file>
:查看具体修改了什么内容
你不断对文件进行修改,然后不断提交修改到版本库里,就好比玩RPG游戏时,每通过一关就会自动把游戏状态存盘,如果某一关没过去,你还可以选择读取前一关的状态。有些时候,在打Boss之前,你会手动存盘,以便万一打Boss失败了,可以从最近的地方重新开始。Git也是一样,每当你觉得文件修改到一定程度的时候,就可以“保存一个快照”,这个快照在Git中被称为commit。一旦你把文件改乱了,或者误删了文件,还可以从最近的一个commit恢复,然后继续工作,而不是把几个月的工作成果全部丢失。
git log
命令显示从最近到最远的提交日志,如果嫌输出信息太多,看得眼花缭乱的,可以试试加上git log --pretty=oneline
参数。
我们可以看到通过SHA1计算出来的commit id。接下来我们通过 git reset 命令回退到上一个版本。
git reset --hard HEAD^
接下来通过git log
来查看现在版本库的状态,会发现最新的 commit 不见了,此时如果我们想回去怎么办,可以找到之前 git log 打印出来的 commit id,通过如下命令回去(版本号没必要写全,前几位就可以了,Git会自动去找。当然也不能只写前一两位,因为Git可能会找到多个版本号,就无法确定是哪一个了。)
git reset --hard commit id
此时会发现版本回去了,可是总感觉不对劲,what~~~!如果我窗口关了,找不到 commit id,难道就回不去了?你不是在开玩笑吧。
在Git中,总是有后悔药可以吃的。当你用git reset --hard HEAD^
回退版本时,想再回去就必须知道之前的commit id,Git提供了一个命令git reflog
用来记录你的每一次命令。
总结
- HEAD指向的版本就是当前版本,因此,Git允许我们在版本的历史之间穿梭,使用命令git reset --hard commit_id。
- 穿梭前,用git log可以查看提交历史,以便确定要回退到哪个版本。
- 要重返未来,用git reflog查看命令历史,以便确定要回到未来的哪个版本。
扩展
- git reset –-soft:回退到某个版本,只回退了commit的信息,不会恢复到index file一级。如果还要提交,直接commit即可;
- git reset -–hard:彻底回退到某个版本,本地的源码也会变为上一个版本的内容,撤销的commit中所包含的更改被冲掉
- git reset (--mixed):回退到某个版本,撤销commit和add,工作区内容不变
工作区和暂存区
工作区有一个隐藏目录.git,这个不算工作区,而是Git的版本库。Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD。
把文件添加到版本库,分两步的具体解释如下
- 第一步是用git add把文件添加进去,实际上就是把文件修改添加到暂存区;
- 第二步是用git commit提交更改,实际上就是把暂存区的所有内容提交到当前分支。
因为我们创建Git版本库时,Git自动为我们创建了唯一一个master分支,所以,现在,git commit就是往master分支上提交更改。
管理修改
为什么Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件。
如何理解这个呢?比如我们按照这个流程:第一次修改 -> git add -> 第二次修改 -> git commit,你会发现第二次修改并没有提交。
撤销修改
丢弃工作区的修改,两种方式
- 手动还原,但可能比较啰嗦
- 使用
git checkout -- file
,这里有两种情况- 修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
- 已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。
- 就是让这个文件回到最近一次git commit或git add时的状态。
git checkout -- file命令中的--很重要,没有--,就变成了“切换到另一个分支”的命令
如果你不小心使用git add
加入到暂存区了,怎么办呢?用命令git reset HEAD <file>
可以把暂存区的修改撤销掉(unstage),重新放回工作区
git reset命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用HEAD时,表示最新的版本
如果问题来的更加严重一点,不但改错了东西,还从暂存区提交到了版本库,此时我们还可以使用版本回退来达到目的。前提是你没有把本地版本库推送到远程。如果推送到远程了,这次提交就真的干不掉了。
删除文件
直接手动删除文件,或者使用rm删除文件,可以理解成一次本地一次修改操作。确实要从版本库中删除的,可以使用命令git add
,并且git commit
即可。
更快捷的方式,你可以使用git rm file
,然后直接git commit
即可。经实践,git rm file
等同于rm file && git add file
。
如果是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本
git checkout -- test.txt
git checkout其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
如果删除了且添加到了暂存区,则需要两个步骤
git reset HEAD test.txt
git checkout -- test.txt
命令git rm用于删除一个文件。如果一个文件已经被提交到版本库,那么你永远不用担心误删,但是要小心,你只能恢复文件到最新版本,你会丢失最近一次提交后你修改的内容。
远程仓库
创建SSH Key。在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa和id_rsa.pub这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开Shell(Windows下打开Git Bash),创建SSH Key:
ssh-keygen -t rsa -C "youremail@example.com"
登陆GitHub,打开“Account settings”,“SSH Keys”页面:然后,点“Add SSH Key”,填上任意Title,在Key文本框里粘贴id_rsa.pub文件的内容即可。
note:非必须,只不过方便我们提交,同时通过添加多个 SSH Key 方便协同开发
添加远程库
在 Github 上 Create a new repo 仓库后,将本地仓库和远程仓库进行关联,然后把本地仓库的内容推送到 GitHub 仓库。添加后,远程库的名字就是origin
,这是Git默认的叫法,也可以改成别的,但是origin这个名字一看就知道是远程库。
git remote add origin https://github.com/liuxinqiong/learngit.git
git push -u origin master
把本地库的内容推送到远程,用git push命令,实际上是把当前分支
master推送到远程。
由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
-u参数的作用
- 如果当前分支与多个主机存在追踪关系,那么这个时候
-u
选项会指定一个默认主机
,这样后面就可以不加任何参数使用git push
。 - 如果不想推送默认主机呢,可以通过
git push origin master
,推送特定主机的特定分支
从远程库clone
git clone
分支
分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。
现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。
创建与合并分支
为什么 git 相比 svn,创建分支,切换分支,合并分支,会那么快呢?因此至始至终都只是操作了指针而已。
比如新建 dev 分支,底层就是新建一个dev指针,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上。
从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变。
分支合并,直接把master指向dev的当前提交,就完成了合并。
合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉。
下面具体操作一下
# 新建分支并切换 -b表示切换
git checkout -b dev
# 相当于一下两条命令
git branch dev
git checkout dev
# git branch命令会列出所有分支,当前分支前面会标一个*号
git branch
# 切换回master
git checkout master
# git merge合并指定分支到当前分支
git merge dev
# 合并完成后,删除dev分支
git branch -d dev
因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。
分支合并有两种方式,一种是merge,一种是rebase,rebase可以多了解一下
解决冲突
人生不如意之事十之八九,合并分支往往也不是一帆风顺的。
比如我们新建feature1分支,然后对readme.txt文件进行了修改,然后切换回去master分支,对同一个文件进行了修改(或者是队友进行了修改),这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突。
此时Git会提示我们,readme.txt文件存在冲突,必须手动解决冲突后再提交。git status也可以告诉我们冲突的文件。
解决冲突就是把Git合并失败的文件手动编辑为我们希望的内容,再提交。Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容,我们修改如下后保存。
git add readme.txt
git commit -m "conflict fixed"
用带参数的git log也可以看到分支的合并情况
git log --graph --pretty=oneline --abbrev-commit
分支管理策略
通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。
如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
# 请注意--no-ff参数,表示禁用Fast forward
# 因为本次合并要创建一个新的commit,所以加上-m参数,把commit描述写进去。
git merge --no-ff -m "merge with no-ff" dev
合并分支时,加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward合并就看不出来曾经做过合并。
在实际开发中,我们应该按照几个基本原则进行分支管理:
- master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活
- 干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
- 你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
现场储存
在实际开发中,你在dev上进行的工作还没有提交,因为你只进行到一半,还没发提交,但是需要紧急修复bug,此时怎么办呢。
Git还提供了一个stash功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作。
git stash
首先确定要在哪个分支上修复bug,假定需要在master分支上修复,就从master创建临时分支
git checkout master
git checkout -b issue-101
修复完bug后,切换回master分支,合并且删除issue-101分支,此时继续自己的开发,回到dev分支,此时如何恢复现场呢
# 查看工作现场列表
git stash list
# 恢复不删除,需要额外使用git stash drop删除
git stash apply
# 恢复删除
git stash pop
可以多次stash,恢复的时候,先用git stash list查看,然后恢复指定的stash,git stash apply stash@{0}
feature分支
主要知识点:未被合并的分支删除会报错,如果删除,将丢失掉修改,如果要强行删除,需要使用大写的-D参数。
多人协作
查看远程库信息
git remote
# 查看更详细信息
git remote -v
推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上
git push origin master
git push origin dev
并不是一定要把本地分支往远程推送,那么,哪些分支需要推送,哪些不需要呢?
- master分支是主分支,因此要时刻与远程同步;
- dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;
- bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;
- feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。
抓取分支
当你的小伙伴从远程库clone时,默认情况下,你的小伙伴只能看到本地的master分支。
你的小伙伴要在dev分支上开发,就必须创建远程origin的dev分支到本地,于是他用这个命令创建本地dev分支
# 在本地创建和远程分支对应的分支,本地和远程分支的名称最好一致;
git checkout -b dev origin/dev
小伙伴修改了文件,提交到了远程。此时你也对同样的文件进行了修改,并试图推送。这是Git提示我们,先用git pull把最新的提交从origin/dev抓下来,然后,在本地合并,解决冲突,再推送。这样其实也很美好。问题在于git pull
也失败了。心塞了。原因是没有指定本地dev分支与远程origin/dev分支的链接,根据提示,设置dev和origin/dev的链接
git branch --set-upstream-to=origin/dev dev
此时再次git pull
就成功啦。接下来要做的就是解决冲突,并提交即可。
因此,多人协作的工作模式通常是这样:
- 首先,可以试图用git push origin
推送自己的修改; - 如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;
- 如果合并有冲突,则解决冲突,并在本地提交;
- 没有冲突或者解决掉冲突后,再用git push origin
推送就能成功!
如果git pull提示no tracking information,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to
origin/ 。
标签管理
发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。
Git的标签虽然是版本库的快照,但其实它就是指向某个commit的指针(跟分支很像对不对?但是分支可以移动,标签不能移动),所以,创建和删除标签都是瞬间完成的。
Git有commit,为什么还要引入tag?tag就是一个让人容易记住的有意义的名字,它跟某个commit绑在一起。
# 创建tag
git tag v1.0
# 查看所有tag
git tag
# 默认标签是打在最新提交的commit上的。也可以指定commit
git tag v0.9 f52c633
# 标签不是按时间顺序列出,而是按字母排序的。可以用git show <tagname>查看标签信息
git show v0.9
# 可以创建带有说明的标签,用-a指定标签名,-m指定说明文字
git tag -a v0.1 -m "version 0.1 released" 1094adb
注意:标签总是和某个commit挂钩。如果这个commit既出现在master分支,又出现在dev分支,那么在这两个分支上都可以看到这个标签。
如果tag打错了,使用如下命令删除
git tag -d v0.1
因为创建的标签都只存储在本地,不会自动推送到远程。所以,打错的标签可以在本地安全删除
如果要推送某个标签到远程,使用命令git push origin
# 指定tag
git push origin v1.0
# 一次性推送全部尚未推送到远程的本地标签
git push origin --tags
推送成功后,可以在github的release下看到具体记录!
如果想删除已经推送到远程的tag,操作如下,相对麻烦一些
# 本地删除
git tag -d v0.9
# 推送远程
git push origin :refs/tags/v0.9
使用GitHub
如何参与开源项目呢?
- 点fork,在自己帐号下克隆项目,然后从自己的账号下clone。一定要从自己的账号下clone仓库,这样你才能推送修改。
- 代码写完后,往自己仓库推送
- 如果希望官方库能接受你的修改,你就可以在GitHub上发起一个pull request。
使用码云
主要知识点,学会如何关联多个远程库。如果我们在码云上,新建远程仓库,想和和我们本地库关联。
git remote add origin url
会提示报错remote origin already exists.
,这说明本地库已经关联了一个名叫origin的远程库,此时,可以先用git remote -v查看远程库信息:
git remote -v
# 删除远程关联,这样再次关联码云就不会报错,但是这里我们想关联多个
git remote rm origin
# 主要原因是origin名称已经被占用,我们换个名字就好了,比如使用gitee名称
git remote add gitee url
添加多个后,推送就需要指定推送到那个远程库啦
git push github master
git push gitee master
自定义git
忽略特殊文件
在Git工作区的根目录下创建一个特殊的.gitignore
文件,然后把要忽略的文件名填进去,Git就会自动忽略这些文件。
不需要从头写.gitignore
文件,GitHub已经为我们准备了各种配置文件,只需要组合一下就可以使用了。所有配置文件可以直接在线浏览:https://github.com/github/gitignore
忽略文件的原则是:
- 忽略操作系统自动生成的文件,比如缩略图等;
- 忽略编译生成的中间文件、可执行文件等,也就是如果一个文件是通过另一个文件自动生成的,那自动生成的文件就没必要放进版本库,比如Java编译产生的.class文件;
- 忽略你自己的带有敏感信息的配置文件,比如存放口令的配置文件。
设置别名
通过设置别名达到简写命令的目的
git config --global alias.shortName fullName
配置Git的时候,加上--global是针对当前用户起作用的,如果不加,那只针对当前的仓库起作用。
- 配置文件放哪了?每个仓库的Git配置文件都放在.git/config文件中
- 当前用户的Git配置文件放在用户主目录下的一个隐藏文件.gitconfig中
团队协作
github flow工作流:基于分支的工作流
- 创建一个分支
- 添加新版本
- 开启PR
- 讨论和代码审核
- 合并分支和部署
代码协作开发两种情况
- 彼此互相熟悉的团队间的协作
- 给队员添加写权限:Settings -> Collaborators -> add collaborator
- 如果是小功能,直接一起推送master也可以,如果是大功能,开发周期长,需要讨论和测试的,就有必要走完整的github flow流程了
- 开源项目中和互不相识的开源贡献者合作
贡献开源项目流程:核心也是PR
- fork项目,因为不可能像自己团队一样,给每个人添加写权限
- clone项目,修改且同步
- 作者如何知道谁fock了和查看commit的内容呢。graph -> network
- pull request
- 贡献者在fork的项目下,pull request -> new pull request
- 此时会对比作者源项目分支和你自己项目的分支,填写内容确定即可。
- 这样一来作者就可以看到你提交的PR
quick pull request:如果只是简单的修改某一个文件,完全可以不借助客户端和编辑器,直接找到想要修改的文件,点击edit,GitHub检测到你没有权限时,会自动fork到你账户下,你修改后,支持直接pull request。如果你本身对这个项目有写权限,就会有一个额外的选项,直接提交到分支。
项目基础设施
- GitHub pages搭建项目主页
- wiki
- issue
- 通过右侧的Assignee,可以选择分配给谁处理
- 讨论区可以@任意用户
- 通过
>
来表示回答某个讨论 - 引用已经写过的内容,点击issue下某个讨论的时间,复制浏览器链接,然后发布即可
- 通过#number直接指向某个issue
- issue能不能提交代码呢?可以的。添加代码时,在版本留言里通过#number关联特定issue,此时在issue中可以看到关联的代码
- 如果修复了issue,直接在版本留言里写,xxx fix #number,可以直接关闭指定的issue
GitHub Pages
- 用户或组织网站
- 项目网站
- 方式
- 自动生成器
- 手写
- 手写方式
- 创建gh-pages分支(对名字有特殊要求)
- username.github.io/projectname
GitHub密码机关
- GitHub追求界面简洁,因此很多功能需要快捷键才能唤醒,具体看BLOG:https://zachholman.com/talk/git-github-secrets/
- 快捷键T打开项目全局搜索
- 关注名人,通过热门项目找人
经验
- 多用客户端工具,少用命令行(source tree、github官方客户端)
- 每次提交代码前,diff自己的代码,以免提交错误的代码
- 下班之前,整理好自己的工作区
- 并行的代码,一定要使用分支开发
- 遇到冲突时,搞明白冲突的原因,千万不要随意丢弃别人的代码
- 产品发布后,记得打tag,方便我们日后拉分支修bug
git flow工作流
git flow:围绕项目发布的严格分支模型(感觉source tree就是为他定制的哈)
release分支:只应该做Bug修复、文档生成和其他面向发布的任务