Git 仓库数据结构
- Git 仓库由一个个的
commit
组成 - 某些
commit
上会有一些branch
指向它们,这些branch
的本质是引用 - 有一个特殊的引用叫做
HEAD
,它始终指向当前的位置,这个位置可以是commit
,也可以是branch
staging area 暂存区和 add
staging
原意:舞台表演前的筹划准备(例如汇集道具和演员)。Git 中的意思:把改动内容汇集起来以待提交。staging area
:待提交的修改内容暂时存放的地方。主要用于和已经改动但不打算提交的内容区分开来。add
指令:把指定的内容放进暂存区。
- Workspace:工作区
- Stage:暂存区
- Repository:仓库区(本地仓库)
- Remote:远程仓库
bash
git add [file1] [file2] ... # 添加指定文件到暂存区
git add [dir] # 添加指定目录到暂存区,包括子目录
git add . # 添加当前目录的所有文件到暂存区
git add -p # 添加每个变化前,都会要求确认
# 对于同一个文件的多处变化,可以实现分次提交
git rm [file1] [file2] ... # 删除工作区文件,并且将这次删除放入暂存区
git rm --cached [file] # 停止追踪指定文件,但该文件会保留在工作区
git mv [file-original] [file-renamed] # 改名文件,并且将这个改名放入暂存区
git commit
commit
表示对于一次改动的提交,它可以代表当前时刻下 Git 仓库的完整快照,但本质上,commit
只是记录了距离上一次commit
之间的改动。
bash
git commit -m [message] # 提交暂存区到仓库区
git commit [file1] [file2] ... -m [message] # 提交暂存区的指定文件到仓库区
git commit -a # 提交工作区所有更改到仓库区
git commit -v # 提交时显示所有diff信息
git commit -am 'message' # 将add和commit合为一步
# 使用一次新的commit,替代上一次提交
git commit --amend -m [message] # 如果代码没有任何新变化,则用来改写上一次commit的提交信息(用于反复修改)
git commit --amend [file1] [file2] ... # 重做上一次commit,并包括指定文件的新变化
branch 和 master
branch
的含义是分支,指的是仓库结构出现分叉时的不同的「叉」- 本质上,git 的
branch
是引用(reference),即指向某个commit
的指针
直观感觉的 branch
:
而实质上的 branch
:
所以,branch
和你什么时候创建的它无关,也和仓库的起点无关,只和它当前指向哪个 commit
有关。
master
是⼀个特殊的branch
,因为它是 Git 的默认branch
(默认branch
可以修改)。默认branch
的特点:- 执行
clone
方法把仓库取到本地的时候,默认checkout
出来的是默认branch
,即master
; - 执行
push
命令把本地内容推送到远端仓库的时候,远端仓库的HEAD
永远跟随默认branch
,而不是和本地HEAD
同步。换句话说,只有push
master
分支到远端的时候,远端的HEAD
才会移动。
- 执行
bash
git branch # 列出所有本地分支
git branch -r # 列出所有远程分支
git branch -a # 列出所有本地分支和远程分支
git branch -v # 查看各个分支最后一次提交
git branch --merged # 查看哪些分支合并入当前分支
git branch --no-merged # 查看哪些分支未合并入当前分支
git branch [branch-name] # 新建一个分支,但依然停留在当前分支
git checkout -b [branch] # 新建一个分支,并切换到该分支
git branch [branch] [commit] # 新建一个分支,指向指定commit
git checkout - # 切换到上一个分支
git branch -d [branch-name] # 删除分支
git branch -D mybranch # 强制删除分支
git push origin --delete [branch-name] # 删除远程分支
git branch -dr [remote/branch] # 删除远程分支
HEAD
HEAD
也是引用,但它不是branch
,它代表了当前所处的位置。HEAD
不仅可以指向某个commit
,也可以指向某个branch
(例如master
、feature1
)- 当每次
commit
的时候,HEAD
不仅随着新的commit
一起移动,而且如果它指向了某个branch
,那么它也会带着branch
一起移动
git clone
clone
是从远端仓库初次把数据取下来:
bash
git clone https://github.com/xxxx.git
clone
命令具体会做两件事:
- 把整个仓库中的所有
branch
取下来,并把从初始commit
到达这些branch
的路径上的所有commit
都取下来:
- 从初始
commit
开始,向master
指向的commit
,一个个地把每个commit
应用,最终得到一个「当前」状态的仓库内容,写进 Git 所在的目录(这个目录叫做 working tree)
git log
- 从
HEAD
指向的commit
开始,倒序 显示每一个commit
的摘要信息(最新的排在最前面)
bash
git status # 显示有变更的文件
git log # 显示当前分支的版本历史
git log --stat # 显示commit历史,以及每次commit发生变更的文件
git log -S [keyword] # 搜索提交历史,根据关键词
git log [tag] HEAD --pretty=format:%s # 显示某个commit之后的所有变动,每个commit占据一行
git log [tag] HEAD --grep feature # 显示某个commit之后的所有变动,其"提交说明"必须符合搜索条件
git log --follow [file] # 显示某个文件的版本历史,包括文件改名
git whatchanged [file]
git log -p [file] # 显示指定文件相关的每一次diff
git log -5 --pretty --oneline # 显示过去5次提交
git shortlog -sn # 显示所有提交过的用户,按提交次数排序
git merge
merge
就是合并,它会把当前commit
和指定commit
(所谓commit
,可以直接用它的hash
值来指定,例如4a0a1b
,也可以用一个直接或间接指向它的引用来指定,例如master
或者HEAD
)进行合并,并把这个合并行为创建成一个新的commit
。
bash
git merge feature1 # 合并feature1分支至当前分支
git merge origin/master # 合并远程master分支至当前分支
git merge
会产生一个新的提交。
merge
行为所产生的 commit
,是一种特殊的 commit
:
- 它不需要有改动,只要指定两个(或更多个)父
commit
就好 - 正如上面这句说的,它有两个或更多个父
commit
,这是一般的commit
不具有的性质
git checkout master
相当于merge from master
,git merge mybranch
相当于 merge from mybranch
。
merge 冲突
当 Git 不知道怎么合并某两处冲突的修改时,会中断自动合并,并对冲突文件进行标记。解决方法:
- 把文件手动修改好
- 把修改的内容使用
add
来添加进暂存区 - 用
git merge --continue
来继续自动合并流程
关于 origin/ 打头的 branch
- 本地仓库中,有一些以
origin/
打头的branch
,它们是远端仓库(别名为origin
)的本地镜像。它们的作用是方便在本地查看远端仓库的branch
状态。
远端仓库默认名称是
origin
,但也可以给它们起别的名称
origin/
分支并不在本地直接操作,它们一般只在两种情况下会进行自动更新:
- 在执行
push
的时候,push
成功后,push
成功的branch
会把它对应的origin/
branch更新到当前commit
(因为远端的branch
已经随着push
的成功而更新,所以本地镜像也一起更新) - 在执行
pull
或者fetch
的时候,由于从远端拿到了所有最新的branch
状态,所以也会一同更新所有的origin/
branch
关于
origin/HEAD
:这是一个永远跟随origin/master
的引用,它最大的作用是用来标记默认branch
git push
- 把当前的本地分支推送到远端仓库的指定分支。
bash
git push origin feature1
具体做两件事:
- 把
HEAD
所指向的branch
(只是一个引用哦)推送到远端仓库 - 从这个
branch
向前回溯,远端仓库缺少的每一个commit
也推送到远端仓库。 - 将
push
的branch
的本地镜像origin/xxx
更新
注意:
origin/HEAD
并没有在图上画出来,但如果push
是master
(即默认branch
),那么本地的origin/HEAD
也会更新到master
的最新位置;当push
的是其他branch
的时候,origin/HEAD
并不会更新。也就是说,
origin/HEAD
只和默认分支相关,和HEAD
是无关的。
bash
git push [remote] [branch] # 上传本地指定分支到远程仓库
git push [remote] --force # 强行推送当前分支到远程仓库,即使有冲突
git push [remote] --all # 推送所有分支到远程仓库
git push origin master # 将当前分支push到远程master分支
git pull
- 把远端
branch
取到本地。
bash
git pull origin feature1
具体做的事有三件:
- 把远端所有
branch
的最新位置更新到本地的origin/xxx
镜像 - 要到达这些
branch
,本地所缺少的所有commit
,也取到本地 - 把
origin/当前branch
的内容合并到当前branch
事实上,git pull origin feature1
会分成两部执行,它等价于下面两行:
bash
git fetch
git merge origin/feature1
git fetch
的含义:
bash
git fetch [remote] # 下载所有远程分支,但不更新本地分支(另需merge)
git checkout
- 移动
HEAD
,让它指向某个commit
或某个branch
。 checkout --detach
:让HEAD
脱离当前branch
,直接指向下面的commit
。
bash
git checkout -b dev origin/master
从远程的 master
分支检出到本地的dev
上,并切换到新建的本地dev
分支上。
其实也是相当于两个命令的缩写:
bash
git branch dev # 创建分支
git checkout dev # 切换分支
本地的项目中,一直都保留着一个master
分支,并关联远程的master
分支。本地的master
分支,你可以当作是个干净的主线,每次新来一个需求的时候你都可以以该分支为基准,新建一个分支进行开发,最后进行合并,并由这个基准分支(本地master
)提交到远程;具体的专业信息你可以参照文档:Git-分支-分支的新建与合并
通过 checkout
可以恢复指定文件(如不小心commit
掉的)到工作区(未提交状态):
bash
git checkout [file] # 恢复暂存区的指定文件到工作区
git checkout [commit] [file] # 恢复某个commit的指定文件到暂存区和工作区
git checkout . # 恢复暂存区的所有文件到工作区
bash
git checkout -b master_copy # 从当前分支创建新分支master_copy并检出
git checkout -b master master_copy # 上面的完整版
git checkout features/performance # 检出已存在的features/performance分支
git checkout --track hotfixes/BJVEP933 # 检出远程分支hotfixes/BJVEP933并创建本地跟踪分支
git checkout v2.0 # 检出版本v2.0
git checkout -b devel origin/develop # 从远程分支develop创建新本地分支devel并检出
git checkout -- README # 检出head版本的README文件(可用于修改错误回退)
git rebase
- 把当前
commit
(以及它之前的commits
)应用到指定的需要rebase
的commit
上。
Git 中的每一个
commit
都是不会改变的,所以rebase
之后的每个commit
都是新产生的,而不是对原先的commit
进行「修改」
rebase 冲突
rebase
的冲突解决方法和 merge
冲突一样,只是把 git merge --continue
改成 git rebase --continue
就行了。
git reset
- 把当前
branch
指向指定的commit
。
bash
git reset [指定commit] # 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变
移动到指定 commit
,并保留 working tree 的内容。
bash
git reset --hard [指定commit]
移动到指定 commit
,并重置 working tree。
bash
git checkout . # 撤销未暂存修改
git reset # 撤销未提交的暂存至 git add . 状态
git reset --hard # 撤销所有修改,保持与上一次一致
git reset --hard origin/master # 撤销本地提交
git reset --hard HEAD^ & git push --f # 撤销已推送
git reset --keep [commit] # 重置当前HEAD为指定commit,但保持暂存区和工作区不变
reset 和 checkout 的区别
它们都是移动 HEAD
,但 chekcout
移动的时候是自己移动,不带着 branch
一起;而 reset
会带着 branch
一起移动。
git tag
bash
git tag # 列出所有tag
git tag [tagname] # 新建一个tag在当前commit
git tag [tagname] [commit] # 新建一个tag在指定的commit
git tag -d [tagname] # 删除本地tag
另⼀种引用类型。作用:设置持久标记,例如版本号。和 branch
区别:
- 不能改变
- 不能被
HEAD
指向 origin/master
,origin/feature
,origin/HEAD
和tag
有相似之处:也不能从本地改变位置,也不能被HEAD
指向
bash
git tag # 显示已存在的tag
git tag -a v2.0 -m 'xxx' # 增加v2.0的tag
git show v2.0 # 显示v2.0的日志及详细内容
git log v2.0 # 显示v2.0的日志
git checkout v2.0 # 检出版本v2.0
git checkout v2.0
会把远程 tag v2.0
拉到本地,在本地创建一个无Head
指向的分支,而远程tag
分支不受任何影响,将本地分支push
后将发布一个新的分支。
基于 tag
的 git check out
出来的分支可以执行 git branch xxx
创建新的本地分支,然后git check out xxx
, commit
后push
到远端可以创建一个新分支(也可以不提交)。
bash
git push [remote] [tag] # 提交指定tag
git push [remote] --tags # 提交所有tag
git checkout -b [branch] [tag] # 新建一个分支,指向某个tag
git push origin :refs/tags/[tagName] # 删除远程tag
git revert
- 撤销指定的提交,用于已经
push
到master
的内容需要删除。它的原理是创建一个新的commit
,内容是指定commit
的「相反内容」。
bash
# 新建一个commit,用来撤销指定commit 后者的所有变化都将被前者抵消,并且应用到当前分支
git revert [commit]
git cherry-pick
- 选择指定的某个或某些
commit
,合并进当前分支。 将commit
到HEAD
处。
bash
git cherry-pick [commit1] [commit2]
git cherry-pick ff44785404a8e # 合并提交ff44785404a8e的修改
⽐如我想抛弃某个 branch
,这个特性不要了,但它的某两个 commit
我希望保留。
git reflog
bash
git reflog <branch>
查看指定的引用(HEAD
或 branch
)的移动历史,从而找到之前的某个特定 commit
branch 的作用
- 让项目可以同时做多件事
- 未做完的事不会被项目真正收录
Feature Branching
- 做法:每开发一个新的功能做一个修复,都使用单独的分支,在做完之后
merge
到master
去 - 本地
merge
:由于别人可能在你之前push
过,所以你的push
可能失败。所以通常会需要先pull
一下,然后再push
。
- 使用 github:先创建
pull request,
在同事审阅完成之后,通过按钮实现在线merge
pull request
是什么:是开发者对远端仓库的提出的「拉取某个branch
」的请求
Git Flow
两条持久主线:master 和 develop
master
- 最稳定的分支(长期分支),只用于存放所有的发布
- 每次有了新的
commit
之后,立即打一个tag
来记录 - 正式发布的集成目标
develop
- 日常开发分支(长期分支),用于存放不稳定版本的发布
develop
并不是直接用于开发feature
的,开发feature
需要专门的branch
develop
在第一时间从master
上分离出来- 需要开发任何功能的时候,从
develop
创建出新的feature branch
,开发完成后合并回develop
(合并的时候使用--no-ff
),然后删掉feature branch
- 当下一正式版本需要的所有功能开发完成之后,从
develop
上创建新的release branch
,并在release branch
合并到master
后合并回develop
(合并的时候用--no-ff
),然后删掉release branch
feature branches
- 功能型开发分支(目的型分支)
- 始于
develop
,终于develop
,每次开发新功能是从develop
创建,开发完成后合并到develop
(使用--no-ff
),然后被删掉
release branches
- 预交付分支(目的型分支)
- 始于
develop
,终于master & develop
- 每次下一版本的功能开发完毕后,从
develop
上创建 - 创建完成后,更新版本号,然后单独做一个新的
commit
- 如果有
bug fix
,直接在release branch
上修复,bug fix
完成后,合并到master
和develop
(使用--no-ff
),然后release branch
被删掉
hotfix branches
- 热修复分支(目的型分支)
- 始于
master
,终于master & develop
- 已正式发布的产品发现
bug
,直接从master
或者出问题的tag
上创建hotfix branch
进行紧急修复,修复完成后合并到master
和develop
(或release branch
如果有的话)(使用--no-ff
),然后被删掉。
这里还有一个点,如果是本地创建的 feature
分支(从develop
分支创建)开发完成后,成功合并到 develop
分支后,这个本地 feature
分支是可以删掉的,并不一定要提交到远程,不然远程仓库里会有很多冗余分支。
Git 配置
首先需要下载Git客户端:https://git-scm.com/
然后输入下面命令进行配置(主要是用户名信息):
bash
# 设置提交代码时的用户信息
git config [--global] user.name "[name]" # 配置用户名
git config [--global] user.email "[email address]" # 配置邮件
git config --list # 显示当前的Git配置
git config -e [--global] # 编辑Git配置文件
Git的设置文件为.gitconfig
,它可以在用户主目录下(全局配置),也可以在项目目录下(项目配置)
生成 RSA 密钥 SSH Key
bash
ssh-keygen -t rsa
cat ~/.ssh/id_rsa.pub
第一次使用该命令时,之后会在用户目录下生成.ssh
文件夹(如果是windows就是在C:/Users/用户名/目录下, 如果是Mac就用上面的cat
命令查看即可),其中会包括:id_rsa
、id_rsa.pub
以及其他的文件,其中 id_rsa
是私钥,id_rsa.pub
是公钥。
将id_rsa.pub
公钥文件中的内容全部复制,以使用 gitlab 仓库为例,打开gitlab,找到Profile Settings --> SSH Keys ---> Add SSH Key
,把复制的内容粘贴到Key
所对应的文本框,在Title
对应的文本框中给这个sshkey设置一个名字,点击Add key
按钮。
检测是否配置成功:本地 git-bash 输入以下命令
bash
$ ssh -T git@github.com
若显示如下,则代表配置成功:
绑定成功了之后,就可以复制 gitlab上面项目的 ssh 地址进行 git clone ,将 gitlab上项目克隆到本地了:
gitlab 上还可以添加项目成员和设置权限:
访问权限
- Private - 私有,只有属于该项目成员才能clone
- Internal - 内部,有Gitlab账号的人都可以clone
- Public - 公开,任何人可以clone
行为权限
- Guest - 访客
- Reporter - 报告者; 可以理理解为测试员、产品经理理等
- Developer - 开发者
- Master - 主人,负责对Master分⽀支进行维护
- Owner - 拥有者
git 克隆项目时使用 HTTPS url 跟 SSH url 的区别:
- 使用
https url
克隆对初学者来说会比较方便,复制https url
然后到git Bash
里面直接用clone
命令克隆到本地, 但是每次fetch
和push
代码都需要输入账号和密码,这也是https
方式的麻烦之处。 - 而使用
SSH url
克隆却需要在克隆之前先配置和添加好SSH key
,因此,如果你想要使用SSH url
克隆的话,你必须是这个项目的拥有者(或者你的项目的主管有权限添加SSH key
)。否则你无法添加SSH key
,另外 ssh 默认每次fetch
和push
代码不需要输入账号和密码 ,如果你想要每次都输入账号密码才能进行fetch
和push
也可以另外进行设置。
Git 命令速查表
这个图上的命令不全,这里有一个总结的比较全面的帖子:Git常用命令,总结的很全了!