我是基于ChatGPT-turbo-3.5实现的AI助手,在此网站上负责整理和概括文章

本文介绍了版本控制工具的两种主要类型:集中式与分布式。集中式版本控制工具(如CVS、Subversion)依赖单一服务器存储所有版本数据,优点是便于管理,但存在单点故障风险,如服务器宕机或数据丢失问题。分布式版本控制工具(如Git)将代码仓库完整复制到每个客户端,允许多地协作,断网情况下仍可本地操作,且数据冗余性高,安全性更强。文章重点介绍了Git的简介、工作原理、安装配置步骤及常用命令,强调了其在现代软件开发中的重要性,并提供了官网及下载地址供读者参考。

# 版本控制工具

# 集中式版本控制工具

集中化的版本控制系统诸如 CVS,Subversion 以及 Perforce 等,都有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。多年以来,这已成为版本控制系统的标准做法

这种做法带来了许多好处,现在,每个人都可以在一定程度上看到项目中的其他人正在做些什么。而管理员也可以轻松掌控每个开发者的权限,并且管理一个集中化的版本控制系统;要远比在各个客户端上维护本地数据库来得轻松容易

事分两面,有好有坏。这么做最显而易见的缺点是中央服务器的单点故障

如果服务器宕机一小时,那么在这一小时内,谁都无法提交更新,也就无法协同工作

并不是说服务器故障了就没有办法写代码了,只是在服务器故障的情况下写的代码是没有办法得到保障的。试想 svn 中央服务器挂机一天,你还拼命写了天代码,其中 12 点之前的代码都是高质量可靠的,而且有很多闪光点。而 12 点之后的代码由于你想尝试一个比较大胆的想法,将代码改的面目全非了。这样下来你 12 点之前做的工作也都白费了。有记录的版本只能是 svn 服务器挂掉时保存的版本!

要是中央服务器的磁盘发生故障,碰巧没做备份,或者备份不够及时,就会有丢失数据的风险。最坏的情况是彻底丢失整个项目的所有历史更改记录,而被客户端偶然提取出来的保存在本地的某些快照数据就成了恢复数据的希望。但这样的话依然是个问题,你不能保证所有的数据者都已经有人事先完整提取出来过。只要整个项目的历史记录被保存在单一位置,就有丢失所有历史更新记录的风险

集中式总结

  • 优点:代码存放在单一的服务器上,便于项目管理
  • 缺点:服务器宕机、故障

# 分布式版本控制工具

于是分布式版本控制系统面世了。在这类系统中,像 Git、BitKeeper 等,客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来。这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。因为每一次的提取操作,实际上都是一次对代码仓库的完整备份

分布式的版本控制系统在管理项目时存放的不是项目版本与版本之间的差异,它存的是索引(所需磁盘空间很少,所以每个客户端都可以放下整个项目的历史记录)

分布式的版本控制系统出现之后,解决了集中式版本控制系统的缺陷:

  • 1)断网的情况下也可以进行开发(因为版本控制是在本地进行的)
  • 2)使用 git 业进行团队协作,哪怕 github 挂了,每个客户端保存的也都是整个完整的项目(包含历史记录的!!)

# Git 简介

  • Git 是 Linux 发明者 Linus 开发的一款新时代的版本控制系统 ,版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统,对于软件开发领域来说版本控制是最重要的一环,而 Git 毫无疑问是当下最流行、最好用的版本控制系统。

  1. 图中左侧为工作区,右侧为版本库。在版本库中标记为 index 的区域是暂存区(stage, index),标记为 master 的是 master 分支所代表的目录树。
  2. 图中我们可以看出此时 HEAD 指针实际是指向 master 分支的一个“游标”。所以图示的命令中出现 HEAD 的地方可以用 master 来替换。
  3. 图中的 objects 标识的区域为 Git 的对象库,实际位于 .git/objects 目录下。
  4. 当对工作区修改(或新增)的文件执行 git add 命令时,暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,而该对象的 ID 被记录在暂存区的文件索引中。
  5. 当执行提交操作 git commit 时,暂存区的目录树写到版本库(对象库)中,master 分支会做相应的更新。即 master 指向的目录树就是提交时暂存区的目录树。
  6. 当执行 git reset HEAD 命令时,暂存区的目录树会被重写,被 master 分支指向的目录树所替换,但是工作区不受影响。
  7. 当执行 git rm --cached <file> 命令时,会直接从暂存区删除文件,工作区则不做出改变。
  8. 当执行 git checkout . 或者 git checkout -- <file> 命令时,会用暂存区全部或指定的文件替换工作区的文件。这个操作很危险,会清除工作区中未添加到暂存区的改动。
  9. 当执行 git checkout HEAD . 或者 git checkout HEAD <file> 命令时,会用 HEAD 指向的 master 分支中的全部或者部分文件替换暂存区和以及工作区中的文件。这个命令也是极具危险性的,因为不但会清除工作区中未提交的改动,也会清除暂存区中未提交的改动。

# 官网及下载地址

# 安装与设置

# Git 安装

官网地址:https://git-scm.com/

查看 GNU 协议,可以直接点击下一步

选择 Git 安装位置,要求是非中文并且没有空格的目录,然后下一步

Git 选项配置,推荐默认设置,然后下一步

Git 安装目录名,不用修改,直接点击下一步

Git 的默认编辑器,建议使用默认的 Vim 编辑器,然后点击下一步

默认分支名设置,选择让 Git 决定,分支名默认为 master,下一步

修改 Git 的环境变量,选第一个,不修改环境变量,只在 Git Bash 里使用 Git

选择后台客户端连接协议,选默认值 OpenSSL,然后下一步

配置 Git 文件的行末换行符,Windows 使用 CRLF,Linux 使用 LF,选择第一个自动转换,然后继续下一步

选择 Git 终端类型,选择默认的 Git Bash 终端,然后继续下一步

选择 Git pull 合并的模式,选择默认,然后下一步

选择 Git 的凭据管理器,选择默认的跨平台的凭据管理器,然后下一步

其他配置,选择默认设置,然后下一步

实验室功能,技术还不成熟,有已知的 bug,不要勾选,然后点击右下角的 Install 按钮,开始安装 Git

点击 Finsh 按钮,Git 安装成功!

右键任意位置,在右键菜单里选择 Git Bash Here 即可打开 Git Bash 命令行终端

在 Git Bash 终端里输入 git --version 查看 git 版本,如图所示,说明 Git 安装成功

# Git 初始化配置

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

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

    git --version             #Git 版本
    git update-git-for-windows         #升级版本
    git config --system [--unset] user.name 用户名    #设置/删除用户签名(全局)
    git config --system [--unset] user.email 邮箱     #设置/删除用户签名(全局)
    git config --global [--unset] user.name 用户名    #设置/删除用户签名(用户)
    git config --global [--unset] user.email 邮箱     #设置/删除用户签名(用户)
    git config [--unset] user.name 用户名             #设置/删除用户签名(项目)
    git config [--unset] user.email 邮箱              #设置/删除用户签名(项目)
    git config --unset credential.helper              #重置凭证
    git config --system gui.encoding utf-8             #编码设置(全局)
    git config --system i18n.commitEncoding utf-8      #编码设置(全局)
    git config --system i18n.logoutputencoding utf-8   #编码设置(全局)
    git config --global gui.encoding utf-8             #编码设置(用户)
    git config --global i18n.commitEncoding utf-8      #编码设置(用户)
    git config --global i18n.logoutputencoding utf-8   #编码设置(用户)
    git config gui.encoding utf-8                      #编码设置(项目)
    git config i18n.commitEncoding utf-8               #编码设置(项目)
    git config i18n.logoutputencoding utf-8            #编码设置(项目)
    git config --system alias.别名 命令参数  #设置命令别名(全局)
    git config --global alias.别名 命令参数  #设置命令别名(用户)
    git config alias.别名 命令参数           #设置命令别名(项目)
    git config --system --list              #查看所有配置(全局)
    git config --global --list              #查看所有配置(用户)
    git config --list                       #查看所有配置(项目)
    git init                                #初始化本地库

# Git 常用配置

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

    # 打开配置文件
    git config --global --edit 
    # 设置主分支名称
    git config --global init.defaultBranch main
    # 用户名和邮箱也可以填写别的(只要是用户名和邮箱格式就 OK)。
    git config --global user.email "[email protected]"
    git config --global user.name  "fulsun"
    # 忽略 ssl 证书错误 禁用 ssl 校验
    git config --global http.sslVerify false
    # 保存密码
    # git config --global --unset credential.helper store
    git config  --global credential.helper store
    git config --global color.ui auto
    # 默认编辑器为nano
    git config --global core.editor vim
    # Git 大小写敏感
    git config --global core.ignorecase false
    #  换行符设置:Linux 建议设置 input
    #  在 Windows 下建议为 true: 把结束符 CRLF 转换成 LF,而在拉取代码时把 LF 转换成 CRLF
    git config --global core.autocrlf true
    # 拒绝提交混合换行符的代码
    git config --global core.safecrlf true
    # 不区分文件的执行权限
    git config --globa core.filemode false
    # 设置不转义中文字符(命令行或 bash 运行 git 命令时显示的文件路径不用编码模式显示)
    git config --global core.quotepath false
    # 配置 Http 的 Post 缓存容量
    git config --global http.postBuffer 524288000
    # 配置提交时的编码
    git config --global i18n.commitencoding utf-8
    # 配置日志输出的编码
    git config --global i18n.logoutputencoding utf-8
    git config --global gui.encoding utf-8
    git config --global init.defaultBranch main
    # 如果上游分支的名称与本地分支名称不同,则拒绝推送
    git config --global push.default simple
    git config --global safe.directory "*"
    git config --global http.sslverify false
    git config --global protocol.file.allow always
  • 全局过滤列表

    # 全局过滤列表
    git config --global core.excludesfile /e/fulsun/Documents/.gitignore_global
    #.gitignore_global
    target/
    !.mvn/wrapper/maven-wrapper.jar
    
    ## STS ##
    .apt_generated
    .classpath
    .factorypath
    .project
    .settings
    .springBeans
    
    ## IntelliJ IDEA ##
    .idea
    *.iws
    *.iml
    *.ipr
    
    ## JRebel ##
    rebel.xml
    .rebel.xml.bak
    
    ## MAC ##
    .DS_Store
    
    ## Other ##
    logs/
    temp/

# git 别名

git config --global alias.co checkout
git config --global alias.ci commit
git config --global alias.br branch
git config --global alias.st status
git config --global alias.unstage 'reset HEAD'
git config --global alias.last 'log -1'


git config --global alias.lg1 "log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(bold yellow)%d%C(reset)' --all"

git config --global alias.lg2 "log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n''%C(white)%s%C(reset) %C(dim white)-   %an%C(reset)' --all"

git config --global alias.lg '!"git lg1"'

# 下载问题

git config --global http.postBuffer 104857600   # 100MB
# Git 缓存值设置为 5G 5242880000
git config --global http.postBuffer 5242880000
git config --global https.postBuffer 5242880000

# status 乱码问题

 git config --global core.quotepath false  		# 显示 status 编码
# 将所有文本文件的编码统一为 UTF-8 
$ git config --global gui.encoding utf-8			# 图形界面编码
$ git config --global i18n.commit.encoding utf-8	# 提交信息编码
$ git config --global i18n.logoutputencoding utf-8	# 输出 log 编码
export LESSCHARSET=utf-8 # (windows下为:set LESSCHARSET=utf-8)
# 最后一条命令是因为 git log 默认使用 less 分页,所以需要 bash 对 less 命令进行 utf-8 编码

# Git 命令操作

  • 命令行(win + R),输入 cmd 后,输入 git 可以判断是否成功安装。

  • 网上找了个图,别人整理的一张图,很全很好,借来用下。下面详细解释一些常用命令。

  • 在掌握具体命令前,先理解下 HEADHEAD,它始终指向当前所处分支的最新的提交点。你所处的分支变化了,或者产生了新的提交点,HEAD 就会跟着改变。

# Init/Clone

# 初始化本地库
git init

# 浅尝 clone: depth 的参数控制着 clone 最近多少次的提交。
git clone <版本库的网> <本地目录>
git clone --depth=1 http://xxx.git
git clone --depth=1 [email protected]:xxx.git

# 拉取完整当前分支
git fetch --unshallow

# 追踪所有远程分支
git remote set-branches origin '*'

# 拉取所有远程分支
git fetch -v
  • 以上 2 种 clone 方式有如下区别:

    • https 方式:不管是谁,只要拿到该项目的 url 可以随便 clone,但是在 push 到远程的时候需要验证用户名和密码;
    • ssh 方式:需要现将你电脑的 SSH key(SSH 公钥)添加到 GitHub(或者其他代码托管网站)上,这样在 clone 项目和 push 项目到远程时都不需要输入用户名和密码。
    • 通常来说,Git 协议下载速度最快,SSH 协议用于需要用户认证的场合。各种协议优劣的详细讨论请参考 官方文档
  • git clone 支持多种协议,除了 HTTP(s)以外,还支持 SSH、Git、本地文件协议等,下面是一些例子。

    git clone http[s]://example.com/path/to/repo.git
    git clone ssh://example.com/path/to/repo.git
    git clone git://example.com/path/to/repo.git
    git clone /opt/git/project.git
    git clone file:///opt/git/project.git
    git clone ftp[s]://example.com/path/to/repo.git
    git clone rsync://example.com/path/to/repo.git
    # SSH 协议还有另一种写法
    git clone [user@]example.com:path/to/repo.git

# git status

  • git status 这个命令顾名思义就是查看状态,这个命令可以算是使用最频繁的一个命令了,建议大家没事就输入下这个命令,来查看你当前 git 仓库的一些状态。一般会显示当前所处的分支,以及当前工作区是否干净,是否有文件要提交。

  • 当你克隆远程仓库到本地后,通过该命令查看当前状态时,显示信息如下图所示:

  • 当你修改了一个文件后,再用该命令查看当前状态,它会提示你把当前的变更添加到暂存区或者丢弃该变更,显示信息如下图所示:

  • 当你新增一个文件后,用该命令查看当前状态,它会显示新增的文件状态是未跟踪,并且提示用 git add 命令将其添加到暂存区,显示信息如下图所示:

# git add

新建立一个文件,输入 git status 后查看 ,Untracked files ,就是说 a.md 这个文件还没有被跟踪输入 git add a.md,然后再输入 git status : 提示以下文件 Changes to be committed , 意思就是 a.md 文件等待被提交,当然你可以使用 git rm --cached 这个命令去移除这个缓存.

# 添加当前目录的所有文件到暂存区 
git add .
# 添加指定目录到暂存区,包括子目录
git add [dir]
# 添加指定的文件到暂存区
git add [file1] 
# 使用命令,删除暂存区该文件(只是删除暂存区,不影响工作区)
git rm --cached hello.txt

# git commit

# -m 表示添加一个版本日志信息,不写此参数也会打开日志信息的文件框。一般带参数
# 提交暂存区到本地仓库,message 代表说明消息
git commit -m [message] [文件名]
# 提交暂存区的指定文件到本地仓库
git commit [file1] -m [message]
# 使用一次新的 commit,替代上一次提交 
git commit --amend -m [message]

# 保存后,git 里是按照行维护文件, 会将本次修改行数信息进行提示
# 1 insertion(+), 1 deletion(-)

# git log

这个时候我们输入 git log 命令,查看所有产生的 commit 记录,所以可以看到已经产生了一条 commit 记录,而提交时候的附带信息叫 ‘first commit’ 。

git reflog # 查看精简版本信息(引用日志)
git log		# 查看详细版本信息
git log --pretty=oneline                   #一行化
git log --oneline                          #一行化并精简 hash
git log --oneline --decorate               #查看当前分支所指对象
git log --oneline --decorate --graph --all #查看所有分支历史
git log --graph  # 以图形的方式查看历史(这个我比较常用,能很好的显示各个分支之间的关系)
  • git log

  • --graph 左边显示出了分支的图形

# git diff

  • diff 显示变更内容, 当你对文件进行了修改,想查看进行了哪些修改时,可以通过该命令查看。

  • git diff 命令会显示修改的文件中哪些内容进行了修改,包括新增了哪些内容,删除了哪些内容。

    # 后面不接参数,表示显示所有修改文件的变更
    git diff
    # 显示工作区与当前分支最新 commit 之间的差异
    git diff HEAD
    # 后面接文件名,表示只显示该文件的变更
    git diff README.md
  • 例如:我对 README.md 文件进行了修改,删除了 1 行,新增了 2 行,然后用该命令查看进行了哪些修改,如下图所示: (“+” 表示新增的内容,“-” 表示删除的内容)

# git add & git commit

看到这里估计很多人会有疑问,我想要提交直接进行 commit 不就行了么,为什么先要再 add 一次呢?

首先 git add 是先把改动添加到一个「暂存区」,你可以理解成是一个缓存区域,临时保存你的改动,而 git commit 才是最后真正的提交。这样做的好处就是防止误提交。

当然也有办法把这两步合并成一步,为了实现一次性进行 add 和 commit,我们可以使用 git commit -a 命令。这个命令的含义是将当前 Git 跟踪的所有已修改的文件一并提交,并且会自动将这些文件添加到暂存区。

# -a 参数的作用是将所有已修改的文件添加到暂存区,
# -m 参数用来设置提交信息。
git commit -am "Initial commit"

# reset

  • reset 命令把当前分支指向另一个位置,并且相应的变动工作区和暂存区。

命令 说明
git reset --soft [commithash] 只改变提交点,暂存区和工作目录的内容都不改变
git reset --mixed [commithash] 只改变提交点,同事改变暂存区的内容
git reset --hard [commithash] 暂存区和工作区的内容都会被修改到与提交点完全一致的状态
git reset –hard HEAD 让工作区回到上次提交的状态
  • 我们可以先用 git log 看一下当前历史版本,如下图所示:

  • 如果要回退到前一个版本,则只需要输入:git reset HEAD~1,执行完再看一下历史版本:

  • 我们已经回退到前一个版本了。如果需要回退到前 2 个版本,命令是:git reset HEAD~2,回退到前 n 个版本就是:git reset HEAD~n

  • 如果需要回退到任何一个版本,则需要替换成该版本的 commit id 就可以了,例如:git reset a8336834b50daafa0793370,执行完再看一下历史:

# revert

  • git revert 用一个新提交来消除一个历史提交所做的任何修改。

# revert 与 reset 的区别

  • git revert 是用一次新的 commit 来回滚之前的 commit

  • git reset 是直接删除指定的 commit。

  • 在回滚这一操作上看,效果差不多。但是在日后继续 merge 以前的老版本时有区别。

    • 因为 git revert 是用一次逆向的 commit“中和”之前的提交,因此日后合并老的 branch 时,导致这部分改变不会再次出现,减少冲突。
    • 但是 git reset 是之间把某些 commit 在某个 branch 上删除,因而和老的 branch 再次 merge 时,这些被回滚的 commit 应该还会被引入,产生很多冲突。
  • git reset 是把 HEAD 向后移动了一下,而 git revert 是 HEAD 继续前进,只是新的 commit 的内容和要 revert 的内容正好相反,能够抵消要被 revert 的内容。

# git tag

  • 我们在客户端开发的时候经常有版本的概念,比如 v1.0、v1.1 之类的,不同的版本肯定对应不同的代码,所以我一般要给我们的代码加上标签,这样假设 v1.1 版本出了一个新 bug,但是又不晓得 v1.0 是不是有这个 bug,有了标签就可以顺利切换到 v1.0 的代码,重新打个包测试了。

  • 所以如果想要新建一个标签很简单,比如 git tag v1.0 就代表我在当前代码状态下新建了一个 v1.0 的标签,输入 git tag 可以查看历史 tag 记录。

  • 想要切换到某个 tag 怎么办?也很简单,执行 git checkout v1.0 ,这样就顺利的切换到 v1.0tag 的代码状态了。

  • 截止到图中的最近一次提交,我们完成了 1.0 版本的开发,则可以通过以下命令为其打上版本的标签。

    • 命令:

      git tag v1.0 //为当前提交打上 v1.0 的标签
      git tag v1.0 ab1591eb4e06c1e93fdd50126b9fab8a88d89155 //为这个节点打上 v1.0 的标签

    • 图中可以看出 v1.0 标签已经打上了。 如果发现标签打错了,想删除某个标签,则可以通过如下命令来执行。 命令:git tag -d v1.0 删除 v1.0 标签

    • 如果想将标题推送到远程库,则可以使用如下命令来完成。 命令:git push origin --tags 将打的 tag 都推送到远程库

# git show

  • 显示信息, 可用于显示某次提交或者某个 tag 相关的信息。

  • 命令:git show commit_id 显示某次提交的详细信息

  • 命令:git show tag_name 显示某个 tag 的详细信息

# git blame

  • 查看文件每行的提交历史(追责), 可用于查看某个文件中的每一行是那次提交产生的,是谁提交的,什么时候提交的,提交的版本号是多少等等详细信息,在实际工作中方便对出问题的代码进行追责,找到产生 BUG 的责任人。

  • 命令:git blame file_name

  • 上图中可以看到 README.md 这个文件有 5 行,其中后 4 行都是我在 2018 年提交的,第 1 行是另外一个人在 2017 年提交的。

# git remote

为了便于管理,Git 要求每个远程主机都必须指定一个主机名。git remote 命令就用于管理主机名。不带选项的时候,git remote 命令列出所有远程主机。

# 列出所有远程主机 -v 选项,可以参看远程主机的网址
git remote -v

# 添加远程主机。
git remote add <主机> <>

#修改别名的远程地址
git remote set-url --add 别名 远程地址   

# 远程主机的改名
git remote rename <原主机> <新主机>

# 克隆仓库
git clone 远程地址 # 克隆仓库时,主机命名为 origin

# 如果想用其他的主机名,需要用 git clone 命令的-o 选项指定。
git clone 远程地址 -o origin # 克隆仓库时,主机命名为 origin

# 查看该主机的详细信息 git remote show origin
git remote show <主机>

# 删除远程主机
git remote rm <主机>

# git fetch

一旦远程主机的版本库有了更新(Git 术语叫做 commit),需要将这些更新取回本地,这时就要用到 git fetch 命令。

git fetch <远程主机>

上面命令将某个远程主机的更新,全部取回本地。

git fetch 命令通常用来查看其他人的进程,因为它取回的代码对你本地的开发代码没有影响。

默认情况下,git fetch 取回所有分支(branch)的更新。如果只想取回特定分支的更新,可以指定分支名。

git fetch <远程主机> <分支>

比如,取回 origin 主机的 master 分支。

git fetch origin master

所取回的更新,在本地主机上要用 "远程主机名/分支名" 的形式读取。比如 origin 主机的 master,就要用 origin/master 读取。

git branch 命令的 -r 选项,可以用来查看远程分支,-a 选项查看所有分支。

$ git branch -r
origin/master

$ git branch -a
* master
  remotes/origin/master

上面命令表示,本地主机的当前分支是 master,远程分支是 origin/master

取回远程主机的更新以后,可以在它的基础上,使用 git checkout 命令创建一个新的分支。

git checkout -b newBrach origin/master

上面命令表示,在 origin/master 的基础上,创建一个新分支。

此外,也可以使用 git merge 命令或者 git rebase 命令,在本地分支上合并远程分支。

$ git merge origin/master
# 或者
$ git rebase origin/master

上面命令表示在当前分支上,合并 origin/master

# Git 分支操作

命令 说明
git branch 列出本地所有分支
git branch -r 列出所有远程分支
git branch -a 列出所有本地分支和远程分支
git branch [branch-name] 新建一个分支,但是停留在当前分支
git branch -b [branch-name] 新建一个分支并切换到该分支
git branch --track [branch][remote-branch] 新建一个分支,与指定的分支建立追踪关系
git checkout [branch-name] 切换到指定的分区,并更新工作区
git branch -d [branch-name] 删除分支
git push origin –delete [branch-name] 删除远程分支,不要加 origin/

# 什么是分支

在版本控制过程中,同时推进多个任务,为每个任务,我们就可以创建每个任务的单独分支。使用分支意味着程序员可以把自己的工作从开发主线上分离开来,开发自己分支的时候,不会影响主线分支的运行。对于初学者而言,分支可以简单理解为副本,一个分支就是一个单独的副本(分支底层其实也是指针的引用)

分支的好处:同时并行推进多个功能开发,提高开发效率,各个分支在开发过程中,如果某一个分支开发失败,不会对其他分支有任何影响。失败的分支删除重新开始即可

# 创建/查看分支

执行 git init 初始化 git 仓库之后会默认生成一个主分支 master ,也是你所在的默认分支,也基本是实际开发正式环境下的分支,一般情况下 master 分支不会轻易直接在上面操作的,你们可以输入 git branch 查看下当前分支情况:

git branch 分支名 [commithash]  #创建分支
# 查看的当前分支情况, * 号表示当前分支
git branch
git branch -v
git branch -vv      #查看本地分支与远程跟踪分支对应关系 方括号 [ ] 内的就是当前本地分支跟踪的 远程跟踪分支名
git branch -r  # 查看所有远程跟踪分支

# 修改分支

git branch -m oldName newName     #修改本地分支名

# checkout 分支

切换分支执行这个命令 git checkout [branchName], 那有人就说了,我要先新建再切换,未免有点麻烦,有没有一步到位的,聪明: git checkout -b [branchName] 这个命令的意思就是 新建分支,并且自动切换到分支

# 切换分支 
git checkout 分支名
# 创建并切换分支
git checkout -b [branchName]

# 删除分支

有新建分支,那肯定有删除分支,假如这个分支新建错了,或者 a 分支的代码已经顺利合并到 master 分支来了,那么 a 分支没用了, 就可以把 a 分支删除了。

有些时候可能会删除失败,比如如果 a 分支的代码还没有合并到 master,你执行 git branch -d a 是删除不了的,它会智能的提示你 a 分支还有未合并的代码,但是如果你非要删除,那就执行 git branch -D a 就可以强制删除 a 分支。

# 删除分支
git branch -d [branch]
# 强制删除 
git branch -D [branch]

如果远程主机删除了某个分支,默认情况下,git pull 不会在拉取远程分支的时候,删除对应的本地分支。这是为了防止,由于其他人操作了远程主机,导致 git pull 不知不觉删除了本地分支。但是,你可以改变这个行为,加上参数 -p 就会在本地删除远程已经删除的分支。

git pull -p

# merge 分支

  • merge 命令把不同的分支合并起来。如图,在实际开放中,我们可能从 master 分支中切出一个分支,然后进行开发完成需求,中间经过 R3, R4, R5 的 commit 记录,最后开发完成需要合入 master 中,这便用到了 merge。

    # merge 之前先拉一下远程仓库最新代码
    git fetch [remote]
    # 合并指定分区到当前分区
    git merge [branch]

  • A 同学在 a 分支代码写的不亦乐乎,终于他的功能完工了,并且测试也都 ok 了,准备要上线了,这个时候就需要把他的代码合并到主分支 master 上来,然后发布。git merge 就是合并分支用到的命令. 针对这个情况,需要先做两步,

    • 第一步是切换到 master 分支,如果你已经在了就不用切换了
    • 第二步执行 git merge a,意思就是把 a 分支的代码合并过来,不出意外, 这个时候 a 分支的代码就顺利合并到 master 分支来了。
    • 为什么说不出意外呢?因为这个时候可能会有冲突而合并失败。

# 正常合并

# 冲突合并

冲突产生的原因:合并分支时,两个分支在同一个文件的同一个位置有两套完全不同的修改。Git 无法替我们决定使用哪一个。必须人为决定新代码内容

# 解决冲突

# rebase 分支

  • rebase 又称为衍合,是合并的另外一种选择。

  • 在开始阶段,我们处于 new 分支上,执行 git rebase dev,那么 new 分支上新的 commit 都在 master 分支上重演一遍,最后 checkout 切换回到 new 分支。这一点与 merge 是一样的,合并前后所处的分支并没有改变。

  • git rebase dev,通俗的解释就是 new 分支想站在 dev 的肩膀上继续下去。rebase 也需要手动解决冲突

  • 使用说明:https://git-scm.com/docs/git-rebase

    • 不要通过 rebase 对任何已经提交到公共仓库中的 commit 进行修改,
    • 不要在 master 分支上 rebase,在 master 分支上 rebase 就会出现游离分支。
    • 正常做法是比如在 dev 分支开发,然后需要的话在 dev 分支 rebase,然后再 merge 到 master

# 准备示例

# 我们初始化一个项目
git init
# 制造一些提交
touch base.txt
git add .
git commit -m "add base"

touch 1.txt
git add .
git commit -m "add 1"

touch 2.txt
git add .
git commit -m "add 2"

touch 3.txt
git add .
git commit -m "add 3"

touch 4.txt
git add .
git commit -m "add 4"

touch 5.txt
git add .
git commit -m "add 5"

# 合并 commit

  • 命令 git rebase -i [startpoint] [endpoint]

    • -i 的意思是 --interactive,即弹出交互式的界面让用户编辑完成合并操作,
    • [startpoint] [endpoint] 则指定了一个编辑区间,如果不指定 [endpoint],则该区间的终点默认是当前分支 HEAD 所指向的 commit(注:该区间指定的是一个 前开后闭的区间)。
  • 合并多个 commit 为一个完整 commit

    # 在查看到了 log 日志后,我们运行以下命令:
    * 09b85cb - (17 minutes ago) add 5 - XXX (HEAD -> main)
    * 0f89d77 - (17 minutes ago) add 4 - XXX
    * 6c82fb4 - (17 minutes ago) add 3 - XXX
    * 4c4e075 - (17 minutes ago) add 2 - XXX
    * eda0aa7 - (17 minutes ago) add 1 - XXX
    * e90bc6c - (17 minutes ago) add base - XXX
  • 把如下分支 add 4/5 二个提交记录合并为一个完整的提交,然后再 push 到公共仓库。

      # 合并最近二次提交 需要选择二次提交的前一个提交
      git rebase -i 6c82fb4
      # 或者
      git rebase -i HEAD~2
  • 出交互式的界面,我们可以编辑说明

    pick 0f89d77 add 4
    pick 09b85cb add 5
    
    # Rebase 6c82fb4..09b85cb onto 6c82fb4 (2 commands)
    # Commands:
    每一个commit id 前面的pick表示指令类型,git 为我们提供了以下几个命令:
    pick:保留该commit(缩写:p)
    reword:保留该commit,但我需要修改该commit的注释(缩写:r)
    edit:保留该commit, 但我要停下来修改该提交(不仅仅修改注释)(缩写:e)
    squash:将该commit和前一个commit合并(缩写:s)
    fixup:将该commit和前一个commit合并,但我不要保留该提交的注释信息(缩写:f)
    exec:执行shell命令(缩写:x)
    drop:我要丢弃该commit(缩写:d)
    
    ## 合并 add 5 到 add 4
    pick 0f89d77 add 4
    s 09b85cb add 5
  • 保存后是注释修改界面,修改保存后查看日志

    PS C:\Users\sfuli\Desktop\test> git lg
    * 7290f75 - (31 minutes ago) add 4 5 - XXX (HEAD -> main)
    * 6c82fb4 - (31 minutes ago) add 3 - XXX
    * 4c4e075 - (31 minutes ago) add 2 - XXX
    * eda0aa7 - (31 minutes ago) add 1 - XXX
    * e90bc6c - (31 minutes ago) add base - XXX

# commit 粘到其他分支

  • 当我们项目中存在多个分支,有时候我们需要将 某一个分支中的一段 commit 记录 同时应用到其他分支中

  • 我们希望将 develop 分支中的 C~E 部分复制到 master 分支中,这时我们就可以通过 rebase 命令来实现(如果只是复制某一两个提交到其他分支,建议使用更简单的命令: git cherry-pick)。

  • 命令: git rebase [startpoint] [endpoint] --onto [branchName]

    • [startpoint] [endpoint] 仍然和上一个命令一样指定了一个编辑区间(前开后闭)
    • --onto 的意思是要将该指定的提交复制到哪个分支上。
    # 将 develop 分支中的 C~E 部分复制到 master 分支中
    # 为了让这个区间包含 C(90bc0045b)提交,我们将区间起始点向后退了一步。C(90bc0045b)和 E(5de0da9f2)
    git rebase 90bc0045b^ 5de0da9f2 --onto master
    
    # 执行完成后虽然此时 HEAD 所指向的内容正是我们所需要的,但是 master 分支是没有任何变化的
    # git 只是将 C~E 部分的提交内容复制一份粘贴到了 master 所指向的提交后面
    # 我们需要做的就是将 master 所指向的提交 id 设置为当前 HEAD 所指向的提交 id
    # 即
    git checkout master
    git reset --hard  0c72e64

# rebase 与 merge 的区别

  • merge 操作会生成一个新的节点,之前的提交分开显示。

  • rebase 操作不会生成新的节点,是将两个分支融合成一个线性的提交。

  • 现在我们有这样的两个分支, test 和 master,提交如下:

          D---E test
         /
    A---B---C---F master
  • 在 master 执行 git merge test,然后得到如下结果:

          D--------E
         /
    A---B---C---F----G   test, master
  • 在 master 执行 git rebase test,然后得到如下结果:

    A---B---D---E---C'---F'   test, master
  • 如果你想要一个干净的,没有 merge commit 的线性历史树,那么你应该选择 git rebase

  • 如果你想保留完整的历史记录,并且想要避免重写 commit history 的风险,你应该选择使用 git merge

# git cherry-pick

  • 挑拣节点合并到当前分支上, 该命令一般用于从其他分支上挑拣某些节点到当前分支。

  • 命令:git cherry-pick commit_id

  • 上图中我想把 ruby_client 分支上的 e43a6 这个节点合并到 master 分支上,但不需要 5ddae 这个节点,那么我们就可以使用下面的命令:

    git checkout master // 先切换到 master 分支
    git cherry-pick e43a6 //将 e43a6 节点挑拣合并到当前分支
  • 完成后如下图所示: 注意:该节点被挑拣合并到 master 上后会产生一个新的节点 a0a41

# 创建分支和切换分支图解

master、hot-fix 其实都是指向具体版本记录的指针。当前所在的分支,其实是由 HEAD 决定的。所以创建分支的本质就是多创建一个指针

  • HEAD 如果指向 master,那么我们现在就在 master 分支上
  • HEAD 如果指向 hotfix,那么我们现在就在 hotfix 分支上

所以切换分支的本质就是移动 HEAD 指针

# stash 分支

  • git stash 命令:将当前未提交的修改(即,工作区的修改和暂存区的修改)先暂时储藏起来,这样工作区干净了后,就可以切换切换到 master 分支下拉一个 fix 分支。在完成线上 bug 的修复工作后,重新切换到 dev 分支下通过 git stash pop 命令将之前储藏的修改取出来,继续进行新功能的开发工作

  • 没有在 git 版本控制中的文件,是不能被 git stash 存起来的

  • 常规 git stash 的一个限制是它会一下暂存所有的文件。有时,只备份某些文件更为方便,让另外一些与代码库保持一致。一个非常有用的技巧,用来 备份部分文件

    1. add 那些你不想备份的文件(例如: git add file1.js, file2.js
    2. 调用 git stash –keep-index。只会备份那些没有被 add 的文件。
    3. 调用 git reset 取消已经 add 的文件的备份,继续自己的工作。
    # 查看储藏记录列表
    git stash list
    stash@{index}: WIP on [分支名]: [最近一次的commitID] [最近一次的提交信息]
    
    # 标识储藏记录
    git stash save [stashMessage]
    
    # 取出最近一次储藏的修改到工作区
    # 命令恢复之前缓存的工作目录,将缓存堆栈中的对应 stash 删除,并将对应修改应用到当前的工作目录下, 默认为第一个 stash, 即 stash@{0}
    # 如果要应用并删除其他 stash 比如应用并删除第二个:git stash pop stash@{1}
    git stash pop
    git stash pop stash@{$num}
    
    # 取出指定 index 的储藏的修改到工作区中, 但不会把存储从存储列表中删除,默认使用第一个存储, 即 stash@{0}
    # 取出第二个:git stash apply stash@{1}
    git stash apply stash@{index}
    
    # 将指定 index 的储藏从储藏记录列表中删除
    git stash drop stash@{index}
    
    # 清空所有缓存的 stash  
    git stash clear
    
    #  显示做了哪些改动,默认 show 第一个存储, 如果要显示其他存贮,后面加 `stash@{$num}`,比如第二个 ` git stash show stash@{1}`
    git stash show
    # 显示指定的 stash 中保存的所有文件的列表
    git stash show stash@{0} --name-only
    # 显示第一个存储的改动,如果想显示其他存存储,命令:git stash show stash@{$num} -p 
    git stash show -p 

# push 推送

  • 上传本地仓库分支到远程仓库分支,实现同步。

    # 上传本地指定分支到远程仓库
    git push <远程主机> <本地分支>:<远程分支>
    # 如果省略远程分支名,则表示将本地分支推送与之存在 "追踪关系" 的远程分支(通常两者同名),如果该远程分支不存在,则会被新建。
    # 将本地的 `master` 分支推送到 `origin` 主机的 `master` 分支。如果后者不存在,则会被新建。
    git push origin master
    # 强行推送当前分支到远程仓库,即使有冲突
    git push [remote] –force
    # 推送所有分支到远程仓库
    git push [remote] -all
    
    # 如果省略本地分支名,则表示删除指定的远程分支,因为这等同于推送一个空的本地分支到远程分支。
    git push origin :master
    # 等同于
    git push origin --delete master
    
    # 如果当前分支与远程分支之间存在追踪关系,则本地分支和远程分支都可以省略。
    git push origin
    # 如果当前分支只有一个追踪分支,那么主机名都可以省略。
    git push
    
    # 如果当前分支与多个主机存在追踪关系,则可以使用 `-u` 选项指定一个默认主机,这样后面就可以不加任何参数使用 `git push`。
    # 将本地的 `master` 分支推送到 `origin` 主机,同时指定 `origin` 为默认主机,后面就可以不加任何参数使用 `git push` 了
    git push -u origin master
  • 不带任何参数的 git push,默认只推送当前分支,这叫做 simple 方式。此外,还有一种 matching 方式,会推送所有有对应的远程分支的本地分支。Git 2.0 版本之前,默认采用 matching 方法,现在改为默认采用 simple 方式。如果要修改这个设置,可以采用 git config 命令。

    git config --global push.default matching
    # 或者
    git config --global push.default simple
  • 还有一种情况,就是不管是否存在对应的远程分支,将本地的所有分支都推送到远程主机,这时需要使用 --all 选项。

    git push --all origin
    # 上面命令表示,将所有本地分支都推送到 `origin` 主机。
    
    # 如果远程主机的版本比本地版本更新,推送时 Git 会报错,要求先在本地做 `git pull` 合并差异,然后再推送到远程主机。这时,如果你一定要推送,可以使用 `--force` 选项。
    git push --force origin
    
    # 上面命令使用 `--force` 选项,结果导致远程主机上更新的版本被覆盖。除非你很确定要这样做,否则应该尽量避免使用 `--force` 选项。
    
    最后,`git push`不会推送标签(tag),除非使用`--tags`选项。
    git push origin --tags
  • 新建仓库 推送

    echo "# README" >> README.md
    git init
    git add README.md
    git commit -m "first commit"
    git branch -M main
    git remote add origin [email protected]:xxx/xxx.git
    
    git push -u origin main
  • 已经存在的仓库

    # 删除关联的 origin 的远程库
    git remote rm origin
    git remote add origin https://gitee.com/xxxxxx.git
    git branch -M main
    git push -u origin main  # git push --set-upstream origin main

# pull 分支

  • git pull 命令的作用是,取回远程主机某个分支的更新,再与本地的指定分支合并。它的完整格式稍稍有点复杂。一般是本地分支的进度落后于远程分支时,需要使用该命令:git pull <远程主机名> <远程分支名>:<本地分支名>

    # 取回 origin 主机的 next 分支,与本地的 master 分支合并,需要写成下面这样。
    git pull origin next:master
    # 如果远程分支是与当前分支合并,则冒号后面的部分可以省略。
    git pull origin next
    # 上面命令表示,取回 `origin/next` 分支,再与当前分支合并。实质上,这等同于先做 `git fetch`,再做 `git merge`
    $ git fetch origin
    $ git merge origin/next
    
    # 在本地删除远程已经删除的分支 如果远程主机删除了某个分支,默认情况下,`git pull` 不会在拉取远程分支的时候,删除对应的本地分支。
    $ git pull -p
    # 等同于下面的命令
    $ git fetch --prune origin
    $ git fetch -p
  • 在某些场合,Git 会自动在本地分支与远程分支之间,建立一种追踪关系(tracking)。比如,在 git clone 的时候,所有本地分支默认与远程主机的同名分支,建立追踪关系,也就是说,本地的 master 分支自动 "追踪" origin/master 分支。Git 也允许手动建立追踪关系。

    # 指定 `master` 分支追踪 `origin/next` 分支。
    git branch --set-upstream master origin/next
    
    # 如果当前分支与远程分支存在追踪关系,`git pull` 就可以省略远程分支名。
    # 下面命令表示本地的当前分支自动与对应的 `origin` 主机 "追踪分支"(remote-tracking branch)进行合并。
    $ git pull origin
    
    # 如果当前分支只有一个追踪分支,连远程主机名都可以省略。表示当前分支自动与唯一一个追踪分支进行合并。
    $ git pull
  • 常用 git pull --rebase origin master 用 rebase 的方式进行,不会产生 merge 保持分支干净、整洁

  • pull 分支报错

    $ git pull origin
    提示:您有不同的分支,需要指定如何协调它们。
    hint: You have divergent branches and need to specify how to reconcile them.
    提示:您可以通过在之前某个时间运行以下命令之一来做到这一点
    hint: You can do so by running one of the following commands sometime before
    hint: your next pull:
    hint:
    hint:   git config pull.rebase false  # merge
    hint:   git config pull.rebase true   # rebase
    hint:   git config pull.ff only       # fast-forward only 仅快进
    hint:
    提示:可以将“git config”替换为“git config——global”来设置默认值
    hint: You can replace "git config" with "git config --global" to set a default
    提示:首选所有存储库。你也可以传递——rebase,——no-rebase,
    hint: preference for all repositories. You can also pass --rebase, --no-rebase,
    提示:或命令行上的——ff-only,以覆盖配置的默认per
    hint: or --ff-only on the command line to override the configured default per
    hint: invocation.
    fatal:需要指定如何协调不同的分支。
    fatal: Need to specify how to reconcile divergent branches.
  • 解决

    # 默认将 pull 下来的代码与现有改动的代码进行合并
    git config --global pull.rebase false
    
    # 回退到合并之前的代码,在进行 pull 拉取最新代码
    git reset --hard c129513
    git pull origin

# pull rebase 模式

  1. 命令 git pull --rebase <远程主机名> <远程分支名>:<本地分支名>

  2. 多人使用同一个远程分支合作开发,在 push 代码的时候很可能出现以下问题:

    $ git push origin master
    
    # 结果如下
    To github.com:hello/demo.git
     ! [rejected]        master -> master (fetch first)
    error: failed to push some refs to '[email protected]:hello/demo.git'
    hint: Updates were rejected because the remote contains work that you do
    hint: not have locally. This is usually caused by another repository pushing
    hint: to the same ref. You may want to first integrate the remote changes
    hint: (e.g., 'git pull ...') before pushing again.
    hint: See the 'Note about fast-forwards' in 'git push --help' for details.
  3. 从输出结果中可以明显看出此时远程分支有新的 commit 未同步到本地,无法推送。此时我们一般会执行以下操作:

    git pull origin master
    git push origin master
  4. 确实 push 成功了,但是此时用 git log 查看提交记录:多出了一条 merge commit,这个 commit 就是在执行 git pull origin master 的时候自动生成的。如果多人多次如此操作,那么提交记录就会出现很多条这种自动生成的 merge commit,非常难看。

    # 结果如下
    commit 1aefef1a2bedbd3ebd82db8dcf802011a35a9888 (HEAD -> master, origin/master, origin/HEAD)
    Merge: 24cfa5c f318a05
    Author: hello <[email protected]m>
    Date:   Tue Jul 23 09:53:47 2019 +0800
    
        Merge branch 'master' of github.com:hello/demo
    
    commit 24cfa5c3ad271e85ff0e64793bf2bcc9d700c233
    Author: hello <[email protected]m>
    Date:   Tue Jul 23 09:50:06 2019 +0800
    
        feat: 新功能提交
    
    commit f318a05b1a4cbc0a6cf8d7dc7d3fb99cbafb0363
    Author: world <[email protected]m>
    Date:   Tue Jul 23 09:48:20 2019 +0800
    
        feat: 其他功能提交
  5. 解决方案: 要解决以上问题,不再出现自动生成的 merge commit,那么只要在执行 git pull origin master 的时候带上 --rebase 即可:

    # 等价于:git fetch --all && git rebase branch
    $ git push origin master
  6. 注意事项: 执行 git pull --rebase 的时候必须保持本地目录干净。即:不能存在状态为 modified 的文件。(存在 Untracked files 是没关系的), 如果出现冲突,可以选择手动解决冲突后继续 rebase,也可以放弃本次 rebase

  7. 如果 A、B 同学修改了同一个文件。那么很有可能会出现冲突,当 B 同学来执行命令的时候会出现如下状况:

    $ git pull --rebase
    
    # 结果如下
    remote: Counting objects: 6, done.
    remote: Compressing objects: 100% (6/6), done.
    remote: Total 6 (delta 1), reused 0 (delta 0)
    Unpacking objects: 100% (6/6), done.
    From gitlab.lrts.me:fed/gitlab-merge
       93a1a93..960b5fc  master     -> origin/master
    First, rewinding head to replay your work on top of it...
    Applying: feat: 其他功能提交
    Using index info to reconstruct a base tree...
    M one.md
    Falling back to patching base and 3-way merge...
    Auto-merging one.md
    CONFLICT (content): Merge conflict in one.md
    error: Failed to merge in the changes.
    Patch failed at 0001 feat:其他功能提交
    hint: Use 'git am --show-current-patch' to see the failed patch
    
    Resolve all conflicts manually, mark them as resolved with
    "git add/rm <conflicted_files>", then run "git rebase --continue".
    You can instead skip this commit: run "git rebase --skip".
    To abort and get back to the state before "git rebase", run "git rebase --abort".
    
    # 这种情况下,可以手动打开 one.md 文件解决冲突,然后再执行:
    $ git add one.md
    $ git rebase --continue
    
    # 也可以用 git rebase --abort 放弃本次 rebase 操作。
  8. 总结: 多人基于同一个远程分支开发的时候,如果想要顺利 push 又不自动生成 merge commit,建议在每次提交都按照如下顺序操作:

    # 把本地发生改动的文件贮藏一下
    $ git stash
    
    # 把远程最新的 commit 以变基的方式同步到本地
    $ git pull --rebase
    
    # 把本地的 commit 推送到远程
    $ git push
    
    # 把本地贮藏的文件弹出,继续修改
    $ git stash pop

# 关联远程分支

没有跟踪时,每次 git pullgit push 都需要手动指定远程分支:

git push origin 本地分支名
git pull origin 远程分支名

配置本地分支应该和哪个远程分支对话

git branch -r  # 查看所有远程跟踪分支
git ls-remote --heads origin # 查看远程仓库的所有分支
git remote show origin # 查看远程仓库的详细信息

git fetch origin  # 先同步远程分支信息
git branch -u 远程跟踪分支名 # 将当前本地分支关联到指定的远程分支
git branch --set-upstream-to=origin/remote_branch  local_branch  #本地分支跟踪远程分支
git checkout -b 本地分支名 远程跟踪分支名  #创建本地分支并跟踪远程分支 
git checkout --track 远程跟踪分支名       #创建本地分支并跟踪远程分支

常见场景示例

# 从远程分支创建本地跟踪分支
## 方式1:直接创建并跟踪
git checkout -b develop origin/develop
## 方式2:先切换到远程分支,再创建本地分支
git checkout --track origin/feature/login


# 手动设置现有本地分支的跟踪
# 如果远程分支不存在,需要先推送本地分支:
git push -u origin 本地分支名  # -u 会同时建立跟踪
# 或
git branch -u origin/feature/login  # 当前分支跟踪远程分支
# 或
git branch --set-upstream-to=origin/feature/login feature/login  # 指定本地分支

# Git 撤回与重置

# 撤销工作区修改

场景:修改了文件但未 git add,想丢弃工作区修改

git checkout -- <file>      # 撤销单个文件
git checkout -- .           # 撤销所有修改

# 撤销暂存区内容

场景:已 git add 但未 git commit,想取消暂存

git reset HEAD <file>       # 将文件移出暂存区(保留工作区修改)
git reset HEAD .            # 移出所有暂存文件

# 撤销最近一次提交

场景1:提交信息写错/漏文件,想修改提交

git add <漏掉的文>          # 添加遗漏文件
git commit --amend          # 修改上次提交(会进入编辑器修改信息)

场景2:丢弃最近一次提交(保留工作区修改)

git reset --soft HEAD^      # HEAD^ 表示上一个提交

# 回滚到历史版本

场景:需回退到指定提交,根据需求保留或丢弃修改

git reset --soft <commit-hash>  # 仅移动 HEAD,保留工作区和暂存区
git reset --mixed <commit-hash> # 默认:移动 HEAD 并重置暂存区,保留工作区修改
git reset --hard <commit-hash>  # 彻底回退(丢弃工作区和暂存区修改)

注意

  • --hard永久丢弃目标版本之后的修改!
  • 若已推送到远程仓库,需用 git push -f 强制覆盖(谨慎操作)。

# 临时保存修改

场景:切换分支前暂存未完成的修改

git stash                   # 保存工作区和暂存区到栈中
git stash -u                # 包括未跟踪文件
git stash list              # 查看存储列表
git stash apply stash@{n}   # 恢复指定存储(不删除)
git stash pop               # 恢复最新存储并删除
git stash drop stash@{n}    # 删除指定存储

# 文件操作

git rm <file>               # 删除文件并暂存
git rm --cached <file>      # 停止跟踪但保留本地文件
git mv <old> <new>          # 重命名文件并暂存

# 大文件清理

# 斩草除根式

  • 把根目录下面的.git 文件夹删除,然后重建,简单粗暴,但如果需要 git log 历史记录的,请不要这样做

    rm -rf .git
    # 删除之后,该目录就不是一个 git 仓库了,因此需要重建
    # 输入 rm -rf + github 仓库地址  在 github 的对应的库中到 setting 删除库
    rm -rf https://github.com/xxx/xxx.git

# 逐个攻破式

  • 对仓库进行 gc 操作

    git gc
  • 查看空间使用,size-pack 是以千字节为单位表示的 packfiles 的

     git count-objects -v
     du -ah .git/objects
  • 运行底层命令 git verify-pack 以识别大对象,对输出的第三列信息即文件大小进行排序.

    # 占用空间最多的五个文件
    git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -5
    # 787d86fa5890fa037cca876b7b67d581d424b357 blob   1259085 1168273 16059429
    # 第一行的字母其实相当于文件的 id, 用以下命令可以找出 id 对应的文件名:
    git rev-list --objects --all | grep 787d86fa5890fa037cca876b7b67d581d424b357
    
    # 使用 rev-list 命令,传入 --objects 选项,它会列出所有 commit SHA 值,blob SHA 值及相应的文件路径,这样查看 blob 的文件名。
    git rev-list --objects --all | grep "$(git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -5 | awk '{print$1}')"
  • 查找文件

    # 查出文件提交 commit 记录
    git log --pretty=oneline --branches -- ${FILE_PATH}
    # 想要知道这条 commit id 所在的分支,可以使用以下命令
    git branch -a --contains <COMMIT ID>
  • 删除文件

    # 遍历所有提交: commit 多了会比较慢
    git filter-branch -f --prune-empty --index-filter 'git rm -rf --cached --ignore-unmatch <file>' --tag-name-filter cat -- --all
    
    filter-branch命令可以用来重写Git仓库中的提交
    -f , --force 假如遇到冲突也让git强制执行
    --prune-empty 选项告诉git,如果因为重写导致某些commit变成了空(比如修改的文件全部被删除),那么忽略掉这个commit。
    --index-filter参数用来指定一条Bash命令,然后Git会检出(checkout)所有的提交, 执行该命令,然后重新提交。
     git rm --cached --ignore-unmatch file 让git删除掉缓存的文件,如果有匹配的话
    --tag-name-filter 表示对每一个tag如何重命名,重命名的命令紧跟在后面,当前的tag名会从标注输入送给后面的命令,用cat就表示保持tag名不变。
    -- 紧跟着的-- 表示分割符,
    --all 表示对所有的文件都考虑在内
    
    # 指定 commit 修改
    git filter-branch -f --prune-empty --index-filter 'git rm -rf --cached --ignore-unmatch <file>' -- <commit_id>
  • 回收内存

    rm -rf .git/refs/original/
    git reflog expire --expire=now --all
    git fsck --full --unreachable
    git repack -A -d  # 将所有未被包含在一个 pack 的松散对象连结成一个 pack
    git gc --aggressive --prune=now
  • 提交到远程仓库

    git push --force [remote] master
    # 让远程仓库变小
    git remote prune origin

# 脚本-更新目录的多个仓库

# vim ~/git_pull_Batch.sh
#!/bin/bash

function showGreen(){ echo -e "\033[32m $1 \033[0m"}
function showBlue(){ echo -e "\033[36m $1 \033[0m"}
function showYellow(){ echo -e "\033[33m $1 \033[0m"}
function showWhite(){ echo -e "\033[37m $1 \033[0m"}

function traversal_dir(){
    for sub_dir in `ls $1` #通过 ls root_dir 遍历出子目录,装入子目录 sub_dir 中
    do
        dir=$1"/"$sub_dir #将根目录 $1 与子目录 sub_dir 拼接成完整的目录
        if [ -d $dir ] #判断:是目录的继续下一步
        then
            cd $dir
   showBlue $dir
            showGreen 'git pull '$sub_dir
            git pull
   echo #打印空行
        else
            showYellow $dir
   echo #打印空行
        fi
    done
}

root_dir="N:\Desktop\qnit" #定义根目录,即项目 project 的上级目录。例如:root_dir/project/.git
traversal_dir $root_dir

# 代码行数统计

# 个人提交的代码行数统计
git log --author="username" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\n", add, subs, loc }' -

# 查看项目每个人提交的代码行数统计
git log --format='%aN' | sort -u | while read name; do echo -en "$name\t"; git log --author="$name" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\n", add, subs, loc }' -; done
# 查询所有用户的提交总次数
git log --pretty='%aN' | sort | uniq -c | sort -k1 -n -r

# Git 在工作中的使用

# Git 工作流分类

  • GItFlow 是在项目开发过程中使用 Git 的方式
  • 集中式工作流 :像 SVN 一样,集中式工作流以中央仓库作为项目所有修改的单点实体。所有修改都提交到 Master 这个分支上。 这种方式与 SVN 的主要区别就是开发人员有本地库。Git 很多特性并没有用到。
  • GitFlow 工作流:为功能开发、发布准备和维护设立了独立的分支,让发布迭代过程更流畅。严格的分支模型也为大型项目提供了一些非常必要的结构
  • Forking 工作流:在 GitFlow 基础上,充分利用了 Git 的 Fork 和 pull request 的功能以达到代码审核的目的。更适合安全可靠地管理大团队的开发者,而且能接受不信任贡献者的提交。

# 分支种类详解

  • 主干分支 master: 主要负责管理正在运行的生产环境代码。永远保持与正在运行的生产环境完全一致。
  • 开发分支 develop: 主要负责管理正在开发过程中的代码。一般情况下应该是最新的代码。
  • bug 修理分支 hotfix: 主要负责管理生产环境下出现的紧急修复的代码。 从主干分支分出,修理完毕并测试上线后,并回主干分支。并回后,视情况可以删除该分支。
  • 准生产分支(预发布分支) release: 较大的版本上线前,会从开发分支中分出准生产分支,进行最后阶段的集成测试。该版本上线后,会合并到主干分支。生产环境运行一段阶段较稳定后 可以视情况删除。
  • 功能分支 feature: 为了不影响较短周期的开发工作,一般把中长期开发模块,会从开发分支 中独立出来。 开发完成后会合并到开发分支。

# Github 跨团队协作

  1. 假设 zs 团队在开发,要请 ls 团队协作。
  2. ls 登录 Github 账户,forkzs 的项目后
  3. 修改本地代码后,推送到远程仓库中,访问 Github 后,点击 Pull Request,新建一个 pull 请求。
  4. 这时候 zs 登录 github 后可以在 pull request 中看到请求, 可以进行交流和审核提交的代码。
  5. 确认无误后,合并代码,选择 merge pull request, 填写操作的日志信息后, confirm merge 即可。
  6. zs 团队后续继续开发,ls 要获取代码,同样取 zs 项目和自己项目的 diff,创建 pull request ,然后 merge 即可。

# 普通开发人员的操作

普通开发人员,一般按照如下几个步骤来进行开发、测试工作就可以了:

  1. 将远程 dev 分支 clone 到本地,例如:git clone [email protected]:goto456/test.git
  2. 从 dev 分支拉出(新建)自己的 feature 分支用于开发,例如:git checkout -b feature_login
  3. 在自己的 feature 分支上进行开发工作;
  4. 开发完了用 add、commit 等操作提交到当前分支;
  5. 如果需要在测试环境进行测试,则将远程 test 分支拉到本地,例如:git branch test origin/test;
  6. 将自己的 feature 分支合并到 test 分支,并将 test 分支 push 到远程,例如:git rebase test, git checkout test, git merge feature_login, git push origin test(注意:我们推荐用 rebase 来合并,以保证分支的整洁、美观)
  7. 通过公司的发布平台将远程 test 分支发布到测试环境进行测试;
  8. 如果测试没问题或者开始就不需要测试,这可以直接将当前 feature 分支合并到 dev 分支,并 push 到远程库,例如:git rebase dev, git checkout dev, git merge feature_login, git push origin dev(注意:我们推荐用 rebase 来合并,以保证分支的整洁、美观)
  9. 这时表示该功能已经开发完成了,代码的 review 以及发布,需要团队 leader 在合并到 master 操作时进行;这时可以删除了自己的 feature 分支,例如:git branch -d feature_login
  10. 如果在 push 到远程的时候提示需要先 pull 时,我们推荐使用 rebase 的方式:git pull --rebase 以保持分支的整洁、美观。

# 团队 leader 的操作

因为只有 leader 有操作 master 分支的权限,所以需要完成 dev 分支到 master 分支的合并,以及后续打 tag 和正式上线发布的工作:

  1. 先切换到 dev 分支,并拉取最新的状态,例如:git checkout dev, git pull --rebase origin dev
  2. 进行代码 review 等过程后,合并到 master 分支,例如:git rebase master, git checkout master, git merge dev; (注意:我们推荐用 rebase 来合并,以保证分支的整洁、美观)
  3. 为本次完成的版本打上标签,例如:git tag v1.0 -m "release version 1.0"
  4. 将本地合并后的 master 分支以及标签 push 到远程库,例如:git push orgin master --tags