Post

Git常用命令总结

1.配置用户信息

git config --global user.name "<name>" 设置默认名字

git config --global user.email "<email>" 设置默认Email

2.基本命令

2.1 常用命令

查看帮助

1
2
3
git help <cmd>
git <cmd> --help
man git-<cmd>

git <cmd> -h 查看简短帮助

git init 在当前目录创建空仓库

git add <file> 将文件添加到暂存区

git commit [-a] -m "<message>" 将暂存区中的文件提交到仓库

  • -a:自动将所有已跟踪的文件添加到暂存区
  • 注意:若git add后又对文件做了修改,则提交前需再次git add,否则只会提交上一次git add的内容
  • 提交信息可以是单行,也可以是多行:第一行称为“标题”,之后是一个空行,空行之后的部分称为“主体”(见git commit --help的DISCUSSION一节)。要指定多行提交信息,省略-m选项,Git将自动打开一个文本编辑器(默认是Vim),在其中输入提交信息即可

git commit --amend [-m "<message>"] 替换上一次提交,如果不指定提交信息则使用原提交信息

  • 如果文件有变化则相当于将修改合并到上一次提交
  • 如果文件没有变化则相当于修改提交信息

git status 查看仓库状态

2.2 比较文件的差别

git diff [<file>] 比较工作目录和暂存区

git diff --cached [<commit>] [<file>] 比较暂存区和指定提交,可用HEAD表示当前分支指向的提交,也可用提交的SHA-1或其前几位表示指定的提交,默认为HEAD;--staged--cached

git diff <commit> [<file>] 比较工作目录和指定提交

git diff <commit1> <commit2>

git diff <commit1>..<commit2>
比较指定的两次提交(从commit1到commit2如何变化)

git diff <commit1>...<commit2> 比较commit1和commit2的公共祖先与commit2

diff

2.2.1 GitHub pull request diff

假设在master分支的提交A上创建了一个主题分支topic,并创建了两次提交B和G,期间两次将master分支合并到topic分支,合并提交为D和H,提交历史如下图所示:

1
2
3
    B--D--G--H--topic
   /  /     /
--A--C--E--F--master

如果想查看真正属于topic分支引入的diff(即提交B和G的累加diff,而不包括提交D和H,就像在GitHub上创建一个从topic分支到master分支的pull request所能看到的diff),可使用上面的三点形式git diff命令:git diff master...topic,该命令将比较master分支和topic分支的公共祖先(提交F)与topic分支最新位置(提交H),等价于git diff $(git merge-base master topic) topic,即git diff F topic,这恰好就是提交B和G的累加diff。

参考:

2.3 版本回退

注意:git checkout和git reset命令在指定和不指定文件名时功能不同!

git reset [<mode>] <commit> 将当前分支移动到指定提交(HEAD仍指向当前分支),暂存区和工作目录中的文件如何修改取决于<mode>

  • --soft:不更新暂存区和工作目录
  • --mixed:更新暂存区,不更新工作目录(默认选项)
  • --hard:更新暂存区和工作目录

这里的<commit>除了SHA-1还可用HEAD^表示上一个版本,HEAD~n表示往上n个版本,完整格式参考git help gitrevisions

git reset [<commit>] -- <file> 将暂存区中的指定文件替换成仓库中指定提交(默认为HEAD)的内容,不影响工作目录(执行该命令后git diff --cached <file>将没有差别),该命令是git add <file>的反向操作,即取消暂存

git checkout -- <file> 将工作目录中的指定文件替换成暂存区的内容(执行该命令后git diff <file>将没有差别),该命令将丢弃本地修改,不可恢复!

git checkout <commit> -- <file> 将工作目录和暂存区中的指定文件都替换成仓库中指定提交的内容(执行该命令后git diff <file>git diff --cached <file>都将没有差别),该命令将丢弃本地修改,不可恢复!

基本命令1

基本命令2

Git 2.23.0引入了一个新的命令git restore,可替代git reset和git checkout进行撤销操作

git restore [-s <commit> | --source=<commit>] [-S | --staged] [-W | --worktree] <file> 恢复工作目录或暂存区中的指定文件

  • --staged--worktree:指定要恢复的位置
    • --staged:只恢复暂存区,等价于git reset <commit> <file>
    • --worktree:只恢复工作目录,等价于git checkout -- <file>
    • --staged --worktree:恢复暂存区和工作目录,等价于git checkout <commit> -- <file>
    • 二者都未指定:只恢复工作目录
  • --source:指定恢复内容的来源,如果未指定:如果有--staged选项则默认为HEAD,否则从暂存区恢复

2.4 查看提交历史

git log [<options>] [<revision-range>] [[--] <path>...] 查看提交历史

通用选项

  • --abbrev-commit:只显示SHA-1的前几个字符
  • --relative-date:显示相对日期
  • --graph:以图形方式展示
  • --pretty=<format>:指定每次提交的展示格式,<format>可以是oneline, short, medium, full, fuller, reference, email, raw, format:"<string>"之一,其中最后一种是自定义格式,可以使用类似于printf的%占位符,详见git log –help的PRETTY FORMATS一节
  • --oneline:等价于--pretty=oneline --abbrev-commit

例如,要获取指定提交的父提交id,可以使用git log --pretty=%P -1 <commit>git log -1 <commit>^

限制提交范围选项

  • -n <number>-<number>:只显示最后n次提交
  • --since=<date>, --after=<date>:只显示指定日期之后的提交,其中<date>可以是类似于”2008-01-15”的绝对日期,也可以是类似于”2 weeks ago”的相对日期
  • --until=<date>, --before=<date>:只显示指定日期之前的提交
  • --author=<pattern>:只显示作者姓名匹配指定模式的提交
  • --grep=<pattern>:只显示提交信息匹配指定模式的提交
  • --all:显示所有分支的提交

path参数

<path>参数表示只显示指定的文件发生变化的提交

--pretty参数常用占位符

占位符描述
%H提交的哈希值
%h提交的简短哈希值
%an作者姓名
%ae作者邮箱
%ad作者提交日期
%ar作者提交相对日期
%s提交信息标题
%b提交信息主体

revision-range参数

<revision-range>参数的作用是列出沿着给定提交的父链接能够到达的所有提交,但排除可以从前面带有^的提交到达的提交。可以理解为集合操作,每个提交表示从该提交可达的提交集合,结果是所有提交对应集合的并集与前面带有^的提交对应集合的差集。

例如,假设有以下提交记录:

1
2
3
4
5
       C (foo)
      /
A - B - D - E (bar)
      \
       F - G (baz)

则命令git log foo bar ^baz将列出从foo或bar可达、但从baz不可达的提交,即C、D、E。解释:用S(x)表示从提交x可达的提交集合,则S(foo bar \^baz) = S(foo) ∪ S(bar) - S(baz) = {A, B, C} ∪ {A, B, D, E} - {A, B, F, G} = {C, D, E}

<commit1>..<commit2>等价于^<commit1> <commit2>,例如git log foo..bar等价于git log ^foo bar,列出D、E。解释:S(foo..bar) = S(^foo bar) = S(bar) - S(foo) = {A, B, D, E} - {A, B, C} = {D, E}

<commit1>...<commit2>的结果是两个提交对应集合的对称差,例如git log foo...baz列出C、F、G。解释:S(foo…baz) = S(foo) △ S(baz) = (S(foo) - S(baz)) ∪ (S(baz) - S(foo)) = {C} ∪ {F, G} = {C, F, G}

2.5 删除文件

git rm <file> 从暂存区和工作目录删除文件

git rm --cached <file> 仅从暂存区删除文件

若要仅从工作目录删除文件而不改变暂存区,直接从本地删除文件即可

2.6 重命名和移动

git mv <src> <dst> 将文件src重命名为dst,相当于执行以下三条命令:

1
2
3
mv <src> <dst>
git rm <src>
git add <dst>

git mv -r <src> <dst> 将文件夹src重命名为dst

若要将文件foo.txt移动到bar目录可以使用git mv foo.txt bar/foo.txt

3.分支管理

git branch [-r | -a] [-v] 列出分支

  • 无参数:仅列出本地分支
  • -r:列出远程分支
  • -a:列出本地和远程分支
  • -v:显示分支指向的提交
  • -vv:显示分支指向的提交及跟踪的远程分支

git branch <branch-name> [<start-point>] 创建新分支,起始点默认为HEAD

git branch -m [<old-branch>] <new-branch> 重命名分支,如果省略<old-branch>则默认为当前分支

git branch (-d | -D) <branch-name> 删除分支,如果未与其他分支合并则不能删除,但使用参数-D可以强制删除(未合并的提交将会丢失)

git checkout <branch-name> 切换到指定分支(改变HEAD指向的分支,不改变分支本身)并更新暂存区和工作目录,如果有未暂存或未提交的修改则git拒绝切换

git checkout -b <branch-name> [<start-point>] 创建并切换到新分支,相当于git branch+git checkout

Git 2.23.0引入了一个新的命令git switch,可替代git checkout进行切换分支操作

git switch <branch-name> 切换到指定分支,等价于git checkout

git switch -c <branch-name> [<start-point>] 创建并切换到新分支,等价于git checkout -b

git switch - 切换到之前所在的分支

合并

git merge [--no-ff] [-m "<message>"] <branch> 将指定分支合并到当前分支上,如果没有提供提交信息则默认为”Merge branch ‘xxx’ into xxx”

  • 如果当前分支指向的提交是被合并分支指向提交的祖先,则只将当前分支移动到指定的提交,不生成新提交(fast-forward合并),但如果有--no-ff选项则生成新提交(使用--no-ff合并后的历史有分支信息,而fast-forward合并看不出曾经做过合并)
  • 否则进行一次三方合并(当前分支、被合并分支以及二者的公共祖先),在当前分支上生成一次新的提交

典型用法:将开发分支合并到主分支,当前分支应当是主分支

1
2
git checkout master
git merge new-feat

合并冲突:合并时如果两个分支都修改了同一文件就会产生冲突

  • git status 查看存在冲突的文件
  • 手工解决冲突
  • git add <file> 将解决冲突后的文件重新添加到暂存区,标记为已解决
  • git merge --continue 继续合并,也可用git commit完成合并
  • git merge --abort 放弃合并

变基

git rebase <to-branch> [<from-branch>] 变基(rebase)操作,将<from-branch>的提交重新应用到<to-branch>上,如果指定了<from-branch>则先执行git checkout <from-branch>,否则默认为当前分支

  • 相当于先将<from-branch>上的提交临时保存起来,之后git reset --hard <to-branch>,最后依次将提交重新“复制粘贴”到<from-branch>
  • rebase与merge的区别是可以保持线性的提交历史

典型用法:将开发分支rebase到主分支上,当前分支应当是开发分支

1
2
git checkout new-feat
git rebase master

rebase操作也可能产生合并冲突,解决方法与merge类似 git rebase --continue 继续rebase git rebase --abort 放弃rebase

4.远程仓库

4.1 生成SSH key

使用SSH协议克隆远程仓库和向远程仓库推送时需要关联SSH key,使用HTTP协议则不需要

参考:Generating a new SSH key and adding it to the ssh-agent

在Git Bash中执行以下命令:ssh-keygen -t rsa -b 4096 -C "your_email@example.com" 提示输入保存文件位置,直接按回车使用默认位置~/.ssh/id_rsa;提示输入密码,可直接按回车跳过 eval $(ssh-agent -s) 运行ssh-agent ssh-add ~/.ssh/id_rsa 将SSH密钥添加到ssh-agent

将SSH key添加到GitHub账户:Adding a new SSH key to your GitHub account

查看关联的SSH key:Reviewing your SSH keys

1
2
3
eval $(ssh-agent -s)
ssh-add ~/.ssh/id_rsa
ssh-add -l -E md5

多个SSH key:同一台机器可以创建多个SSH key(例如需要使用不同的邮箱),在创建SSH key时提示输入文件位置的步骤输入不同的文件名即可(例如~/.ssh/id_rsa_2),此时需要创建配置文件

  • 在.ssh目录下创建一个config文件,内容如下:
    1
    2
    3
    4
    5
    
    Host github.com
      HostName github.com
      User <your name>
      IdentityFile ~/.ssh/id_rsa_2
      PreferredAuthentications publickey
    

    否则会报错git@github.com: Permission denied (publickey)

  • 测试:ssh -T git@github.com

4.2 远程仓库命令

git clone <url> 克隆远程仓库

git remote [-v] 列出已关联的远程仓库

git remote add <name> <url> 添加远程仓库,习惯上名称取origin,url的格式是git@github.com:{username}/{project-name}.git或https://github.com/{username}/{project-name}.git,需要先在GitHub上创建相应的仓库

git push [-u] [<remote> [<branch>]] 将指定分支的提交推送到远程仓库并更新远程分支

  • 如果省略分支名则默认为当前分支,如果省略远程仓库名则默认为origin
  • 如果远程仓库没有对应的分支则使用参数-u使本地分支与远程分支关联
  • 常用形式:git push origin foo 将foo分支的提交推送到远程仓库,更新origin/foo分支

git fetch [<remote> [<branch>]] 获取远程仓库指定分支的提交

  • 如果省略分支名则获取所有分支,如果省略远程仓库名则默认为origin
  • 对于新的远程分支origin/foo,该命令不会自动创建对应的本地分支foo,需要手动创建:git checkout -b foo origin/foo,这将自动建立foo和origin/foo的关联

git pull [--rebase] [<remote> [<branch>]] 获取远程仓库指定分支的提交并与当前分支合并

  • 如果省略分支名则获取所有分支,如果省略远程仓库名则默认为origin
  • 相当于git fetch+git merge,若有冲突则需手动解决冲突
  • 如果指定了--rebase选项则使用rebase合并,即git fetch+git rebase
  • 常用形式:git pull origin foo 获取远程仓库foo分支的提交,并将当前分支合并到origin/foo(当前分支应当是foo,此时应当是fast-forward合并),相当于
    1
    2
    
    git fetch origin foo
    git merge origin/foo
    
  • 无参数形式:git pull 获取远程仓库所有分支,并将当前分支合并到origin/当前分支,如果当前分支没有跟踪远程分支则不合并,相当于
    1
    2
    
    git fetch
    git merge origin/当前分支
    

4.3 远程分支管理

git branch -vv 查看本地分支跟踪的远程分支

git branch [-t | --track] <branch> <remote-branch> 创建新分支,并跟踪指定的远程分支

  • 如果git branch的第二个参数是一个远程分支,则即使不指定-t选项也会自动建立关联
  • 例如:git branch -t foo origin/foogit branch foo origin/foo将创建foo分支,并使其跟踪远程分支origin/foo

git checkout -b <branch> (-t | --track) <remote-branch> 创建新分支,跟踪指定的远程分支,并切换到新分支

  • 类似于git branch,如果git checkout的第二个参数是一个远程分支,则可省略-t或-b选项
  • 例如:以下几条命令等价,都将创建foo分支,使其跟踪远程分支origin/foo并切换到foo分支:
    1
    2
    3
    4
    
    git checkout -b foo -t origin/foo
    git checkout -b foo origin/foo
    git checkout -t origin/foo
    git checkout foo
    
  • 如果git checkout切换分支时分支名不存在,但匹配一个远程分支名,则将自动创建一个本地分支并跟踪该远程分支,因此上面最后一条命令和前两条等价

git branch -u <remote-branch> [<branch>] 建立本地分支和远程分支的关联,<branch>默认为当前分支

git branch -d -r <remote-branch> 删除本地的远程分支,不删除本地分支(只有当分支已在远程仓库中不存在时才有意义)

  • 例如:如果foo分支已在远程仓库origin中不存在,则git branch -d -r origin/foo删除本地的origin/foo分支,不删除foo分支

git push -d <remote> <branch> 删除远程仓库的分支和本地的远程分支,不删除本地分支

  • 例如:git push -d origin foo删除远程仓库的foo分支和本地的origin/foo分支,不删除本地的foo分支

git remote prune <remote> 删除所有远程仓库中已经不存在的远程分支,不删除本地分支

4.4 多人协作工作流程

(1)切换到master分支,拉取远程仓库的修改

1
2
checkout master
git pull
  • 如果本地有未提交的修改可使用git stash
    1
    2
    3
    
    git stash
    git pull
    git stash pop
    

(2)在本地创建一个新分支(假设名为new-feat),用于开发新功能

1
2
3
git checkout -b new-feat
# develop
git commit -m "new feature"
  • 开发同一个功能最好保持单次提交,如果已经在本地提交,但尚未推送到远程仓库,之后又有其他修改,则可使用git commit --amend将修改“合并”到上一次提交

(3)将提交推送到远程仓库

1
git push -u origin new-feat
  • 如果已经推送到远程仓库,之后又有其他修改,要保持单次提交可强制推送到远程new-feat分支
    1
    2
    
    git commit --amend
    git push -f origin new-feat
    
  • 如果向远程仓库推送时master分支上已经有很多新的提交,则之后合并时可能产生合并冲突,可以先在本地rebase到master分支再推送,从而提前解决合并冲突
    1
    2
    3
    
    git fetch origin master
    git rebase master
    git push origin new-feat
    

    (4)在GitHub或GitLab上创建PR、代码评审、合并到master分支

5.标签管理

git tag 列出所有标签

git tag <name> [<commit>] 在指定的提交上创建标签,默认为HEAD

git tag -d <name> 删除标签

git show <tag-name> 查看标签详细信息

6.储藏工作目录

git stash [push [-m <message>]] 临时保存工作目录中所有修改并恢复到HEAD指向的提交

git stash list 列出所有已储藏的工作目录

git stash show [<stash>] 比较<stash>与其所在的提交,<stash>的格式为stash@{n}n,默认为最近一次stash(即stash@{0}),下同

git stash apply [<stash>] 恢复指定的stash

git stash drop [<stash>] 删除指定的stash

git stash pop [<stash>] 相当于git stash apply+git stash drop

参考

This post is licensed under CC BY 4.0 by the author.