Git

Git 简介

  1. 分布式版本控制系统,能够记录操作的历史记录
  2. 完成安装后需要的配置:
$ git config --global user.name "Your Name"
$ git config --global user.email "email@example.com"

git config 命令的 --global 参数,表示使用的这台机器上所有的 Git 仓库都会使用这个配置

创建版本库

  1. 选择某个文件夹,打开 Git Bash here
$ mkdir learngit
$cd learngit
$pwd
<!--将会显示出当前文件夹目录-->

pwd 命令用于显示当期目录

  1. 通过 git init 命令把这个目录变成Git可以管理的仓库
$ git init
Initialized empty Git repository in /Users/michael/learngit/.git/

当前目录下多了一个.git的目录,没有看到.git目录,那是因为这个目录默认是隐藏的,用ls -ah命令就可以看见。

把文件添加到版本库

  1. 把编辑的文件放到learngit目录下
  2. 用命令git add告诉Git,把文件添加到仓库:
$ git add readme.txt
  1. 使用命令git commit告诉Git,把文件提交到仓库
$ git commit -m "wrote a readme.text"

-m 后面输入的是本次提交的说明,可以输入任意内容

记录修改及查看

修改记录

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 add readme.txt

$ git commit -m "add distributed"

版本回退

  1. git log 命令:告诉我们修改的历史记录,显示从最近到最远的提交日志;加上 --pretty=oneline 参数,显示一堆版本号
$ git log --pretty=oneline
3628164fb26d48395383f8f31179f24e0882e1e0 append GPL
ea34578d5496d7dd233c827ed32a8cd576c5ee85 add distributed
cb926e7ea50ad11b8f9e909c05226233bf755030 wrote a readme file
  1. git reset: 返回某个指定的版本

Git 必须知道当前版本是哪个版本,在Git中, 用 HEAD 表示当前版本,上一个版本就是 HEAD^,上上一个版本就是 HEAD^^,当然往上100个版本写100个 ^ 比较容易数不过来,所以写成 HEAD~100

$ git reset -hard HEAD^

// 回到指定的版本,包括前一个和后一个
$ git reset -hard (版本号) //版本号没必要写全,前几位就可以了,Git会自动去找。

Git 的版本回退速度非常快,因为 Git 在内部有个指向当前版本的 HEAD 指针,当回退版本的时候,Git 仅仅是把 HEAD 从指向老的版本。

  1. git reflog: 记录操作的每一条命令(包括版本号),用 git reflog 查看命令历史,以便确定要回到未来的哪个版本。所以,可以通过此命令找到版本回退前的版本号,将版本再次恢复到回退前的版本。

工作区和暂存区

在 Git 中,有工作区和暂存区的概念:

  • 工作区:在电脑看到的目录

  • 版本库:工作区有一个隐藏目录 .git,这个不算工作区,而是 Git 的版本库。

Git的版本库里存了很多东西,其中最重要的就是称为 stage(或者叫 index)的暂存区,还有 Git 为我们自动创建的第一个分支 master,以及指向 master 的一个指针叫 HEAD。

git 版本库

git add 把文件添加进去,实际上就是把文件修改添加到暂存区;

git commit 提交更改,实际上就是把暂存区的所有内容提交到当前分支。

一旦提交后,工作区就是“干净”的,此时暂存区没有任何文件:

commit 后的暂存区

管理修改

  1. Git 跟踪管理的是修改,并非文件
  2. 提交后,通过 git diff HEAD -- readme.txt 命令可以查看工作区和版本库里面最新版本的区别
  3. 每次修改,如果不 add 到暂存区,那就不会加入到 commit 中。

撤销修改

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 reset HEAD file ,把暂存区的修改撤销掉(unstage),重新放回工作区

$ git reset HEAD readme.txt
Unstaged changes after reset:
M       readme.txt

git reset 命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用 HEAD 时,表示最新的版本。

撤销暂存区的修改后,再次撤销工作区的修改即可。即先进行 reset,在进行 checkout

删除文件

当删除工作区文件后,工作区和版本库就不一致了,此时分为两种情况:

一是确实要从版本库中删除该文件,那就用命令 git rm 删掉,并且 git commit

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

远程仓库

添加远程库

首先需要在远程库上创建一个 repo。

  • 在本地的所需要关联的仓库下运行命令
$ git remote add origin https://github.com/xiao-ming9/learngit.git

//下一步把本地库的所有内容推到远程库上
$ git push -u origin master

把本地库的内容推送到远程,用 git push 命令,实际上是把当前分支 master 推送到远程。由于远程库是空的,我们第一次推送 master 分支时,加上了 -u 参数,Git 不但会把本地的 master 分支内容推送的远程新的 master 分支,还会把本地的 master 分支和远程的 master 分支关联起来,在以后的推送或者拉取时就可以简化命令。

以后只要本地做了提交,以后只需要 $git push origin master

删除远程库

如果发现添加错了远程库,或者想要删除远程库,可以使用 git remote rm <name> 命令。在使用前,建议使用 git remote -v 查看远程库的信息:

$ git remote -v
origin  git@github.com:michaelliao/learn-git.git (fetch)
origin  git@github.com:michaelliao/learn-git.git (push)

然后,根据名字删除,比如删除 origin

$ git remote rm origin

此处的“删除”其实是解除了本地和远程的绑定关系,并不是物理上删除了远程库。远程库本身并没有任何改动。要真正删除远程库,需要登录到GitHub,在后台页面找到删除按钮再删除。

从远程库克隆

在GitHub上创建一个存储库之后,在本地的仓库下运行命令git clone

//ssh协议
$ git clone git@github.com:xiao-ming9/gitskills.git
//https协议
$ git clone https://github.com/xiao-ming9/gitskills.git

分支管理

在 Git 里,master 分支叫主分支。HEAD严格来说不是指向提交,而是指向 mastermaster 才是指向提交的,所以,HEAD 指向的就是当前分支。

一开始的时候,只有 master 分支,Git 用 master 指针指向最新的提交,在用 HEAD 指向 master,就能确定当前分支,以及当前分支的提交点:

git 分支管理1

每次提交,master 分支都会向前移动一步,这样,随着不断提交,master 分支的线也越来越长。

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

分支管理2

Git 创建一个分支很快,因为除了增加一个 dev 指针,修改 HEAD 的指向,工作区的文件都没有任何变化!

不过,从现在开始,对工作区的修改和提交就是针对 dev 分支了,比如新提交一次后,dev 指针往前移动一步,而 master 指针不变:

git 分支管理3

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

git分支管理4

所以 Git 合并分支也很快!就改改指针,工作区内容也不变!

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

git分支管理5

创建与合并分支

  1. 创建dev分支,然后切换到 dev 分支
$ git checkout -b dev
Switched to a new branch 'dev'
  • git checkout 加上 -b 参数表示创建并切换,相当于:
$ git branch dev //创建分支
$ git checkout dev//合并分支
  1. 查看当前分支
$ git branch
*dev
 master
  • git branch命令会列出所有分支,当前分支前面会标一个 * 号。
  1. 切换回 master 分支
$ git checkout master
S witched to branch 'master'
  1. 把 dev 分支的工作成果合并到 master 分支上
$ git merge dev
Updating d17efd8..fec145a
Fast-forward
 readme.txt |    1 +
 1 file changed, 1 insertion(+)

git merge 命令用于合并指定分支到当前分支。合并后,

注意到上面的 Fast-forward 信息,Git告诉我们,这次合并是“快进模式”,也就是直接把 master 指向 dev 的当前提交,所以合并速度非常快。当然,也不是每次合并都能 Fast-forward

  1. 删除 dev 分支使用 git branch -d 命令:
$ git branch -d dev

另外,为了与 git checkout -- <file> 区分,新版的 Git 提供了新的 git switch 命令来切换分支分支

创建并切换到新的 dev 分支,使用:

git switch -c dev

直接切换到已有的 master 分支,可以使用:

git switch master

使用新的 switch 命令比较容易理解。

解决冲突

  • 当 Git 无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。用git log --graph 命令可以看到分支合并图。
$ git log --graph --pretty=oneline --abbrev-commit
*   59bc1cb conflict fixed
|\
| * 75a857c AND simple
* | 400b400 & simple
|/
* fec145a branch test
...

分支管理策略

  • 合并分支时,如果可能,Git会用 Fast forward 模式,但这种模式下,删除分支后,会丢掉分支信息。
  • 如果要强制禁用 Fast forward 模式,Git 就会在 merge 时生成一个新的 commit,这样,从分支历史上就可以看出分支信息。因为这里要创建一个新的 commit,所以加上了 -m 参数,把 commit 信息描述进去。
$ git merge --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
 readme.txt |    1 +
 1 file changed, 1 insertion(+)
  • 可以用带参数的 git log 查看分支情况
$ git log --graph --pretty=onrline --abbrev-commit

*   e1e9c68 (HEAD -> master) merge with no-ff
|\  
| * f52c633 (dev) add merge
|/  
*   cf810e4 conflict fixed
...

当不使用 Fast forward 模式时, merge 后就像这样:

不使用Fast forward 的 merge

  • 分支策略

master 分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;干活都在 dev 分支上,也就是说,dev 分支是不稳定的,到某个时候,比如1.0版本发布时,再把 dev 分支合并到 master 上,在 master 分支发布 1.0 版本

Bug分支

  • 修改 bug 前,若当前内容还不想提交,使用 git stash 把当前内容封藏
$ git stash
  • 使用 git stash list 查看封藏内容
$ git stash list
stash@{0}: WIP on dev: 6224937 add merge
  • 恢复方法:
  1. git stash apply:恢复后,stash 内容并不删除,你需要用 git stash drop 来删除;
  2. git stash pop:恢复的同时把 stash 内容也删了
  • 可以多次 stash,恢复的时候,先用 git stash list 查看,然后恢复指定的 stash,用命令:
$ git stash apply stash@{0}

另外,当修复了 bug 后,当前还在开发的 dev 分支也需要引入修复后的代码,此时可以使用 Git 提供的 cherry-pick 命令,它能耦复制一个特定的提交到当前的分支:

$ git branch
* dev
  master
$ git cherry-pick 4c805e2
[master 1d4b803] fix bug 101
 1 file changed, 1 insertion(+), 1 deletion(-)

此时就会把修复 bug 的 commit 复制到当前的分支上。

Feature分支

  • 开发一个新 feature,最好新建一个分支;

  • 如果要丢弃一个没有被合并过的分支,可以通过 git branch -D <name> 强行删除。

多人协作

  • 当从远程仓库克隆时,实际上 Git 自动把本地的 master 分支和远程的 master 分支对应起来了,并且,远程仓库的默认名称是 origin。
  1. 查看远程库信息:git remote (-v)
$ git remote
origin

//git remote -v 显示更详细的信息
$ git remote -v
origin  https://github.com/xiao-ming9/learngit.git (fetch)
origin  https://github.com/xiao-ming9/learngit.git (push)

上面显示了可以抓取和推送的origin的地址。如果没有推送权限,就看不到 push 的地址。
2. 推送分支

  • 推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git 就会把该分支推送到远程库对应的远程分支上:
git push origin master

如果要推送其他分支,比如dev,就改成:

git push origin dev

一般来说,需要推送的分支有 master,dev 和 future 分支,bug 分支只用于本地修复 bug,一般不需要推送到远程库。

  1. 抓取分支

    当从远程库进行 clone 时,默认情况下,clone 下来的都是 master 分支,如果需要拉取其他分支的内容,可以使用 git checkout 命令:

    git checkout -b dev origin/dev
    
  • 通过使用 git pull 把最新的提交从 origin/dev 抓下来

  • 如果 git pull 失败了,原因是没有指定本地 dev 分支与远程 origin/dev 分支的链接,根据提示,设置 dev 和 origin/dev 的链接:

git branch --set-upstream dev origin/dev
  • 多人协作的工作模式通常是这样:
  1. 首先,可以试图用git push origin branch-name推送自己的修改;

  2. 如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并

  3. 如果合并有冲突,则解决冲突,并在本地提交;

  4. 没有冲突或者解决掉冲突后,再用git push origin branch-name推送就能成功!

  5. 如果git pull提示“no tracking information”,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream branch-name origin/branch-name

在本地创建和远程分支对应的分支,使用git checkout -b branch-name origin/branch-name,本地和远程分支的名称最好一致;

Rebase

Rebase 用于把本地尚未 push 的提交历史整理成直线。使用 Merge 命令通常其 commit 信息会比较混乱,同时在 merge 时会有一次 merge 的 commit 信息,对比如下:

使用 merge:

使用 merge

使用 rebase:

使用rebase

rebase 的适用场景是在个人分支上开发时,当需要获取最新的 master 提交的时候,可以通过 rebase 来整理 commit 信息。不推荐在公共分支上使用 rebase,会导致其他在当前分支开发的人也需要执行 rebase 操作

常用指令

  • git rebase master 可以将dev分支合并到当前分支
  • git rebase –abort 放弃一次合并
  • 合并多次commit操作:
    1. git rebase -i dev
    2. 修改最后几次commit记录中的 pick 为 squash
    3. 保存退出,弹出修改文件,修改 commit 记录再次保存退出(删除多余的 change-id 只保留一个)
    4. git add .
    5. git rebase --continue

标签管理

创建标签

  1. 首先切换到需要打标签的分支上

  2. git tag <name> 创建一个新标签

     git tag v1.0
    
  3. 可以用命令 git tag 查看所有标签

    git tag
    
  • 默认标签是打在最新提交的 commit 上的,如果需要在之前的commit上打标签,可以找到历史提交的commit id,再打上标签即可
$ git log --pretty=oneline --abbrev-commit

$ git tag v0.9 [commit id]

可以使用 git show <tagname> 查看标签信息

可以创建带有说明的标签,用 -a 指定标签名,-m 指定说明文字

$ git tag -a v0.1 -m "说明内容" [commit id]

操作标签

  1. 删除标签:
$ git tag -d v0.1
Deleted tag 'v0.1' (was e078af9)
  1. 推送某个标签到远程:git push origin <tagname>,或者一次性推送:git push origin --tags
  2. 如果标签已经推送到远程,则应该先在本地删除,然后在从远程删除
$ git tag -d v0.9

$ git push origin :refs/tags/v0.9

使用码云

  • 国内的Git托管服务
  • 本地库可以与多个远程库互相同步,但是不能同名

自定义Git

  • 让 Git 显示颜色:git config --global color.ui true

忽略特殊文件

  • 在 Git 工作区的根目录下创建一个特殊的 .gitignore 文件,然后把要忽略的文件名填进去,然后把 .gitignore 提交到Git,Git 就会自动忽略这些文件。
  • 如果添加一个文件到 Git,添加不了,原因是这个文件被 .gitignore 忽略了,如果要添加:
  1. git add -f 文件名 强制添加
  2. 可以用 git check-ignore 命令检查哪个规则写错:
$ git check-ignore -v App.class
.gitignore:3:*.class   App.class

配置别名

  • 替换名字:

    $ git config –global alias.新名字 旧名字

#用st表示status,co表示commit,ci表示commit,br表示branch
$ git config --global alist.st status
$ git config --global alias.co checkout
$ git config --global alias.ci commit
$ git config --global alias.br branch
  • --global 参数是全局参数,也就是这些命令在这台电脑的所有 Git 仓库下都有用。

配置文件

  • 配置 Git 的时候,加上 --global 是针对当前用户起作用的,如果不加,那只针对当前的仓库起作用。
  1. 每个仓库的 Git 配置文件都放在 .git/config 文件中
$ cat .git/config
[core]
        repositoryformatversion = 0
        filemode = false
        bare = false
        logallrefupdates = true
        symlinks = false
        ignorecase = true
[branch "master"]
[remote "github"]
        url = git@github.com:xiao-ming9/learngit.git
        fetch = +refs/heads/*:refs/remotes/github/*
[remote "gitee"]
        url = git@gitee.com:xiao_ming9/learngit.git
        fetch = +refs/heads/*:refs/remotes/gitee/*
  1. 当前用户的 Git 配置文件放在用户主目录下的一个隐藏文件 .gitconfig
$cat .gitconfig
[user]
        name = xingziming
        email = 934933088@qq.com
[color]
        ui = true
  • 配置别名也可以直接修改这个文件,如果改错了,可以删掉文件重新通过命令配置。

参考文章

Git教程——廖雪峰的官方网站

GIT使用rebase和merge的正确姿势

git rebase 还是 merge的使用场景最通俗的解释

彻底搞懂 Git-Rebase