layout: post
title: Git指令整理&代码版本管理
subtitle: 多人协同开发代码版本管理流程
date: 2019-09-09
author: NSX
header-img: img/post-bg-ios9-web.jpg
catalog: true
tags:
- 技术
- 教程
- Git


Git指令整理&代码版本管理

Git可视化学习可以参考 🌳🚀 CS Visualized: Useful Git Commands,基本上看完就会了!!!

  1. Git是什么?
  2. 基本命令(init clone add commit push pull)
  3. 分支(brach checkout merge rebase)
  4. 如何使用Git进行团队协作
  5. 其他命令(前置拉取更新)

    1. Git是什么?

  • Linus(Linux之父)花了两周时间自己用C写了一个分布式版本控制系统,这就是Git!
  • Git是目前世界上最先进的分布式版本控制系统。

工作原理 / 流程:

2019-09-09-Git指令介绍&版本管理 - 图1

  • Workspace:工作区
  • Index / Stage:暂存区
  • Repository:仓库区(或本地仓库)
  • Remote:远程仓库

2. 基本命令

2.1. git config: 环境设置命令

通常情况下,安装完Git后的第一件事就是设置用户名称邮件地址。每一个Git的提交都会使用这些信息,如果不设置则无法进行提交。

  1. $ git config --global user.name "goto456" // 设置用户名称
  2. $ git config --global user.email "goto456@126.com" // 设置邮件地址

使用--global参数表示设置了全局的环境,如果想对与特定的项目使用不同的用户名和邮件地址,则可已在该项目目录下不使用--global参数设置不同的用户名和邮件地址。

git config --list命令可以列出当前Git所有的配置信息。

2.2. git init: 初始化本地仓库

获取一个 Git 仓库有2中方法:

  1. 本地初始化一个仓库
  2. 从远程克隆一个仓库到本地

对于第1种方式,如果想对本地现有的一个项目用 Git 来管理,可以直接进入该项目的目录下执行如下命令,就可以将其初始化成一个 Git 仓库了。

  1. $ git init

2.3. git clone: 克隆远程仓库到本地

对于第二种方式,也是最常用的方式,比如你在 GitHub 上(或者其他代码托管网站)已经建立了一个项目,你就可以将该项目从远程克隆到本地,这就有了该项目在本地的 Git 仓库。

  1. $ git clone git@github.com:goto456/leetcode.git // 通过ssh方式克隆
  2. $ git clone https://github.com/goto456/leetcode.git // 通过https方式克隆

以上2种方式有如下区别:

1. https方式:不管是谁,只要拿到该项目的 url 可以随便 clone,但是在 push 到远程的时候需要验证用户名和密码;
2. ssh方式:需要现将你电脑的SSH key(SSH公钥)添加到GitHub(或者其他代码托管网站)上,这样在 clone 项目和 push 项目到远程时都不需要输入用户名和密码。

如何生成SSH key,参见下一条命令:ssh-keygen

2.4. ssh-keygen: 生成SSH公钥

生成公钥之前先检查系统中是否已经有了公钥,公钥一般在~/.ssh/目录下。如果该目录下存在id_rsa.pub文件,这就是公钥(id_rsa 文件是私钥);如果不存在此文件,甚至连.ssh目录都不存在,则需要用 ssh-keygen 命令来生成。如下所示:

  1. $ ssh-keygen -t rsa
  2. $ ssh-keygen -t rsa C youremail@example.com // 然后一直按回车键

这就可以生成 SSH key 了,id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人。

登录github,打开” settings”中的SSH Keys页面,然后点击“Add SSH Key”,填上任意title,在Key文本框里黏贴idrsa.pub文件的内容。_PS: 秘钥所在目录 ~/.ssh

2.5. git status 查看当前状态

可以在任何时候使用该命令查看当前的状态。一般会显示当前所处的分支,以及当前工作区是否干净,是否有文件要提交。

2.6. git add: 添加到暂存区

无论你新增了一个文件或者对已有的文件进行了修改,都需要将其添加到暂存区,然后提交到版本库。

2.7. git commit: 提交到版本库

该命令是将添加到暂存去的变更提交到版本库,主要有一下几个常用的:

  1. $ git commit -m "变更的说明信息" //标准用法,提交到版本库并填写相关说明信息
  2. $ git commit -am "变更的说明信息" //加上-a参数,则不需要上一步的git add过程,可以直接将修改过的变更直接提交到版本库,但不会把新增的文件提交

注:第2条命令一般在不想把新增的文件提交到版本库时用的比较多

执行完git commit命令提交到版本库后,一次简单的流程就算走完了。接下来可以通过git log命令来查看所有的提交历史。

2.8. git log: 查看提交历史

如果发现提交了一个错误版本,想回退到上个版本时,就可以通过该命令来查看所有的历史版本,以选择回退到历史的哪个版本。

  1. $ git log //查看历史版本
  2. $ git log –-pretty=oneline
  3. $ git log --graph //以图形的方式查看历史(这个我比较常用,能很好的显示各个分支之间的关系)

2.9. git diff: 显示变更内容

当你对文件进行了修改,想查看进行了哪些修改时,可以通过该命令查看。
git diff命令会显示修改的文件中哪些内容进行了修改,包括新增了哪些内容,删除了哪些内容。

  1. $ git diff //后面不接参数,表示显示所有修改文件的变更
  2. $ git diff README.md //后面接文件名,表示只显示该文件的变更

git比较两个分支的文件的差异

  1. git diff branch1 branch2 --stat //显示出所有有差异的文件列表
  2. git diff branch1 branch2 文件名(带路径) //显示指定文件的详细差异
  3. git diff branch1 branch2 //显示出所有有差异的文件的详细差异

2.10. git reset: 回退版本

常用于回退版本或者在各个版本间进行跳跃切换。 我们可以先用 git log 看一下当前历史版本,如果要回退到前一个版本,则只需要输入:git reset HEAD~1,如果需要回退到前2个版本,命令是:git reset HEAD~2,回退到前n个版本就是:git reset HEAD~n

注意:在 Git 中,用 **HEAD** 表示指向当前版本的指针

如果需要回退到任何一个版本,则需要替换成该版本的 commit id 就可以了,例如:

  1. git reset a8336834b50daafa0793370

一般情况下会加一个 **--hard** 参数:**git reset --hard HEAD~1****git reset --hard a8336834b50daafa0793370**,表示回退到某个版本并且丢弃调工作区进行的修改,而不加该参数表示回退到某个版本但保留工作区的修改。

再次提交-强制:

  1. git push -u origin master -f

在重置之前可以通过从master创建一个分支来维护当前的本地提交:

  1. # 新建一个分支,并切换到该分支
  2. git checkout -b [branch]
  3. # 下载远程仓库的所有变动,而不尝试合并或rebase任何东西
  4. git fetch --all
  5. # 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致
  6. git reset --hard origin/master

2.11. git push: 推送本地分支到远程

当修改完成,本地的改动都已经提交到本地库,则可以将本地分支推送到远程代码库了。

命令:git push origin master
origin 表示远程代码库的一个别名(也可以修改为其他名字,可通过 git remote 修改),master 表示需要推送的分支名称。

如果,push 的过程中提示当前分支进度落后于远程的分支,则需要通过 git pull 命令来拉取远程最新状态和本地分支进行合并,完成之后再 push 到远程就可以了。

如果本地新建了一个分支,如何提交分支数据到远程服务器?

  1. git push origin <local_branch_name>:<remote_branch_name>

例如:

  1. git push origin branch_abc: branch_abc

2.12. git pull: 拉取远程分支到本地并合并

一般是本地分支的进度落后于远程分支时,需要使用该命令(pull = fetch + merge)

命令:git pull origin master
origin 表示远程代码库的一个别名(也可以修改为其他名字,可通过 git remote 修改),master 表示需要拉取合并的分支名称。

常用 **git pull --rebase origin master** 用 rebase 的方式进行,不会产生 merge 保持分支干净、整洁

PS:强制拉取更新

  • 回到远程仓库的状态
  • 抛弃本地所有的修改,回到远程仓库的状态。
  1. # 下载远程仓库的所有变动,而不尝试合并或rebase任何东西
  2. git fetch --all
  3. # 将主分支重置为您刚刚获取的内容
  4. git reset --hard origin/master

3. 分支

3.1. git branch: 分支操作

命令:git branch 用于显示本地所有分支以及当前所在哪个分支

命令:git branch branchName 用于新建名为 branchName 的新分

命令:git branch -d branchName 用于删除名为 branchName 的分支。

命令:git branch -D branchName 用于强制删除分支。

删除一个远程分支:

  1. (main)$ git push origin --delete my-branch

你也可以:

  1. (main)$ git push origin :my-branch

3.2. git checkout: 分支间切换

该命令除了进行分支间切换功能外,还可以用来丢弃工作区中的修改内容,这里就不作介绍了,仅介绍分之间的切换功能。

命令:git checkout branchName 用于从当前分支切换到名为 branchName 的分支上。

命令:git checkout -b branchName 用于新建名为 branchName 的分支并切换到该分支上。

从别人正在工作的远程分支签出(checkout)一个分支:

  1. # 首先, 从远程拉取(fetch) 所有分支:
  2. git fetch --all
  3. # 假设你想要从远程的daves分支签出到本地的daves
  4. git checkout --track origin/daves
  5. # --track 是 git checkout -b [branch] [remotename]/[branch] 的简写)
  6. # 这样就得到了一个daves分支的本地拷贝, 任何推过(pushed)的更新,远程都能看到.

3.3. git merge: 合并分支

该命令用于合并两个分支。

命令:**git merge branchName** 用于将名为 branchName 的分支合并到当前分支。

有两种合并方式:

  1. fast-forward 方式合并:
    命令:git merge dev

2019-09-09-Git指令介绍&版本管理 - 图2

  1. 非fast-forward 方式合并:
    命令:git merge dev

2019-09-09-Git指令介绍&版本管理 - 图3

注意两种方式的区别:fast-forward 方式仅仅是移动了 HEAD 指针,而非 fast-forward 方式则是新建了一个节点

3.4. git rebase: 分支的变基

命令:git rebase master 将当前分支 rebase 到 master 分支
命令:git rebase master dev 将 dev 分支 rebase 到 master 分支

这种操作很难用语言解释,我用一个图来说明其与 merge 操作的区别:

2019-09-09-Git指令介绍&版本管理 - 图4
由图可知,rebase 操作相当于将 C3 节点拿下来换了一个位置重新放置。最后不会产生合并的痕迹,所有分支都是同一条直线。

3.5 rebase合并多个提交commit

当你的项目充满了无用的 commit 纪录,如果有一天线上出现了紧急问题,你需要回滚代码,却发现海量的 commit 需要一条条来看。

rebase用途:合并多次提交纪录,然后再merge到主干

  1. git rebase -i [startpoint] [endpoint]
  2. `-i`的意思是`--interactive`,即弹出交互式的界面让用户编辑完成合并操作
  3. `[startpoint] [endpoint]`则指定了一个编辑区间
  4. 如果不指定`[endpoint]`,则该区间的终点默认是当前分支
  1. 我们来合并最近的 4 次提交纪录,执行:
  1. git rebase -i HEAD~4
  1. 这时候,会自动进入 vi 编辑模式:
    将后面两个改成squash,就是合并到第一个上去
  1. p cacc52da add: qrcode
  2. s f072ef48 update: indexeddb hack
  3. s 4e84901a feat: add indexedDB floder
  4. s 8f33126c feat: add test2.js
  • 4次提交的信息会倒序排列,最上面的是第四次提交,最下面的是最近一次提交。
    • 有几个命令需要注意一下:
    • pick:保留该commit(缩写:p)
    • reword:保留该commit,但我需要修改该commit的注释(缩写:r)
    • edit:保留该commit, 但我要停下来修改该提交(不仅仅修改注释)(缩写:e)
    • squash:将该commit和前一个commit合并(缩写:s)
    • fixup:将该commit和前一个commit合并,但我不要保留该提交的注释信息(缩写:f)
    • exec:执行shell命令(缩写:x)
    • drop:我要丢弃该commit(缩写:d)
  1. 按照如上命令来修改你的提交纪录,然后wq保存退出,Git会压缩提交历史。
  2. 最后,修改一下合并后的commit的描述信息,编辑完保存即可完成了.
  3. 如果你想放弃这次压缩的话,执行以下命令:

    1. git rebase --abort
  4. 同步到远程仓库
    若强制推送提示后出现 “You are not allowed to force push code to a protected branch on this project.”
    解决方案: https://github.com/LeachZhou/blog/issues/11
    解决方案: https://backlog.com/git-tutorial/cn/stepup/stepup2_8.html

    1. git push -u origin master
    2. git push -u origin master -f # 强制push
    1. E:\xxx>git push -f origin master
    2. Total 0 (delta 0), reused 0 (delta 0)
    3. remote: GitLab: You are not allowed to force push code to a protected branch on this project.
    4. To http://git.xxx.cn/xxx/xxx
    5. ! [remote rejected] master -> master (pre-receive hook declined)
    6. error: failed to push some refs to 'http://xxx@git.xxx.cn/xxx/xxx.git'
  5. git放弃修改,强制覆盖本地代码
    ``` // 从远程拉取所有内容 git fetch —all

// reset 本地代码 git reset —hard origin/master

// 重启拉取对齐 git pull

  1. <a name="NDtqi"></a>
  2. ### 3.6 提交 Merge Request
  3. 当完成功能后提交到远程后,需要经过下述流程:
  4. 1. 提交 Merge Request:通过「+Create Merge Request」按钮创建一个 Merge Request;
  5. 1. 必须指定「Assignee」:指定需要 review 你代码的同学,禁止指定自己;
  6. 1. 更改「Target branch」:改变为需要合并进去的目标分支;
  7. 1. 设置合并后删除被合并分支的选项:勾选「Remove source branch when merge request is accepted.」选项,在合并完成后删除源分支,控制分支总数量;
  8. 1. 使用「Submit Merge Request」提交合并请求。
  9. 完成上述设置后,相关同学将会收到邮件通知,此时可进入 GitLab 进行 code review。<br />如果发现问题则对问题代码进行点评并拒绝关闭申请,反之则通过合并申请。<br />合并申请通过后留下的 Merge Request 记录,也就记录了 code reviewer 。
  10. <a name="5c8f3a97"></a>
  11. ## 4. 多人协同开发代码版本管理流程
  12. 在团队协作过程中一般会有多个分支,比如有默认的 master 分支,有用于开发的 dev 分支,还有用于测试的 test 分支,用于对外发布的 release 分支,以及每个开发人员开发不同功能时用到的 feature_xx 分支等等。
  13. 公司中一般是用 GitLab 搭建的代码托管服务,几个人的小团队也可以自己搭建。
  14. 每个团队业务不一样,分支数量的设置也会不一样,下面我介绍一下我们团队的分支设置,以及普通开发人员和项目 leader 对不同分支的不同权限以及不同的操作。
  15. ![](https://ningshixian.github.io/resources/images/%E5%A4%9A%E4%BA%BA%E5%8D%8F%E5%90%8C%E5%BC%80%E5%8F%91%E4%BB%A3%E7%A0%81%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86%E6%B5%81%E7%A8%8B.png#crop=0&crop=0&crop=1&crop=1&id=jvG4X&originHeight=440&originWidth=898&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
  16. <a name="c7918e22"></a>
  17. ### 4.1 分支设置
  18. 我们常用的分支有3个(master 分支、dev 分支、test 分支)以及若干个 feature_xx 分支。
  19. 1. `master` 分支:是主分支,是最终上线代码的分支,该分支被设置被保护分支(锁住),普通开发人员没有权限操作,只有团队 leader 有合并的权限;
  20. 2. `dev` 分支:是用于开发的分支,该分支被设置为默认 clone 的分支,也用于合并到 master 之前进行测试的分支,普通开发人员从远程 clone 到本地的默认分支,可以进行合并等操作;
  21. 3. `test` 分支:是用于测试的分支,测试人员可以将自己开发分支中的修改合并到 test 分支在测试环境进行测试,一般该分支不合并到任何分支;
  22. 4. `feature_xx` 分支:是用户开发自己模块功能的特征分支,可以叫 feature_login, feature_ui, feature_payment 等与开发的功能相关的名称,该分支上的功能开发完、测试无误后可合并到 dev 分支上。
  23. <a name="d3e97ddc"></a>
  24. ### 4.2 普通开发人员的操作
  25. [将master分支内容合并到dev分支](https://blog.csdn.net/qq_34206198/article/details/52055395)
  26. 普通开发人员,一般按照如下几个步骤来进行开发、测试工作就可以了:
  27. 1. 将远程 dev 分支 clone 到本地,例如:`git clone git@github.com:goto456/test.git`;
  28. 2. 从 dev 分支拉出(新建)自己的 feature 分支用于开发,例如:`git checkout -b feature_login`;
  29. 3. 在自己的 feature 分支上进行开发工作;
  30. 4. 开发完了用 add、commit 等操作提交到当前分支;
  31. 5. 如果需要在测试环境进行测试,则将远程 test 分支拉到本地,例如:`git branch test origin/test`;
  32. 6. 将自己的 feature 分支合并到 test 分支,并将 test 分支 push 到远程,例如:`git rebase test`, `git checkout test`, `git merge feature_login`, `git push origin test`;**(注意:我们推荐用 rebase 来合并,以保证分支的整洁、美观)**
  33. 7. 通过公司的发布平台将远程 test 分支发布到测试环境进行测试;
  34. 8. 如果测试没问题或者开始就不需要测试,这可以直接将当前 feature 分支合并到 dev 分支,并 push 到远程库,例如:`git rebase dev`, `git checkout dev`, `git merge feature_login`, `git push origin dev`;**(注意:我们推荐用 rebase 来合并,以保证分支的整洁、美观)**
  35. 9. 这时表示该功能已经开发完成了,代码的 review 以及发布,需要团队 leader 在合并到 master 操作时进行;这时可以删除了自己的 feature 分支,例如:`git branch -d feature_login`;
  36. 10. **如果在 push 到远程的时候提示需要先 pull 时,我们推荐使用 rebase 的方式:**`**git pull --rebase**`** 以保持分支的整洁、美观。**
  37. <a name="a453c249"></a>
  38. ### 4.3 团队 leader 的操作
  39. 因为只有 leader 有操作 master 分支的权限,所以需要完成 dev 分支到 master 分支的合并,以及后续打 tag 和正式上线发布的工作:
  40. 1. 先切换到 dev 分支,并拉取最新的状态,例如:`git checkout dev`, `git pull --rebase origin dev`;
  41. 2. 进行代码 review 等过程后,合并到 master 分支,例如:`git rebase master`, `git checkout master`, `git merge dev`;**(注意:我们推荐用 rebase 来合并,以保证分支的整洁、美观)**
  42. 3. 为本次完成的版本打上标签,例如:`git tag v1.0 -m "release version 1.0"`;
  43. 4. 将本地合并后的 master 分支以及标签 push 到远程库,例如:`git push orgin master --tags`。
  44. <a name="3b95ca57"></a>
  45. ## 5. 其他命令
  46. <a name="d755bd13"></a>
  47. ### 查看分支的版本历史

git log git reflog # 显示当前分支的最近几次提交 git log –-pretty=oneline git log -S [keyword] # 搜索提交历史,根据关键词

开头的即是commit id号

  1. <a name="e9ad8e2e"></a>
  2. ### 删除上一次提交(commit)
  3. 如果你需要删除推了的提交(pushed commits),你可以使用下面的方法。可是,这会不可逆的改变你的历史,也会搞乱那些已经从该仓库拉取(pulled)了的人的历史。简而言之,如果你不是很确定,千万不要这么做。
  4. ```python
  5. $ git reset --hard HEAD^
  6. $ git push -f [remote] [branch]

如果你还没有推到远程, 把Git重置(reset)到你最后一次提交前的状态就可以了(同时保存暂存的变化):

  1. git reset --soft HEAD~1

我需要提交到一个新分支,但错误的提交到了main

在main下创建一个新分支,不切换到新分支,仍在main下:

  1. (main)$ git branch my-branch

把main分支重置到前一个提交:

  1. (main)$ git reset --hard HEAD^

签出(checkout)刚才新建的分支继续工作:

  1. (main)$ git checkout my-branch

commit写错-撤销重写

或者说,把暂存的内容添加到上一次的提交(commit)

  1. git commit --m '提交信息'
  2. git commit --amend -m '修改后的提交信息'

强制 push 报错

E:\xxx>git push -f origin master Total 0 (delta 0), reused 0 (delta 0) remote: GitLab: You are not allowed to force push code to a protected branch on this project. To http://git.xxx.cn/xxx/xxx ! [remote rejected] master -> master (pre-receive hook declined) error: failed to push some refs to ‘http://xxx@git.xxx.cn/xxx/xxx.git’

问题:若强制推送 push -f 提示 You are not allowed to force push code to a protected branch on this project.
解决方案: Gitlab强制推送提示”You are not allowed to force push code to a protected branch on this project.” #11
PS:由于当前分支的版本低于远程分支的版本,强制推送—force 将新的代码覆盖掉远程仓库版本代码,这样子就达到了撤销远程仓库代码一样的效果

意外硬重置(hard reset),如何找回修改的内容

如果你意外的做了 git reset —hard, 你通常能找回你的提交(commit), 因为Git对每件事都会有日志,且都会保存几天。

  1. git reflog

你将会看到一个你过去提交(commit)的列表, 和一个重置的提交。选择你想要回到的提交(commit)的SHA,再重置一次:

  1. git reset --hard SHA1234

这样就完成了。

★★强制拉取更新

  1. # 新建一个分支,并切换到该分支
  2. git checkout -b [branch]
  3. # 下载远程仓库的所有变动,而不尝试合并或rebase任何东西
  4. git fetch --all
  5. # 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致
  6. git reset --hard origin/master

★★git 添加 gitignore 规则无效

在使用git的时候我们有时候需要忽略一些文件或者文件夹。我们一般在仓库的根目录创建.gitignore文件

在提交之前,修改.gitignore文件,添加需要忽略的文件。然后再做 add commit push

但是有时在使用过称中,需要对.gitignore文件进行再次的修改。

这次我们需要清除一下缓存cache,才能是.gitignore 生效。

具体做法:

  1. git rm -r --cached . #清除缓存
  2. git add . #重新trace file
  3. git commit -m "update .gitignore" #提交和注释
  4. git push origin master #可选,如果需要同步到remote上的话

这样就能够使修改后的.gitignore生效。

参考1参考2

★★常见问题

ssh登录出现异常警告:WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!

原因分析:

It is also possible that a host key has just been changed.

之前,与远程库建立首次连接时,双方相互记录了对方的公钥(ssh基于非对称密钥技术),在ssh服务主机重装系统后,公钥改变了,而ssh连接仍以旧版本公钥,自然是无法与新系统连接的。

解决方案:

删除~/.ssh/known_hosts文件,或者如果你可以判断出known_hosts中原ssh服务器的公钥,删去那部分,然后后再次建立新的连接,即可获得新的公钥。

参考

Git使用教程:最详细、最傻瓜、最浅显、真正手把手教!
简明 Git 教程。浅显易懂,快速入门
45 个 Git 经典操作场景,专治不会合代码