Git 基本应用

本文用来整理记录日常工作中经常使用到的 Git 命令,方便日常查询使用。关于 Git 诞生的历史及相关内部原理本文暂不涉及。

基础概念

首先通过下图(图片来源于网络)了解日常工作中 Git 的操作流程

git_process

基于上图介绍下 Git 的相关工作空间

术语 解释
本地仓库/版本库(Repository) 一个仓库包括了所有的版本信息、所有的分支和Tag信息。在Git中仓库的每份拷贝都是完整的。仓库让你可以从中取得你的工作副本。工作区目录下有一个.git的目录,这个目录就是Git来跟踪管理版本库的。
工作区(workspace) 本地项目存放源文件的文件夹就是工作区
暂存区(Index/Stage) 用于临时跟踪存储相关修改
远程仓库(Remote) 一般指托管在远程Git服务器上的仓库,用于托管文件供多人协作使用。如GitHub、Gitee、自建的Git服务等

常用操作

基础配置

  • 配置提交人标识和邮箱

    $ git config --global user.name 'your_name'
    $ git config --global user.email 'your_email@domain.com'
    # 取消配置
    git config --unset --global user.name
    git config --unset --global user.email
    

    config 的三个作用域

    $ git config --local #local 只对某个仓库有效
    $ git config --global #global 对当前用户所有仓库有效
    $ git config --system # system 对系统所有登录的用户有效
    # local的优先级最高,即local的配置信息可以覆盖global配置的信息
    

    显示 config 的配置

    $ git config --list --local
    $ git config --list --global
    $ git config --list --system
    

    删除 config 中的配置项

    git config --global --remove-section name # name为要删除的配置项名称
    

    执行git config后相关的配置将保存到纯文本文件中,对应生成文件位置如下:

    1. local 在.git/config里面;
    2. global 在个人 home 目录下的 .gitconfig里面;
    3. system 在 git 安装目录的下。

    所以 git config 命令其实只是一个提供便捷命令行的接口。

  • 其他常用配置

    $ git config --global alias.lg "log -10 --pretty=oneline --graph" # 别名设置
    $ git config --system core.editor <editor>
    $ git config --global --edit
    

仓库初始化

  • 本地有个项目代码写了一段时间了,但还没有用 git 管理起来,现在想用 git 做版本管理

    $ cd existing_floder
    $ git init
    $ git add .
    $ git commit -m"Initial commit"
    
  • 本地生成了一个 git 的仓库,开发了一段时间,想把这个git仓库提交到公司git服务器新建的项目中

    $ cd existiong_repo
    $ git remote add origin git@your_git_server:your_group/your_project.git
    $ git push -u origin master
    #如想推送所有分支和tag则按如下执行
    $ git push -u origin --all
    $ git push -u origin --tags
    # 官方解释:push 后增加 -u 的目的是对于每个最新的或成功 push 的分支,添加 upstream(跟踪)引用。该引用由后续供无参数git-pull 和其他命令使用。
    # 白话解释:也就是加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
    
  • 仓库已经在公司git服务器上创建,检出仓库到本地

    $ git clone git@your_git_server:your_group/your_project.git
    #克隆指定分支
    $ git clone -b master git@your_git_server:your_group/your_project.git
    

    Git支持多种协议,默认的git://使用ssh,但也可以使用https等其他协议。

    使用https除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用ssh协议而只能用https

    同时作为服务端存储的 Git 仓库都为裸仓库,裸仓库没有工作区,因为服务器上的 Git 仓库纯粹是为了共享,所以不让用户直接登录到服务器上去改工作区,并且服务器上的Git仓库通常都以.git结尾

    #在Github、GitLab等Git托管服务上创建的仓库均为裸仓库。如想手动创建可参考如下命令
    #以下命令都可以生成裸仓库
    $ git clone --bare ...
    $ git init --bare 
    

本地仓库变更

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

git_local_change
  • git status

    查看工作目录和暂存区的状态

  • git add

    git add . 将工作区新增、修改和删除(Git 2.x 才支持)的文件添加到暂存区,即对 Git 已跟踪和未跟踪的文件都进行处理。只对当前路径及其子路径文件有效

    git add -A 等同于 git add --all 将文件的修改、新增、删除添加到暂存区。对整个仓库下的文件都有效

    git add -u 将工作区内被修改和删除的文件添加到暂存区(不包括未被 Git 跟踪的新增文件)。好处是可以避免把工作区内还没准备好的文件加到暂存区

    git add file1 file2 ... 一次可添加一个或多个文件及文件夹到暂存区

    git add -p file 交互式的选择工作区及暂存区文件的相关补丁,并新增到暂存区

  • git commit

    git commit -m'xxx'提交暂存区的变更内容到版本历史,生成 commit 节点

    git commit -am'xxx' 会将已经被Git跟踪的变更内容,提交到暂存区并提交内容,生成 commit 节点。==不推荐此方式==,此方式将工作的文件直接添加到版本历史中去了,跳过了人工进行暂存的处理。

忽略指定文件

可以配置 Git 忽略指定的文件或者是文件夹。这些配置都放在 .gitignore文件中。这个文件可以存在于不同的文件夹中,可以包含不同的文件匹配模式。

具体配置在此不做具体描述,下面介绍下使用中可能遇见的问题:

  • 某文件已被提交到远程仓库后,想对其忽略

    1. git rm --cached <file>
    2. .gitignore文件中增加配置
    3. 重新提交推送至远程仓库即可
  • 空文件夹无法被追踪

    因为 Git 是针对文件的变更做管理追踪的故需要在文件夹下添加文件 .gitkeep ,这是一个约定俗成的文件名。

    递归创建.gitkeep文件

    find . -type d -empty -not -path"./.git/*" -exec touch {}/.gitkeep \\;
    

文件重命名

  • 常规方式

    mv readme readme.md

    git_mv_old1

    执行 git add readme.mdgit rm readme

    git_mv_old2
  • 简便方式

    git mv readme readme.md ,前提此文件已被 Git 所管理跟踪

    git_mv_new

    直接执行commit即可,无效执行add。

删除文件

  • 常规方式

    $ rm <file>     # 删除工作区文件
    $ git rm <file> # 加入暂存区
    
  • 简便方式

    $ git rm <file> # 删除文件并加入暂存区
    

差异对比

file
  • 比较暂存区与 HEAD 所指向分支所含文件的差异

    $ git diff --cached
    $ git diff --staged
    # 以上命令皆可
    
  • 比较工作区与暂存区所含文件的差异

    $ git diff #默认比较的是工作区与暂存区所有文件的差异
    $ git diff -- file1 file2 #可以指定一个或多个具体文件名,在工作区与暂存区进行比较
    
  • 比较工作区与 HEAD 所指向分支所含文件的差异

    $ git diff HEAD
    $ git diff HEAD -- file1 file2
    # -- 为了让git命令读取命令参数的时候消除歧义用的,双连字符后面的是路径或文件
    
  • 比较 COMMIT 间的文件差异

    $ git diff 5d39588 56f6198
    $ git diff HEAD HEAD~1
    $ git diff HEAD HEAD^1
    # 上述两命令等价 HEAD 指向5d39588 commit,HEAD~1 指向 5d39588 的父 commit 56f6198 
    $ git diff HEAD HEAD^1 -- readme.md #后面的commit为比较基准
    

    通过下图来看下 diff 操作时 ~^ 差异

    G   H   I   J
     \ /     \ /
      D   E   F
       \  |  / \
        \ | /   |
         \|/    |
          B     C
           \   /
            \ /
             A
    
    A =      = A^0
    B = A^   = A^1     = A~1
    C = A^2
    D = A^^  = A^1^1   = A~2
    E = B^2  = A^^2
    F = B^3  = A^^3
    G = A^^^ = A^1^1^1 = A~3
    H = D^2  = B^^2    = A^^^2  = A~2^2
    I = F^   = B^3^    = A^^3^
    J = F^2  = B^3^2   = A^^3^2
    
    $ git log
    *   29392c8 (HEAD -> master, tag: A) A
    |\
    | * a1ef6fd (tag: C) C
    | |
    |  \
    *-. \   8ae20e9 (tag: B) B
    |\ \ \
    | | |/
    | | *   03160db (tag: F) F
    | | |\
    | | | * 9df28cb (tag: J) J
    | | * 2afd329 (tag: I) I
    | * a77cb1f (tag: E) E
    *   cd75703 (tag: D) D
    |\
    | * 3043d25 (tag: H) H
    * 4ab0473 (tag: G) G
    

    可以看出 ~^ 都是指父节点,n个~ 与n个^ 或者^1~1 都是等价的。它们都是以线性的形式来找其父节点。

    区别在于 ~^后跟数字的情形,~n 仍是以线性来找父节点,但^n会按其所有的父节点来计算查找

查看 commit 历史

# 查看当前所在分支记录
$ git log
# 高版本命令行会进入交互界面,如不进入交互界面直接输出日志按以下命令执行
$ git --no-page log #--no-page 禁用page分页,git 很多命令默认都是page分页的
# 日期格式
$ git log --date=format:%Y-%m-%d\ %H:%M:%S
$ git log --date=iso
$ git log --date=short
# 以单行简洁方式展示历史
$ git log --oneline
# 查看最近5条记录
$ git log -n5 --oneline
$ git log -n5 --oneline #"-<number>"同"-n<number>"。
# 查看具体分支记录
$ git log temp
# 查看所有分支log记录
$ git log --all
# 以图形化展示记录
$ git log --all --graph
# 美化记录
$ git log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
# 查看某文件中某人何时修改了何处
$ git blame <file>

重写 commit 历史

# 将本次暂存区的变更与上次commit的变更合并生成新的commit,并替换 message
# 前提当前commit为最新的
$ git commit --amend # 此操作只可修改未被 push 到远程仓库的 commit 消息
# 修改旧commit的message信息、合并commit消息
$ git rebase -i <base> # 变基只用于本地管理未被push到远程仓库中代码,如果push到远程后又使用了rebase,则会影响团队其他成员。

撤销变更

  • checkout

    checkout命令用于从历史提交(或者暂存区域)中拷贝文件到工作目录,也可用于切换分支。

    当给定某个文件名(或者打开-p选项,或者文件名和-p选项同时打开)时,git会从指定的提交中拷贝文件到暂存区域和工作目录。比如,git checkout HEAD~ foo.c会将提交节点HEAD~(即当前提交节点的父节点)中的foo.c复制到工作目录并且加到暂存区域中。(如果命令中没有指定提交节点,则会从暂存区域中拷贝内容。)注意当前分支不会发生变化。

    # 工作区回退到暂存区的版本
    # 指定文件
    $ git checkout -- file
    # 所有内容
    $ git checkout *
    # 指定提交中文件回退到暂存区和工作目录
    $ git checkout HEAD index.html
    
  • reset(复位)

    reset命令把当前分支指向另一个位置,并且有选择的变动工作目录和索引。也用来在从历史仓库中复制文件到索引,而不动工作目录(即 git reset 命令既可以回退版本,也可以把暂存区的修改回退到工作区)。它应该只被用于 本地 修改,切记不应该在和其他开发者共享的分支执行 reset。

    # 移动分支位置,并清空暂存区跟踪文件
    $ git reset <commit> # 默认为--mixed
    # 移动分支位置,并清空工作区和暂存区跟踪文件
    $ git reset --hard <commit> 
    # 只移动分支位置
    $ git reset --soft <commit>
    # 分支位置不变,清空暂存区跟踪文件
    $ git reset # 分支默认为HEAD
    # 分支位置不变,清空暂存区指定的跟踪文件
    $ git reset <file> # 分支默认为HEAD,把暂存区的修改撤销掉(unstage),重新放回工作区
    $ git reset # 撤销所有暂存区域文件。
    
  • revert(撤销)

    在远程分支中可以通过撤销某个提交引入的更改,revert操作方式是在最后加上一个撤销了此更改的新提交,而不是从项目历史中移除这个提交。这避免了Git丢失项目历史,而且对于版本历史和协作的可靠性来说是很重要的。

    git revert 用于在公共的分支上撤销提交,git reset用于复位本地的历史变更
    因为两个命令的目的不同,它们的实现也不一样:reset完全地移除了一堆更改,revert保留了原来的更改,用一个新的提交来实现撤销。

    # 创建一个新的commit,此commit将要revert的commit记录中的变更都撤销
    $ git revert <commit>
    
  • clean

    git clean命令将未被 Git 跟踪的文件从工作目录中移除。与rm作用一样,只是提供了简便方式。
    git clean 命令经常和 git reset --hard 一起使用,新增的文件没有被加入暂存区,它们不会被git reset --hard影响,必须使用git clean删除它们。==此操作请谨慎使用==。

    # 告诉你哪些文件在命令执行后会被移除,而不是真的删除它。
    $ git clean -n
    # 移除当前目录下未被跟踪的文件
    $ git clean -f
    # 移除未跟踪的文件,但限制在某个路径下。
    $ git clean -f <path>
    # 移除未跟踪的文件,以及目录。
    $ git clean -df
    
  • reflog

    git relog 在 HEAD 更新时(如切换分支、拉取新更改、重写历史或只是添加新的提交),引用日志都会添加一个新条目(操作记录包括对应的commitId)。它的作用在于在执行某些撤销变更、rebase等命令后某些commit记录无法在log中查询到,但又需要对其恢复则可使用此功能来找寻其对应的commit记录。

分支与Tag

# 查询本地已有分支
$ git branch
# 查询本地已有分支并输出详细信息
$ git branch -v
# 查询已存在的本地分支及远程分支并输出详细信息
$ git branch -av
# 创建分支
$ git branch <new-branch>  # 默认以HEAD当前最终指向的Commit为基准
$ git branch <new-branch> <commitId/branch> # 已指定的Commit或分支为基准
# 删除分支
$ git branch -d <branch> # 如果执行报错(报此分支还没有被完全merge,也就是还没合并到上游分支或HEAD上,删除会存在代码丢失风险),如已确认删除对当前工程无影响,则可改用 -D 强制删除
# 切换分支
$ git checkout <branch>
$ git switch <branch> # 新版本Git提供,此命令从字面上更容易理解,不易混淆
# 创建并切换分支
$ git checkout -b <new-branch> <commitId/branch> # 如不指定commitId或branch默认以HEAD当前最终指向的Commit为基准
$ git switch -c <new-branch> <commitId/branch> # 新版本Git提供,此命令从字面上更容易理解,不易混淆
# 以远程分支为基准创建分支
$ git checkout --track origin/hack
# 使用 Tag 标识当前commit
$ git tag <tag-name>

merge 与 rebase

如果期望提交历史以线性结构展示则使用rebase,merge操作对合并要求更宽松但展示的历史结构会复杂

$ git merge <branch>
$ git rebase <branch>
# 终止变基操作
$ git rebase --abort
# 继续变基操作
$ git rebase --continue
# 使用配置的merge工具来解决冲突
$ git mergetool
# 解决冲突后追踪已解决的文件
$ git add <resolved-file>
# 解决冲突后删除已解决的文件
$ git rm <resolved-file>

临时紧急任务处理

工作只进行到一半,还没法提交,预计完成还需1天时间。但是,必须在两个小时内修复该bug,怎么办?

幸好Git还提供了一个stash功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:

# stash 是堆栈结构,执行pop后会将最上面也就是最新存储的弹出来
➜  Git_Learning git:(rebase) ✗ git stash
Alias tip: g stash
Saved working directory and index state WIP on rebase: 3fb18e1 add test
➜  Git_Learning git:(rebase) git stash list
Alias tip: gstl
➜  Git_Learning git:(rebase) git --no-pager stash list
Alias tip: g --no-pager stash list
stash@{0}: WIP on rebase: 3fb18e1 add test
➜  Git_Learning git:(rebase)
# git stash apply 只应用堆栈上最新的代码但不将其从stash中删除,即apply可多次执行
#pop即应用代码又删除
➜  Git_Learning git:(rebase) git stash pop  
Alias tip: gstp
On branch rebase
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
    modified:   index.html

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (fdee00b4363f4a3d718ee0aa0825a52bff036d1d)

git stash list 命令显示的最左一列显示的是 stash 的序号,如stash@{2} 和 stash@{1},序号中数字大的代表的是较早的stash。我们pop的时候可以加具体的序号,不加序号的(缺省情况下)为 stash@{0}。用法:git stash pop stash@{2} git stash pop = git stash pop stash@{0}

注意 git stash不会保存工作区新增的文件(未被跟踪的),需执行git stash push -u -m '指定stash名字'

cherry-pick

在master分支上修复了bug后,因为dev分支是从master分支分出来的,所以,这个bug在当前dev分支上也存在。

那怎么在dev分支上修复同样的bug?重复操作一次,提交不就行了?

为了方便操作,Git专门提供了一个cherry-pick命令,让我们能复制一个特定的提交到当前分支:

$ git cherry-pick 3fb18e1
[master b257a0d] fix bug
 1 file changed, 1 insertion(+), 1 deletion(-)

Git自动给dev分支做了一次提交,注意这次提交的commit是b257a0d,它并不同于master的3fb18e1,因为这两个commit只是改动相同,但确实是两个不同的commit。用git cherry-pick,我们就不需要在dev分支上手动再把修bug的过程重复一遍。

远程仓库

# 显示本地与远程仓库的连接
$ git remote
# 显示本地与远程仓库连接详细信息
$ git remote -v
# 创建一个新的远程仓库连接。执行clone操作时,会自顶创建与远程仓库的连接,名称为origin
$ git remote add <name> <url>
# 移除名为 <name> 的远程仓库的连接。
$ git remote rm <name>
# 重命名远程连接
$ git remote rename <old-name> <new-name>
# 将本地分支与远程分支关联
$ git branch --set-upstream <upstream> [<branchname>]
# 将本地分支与远程分支取消关联
$ git branch --unset-upstream [<branchname>]
# 拉取远程仓库指定分支的到本地,但不与本地当前分支合并。如不指定<branch>则拉取远程仓库的所有分支引用
$ git fetch <remote> <branch>
# 拉取当前分支对应的远程副本中的更改,并立即并入本地副本(前提本地分支与远程分支已关联upstream)
$ git pull <remote> 
# 等价于
$ git fetch && git merge
# 底层使用 rebase 合并远程分支和本地分支,而不是使用 merge。--rebase 标记可以用来保证线性的项目历史,防止合并提交(merge commits)的产生。很多项目组倾向于使用 rebase 而不是 merge,因为提交历史更简洁明了
$ git pull --rebase <remote>
# 本地仓库master分支推动到远程仓库的master分支
$ git push origin master:master # :前面的是本地分支名称,:后面的是远端分支的名称。
# 本地仓库master分支推送到远端关联分支
$ git push origin master # 后面没带远端的master,那是因为git已经为本地的master分支和远端的分支建立了所谓的 upstream 的关联,它知道本地master对应远端的master分支
# 将当前本地仓库所在分支提交到远程相同分支下
$ git push # origin 是缺省的 remote url;
# 强制推送,集成分支不推荐使用
$ git push -f
# 将本地所有分支推送到远程仓库
$ git push -all
# 推送分支时,标签不会被自动推送上去。`--tags` 将你所有的本地标签推送到远程仓库中去。
$ git push --tags

Git集成使用禁忌

前面提到过在本地分支执行 git checkout/git reset时要谨慎,下面的命令在多人协作的集成环境下则完全禁止使用

  • 禁止向集成分支执行 git push -f
  • 禁止向集成分支执行变更 commit 历史的操作,如resetrebase

参考

Git官方教程

廖雪峰Git教程

图解Git

文章持续更新,可以微信搜一搜「 Java夜校 」第一时间阅读,若有错误或者不当之处,还请大家留言指正,一起学习交流!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345