chapter 1: 如何创建版本库
- 初始化一个仓库
$ git init
- 添加文件到Git仓库的过程:
$ git add readme.txt
(没有消息提示, Unix哲学: 没有消息就是好消息)
$git commit -m 'write a readme file'
[master (root-commit) cb926e7] wrote a readme file
1 file changed, 2 insertions(+)
create mode 100644 readme.txt
chapter 2: 时光穿梭机
- 修改完文件后, 查看结果:
$ git status
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
git status命令可以让我们时刻掌握仓库当前的状态,上面的命令告诉我们,readme.txt被修改过了,但还没有准备提交的修改
-
"Changes not staged for commit": 表示修改没有add到暂存区中等待提交, 也就是修改还只存在工作区
-
经过'$ git add readme.txt'后, 执行'$ git status', 输出了如下日志:
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: readme.txt
"Changes to be committed": 表示修改已经add到了staged(暂存区), 等待被commit到HEAD master.
- 执行完'$ git commit - m 'add something'', 再执行"$ git status", 输出如下日志:
$ git status
On branch master
nothing to commit (working directory clean)
Git告诉我们当前没有需要提交的修改,而且,工作目录是干净(working directory clean)的
- 查看修改了什么内容
$ git diff readme.txt
section 1: 版本回退
- 查看历史记录
$ git log
$ git log
commit 3628164fb26d48395383f8f31179f24e0882e1e0
Author: Michael Liao askxuefeng@gmail.com
Date: Tue Aug 20 15:11:49 2013 +0800
append GPL
commit ea34578d5496d7dd233c827ed32a8cd576c5ee85
Author: Michael Liao askxuefeng@gmail.com
Date: Tue Aug 20 14:53:12 2013 +0800
add distributed
commit cb926e7ea50ad11b8f9e909c05226233bf755030
Author: Michael Liao askxuefeng@gmail.com
Date: Mon Aug 19 17:51:55 2013 +0800
wrote a readme file
git log命令显示从最新到最久的提交日志
如果想输出信息更见简洁:
$ git log --pretty=oneline
$ git log --pretty=oneline
3628164fb26d48395383f8f31179f24e0882e1e0 append GPL
ea34578d5496d7dd233c827ed32a8cd576c5ee85 add distributed
cb926e7ea50ad11b8f9e909c05226233bf755030 wrote a readme file
3628164fb26d48395383f8f31179f24e0882e1e0 是commit id(版本号).
每提交一个新版本, 实际上Git就会把它们自动串成一条时间线
- 如何将文件回退到上一个版本
Git中使用HEAD表示当前版本, 也就是最新提交的commit id 所在的版本. 上一个版本就是"HEAD^", 上上个版本就是"HEAD^^".
上100个版本就是"HEAD~100"
$ git reset --hard HEAD^
$ git reset --hard HEAD^
HEAD is now at ea34578 add distributed
此时, 你如果想回到最新的commit去. 只要找到那个版本的commit id(一般取前7位):
$ git reset --hard 3628164
就可以指定回到未来的某个版本!
Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git仅仅是把HEAD从指向“append GPL”:
git-head
改为指向“add distributed”:
git-head-move
然后顺便把工作区的文件更新了。所以你让HEAD指向哪个版本号,你就把当前版本定位在哪。
如果, 你在当前的命令行中找不到最新版本的commit id. 可以通过如下命令:
$ git reflog
$ git reflog
ea34578 HEAD@{0}: reset: moving to HEAD^
3628164 HEAD@{1}: commit: append GPL
ea34578 HEAD@{2}: commit: add distributed
cb926e7 HEAD@{3}: commit (initial): wrote a readme file
3628164就是你要找的那个版本commit id.
section 2: 工作区和暂存区
工作区: working directory
版本库: Reposity, 工作区中的那个'.git'目录, 版本库中存了许多东西, 最重要的就是被称为'stage(or index)'的暂存区, 还有Git为我们自动创建的第一个分支master, 以及指向master的一个指针叫HEAD
git-repo
暂存区: stage(或index), 作为版本库的最重要的部分存在与'.git'目录中.
把文件往Git版本库里添加的时候,是分两步执行的:
第一步是用“git add”把文件添加进去,实际上就是把文件修改添加到暂存区;
第二步是用“git commit”提交更改,实际上就是把暂存区的所有内容提交到当前分支;
因为我们创建Git版本库时,Git自动为我们创建了唯一一个master分支,所以,现在,commit就是往master分支上提交更改
从来没有add过的文件, 使用'git status'查看, 显示的是'Untracked files'
执行了git add命令后, 暂存区的状态如图:
git-stage
所以,git add命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行git commit就可以一次性把暂存区的所有修改提交到分支
执行了git commit命令后, 暂存区就没有内容了, 修改的文件已经被commit到了master上:
现在版本库变成了这样,暂存区就没有任何内容了:
git-stage-after-commit
section 3: 管理修改
为什么Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件。
你会问,什么是修改?比如你新增了一行,这就是一个修改,删除了一行,也是一个修改,更改了某些字符,也是一个修改,删了一些又加了一些,也是一个修改,甚至创建一个新文件,也算一个修改
如果你进行了如下操作:
第一次修改 -> git add -> 第二次修改 -> git commit
则只有第一次修改的内容被提交到了reposity中, 因为只有第一次修改被add到stage中
每次修改,如果不add到暂存区,那就不会加入到commit中
section 4: 撤销修改
- 丢弃工作区的修改:
$ git checkout -- readme.txt
命令git checkout -- readme.txt意思就是,把readme.txt文件在工作区的修改全部撤销,这里有两种情况:
一种是readme.txt自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
一种是readme.txt已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。
总之,就是让这个文件回到最近一次git commit或git add时的状态。
注意: '--'很重要, 如果没有, 将变成了"创建一个新分支"的命令
- 撤销掉暂存区的修改(unstage) , 重新放回到工作区
$ git reset HEAD readme.txt
Unstaged changes after reset:
M readme.txt
git reset命令既可以回退版本, 也可以把暂存区中的修改回退到工作区
还记得如何回退版本吗?
section 5: 删除文件
在本地删除了文件"rm text.txt", 立刻可以通过"git status"看到变化
- 如果你要从版本库中删除文件
$ git rm test.txt
rm 'test.txt'
$ git commit -m "remove test.txt"
[master d17efd8] remove test.txt
1 file changed, 1 deletion(-)
delete mode 100644 test.txt
- 如果你错删了文件, 把误删的文件恢复到最新版本:
$ git checkout -- test.txt
git checkout其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
chapter 3: 远程仓库
- 配置github
第1步:创建SSH Key。在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa和id_rsa.pub这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开Shell(Windows下打开Git Bash),创建SSH Key:
$ ssh-keygen -t rsa -C "youremail@example.com"
如果一切顺利的话,可以在用户主目录里找到.ssh目录,里面有id_rsa和id_rsa.pub两个文件,这两个就是SSH Key的秘钥对,id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人.
第2步:登陆GitHub,打开“Account settings”,“SSH Keys”页面:
然后,点“Add SSH Key”,填上任意Title,在Key文本框里粘贴id_rsa.pub文件的内容
chapter: 创建和合并分支
-
git中默认有一个主分支, 叫做master. HEAD严格来说不是指向提交, 而是指向master分支, 而master才是指向提交的. 所以HEAD就是指向当前的分支.
-
每一次提交, master分支都会向前移动一步
-
当你创建新的分支如dev时, Git新建了一个指针叫dev, 指向master相同的提交, 再把HEAD指向dev, 就表示当前分支在dev上:
git-br-create
从现在开始, 对工作区的修改和提交都市针对dev分支了, 比如新提交一次后, dev分支就向前移动一步, 而master指针不变:
git-br-dev-fd
假如我们在dev中的工作完成了, 就可以把dev合并到master上.
创建分支, 并切换到分支:
$ git checkout -b 15_learn_git_flow -t master
git checkout命令加上-b参数表示创建并切换,相当于以下两条命令:
$ git branch dev
$ git checkout dev
Switched to branch 'dev'
然后,用git branch命令查看当前分支:
$ git branch
- dev
master
git branch命令会列出所有分支,当前分支前面会标一个*号。
然后,我们就可以在dev分支上正常提交,比如对readme.txt做个修改,加上一行:
Creating a new branch is quick.
然后提交:
$ git add readme.txt
$ git commit -m "branch test"
[dev fec145a] branch test
1 file changed, 1 insertion(+)
现在,dev分支的工作完成,我们就可以切换回master分支:
$ git checkout master
Switched to branch 'master'
切换回master分支后,再查看一个readme.txt文件,刚才添加的内容不见了!因为那个提交是在dev分支上,而master分支此刻的提交点并没有变:
git-br-on-master
现在,我们把dev分支的工作成果合并到master分支上:
$ git merge dev
Updating d17efd8..fec145a
Fast-forward
readme.txt | 1 +
1 file changed, 1 insertion(+)
git merge命令用于合并指定分支到当前分支。合并后,再查看readme.txt的内容,就可以看到,和dev分支的最新提交是完全一样的。
注意到上面的Fast-forward信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度非常快。
当然,也不是每次合并都能Fast-forward,我们后面会将其他方式的合并。
合并完成后,就可以放心地删除dev分支了:
$ git branch -d dev
Deleted branch dev (was fec145a).
删除后,查看branch,就只剩下master分支了:
$ git branch
- master
因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。
小结
Git鼓励大量使用分支:
查看分支:git branch
创建分支:git branch name
切换分支:git checkout name
创建+切换分支:git checkout -b name
合并某分支到当前分支:git merge name
删除分支:git branch -d name
问题: 如何创建远程的分支?
- 先在本地创建分支
$ git checkout -b 18_learn_git_flow -t master
- 将本地的分支push到远程
$ git push origin 18_learn_git_flow
section 4: Bug分支
修复bug时,我们会通过创建新的bug分支进行修复,然后合并,最后删除;
当手头工作没有完成时,先把工作现场git stash一下,然后去修复bug,修复后,再git stash pop,回到工作现场。
查看全部的工作现场 :
$ git stash list
恢复工作现场:
方式一: 通过git stash apply stash@{0}, 但恢复后, stash内容并不删除, 需要使用git stash drop来删除
方式二: 通过 git stash pop, 恢复的同时把stash内容也删掉.
section 5: Feature分支
删除分支:
$ git branch -d feature1 --- 删除本地分支1, 如果该分支没有被merge request, 则不能删除
$ git branch -D feature1 --- 删除本地分支, 不管该branch有没有merge request
删除远程分支:
$ git push origin --delete 18_learn_git_flow
或者, 通过推送一个空的branch到远程达到删除的目的:
$ git branch -d branchName
$ git push origin :branchName
删除不存在对应远程分支的本地分支:
假设这样一种情况:
1. 我创建了本地分支b1并pull到远程分支 origin/b1;
2. 其他人在本地使用fetch或pull创建了本地的b1分支;
3. 我删除了 origin/b1 远程分支;
4. 其他人再次执行fetch或者pull并不会删除这个他们本地的 b1分支,运行 git branch -a 也不能看出这个branch被删除了,如何处理
使用$ git remote show origin 查看b1的状态
如果显示b1是stable, 使用 $ git remote prune origin 可以将其从本地版本库中删除
或者, 更简单的方式是, 使用 $ git fetch -p , 它在fetch之后删除掉没有与远程分支对应的本地分支
重命名本地分支:
$ git branch -m oldName newName
重命名远程分支, 其实也就是先删除远程分支, 然后将重命名后的本地分支推送上去:
$ git push --delete origin oldName
$ git branch -m oldName newName
$ git push origin newName
chapter: 多人协作
因此,多人协作的工作模式通常是这样:
首先,可以试图用git push origin branch-name推送自己的修改;
如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;
如果合并有冲突,则解决冲突,并在本地提交;
没有冲突或者解决掉冲突后,再用git push origin branch-name推送就能成功!
如果git pull提示“no tracking information”,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --track 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 --track branch-name origin/branch-name;
从远程抓取分支,使用git pull,如果有冲突,要先处理冲突。
chapter: 标签管理
发布一个版本时,我们通常先在版本库中打一个标签,这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。
Git的标签虽然是版本库的快照,但其实它就是指向某个commit的指针(跟分支很像对不对?但是分支可以移动,标签不能移动),所以,创建和删除标签都是瞬间完成的
命令git tag name用于新建一个标签,默认为HEAD,也可以指定一个commit id;
-a tagname -m "blablabla..."可以指定标签信息;
-s tagname -m "blablabla..."可以用PGP签名标签;
命令git tag可以查看所有标签;
$ git checkout master
$ git tag v1.0
$ git tag v1.0 36564232
$ git tag -a v1.0 -m "version 1.0 released" 36564232
查看tag的详情内容:
$ git show tagname
删除标签:
$ git tag -d v1.0
因为创建的标签都只存储在本地, 不会自动推送到远程, 所以, 打错了标签可以在本地安全删除.
如果要推送某个标签到远程, 使用命令:
$ git push origin tagname
或者, 一次性推送全部尚未推送到远程的本地标签:
$ git push origin --tags
删除远程标签(其实是推送一个空的tag到远程tag达到删除目的):
1) 先删除本地的标签: $ git tag -d v.0
2) 然后从远程删除: $ git push origin :refs/tags/v1.0
或者, 1.7以后可以这么做:
$ git push origin --delete tag v1.0
获取远程的tag:
$ git fetch origin tag <tagname>
小结
命令git push origin tagname可以推送一个本地标签;
命令git push origin --tags可以推送全部未推送过的本地标签;
命令git tag -d tagname可以删除一个本地标签;
命令git push origin :refs/tags/tagname可以删除一个远程标签。
网友评论