git

git进阶学习资料:


git小技巧


git基础概念

commit branch HEAD

commit就是节点,可以理解为整个git就是通过一个个commit构建起来的,每个commit都会基于一个父节点。commit 通过哈希值唯一标识

branch会指向一个具体的节点

HEAD指向一个分支(间接指向了那个分支的commit节点),如果直接指向某个具体的commit,则处于detached状态,新的提交不属于任何分支,新提交可能丢失,所以需要及时转成新的分支


git reset和git checkout切换commit区别

理解这个,你首先需要理解git基础概念的知识,理解branch和HEAD的区别

  • git reset移动的是HEAD当前所在的分支,把当前分支移动到某个commit节点
  • git checkout移动的是HEAD,进入detached HEAD状态 branch仍然指向原来的commit节点 git checkout使用前需要清除local changes,所以不用需要stash,不用担心类似reset —hard丢失内容

例如:

1
2
3
4
5
6
7
8
9
10
# 原来的状态
A - B - C1 - C2 - C3 (main -> C3, HEAD -> main -> C3)

# 如果是:git reset --hard C1
A - B - C1 (main -> C1, HEAD -> main -> C1)

# 如果是:git checkout C1
A - B - C1 - C2 - C3 (main -> C3)
         \
          D (HEAD -> D) <-- 新提交,但无分支引用

git rebase和merge区别

参考:https://blog.csdn.net/u010698107/article/details/129000177

简单来说,就是在合并分支过程中:

  • merge会保留分支更多的信息,但也会更乱一些,对应下图紫色箭头变化
  • rebase会直接把新的提交移动到主分支顶部,更简洁一些,没有分支分叉

image.png

特性 Rebase Merge
提交历史 线性、整洁 保留原始分支结构
工作原理 重新应用提交到新基础 创建新的合并提交
冲突处理 可能在每个提交时遇到冲突 一次性解决所有冲突

推荐使用rebase进行分支合并:

1
2
3
4
5
6
git checkout feature
git rebase master
# 如果rebase出现冲突
git add <file>  # 标记冲突已解决
git rebase --continue  # 继续 rebase
git rebase --abort # 取消rebase

刚开始时可能会提示要设置pul.rebase ,我们设置成true

  • 默认行为git pull = git fetch + git merge
  • 设置后git pull = git fetch + git rebase
1
git config --global pull.rebase true

git 处理历史记录 合并历史记录等

有两种方法,第一种是cherry pick,比如我们想处理abc分支的几条commit,

可以创建另一个分支,然后cherry pick这几个有需要的分支,然后再使用git rebase合并这几个cherry pick过来的内容

另一个就是直接git rebase 合并几个分支:

git rebase -i HEAD~N 比如: git rebase -i HEAD~3

然后把几个对应的commit前面改成s,也就是squash,合入到最新的分支

退出再退出就可以了


git 永久记住账号密码:(适用于http连接)

git config --global credential.helper store

git如果要使用http进行push,github已经不再支持输入账号密码进行连接,可以使用token

具体参考:https://blog.csdn.net/matafeiyanll/article/details/130091999


git已经拉取了一个分支,想把别的分支拉取下来,怎么做

  • 单个分支直接:直接git checkout分支名就行了

  • 拉取所有分支:

    1
    2
    3
    for branch in $(git branch -r | grep -v '\->'); do
        git branch --track "${branch#origin/}" "$branch"
    done

git切换到指定的tag

1
2
3
4
5
6
7
8
git checkout <tag_name>

# 如果这时候tag在远程,不在本地,先拉下来
git fetch --tags

# 查看可以切的tag
git tag
git tag -l -n # 查看带注释的tag

git修改最近一次提交的用户名和邮箱

有时候提交前忘了修改邮箱

可以先把本地邮箱改了,例如:

git config --local user.email "gongxiaotian@didiglobal.com" && git config --local user.name "gongxiaotian"

修改完成后,再重新amend提交

1
git commit --amend --reset-author --no-edit

git拉取github的PR并cherrypick到自己的分支

git remote add upstream {git_addr} 先添加github对应仓库代码地址

git fetch origin pull/123/head:pr-123 拉取对应pr分支,把pr#123拉取到本地,命名为pr-123

git log pr-123 查看那个pr分支的提交记录

  • git cherry-pick -n <提交哈希> cherry-pick那个pr的commit
  • git cherry-pick -n <提交A的哈希>^..<提交D的哈希> 如果有多个commityi一起cherry-pick
  • git cherry-pick —continue 报错了修改后继续提交

git常用命令


git config

git配置总共分成三个地方

  1. /etc/gitconfig (几乎不会使用),git config —system
  2. ~/.gitconfig (常用),git config —global
  3. .git/config (针对当前git项目仓库),git config —local

最常用的还是第二个,例如 git config —global user.emailgit config —global user.name

默认如果不加就是-local

使用git config --list列出当前的配置信息

其中优先级 3>2>1


git status

用来看当前的仓库代码状态

这个其实超级重要,用好了很厉害,它会给出很多git的使用提示

比如你已经把test.txt stage了,然后不小心rm了文件

这时候执行git status,会看到提示可以git restore <file>可以恢复文件


.gitignore

  • <name> 忽略匹配到的文件名或目录名(默认递归)
  • # 表示注释
  • *.a 忽略所有以.a结尾的文件(默认递归)
  • !test.a 不忽略test.a文件
  • /<name> 只忽略项目根目录下的内容,子目录仍然跟踪
  • <name>/ 忽略目录下的所有文件,其实加不加/都一样
  • /*/<name> 忽略一级目录下的文件内容
  • /**/<name> 忽略任意级目录下的文件内容(包括0级,也就是ignore文件所在根目录)

其它细节:

  1. 使用* 的细节
    1. doc/*.txt 忽略doc下的0级子目录以txt结尾的文件
    2. doc/*/*.txt 忽略doc下的一级子目录下的以txt结尾的文件
    3. doc/**.txt 忽略doc下的任意级子目录以txt结尾的文件

git branch/checkout

git branch <branchname> 创建一个新的分支

git checkout -b <branchname> 创建一个新的分支并切换过去

git checkout -b <branchname> <frombranch> 从一个分支frombranch创建一个新的分支并切换过去

git branch -d <branchname> 删除一个分支,如果有未merge的内容,会报错,强制删除使用-D

git branch -D <branchname> 强制删除一个分支

git push origin -d <branch-name> 删除远程分支

git checkout - 和linux的cd -一样,在最近的两个分支之间切换

git branch -m <old_branchname> <new_branchname> 将分支重命名

git branch -av 查看本地和远程的分支和最新的提交信息


git merge

git merge <branchname> 把branchname分支的更改合并到当前HEAD所在分支

merge 在可以的情况下默认会使用fast forward模式

fast forward不会创建新的commit记录,比如master也会和dev保持相同的commit信息

关于快进合并(fast forward)可以查看https://blog.csdn.net/weixin_42051619/article/details/107449425

git merge --no-ff <branchname> 不使用快进合并分支

不快进合并会额外产生一条commit记录,(记录了merge记录)

好处是可以保留分支信息


git reset

git reset —hard HEAD^ 回退到之前版本,回退个数是^符号个数

git reset —hard HEAD~1 回退到之前版本,回退个数是~后跟的数字

git reset —hard <commitID> 回退到之前的指定版本

如果回退以后无法找到新的commit记录,可以使用git reflg 查看操作的历史记录,从中找到之前的commit记录,然后再reset退回去就好了

总的来说,三种reset针对不同区域的内容会做如下变化,不显示指定默认是mixed

模式 HEAD 移动 commit内容 暂存区 staged 工作目录 unstaged 安全性
--soft 给到staged 保留 保留 ⭐⭐⭐⭐
--mixed 给到unstaged 给到unstaged 保留 ⭐⭐⭐
--hard 丢弃 丢弃 保留

git reflog

git log记录的是commit记录

git reflog记录的是操作记录


git stash

参考:https://git-scm.com/docs/git-stash

git stash [push] [-all] [-m “info”]

  • 默认不加就是push
  • -all 可以把untracked也放进来
  • -m添加信息例如:git stash push -m "WIP: info"

git stash apply [--index] [<stash>]应用stash,apply应用后不会删掉对应的stash —index可以让原本处于staged区域的继续在staged

git stash pop [--index] [<stash>]和apply基本一样,区别在于应用后会删掉对应的stash

git stash list 列出stash

git stash show [-p] [<stash>] 显示stash内容,-p可以详细展示

git stash drop [<stash>] 删除stash

git stash branch <branchname> [<stash>]创建新的分支并把stash应用于分支

git stash save和push基本相同,优先用push

git stash clear删除所有stash,谨慎操作

最常用的就是pull或者切分支之前先git stash,然后操作执行完后再git stash apply —index


git tag

git tag 列出所有标签

git tag v1.0 创建轻量化标签

git tag -a v2.0 -m “release” 创建带注释的标签

git tag -l “v*” 搜索标签,支持正则(和gitignore类似)

git show <tagname> 显示tag信息

git push origin <tagname> 推送某些指定的标签

git push origin —tags 推送所有标签

git tag -d <tagname> 删除本地标签

git push origin :refs/tags/<tagname> 删除远程标签,以下都是一样的

git push origin —delete tag <tagname>

git tag -d <tagname>

git fetch --tags 从远程拿到tag

git fetch origin --tags 从指定远程拿到tag


git blame

git blame <filename>


diff/ git diff

diff <srcfile> <descfile> 比较两个文件的差异

diff -u <srcfile> <desfile> 比较两个文件的差异,展示为git diff样式

git diff 查看当前内容和暂存区的内容的差异(有哪些更改没加到暂存区)暂存区是src,工作区是des

git diff HEAD 查看最新节点和当前工作区的差异(没有commit的差异)HEAD是src,工作区是des

git diff —cached 查看暂存区和HEAD的差异(添加了哪些到暂存区)HEAD是src,暂存区是des

diff格式解析:

1
2
3
4
5
6
7
8
9
10
--- test1.txt   2024-07-16 14:24:47.623616008 +0800   # - 代表src文件,后面是修改日期
+++ test2.txt   2024-07-16 15:55:21.253642765 +0800   # + 代表des文件,后面是修改日期
@@ -1,4 +1,6 @@         # -1,4代表src第一行开始选择4行,+1,6代表des第一行开始选择6行
-12312                  # 下面的代表src需要删除/增加/不变可以变成des
+1212

 dfa
 f
+
+dfadf

git remote

git remote add origin <origin_url> 这个其实就是给远程链接取个名字叫做origin

git remote remove origin

git remote set-url origin <origin_url>

git remote -v


git alias

git config —local/global/system alias.<aliasname> name 就是把替换为name,name如果以!开头就是表示外部命令(bash)

例如 git config —global alias.br branch 就是把br替换为branch,下次就可以直接输入git br


git remote

git push —set-upstream origin develp 设置当前分支绑定到远程的develop分支

git checkout -b develop origin/develop这个可以用连接远程分支比如

git push的完整操作其实是:git push origin src:desorigin 表示远程的链接别名,src表示本地分支,origin表示远程分支

删除远程分支:推一个空的分支上去 git push origin :des

也可以: git push origin —delete des


git submodule

子模块适用于项目依赖别的项目的情况

git submodule add git@github.com:QuarkGluonPlasma/git-research-child.git child 添加子模块,并clone到目录child下

添加子模块后,会生成一个.gitmodule的文件,里面记录了所有的子模块链接和路径

子模块怎么更新:

  1. 进入到子模块目录然后git pull
  2. 更新所有的子模块,在主目录下执行

git clone 默认不会拉取子模块,需要我们手动拉取,有两种

  1. clone时就指定拉取子模块,git clone <url> —recursive
  2. 已经clone好了,然后 git submodule init 更新,git submodule update —recursive拉取

git subtree

git remote add subtree-origin <url> 添加远程库

git subtree add —prefix=subtree subtree-origin master 把subtree-origin的master分支clone到subtree目录

git subtree pull —prefix=subtree subtree-origin master 更新pull subtree仓库


git cherry-pick

git cherry-pick <commitid> 将某一次提交应用于当前分支

场景 命令
应用单个 commit git cherry-pick <commit-hash>
应用连续多个commit git cherry-pick <start-commit>^..<end-commit>

例如: git cherry-pick abc1234^..def5678 | | 应用但不提交 | git cherry-pick -n <commit-hash> |


git worktree

git worktree add <path> <branch> 如果分支当前不存在 ,可以添加-b选项创建新分支

示例: git worktree add ../name-feature-1 feature-1

git worktree list 列出所有工作树

git worktree remove <path> 删掉指定工作数

git worktree remove --force <path>