版本库
创建版本库
通过git init
命令把目录变成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 file
1 file changed, 2 insertions(+)
create mode 100644 readme.txt
简单解释一下git commit
命令,-m
后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义的,这样你就能从历史记录里方便地找到改动记录。
git commit
命令执行成功后,1 file changed
:1个文件被改动(我们新添加的readme.txt文件);2 insertions
:插入了两行内容(readme.txt有两行内容)。
状态和差异【git status、git diff】
运行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
被修改过了,但还没有准备提交的修改。
用git diff
这个命令看修改:
git diff readme.txt
小结
- 使用
git status
命令随时掌握工作区的状态。 - 如果
git status
告诉你有文件被修改过,用git diff
可以查看修改内容。
版本回退
在Git中,我们用git log
命令查看历史记录。如果嫌输出信息太多,可以试试加上--pretty=oneline
参数
git log --pretty=oneline
1094adb7b9b3807259d8cb349e7df1d4d6477073 (HEAD -> master) append GPL
e475afc93c209a690c39c13a46716e8fa000c366 add distributed
eaadf4e385e865d25c48e7ca9c8395c3f7dfaef0 wrote a readme file
把当前版本append GPL
回退到上一个版本add distributed
,就可以使用git reset
命令(^的次数代表回退多少版本):
git reset --hard HEAD^
找到对应版本的commit id
是1094adb...
,于是就可以指定回到未来的某个版本:
git reset --hard 1094a
Git提供了一个命令git reflog
用来记录你的每一次命令:
git reflog
e475afc HEAD@{1}: reset: moving to HEAD^
1094adb (HEAD -> master) HEAD@{2}: commit: append GPL
e475afc HEAD@{3}: commit: add distributed
eaadf4e HEAD@{4}: commit (initial): wrote a readme file
现在总结一下:
HEAD
指向的版本就是当前版本,因此,Git允许我们在版本的历史之间穿梭,使用命令git reset --hard commit_id
。- 穿梭前,用
git log
可以查看提交历史,以便确定要回退到哪个版本。 - 要重返未来,用
git reflog
查看命令历史,以便确定要回到未来的哪个版本。
工作区和暂存区
工作区(Working Directory)
就是你在电脑里能看到的目录,比如我的learngit
文件夹就是一个工作区
版本库(Repository)
工作区有一个隐藏目录.git
,这个不算工作区,而是Git的版本库。
Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master
,以及指向master
的一个指针叫HEAD
。
分支和HEAD
的概念我们以后再讲。
前面讲了我们把文件往Git版本库里添加的时候,是分两步执行的:
- 第一步是用
git add
把文件添加进去,实际上就是把文件修改添加到暂存区; - 第二步是用
git commit
提交更改,实际上就是把暂存区的所有内容提交到当前分支。
因为我们创建Git版本库时,Git自动为我们创建了唯一一个master
分支,所以,现在,git commit
就是往master
分支上提交更改。
撤销修改
git checkout -- file
可以丢弃工作区的修改:
git checkout -- readme.txt
命令git checkout -- readme.txt
意思就是,把readme.txt
文件在工作区的修改全部撤销,这里有两种情况:
一种是readme.txt
自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
一种是readme.txt
已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。
总之,就是让这个文件回到最近一次git commit
或git add
时的状态。
git checkout -- file
命令中的--
很重要,没有--
,就变成了“切换到另一个分支”的命令,我们在后面的分支管理中会再次遇到git checkout
命令。
一般情况下,你通常直接在文件管理器中把没用的文件删了,或者用rm
命令删了:
rm test.txt
这个时候,Git知道你删除了文件,因此,工作区和版本库就不一致了,git status
命令会立刻告诉你哪些文件被删除了
-
确实要从版本库中删除该文件,那就用命令
git rm
删掉,并且git commit
:git rm test.txt
-
另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本:
git checkout -- test.txt
git checkout
其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
添加远程库
-
首先,登陆GitHub,然后,在右上角找到“Create a new repo”按钮,创建一个新的仓库
-
在Repository name填入
learngit
,其他保持默认设置,点击“Create repository”按钮,就成功地创建了一个新的Git仓库 -
根据GitHub的提示,在本地的
learngit
仓库下运行命令:git remote add origin git@github.com:【GitHub账户名】/【远程库名】.git
-
下一步,就可以把本地库的所有内容推送到远程库上:
git push -u origin master
-
由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
删除远程库
如果添加的时候地址写错了,或者就是想删除远程库,可以用git remote rm <name>
命令。使用前,建议先用git remote -v
查看远程库信息:
git remote -v
origin git@github.com:【GitHub账户名】/【远程库名】.git (fetch)
origin git@github.com:【GitHub账户名】/【远程库名】.git (push)
然后,根据名字删除,比如删除origin
:
git remote rm origin
从远程库克隆
远程库已经准备好了,下一步是用命令git clone
克隆一个本地库:
git clone git@github.com:【GitHub账户名】/【远程库名】.git
GitHub给出的地址不止一个,还可以用https://github.com/【GitHub账户名】/【远程库名】.git
这样的地址。实际上,Git支持多种协议,默认的git://
使用ssh,但也可以使用https
等其他协议。
使用https
除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用ssh
协议而只能用https
。
分支管理
创建与合并分支
理论过程
-
一开始的时候,
master
分支是一条线,Git用master
指向最新的提交,再用HEAD
指向master
,就能确定当前分支,以及当前分支的提交点‘HEAD │ ▼ master │ ▼ ┌───┐ ┌───┐ ┌───┐ │ │───▶│ │───▶│ │ └───┘ └───┘ └───┘
-
每次提交,
master
分支都会向前移动一步,这样,随着你不断提交,master
分支的线也越来越长。 -
当我们创建新的分支,例如
dev
时,Git新建了一个指针叫dev
,指向master
相同的提交,再把HEAD
指向dev
,就表示当前分支在dev
master │ ▼ ┌───┐ ┌───┐ ┌───┐ │ │───▶│ │───▶│ │ └───┘ └───┘ └───┘ ▲ │ dev ▲ │ HEAD
-
不过,从现在开始,对工作区的修改和提交就是针对
dev
分支了,比如新提交一次后,dev
指针往前移动一步,而master
指针不变 -
假如我们在
dev
上的工作完成了,就可以把dev
合并到master
上。Git怎么合并呢?最简单的方法,就是直接把master
指向dev
的当前提交,就完成了合并 -
合并完分支后,甚至可以删除
dev
分支。删除dev
分支就是把dev
指针给删掉,删掉后,我们就剩下了一条master
分支
实战过程
-
首先,我们创建
dev
分支,然后切换到dev
分支:git checkout -b dev Switched to a new branch 'dev'
-
git checkout
命令加上-b
参数表示创建并切换,相当于以下两条命令:git branch dev git checkout dev
-
然后,用
git branch
命令查看当前分支。git branch
命令会列出所有分支,当前分支前面会标一个*
号。git branch * dev master
-
把
dev
分支的工作成果合并到master
分支上。git merge
命令用于合并指定分支到当前分支。合并后,再查看readme.txt
的内容,就可以看到,和dev
分支的最新提交是完全一样的。git merge dev Updating d46f35e..b17d20e ...
-
合并完成后,就可以放心地删除
dev
分支了:git branch -d dev Deleted branch dev (was b17d20e).
因为创建、合并和删除分支非常快,所以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>
解决冲突
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 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.txt
no changes added to commit (use "git add" and/or "git commit -a")
Git用<<<<<<<
,=======
,>>>>>>>
标记出不同分支的内容,我们修改如下后保存
再提交:
git add readme.txt
git commit -m "conflict fixed"
[master cf810e4] conflict fixed
用带参数的git log
也可以看到分支的合并情况:
git log --graph --pretty=oneline --abbrev-commit
* cf810e4 (HEAD -> master) conflict fixed
|\
| * 14096d0 (feature1) AND simple
* | 5dc6824 & simple
|/
* b17d20e branch test
最后,删除feature1
分支:
git branch -d feature1
Deleted branch feature1 (was 14096d0).
Bug分支
Git提供了一个stash
功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:
git stash
Saved working directory and index state WIP on dev: f52c633 add merge
多人协作
- 查看远程库信息,使用
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中打标签非常简单,首先,切换到需要打标签的分支上:
git branch
* dev
master
git checkout master
Switched to branch 'master'
然后,敲命令git tag <name>
就可以打一个新标签:
git tag v1.0
可以用命令git tag
查看所有标签:
git tag
v1.0
比方说要对add merge
这次提交打标签,它对应的commit id是f52c633
,敲入命令:
git tag v0.9 f52c633
还可以创建带有说明的标签,用-a
指定标签名,-m
指定说明文字:
git tag -a v0.1 -m "version 0.1 released" 1094adb
用命令git show <tagname>
可以看到说明文字:
git show v0.1
如果标签打错了,也可以删除:
git tag -d v0.1
Deleted tag 'v0.1' (was f15b0dd)
一次性推送全部尚未推送到远程的本地标签:
git push origin --tags
Total 0 (delta 0), reused 0 (delta 0)
To github.com:michaelliao/learngit.git
* [new tag] v0.9 -> v0.9
如果标签已经推送到远程,要删除远程标签就麻烦一点,先从本地删除:
git tag -d v0.9
Deleted tag 'v0.9' (was f52c633s)
小结
- 命令
git push origin <tagname>
可以推送一个本地标签; - 命令
git push origin --tags
可以推送全部未推送过的本地标签; - 命令
git tag -d <tagname>
可以删除一个本地标签; - 命令
git push origin :refs/tags/<tagname>
可以删除一个远程标签。