git用法总结

本文最后更新于:2025年11月19日 下午

git使用

git 2.23之后,多了switch和restore命令,git –help已看不到checkout的踪影(还是支持)。因为checkout用法太多,语义的歧义太大了。

但是很多商业公司一直用着老旧的系统,老旧的软件,所以下面基本只会涉及checkout命令。

[toc]

配置

(1)初次配置用户名邮箱

1
2
git config --global user.name "leon"
git config --global user.email xxx@xxx

(2)解决git status中文文件名乱码

1
git config --global core.quotepath false

(3)关闭git自动转换换行符

1
git config --global core.autocrlf false

(4)git status时忽略权限改变

1
git config core.filemode false

(5)配置默认编辑器

1
2
git config --global core.editor vim
git config --global merge.tool vimdiff

(6)配置别名

1
2
3
4
5
6
7
8
9
10
11
12
13
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.br branch
# 简短的查看日志
git config --global alias.slog 'log --pretty=format:"%h %aN %ad %s" --date=format:"%Y-%m-%d %H:%M:%S"'
# 彩色查看log
git config --global alias.clog 'log --pretty=format:"%Cred%h%Creset %Cgreen%cr%Creset %s %Cblue%aN%Creset"'
# 图形化log
git config --global alias.cglog 'log --graph --pretty=format:"%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset%n" --abbrev-commit --date=relative --branches'

alias.calog=log --graph --pretty=format:"%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset%n" --abbrev-commit --date=relative --branches
# 清理,但是保留一些文件
mclean = clean -xfd -e .cache -e .clang-format -e .vscode -e compile_commands.json

(7)配置提交注释模版

1
2
vi ~/.gitCommitTemplate
git config --global commit.template ~/.gitCommitTemplate

(8)查看现有配置

1
2
3
git config --list
# 同时显示来源
git config --list --show-origin

忽略文件

创建.gitignore文件即可,忽略遵从如下规则

  • #开头的行表示注释
  • 可以使用标准的 glob 模式匹配,它会递归地应用在整个工作区中
  • 匹配模式可以以(/)开头防止递归
  • 匹配模式可以以(/)结尾指定目录
  • 要忽略指定模式以外的文件或目录,可以在模式前加上叹号(!)取反
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 忽略所有的 .a 文件
*.a

# 但跟踪所有的 lib.a,即便你在前面忽略了 .a 文件
!lib.a

# 只忽略当前目录下的 TODO 文件,而不忽略 subdir/TODO
/TODO

# 忽略任何目录下名为 build 的文件夹
build/

# 忽略 doc/notes.txt,但不忽略 doc/server/arch.txt
doc/*.txt

# 忽略 doc/ 目录及其所有子目录下的 .pdf 文件
doc/**/*.pdf

使用

git中的重要概念

工作区、暂存区、仓库

这是经常用到的概念,关系图如下:

sequenceDiagram
	participant ws as 工作区
	participant staged as 暂存区(staged)<br/>git里叫index
	participant .git as .git目录(仓库)
	participant remote as 远程仓库
	ws ->> staged: git add
	staged->>ws: git reset <file>
	staged->>.git: git commit
	.git->>ws: git checkout
	.git->>remote: git push
	remote->>.git: git fetch
	remote->>ws: git pull
	
	

查看修改diff

  • 查看当前工作区的修改(修改后还未暂存)
    git diff

  • 查看暂存区中的修改(已暂存,下一次commit的修改)
    git diff --staged

  • 查看某几个版本间修改文件列表
    git diff --stat 38bb22e88660..HEAD -- . :^userspace/public/apps/lighttpd/web
    :^表示排除这个目录

远程仓库

1
2
3
4
5
6
7
8
# 查看远程仓库
git remote -v
# 添加远程仓库
git remote add shortName https://github.com/paulboone/ticgit
# 从远程仓库拉取(原厂仓库的所有内容会获取下来)
git fetch
# 将提交推送到远程仓库
git push origin master

日志查看

-p 显示修改内容

-2 显示最近两次修改

git log -p -2

git log --stat 查看提交修改的文件统计

git log --since="2023-06-16 00:00:00" 查看从某天起迄今的日志。

查看某个函数的修改记录

1
2
git log -L :FunctionName:FilePath
git log -L :lan_connect:users/tianyi/tylib/ty_common/system/set_bridge.c

查看某行的修改记录

1
git blame -L 58,100 file_name # 58~100 行代码

双点和三点

分支多了,merge,cherry-pick混合使用,有时不清楚一个分支上究竟有哪些提交是独有的,可以通过如下命令来办。

双点

1
2
git log foo..bar
等价于: git log ^foo bar, 包含bar不包含foo的部分,即bar特有的

三点

1
2
git log foo...bar
等价于:包含 foo bar,但不包含二者公共部分

可以通过下图看懂命令的不同

忽略cherry-pick,merge的提交

每次merge和cherry-pick都会产生新的提交,默认在使用双点,3点命令时,这些提交也会显示出来。但实际上并不希望看到这些。可通过如下参数控制。(实测双点没有效果,3点效果较好)
git log foo...bar,只是打印二者所有的不同提交,看不出来谁是谁的,使用--left-right可以看出来哪边是哪边的

1
2
3
4
5
git log --left-right master...experiment
< F
< E
> D
> C

--no-merges将不显示merge的提交
--cherry-pick将会把cherry-pick的视为相同的提交
--left-only --right-only将只显示一边的提交
--cherry-mark将相同的提交用”=”号表示,不同的用”+”号标记

1
2
3
4
# 显示foo特有的
git log --cherry-pick --left-only --no-merges foo...bar
# 显示bar特有的
git log --cherry-pick --right-only --no-merges foo...bar

cherry-pick大量代码

master分支上,哪些需要合并到ctc分支

1
2
# 检查从2025-3-1日起,哪些带通用的提交,可能需要合并到ctc上
git log --cherry-pick --left-only --no-merges --grep "通用" --since "2025-03-01" --pretty=format:"%ad %h %s (%an)" --date=format:"%Y-%m-%d %H:%M:%S" master...ctc

终端还是不方便,借助gitk来使用

1
gitk --cherry-pick --left-only --no-merges  --since="2025-3-1" master...ctc

gitk界面搜索”通用”来过滤,右键cherry-pick

撤销

(1) 修改刚提交的日志信息

1
git commit --amend -m "刚修改错了"

(2) 提交时漏了文件

1
2
git add forget_file
git commit --amend -m "漏了文件,重新提交"

(3) 撤销暂存区的文件(不会修改文件,只是取消文件暂存)

1
git reset HEAD xxx.file

(4) 撤销对文件的修改,会拷贝最新版本的文件覆盖当前文件

1
git checkout -- xxx.file

(5) git commit之后撤销,只撤销commit,代码修改仍在

1
git reset --soft HEAD^

--soft 不删除工作空间改动代码

--hard 删除工作空间改动

HEAD^ 上一个版本,也可以写成HEAD~1

(6) 撤销中间某次commit

1
git revert commit_id

执行之后会自动提交。

status

(1)git status不显示Untracked的文件

1
git status -uno

(2)取消某个文件的跟踪

如下命令会删除对这个文件的跟踪,即从版本库中删除,但本地还存在

1
2
git rm --cached -r dir
git commit -m "untrack some dir"

git忽略已经被提交的文件 - SegmentFault 思否

(3)对于已经跟踪的文件,使用.gitignore文件无效,可以使用

这个命令只对本地生效,只是设置了文件未修改的标记。

1
git update-index --assume-unchanged file命令

显示哪些文件做了标记

1
2
git ls-files -v | grep '^[[:lower:]]'
git ls-files -v | grep '^h'

撤销对这些文件的忽略

1
git update-index --no-assume-unchanged file

分支

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 新建分支
git branch iss90
# 切换到分支
git checkout iss90
# 两个命令可以合并
git checkout -b iss90

# 查看分支
git branch
# 查看已合并过的分支
git branch --merged

# 合并分支到主线,先切换到master分支,然后合并
git checkout master
git merge iss90

# 删除分支
git branch -d iss90

查看未合并的分支

1
2
3
4
5
6
7
8
9
10
# 查看已合并的分支
git br --merged
# 查看未合并的分支
git br --no-merged

# 查看分支ft上有哪些提交没有合并到master分支上
git log master..ft

# 忽略merge的提交,因为merge是一条单独的提交
git log master..ft --no-merges

合并

合并单条提交

1
git cherry-pick 62ecb3

变基rebase

变基:修改当前分支的基底。

如果可以直接快进合并,那么不需要变基。此时变基会提示:
Current branch mesh_bug is up to date.

如果两边分支都有提交,那么可以变基,如下

1
2
3
4
5
6
7
8
# 将experiment分支上的修改变基到master分支上
$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
# 然后进行一次快进合并
$ git checkout master
$ git merge experiment

如果要线性修改提交记录,那么可以一直使用rebase,pull代码时也是用rebase: git pull -r

远程分支

1
git remote show <remote>

贮藏 stash

有时需要切换分支,但本地工作区已经很乱了,改了很多东西。想临时储存一下当前混乱的工作区。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 贮藏当前修改(只会贮藏已修改和已暂存的,未跟踪的不会贮藏)
git stash push
# 贮藏所有内容(未跟踪的也会贮藏,.gitignore忽略的文件也会贮藏,贮藏之后工作区完全干干净净)
git stash push -a -m "msg"

# 查看贮藏列表
git stash list

# 恢复上一次贮藏
git stash apply
# 贮藏很多次后,恢复指定贮藏
git stash apply stash@{1}

# 丢弃贮藏
git stash drop stash@{1}

# 恢复上一次贮藏并丢弃它
git stash pop

# 查看贮藏区的修改
git stash show stash@{0}
# 查看修改内容
git stash show -p stash@{0}

交互式暂存

有时我们修改了一个文件的两个地方,但是想分开提交,一次只提交文件中某几处差异。这时就可以使用交互式暂存。git add -i

1
2
3
4
5
6
7
8
9
10
$ git add -i
staged unstaged path
1: unchanged +0/-1 TODO
2: unchanged +1/-1 index.html
3: unchanged +5/-1 lib/simplegit.rb

*** Commands ***
1: [s]tatus 2: [u]pdate 3: [r]evert 4: [a]dd untracked
5: [p]atch 6: [d]iff 7: [q]uit 8: [h]elp
What now>

s查看状态,p就是我们需要的命令。
p后,会让你选择要操作那个文件,选择文件后,什么也不输入回车,进入下一个选择页面,这个页面会一次次的出现差异,让你选择是否暂存,输入y或者n即可。

三棵树

HEAD: 上一次提交的快照,下一次提交的父节点,相当于该分支的最后一次提交。

Index:预期的下一次提交的快照,就是暂存区

work目录:就是自己的工作区。前两个都是抽象的.git目录下的一些东西。

每一次git操作,都会有一个快照产生,三棵树都指向各自的快照。

当work和index快照指向不同,那么git status就会显示“文件已修改,但未暂存”

当index和HEAD指向不同,那么git status就会显示“文件已暂存”

当三棵树的指向全部相同,那么git status就是干净的。

reset命令

对三棵树的操作根据选项不同,区别如下(默认是mixed选项)

1
2
3
--mixed               reset HEAD and index
--soft reset only HEAD
--hard reset HEAD, index and working tree

–soft

只是修改HEAD指向,相当于撤回了commit动作。所有修改的文件都是已暂存状态。

所以可以用–soft合并之前的多个提交(压缩提交,有时在一个bug分支上修改bug,修改完后又发现引入新问题,还要修改,提交了几次,最后验收通过,为了让分支上的日志就更容易看出来具体修改,而不是几次不完善的修改,可以压缩提交)。

–mixed

修改HEAD和index指向,相当于撤回了commit和add动作。所有撤回的修改都存在工作区中。

–hard

三棵树同时修改,相当于撤回了commit,add,和本地修改。(因为之前的已经提交了,还可以通过reflog救回来)

git reset [版本aa] 路径

git reset 跟路径的情况下,会把index更改为reset的版本状态,把work中的文件改为HEAD中的状态(即修改撤回到work了)。如果此时直接commit,那么commit后仓库中的文件跟版本aa是一样。

例子

1
2
3
4
5
6
7
8
9
10
11
12
# 回退到当前版本
git reset HEAD
# 回退到上一个版本
git reset HEAD~
# 回退到指定版本
git reset 3f51

# 撤销暂存区的文件(不会修改文件,只是取消文件暂存)
git reset HEAD xxx.file

# git commit之后撤销,只撤销commit,代码修改仍在
git reset --soft HEAD^

tag

查看tag

1
2
3
git tag
# 模糊匹配
git tag -l "v1.8.5*"

添加tag

添加有注释的tag

1
git tag -a v1.4.1 -m "my version 1.4" [shaid]

如果不使用-m参数,会打开编辑器,让你输入信息。查看tag的提交信息可以使用

1
git show v1.4.1

添加轻量级tag

1
git tag v1.4.1 [shaid]

推送tag

tag默认不会推送到远程服务器。需要自己推送。

1
git push origin v1.4.1

如果要推送所有tag,可以使用如下命令,会推送所有本地的tag到远端服务器。

1
git push origin --tags

gerrit推送tag也是上面这种命令,没有特殊的地方。

删除tag

1
2
3
git tag -d v.1.4.1
# 删除远程标签
git push origin --delete <tagname>

git-svn

使用git本地管理,修改推送到svn

注意:

git-svn并不能使用完整的git功能,为了避免遇到麻烦,保持线性提交,不能有merge产生的提交。把master分支外的修改全部变基到master分支。

(1)clone分支,-T表示trunk目录的名字,-b表示分支的名字,这样才会把分支这些一起clone下来

1
2
3
4
git svn clone file:///tmp/test-svn -T trunk -b branches -t tags

# 不clone所有版本,有的svn仓库提交非常多,使用-r只clone部分提交下来
git svn clone http://192.168.21.225/svn/rtl-ax1800-umc/trunk rtl-ax1800-umc-git-code -r 1400:HEAD

svn上分支管理有可能比较乱,没有统一命名和目录管理,所以可以不用弄这些,直接clone一个分支

(2)日常修改,然后把修改推送到svn

1
2
3
4
5
6
7
8
git add
git commit
# stash一下,保持本地干净,不然svn dcommit不上去
git stash push
# 默认dcommit后会拉取远程修改合并到本地
git svn dcommit
# 贮藏的修改再拉下来
git stash pop

(3)如果本地在git分支上修改,需要先变基到跟踪的分支,再dcommit

1
2
3
git rebase master
git checkout master
git svn dcommit

(4)拉取远程修改

1
2
3
4
git svn fetch
git rebase
# 以上两个命令也可以合成以下一个命令
git svn rebase

(5)其他svn操作

1
2
git svn log
git svn info

(6)添加svn分支(针对不规则命名情况)
参考 git-svn:通过git来管理svn代码
通过如下命令来添加分支
git config --add svn-remote.<远程分支名称>.url <svn地址,要包含具体分支路径>
git config --add svn-remote.<远程分支名称>.fetch :refs/remotes/<远程分支名称>
实例如下:

1
2
git config --add svn-remote.svn/AX5410.url http://172.20.1.217/svn/bcm-ax3000-ctcc/branches/products/FY-AX5410
git config --add svn-remote.svn/AX5410.fetch :refs/remotes/svn/AX5410

然后下载分支代码

1
git svn fetch <远程分支名称>   #如 git svn fetch svn/AX5410

(7)如何同时在两个svn分支上开发

1
2
3
# 基于远程分支创建本地分支
git br feiyi_ui remotes/origin/feiyi_ui
git co feiyi_ui

注意:git br -vv并不能看到当前分支的跟踪分支是哪个svn分支,这和git远程分支不太一样。当git svn dcommit时,它会自动去找这个本地分支是从哪个svn分支来的,自动提交到对应的svn分支。

解决冲突

当dcommit有冲突时,需要git svn rebase拉取远程修改合入本地,解决冲突后再dcommit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# dcommit发现冲突
git svn dcommit
# 拉取svn新代码合入本地,会有冲突的文件
git svn rebase
# 查看那些文件冲突
git st
# 修改文件,解决冲突
vi xxx
# 标记冲突已解决
git add xxx
# 继续变基
git rebase --continue
# 提交代码
git svn dcommit

空目录问题

git不支持空目录权限管理,无法git add空目录,所以如果svn服务器上有空目录。使用会有一些注意点:
(1)git svn clone的最后一步,git会主动建立空目录,保持代码一致
(2)如果rm所有代码,然后git reset –hard,这时空目录会丢失
(3)如果git stash -a暂存所有文件,空目录也会丢失。
(4)恢复办法为:git svn rebase,这时空目录会重新创建。

解决办法
(1)先通过svn下载代码,然后查找空文件夹 find -type d -empty
(2)在空文件夹下建一个.gitkeep(约定俗成),并提交这个空目录(视情况)

删除空目录问题
git 删除空目录传了之后,git svn dcommit并没有删除空目录。

工作流

2个固定分支
master主分支:不做任何修改,用于merge要提交的代码,从原厂仓库拉最新代码
工作空间分支:提交本地化的管理型代码(gitignore,.vscode,.gitkeep),同步最新的master分支

其他特性分支:从工作空间创建,修改bug,特性开发的分支。修改完后,将修改点rebase或者merge到master。从master提交代码,

(1)git svn clone代码
(2)创建工作空间分支,编译
(3)在工作空间分支创建特性分支
(4)将特性分支的修改merge到master
(5)在master上提交,拉取远程仓库代码
(6)把master分支合并到特性分支

修改url

(1) vi .git/config 修改url为新的url
(2) git svn fetch
(3) vi .git/config 修改url为老的url
(4) git svn rebase -l
(5) vi .git/config 修改url为新的url
(6) git svn rebase

清理

clean用于清理未暂存的文件,保持干净的工作区,可以代替make clean
git clean -xfd

1
2
3
4
-f, --force           force
-d remove whole directories
-x remove ignored files, too 默认忽略的文件不会清理,-x会一并清理
-e 排除某些文件

会删除空目录,记得git svn rebase

github

如何在github上下载某个项目的单独某个目录?

使用svn来下载,例如我想下载我的practice项目的hash_table目录

(1)在github上点开这个目录,浏览器地址栏可以得到这个地址

https://github.com/leon0625/practice/tree/master/hash_table

(2)将地址里的tree/master换成trunk

https://github.com/leon0625/practice/trunk/hash_table

(3)使用svn下载上面的地址

svn co https://github.com/leon0625/practice/trunk/hash_table

子模块

添加子模块

1
git submodule add https://github.com/chaconinc/DbConnector

添加之后,查看状态会出现一个.gitmodules文件和一个目录,这两个文件都要提交

克隆包含子模块的项目

直接clone之后,包含模块目录,但是不包含模块代码。有如下三种方式来clone完整代码
(1)分步执行

1
2
3
$ git clone https://github.com/chaconinc/MainProject
$ git submodule init
$ git submodule update

(2)clone添加参数--recurse-submodules

1
$ git clone --recurse-submodules https://github.com/chaconinc/MainProject

(3)update添加init,recursive参数

1
2
$ git clone https://github.com/chaconinc/MainProject
$ git submodule update --init --recursive

更新子模块

当运行 git submodule update --remote 时,Git 默认会尝试更新 所有 子模块, 所以如果有很多子模块的话,你可以传递想要更新的子模块的名字。
更新之后,需要提交一下子模块目录,不然远程库上引用的子模块版本不会变

在子模块上提交

进入子模块目录,直接操作就好。推到远程库之后,记得在主仓库里面更新一下子模块。然后提交引用。

删除子模块

1
2
3
git submodule deinit -f <submodule_path>  
git rm -f <submodule_path>
git commit -m "Remove submodule <name>"

git用法总结
https://leon0625.github.io/2020/10/14/1132452c5a90/
作者
leon.liu
发布于
2020年10月14日
许可协议