笔记来自廖雪峰Git教程
1. 创建版本库
git init
// 初始化一个 Git 仓库
git add readme.txt
// 把 readme.txt 添加到仓库
git commit -m "wrote a readme file"
// 将文件提交到仓库, -m 后面是本次提交的说明
git status
// 查看仓库当前的状态
git diff readme.txt
// 查看 readme.txt 修改的内容
2. 版本回退
Git 版本回退速度非常快,因为 Git 在内部有个指向当前版本的 HEAD 指针,当回退版本的时候,Git 仅仅调整了 HEAD 指针的指向。
git log
// 查看提交日志, --pretty=onrline 参数可以娴熟更清晰的信息
git reset --hard commit_id
// 改变当前版本
git reflog
// 重返未来时,查看命令历史,确定要回到未来的哪个版本
3. 工作区和暂存区
工作区:电脑里能看见的目录都是工作区
版本库:工作区有个隐藏目录 .git ,是 Git 的版本库。版本库中最重要的就是暂存区(stage/index),还有 Git 为我们自动创建的第一个分支 master,以及指向 master 的一个指针叫 HEAD
将文件往 Git 版本库中添加的时候,是分两步执行的:
- git add 实际就是把文件修改添加到暂存区
- git commit 实际就是把暂存区的所有内容提交到当前分支
创建 Git 版本库时就自动创建了唯一一个分支 master 分支,所以现在 git commit 就是往 master 分支上提交更改。可以理解为需要提交的文件修改通通放到暂存区,然后一次性提交暂存区的所有修改。如果提交后,又没有对工作区做任何修改,那么工作区就是“干净”的。
4. 管理修改
Git 跟踪并管理的是修改,并非文件。
如果 第一次修改 -> git add -> 第二次修改 -> git commit 是不会提交第二次的修改的,因为工作区的第二次修改并没有放入暂存区。
5. 撤销修改
丢弃工作区修改:
git checkout -- readme.txt
// 把 readme.txt 文件在工作区的修改全部撤销,这里有两种情况:
// 1. readme.txt 自修改后还没有放到暂存区,撤销修改就会回到和版本库一模一样的状态
// 2. readme.txt 已经添加到暂存区后,又做了修改,现在撤销就回到添加到暂存区后的状态
// 总之,就是让这个文件回到最近一次 git commit 或 git add 时的状态
// -- 很重要,没有 -- ,就变成了切换到另一个分支的命令
添加到暂存区后丢弃修改:
git reset HEAD readme.txt
// git reset 命令既可以回退版本,也可以把暂存区的修改回退到工作区。HEAD 表示最新的版本。
git status
// 再次查看状态,如果暂存区干净了,但是工作区有修改的话,再用 git checkout --readme.txt 丢弃修改
6. 删除文件
如果在文件管理器中删除了文件,但是版本库中还没删:
// 1. 确实要从版本库中删除该文件
git rm test.txt
git commit -m "remove test.txt"
// 2. 删错了
git checkout -- test.txt
// git checkout 其实是用版本库里的版本替代工作区的版本,所以无论工作区是修改还是删除都可以还原
7. 添加远程库
可以使用 GitHub 免费获得 Git 远程仓库:
-
创建 SSH Key,用户主目录的 .ssh 目录中如果没有 id_rsa 和 id_rsa.pub 两个文件的话就需要创建下:
ssh-keygen -t rsa -C "youremail@example.com" // 一路回车使用默认值即可,id_ras 是私钥不可泄露,id_rsa.pub 公钥可以告诉别人 -
登录 GitHub ,在 Account settings 的 SSH Keys 页面 Add SSH Key 填上 title 和 公钥
在 GitHub 添加了公钥后,GitHub 就可以识别是否是本人推送了
在 GitHub 创建 Git 仓库并与本地的仓库远程同步:
-
登录 GitHub,Create a new repo -> 填写 Repository name -> Create repository 。仓库建好后还是空的,可以从这个仓库克隆出新的仓库;也可以把一个已有的本地仓库与之关联,然后把本地仓库的内容推送到该仓库
-
关联远程仓库
git remote add origin git@github.com:Shark-1024/learngit.git // Shark-1024 换成自己的 GitHub 账户名 // learngit 换成自己的 GitHub 仓库名 // 添加后,远程库的名字就是origin,这是Git默认的叫法,也可以改成别的,但是origin这个名字一看就知道是远程库 -
把本地库的所有内容推送到远程库上
git push -u origin master // git push 命令实际上是把当前分支 master 推送到远程 // 由于远程库是空的,我们第一次推送 master 分支时,加上了-u 参数,Git 不但会把本地的 master 分支内容推送到远程新的 master 分支,还会把本地的 master 分支和远程的 master 分支关联起来,在以后的推送或者拉取时就可以简化命令
8. 从远程库克隆
如果从零开发,最好先创建远程库,然后从远程库克隆
git clone git@github.com:Shark-1024/gitskills.git
9. 创建与合并分支
Git 把每次提交都串成一条时间线,这条时间线就是一个分支。Git 默认只有一条时间线,这个分支叫主分支,即 master 分支,即 master 分支。HEAD 严格来说不是指向分支,而是指向 master, master 才是指向提交的,所以 HEAD 指向的就是当前分支。
每次提交 master 分支都会向前移动一步,提交次数越多,master 分支的线也越来越长。
当我们创建新的分支,比如 dev,Git 就会新建一个名为 dev 的指针,指向 master 相同的提交,再把 HEAD 也指向 dev,表示当前分支在 dev 上:
这时对工作区的修改和提交就是针对 dev 分支了,比如新提交一次后,dev 指针就往前移动一步,而 master 指针不变:
如果我们在 dev 上的工作完成了,就可以把 dev 合并到 master 上。Git 最简单的合并方法就是直接把 master 指向 dev 的当前提交,就完成了合并:
合并完成后,甚至可以删除 dev 分支,删除 dev 分支就是把 dev 指针给删除掉,之后就只剩下了一条 master 分支:
git checkout -b dev
// 创建 dev 分支,并切换到 dev 分支, 相当于以下两条命令
git branch dev
git checkout dev
git branch
// 该命令列出所有分支,当前分支前面会有 * 号
// 然后我们可以在 dev 分支上正常提交,当 dev 分支的工作完成,我们就可以切换回 master 分支:
git checkout master
// 此时查看工作区是看不见在 dev 分支所做的修改的,因为之前的提交是在 dev 分支上,而 master 分支此刻的提交点并没变
git merge dev
// 现在把 dev 分支的工作成果合并到 master 分支上,git merge 命令用于合并指定分支到当前分支,合并后 master 分支和 dev 分支的最新提交就完全一样了
// 这次合并是“快进模式”,Fast-forward,就是把 master 指向 dev 的当前提交,所以合并速度非常快
git branch -d dev
// 合并完成后,就可以放心的删除 dev 分支了,此时再用 git branch 查看分支发现只剩下 master 分支了
因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。
我们注意到切换分支使用 git checkout ,而前面讲过的撤销修改则是 git checkout – 。因此,最新版本的Git提供了新的git switch命令来切换分支:
git switch -c dev
// 创建并切换到新的 dev 分支
git switch master
// 直接切换到已有的 master 分支
10. 解决冲突
准备新的 feature1 分支,继续新分支开发
git switch -c feature1
在 feature1 分支上修改并提交,再切换到 master 分支修改并提交。这时两个分支各自都分别有新的提交:
这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突
git merge feature1
// 合并之后确实发生了冲突
git status
// 也可以用该命令查看冲突的文件
Git 用 <<<<<<<<< ========= >>>>>>>>>> 标记出不同分支的内容,我们根据提示修改文件后保存再提交,就可以解决冲突了。
现在,master 分支和 feature1 分支变成了这样:
git log --graph --pretty=online --abbrev-commit
// 用带参数的 git log 也可以看到分支的合并情况
11. 分支管理策略
通常合并分支时,如果可能的话 Git 会用 Fast forward 模式,但在这种模式下,删除分支后,会丢掉分支信息。如果强制禁用 Fast forward 模式,Git 就会在 merge 时生成一个新的 commit,这样从分支历史上就可以看出分支信息。
git merge --no--ff -m "merge with no-ff" dev
// 合并 dev 分支,-no-ff 参数表示禁用 Fast forward
// 因为本次合并要创建新的 commit,所以要 -m 加入 commit 的描述
// 合并后再用 git log 查看历史
git log --graph --pretty=oneline --abbrev-commit
* e1e9c68 (HEAD -> master) merge with no-ff
|\
| * f52c633 (dev) add merge
|/
* cf810e4 conflict fixed
可以看到,不使用Fast forward模式,merge后就像这样:
合并分支时,加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward合并就看不出来曾经做过合并。
在实际开发中,我们应该按照几个基本原则进行分支管理:
master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;- 干活都在
dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
所以,团队合作的分支看起来就像这样(michael 和 bob 是你和小伙伴的名字):
12. Bug 分支
当接到一个修复一个代号 101 的 bug 的任务时,很自然地,你想创建一个分支issue-101来修复它,但是当前正在dev上进行的工作刚完成了一半还没有办法提交
git stash
// Git 还提供了一个 stash 功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:
储藏工作现场后,再用 git status 查看工作区就是干净的(除非有没有纳入 Git 管理的文件),这时就可以放心的创建分支来修复 Bug 了
git stash list
// 创建临时分支修复好 Bug 并删除临时分支后,用该命令查看储藏的工作现场
git stash pop
// 恢复现场的同时把 stash 内容删除
// 还可以用 git stash apply 恢复,恢复后不会删除 stash,需要用 git stash drop 删除
git stash apply stash@{0}
// 回复列表中指定的 stash 用该命令
在一个分支上修复 Bug 后,可能有早期从该分支分出来的分支还有 Bug 未修复,可以复制修复 Bug 的这次提交到别的分支上。
git cherry-pick 4c805e2
// 复制一个特定的提交到当前分支
13. Feature 分支
每添加一个新功能,最好新建一个 feature 分支,完成后合并,最后删除该 feature 分支。
git switch -c feature-vulcan
// 开发代号为 vulcan 的新功能,创建并切换到该分支
git switch dev
// 开发完毕后切回 dev,准备合并
// 此时如果因为某种原因取消了新功能,需要删除分支
git branch -D feature-vulcan
// 可以使用带大写的 -D 参数的命令强制删除未合并的分支
14. 多人协作
当你从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin。
git remote
// 该命令可以查看远程库的信息,-v 参数可以显示更详细的信息
git remote -v
origin git@github.com:michaelliao/learngit.git (fetch)
origin git@github.com:michaelliao/learngit.git (push)
// 上面显示可以抓取和推送的地址,如果没有推送权限就看不到 push 的地址
推送分支
推送分支就是把该分支上的所有本地提交推送到远程库。推送时要指定本地分支,这样 Git 就会把该分支推送到远程库对应的远程分支上:
git push origin master
// 如果要推送到其他分支,比如 dev ,就改成:
git push origin dev
但是,并不是一定要把本地分支往远程推送,那么,哪些分支需要推送,哪些不需要呢?
master分支是主分支,因此要时刻与远程同步;dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;- bug 分支只用于在本地修复 bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;
- feature 分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。
总之,就是在Git中,分支完全可以在本地自己藏着玩,是否推送,视你的心情而定!
抓取分支
多人协作时,大家都会往 master 和 dev 分支上推送各自的修改
这时一个小伙伴从远程库 clone 到自己电脑后,默认只能看到本地的 master 分支。如果小伙伴要在 dev 分支开发,就必须创建远程 origin 的 dev 分支到本地,于是他用这个命令创建本地 dev 分支:
git checkout -b dev origin/dev
如果我们碰巧和小伙伴对相同的文件做了修改,就会产生冲突。解决的办法很简单,先用git pull把最新的提交从origin/dev抓下来,然后再本地合并,解决冲突,再推送:
$ git pull
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details.
git pull <remote> <branch>
If you wish to set tracking information for this branch you can do so with:
git branch --set-upstream-to=origin/<branch> dev
git pull失败,因为我们没有指定本地 dev 分支与远程 origin/dev 分支的链接,根据提示,设置dev和origin/dev的链接:
$ git branch --set-upstream-to=origin/dev dev
Branch 'dev' set up to track remote branch 'dev' from 'origin'.
再pull:
$ git pull
Auto-merging env.txt
CONFLICT (add/add): Merge conflict in env.txt
Automatic merge failed; fix conflicts and then commit the result.
这回git pull成功,但是合并有冲突,需要手动解决,解决的方法和分支管理中的解决冲突完全一样。解决后,提交,再push:
$ git commit -m "fix env conflict"
[dev 57c53ab] fix env conflict
$ git push origin dev
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 621 bytes | 621.00 KiB/s, done.
Total 6 (delta 0), reused 0 (delta 0)
To github.com:michaelliao/learngit.git
7a5e5dd..57c53ab dev -> dev
因此,多人协作的工作模式通常是这样:
- 首先,可以试图用
git push origin <branch-name>推送自己的修改; - 如果推送失败,则因为远程分支比你的本地更新,需要先用
git pull试图合并; - 如果合并有冲突,则解决冲突,并在本地提交;
- 没有冲突或者解决掉冲突后,再用
git push origin <branch-name>推送就能成功!
如果git pull提示no tracking information,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to <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,如果有冲突,要先处理冲突。
15. Rebase
- rebase操作可以把本地未push的分叉提交历史整理成直线;
- rebase的目的是使得我们在查看历史提交的变化时更容易,因为分叉的提交需要三方对比。
16. 标签管理
发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。
Git的标签虽然是版本库的快照,但其实它就是指向某个commit的指针(跟分支很像对不对?但是分支可以移动,标签不能移动),所以,创建和删除标签都是瞬间完成的。
Git有commit,为什么还要引入tag?
“请把上周一的那个版本打包发布,commit号是6a5819e…”
“一串乱七八糟的数字不好找!”
如果换一个办法:
“请把上周一的那个版本打包发布,版本号是v1.2”
“好的,按照tag v1.2查找commit就行!”
所以,tag就是一个让人容易记住的有意义的名字,它跟某个commit绑在一起。
17. 创建标签
- 命令
git tag <tagname>用于新建一个标签,默认为HEAD,也可以指定一个commit id; - 命令
git tag -a <tagname> -m "blablabla..."可以指定标签信息; - 命令
git tag可以查看所有标签。
18. 删除标签
- 命令
git push origin <tagname>可以推送一个本地标签; - 命令
git push origin --tags可以推送全部未推送过的本地标签; - 命令
git tag -d <tagname>可以删除一个本地标签; - 命令
git push origin :refs/tags/<tagname>可以删除一个远程标签。
19. GitHub
- 在GitHub上,可以任意Fork开源仓库;
- 自己拥有Fork后的仓库的读写权限;
- 可以推送pull request给官方仓库来贡献代码。
20. 自定义 Git
// 让 Git 显示颜色,会让命令输出看起来更醒目
git config --global color.ui true
忽略特殊文件
不需要从头写.gitignore文件,GitHub已经为我们准备了各种配置文件,只需要组合一下就可以使用了。所有配置文件可以直接在线浏览:https://github.com/github/gitignore
忽略文件的原则是:
- 忽略操作系统自动生成的文件,比如缩略图等;
- 忽略编译生成的中间文件、可执行文件等,也就是如果一个文件是通过另一个文件自动生成的,那自动生成的文件就没必要放进版本库,比如Java编译产生的
.class文件; - 忽略你自己的带有敏感信息的配置文件,比如存放口令的配置文件。
有些时候,你想添加一个文件到Git,但发现添加不了,原因是这个文件被.gitignore忽略了,如果你确实想添加该文件,可以用-f强制添加到Git:
$ git add -f App.class
或者你发现,可能是.gitignore写得有问题,需要找出来到底哪个规则写错了,可以用git check-ignore命令检查:
$ git check-ignore -v App.class
.gitignore:3:*.class App.class
配置别名
我们只需要敲一行命令,告诉Git,以后st就表示status(--global参数是全局参数,也就是这些命令在这台电脑的所有Git仓库下都有用):
$ git config --global alias.st status
当然还有别的命令可以简写,很多人都用co表示checkout,ci表示commit,br表示branch:
$ git config --global alias.co checkout
$ git config --global alias.ci commit
$ git config --global alias.br branch
在撤销修改一节中,我们知道,命令git reset HEAD file可以把暂存区的修改撤销掉(unstage),重新放回工作区。既然是一个unstage操作,就可以配置一个unstage别名:
$ git config --global alias.unstage 'reset HEAD'
当你敲入命令:
$ git unstage test.py
实际上Git执行的是:
$ git reset HEAD test.py
配置一个git last,让其显示最后一次提交信息:
$ git config --global alias.last 'log -1'
这样,用git last就能显示最近一次的提交:
$ git last
commit adca45d317e6d8a4b23f9811c3d7b7f0f180bfe2
Merge: bd6ae48 291bea8
Author: Michael Liao <askxuefeng@gmail.com>
Date: Thu Aug 22 22:49:22 2013 +0800
merge & fix hello.py
甚至还有人丧心病狂地把lg配置成了:
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
来看看git lg的效果:
每个仓库的Git配置文件都放在.git/config文件中:
别名就在[alias]后面,要删除别名,直接把对应的行删掉即可。
而当前用户的Git配置文件放在用户主目录下的一个隐藏文件.gitconfig中:
342



被折叠的 条评论
为什么被折叠?



