Git
Git 简介
- 分布式版本控制系统,能够记录操作的历史记录
- 完成安装后需要的配置:
$ git config --global user.name "Your Name"
$ git config --global user.email "email@example.com"
git config 命令的
--global
参数,表示使用的这台机器上所有的 Git 仓库都会使用这个配置
创建版本库
- 选择某个文件夹,打开
Git Bash here
$ mkdir learngit
$cd learngit
$pwd
<!--将会显示出当前文件夹目录-->
pwd 命令用于显示当期目录
- 通过
git init
命令把这个目录变成Git可以管理的仓库
$ git init
Initialized empty Git repository in /Users/michael/learngit/.git/
当前目录下多了一个.git的目录,没有看到.git目录,那是因为这个目录默认是隐藏的,用
ls -ah
命令就可以看见。
把文件添加到版本库
- 把编辑的文件放到learngit目录下
- 用命令
git add
告诉Git,把文件添加到仓库:
$ git add readme.txt
- 使用命令
git commit
告诉Git,把文件提交到仓库
$ git commit -m "wrote a readme.text"
-m 后面输入的是本次提交的说明,可以输入任意内容
记录修改及查看
修改记录
git status
# On branch master
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: readme.txt
#
no changes added to commit (use "git add" and/or "git commit -a")
git status
命令可以让我们时刻掌握仓库当前的状态,上面的命令告诉我们,readme.txt 被修改过了,但还没有准备提交的修改。
查看具体修改了什么内容,可以使用 git diff
命令:
$ git diff readme.txt
修改完把其提交到仓库
$ git add readme.txt
$ git commit -m "add distributed"
版本回退
git log
命令:告诉我们修改的历史记录,显示从最近到最远的提交日志;加上--pretty=oneline
参数,显示一堆版本号
$ git log --pretty=oneline
3628164fb26d48395383f8f31179f24e0882e1e0 append GPL
ea34578d5496d7dd233c827ed32a8cd576c5ee85 add distributed
cb926e7ea50ad11b8f9e909c05226233bf755030 wrote a readme file
git reset
: 返回某个指定的版本
Git 必须知道当前版本是哪个版本,在Git中, 用 HEAD
表示当前版本,上一个版本就是 HEAD^
,上上一个版本就是 HEAD^^
,当然往上100个版本写100个 ^ 比较容易数不过来,所以写成 HEAD~100
。
$ git reset -hard HEAD^
// 回到指定的版本,包括前一个和后一个
$ git reset -hard (版本号) //版本号没必要写全,前几位就可以了,Git会自动去找。
Git 的版本回退速度非常快,因为 Git 在内部有个指向当前版本的 HEAD
指针,当回退版本的时候,Git 仅仅是把 HEAD 从指向老的版本。
git reflog
: 记录操作的每一条命令(包括版本号),用 git reflog 查看命令历史,以便确定要回到未来的哪个版本。所以,可以通过此命令找到版本回退前的版本号,将版本再次恢复到回退前的版本。
工作区和暂存区
在 Git 中,有工作区和暂存区的概念:
工作区:在电脑看到的目录
版本库:工作区有一个隐藏目录 .git,这个不算工作区,而是 Git 的版本库。
Git的版本库里存了很多东西,其中最重要的就是称为 stage(或者叫 index)的暂存区,还有 Git 为我们自动创建的第一个分支 master,以及指向 master 的一个指针叫 HEAD。
用 git add
把文件添加进去,实际上就是把文件修改添加到暂存区;
用 git commit
提交更改,实际上就是把暂存区的所有内容提交到当前分支。
一旦提交后,工作区就是“干净”的,此时暂存区没有任何文件:
管理修改
- Git 跟踪管理的是修改,并非文件
- 提交后,通过
git diff HEAD -- readme.txt
命令可以查看工作区和版本库里面最新版本的区别 - 每次修改,如果不 add 到暂存区,那就不会加入到 commit 中。
撤销修改
git checkout -- file
可以丢弃工作区的修改:
$ git checkout -- readme.txt
命令 git checkout -- readme.txt
意思就是,把 readme.txt 文件在工作区的修改全部撤销,这里有两种情况:
一种是 readme.txt 自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
一种是 readme.txt 已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。
总之,就是让这个文件回到最近一次 git commit 或 git add 时的状态。也就是说,**git checkout -- file
命令用于丢弃尚未添加到暂存区的修改**。
这里的 --
很重要,如果没有 --
,就会变成切换到另一个分支。
而如果想要丢弃已经放入了暂存区的修改,需要使用 git reset HEAD file
,把暂存区的修改撤销掉(unstage),重新放回工作区
$ git reset HEAD readme.txt
Unstaged changes after reset:
M readme.txt
git reset
命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用 HEAD 时,表示最新的版本。
撤销暂存区的修改后,再次撤销工作区的修改即可。即先进行 reset,在进行 checkout
删除文件
当删除工作区文件后,工作区和版本库就不一致了,此时分为两种情况:
一是确实要从版本库中删除该文件,那就用命令 git rm
删掉,并且 git commit
:
$ git rm test.txt
rm 'test.txt'
$ git commit -m "remove test.txt"
[master d17efd8] remove test.txt
1 file changed, 1 deletion(-)
delete mode 100644 test.txt
另一种情况是删错了,因为版本库里还有,所以可以很轻松地把误删的文件恢复到最新版本:
$ git checkout -- test.txt
git checkout
其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
远程仓库
添加远程库
首先需要在远程库上创建一个 repo。
- 在本地的所需要关联的仓库下运行命令
$ git remote add origin https://github.com/xiao-ming9/learngit.git
//下一步把本地库的所有内容推到远程库上
$ git push -u origin master
把本地库的内容推送到远程,用 git push
命令,实际上是把当前分支 master 推送到远程。由于远程库是空的,我们第一次推送 master 分支时,加上了 -u
参数,Git 不但会把本地的 master 分支内容推送的远程新的 master 分支,还会把本地的 master 分支和远程的 master 分支关联起来,在以后的推送或者拉取时就可以简化命令。
以后只要本地做了提交,以后只需要 $git push origin master
删除远程库
如果发现添加错了远程库,或者想要删除远程库,可以使用 git remote rm <name>
命令。在使用前,建议使用 git remote -v
查看远程库的信息:
$ git remote -v
origin git@github.com:michaelliao/learn-git.git (fetch)
origin git@github.com:michaelliao/learn-git.git (push)
然后,根据名字删除,比如删除 origin
:
$ git remote rm origin
此处的“删除”其实是解除了本地和远程的绑定关系,并不是物理上删除了远程库。远程库本身并没有任何改动。要真正删除远程库,需要登录到GitHub,在后台页面找到删除按钮再删除。
从远程库克隆
在GitHub上创建一个存储库之后,在本地的仓库下运行命令git clone
//ssh协议
$ git clone git@github.com:xiao-ming9/gitskills.git
//https协议
$ git clone https://github.com/xiao-ming9/gitskills.git
分支管理
在 Git 里,master 分支叫主分支。HEAD
严格来说不是指向提交,而是指向 master
,master
才是指向提交的,所以,HEAD
指向的就是当前分支。
一开始的时候,只有 master 分支,Git 用 master
指针指向最新的提交,在用 HEAD
指向 master
,就能确定当前分支,以及当前分支的提交点:
每次提交,master
分支都会向前移动一步,这样,随着不断提交,master
分支的线也越来越长。
当创建新的分支,例如 dev
时,Git新建了一个指针叫 dev
,指向 master
相同的提交,再把 HEAD
指向 dev
,就表示当前分支在 dev
上:
Git 创建一个分支很快,因为除了增加一个 dev
指针,修改 HEAD
的指向,工作区的文件都没有任何变化!
不过,从现在开始,对工作区的修改和提交就是针对 dev
分支了,比如新提交一次后,dev
指针往前移动一步,而 master
指针不变:
假如在 dev
上的工作完成了,就可以把 dev
合并到 master
上。Git怎么合并呢?最简单的方法,就是直接把 master
指向 dev
的当前提交,就完成了合并:
所以 Git 合并分支也很快!就改改指针,工作区内容也不变!
合并完分支后,甚至可以删除 dev
分支。删除 dev
分支就是把 dev
指针给删掉,删掉后,就剩下了一条 master
分支:
创建与合并分支
- 创建dev分支,然后切换到 dev 分支
$ git checkout -b dev
Switched to a new branch 'dev'
git checkout
加上-b
参数表示创建并切换,相当于:
$ git branch dev //创建分支
$ git checkout dev//合并分支
- 查看当前分支
$ git branch
*dev
master
git branch
命令会列出所有分支,当前分支前面会标一个*
号。
- 切换回 master 分支
$ git checkout master
S witched to branch 'master'
- 把 dev 分支的工作成果合并到 master 分支上
$ git merge dev
Updating d17efd8..fec145a
Fast-forward
readme.txt | 1 +
1 file changed, 1 insertion(+)
git merge
命令用于合并指定分支到当前分支。合并后,
注意到上面的 Fast-forward
信息,Git告诉我们,这次合并是“快进模式”,也就是直接把 master
指向 dev
的当前提交,所以合并速度非常快。当然,也不是每次合并都能 Fast-forward
。
- 删除 dev 分支使用
git branch -d
命令:
$ git branch -d dev
另外,为了与 git checkout -- <file>
区分,新版的 Git 提供了新的 git switch
命令来切换分支分支。
创建并切换到新的 dev
分支,使用:
git switch -c dev
直接切换到已有的 master
分支,可以使用:
git switch master
使用新的 switch 命令比较容易理解。
解决冲突
- 当 Git 无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。用
git log --graph
命令可以看到分支合并图。
$ git log --graph --pretty=oneline --abbrev-commit
* 59bc1cb conflict fixed
|\
| * 75a857c AND simple
* | 400b400 & simple
|/
* fec145a branch test
...
分支管理策略
- 合并分支时,如果可能,Git会用 Fast forward 模式,但这种模式下,删除分支后,会丢掉分支信息。
- 如果要强制禁用 Fast forward 模式,Git 就会在 merge 时生成一个新的 commit,这样,从分支历史上就可以看出分支信息。因为这里要创建一个新的 commit,所以加上了
-m
参数,把 commit 信息描述进去。
$ git merge --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
readme.txt | 1 +
1 file changed, 1 insertion(+)
- 可以用带参数的
git log
查看分支情况
$ git log --graph --pretty=onrline --abbrev-commit
* e1e9c68 (HEAD -> master) merge with no-ff
|\
| * f52c633 (dev) add merge
|/
* cf810e4 conflict fixed
...
当不使用 Fast forward
模式时, merge 后就像这样:
- 分支策略
master 分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;干活都在 dev 分支上,也就是说,dev 分支是不稳定的,到某个时候,比如1.0版本发布时,再把 dev 分支合并到 master 上,在 master 分支发布 1.0 版本
Bug分支
- 修改 bug 前,若当前内容还不想提交,使用
git stash
把当前内容封藏
$ git stash
- 使用
git stash list
查看封藏内容
$ git stash list
stash@{0}: WIP on dev: 6224937 add merge
- 恢复方法:
git stash apply
:恢复后,stash 内容并不删除,你需要用git stash drop
来删除;git stash pop
:恢复的同时把 stash 内容也删了
- 可以多次 stash,恢复的时候,先用
git stash list
查看,然后恢复指定的 stash,用命令:
$ git stash apply stash@{0}
另外,当修复了 bug 后,当前还在开发的 dev 分支也需要引入修复后的代码,此时可以使用 Git 提供的 cherry-pick
命令,它能耦复制一个特定的提交到当前的分支:
$ git branch
* dev
master
$ git cherry-pick 4c805e2
[master 1d4b803] fix bug 101
1 file changed, 1 insertion(+), 1 deletion(-)
此时就会把修复 bug 的 commit 复制到当前的分支上。
Feature分支
开发一个新 feature,最好新建一个分支;
如果要丢弃一个没有被合并过的分支,可以通过
git branch -D <name>
强行删除。
多人协作
- 当从远程仓库克隆时,实际上 Git 自动把本地的 master 分支和远程的 master 分支对应起来了,并且,远程仓库的默认名称是 origin。
- 查看远程库信息:
git remote (-v)
$ git remote
origin
//git remote -v 显示更详细的信息
$ git remote -v
origin https://github.com/xiao-ming9/learngit.git (fetch)
origin https://github.com/xiao-ming9/learngit.git (push)
上面显示了可以抓取和推送的origin的地址。如果没有推送权限,就看不到 push 的地址。
2. 推送分支
- 推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git 就会把该分支推送到远程库对应的远程分支上:
git push origin master
如果要推送其他分支,比如dev,就改成:
git push origin dev
一般来说,需要推送的分支有 master,dev 和 future 分支,bug 分支只用于本地修复 bug,一般不需要推送到远程库。
抓取分支
当从远程库进行 clone 时,默认情况下,clone 下来的都是 master 分支,如果需要拉取其他分支的内容,可以使用
git checkout
命令:git checkout -b dev origin/dev
通过使用
git pull
把最新的提交从origin/dev
抓下来如果
git pull
失败了,原因是没有指定本地 dev 分支与远程 origin/dev 分支的链接,根据提示,设置 dev 和 origin/dev 的链接:
git branch --set-upstream dev origin/dev
- 多人协作的工作模式通常是这样:
首先,可以试图用
git push origin branch-name
推送自己的修改;如果推送失败,则因为远程分支比你的本地更新,需要先用
git pull试图合并
;如果合并有冲突,则解决冲突,并在本地提交;
没有冲突或者解决掉冲突后,再用
git push origin branch-name
推送就能成功!如果git pull提示“no tracking information”,则说明本地分支和远程分支的链接关系没有创建,用命令
git branch --set-upstream branch-name origin/branch-name
。
在本地创建和远程分支对应的分支,使用
git checkout -b branch-name origin/branch-name
,本地和远程分支的名称最好一致;
Rebase
Rebase 用于把本地尚未 push 的提交历史整理成直线。使用 Merge 命令通常其 commit 信息会比较混乱,同时在 merge 时会有一次 merge 的 commit 信息,对比如下:
使用 merge:
使用 rebase:
rebase 的适用场景是在个人分支上开发时,当需要获取最新的 master 提交的时候,可以通过 rebase 来整理 commit 信息。不推荐在公共分支上使用 rebase,会导致其他在当前分支开发的人也需要执行 rebase 操作。
常用指令
git rebase master
可以将dev分支合并到当前分支git rebase –abort
放弃一次合并- 合并多次commit操作:
git rebase -i dev
- 修改最后几次commit记录中的 pick 为 squash
- 保存退出,弹出修改文件,修改 commit 记录再次保存退出(删除多余的 change-id 只保留一个)
git add .
git rebase --continue
标签管理
创建标签
首先切换到需要打标签的分支上
用
git tag <name>
创建一个新标签git tag v1.0
可以用命令
git tag
查看所有标签git tag
- 默认标签是打在最新提交的 commit 上的,如果需要在之前的commit上打标签,可以找到历史提交的commit id,再打上标签即可
$ git log --pretty=oneline --abbrev-commit
$ git tag v0.9 [commit id]
可以使用 git show <tagname>
查看标签信息
可以创建带有说明的标签,用 -a
指定标签名,-m
指定说明文字
$ git tag -a v0.1 -m "说明内容" [commit id]
操作标签
- 删除标签:
$ git tag -d v0.1
Deleted tag 'v0.1' (was e078af9)
- 推送某个标签到远程:
git push origin <tagname>
,或者一次性推送:git push origin --tags
- 如果标签已经推送到远程,则应该先在本地删除,然后在从远程删除
$ git tag -d v0.9
$ git push origin :refs/tags/v0.9
使用码云
- 国内的Git托管服务
- 本地库可以与多个远程库互相同步,但是不能同名
自定义Git
- 让 Git 显示颜色:
git config --global color.ui true
忽略特殊文件
- 在 Git 工作区的根目录下创建一个特殊的
.gitignore
文件,然后把要忽略的文件名填进去,然后把.gitignore
提交到Git,Git 就会自动忽略这些文件。 - 如果添加一个文件到 Git,添加不了,原因是这个文件被
.gitignore
忽略了,如果要添加:
- 用
git add -f 文件名
强制添加 - 可以用
git check-ignore
命令检查哪个规则写错:
$ git check-ignore -v App.class
.gitignore:3:*.class App.class
配置别名
- 替换名字:
$ git config –global alias.新名字 旧名字
#用st表示status,co表示commit,ci表示commit,br表示branch
$ git config --global alist.st status
$ git config --global alias.co checkout
$ git config --global alias.ci commit
$ git config --global alias.br branch
--global
参数是全局参数,也就是这些命令在这台电脑的所有 Git 仓库下都有用。
配置文件
- 配置 Git 的时候,加上
--global
是针对当前用户起作用的,如果不加,那只针对当前的仓库起作用。
- 每个仓库的 Git 配置文件都放在
.git/config
文件中
$ cat .git/config
[core]
repositoryformatversion = 0
filemode = false
bare = false
logallrefupdates = true
symlinks = false
ignorecase = true
[branch "master"]
[remote "github"]
url = git@github.com:xiao-ming9/learngit.git
fetch = +refs/heads/*:refs/remotes/github/*
[remote "gitee"]
url = git@gitee.com:xiao_ming9/learngit.git
fetch = +refs/heads/*:refs/remotes/gitee/*
- 当前用户的 Git 配置文件放在用户主目录下的一个隐藏文件
.gitconfig
中
$cat .gitconfig
[user]
name = xingziming
email = 934933088@qq.com
[color]
ui = true
- 配置别名也可以直接修改这个文件,如果改错了,可以删掉文件重新通过命令配置。
参考文章: