GIT学习笔记

风尘

文章目录

  1. 1. 工作区和暂存区
  2. 2. 第一次安装设置
  3. 3. 创建版本仓库
  4. 4. 版本管理
    1. 4.1. 文件状态
    2. 4.2. git status -s符号意义
    3. 4.3. Git HEAD
    4. 4.4. 版本回退
    5. 4.5. 撤销修改
    6. 4.6. 修改提交
    7. 4.7. 删除文件
    8. 4.8. 文件移动
  5. 5. 远程仓库
  6. 6. 分支管理
    1. 6.1. 重命名远程分支推荐做法:
    2. 6.2. 删除选程分支
    3. 6.3. 分支合并忽略指定文件方法
    4. 6.4. 分支-变基
  7. 7. 分支管理策略
    1. 7.1. Bug分支
    2. 7.2. Feature分支
    3. 7.3. 预发布分支
    4. 7.4. 推送分支(同远程仓库)
    5. 7.5. 跟踪分支
    6. 7.6. 多人协作
  8. 8. 标签管理
    1. 8.1. 推送远程标签
    2. 8.2. 删除远程标签
    3. 8.3. 检出标签
  9. 9. 子模块
  10. 10. GIT工具
    1. 10.1. 日志
    2. 10.2. 日志搜索
    3. 10.3. 提交区间
    4. 10.4. git show
    5. 10.5. 比较 git diff
    6. 10.6. 交互式暂存
    7. 10.7. 清理 git clean
    8. 10.8. 搜索 git grep
    9. 10.9. 忽略文件
    10. 10.10. 命令自动补全
  11. 11. 常见问题
    1. 11.1. 中文文件名编码问题
    2. 11.2. HTTP/HTTPS方式免密操作
    3. 11.3. 换行符问题
    4. 11.4. 文件名大小写问题

[TOC]

工作区和暂存区

git和其它版本控制系统,如SVN的一个不同之处就是有暂存区的概念

工作区:就是电脑能看到的仓库文件夹目录.

版本库:工作区内的隐藏目录.git,这个不算工作区,而是git的版本库.

GIT版本库里存了很多东西,其中最重要的就是stage(或者叫index)的暂存区,还有GIT为我们自动创建的第一个分支master,以及一个指向master的指针HEAD.

实际上git add就是把所有修改放到暂存区,git commit是把暂缓区的修改提交到分支.

因此,修改一个文件后必须要添加(git add)到暂存区,才能提交(git commit)到分支,否则提交无效

第一次安装设置

#因为git是分布式,这些就是你在提交commit时的签名。(--global全局设置,不加则设置当前版本库)
$ git config [--global] user.name "Your Name"
$ git config [--global] user.email "email@example.com"

创建版本仓库

$ git init [--bare] # 创建版本仓库 (--bare 目地是创建一个裸仓库,作为远程仓库使用)
$ git add [-p|-u|-A][<path>,...] # 添加文件到版本仓库
| -------- | ----------------------------------------------------------------------------------------
| <path>   | 添加<path>路径中修改的文件(不包括删除文件)或新增加文件到暂存区
| -u       | 添加<path>路径中所有已修改的文件(不包括新增加文件)到暂存区,可省略<path>表示当前目录
| -A       | 添加<path>路径中所有已修改的文件(包括删除文件和新增文件)到暂存区,可省略<path>表示当前目录
| -p       | 通过补丁块拣选方式选择要添加到暂存区的内容(? 键查看相关文档)
| -------- | ----------------------------------------------------------------------------------------

$ git add -p # 通过补丁块的方式选择要添加到暂存区的内容( ? 查看快捷键文档)
$ git commit -m '提交说明信息' # 提交版本
$ git commit [-v] # 提交版本,启动编辑器编写注释(-v显示详细变更信息)
$ git commit -a # 提交版本,跳过使用暂存区(不用 git add)

版本库版本库

版本管理

文件状态

$ git status (-s/--short) #查看版本库状态以及文件修改状态(-s 显示简单信息)
1.未修改状态:nothing to commit,working tree clean
2.修改后文件已在缓存区:Changes to be commited
3.新增文件状态:Untracked files
4.修改后文件未在缓存区:Changes not staged for commit
3与4状态区别在于3无法用git commit –am命令将文件添加到本地仓库

git status -s符号意义

?? 新添加的未跟踪文件前面

A 新添加到暂存区中的文件

M 修改过的文件(出现在右边的,表示该文件被修改了但是还没放入暂存区,出现在靠左边的 M 表示该文件被修改了并放入了暂存区)

Git HEAD

HEAD是版本当前分支指针的引用,它是指向最后一次提交的指针。可以通过.git/HEAD文件查看具体引用,也可以通过.git/refs/heads/分支名查看指向的提交。

  • HEAD^

    ^符号表示查找当前提交的 父提交 ,所谓父提交就是当前提交的前一次提交,如果前一次提交有分支合并,则有父提交有两个(其一是当前分支的最近一次非合并提交,其二是合并分支提交的第一个节点)。如下图:

    HEAD 父提交HEAD 父提交

    另一种更简单的可视化方式是把两个分支拆分来看,如下图:

    HEAD 父提交HEAD 父提交

    $ git show --oneline HEAD # 指向 A
    $ git show --oneline HEAD^ # 指向第一个父提交 B( HEAD^ 等同于 HEAD^1 )
    $ git show --oneline HEAD^2 # 指向第二个父节点 E
    $ git show --oneline HEAD^3 # 出错(因为没有第三个父节点)
    

    HEAD^^则表父提交的父提交,即"爷提交",上图中的则会指向C提交。依此类推HEAD^^^则指向D提交。

  • HEAD~

    ~符号表示当前提交的在 当前分支 的后面的提交,注意的是当前分支,而不包含合并分支。可以理解为在一条直线上向后指向。如下图:

    向后提交向后提交

    $ git show --oneline HEAD~  # 指向提交 A( HEAD~ 等同于 HEAD~1 )
    $ git show --oneline HEAD~2 # 指向提交 C
    $ git show --oneline HEAD~3 # 指向提交 D
    $ git show --oneline HEAD^2~1 # 指向提交 F
    
  • HEAD@{}

    @{}符号表示HEAD在本地库被指向时的时间,通常使用git reflog --date=iso可以查看。

版本回退

SVN不一样,GIT每个提交版本的commit id不是1,2,3…的数字而是一个SHA1计算出来的十六进制数字.因为GIT是分布式控制系统,如果用1,2,3…数字表示多人协作会重复.

# git reset 版本号
$ git reset (--mixed) HEAD^ #回退到上个版本,缓存区和你指定的提交同步,但工作目录不受影响(--mixed是默认选项,可以不加)
$ git reset --soft HEAD^ #回退到上个版本,缓存区和工作区都不受影响
$ git reset --hard HEAD^ #回退到上个版本,缓存区和工作目录都同步到你指定的提交(危险指令)

git reset 后面如果是版本号不用写全,只写前几位就可以了.GIT版本回退非常快,因为GIT在内部有个指向当前版本的HEAD指针,当回退版本时GIT仅仅是把HEAD从指向当前版本改为指向回退版本(如下图),顺便把工作区更新了.

当前版本当前版本

版本回退版本回退

由于git log仅显示从当前版本到历史版本的纪录,如果回退了某个版本,又想还原到最新版本,可以使用git reflog查看commitId然后回退到指定版本。
因为,git reflog 可以查看所有分支的所有操作记录(包括commit和reset的操作),包括已经被删除的commit记录,git log则不能察看已经删除了的commit记录。

撤销修改

$ git commit --amend # 撤消上次文件提交的修改,重新提交(--amend选项仅针对当前版本未push情况有效)
$ git reset HEAD 文件名 # 撤销暂存区文件修改(HEAD表示撤销到最新版本)
$ git checkout -- 文件名 # 撤销工作区文件的修改(如果没有--应变成了切换分支的命令)
$ git restore [--staged] 文件名 # 撤销工作区文件的修改(同上),--staged 选项表示撤销暂存区的修改相当于 git reset 操作。
$ git rever <commitId> # 撤销指定历史提交的内容,并会新增一条撤销记录 commidId,主要用于安全地取消历史提交。

已经推送到远程仓库的撤销,需要使用git push --force 强制推送(危险操作慎用)

修改提交

  • 拆分当前提交
# 重置到上一次提交
$ git reset HEAD^
# 拣选补丁块
$ git add -p
# 对拣选出的补丁块进行提交
$ git add -u
$ git commit

如果要拆分的提交,不同的实现逻辑耦合在一起,难以通过补丁块拣选(git add -p)的方式修改提交,此时可以直接编辑文件,删除要剥离出此次提交的修改,然后执行下面命令:

# 追加提交剥离后的修改
$ git commit -a --amend
# 还原上次提交前文件的修改
$ git checkout HEAD@{1} -- .
# 提交文件
$ git commit
  • 拆分历史提交
# 先对历史提交进行变基
$ git rebase -i commitId^
# 在打开的编辑器(如 vi 编辑器)中修改关键字 pick 为 edit
-----------------------------------------
pick commitId 要拆分的提交
pick ...   其他参与变基的提交
-----------------------------------------
||
VV
-----------------------------------------
edit commitId 要拆分的提交
pick ...   其他参与变基的提交
-----------------------------------------
# 保存文件退出,变基开始
# 首先会在 commitId 处停下来,此时要拆分的提交成为当前提交。后续按照 "拆分当前提交" 进行操作。
# 最后拆分结束后再执行下面命令,完成变基。
$ git rebase --continue
  • 修改历史提交

当想要修改一次历史提交内容时,通过fixup 和变基操作即可实现,步骤如下:

# 直接在当前工作区进行修改,然后提交(-m 选项可以修改历史提交信息)
$ git commit -a --fixup commitId [-m]
# 此时提交历史会增加一个 "fixup!" ,此时可以通过变基 --autosquash 选项对修改进行合并
$ git rebase -i -autosquash commitId^
# git 会在编辑器内对提交进行自动排序,修改关键字 fixup 为 squash
-----------------------------------------
pick commitId 最近一次的提交
fixup commitId fixup 的提交
pick ...   其他参与变基的提交
-----------------------------------------
||
VV
-----------------------------------------
pick commitId 最近一次的提交
squash commitId fixup 的提交
pick ...   其他参与变基的提交
-----------------------------------------
# 保存文件退出,变基开始
# 自动打开保存消息,保存退出,历史修改成功

删除文件

$ git rm [--cached] 文件名 #从版本库中删除文件(--cached从缓存区中移除到工作区)
$ git clean [-fd] # 清空未跟踪文件(-f 强制清空 -d 递归文件夹清空)

GIT删除(git rm)文件后,直接提交(git commit)即可,不需要再添加到暂存区(git add).
如果不小心删错了,可以直接撤销修改(git checkout – 文件名)
如果文件已经提交到版本库,那么永远不用担心误删,但只能恢复到最新版本库,最新修改将丢失.

文件移动

$ git mv file_from file_to #重命名文件
实际相当于运行下面三个命令
$ mv file_from file_to
$ git rm file_from
$ git add file_to

远程仓库

$ git remote add origin 远程仓库地址 #关联远程仓库(origin是git远程仓库的默认名,可以修改)
$ git push #本地内容推送到远程仓库

关联后第一次推送仓库的所有内容使用命令:git push -u origin master
此后,每次推送本地修改内容可以使用命令:git push origin master

$ git clone [-b 分支名] 远程仓库地址 #克隆远程仓库到本地(-b指定分支)
$ git fetch 远程仓库名 #从服务器上抓取本地没有的数据,它并不会修改工作目录中的内容, 它只会获取数据然后让你自己合并.
$ git pull (--allow-unrelated-histories) #从服务器抓取分支数据并然后尝试合并.

由于git pull命令经常让人困惑,所以通常单独显式地使用 git fetch 与git merge 命令会更好一些。使用git fetch命令后会返回一个FETCH_HEAD,可以使用git log -p FETCH_HEAD查看详情。

$ git remote #显示远程仓库名
$ git ls-remote #显示远程引用完整列表
$ git remote show [remote-name] #查看远程仓库更多信息

$ git remote -v #显示远程仓库信息
origin  git@github.com:WindusL/LearningNotes.git (fetch)
origin  git@github.com:WindusL/LearningNotes.git (push)

上面显示了可以抓取和推送的origin地址。如果没有推送权限就看不到push的地址。

$ git remote rename [oldname] [newname] #对远程仓库的简写名称重命名指令
$ git remote rm [remote-name] #对远程仓库的简写名称进行移除的命令

分支管理

Git里默认有一个主分支master,其中HEAD严格来说指向的不是提交,而是当前分支,分支才指向提交。

指向图指向图

Git创建一个分支只是增加一个指针,然后将HEAD指向新的分支 ,表示在当前分支,工作区的文件没有任何变化。

$ git branch 分支名 #创建分支
$ git checkout 分支名 #切换分支
#上面两个命令相当于
$ git checkout -b 分支名 #创建并切换分支(加上-b表示先创建后切换)
$ git checkout -b 分支名 远程分支名 # 拉取远程分支到本地分支((加上-b表示先创建后切换)

$ git branch (-a/r) #列出所有本地分支(-a 包括远程分支,-r仅列出远程分支)
$ git branch [-v|-vv] #查看每个分支最后一次的提交(-vv选项会列表更多信息)
$ git branch -d 分支名 #删除已合并本地分支
$ git branch -D 分支名 #强制删除分支(包括未合并的分支)
$ git branch [-m|-M] 旧分支名 新分支名 # 分支重命名(-M强制生命名)
$ git merge 分支名 #合并指定分支到当前分支

$ git switch [分支名|-] # 切换分支,- 表示切换到上一个分支
$ git swtitch -c 分支名 # 创建一个新分支并切换到该分支
$ git switch --detach commitId # 切换一个提交到游离状态

# 分支添加注释(注意,这个注释是本地存储的。它不能被推送,因为它存储在.git/config中)
$ git config branch.hexo.description 注释内容
# 修改分支注释
$ git branch --edit-description

重命名远程分支推荐做法:

1、删除远程分支

2、push本地新分支名到远程

$ git branch --merged|--no-merged #查看已(未)合并的分支

删除选程分支

git branch -dr 分支名 # (-r删除远程分支)
git push [远程名] :[分支名]

分支合并忽略指定文件方法

第一步:自定义merge driver
$ git config --global merge.ours.driver true

第二步:在被合并分支下把忽略文件(如test.json)写入项目根目录.gitattributes文件,并提交
$ git checkout dev
$ echo 'test.json merge=ours' >> .gitattributes  
$ git add .gitattributes  
$ git commit -m 'chore: Preserve email.json during merges' 

第三步:回到要合并分支执行合并操作
$ git checkout master
$ git merge dev 

分支-变基

分叉的提交历史分叉的提交历史

merge 后的提交历史merge 后的提交历史

分支合并会产生记录,如果想不产生这些记录就可以使用分支变基后再合并.

$ git rebase 分支名 #把当前分支变基到指定分支

#第一步:切换到要变基的分支进行变基
$ 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

变基后合并的提交历史变基后合并的提交历史

变基注意事项:
只在从未推送至共用仓库的提交上执行变基命令。如果真的在远程仓库进行了变基产生了混乱,可以使用 git pull --rebase 命令将分支变基到远程仓库再进行合并。

变基的另一种场景是,假如从 master 分支创建一个新的分支server,提交了 C3C4。 然后从 C3 上创建了分支 client,为客户端添加了一些功能,提交了 C8C9。 最后,回到 server 分支,又提交了 C10

变基前提交历史变基前提交历史

此时希望将 client 中的修改合并到master分支并发布,但暂时并不想合并 server 中的修改, 因为它们还需要经过更全面的测试。

# 使用 --onto 选项:取出 client 分支,找出它与 server 分支分歧之后的补丁,然后把这些补丁在 master 分支上重放一遍。
git rebase --onto master server client

变基后提交历史变基后提交历史

# 切换到 master 分支
git checkout master 
# 合并 bug 分支
git merge client

合并后提交历史合并后提交历史

分支管理策略

通常,合并分支,如果可能Git会用Fast Forward模式,但这种模式下,删除分支后,会丢掉分支信息。
如果强制禁用Fast Forward模式,Git会在merge时生成一个新的commit,这样从分支历史上就可以看出分支信息。

$ git merge --no-ff -m 注释 分支名 #合并分支(--no-ff表示禁用Fast Forward模式,因为要生成一个新的commit所以要加上-m注释参数)

Bug分支

当一个分支的工作还没有做完,不能提交,而此时又要及时做其它工作时,可以先把工作区储藏起来,创建bug分支(命名:fixbug-issueId)。

$ git stash #储藏工作区(储藏后再用git status查看就是干净的, 除非是没有被git管理的文件)
$ git stash save (--keep-index|-u|--patch)注释 #储藏工作区并添加注释
(
--keep-index #不储藏任何通过 git add 命令已暂存的;
-u #储藏任何创建的未跟踪文件
--patch #不会储藏所有修改过的任何东西,但是会交互式地提示哪些改动想要储藏、哪些改动需要保存在工作目录中。
)
$ git stash list #查看stash列表
$ git stash apply #恢复stash,但stash不删除
$ git stash pop #恢复stash,同时删除stash
$ git stash drop #移除stash (会删除储存所有修改,谨慎使用)
$ git stash show (-p/--patch) #查看stash(详细)修改

如果多次执行stash后,恢复stash就加上stash名,如:git stash pop/apply stash@{0}
默认情况,stash不会存储Untracked files.如果想要存储,要先git add添加到版本库或者使用git stash -u选项,如下:

git stash -u (--include-untracked) # 存储工作空间包括Untracked files

Feature分支

开发新功能时最好创建一个新的分支(命名:feature-x)。

预发布分支

发布正式版本之前(即合并到 Master 分支之前),我们可能需要有一个预发布的版本进行测试(命名:release-版本号)。

推送分支(同远程仓库)

$ git push origin 分支名 #推送分支到远程仓库

跟踪分支

从一个远程分支检出一个本地分支会自动创建一个叫做 “跟踪分支”(有时候也叫做 “上游分支”)。 
跟踪分支是与远程分支有直接关系的本地分支,Git 能自动地识别去哪个服务器上抓取、合并到哪个分支。

#设置跟踪分支(将本地分支与远程库分支时行连接。)
$ git branch --set-upstream origin 分支名 #最新版本已被废弃
#最新版本命令
$ git branch --set-upstream-to=origin/<branch> 分支名 (可简写为 -u)

#取消跟踪远程分支
$ git branch --unset-upstream 分支名

多人协作

当从远程仓库克隆时,Git自动把本地master分支和远程分支对应起来。并且远程分为默认名是origin。

多人协作的工作模式:
1.试图推送分支。
2.推送失败则要先抓取远程分支,试图合并。
3.合并有冲突,则解决冲突,并在本地提交。
4.没有冲突或解决掉了冲突,再推送到远程分支。

标签管理

发布新版本时,通常在版本库打一个标签,来确定打标签时刻的版本。将来无论什么时候,取某个标签的版本就是那个打标签时候的历史版本。所以,标签也相当于版本库的一个快照。
Git标签虽然是版本库的快照,但其实就是一个指向commit的指针(与分支类似,但分支可以移动,但标签不能移动)。所以,创建标签也是瞬间完成的。

$ git tag #查看所有标签
$ git tag -l # 用特定的搜索模式列出符合条件的标签
#指搜索1.4.2系统标签
$ git tag -l 'v1.4.2.*'
v1.4.2.1
v1.4.2.2
v1.4.2.3
v1.4.2.4

$ git tag 标签名 #打标签(轻量标签,指向提交对象的引用)
$ git tag 标签名 commitId #给指定commit打标签

$ git tag -a 标签名 -m 注释 commitId #创建带有说明的标签(附注标签,仓库中的一个独立对象,一般建议打附注标签)

$ git tag -n<num> #显示<num>行标签显示注释
$ git tag -n2
v1.4.2.1 注释内容
v1.4.2.2 注释内容
v1.4.2.3
v1.4.2.4

$ git tag -d 标签名 #删除标签

还可以通过-s用私钥签名一个标签,采用PGP签名必须先安装gpg

$ git tag -s 标签名 -m 注释 commitId

推送远程标签

$ git push origin 标签名 #推送指定标签到远程仓库
$ git push origin --tags #推送全部尚未推送到远程仓库的标签

删除远程标签

$ git tag -d 标签名 #先删除本地标签
$ git push origin :refs/tags/标签名 #然后删除远程标签

检出标签

#在 Git 中你并不能真的检出一个标签,因为它们并不能像分支一样来回移动。
#如果你想要工作目录与仓库中特定的标签版本完全一样,可以使在特定的标签上创建一个新分支:

语法:git checkout -b [branchname] [tagname]

子模块

当项目中使用第三方或独立开发的代码库时,可以通过Git子模块来解决。子模块允许你将一个Git仓库作了另一个Git仓库的子目录并保持提交的独立。

# 添加指定远程仓为子模块
git submodule add <仓库URL> [可选<子模块目录名>]

添加子模块后,会在项目根目录出现一个.gitmodules文件,内容如下:

$ cat .gitmodules
[submodule "XXX"]
	path = DbConnector
	url = https://github.com/XXX/XXX

虽然子模块是仓库中的子目录,但当不在子模块目录中时,Git不会跟踪它的内容,而是将它看成作仓库中的一个特殊提交。

# 设设使用diff、status等命时显示子模块的摘要内容
git config status.submodulesummary 1

# 查看子模块的差异输出
git diff --submoudle

克隆含有子模块的库时,默认会包含子模块目录,但目录中是空的。可以通过两种方式加载子模块:

# 方法一:--recursive 选项
git clone --recursive <仓库URL> 


# 方法二:
git submodule init # 初始化本地配置文件
git submodule update # 克隆子模块

当项目中仅仅是使用子模块,而不修改子模块的前提下,可以通过两种方式拉取最新子模块:

方法一:
    git submodule update --remote [可选<子模块名字>]
    
方法二:
    进入子模块目录通过git fetch 和 git merge命令

子模块更新默认拉取master分支,可以通过配置.gitmodules.git/config文件修改拉取的分支:

git config [可选<-f .gitmodules>] submodule.<子模块名>.branch <分支名>

# 如果不加 -f .gitmodules 选项,那么配置不会写入.gitmodules中,那么它只会为你做修改
,其他人不会接收到相应的改变。

从子模块仓库中抓取修改时,Git将会获得这些改动并更新子目录中的文件,但是会将子仓库留在一个称作 “游离的 HEAD” 的状态。 这意味着没有本地工作分支(例如 “master”)跟踪改动, 所以你做的任何改动都不会被跟踪。为了将子模块设置得更容易进入并修改,需要进入每个子模块并检出其相应的工作分支,然后通过--merge选项将改动合并到分支。

git submodule update --remote --merge

# 如果忘记加 --merge 选项,Git 会将子模块更新为服务器上的状态。并且会将项目重置为一个游离的 HEAD 状态。此时只需重新检出工作分支,并手动合并改动即可。

在主项目中提交并推送,而不推送子模块上的改动,其他人检出时因为无法获取相应依赖而出现问题。所以在推送主项目时增加--recurse-submodules=checked参数来检查,如果子模块改动没有推送则主项目push将失败。

$ git push --recurse-submodules=check
The following submodule paths contain changes that can
not be found on any remote:
        XXX
    
Please try
	git push --recurse-submodules=on-demand
or cd to the path and use
	git push
to push them to a remote.

# 根据提示信息可以通过 --recurse-submodules=on-demand 选项,或进入子模块目录进行 push 两种方法进行推送子模块。

在主项目中可以使用foreach子模块遍历命来批量处理子模块,如批量切换分支:

$ git submodule foreach 'git stash'
$ git submodule foreach 'git checkout -b 分支名'

如果项目内有一些文件在子目录中,想要将其转换为一个子模块。可以如下做:

$ git rm -r <目录名>
$ git submodule add <模块仓库URL>

# 注意,切记不要使用 rm 删除目录,否则添加子模块时会报错。
# 添加子模块提示已存在时,但工作空间内不存在此目录时,可能是之前子模块删除不彻底。可以检查`.git/modules`目录是否存在模块,将其删除。

此外,当创建一个新分支,在其中添加一个子模块,之后切换到没有该子模块的分支上时,仍然会有一个还未跟踪的子模块目录。可以选则删除那个目录,但当切换那个回含有子模块的分支后,需要重新运行submodule update --init来建立和填充。

GIT工具

日志

$ git log --stat #显示在每个提交(commit)中哪些文件被修改了
$ git log -p #显示每次提交的内容差异
$ git log -n #显示最近几条日志
$ git log --pretty=oneline|format
$ git log --graph #展示分支、合并历史
$ git log --author #仅显示指定作者相关的提交。
$ git log --committer #仅显示指定提交者相关的提交。
$ git log --grep #仅显示含指定关键字的提交
$ git log 文件名 #仅显示指定文件提交历史

日志搜索

git log
	-S 字符串 #显示新增和删除该字符串的提交
	-G 相对于-S更精准,使用正则表达式搜索
	-L 展示代码中一行或者一个函数的历史
	
#找到 ZLIB_BUF_MAX 常量是什么时候引入的
$ git log -SZLIB_BUF_MAX --oneline
e01503b zlib: allow feeding more than 4GB in one go
ef49a7a zlib: zlib can only process 4GB at a time	

#查看 zlib.c 文件中`git_deflate_bound` 函数的每一次变更
$ git log -L :git_deflate_bound:zlib.c

提交区间

1. 双点语法(..)
$ git log master..dev #比对dev还没提交到master分支的记录
$ git log origin/master..HEAD #输出在你当前分支中而不在远程 origin 中的提交(如果留空了其中的一边, Git 会默认为 HEAD)

2. 多点语法(^ 或 --not)
$ git log refA refB ^refC #查看所有被 refA 或 refB 包含的但是不被 refC 包含的提交
$ git log refA refB --not refC

3. 三点语法(...)
语法可以选择出被两个引用中的一个包含但又不被两者同时包含的提交
$ git log master...experiment #看 master 或者 experiment 中包含的但不是两者共有的提交
$ git log --left-right master...experiment(--left-right显示是哪边分支的提交 ">""<")

git show

显示标签、commitId等对象的信息

git show 标签名 #查看标签信息
git show (--stat) commitId #查看指定提交的详细信息(--stat只查看变动文件)

比较 git diff

git diff <filename>#比较工作区与暂存区的差异  
git diff --cached (<commitId>) <filename> #比较暂存区与上次(/指定commitId)提交的差异
git diff HEAD/commitId <filename> #比较工作区与(上次/指定commitId)提交的差异
git diff commitId commitId #比较Git仓库任意两次 commit 之间的差别
git diff --stat #比较统计(如几处删除,几处增加等等)
git diff 本地分支 origin/远程分支 #与远程库比对(比对前需先执行git fetch)

交互式暂存

修改一组文件后,希望这些改动能放到若干提交而不是混杂在一起成为一个提交.

$ git add -i #进入交互终端
$ git add -p(--patch) #Git暂存文件的特定部分(文件中做了两处修改,但只想要暂存其中的一个)

清理 git clean

需要谨慎地使用这个命令,因为它被设计为从工作目录中移除没有忽略的未跟踪文件(任何与 .gitiignore 或其他忽略文件中的模式匹配的文件都不会被移除),可能无法再找回.

git clean 
	-f 表示强制清理
	-d 后面接要清理的目录
	-n 演习删除,显示将要删除的内容
	-x 完全干净删除

搜索 git grep

从提交历史或者工作目录中查找一个字符串或者正则表达式.

git grep
	-n 输出内容所在文件的行号
 	--count 输出内容所在文件的数量

忽略文件

一般情况,可以使用.gitignore文件添加忽略文件.如果当前文件是已经commit ,push到远程仓库后了,.gitignore里面再配置是不起作用了.此时解决办法有两种:

一种方法是移除文件跟踪,然后将文件添加到.gitignore文件中去(此种方法对其他人影响较大).
另一种方法是仅在自己本地忽略:

$ git update-index --assume-unchanged 文件名 #忽略文件
$ git update-index --no-assume-unchanged 文件名 #取消忽略文件
$ git ls-files -v | grep ^h|^S\<space> #显示本地(--assume-unchanged|--skip-worktree)忽略文件列表(<space> 是表示空格)

# 通过awk实现批量文件操作
$ git status | grep 'modified' | awk '{print $2}' |xargs git update-index --assume-unchanged #指添加忽略文件
$ git ls-files -v | grep '^h' | awk '{print $2}' |xargs git update-index --no-assume-unchanged #批量取消忽略文件

更多信息使用 --help 自行查阅

–assume-unchanged与–skip-worktree 的区别
前者 忽略更改文件,当索引中文件条目变化时则失效(即,此文件变化自上游)
后者 忽略更改文件,索引中文件条目变化仍起作用(直至此索引被放弃)

命令自动补全

  • 第一步 下载git官方提供的自动补全git-completion.bash脚本到自己的家目录并重命名为.git-completion.bash
curl https://raw.githubusercontent.com/git/git/master/contrib/completion/git-completion.bash -o ~/.git-completion.bash
  • 第二步 将下载的脚本添加到~/.bash_profile文件
if [ -f ~/.git-completion.bash ]; then 
. ~/.git-completion.bash 
fi 
# . 符号可以换成source
  • 第三步 编译~/.bash_profile使其立即生效
source ~/.bash_profile

常见问题

中文文件名编码问题

在默认设置下,中文文件名在工作区状态输出,中文名不能正确显示,而是显示为八进制的字符编码。

#解决方法:
git config --global core.quotepath false

HTTP/HTTPS方式免密操作

通常ssh方式可以通过ssh密钥方法免密,HTTP方式需要通过GIT自带凭证系统来实现

# 配置存储模式
git config --global credential.helper 模式
  • cache 模式会将凭证存放在内存中一段时间。 密码永远不会被存储在磁盘中,并且在15分钟后从内存中清除。

  • store 模式会将凭证用明文的形式存放在磁盘中,并且永不过期。存放位置为home目录下的.git-credentials文件中。

  • osxkeychain 模式会将凭证缓存到你系统用户的钥匙串中。 这种方式将凭证存放在磁盘中,并且永不过期,但是是被加密的,这种加密方式与存放 HTTPS 凭证以及 Safari 的自动填写是相同的,此种模式只有mac系统中才能使用。

  • winstore 辅助工具 和上面的 osxkeychain 十分类似,但是是使用 Windows Credential Store 来控制敏感信息,需要在Windows系统中下载安装。

换行符问题

由于不同系统的换行符不相同,所以导致git比较文件时混乱,可以通过设置core.autocrlfcore.safecrlf来解决此问题。

core.autocrlf选项:

# 提交时转换为LF,检出时转换为CRLF
git config --global core.autocrlf true   

# 提交时转换为LF,检出时不转换
git config --global core.autocrlf input   

# 提交检出均不转换
git config --global core.autocrlf false

core.safecrlf选项:

# 拒绝提交包含混合换行符的文件
git config --global core.safecrlf true   

# 允许提交包含混合换行符的文件
git config --global core.safecrlf false   

# 提交包含混合换行符的文件时给出警告
git config --global core.safecrlf warn

文件名大小写问题

Git 默认对文件名大小写不敏感,导致更改文件名为小写或大写后,使用git status无法发现修改,可以使用core.ignorecase配置是否忽略大小写敏感。

git config --global core.ignorecase false