Git进阶使用清单

303 阅读12分钟

git rebase 合并多个提交

在自己的开发分支上,经常会产生多个无意义或者不需要后续查看的提交(比如添加日志或者添加测试代码的提交)。在后续将开发分支合并到主分支上,这些提交也会被合并到主分支的提交日志上,这会造成 git 提交信息的冗余,不利于后续提交日志的查看。

因此,可以使用 git rebase 将多个提交合并到一个提交。

下面结合一个具体的实例进行说明。

  1. git log --pretty=oneline 查看提交日志(此处对 commitID 仅给出前6位)
lbsMacBook-Pro:testgit lbs$ git log --pretty=oneline

2e12b3 (HEAD -> master) commit 5
fe134d commit 4
18faed commit 3
44e9df commit 2
efdcfc init 1
  1. 下面将 commit 5commit 4commit 3 这三个提交合并为一个提交,使用 git rebase -i 进行操作
//推荐使用该方式
//语法:git rebase -i startPoint   
//合并 (startPoint, head] 区间的提交,区间左开右闭
//合并的提交不包括 startPoint 对应的提交,即区间左侧为开区间
git rebase -i 44e9df


//语法:git rebase -i startPoint  endPoint
//合并 (startPoint, endPoint] 区间的提交,区间左开右闭
//合并的提交不包括 startPoint 对应的提交,即区间左侧为开区间
//endPoint可以省略,若省略,则默认endPoint为head,即当前的提交
git rebase -i 44e9df 2e12b3


// git rebase -i HEAD~count
// 合并从head当前提交开始的count个提交
git rebase -i HEAD~3
  1. 执行完上述命令,会看到下面的信息
pick 2e12b3 commit 3       
pick fe134d commit 4
pick 18faed commit 5     #head 在最后  时间从前到后


# Rebase xxxxxx.......
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
  1. 提交记录是按照时间顺序排列的,我们需要后面的提交合并到的区间的第一个提交,即将非首个提交的 pick 改为 squash 或者 s,如下修改示例
pick 2e12b3 commit 3       
s fe134d commit 4
s 18faed commit 5     #head 在最后  时间从前到后
  1. 退出上述编辑区间,会看到如下信息,在最上面输入合并提交之后,需要展示的提交信息
# This is a combination of 3 commits.
# This is the 1st commit message:

commit 3


# This is the commit message #2:

commit 4

# This is the commit message #3:

commit 5

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
"~/Desktop/testgit/.git/COMMIT_EDITMSG" 36L, 664C

我们在最上面添加需要展示的提交信息

squash-test-1  

# This is a combination of 3 commits.
# This is the 1st commit message:

commit 3


...

  1. 保存上述编辑,会看到合并成功的提示
Date: Sat Jun 13 22:52:38 2020 +0800
1 file changed, 3 insertions(+), 2 deletion(-)
Successfully rebased and updated refs/heads/master.
  1. 最后查看下提交记录
lbsMacBook-Pro:testgit lbs$ git log --pretty=oneline

5fed13 (HEAD -> master) squash-test-1 
44e9df commit 2
efdcfc init 1

可以看到,commit 5commit 4commit 3 这 3 个提交,已经被合并为一个新的提交了,提交信息为 squash-test-1。 使用 git log 可以看到 squash-test-1 更详细的信息

lbsMacBook-Pro:testgit lbs$ git log
commit 5fed13 (HEAD -> master)
Author: lbs0912 <lbs1203940926@163.com>
Date:   Sat Jun 13 22:52:38 2020 +0800

    squash-test-1
    
    commit 3
    
    commit 4
    
    commit 5

...

  1. 需要注意的是,如果这些多个提交已经推送到了远端,执行完上述操作,会出现如下情况。
ZBMAC-b286c5fb6:MySeckillApp liubaoshuai1$ git status
On branch master
Your branch and 'origin/master' have diverged,
and have 1 and 3 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)

nothing to commit, working tree clean

参考 git合并多个远程commit并提交 | CSDN 解决该问题。

执行 git push -f 将本地提交强制提交到远端,但前提是保证不会覆盖掉远程其他的提交。

git push -f

git本地仓库推送到远端

git remote add origin git@github.com:lbs0912/my-spring-boot-app.git

git push -u origin master   
//-u 表示在推送的同时,将 origin 仓库的 master 分支设置为本地仓库当前分支的 upstream

本地更新远程分支列表

当远程有新的分支时,本地不会自动获取到新分支。可以执行下述代码获取远程新的分支列表。

git remote update origin --prune
//or
git remote update origin -p

创建本地分支跟踪远程分支

git branch -a     # 查看远程分支
git checkout -b localBranch remoteBranch   
git branch        # 检查分支切换效果
  • 需要说明的是,在检出远程分支时,使用远程分支名 remotes/origin/xxxorigin/xxx 均可
# 以下举例说明
> git branch -a
  remotes/origin/master
  remotes/origin/dev
  remotes/origin/feature-1
> git checkout -b dev origin/dev  
> git checkout -b master remotes/origin/master

此处,以创建一个本地分支 features/dev_02 ,并创建一个远程同名分支,最后进行关联为例进行说明。

//基于远程master分支创建一个本地开发分支
git checkout -b features/dev_02 origin/master


//本地分支关联远程同名分支(若远程分支不存在,则会创建)
git push --set-upstream origin features/dev_02

//后续推送代码 
git push
git push origin features/dev_02

删除分支

  • 删除本地 dev 分支
git branch -d <name>
  • 强制删除本地 dev 分支。比如,若分支未合并,直接删除会提示该分支还未合并,删除会造成代码丢失,此时若执意删除分支,可以使用 -D 进行强制删除
git branch -D <name>
  • 删除远程 dev 分支
git push origin --delete dev

git代理

  • 查看当前代理状态
git config --global http.proxy
git config --global https.proxy
  • 命令行git代理
git config --global http.proxy 'socks5://127.0.0.1:1086'
git config --global https.proxy 'socks5://127.0.0.1:1086'
  • 取消代理
git config --global --unset http.proxy
git config --global --unset https.proxy

git: 'credential-wincred' is not a git command

The problem is that you're trying to use the wincred credential helper, which is only available on Windows, on macOS. You mentioned that you've run git config --global credential.helper wincred, which sets the credential helper to wincred. When Git tries to invoke it, it fails because it's unavailable.

Instead, you should run git config --unset-all credential.helper and run git config --global credential.helper osxkeychain. That will result in you using the macOS Keychain for storing credentials, which will work on macOS.

git config --global --unset-all credential.helper

git config --global credential.helper osxkeychain

git reflog 解决提交代码丢失 detached-head

reflog 是 Git 操作的一道安全保障,它能够记录几乎所有本地仓库的改变。包括所有分支 commit 提交,已经删除(其实并未被实际删除)commit 都会被记录。总结而言,只要 HEAD 发生变化,就可以通过 reflog 查看到。

detached-head 代码丢失找回

背景

日常开发中,切换分支误操作,造成本地代码修改丢失。

此时,可以借助 git reflog 找回丢失的代码修改。

丢失产生原因和步骤

首先在 master 分支上开发,此时线上出现 bug 且回到旧版本的 tag。这时 master 分支上有一部分代码修改但未提交。

master 分支上执行 git status,有未提交的代码,如下图所示

master 分支上执行 git tag查看标签信息,如下图所示

此时有未提交的代码,然后执行 git checkout v1.0

这个时候,提示当前分支为 detached HEAD

然后再执行 git add ./git commitgit checkout master,切换回 master 分支。这个时候发现 detached HEAD 分支不见了,master 分支上未提交的代码也不见了。

代码找回

执行 git reflog 查看提交记录

查找对应提交的 commitId247e11b,然后执行下述命令行,找回丢失的代码

git checkout 247e11b    //检出对应的提交
git checkout -b diff    //新建一个新的diff分支
git checkout master     //切换到master分支
git merge diff          //将新建的diff分支合并到master分支

删除所有历史提交记录

此处介绍如何删除所有历史提交记录,形成一个全新的仓库。

  • 1 - Checkout
git checkout --orphan new_branch
  • 2 - Add all the files
git add -A

//等效于 git add --all 或 git add .

git add 中使用参数 -A--all 表示追踪所有操作,包含新增、修改和删除

Git 2.0版开始,-A 参数为默认参数,即 git add . 等效于 git add -Agit add --all

  • 3 - Commit the changes
git commit -am "commit message"
  • 4 - Delete the branch
git branch -D master   //同时删除本地和远程分支
  • 5 - Rename the current branch to master
git branch -m master
  • 6 - force update your repository
git push -f origin master

下面对上述步骤进行说明

git checkout --orphan

如果你的某个分支上积累了无数次的无意义的提交,git log 信息满天飞,那么可以使用 git checkout --orphan <new_branch_name>

  • 基于当前分支创建一个新的“孤儿(orphan)”的分支,没有任何提交历史,但包含当前分支所有内容
  • 执行上述命令后,工作区(Workspace)中所有文件均被认为在该操作中新增(git statue 查看状态,所有文件状态均为 new file,如下图所示),此时执行 git add . 会把所有文件添加到缓存区(Index

  • 严格意义上说,执行 git checkout --orphan <new_branch_name> 后,创建的并不是一个分支,因为此时 HEAD 指向的引用中没有 commit 值。只有在进行一次提交后,它才算得上真正的分支。

orphan 译为“孤儿”,该参数表示创建一个孤立的分支,没有任何提交历史,且与当前分支不存在任何关系(查看提交信息,可发现其为一个孤立的点,如下图所示)

孤儿(orphan)无父辈信息,同理,创建的分支也不包含任何历史提交信息

git commit -am

git branch -m

重命名

git push -f origin master

git项目协作——保证git信息简洁

同一分支 git pull 使用 rebase

默认情况下,git pull 使用的是 merge 行为。多人协作开发时,会产生不必要的 merge 提交记录,造成提交链混乱不堪。

推荐在同一个分支更新代码时,使用 git pull --rebase

# 为某个分支单独设置,这里是设置 dev 分支
git config branch.dev.rebase true
# 全局设置,所有的分支 git pull 均使用 --rebase
git config --global pull.rebase true
git config --global branch.autoSetupRebase always

分支合并使用 merge --no-ff

Usage

  • Fast-Forward:当前分支合并到另一分支时,如果没有冲突要解决,就会直接移动文件指针,并且不会产生合并提交记录。该过程中,存在git 文件指针快速移动, 因此该过程称为 Fast-Forward
  • --no-ff(no fast foward):每一次的合并,都会创建一个新的 commit 记录。使用 --no-ff,可以保持原有分支提交链的完整性,并且当该分支被删除时,提交信息依旧存在。

git-merge--no-ff-1

结合上图分析,在 dev(绿色) 分支上检出 feature-1 分支(蓝色),且 dev 分支不进行任何提交

  • 直接 merge,默认采用 Fast-Forward,两个分支的提交链会合并为一条直线,不利于后期代码审查和维护
  • 使用 git merge --no-ff feature-1 合并代码,会产生一个新的提交,且两个分支的提交链不会重叠,利于后期代码审查和维护

merge 默认设置

git merge 默认使用 fast-forward,可以通过如下方式,修改为默认使用 --no-ff

git config --global merge.commit no
git config --global merge.ff no

此外,SourceTree 在设置中也可以设置 --no-ff

git-merge--no-ff-2

git clone 设置缓存区

当工程较大时,使用 git clone 拉取代码,可能会出现 early EOF 的报错或者拉取代码失败。

这是因为 git clone 本质上是建立一个 HTTP 连接,工程较大时会超过默认设置的缓存大小。

使用 git config --list 查看 http.postbuffer 的大小,确认是否小于下载的工程大小。

使用 git config --global http.postbuffer 524288000 //500x1024x1024 设置为500M 可以对缓存区大小进行设置。

git clean

git clean 用于清理 workspace 中未被 git 版本控制的文件,比如临时文件,构建出来的二进制文件。其附加参数如下

  1. -f: 删除未跟踪的文件
  2. -d: 删除未跟踪的目录
  3. -x: 删除 .gitignore 已经设置不跟踪的文件
  4. -n: 显示要删除的文件和目录列表(-n 并不实际执行删除操作,只显示出将被清理的文件列表)
# 删除 untracked files
git clean -f
 
# 连 untracked 的目录也一起删掉
git clean -fd
 
# 连 gitignore 的untrack 文件/目录也一起删掉 (慎用,一般这个是用来删掉编译出来的 .o之类的文件用的)
git clean -fdx
 
# 在用上述 git clean 前,墙裂建议加上 -n 参数来先看看会删掉哪些文件,防止重要文件被误删
git clean -fdx  -n
git clean -f -n
git clean -fd -n

为了避免文件误删,建议在使用 git clean 前先使用 -n 查看将要删除的文件列表,确认无误后再删除文件,如下示例。

# git clean -fd -n
ZBMAC-b286c5fb6:JavaTest liubaoshuai1$ git clean -fd -n
Would remove .idea/codeStyles/
Would remove .idea/sbt.xml
Would remove out/production/JavaTest/com/lbs0912/java/demo/FutureTaskExample.class
Would remove out/production/JavaTest/com/lbs0912/java/demo/FutureTaskForMultiCompute$ComputeTask.class

# git clean -fd 
ZBMAC-b286c5fb6:JavaTest liubaoshuai1$ git clean -fd
Removing .idea/codeStyles/
Removing .idea/sbt.xml
Removing out/production/JavaTest/com/lbs0912/java/demo/FutureTaskExample.class
Removing out/production/JavaTest/com/lbs0912/java/demo/FutureTaskForMultiCompute$ComputeTask.class

git tag

  • git tag: 显示所有标签
  • git tag -l 'v1.0.*': 用通配符查看符合筛选条件的标签
  • git show xxx: 查看标签信息(提交者,邮箱等)
  • git tag xxx: 创建轻量标签
  • git tag -a xxx: 创建含有附注的标签
  • git tag -a xxx -m 'xxxx': 创建含有附注的标签,并附加提交信息(默认标签打到当前Head提交状态)
  • git tag -a xxx -m 'xxxx' \<commitID>: 创建补丁标签,即对之前的提交添加标签
  • git tag -d xxx: 删除本地标签
  • git push origin --delete tag <tagname>: 删除远程标签

需要注意的是, Git 使用的标签有 2 种类型:轻量级的(lightweight)和含附注的(annotated)。 —— git tag | Doc

  • 轻量级标签就像是个不会变化的分支,实际上它就是个指向特定提交对象的引用。
  • 含附注标签,实际上是存储在仓库中的一个独立对象,它有自身的校验和信息,包含着标签的名字,电子邮件地址和日期,以及标签说明,标签本身也允许使用 GNU Privacy Guard (GPG) 来签署或验证。

一般我们都建议使用含附注型的标签,以便保留相关信息;当然,如果只是临时性加注标签,或者不需要旁注额外信息,用轻量级标签也没问题。

cherry-pick

使用场景

cherry 译为樱桃,pick 译挑选。git cherry-pick 即选择某一个分支中的一个或几个提交,合并到其他分支中(选择的提交即所需的樱桃),主要使用场景为

  • 情况1: 把弄错分支的提交移动到正确的分支上
  • 情况2: 将其他分支的提交添加到当前分支

Demo

假设工程有个稳定版分支 v2.0,还有个开发版分支 v3.0。开发分支还未彻底完成,不能直接把两个分支合并,这样会导致稳定版本混乱,但是又想增加一个 v3.0 中的功能到 v2.0 中,这里就可以使用 cherry-pick 了。

// 先在v3.0中查看要合并的commit的commit id
git log
// 假设是 commit f79b0b1ffe445cab6e531260743fa4e08fb4048b

// 切到v2.0中
git checkout v2.0

// 合并commit
git cherry-pick f79b0b1ffe445cab6e531260743fa4e08fb4048b     
git cherry-pick -x f79b0b1ffe445cab6e531260743fa4e08fb4048b   //表示保留原提交的作者信息进行提交

语法

  • git cherry-pick commitID:将其他分支的 commitID 提交合并到当前分支
  • git cherry-pick -x commitID:将其他分支的 commitID 提交合并到当前分支,-x 表示保留原提交的作者信息进行提交
  • git cherry-pick <start-commit-id>…<end-commit-id>: 该功能在Git 1.7.2 版本后才支持,将一个连续区间范围的提交,合并到到当前分支。提交范围区间左开右闭,即(start, end]
  • git cherry-pick <start-commit-id>^ … <end-commit-id>: 同上,使用 ^ 表示包含 start-commit-id,即[start, end]

JetBranins 系列IDE,内置了git cherry-pick 快捷键(樱桃图标)

在 SourceTree 中,选中要 cherry-pick 的提交,鼠标右键的菜单栏中有对应的 cherry-pick 操作(中文译名为“遴选”)。

Ref