版本库

创建版本库

通过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 id1094adb...,于是就可以指定回到未来的某个版本:

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

Git教程-LMLPHP

分支和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 commitgit 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其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。

添加远程库

  1. 首先,登陆GitHub,然后,在右上角找到“Create a new repo”按钮,创建一个新的仓库

  2. 在Repository name填入learngit,其他保持默认设置,点击“Create repository”按钮,就成功地创建了一个新的Git仓库

  3. 根据GitHub的提示,在本地的learngit仓库下运行命令:

    git remote add origin git@github.com:【GitHub账户名】/【远程库名】.git
    
  4. 下一步,就可以把本地库的所有内容推送到远程库上:

    git push -u origin master
    
  5. 由于远程库是空的,我们第一次推送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

分支管理

创建与合并分支

理论过程
  1. 一开始的时候,master分支是一条线,Git用master指向最新的提交,再用HEAD指向master,就能确定当前分支,以及当前分支的提交点‘

                      HEAD
                        │
                        ▼
                     master
                        │
                        ▼
    ┌───┐    ┌───┐    ┌───┐
    │   │───▶│   │───▶│   │
    └───┘    └───┘    └───┘
    
  2. 每次提交,master分支都会向前移动一步,这样,随着你不断提交,master分支的线也越来越长。

  3. 当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev

                     master
                        │
                        ▼
    ┌───┐    ┌───┐    ┌───┐
    │   │───▶│   │───▶│   │
    └───┘    └───┘    └───┘
                        ▲
                        │
                       dev
                        ▲
                        │
                      HEAD
    
  4. 不过,从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变

  5. 假如我们在dev上的工作完成了,就可以把dev合并到master上。Git怎么合并呢?最简单的方法,就是直接把master指向dev的当前提交,就完成了合并

  6. 合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下了一条master分支

实战过程
  1. 首先,我们创建dev分支,然后切换到dev分支:

    git checkout -b dev
    Switched to a new branch 'dev'
    
  2. git checkout命令加上-b参数表示创建并切换,相当于以下两条命令:

    git branch dev
    git checkout dev
    
  3. 然后,用git branch命令查看当前分支。git branch命令会列出所有分支,当前分支前面会标一个*号。

    git branch
    * dev
      master
    
  4. dev分支的工作成果合并到master分支上。git merge命令用于合并指定分支到当前分支。合并后,再查看readme.txt的内容,就可以看到,和dev分支的最新提交是完全一样的。

    git merge dev
    Updating d46f35e..b17d20e
    ...
    
  5. 合并完成后,就可以放心地删除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教程-LMLPHP

这种情况下,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教程-LMLPHP

用带参数的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>可以删除一个远程标签。
07-18 15:35