Git 学习笔记
配置
$ git config --global user.name "Your Name"
$ git config --global user.email "email@example.com"
因为 Git 是分布式版本控制系统,所以,每个机器都必须自报家门:你的名字和 Email 地址。你也许会担心,如果有人故意冒充别人怎么办?这个不必担心,首先我们相信大家都是善良无知的群众,其次,真的有冒充的也是有办法可查的。 自定义 Git
让 Git 显示颜色,会让命令输出看起来更醒目:
$ git config --global color.ui true
更多配置查看Pro Git 自定义 Git 配置
创建版本库
什么是版本库呢?版本库又名仓库,英文名 repository,你可以简单理解成一个目录,这个目录里面的所有文件都可以被 Git 管理起来,每个文件的修改、删除,Git 都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
首先创建一个空文件夹,如果你使用 Windows 系统,为了避免遇到各种莫名其妙的问题,请确保目录名(包括父目录)不包含中文。
第二步,通过 git init
命令把这个目录变成 Git 可以管理的仓库:
$ git init
不要使用 Windows 自带的记事本编辑任何文本文件,因为编码问题。
小结:
- 初始化一个 Git 仓库,使用 git init 命令。
- 添加文件到 Git 仓库,分两步:
- 第一步,使用命令
git add <file>
,注意,可反复多次使用,添加多个文件; - 第二步,使用命令
git commit
,完成。
查看状态
- 要随时掌握工作区的状态,使用
git status
命令。 - 如果
git status
告诉你有文件被修改过,用git diff
可以查看修改内容。
查看 log
git log
命令显示从最近到最远的提交日志
git log
简化一行显示使用参数 --pretty-oneline
git log --pretty-oneline
恢复指定版本
首先,Git 必须知道当前版本是哪个版本,在 Git 中,用 HEAD
表示当前版本,也就是最新的提交 3628164...882e1e0
(注意我的提交 ID 和你的肯定不一样),上一个版本就是 HEAD^
,上上一个版本就是 HEAD^^
,当然往上 100 个版本写 100 个^比较容易数不过来,所以写成 HEAD~100
。
git reset --hard HEAD^
返回后面的版本,版本号没必要写全,前几位就可以了,405e7f6
Git 会自动去找。
$ git reset --hard 405e7f6
在 Git 中,总是有后悔药可以吃的。当你用 $ git reset --hard HEAD^
回退到以前时,再想恢复到当前版本时,就必须找到当前的 commit id。Git 提供了一个命令 git reflog
用来记录你的每一次命令:
$ git reflog
提交后,用 git diff HEAD -- test.md
命令可以查看工作区和版本库里面最新版本的区别:
$ git diff HEAD -- test.md
小结:
- HEAD 指向的版本就是当前版本,因此,Git 允许我们在版本的历史之间穿梭,使用命令
git reset --hard commit_id
。 - 穿梭前,用
git log
可以查看提交历史,以便确定要回退到哪个版本。 - 要重返未来,用
git reflog
查看命令历史,以便确定要回到未来的哪个版本。
撤销修改
Git 会告诉你,git checkout -- file
可以丢弃工作区的修改:
$ git checkout -- test.md
就是让这个文件回到最近一次 git commit
或 git add
时的状态。
场景 1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令 git checkout -- file
。
场景 2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令 git reset HEAD file,就回到了场景 1,第二步按场景 1 操作。
场景 3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节,不过前提是没有推送到远程库。
管理修改
每次修改,如果不 add
到暂存区,那就不会加入到 commit
中
删除文件
一般情况下,你通常直接 在文件管理器中把没用的文件删了,或者用 rm
命令删了:
$ rm test.txt
这个时候,Git 知道你删除了文件,因此,工作区和版本库就不一致了,git status
命令会立刻告诉你哪些文件被删除了:
现在你有两个选择,一是确实要从版本库中删除该文件,那就用命令 git rm
删掉,并且 git commit
。
另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本:
$ git checkout -- test.txt
命令 git rm
用于删除一个文件。如果一个文件已经被提交到版本库,那么你永远不用担心误删,但是要小心,你只能恢复文件到最新版本,你会丢失最近一次提交后你修改的内容。
Github
在 github 新建仓库后, 新建仓库内容:
echo "# learngit" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin https://github.com/kenve/learngit.git
git push -u origin master
本地仓库的内容推送到 GitHub 仓库:
git remote add origin https://github.com/kenve/learngit.git
git push -u origin master
由于远程库是空的,我们第一次推送 master
分支时,加上了 -u
参数,Git 不但会把本地的 master
分支内容推送的远程新的 master
分支,还会把本地的 master
分支和远程的 master
分支关联起来,在以后的推送或者拉取时就可以简化命令。
要关联一个远程库,使用命令 git remote add origin https://github.com/kenve/learngit.git
关联后,使用命令 git push -u origin master
第一次推送 master 分支的所有内容;
此后,每次本地提交后,只要有必要,就可以使用命令 git push origin master
推送最新修改。
克隆项目
假设远程库已经准备好了,下一步是用命令 git clone
克隆一个本地库:
$ git clone git@github.com:kenve/gitskills.git
# $ git clone https://github.com/reactjs/redux.git
分支管理
创建与合并分支
创建 dev 分支,然后切换到 dev 分支:
$ git checkout -b dev
git checkout
命令加上 -b
参数表示创建并切换,相当于以下两条命令:
$ git branch dev
$ git checkout dev
然后,用 git branch
命令查看当前分支:
$ git branch
git branch
命令会列出所有分支,当前分支前面会标一个 *
号。
切换分支用 $ git checkout name
命令。
git merge
命令用于合并指定分支到当前分支,把 dev
分支的工作成果合并到 master
分支上:
$ git merge dev
因为创建、合并和删除分支非常快,所以 Git 鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在 master
分支上工作效果是一样的,但过程更安全。
- 小结 Git 鼓励大量使用分支:
查看分支:git branch
创建分支:git branch <name>
切换分支:git checkout <name>
创建+切换分支:git checkout -b <name>
合并某分支到当前分支:git merge <name>
删除分支:git branch -d <name>
解决冲突
Git 用 <<<<<<<
,=======
,>>>>>>>
标记出不同分支的内容。
当 Git 无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。
用 git log --graph
命令可以看到分支合并图。
$ git log --graph --pretty=oneline --abbrev-commit
分支管理策略
通常,合并分支时,如果可能,Git 会用 Fast forward
模式,但这种模式下,删除分支后,会丢掉分支信息。
如果要强制禁用 Fast forward
模式,Git 就会在 merge 时生成一个新的 commit,这样,从分支历史上就可以看出分支信息。
合并分支时,请注意 --no-ff
参数,表示禁用 Fast forward
:
$ git merge --no-ff -m "merge with no-ff" dev
因为本次合并要创建一个新的 commit,所以加上 -m
参数,把 commit 描述写进去。
在实际开发中,我们应该按照几个基本原则进行分支管理:
首先,master
分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
那在哪干活呢?干活都在 dev 分支上,也就是说,dev
分支是不稳定的,到某个时候,比如 1.0 版本发布时,再把 dev
分支合并到 master
上,在 master
分支发布 1.0 版本;
你和你的小伙伴们每个人都在 dev
分支上干活,每个人都有自己的分支,时不时地往 dev
分支上合并就可以了。
合并分支时,加上 --no-ff
参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而 fast forward
合并就看不出来曾经做过合并。
BUG 分支
创建一个分支 issue-101 来修复 BUG,修复好后合并分支,但是,当前正在 dev
上进行的工作还没有提交(没有完成无法提交):
幸好,Git 还提供了一个 stash
功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:
$ git stash
现在,用 git status
查看工作区,就是干净的(除非有没有被 Git 管理的文件),因此可以放心地创建分 支来修复 bug。
首先确定要在哪个分支上修复 bug,假定需要在 master
分支上修复,就从 master
创建临时分支:
$ git checkout master
$ git checkout -b issue-101
修复完成后,切换到 master
分支,并完成合并,最后删除 issue-101
分支.
用 git stash list
命令查看隐藏的分支工作现场。
$ git stash list
工作现场还在,Git 把 stash 内容存在某个地方了,但是需要恢复一下,有两个办法:
一是用 git stash apply
恢复,但是恢复后,stash 内容并不删除,你需要用 git stash drop
来删除;
另一种方式是用 git stash pop
,恢复的同时把 stash 内容也删了:
$ git stash pop
你可以多次 stash,恢复的时候,先用 git stash list
查看,然后恢 复指定的 stash,用命令:
$ git stash apply stash@{0}
Feature 分支
添加一个新功能时,你肯定不希望因为一些实验性质的代码,把主分支搞乱了,所以,每添加一个新功能,最好新建一个 feature 分支,在上面开发,完成后,合并,最后,删除该 feature 分支。
但不需要这个新功能时,要将其销毁,当使用 $ git branch -d <name>
进行销毁时,会提醒销毁失败。Git 友情提醒,feature
分支还没有被合并,如果删除,将丢失掉修改,如果要强行删除,需要使用命令 git branch -D <name>
。
$ git branch -D feature-vulcan
多人协作
当你从远程仓库克隆时,实际上 Git 自动把本地的 master
分支和远程的 master
分支对应起来了,并且,远程仓库的默认名称是origin
。
要查看远程库的信息,用 git remote
, 或者,用 git remote -v 显示更详细的信息:
$ git remote
$ git remote -v
origin git@github.com:kenve/learngit.git (fetch)
origin git@github.com:kenve/learngit.git (push)
推送分支:
上面显示了可以抓取和推送的origin
的地址。如果没有推送权限,就看不到 push 的地址。
推送分支:git push origin <name>
$ git push origin dev
但是,并不是一定要把本地分支往远程推送,那么,哪些分支需要推送,哪些不需要呢?
master
分支是主分支,因此要时刻与远程同步;dev
分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;- bug 分支只用于在本地修复 bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个 bug;
- feature 分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。
总之,就是在 Git 中,分支完全可以在本地自己藏着玩,是否推送,视你的心情而定!
更新分支:
git fetch
命令与一个远程的仓库交互,并且将远程仓库中有但是在当前仓库的没有的所有信息拉取下来然后存储在你本地数据库中。
从远程端获取最新版到本地:
$ git fetch origin master
比较本地仓库与远程参考区别:
$ git log -p master.. origin/master
把远程端下载下来的代码合并到本地仓库,远程和本地合并:
$ git merge origin/master
方式二:可以下载 master
为新分支 temp
然后在合并分支,然后删除 temp
# 1
$ git fetch origin master:temp
# 2
$ git diff temp
# 3
$ git merge temp
# 4
$ git branch -d temp
抓取分支
git pull
命令基本上就是 git fetch
和 git merge
命令的组合体,Git 从你指定的远程仓库中抓取内容,然后马上尝试将其合并进你所在的分支中。
多人协作时,大家都会往master
和dev
分支上推送各自的修改。
当你的小伙伴从远程库 clone 时,默认情况下,你的小伙伴只能看到本地的master
分支。
现在,你的小伙伴要在 dev 分支上开发,就必须创建远程 origin 的 dev 分支到本地:
$ git checkout -b dev origin/dev
他就可以在dev
上继续修改,然后,时不时地把dev
分支 push 到远程。而碰巧你也对同样的文件作了修改,并试图推送:$ git push origin dev
。推送失败,因为你的小伙伴的最新提交和你试图推送的提交有冲突,解决办法也很简单,先用git pull
把最新的提交从origin/dev
抓下来,然后,在本地合并,解决冲突,再推送:
git pull
也失败了,原因是没有指定本地dev
分支与远程origin/dev
分支的链接,根据提示,设置dev
和 origin/dev
的链接:
$ git branch --set-upstream dev origin/dev
这回git pull
成功,但是合并有冲突,需要手动解决,解决的方法和分支管理中的解决冲突完全一样。解决后,提交,再 push。
多人协作的工作模式通常是这样:
- 首先,可以试图用
git push origin branch-name
推送自己的修改; - 如果推送失败,则因为远程分支比你的本地更新,需要先用 git pull 试图合并;
- 如果合并有冲突,则解决冲突,并在本地提交;
- 没有冲突或者解决掉冲突后,再用
git push origin branch-name
推送就能成功! - 从远程端获取最新版到本地
$ git fetch origin branch-name
如果git pull
提示“no tracking information”,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream branch-name origin/branch-name
。
小结
- 查看远程库信息,使用
git remote -v
; - 本地新建的分支如果不推送到远程,对其他人就是不可见的;
- 从本地推送分支,使用
git push origin branch-name
,如果推送失败,先用git pull
抓取远程的新提交; - 在本地创建和远程分支对应的分支,使用
git checkout -b branch-name origin/branch-name
,本地和远程分支的名称最好一致; - 建立本地分支和远程分支的关联,使用
git branch --set-upstream branch-name origin/branch-name
; - 从远程抓取分支,使用
git pull
,如果有冲突,要先处理冲突。
标签管理
发布一个版本时,我们通常先在版本库中打一个标签,这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。 Git 的标签虽然是版本库的快照,但其实它就是指向某个 commit 的指针(跟分支很像对不对?但是分支可以移动,标签不能移动),所以,创建和删除标签都是瞬间完成的。
创建标签
在 Git 中打标签非常简单,首先,切换到需要打标签的分支上$ git checkout master
,然后,敲命令git tag <name>
就可以打一个新标签:
$ git tag v1.0
可以用命令git tag
查看所有标签。
默认标签是打在最新提交的 commit 上的。有时候,如果忘了打标签,比如,现在已经是周五了,但应该在周一打的标签没有打,怎么办?
方法是找到历史提交的commit id
,使用$ git tag <tag-name> <commit-id>
,然后打上就可以了:
$ git log --pretty=oneline --abbrev-commit
$ git tag v0.9 6224937
注意,标签不是按时间顺序列出,而是按字母排序的。可以用git show <tagname>
查看标签信息:
还可以创建带有说明的标签,用-a
指定标签名,-m
指定说明文字:
$ git tag -a v0.1 -m "version 0.1 released" 3628164
还可以通过-s
用私钥签名一个标签:
$ git tag -s v0.2 -m "signed version 0.2 released" fec145a
签名采用 PGP 签名,因此,必须首先安装 gpg(GnuPG),如果没有找到 gpg,或者没有 gpg 密钥对,就会报错: 如果报错,请参考 GnuPG 帮助文档配置 Key,GitHub GPG key 配置。
小结:
- 命令
git tag <name>
用于新建一个标签,默认为 HEAD,也可以指定一个 commit id; git tag -a <tagname> -m "blablabla..."
可以指定标签信息;git tag -s <tagname> -m "blablabla..."
可以用 PGP 签名标签;- 命令
git tag
可以查看所有标签。
操作标签
如果标签打错了,也可以使用$ git tag -d <name>
删除:
$ git tag -d v0.1
因为创建的标签都只存储在本地,不会自动推送到远程。所以,打错的标签可以在本地安全删除。
如果要推送某个标签到远程,使用命令git push origin <tagname>
:
$ git push origin v1.0
或者,一次性推送全部尚未推送到远程的本地标签:
$ git push origin --tags
如果标签已经推送到远程,要删除远程标签就麻烦一点,先从本地删除:
$ git tag -d v0.9
然后,从远程删除。删除命令也是 push,但是格式如下:
$ git push origin :refs/tags/v0.9
小结:
- 命令
git push origin <tagname>
可以推送一个本地标签; - 命令
git push origin --tags
可以推送全部未推送过的本地标签; - 命令
git tag -d <tagname>
可以删除一个本地标签; - 命令
git push origin :refs/tags/<tagname>
可以删除一个远程标签。
自定义 Git
忽略文件
忽略某些文件时,需要编写.gitignore
,查看Github 所有配置文件;
.gitignore
文件本身要放到版本库里,并且可以对.gitignore
做版本管理!
# Windows:
Thumbs.db
ehthumbs.db
Desktop.ini
# Python:
*.py[cod]
*.so
*.egg
dist
build
忽略文件的原则是: