GIT 命令

JeremyJone ... 2022-3-29 大约 17 分钟

# GIT 命令

# 安装

  • # Linux

    • Linux 下

      # ubuntu
      sudo apt-get install git
      
      # centos
      yum install -y git
      
      1
      2
      3
      4
      5
    • 老一点的 Debian 或 Ubuntu Linux,要把命令改为

      sudo apt-get install git-core

  • # Windows

    • 在 Windows 上使用 Git,可以从 Git 官网直接下载安装程序,然后按默认选项安装即可。安装完成后,在开始菜单里找到“Git”->“Git Bash”,蹦出一个类似命令行窗口的东西,就说明 Git 安装成功!
    git bash
  • # Mac OS X

    • 一是安装 homebrew,然后通过 homebrew 安装 Git,具体方法请参考 homebrew 的文档:http://brew.sh/

    • 第二种方法更简单,也是推荐的方法,就是直接从 AppStore 安装 Xcode,Xcode 集成了 Git,不过默认没有安装,你需要运行 Xcode,选择菜单“Xcode”->“Preferences”,在弹出窗口中找到“Downloads”,选择“Command Line Tools”,点“Install”就可以完成安装了。

安装完成后,可以在 shell 或 bash 中查看 git

git --version
1

显示当前安装的 git 版本信息,说明已经安装成功了。

# 配置

  • 提交名字
# 全局
git config --global user.name "yourname"

# 当前仓库
git config user.name "yourname"
1
2
3
4
5
  • 提交邮箱
# 全局
git config --global user.email "yourname@example.com"

# 当前仓库
git config user.email "yourname@example.com"
1
2
3
4
5
  • 创建 SSH

    ssh-keygen -t rsa -C "youremail@example.com"
    
    1
  • 将新生成的 SSH 全部拷贝并添加到远程仓库的相应设置中。

# 多用户行为

在配置全局 --global 用户之后,我们默认与远程仓库的联系都是该用户。如果我们需要通过其他账户进行远程操作,则可以变更远程仓库路径的办法。

  • 只是在域名前面加上用户名和 token 即可:
# 之前的路径
https://github.com/<username>/<repository_name>.git

# 那么现在天上用户名即可
https://<username>:<personal_access_token>@github.com/<username>/<repository_name>.git
1
2
3
4
5
  • 如果不希望 git 保存我们的 token,则可以只填用户名:
https://<username>@github.com/<username>/<repository_name>.git
1

之后会要求输入 密码token

注意

密码 不再是认证方式之一。

提示

token 需要在 https://github.com/settings/tokens 中申请。

填写一个描述(Note),选择有效时间,最后选择域(scopes)即可,通常选择第一个 repo 即可,点击 生成 token

页面会显示一个 token 字串,保存它,它就是每次需要填写的内容,该字串只显示一次,后续看不到它。

# 使用

# 初始化本地仓库

git init
1

# 帮助

git 提供了完整的帮助,可以通过 --help 命令来查看。同时,在任何命令下使用 --help 就会显示当前命令的帮助文档。

git --help  # 会列出所有可用的命令

git add --help  # 会打开关于 add 命令的帮助

git log --help  # 会打开关于 log 命令的帮助
1
2
3
4
5

# Git 的流程

Git 分成 local(本地)staged(暂存区) 以及 Git(仓库),另外还包括 远程仓库(Remote),如 GitHub 等。

它的操作流程如下:

# 提交

# 添加到暂存区

  • 存放指定文件

    git add <filename>
    
    # 支持多个文件
    git add <filename1> <filename2> [...]
    
    # 支持通配符,比如添加所有 js 文件
    git add *.js
    
    1
    2
    3
    4
    5
    6
    7
  • 将所有文件的修改、新建添加到暂存区

    该命令可以将所有内容都添加到暂存区

    git add -A
    # 或者
    git add --all
    
    1
    2
    3

    该命令只能够添加当前目录或者子目录的文件

    git add . # 注意 add 后面的一个点,它表示所有文件
    
    1
  • 将文件的修改、删除添加到暂存区

    git add -u
    
    1

# 取消暂存区文件

该命令会将暂存区的文件回退到工作区,并不会改变文件内容,如果需要重新添加到咱群去,只需要重新执行 git add 命令即可。

git reset HEAD  # 取消全部文件

git reset HEAD <filename>  # 取消指定文件
1
2
3

# 删除文件

如果文件还没有提交,可以通过 .gitignore 文件来忽略它。

但是如果文件已经纳入管理,那么需要删除它:

  • 文件不再纳入版本管理,同时删除本地文件

    git rm filename
    
    1
  • 文件不再纳入版本管理,同时保留本地文件

    git rm --cached filename
    
    1

# 暂存工作现场

将所有未提交的更改保存到本地,并隐藏它们,回退到当前分支的提交状态,通常是 HEAD

git stash

# 不跟参数等同于
git stash push
1
2
3
4

# 查看工作现场

查看已保存的工作现场列表,可以获取到工作现场的索引:

git stash list
1

# 恢复工作现场

  • 恢复最近一次保存的工作现场
git stash apply
1
  • 恢复后删除该工作现场
git stash pop
1

上面两个命令都支持通过索引恢复指定工作现场,具体索引按照实际填写即可:

git stash apply stash@{0}

git stash pop stash@{0}
1
2
3

# 删除工作现场

  • 删除最近的一个工作现场
git stash drop
1
  • 删除所有工作现场
git stash clear
1

# 查看工作区状态

显示当前工作区中所有变化文件的状态

git status
1

# 提交修改内容

git commit -m "desc"
1
# 多行desc
# 这里需要先暂时输入一个单引号,然后写多行信息,写完之后再输入下一个单引号
git commit -m '
1. log1
2. log2
3. log3
'
1
2
3
4
5
6
7

建议

提交内容应该严格按照现代提交规范,使用规范工具,不自行填写内容。

# 重新提交

这将与上次提交内容合并为一次提交。

git commit --amend -m "desc"  # 注意 --amend 必须在 -m 的前面
1

# 撤销修改

撤销对工作区文件的修改,若修改后没有放到暂存区,则与上个版本一致,若修改后放到暂存区,则和暂存区一致。

git checkout -- <filename>
1

# 删除某次提交

git log  # 获取提交信息
git rebase -i <commit-id>  # commit-id 为提交版本的hash code
1
2

注意: 这里有个坑,commit-id 是需要删除的前一个 hash code,用图说明:

rebase

使用命令后,打开一个文件,将需要删除版本前面的 pick 改为 drop,用图说明:

rebase

修改后保存关闭,ZZ 或者 :wq,vim 的命令这里不赘述。

退出后使用git log再次查看,可以看到对应版本已经没有了。

# 合并提交

分支开发时,会有很多提交,但是合并到主分支时,希望只有一个提交,此时需要合并提交。

  • 修改 HEAD 并重新提交
git reset HEAD~<n>  # n 为需要合并提交的数量
git add .
git commit -am "desc"
1
2
3
  • 通过 rebase 界面自定义

此方法更适合需要保留之前的所有/部分提交信息。

git rebase -i <remoteRepo>/<branchname>

# 通常为
git rebase -i origin/master
1
2
3
4

此时会打开一个 vim 界面:

最上面会列出当前本地提交的所有内容(自与远程仓库不一致开始),按时间顺序依次向下列出,所以越下面的内容越新,最下面一行是最新提交的。

根据下面的注释:

pick:  正常选中
reword:选中,并且修改提交信息;
edit:  选中,rebase 时会暂停,允许你修改这个 commit(参考这里)
squash:选中,会将当前 commit 与上一个 commit 合并
fixup: 与 squash 相同,但不会保存当前 commit 的提交信息
exec:  执行其他shell命令
1
2
3
4
5
6

根据需要将指定提交的 pick 修改为 squash(或 s) 或者 fixup(或 f),注意第一行不能修改,可以修改后面的内容。

然后保存文件(ZZ 或者 :wq),就可以进行合并提交了。

保存文件后,会跳转到另一个 vim 界面,显示了提交信息,如果填写的是 squash,则会显示所有提交的信息,这里做了一个合并。如果填写的是 fixup,则不会保留其他提交信息。

填写好信息,保存文件,就可以看到只有一个(如果在基变文件中只保留了一个 pick 的话)提交信息了。

# 日志

# 查看提交日志

git log  # 从近到远显示提交日志
1

通过日志我们可以查询很多需要的数据

git log --pretty=oneline  # 将每个提交记录放在一行显示,其它参数:oneline、short、full、fuller等
1

可以显示每次提交的轨迹,这在多分支下非常有用

git log --graph

# 查看分支合并情况
git log --graph --pretty=oneline --abbrev-commit
1
2
3
4
  • 统计提交的行数
git log --stat | perl -ne 'END { print $c } $c += $1 if /(\d+) insertions/;'

# 指定起始日期开始
git log --stat --since 2020-06-01 |perl -ne 'END { print $c } $c += $1 if /(\d+) insertions/;'
1
2
3
4
  • 查看指定日期的日志
git log --since 2020-06-01

# 统计日志的行数
git log --since 2020-06-01 | wc -l
1
2
3
4
  • 查看提交的次数
git log --oneline | wc -l
1
  • 按用户查看添加、删除的行信息
git log --author="$(git config --get user.name)" --pretty=tformat: --numstat| gawk '{ add += $1 ; subs += $2 ; loc += $1 - $2 } END { printf "added lines: %s removed lines : %s total lines: %s\n",add,subs,loc }'

# 上面的方法可以直接使用,会使用当前账户的用户,如果想查看其他人,可以将--author后面的参数直接换成指定用户名即可。例如:
git log --author="jeremyjone" --pretty=tformat: --numstat| gawk '{ add += $1 ; subs += $2 ; loc += $1 - $2 } END { printf "added lines: %s removed lines : %s total lines: %s\n",add,subs,loc }'
1
2
3
4
  • 查看仓库的提交者名称
git log --pretty='%aN' | sort | uniq -c | sort -k1 -n -r

# 查看前几名,比如 5
git log --pretty='%aN' | sort | uniq -c | sort -k1 -n -r | head -n 5
1
2
3
4

# 回退

# 记录回退的命令

该命令可以查看历史提交以及被回退的记录。但该记录仅在本地,且有时限。

git reflog
1

# 回退到当前最新提交

git reset --hard HEAD
1

# 回退到上次提交

git reset --hard HEAD^
1

# 回退到上 n 次提交

git reset --hard HEAD~<n>  #n 为一个数字
1

# 回退到指定版本

该命令既可以回退到过去版本,也可以回到未来版本。

git reset --hard <commitId>  # commitId 可以使用 reflog 命令查看
1

# 分支

# 查看分支

# 仅查看本地
git branch

# 查看本地和远程。远程名称为 <remoteRepo>/<branchname>
git branch -a
1
2
3
4
5

# 创建分支

git branch <branchname>
1

# 切换分支

git checkout <branchname>
git checkout -b <branchname>  # 创建并切换到该分支
git checkout -f <branchname>  # 强制切换到该分支
1
2
3

# 删除分支

# 删除为合并分支
git branch -D <branchname>

# 删除已合并分支
git branch -d <branchname>
1
2
3
4
5

# 合并分支

将某个分支合并到当前分支。

git merge <branchname>
1

# 忽略合并某些文件

在合并时可以忽略特定文件。

先配置一个 merge.ours.driver

git config --global merge.ours.driver true
1

然后在被合并分支的根目录下添加一个 .gitattributes 文件,并把要忽略的文件填进去,例如:

config.js merge=ours
scripts/index.js merge=ours
1
2

它会在合并过程中保持这些文件不变。

# 查看合并记录

# 查看分支合并情况
git log --graph --pretty=oneline --abbrev-commit

# 查看已合并到当前分支的分支、上游分支
git branch --merged

# 查看尚未合并的分支
git branch --no-merged
1
2
3
4
5
6
7
8

# 将本地分支关联到远程分支

git branch --set-upstream <localBranch> origin/<remoteBranch>
1

# 比较

# 比较工作区与暂存区的差异

git diff
1

# 检查多余的空白字符

git diff --check
1

# 比较工作区与当前分支库的差异

git diff HEAD
git diff HEAD -- path  # 与当前分支库同一目录比较
1
2

# 比较暂存区与版本库的差异

git diff --cached (或 --staged)
1

# 比较不同版本库中不同文件的差异

git diff HEAD:<filename> HEAD:<filename>
1

# 查看每次提交差异

git log -p -2  # 查询每次提交的行差异, 2查询的是提交次数,-p是展开显示每次提交的内容差异
git log -U1 --word-diff  # 查询每次提交的单词差异
git log --stat  # 显示改动的概要信息
1
2
3

# 标签

标签是用于特定版本的标记。

# 查看标签

git tag
1

# 创建标签

# 给当前版本添加标签
git tag <content>

# 给历史版本添加标签
git tag <content> <commitId>
1
2
3
4
5

# 删除标签

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

# 删除远程标签
git push <remoteRepo> -d <content>
1
2
3
4
5

# 发布标签

# 发布指定标签
git push -u <remoteRepo> <content>

# 发布所有未提交的标签
git push <remoteRepo> --tags
1
2
3
4
5

# 拉取标签

从远程仓库更新本地标签内容。

git pull <remoteRepo> --tags
1

# 远程操作

# 查看仓库信息

# 不详细信息
git remote

# 详细信息
git remote -v
1
2
3
4
5

# 添加远程仓库

git remote add <remoteRepo> <url>
1
  • remoteRepo 名称可以随意填写,通常默认填写 origin
  • url 可以在远程仓库网站找到

# 将远程仓库分支添加到本地

该命令仅仅是拉取远程仓库内容并更新到本地,永远不会影响当前的工作区。

git fetch <remoteRepo> <branchname>
1

# 克隆远程仓库到本地

git clone <url>
1

# 拉取所有远程分支到本地

三种方法都可以:

# 1  注意 remoteRepo 需要根据实际需要进行替换
git branch -r | grep -v '\->' | while read remote; do git branch --track "${remote#remoteRepo/}" "$remote"; done

# 2
git fetch --all

# 3 慎用,会覆盖当前工作区
git pull --all
1
2
3
4
5
6
7
8

# 更新分支

该命令相当于是 git fetchgit merge FETCH_HEAD 的合体,拉取远程仓库内容并且合并到当前工作区,该操作可能会出现冲突,需要注意。

  • 更新当前分支
git pull
1
  • 获取远程仓库的 master 分支,并合并到本地的 work 分支
git pull <remoteRepo> master:work
1
  • 将远程仓库分支拉取到本地,分支名称相同
git pull <remoteRepo> <branchname>
1

# 推送分支

  • 向远程仓库推送当前分支
git push
1
  • 将本地仓库提交到远程仓库
git push <remoteRepo> <branchname>

# 向远程仓库推送 master 分支
git push <remoteRepo> master
1
2
3
4
  • 强制推送需要添加参数 -f
git push -f <remoteRepo> <branchname>
1
  • 第一次推送时需要参数 -u
git push -u <remoteRepo> <branchname>
1
  • 推送发布版本时同时推送 tag
git push --follow-tags <remoteRepo> <branchname>
1

# 合并远程分支

git merge fork               # 将fork分支合并到当前分支
git checkout fork a.c b.c    # 将fork分支的a.c b.c文件强制覆盖当前分支的对应文件
git merge origin branchname  # 将本地分支与远程分支合并
git mergetool                # 使用工具比较查看冲突
1
2
3
4

# 将最近两次提交合并为一个提交,并强制提交到远程仓库

git reset --soft HEAD
git commit -m "..."
git push --force
1
2
3

# 删除远程分支

git push <remoteRepo> -d <branchname>
1

# 提交规范

在工程化项目中,随着项目越来越大,开发人员越来越多,为避免风格迥异,都会有各种各样的风格约束。Git 的提交信息也不例外,它在实践过程中,逐渐形成了一种规范化的提交内容规范。

Tim Pope 在他的 博客 中主张采用特定的消息样式。具体的讨论帖子可以参考 这里,各路大神在这里讨论的很详尽,有兴趣可以去看看。

在实践中,越来越多的开发者参与进来,逐渐形成了现在流行的提交规范。在这其中,最知名的应该是 Angular 规范,它具有如下好处:

  • 允许通过脚本直接生成 CHANGELOG.md
  • 允许忽略 git bisect 提交(不像格式化那样重要的提交)
  • 浏览记录时可以提供更清晰的历史信息

具体示例,可以参考 Angular 提交实例

# 提交格式

每次提交,都应当包含三部分内容 HeaderBodyFooter

&lt;type>(&lt;scope>): &lt;subject>
// 空一行
&lt;Body>
// 空一行
&lt;Footer>
1
2
3
4
5

其中,Header 是必须的,它由 typesubject 构成,而 BodyFooter 是可选的。

提交消息的任何一行都不能超过 100 个字符(或 72 个字符)! 这使得消息在 github 以及各种 git 工具中更容易阅读。

消息头只有一行,其中包含了对包含类型、可选范围和主题的更改的简洁描述。

# type(类型)

type 用于说明提交的类型,只允许使用如下标识:

  • feat:添加一个新功能
  • fix:修复一个 bug
  • docs:文档变更
  • style:不影响功能的代码格式修正(如空格、分号等格式的修改)
  • refactor:不包含修复 bug、功能新增的代码重构
  • test:添加、修改测试用例
  • chore:对构建过程或辅助工具和库的更改(不影响源文件、测试用例等)

随着技术的更迭,现在也可以有如下标识(commitlint):

  • build:对构建流程、外部依赖等的变更(如升级 npm 包、修改 webpack 配置等)
  • ci:修改 CI 配置、脚本
  • perf:性能优化
  • revert:回滚 commit

其中,featfix 类型的内容会自动生成在 CHANGELOG.md 中,其它内容可以自由配置,但建议使用默认即可。

# scope(范围)

scope 用于说明提交影响的范围,比如数据层、控制层等,视项目而定,也可以不填。

# subject(主题)

subject 是提交的简短描述。最好不超过 50 个字。最好使用英文,并且:

  • 以动词开头,使用祈使句、现在时,如 change,而不是 changed 或者 changes
  • 第一个词小写即可
  • 最后不要加句号(.

# Body

消息体是针对本次提交的详细描述,可以分成多行,同时也有两点需要注意:

  • 与在 subject 中一样,内容使用祈使句、现在时,如 change,而不是 changed 或者 changes
  • 具体说明改变的动机,以及与以前行为的对比

Footer 只包含以下两种情况:

  • 不兼容变更
  • 关闭 ISSUE

# BREAK CHANGE(不兼容变更)

所有的 BREAK CHANGE 都必须作为中断更改块出现在 Footer 中,应该以 BREAKING CHANGE: 开始,用一个空格或者两个换行符。提交消息的其余部分是对更改、理由和迁移说明的描述。如:

BREAKING CHANGE: isolate scope bindings definition has changed and
    the inject option for the directive controller injection was removed.

    To migrate the code follow the example below:

    Before:

    scope: {
      myAttr: 'attribute',
      myBind: 'bind',
      myExpression: 'expression',
      myEval: 'evaluate',
      myAccessor: 'accessor'
    }

    After:

    scope: {
      myAttr: '@',
      myBind: '@',
      myExpression: '&amp;',
      // myEval - usually not useful, but in cases where the expression is assignable, you can use '='
      myAccessor: '=' // in directive's template change myAccessor() to myAccessor
    }

    The removed `inject` wasn't generaly useful for directives so there should be no code using it.
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

# 关闭 ISSUE

如果当前提交时针对某个 ISSUE,则可以在 Footer 中关闭该 ISSUE,可以同时关闭多个。如:

Closes #1
// or
Closes #1, #2
1
2
3

# 操作 git 中的文件

# 查看 git 占用空间

du -sh
1

# 查找 git 中的文件

git verify-pack -v .git/objects/pack/pack-*.idx | sort -k 3 -g | tail -5  // 找出git中占空间最大的前5个文件的id

git rev-list --objects --all | grep .  // 查看文件列表,可以和grep一起使用,grep后跟需要查找的文件名或id
1
2
3

# 删除匹配的*.rar 文件

git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch *.rar' --prune-empty --tag-name-filter cat -- --all
1

# 回收空间

rm -r .git/refs/original
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now
1
2
3
4

# 常见操作

一些具体的问题解决方案

# 解决上游(fork 源)冲突

  • 从当前自己的项目中签出一个新的分支用于测试修改
git checkout -b <newBranch> [<oldBranch>]
git pull <forkSource> <oldBranch>
1
2
  • 尝试合修改冲突并更新
git checkout <oldBranch>   # 如果有修改,无法切换,尝试先 git commit
git merge --no-ff <newBranch>
git push <remoteRepo> <oldBranch>
1
2
3