Git 实战分享

一、基本概念

1. git 的四个区域

Image zoo

  • 工作区(Workspace):开发时放代码的目录
  • 暂存区(Index / Stage):临时存放改动文件,就是一个二进制文件。路径.git/index
  • 版本库(Repository):.git目录,记录所有版本的数据和提交记录。
  • 远端库(Remote):托管代码的地方,如gitlab或github等。
2. 文件的四种状态

Image zoo

  • 未跟踪(Untracked): 文件在工作区中的文件夹,但没有加入到git的管理,通过git add可以将状态变成Staged。
  • 未修改(Unmodify): 文件已经通过git管理,未修改表示当前文件与版本库的文件完全一致。
  • 已修改(Modified): 仅仅是修改, 并没有进行其他的操作。
  • 已暂存(Staged): 文件放到暂存区,执行git commit会将其放到git版本库中。
3. git 的目录结构

Image zoo

  • hooks/: 钩子,存放脚本,执行git命令前后触发相应的脚本
  • info/: 仓库的一些信息
  • logs/: 保存所有更新的引用记录
  • objects/: 保存所有的git对象,对象为SHA1哈希值,前两位为文件夹,后38位为文件名(快速检索)
  • refs/: 主要包括三个文件夹heads、remotes、tags以及一个文件stash,主要记录项目中各个分支指向的commit
  • COMMIT_EDITMSG: 保存最新的commit message,git不使用,仅供用户参考展示
  • config: git仓库的配置文件
  • description: git仓库描述信息,主要给gitlab等托管系统使用
  • index: 暂存区,二进制文件
  • HEAD: 当前分支的引用,并可获取到下一次commit的parent
  • ORIG_HEAD: HEAD指针的前一个状态
  • FETCH_HEAD: 版本链接,指向着目前已经从远程仓库取下来的分支的末端版本
4. git 的对象类型

image2019-1-25 20_51_18

5. 对象类型概念
  • blob:表示一个(版本的)文件,只包含文件的数据,无其他元数据比如名称、路径、格式。
  • tree: 表示一个目录信息,包括此目录下的 blob、子目录(子 tree)、文件名、路径、文件属性。
  • commit: 提交一个更新的所有元数据,如指向的 tree,父 commit,作者、提交者、提交日期、提交日志等。一个 commit 可以有多个父类 commit。
6. 查看对象类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查看index文件内容
git ls-files --stage
# 查看HEAD指向的目录树
git ls-tree -l head
# 查看commit记录
git log
# 查看对象类型
git cat-file -t [id]
# 查看对象内容
git cat-file -p [id]
# 查看分支tree结构
git ls-tree master^{tree}
# 查看所有对象
find ./.git/objects -type f

二、基本命令

以下命令中[]请替换。

1. 新建 git 版本库
1
2
3
4
5
6
7
8
# 当前目录新建git版本库
git init
# 当前目录新建一个目录,初始化为git版本库
git init [project-name]
# 从远端库下载一个项目和代码的提交历史
git clone [url]
# 从远端库下载一个项目和最近一次代码的提交历史,节省拉取时间
git clone --depth=1 [url]

TIP: git clone --depth=1只 clone 默认分支,如想要拉取其他分支

1
2
3
4
5
6
7
8
# 克隆
git clone --depth=1 [url]
# 设置远端分支
git remote set-branches origin [remote-branch-name]
# 拉取远端分支
git fetch --depth=1 origin [remote-branch-name]
# 切换分支
git checkout [remote-branch-name]
2. 查看文件状态
1
2
3
4
# 查看所有文件的状态
git status
# 查看指定文件的状态
git status [filename]
3. 查看提交日志
1
2
3
4
5
6
# 表示查看最近几次的提交, n为数字
git log -n
# 显示最近两次提交的不同点
git log -p -2
# 筛选某个author的提交
git log --author [author-name]

TTP:git log按q键退出

4. 工作区<–>暂存区
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 将工作区当前目录下的所有文件添加到暂存区
git add .
# 将工作区指定文件添加到暂存区
git add [filename1] [filename2] [...]
# 将工作区指定目录(包括子目录和文件)添加到暂存区
git add [dir]

# 删除暂存区的文件, 但不删除工作区的文件(会脱离git管理)
git rm --cached [filename]
# 删除暂存区的文件同时工作区的文件(慎用)
git rm [filename]

# 将暂存区将文件恢复到工作区,如工作区有该文件则覆盖
git checkout -- [filename]
# 从其他分支拉取文件,会同时覆盖工作区和暂存区的文件
git checkout [branch-name] -- [filename]
# 将暂存区所有文件恢复到工作区(慎用)
git checkout .

TIP: git checkout [filename]可以直接将暂存区将文件恢复到工作区,增加--是为了告知 git 这是一个文件名,与分支名做区分。

1
2
3
4
# 切换分支
git checkout [branch-name]
# 将暂存区将文件恢复到工作区
git checkout -- [filename]

TIP:git 无法提交空目录,如果必须提交空目录,可在该目录下添加.gitkeep

1
2
# 批量给空目录添加.gitkeep文件,别忘了最后的分号
find . -type d -empty -and -not -regex ./\.git.* -exec touch {}/.gitkeep \;
5. 工作区/暂存区<–>版本库
1
2
3
4
5
6
7
8
9
10
11
12
13
# 将暂存区文件提交至版本库
git commit -m "[commit message]"
# 将暂存区已跟踪的文件提交至版本库
git commit -am "[commit message]"
文件a.txt处于已跟踪,但未暂存状态。这时,如果使用git commit -m是无法提交最新版本的a.txt的,提交的只是最开始空内容的旧版本a.txt
要提交新版本a.txt,即内容为'a'的a.txt,则需要使用git add a.txt,将新版本的a.txt放到staged暂存区,然后才能使用git commit -m进行提交,而如果使用git commit -am,则可以省略git add a.txt这一步,因为git commit -am可以提交跟踪过的文件,而a.txt一开始已经被跟踪过了

# 追加,如果已经推送到远端库不推荐
git commit --amend
# 移除暂存区的文件(不会脱离git管理)
git reset HEAD -- [filename]
# 去掉上一次提交
git reset HEAD^

TIP:git commit -mgit commit -am的区别

git commit -m用于提交暂存区文件;

git commit -am用于提交已跟踪的文件,一般可理解成git add .git commit -m合并,但仍有区别,如果新建文件(未跟踪过的文件)则无法提交。

TIP:git rm --cached [filename]git reset HEAD -- [filename]两个命令都是删除暂存区,区别

1
2
3
4
# 该删除会脱离git管理,一般处理不想通过git管理的文件,比如临时文件等
git rm --cached [filename]
# 该删除不会脱离git管理,主要处理某次不想提交的代码
git reset HEAD -- [filename]
6. 工作区/版本库<–>远端库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 将远端库代码拉取至版本库,不合并工作区代码,使用`git fetch [remote-name]`,一般而言采用下列命令即可
git fetch origin

# 将远端库代码拉取至版本库,且会合并工作区代码
git pull origin
# 采用rebase方式拉取代码
git pull --rebase
# 将远端库代码拉取至版本库,并合并工作区指定分支代码
git pull [remote-name] [remote-branch-name]:[branch-name]


# 将当前分支的代码推送至远端库
git push origin
# 将本地版本库指定分支的代码推送至远端库
git push origin [branch-name]
# 将本地版本库指定分支的代码推送到指定的远端库
git push -u [remote-name] [branch-name]
# 强制推送(慎用)
git push -f origin [branch-name]

TIP:git fetch/git pull/git pull –rebase 区别

git pull相当于git fetch+git mergegit pull --rebase相当于git fetch+git rebase

虽说git pull相当于git fetch+git merge,但不推荐直接使用。一般推荐使用git pull --rebase或使用

1
2
3
4
# 先拉取代码到版本库,如master
git fetch origin master
# 查看冲突或者是否需要合并,再进行合并
git merge origin/master
7. 工作区<–>储藏区
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 将工作区的所有改动,放到储藏区
git stash
# 查看储藏区内容
git stash list
# 弹出储藏区最近一次保存的内容并删除储藏区该条记录
git stash pop
# 储藏时打标记
git stash save "[message]"
# 应用某条储藏,不删除储藏区记录,下列`n`为数字
git stash apply stash@{n}
# 删除某条储藏记录,下列`n`为数字
git stash drop stash@{n}
# 清空储藏记录
git stash clear
8. git 分支
1
2
3
4
5
6
7
8
9
# 切换分支
git checkout [branch-name]
# 强制切换分支,会丢失当前工作区修改过文件(慎用)
git checkout -f [branch-name]
# 切换到上一个分支
git checkout -

# 撤销工作区中当前目录中的所有更改
git checkout .

TIP:git checkout 可以操作文件和操作分支,一定要注意!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 列出所有本地分支
git branch
git branch -l
# 列出所有远端分支
git branch -r
# 列出所有分支,包括本地和远端
git branch -a
# 新建一个分支,不切换分支
git branch [branch-name]
# 新建一个分支,并切换到该分支
git checkout -b [branch-name]
# 分支重命名
git branch -m [old-branch-name] [new-branch-name]
# 删除分支,不能删除当前所在的分支
git branch -d [branch-name]
# 强制删除分支(慎用)
git branch -D [branch-name]
# 查看本地分支对应的远端分支
git branch -vv
# 删除远端库分支
git push origin --delete [remote-branch-name]

# 新建分支时跟远端特定的分支关联(推荐使用关联创建分支)
git checkout -b [local-branch-name] -t origin/[remote-branch-name]
# 已存在的分支与远端分支关联,本地必须存在该分支
git branch -u origin/[remote-branch-name] [local-branch-name]
9. git 标签
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 列出所有的标签
git tag
# 查看标签附加信息
git tag -n
# 通过*过滤标签,如下就可以过滤出v0.1版本的标签
git tag -l "v0.1.*"
# 显示详细的附注信息
git show [tag-name]

# 创建标签
git tag -a [tag-name] -m [tag-message]
# 删除标签
git tag -d [tag-name]

# 将所有的标签推送至远端库
git push origin --tags
# 将特定的标签推送至远端库
git push origin [tag-name]
# 获取远端库标签
git fetch origin tag [tag-name]
# 切换标签
git checkout [tag-name]
# 删除远端库标签
git push --delete origin [tag-name]
10. 删除、撤销与回滚
1
2
3
4
5
6
7
8
# 查看工作区可删除的未跟踪文件
git clean -n
# 移除当前目录下未被跟踪的文件,但不会删除目录和`.gitignore`中指定的未跟踪的文件
git clean -f [path]
# 移除当前目录下未被跟踪的文件和目录
git clean -df
# 移除当前目录下未被跟踪的文件和目录和`.gitignore`中指定的未跟踪的文件
git clean -xf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 将HEAD和暂存区重置到某个commit,不重置工作区
git reset --mixed [commit-id]
# 将HEAD重置到某个commit,不重置工作区和暂存区
git reset --soft [commit-id]
# 将HEAD重置到某个commit,同时也重置工作区和暂存区
git reset --hard [commit-id]

# 重置前一次commit
git reset HEAD^
# 重置前两次commit
git reset HEAD~2

# 查看reset前的commit提交
git reflog

TIP:HEAD的使用。HEAD为当前游标指向,即最近一次commit;HEAD^为上一次commit, HEAD^^为上一次的上一次的commit,即倒数第三次提交;HEAD~3为倒数第四次提交

1
2
3
4
5
6
# 撤销(回滚)到某个commit
git revert [commit-id]
# 工作区出现冲突时,解决后继续执行
git revert --continue
# 工作区出现冲突时,取消revert操作
git revert --abort
11. 合并分支
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 允许以fast-forward的方式合并
git merge [branch-name]
# 只能以fast-forward的方式合并
git merge --ff-only [branch-name]
# 不使用fast-forward的方式合并
git merge --no-ff [branch-name]
# 将所有的commit压缩成一个进行合并
git merge --squash [branch-name]

# 采用rebase的形式合并
git rebase [branch-name]

# 合并某个特定的commit
git cherry-pick [commmit-id]
12. 分支或文件比较
1
2
3
4
5
6
7
8
# 比较工作区和上一次提交的文件差异
git diff HEAD
# 查看当前分支跟指定的分支的差异
git diff [branch-name]
# 查看两个分支的差异
git diff [branch-name-1] [branch-name-2]
# 查看冲突文件
git diff --name-only --diff-filter=U

三、配置与别名

1. 忽略文件提交

git中有三种方式可以忽略文件

  1. 在git项目中创建.gitignore文件(推荐)

    在项目某个文件夹下创建.gitignore文件,在文件内定义相应的忽略规则,便可管理当前文件夹下的文件是否提交。

  2. 编辑git项目.git/info/exclude文件

    编辑git项目.git/info/exclude文件也可忽略文件,但这种只针对于跟目录

  3. 定义全局.gitignore文件

    通过git config --global core.excludesfile ~/.gitignore可定义全局的忽略文件,项目不共享仅限本机用户。

git匹配示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 忽略当前路径下的bin文件夹,该文件夹下的所有内容都会被忽略,但不忽略bin文件
bin/
# 忽略根目录下的bin文件
/bin
# 忽略项目中所有以.log结尾的文件
*.log
# 忽略项目根目录下以.log结尾的文件,但不忽略子文件下的.log文件
/*.log
# 忽略/foo, a/foo, a/b/foo等
**/foo
# 忽略 debug/io.obj,不忽略 debug/common/io.obj 和 tools/debug/io.obj
debug/*.obj
#表示忽略/src/main/test/目录下的所有文件但不忽略文件夹
src/main/test/*
# 不忽略 bin 目录下的 run.sh 文件
!/bin/run.sh
2. git配置

git可以配置用户信息、配置信息、别名等信息,配置有三个不同的路径

  1. 系统级配置/etc/gitconfig,可以通过git config --system添加
  2. 当前用户级配置~/.gitconfig~/.config/git/config,可以通过git config --global添加
  3. 当前项目配置.git/config,可以通过git config添加
1
2
3
4
5
6
7
8
9
10
11
12
13
# 配置项目级的用户名
git config user.name "your name"
# 配置全局用户名
git config --global user.name "your name"
# 配置全局邮箱
git config --global user.email "your email"

# 查看全部配置信息
git config --list
# 删除某些全部配置,如删除全局用户名
git config --global --unset user.name
# 编辑配置文件
git config --global --edit
3. 别名

通过git config可以设置别名,便于用户使用

1
2
# 给checkout起别名co,可以通过git co进行分支切换
git config --global alias.co checkout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[alias]
co = checkout
br = branch
ci = commit
cm = commit -m
st = status
ss = status --short
cp = cherry-pick
lm = log --no-merges --color --date=format:'%Y-%m-%d %H:%M:%S' --pretty=format:'%Cgreen%h%Creset -%C(bold white) %s %C(yellow)%d %Cred(%cd) %C(bold blue)<%an>%Creset' --abbrev-commit
ll = log --no-merges --color --date=format:'%Y-%m-%d %H:%M:%S' --pretty=format:'%Cgreen%h%Creset -%C(bold white) %s %C(yellow)%d %Cred(%cd) %C(bold blue)<%an>%Creset' --abbrev-commit -10
lg = log --graph --color --date=format:'%Y-%m-%d %H:%M:%S' --pretty=format:'%Cgreen%h%Creset -%C(bold white) %s %C(yellow)%d %Cred(%cd) %C(bold blue)<%an>%Creset' --abbrev-commit
ls = log --stat --color --date=format:'%Y-%m-%d %H:%M:%S' --pretty=format:'%Cgreen%h%Creset -%C(bold white) %s %C(yellow)%d %Cred(%cd) %C(bold blue)<%an>%Creset' --abbrev-commit
reflg = reflog --pretty=reflog
undo = "!f() { git reset --hard $(git rev-parse --abbrev-ref HEAD)@{${1-1}}; }; f"
[pretty]
reflog = %C(green)%h -%Cred%gd %C(bold white) %s %C(yellow)%d %C(blue)%cr%C(reset) %gs (%s)

其中lm表示无合并commit的日志,ll查看近10条记录,lg查看个分支关系图,ls查看日志并比较各commit,undo撤销

TIP:日志的查看可以使用gitk &进行查看当前分支,或通过gitk --all &查看各个分支的情况

读后有收获可以请作者喝杯咖啡