本文记录了我自己使用Git的习惯,并列出了一些优秀的Git教程。

下面这张图实在太漂亮了,我把她放在这个醒目的位置。

gitflow

安装

Getting Started Installing Git

配置

git的配置分为系统(System)、全局(Global)和本地(Local)三个级别,对应存放在系统不同目录的三个config文件。可以通过git config --system/global/local相关命令进行增删查改,也可通过编辑配置文件。通常最常用的是修改Global级别的配置,Linux存放在~/.gitconfig文件中。

常用的配置命令:

git config --global user.email zhjwpku@gmail.com
git config --global user.name "Zhao Junwang"
git config --global core.editor vim
git config --global credential.helper store
git config --global push.default simple
#git config --global core.autocrlf true

# use less pager by default
git config --global --replace-all core.pager "less -+S"

git config --global --color.ui auto

# alias
git config --global alias.dt difftool
git config --global alias.mt "mergetool -y"
git config --global alias.br "branch -vv"
git config --global alias.co checkout
git config --global alias.cob "checkout -b"
git config --global alias.ci "commit -s"
git config --global alias.cp cherry-pick

#  去掉默认的前缀'a b'
git config --global alias.df "diff --no-prefix"

#  按单词diff,而不是行
git config --global alias.dw "diff --no-prefix --color-words"

#  与HEAD^进行diff
git config --global alias.dh "diff --no-prefix HEAD^"
git config --global alias.st status
git config --global alias.pl "pull --ff-only"
git config --global alias.ps push

git config --global alias.lg "log --graph --format='%C(auto)%h%C(reset) %C(dim white)%an%C(reset) %C(green)%ai%C(reset) %C(auto)%d%C(reset)%n   %s'"
git config --global alias.lg10 "log --graph --pretty=format:'%C(yellow)%h%C(auto)%d%Creset %s %C(white)- %an, %ar%Creset' -10"
git config --global alias.lg20 "log --graph --pretty=format:'%C(yellow)%h%C(auto)%d%Creset %s %C(white)- %an, %ar%Creset' -20"
git config --global alias.lg30 "log --graph --pretty=format:'%C(yellow)%h%C(auto)%d%Creset %s %C(white)- %an, %ar%Creset' -30"
git config --global alias.fp "format-patch --stdout --no-prefix"

下载单一文件

很多时候我们只需要下载代码仓库的单一文件,而不想将下载整个repo,进入文件页面,点击文件的Raw视图按钮,然后使用wget下载即可。Windows下在Raw视图界面右击另存为即可。另外,安装Chrome扩展插件Octotree,当鼠标停留到文件位置的时候会显示下载的提示,点击即可下载。

wget -O ~/.git-completion.bash https://raw.githubusercontent.com/git/git/master/contrib/completion/git-completion.bash
wget -O ~/.git-prompt.sh https://raw.githubusercontent.com/git/git/master/contrib/completion/git-prompt.sh

如果想要下载某个文件夹,参照:

Prompt和Completion

Prompt可以修改命令提示符,而Completion用于自动补全。

修改~/.bashrc文件,添加如下内容

# git
# [[ -f ~/.git-completion.bash ]] && . ~/.git-completion.bash
[[ -f ~/.git-prompt.sh ]] && . ~/.git-prompt.sh
export GIT_PS1_SHOWDIRTYSTATE=1
export PS1='\[\e[1;36m\]→\[\e[m\] \[\e[0;32m\]\w\[\e[0;35m\]$(__git_ps1)\[\e[1;32m\] \$\[\e[m\] '

# fix big repo slow problem
export GIT_PS1_SHOWUNTRACKEDFILES=

Git Three States

ThreeStates

Git中的文件可以分为三种状态:Commited,Modified和Staging。Commited为已经安全保存在本地仓库的数据,Modified为有改动但是并没有提交到本地仓库的文件,而Staged是将改动过的文件标记为可以进入下次commit snapshot的状态。

git本地由三个部分组成:工作区(working directory),暂存区(snapshot/stage/index)和本地库(local repo)。.git目录是仓库的灵魂,.git目录里边包括暂存区和本地库,.git目录外的地方是工作区。git中包含四类对象:commit提交对象,tree目录,blob文件,tag标记。

基本的Git工作流为:

  • 修改工作区中的文件
  • 标记修改过的文件为Stage状态,将它们的Snapshot加入到暂存区
  • 提交,将暂存区的文件存储到本地Git仓库

Git Branching

有人称Git的分支模型为它”Killer feature”,因为对于其它VCS,分支意味着一份新的源码拷贝,这是一个昂贵的操作。而Git的分支模型是非常轻量的。

具体参照 https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell

解决冲突

$ git fetch develop  # 远程仓库的分支
$ git checkout bug-feature # 出现冲突的分支
$ git rebase develop
# merge conflict ...
# 把有冲突的文件进行修改
$ git add . #解决完冲突需要把修改过的文件git add到staging区
$ git rebase --continue
# 可能还会出现冲突,继续解决
...
$ git push -f # 将解决完之后的代码强制推送到远程仓库

删除远程分支

$ git checkout develop
$ git pull  # 将merge的bug-feature更新到develop分支
$ git branch -d bug-feature
$ git push --delete origin bug-feature # 或者在网页上删除,由Reviewer或自己删除
# 或用空值替换某个分支来达到删除远程分支的目的
$ git push origin :bug-feature

命令解释

$ git push origin master # 上传master分支
$ git fetch # 从远程仓库下载更新代码到本地仓库,工作区和暂存区不变
$ git pull  # = git fetch + git checkout 下载到本地仓库,并更新到暂存区和工作区
$ git add -i # 进入交互式add
$ git format-patch -n # 格式化生成patch
$ git blame <file> # 显示文件的每行的版本以及最后更改的人员
$ git am [<mbox>|<Maildir>] # Apply a series of patches from a mailbox
$ git apply <patch> # Apply a patch to files and/or to the index

# clone时提供用户名、密码
$ git clone https://username:password@github.com/username/repository.git

# git diff
$ git diff # 比较工作目录和暂存区的差异
$ git diff --cached # 比较暂存区和HEAD的差异
$ git diff --name-only # 只列出不同文件的名字,而不显示具体的差异之处
$ git diff --stat # 显示diff变更的数据,--numstat会显示增加的行数和删除的行数

# git checkout 恢复工作区的内容
$ git checkout test.txt # 从暂存区恢复到工作区,相当于撤销test.txt文件未add的修改
$ git checkout -f # 从本地库恢复到暂存区和工作区,强制修改工作区的内容,一般对于当前修改不满意,放弃修改后重新开始
$ git checkout HEAD test.txt # 从本地库恢复到暂存区和工作区,取HEAD中的test.txt文件
$ git checkout $(git rev-list master -n -1 --first-parent --before=2016-10-30) # 恢复到2016-10-30号前的最后一个版本

# git reset
$ git reset --soft <commit> # 暂存区和工作区中的内容不作任何改变,仅把HEAD指向<commit>。这个模式下可以在不改动当前工作环境的情况下,改变head指针,即切换到任意commit
$ git reset --mixed <commit> # reset的默认模式。暂存区中的内容会发生变化(恢复到工作区),但工作区中的内容不变
$ git reset --hard <commit> # 暂存区和工作区都会发生变化,还原到<commit>

# git clean
$ git clean -dfx # 删除未被 git 跟踪的文件,通常与 git reset 一起使用准备一个干净的编译环境

# git revert
$ git revert <commit> # 撤销到<commit>,且撤销会作为一次提交进行保存
$ git revert -n master~5..master~2 # 丢弃从最近第五个commit(含)到第二个(不含),但是不自动生成commit,仅修改工作区和暂存区

# git tag
$ git tag # list all tag
$ git tag -a 3.10.1 -m "Release 3.10.1" # 打标签,最好在Pull Request合并之后
$ git show <tag> # 显示tag详情
$ git tag -d 3.10.1 # 删除标签
$ git push origin --tags # 上传本地标签
$ git checkout <tag> # 切换到tag,同切换分支一样
$ git tag -l | xargs git tag -d && git fetch -t  # 同步远程tag
$ git fetch -p -t # 同步远程tag,不确定是否可用

# git stash
# case.1 工作空间与upstream冲突,导致pull失败
$ git stash
$ git pull
$ git stash pop

# case.2 当你正处于某个任务之中的时候,老板突然让你去修复一个不相干的bug
# 一般你会这样操作
# ... hack hack hack ...
$ git checkout -b my_wip
$ git commit -a -m "WIP"
$ git checkout master
$ edit emergency fix # 紧急修复
$ git commit -a -m "Fix in a hurry"
$ git checkout my_wip
$ git reset --soft HEAD^
# ... continue hacking ...

# 可以使用stash简化操作
# ... hack hack hack ...
$ git stash
$ edit emergency fix
$ git commit -a -m "Fix in a hurry"
$ git stash pop
# ... continue hacking ...

# 可能存在多个Stash的内容,使用栈来管理,pop会从最近一个stash中读取内容并恢复
$ git stash list # 显示Git栈内的所有备份
$ git stash clear # 清空Git栈
$ git stash apply <版本号> # 取出特定的版本号对应的工作空间

# Describe a commit using the most recent tag reachable from it
$ git describe
3.12.2-76-g5c51e1b
# |     |    |
# 1     2    3
# 1->最近的里程碑,2->离上次里程碑提交的此处,3->g+最新的commit ID简写

# rev-parse是git的一个底层命令,功能丰富,很多Git脚本或工具都会用到这条命令
# 显示版本库.git的位置
$ git rev-parse --gitdir
# 显示当前工作区的目录深度
$ git rev-parse --show-cdup
# 显示分支
$ git rev-parse --symbolic --branches
# 显示里程碑
$ git rev-parse --symbolic --tags
# 将一个git对象表示为一个SHA1哈希值
$ git rev-parse HEAD

2016-12-07 更新

git reset往往是个危险的命令,假如使用了git reset --hard,又没有记下reset前的commit id,那么该如何挽救呢?这时reflog应该能够帮你一把。

gitreflog

2016-12-09 更新

一个项目仓库可能会引用其他项目,被引用的项目通常以独立的仓库存在。Ceph项目包含了多个子项目,可以查看项目根目录下的.gitmodules文件来获取子项目的url、子项目在父项目的位置等信息。

命令git submodule status可以参看各个子项目的状态:

gitsubmodule

上图中的-的含义是该子项目未检出到父项目中,+的含义为对子项目进行了新的提交更改。

如果想要克隆子项目到父项目,首先要执行git submodule init将子项目注册到.git/config文件中,然后执行git submodule update进行代码检出。

Example:

$ git submodule update --init --recursive

2017-11-28 更新

从一个远程分支check分支:

# 从远程分支 origin/release-1.0.0 切出一个 fix-bug 分支
$ git checkout -b fix-bug origin/release-1.0.0

git 奇技淫巧

# 显示一个commit更改的文件列表
git show --name-only <commitid>
# 反向显示提交记录
git log --reverse