Git 简明教程

Git 简介

Git 属于分散型版本管理系统,是为版本管理而设计的软件。 Linux 的创始人 Linus Torvalds 在 2005 年开发了 Git 的原型程序。当时,由于在 Linux 内核开发中使用的既有版本管理系统的开发方许可证发生了变更,为了更换新的版本管理系统,Torvalds 开发了 Git。

什么是版本管理

版本管理就是管理更新的历史记录。它为我们提供了一些在软件开发过程中必不可少的功能,例如记录一款软件添加或更改源代码的过程,回滚到特定阶段,恢复误删除的文件等。在 Git 出现以前,人们普遍采用 Subversion 等集中型版本管理系统,而现在 Git 已成为主流。

集中型与分散型

以 Subversion 为代表的集中型,会将仓库集中存放在服务器之中,所以只存在一个仓库。这就是为什么这种版本管理系统会被称作集中型。 集中型将所有数据集中存放在服务器当中,有便于管理的有点。但是一旦开发者所处的环境不能连接服务器,就无法获取最新的源代码,开发也就几乎无法进行。服务器宕机时也是同样的道理,而且万一服务器故障导致数据消失,恐怕开发者就再也见不到最新的源代码了。

以 Git 为代表的分散型,例如 GitHub 会将仓库 Fork 给每一个用户。Fork 就是将 GitHub 的某个特定仓库复制到自己的账户下。Fork 出的仓库与原仓库是两个不同的仓库,开发者可以随意编辑。分散型拥有多个仓库,相对而言稍显复杂。不过,由于本地的开发环境中就有仓库,所以开发者不必连接远程仓库就可以进行开发。

Git 常用命令流程图

Git 客户端

Git 官方客户端 TortoiseGit SourceTree

初始设置

设置姓名和邮箱地址 首先设置使用 Git 时的姓名和邮箱地址。名字请用英文输入。--global 为全局设置参数。

$ git config --global user.name "<用户名>"
$ git config --global user.email "<用户邮箱>"

这个命令,会在 “~/.gitconfig” 中以如下形式输出设置文件。

[user]
name = <用户名>
email = <用户邮箱>

Git 命令

初始化仓库

$ git init                #初始化仓库
$ git status              #查看仓库状态
$ git status -s           #查看简单版仓库的状态
$ git show                #显示某次提交的内容

添加

$ git add              #向暂存区中添加文件
$ git add .            #添加当前目录下的所有文件
$ git add <filename>   #添加 <filename> 文件
$ git add -i           #交互式添加文件到暂存区
$ git add -p           #交互式的保存和取消保存变化

提交

$ git commit                 #提交仓库的历史记录
$ git commit -m "<comment>"  #以<comment>作为提交操作的介绍信息进行提交
$ git commit --amend         #与上次 commit 合并
$ git commit                 #在编辑器中记述提交信息的格式如下。
                             #第一行:用一行文字简述提交的更改内容
                             #第二行:空行
                             #第三行以后:记述更改的原因和详细内容

git三棵树 (Source:罗杰·杜德勒)

日志

$ git log                  #查看提交日志
$ git log --pretty=short   #只显示提交信息的第一行
$ git log -p <filename>    #显示文件 <filename> 的改动
$ git log -5               #显示 5 条日志
$ git log <分支名>          #查看某分支的日志

查看某一文件的历史改动

上述命令中 git log -p 可实现查看某一文件的历史改动,不过在 terminal 里面看文本修改为免太不爽,可以试试 gitk 命令:

$ gitk <filename>
$ gitk <file_path>

在查看时文件历史时可以在命令中添加 --follow 参数跟踪文件一切变动,使用此命令时,如果查看的特定文件文件曾重命名,也将被跟踪到并输出历史改动

查找包含特定文件的 commit

$ git log <filename>
$ git show <commit_id>
$ git log --follow <文件绝对路径>

比较

HEAD 表示上一次 commit 的版本 HEAD~n 表示前第 n 次 commit 的版本

$ git diff                     #查看更改前后的差别
$ git diff HEAD                #查看工作树和最新 commit 的差别
$ git diff HEAD~1 HEAD	       #比较上一次和这一次代码之间的差异
$ git diff HEAD~3 HEAD	       #比较前第三次和这一次代码之间的差异
$ git diff HEAD^^^^^ HEAD      #比较前第五次和这一次代码之间的差异
$ git checkout -- <filename>   #回滚到修改前的状态

养成这样一个好习惯:在执行 git commit 命令之前先执行 git diff HEAD 命令,查看本次提交与上次提交之间有什么差别,等确认完毕后再进行提交。这里 HEAD 是指向当前分支中最新一次提交的指针。

回退

$ git reset HEAD filename  #从暂存区移除文件
$ git reset --hard HEAD~n  #直接回退到前第 n 个版本。
$ git reset --hard SHA     #回到 SHA 对应的 commit 的版本。

可选参数: –hard 回退版本,代码也回退,忽略所有修改 –soft 回退版本,代码不变,回退所有的 add 操作 –mixed 回退版本,代码不变,保留 add 操作

分支

$ git branch <分支名>                           #以 <branch-name> 为名创建新分支
$ git branch -d <分支名>                        #删除分支
$ git branch –merged & git branch –no-merged   #返回已合并或未合并的分支列表
$ git checkout <分支名>                         #切换分支
$ git merge                                    #与本地当前分支合并。
$ git merge <分支名>                            #合并分支到当前分支

合并分支 (Source:罗杰·杜德勒)

远程仓库操作

$ git clone <远程仓库网址> <本地目录名>                #将远程仓库克隆到本地
$ git clone -b <分支名> <远程仓库网址> <工作目录名>    #克隆远程仓库的特定分支=
$ git clone -o <自定义仓库名> <远程仓库网址>           #克隆远程仓库并自定义其名称
$ git fetch <远程仓库名>                        #从远程库抓取数据
$ git fetch <远程仓库名> <分支名>                #从远程库抓取特定分支的数据
$ git remote                                   #查看当前远程库
$ git remote -v                                #查看远程仓库详细信息
$ git remote <远程仓库名>                       #查看远程仓库
$ git remote show <远程仓库名>                  #查看远程仓库信息
$ git remote add <远程仓库名> <远程仓库网址>     #添加远程仓库
$ git remote rename <远程仓库名> <新远程仓库名>  #重命名远程仓库
$ git remote update <远程仓库名>                #更新分支列表
$ git remote rm <远程仓库名>                    #删除远程仓库
$ git remote add <别名> <远程仓库网址>           #设置远程仓库别名
$ git remote prune origin                      #删除任何不存在于远端仓库的分支
$ git pull                                     #拉取远程库默认分支并合并到当前仓库,相当于 fetch+merge
$ git pull -b <分支名>                          #拉取远程库特定分支并合并到当前仓库
$ git push origin master                       #推送数据到默认的远程仓库的主分支
$ git push origin <分支名>                      #推送数据到默认的远程仓库的特定分支
$ git push origin --tags                       #推送数据到默认的远程仓库的特定分支并添加标签
$ git push <远程仓库名> <分支名>                 #推送数据到远程仓库的特定分支
$ git push -u <远程仓库名> <分支名>              #指定远程仓库名和分支名为默认值
$ git push -f <远程仓库名> <分支名>              #推送数据并强制覆盖到远程仓库的特定分支
$ git push <远程仓库名> <本地分支名>:<远程分支名> #推送数据到远程仓库的特定分支,后者不存在时则新建。

其他

$ git tag                               #列出所有标签
$ git tag -a <标签名> -m "<提交信息>"    #为版本打一个标签
$ git reflog                            #显示本地已完成的操作列表
$ git shortlog -sn                      #显示提交记录的参与者列表
$ git help                              #帮助文档
$ git reset                             #撤销上一次 git add . 操作
$ gitk                                  #打开图形化 git 界面
$ git config --global color.ui auto     #彩色的 git 输出
$ git config format.pretty oneline      #显示历史记录时,每个提交的信息只显示一行

Git 知识结构 (Source:wustrive2008)

gitignore

对于 Git 项目中不想进行跟踪、同步的文件 / 目录,可将其写入 gitignore 文件中。在 Github 上有人整理了各种开发配置下的 gitignore 文件模板,你可以 在这里 进行挑选。

Q&A

添加 SSH 密钥对

操作远程库(clone/push/pull)时产生如下提示 :

  Permission denied (publickey).
  fatal: Could not read from remote repository.
  Please make sure you have the correct access rights and the repository exists.

这是由于没有将 SSH 密钥对添加到本地 ssh-agent 和 Github 中导致的。如想了解 ssh-agent 原理,可阅读 ssh-agent 与 ssh 的区别,可进行如下操作:

1.确保你已经生成了 SSH 密钥对 2.使用 Git Bash,打开 ssh-agent:

$ eval "$(ssh-agent -s)"
Agent pid 59566

3.添加 SSH 密钥对,默认密钥文件名为 id_rsaid_rsa.pub,可根据需要修改密钥文件名:

$ ssh-add ~/.ssh/id_rsa
Identity added: /c/Users/<用户名>/.ssh/id_rsa (/c/Users/<用户名>/.ssh/id_rsa)

4.登录 Github 用户设置界面,进入 SSH 和 GPG 密钥对界面,将本地的 SSH 公钥(*.pub文件)中的内容复制添加到 Github 的 New SSH key 窗口的 key 中,即可添加成功。

ps. 可在根目录中的 .bash_profile 文件(即 ~/.bash_profile)添加命令别名,今后即可使用别名 sshadd 完成上述操作:

$ alias sshadd='eval "$(ssh-agent -s)" && ssh-add ~/.ssh/id_rsa'

或者在该文件中添加如下命令,今后打开 git bash 时即可自动完成上述关于 SSH 密钥的操作:

$ eval "$(ssh-agent -s)" && ssh-add ~/.ssh/github_rsa

Git 的代理设置

在对 Github 某仓库进行 git clone 操作时发现,由于身处强国,下载速度极其不稳定,时快时慢甚至断流导致 clone 失败。所以想到为 Git 设置代理解决问题。 这也算是一个大众问题了,从 这里 看到了答案:

  1. 确认自己使用的代理工具在本地监听的端口 <localport>

  2. 在 git bash 中运行如下命令:

    $ git config --global https.proxy http://127.0.0.1:<localport>
    $ git config --global https.proxy https://127.0.0.1:<localport>
    

    这样就可以使用 http 或 https 的 URL 进行 clone 操作。 如果需要取消代理设置:

    $ git config --global --unset http.proxy
    $ git config --global --unset https.proxy
    

在 Git 官网的 git-config 页面 看到了这样一段话:

可以用 git config 配置 Git。

Git 使用一系列配置文件来保存你自定义的行为。 它首先会查找 /etc/gitconfig文件,该文件含有系统里每位用户及他们所拥有的仓库的配置值。 如果你传递 --system 选项给 git config,它就会读写该文件。

接下来 Git 会查找每个用户的 ~/.gitconfig 文件(或者 ~/.config/git/config 文件)。 你可以传递 --global 选项让 Git 读写该文件。

最后 Git 会查找你正在操作的版本库所对应的 Git 目录下的配置文件(.git/config)。 这个文件中的值只对该版本库有效。

以上三个层次中每层的配置(系统、全局、本地)都会覆盖掉上一层次的配置,所以 .git/config 中的值会覆盖掉 /etc/gitconfig 中所对应的值。

[NOTE] Git 的配置文件是纯文本的,所以你可以直接手动编辑这些配置文件,输入合乎语法的值。 但是运行 git config 命令会更简单些。

所以我们可以用 git config --help 命令打开帮助页面,研究一下 git config 中各种命令的用途,然后按格式添加在根目录中的 .gitconfig (即 ~/.gitconfig)文件中。比如对照上述设置代理的配置可按如下格式添加:

[http]
	proxy = http://127.0.0.1:1080

[https]
	proxy = http://127.0.0.1:1080

关于 .gitconfig 应该还有很多玩儿法,如果你有什么好思路不妨在评论区分享给大家。

Git 多账户

Git 使用邮箱进行身份验证,所以 Git 多账户存在以下使用情景:

  • 同一台电脑可有多个使用相同邮箱的 Git 账号,密钥默认读取 id_rsa。 为实现在不同网站以不同用户名,相同邮箱进行操作,可编辑 ~/.ssh/config
host github
   hostname github.com
   Port 22
host gitlab
   hostname gitlab.zjut.com
   Port 65095    
  • 同一台电脑可有多个使用不同邮箱的 Git 账号,web1 使用 id_rsa 密钥,web2 使用 id_rsa_github 密钥。为实现在不同网站以不同用户名,不同邮箱进行操作,可进行如下操作:

1.编辑 ~/.ssh/config

## web1
Host web1
    HostName gitlab.com
    PreferredAuthentications publickey
    IdentityFile ~/.ssh/id_rsa
    User name1

## web2
Host web2
    HostName github.com
    PreferredAuthentications publickey
    IdentityFile ~/.ssh/id_rsa_github
    User name2

2.取消用户名和邮箱地址的全局设置

$ git config --global --unset user.name
$ git config --global --unset user.email  

3.进入每个项目单独设置自己的用户名和邮箱地址

$ git config  user.name "<用户名>"
$ git config  user.email "<用户邮箱>"  

4.测试是否配置成功

$ ssh-T [email protected]              #使用 HostName 测试
$ ssh-T git@web2                    #使用 Host 测试

更新 Github 中 fork 的项目

在 Github 上 fork 了一些牛人的项目,但自己 fork 的项目是静止不变的,不会随原项目的更新而更新。当原项目更新时,我们可使用如下方法更新自己仓库中相应的项目: 1.克隆自己的项目到本地:

$ git clone <自己的远程仓库网址>

2.进入克隆到本地的项目目录,把 fork 的原项目作为一个远程仓库以 upstream 为别名添加到本地库中:

$ git remote add upstream <原远程仓库网址>

3.拉取另一个远程仓库的相应分支合并到本地库:

$ git pull upstream master

4.将合并后的项目推送到 Github 上自己的项目中:

$ git push origin master

追加改动的 commit

当完成某一次 commit 后又进行了代码改动,但不想再提交一个新的 commit,这时可以使用如下命令追加改动文件到前一个 commit:

$ git add <文件名>                #将修改的文件添加到暂存区
$ git commit --amend -C HEAD     #追加改动到上一次 commit

暂时处理其他项目的修改

当你正在进行项目中某一部分的工作,里面的东西处于一个比较杂乱的状态,而你想转到其他分支上进行一些工作。问题是,你不想提交进行了一半的工作,否则以后你无法回到这个工作点。这时候可以将现场储藏起来,然后处理插队需求或切换到其他分支工作,之后再将现场取出继续工作:

$ git stash           #储藏现场以便之后继续工作
$ git stash list      #查看所有被储藏的现场列表
$ git stash apply     #恢复现场,但是不删除现场备份
$ git stash pop       #恢复现场,同时删除现场备份
$ git stash drop      #删除现场备份

可以从 pop 关键字看出储藏结构是堆栈,因此你可以在这个上面玩儿出其他花样。

使用外部软件进行 diff/merge

作为一个伪程序狗,终究还是不能逃脱使用 GUI 的宿命。由于 Git 自带的 git diff 并不能够满足某些场景下的使用。我们需要设置一些第三方对比软件进行新旧文档的比较 / 合并。这里笔者使用 Beyond Compare 4 进行举例:

git diff 是普通的逐行对比命令,使用外部比较工具需要使用 git difftool 命令,假设 Beyond Compare 4 已安装至 E:\Program Files\Beyond Compare 4 目录,在使用前我们需进行如下配置,方法有二:

  1. 执行 Git 命令进行配置:
$ git config --global diff.tool bc4
$ git config --global difftool.bc4.path "e:/program files/beyond compare 4/bcomp.exe"
$ git config --global merge.tool bc4
$ git config --global mergetool.bc4.path "e:/program files/beyond compare 4/bcomp.exe"
  1. 修改 . gitconfig 文件进行配置,添加如下项:
[diff]
  tool = bc
[difftool]
  prompt = false
[difftool "bc"]
  path = e:/program files/beyond compare 4/bcomp.exe
[merge]
  tool = bc
[mergetool "bc"]
  path = e:/program files/beyond compare 4/bcomp.exe

Tips

列举一些实用的 Alias,可根据自己需要酌情修改,并将其写入 .bash_profile 文件中实现自动加载:

alias g=git
alias ga='git add'
alias gaa='git add --all'
alias gap='git add --patch'
alias gb='git branch'
alias gba='git branch -a'
alias gbr='git branch --remote'
alias gc='git commit -v'
alias 'gc!'='git commit -v --amend'
alias gca='git commit -v -a'
alias 'gca!'='git commit -v -a --amend'
alias gcl='git config --list'
alias gclean='git reset --hard && git clean -dfx'
alias gcm='git checkout master'
alias gcmsg='git commit -m'
alias gco='git checkout'
alias gcount='git shortlog -sn'
alias gcp='git cherry-pick'
alias gcs='git commit -S'
alias gd='git diff'
alias gdc='git diff --cached'
alias gdt='git difftool'
alias gg='git gui citool'
alias gga='git gui citool --amend'
alias ggpnp='git pull origin `git rev-parse --abbrev-ref HEAD` && git push origin `git rev-parse --abbrev-ref HEAD`'
alias ggpull='git pull origin `git rev-parse --abbrev-ref HEAD`'
alias ggpur='git pull --rebase origin `git rev-parse --abbrev-ref HEAD`'
alias ggpush='git push origin `git rev-parse --abbrev-ref HEAD`'
alias gignore='git update-index --assume-unchanged'
alias gignored='git ls-files -v | grep "^[[:lower:]]"'
alias git-svn-dcommit-push='git svn dcommit && git push github master:svntrunk'
alias gk='gitk --all --branches'
alias gl='git pull'
alias glg='git log --stat --max-count=10'
alias glgg='git log --graph --max-count=10'
alias glgga='git log --graph --decorate --all'
alias glo='git log --oneline --decorate --color'
alias globurl='noglob urlglobber '
alias glog='git log --oneline --decorate --color --graph'
alias glp=_git_log_prettily
alias gm='git merge'
alias gmt='git mergetool --no-prompt'
alias gp='git push'
alias gpoat='git push origin --all && git push origin --tags'
alias gr='git remote'
alias grba='git rebase --abort'
alias grbc='git rebase --continue'
alias grbi='git rebase -i'
alias grep='grep  --color=auto --exclude-dir={.bzr,.cvs,.git,.hg,.svn}'
alias grh='git reset HEAD'
alias grhh='git reset HEAD --hard'
alias grmv='git remote rename'
alias grrm='git remote remove'
alias grset='git remote set-url'
alias grt='cd $(git rev-parse --show-toplevel || echo ".")'
alias grup='git remote update'
alias grv='git remote -v'
alias gsd='git svn dcommit'
alias gsps='git show --pretty=short --show-signature'
alias gsr='git svn rebase'
alias gss='git status -s'
alias gst='git status'
alias gsta='git stash'
alias gstd='git stash drop'
alias gstp='git stash pop'
alias gsts='git stash show --text'
alias gts='git tag -s'
alias gunignore='git update-index --no-assume-unchanged'
alias gunwip='git log -n 1 | grep -q -c "\-\-wip\-\-" && git reset HEAD~1'
alias gup='git pull --rebase'
alias gvt='git verify-tag'
alias gwc='git whatchanged -p --abbrev-commit --pretty=medium'
alias gwip='git add -A; git ls-files --deleted -z | xargs -r0 git rm; git commit -m "--wip--"'
alias gad='git diff --name-only --diff-filter=M --relative | xargs -rt git add'
alias log="git log --oneline --graph --decorate --color=always"
alias logg="git log --graph --all --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(bold white)—     %an%C(reset)%C(bold yellow)%d%C(reset)' --abbrev-commit --date=relative"

Git Cheat Sheet


相关文章 廖雪峰Git系列教程 Git 学习之基本操作(一) Git 入门 Git 安装和初始设置 常用的 12 个 Git 基本命令 git 和 svn diff 命令行可视效果 关于 Git 和 Github 你不知道的十件事 Git 常用命令和 Git 团队使用规范指南 Git 的多账号如何处理? Git 常用命令 git版本管理策略及相关技巧(A)

updatedupdated2023-09-272023-09-27