Git 基础
深入了解 Git,推荐阅读官方的《Pro Git》。
常用命令记录
# 本地检出远程分支
$ git checkout -b <local_branch_name> origin/<origin_branch_name>
# 删除所有未跟踪的文件(主要是修改后未跟踪文件无法被git checkout .时)
$ git clean -nf # 首先查看要删除的文件
$ git clean -f # 执行删除操作
一、Git 纵览
1. 什么是 Git
git(/ɡɪt/)是一个分布式版本控制软件,最初由林纳斯·托瓦兹创作,于2005年以GPL发布。最初目的是为更好地管理Linux内核开发而设计。
目前 git 已经被广泛用于管理项目开发,是当今最流行、最先进的分布式版本控制系统。
GitHub 和 GitLab 都是通过 Git 进行版本控制的软件源代码托管服务平台,GitLab 可以部署私有化 Git Server。
国内知名的 Git 源码托管平台有码云、Coding 等。
2. 为什么使用 Git
- 速度 - 快
Git 基于 C 语言开发,而且版本库本地化;有能力高效管理类似 Linux 内核一样的超大规模项目。 - 完全分布式 - 离线工作
对于像 svn 的集中式版本控制系统来说,没有互联网、没有连接到中央仓库,甚至说中央服务器宕机,所有人就没有办法很好的协同工作。
而 git 作为分布式系统,几乎所有的东西都可以在本地完成;当恢复互联网连接后即可与服务器同步。 - 分支 - 灵活
Git 可以创建许多分支,每个 branch 都是独立的;当我们需要修改代码的时候,commit 也只是对本地分支的修改。分支可以非常方便的合并。
二、安装和基本流程
1. 下载安装
可以从官方网站 https://git-scm.com/downloads下载对应系统的 git 程序。
基本一路 next 即可,也可以参见内网的安装图解。
在命令行输入git --version
,看到版本号即表示 git 已经配置完毕。
$ git --version
git version 2.24.0.windows.2
图形化
安装完 git 客户端后,可以通过命令行进行 git 操作。
当然 git 也有一些图形化工具,可以试试 Sourcetree。
除了第三方 GUI 工具, 现代 IDE 也基本都内置了 Git,比如 JetBrains 系列软件的 VCS,VSCode 的 SOURCE CONTROL 模块等。
以下文档将会以 Windows10 环境,Git Bash + WebStorm 的方式介绍。
2. 基本工作流程
基本的 Git 工作流程如下:
- 在工作目录中修改文件。
- 暂存文件,将文件的快照放入暂存区域(Index)。
- 提交更新,找到暂存区域的文件,将快照永久性存储到 Git 仓库目录(Repository)。
三、Git clone(克隆)
1. 克隆现有仓库
通常加入一个项目,这个项目或空,或已经有了源码,都已经在仓库新建完成。
这时候我们就需要克隆这个仓库到本地以开始进行开发。
值得注意的是,如果你使用
clone
命令克隆了一个仓库,命令会自动将其添加为远程仓库并默认以 origin 为简写。
比如在内网 git 仓库有一个的空项目(仅包含 README.md),项目管理员(Owner/Maintainer)已经添加你到项目中(Guest/Reporter/Developer/Maintainer),登录自己的源码托管平台账户(GitLab),就可以看到这个项目:
1.1 通过 HTTP
点击 Clone with HTTP 复制仓库地址,然后在命令行执行:
# git clone [仓库HTTP地址] [本地文件夹名称]
$ git clone http://192.168.0.252:8000/jehorn/my-git-demo.git my-demo
# 如果是首次从该仓库克隆项目
# 系统会提示你输入凭证
# 输入正确后项目将会被克隆到指定目录
这样就克隆了一个项目到本地,并且和远程仓库关联。
注意:如果是开源项目,克隆是不需要凭证的。
凭证存储
如果你是 Windows 用户,用户凭证存储在 控制面板 - 凭据管理器 - Windows 凭据 中:
当你的 GitLab 账号修改了密码后,可以到这里重置密码。
1.2 通过 SSH 密钥
有时候我们不想输入密码,或者部署 DevOps 工作流时,输入密码不方便,可以考虑使用 ssh 密钥克隆项目:
# 1. 首先进入 .ssh 目录看看是否已经生成
$ cd ~/.ssh && ls
# 如果已经存在 id_rsa 和 id_rsa.pub 表示密钥已经存在
# 跳过第2步
# 2. 生成密钥
# -t 参数可以指定其他加密类型 默认RSA
# -C 参数指定注释
$ ssh-keygen -C "youremail@example.com"
# 3. 拷贝密钥到仓库
# 查看 id_rsa.pub 文件
# 并将其文本粘贴至仓库个人设置中
$ cat ~/.ssh/id_rsa.pub
![](https://img2018.cnblogs.com/blog/1012606/201911/1012606-20191119134704276-118213713.gif
此时你的电脑的公钥已经添加到了仓库中,可以无需密码克隆、提交代码了。
在仓库首页,复制 Clone with SSH 的路径:
$ git clone git@192.168.0.252:jehorn/my-git-demo.git my-demo
2. 在现有目录中初始化仓库(不推荐)
这个主要是用于新建仓库场景,对现有的项目打算使用 Git 来进行管理。
以下命令不推荐的原因是操作比较复杂,推荐直接在远程仓库新建项目,克隆到本地空文件夹,然后将现有项目文件粘贴至空文件夹中再提交来初始化项目。
# 1. 在项目根目录下初始化
$ git init
# 2. 如果项目根目录已经存在文件
# 应该开始对这些文件进行跟踪
$ git add .
$ git commit -m "initial project version"
# 3. 关联远程仓库
$ git remote add origin git@192.168.0.252:jehorn/my-git-demo.git
# 4. 拉取远程仓库的文件
# --allow-unrelated-histories 表示允许不相关历史合并
$ git pull origin master --allow-unrelated-histories
# 5. 提交本地文件到远程仓库
$ git push origin master
四、简单的分支操作
现在我们手上有了一个真实项目的 Git 仓库,并从这个仓库中取出了所有文件的工作拷贝。
通常克隆的项目的默认分支是 master,如果在多人协作开发,要做比较合理的分支管理的情况下,是不应该直接在 master 分支修改的。
具体的分支管理将在后面的分支策略中,这里只是介绍一下一些简单的分支操作命令。
1. 查看分支
# 查看本地分支
$ git branch
# 查看全部分支(包括 origin 分支)
$ git branch -a
在 WebStorm 中点击右下角的分支名称即可看到所有分支。
2. 新建分支
# 新建并检出分支
$ git checkout -b iss53
# 以上命令相当于
$ git branch iss53
$ git checkout iss53
在 WebStorm 中可以这样新建分支:
3. 切换分支
# 切换分支
# 注意切换分支前 stage 或 commit 自己的更改
$ git checkout master
在 WebStorm 中可以这样切换分支:
4. 删除分支
# 删除本地分支
$ git branch -d iss53
# 删除远程分支
# 如果已经检出远程分支到本地
# 这里并不会删除本地分支
$ git push origin --delete test1
To 192.168.0.252:jehorn/my-git-demo.git
- [deleted] test1
在 WS 删除分支:
5. 分支合并
# 合并本地分支 iss53 到当前所在分支
# 假如你当前在 master 分支
# 执行下述命令将会把 iss53 分支合并到 master
$ git merge iss53
# 合并远程分支 origin/test1 到当前分支
# 首先获取更新
$ git fetch
# 合并远程 test1 分支至当前分支
$ git merge origin/test1
# 推荐还是先检出远程分支
# 拉取更新后切换回当前分支
# 然后执行本地分支合并
WS 上合并某分支至当前分支:
6. 解决冲突
不推荐直接编辑文本,可以通过WS的可视化工具合并:
VCS
-> Git
-> Resolve Conflicts
。
五、Git 拉取
1. fetch
git fetch
是将远程主机的最新内容拉到本地,用户在检查了以后决定是否合并到工作本机分支中,这一步骤可以叫做获取。
# 将远程仓库全部更新取回本地
$ git fetch <远程主机(可选/默认 origin)>
# 取回指定分支的更新
$ git fetch <远程主机> <分支名>
# 查看取回的更新内容
$ git log -p FETCH_HEAD
2. pull
git pull
是将远程主机的最新内容拉下来后直接合并,可以称为拉取。
git pull
过程可以理解为:
# 获取远程 master 分支的最新内容
$ git fetch origin master
# 将获取到的内容合并至当前分支
$ git merge FETCH_HEAD
git pull <远程主机> <远程分支>:<本地分支(如果远程分支与当前分支合并, 该参数可省略)>
# 比如拉取远程 master 并合并到当前分支
# 为保证按照预期 确保你此时在本地 master 分支
$ git pull origin master
WS 拉取代码:
六、Git 提交/保存
基于文件状态,基本工作流程如下图所示:
1. add
git add
命令用于将文件添加为staged(已暂存)状态
# 暂存单个文件
$ git add <文件名>
# 暂存所有变化
# 包括 modified(已修改)和 new(新添加)
# 不包括 deleted(已删除)的文件
$ git add .
# 暂存已跟踪的变化
# 暂存 modified(已修改)和 deleted(已删除)文件
# 不包括 new(新文件)
$ git add -u
# 暂存所有变化
$ git add -A
在图形化界面中,通常把 add
包括在 commit
中。
2. commit
# 提交已暂存的更改到本地仓库
$ git commit -m <注释>
3. push
# 先拉取远程更新
$ git pull origin master
# 推送本地更新
$ git push origin master
3. stash(储藏)
经常有这样的事情发生,当你正在进行项目中某一部分的工作,里面的东西处于一个比较杂乱的状态,而你想转到其他分支上进行一些工作。问题是,你不想提交进行了一半的工作,否则以后你无法回到这个工作点。解决这个问题的办法就是 git stash
命令。
假设有一个项目,再工作区中工作了一半,突然测试要求你紧急修复一个 bug:
# 查看当前工作区状态
$ git status
On branch master
Your branch is up to date with 'origin/master'.
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
这时我们执行git stash
命令储藏当前工作状态:
$ git stash
# 再查看工作区状态
$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
# 储藏变更 包括新增的 untracked 文件
# message 是备注
$ git stash save "message" --include-untracked
可以看到此时工作区是干净的了。
此时你可以随意切换到其他分支,进行 bug 修复等紧急工作。
# 查看已经保存的 stash
$ git stash list
stash@{0}: WIP on master: dd164f5 add .gitignore
# 重新应用保存的状态
$ git stash apply stash@{0} # stash@{0} 是可选参数 如果不指定会默认应用最新的一个
On branch master
Your branch is up to date with 'origin/master'.
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
可以看到我们之前的修改又回到了工作目录中。
以上示例是在同一个分支储藏和应用的,当然它也可以在一个分支创建,在另一个分支应用,但是可能会有冲突。
stash 也可以被删除。
# 删除 stash
$ git stash drop stash@{0}
Dropped stash@{0} (42dc836537c70cf5a1bf3169db98a24b030e9745)
$ git stash list
# 显示为空 已经被删除
拓展
stash 可以直接检出一个新的分支,便于之后的其它操作:
$ git stash branch test01
Switched to a new branch 'test01'
On branch test01
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} (c976f3681b21e091b7380708977f948dea8963c1)
可以看到检出了一个新分支test01
并自动删除了 stash。
了解更多 stash。
5. .gitignore
gitignore - Specifies intentionally untracked files to ignore.
忽略提交到远程仓库的文件的配置。
这是存在于工作区目录根路径下的一个文件,如果在 Windows 系统下看不到,可以设置资源管理器的隐藏文件可见。
常见的比如:
# 忽略 IDE 配置文件
/.idea
/.vscode
# 忽略 依赖包
/node_modules
七、Git 分支策略
1. Git Flow
- 长期分支
- Master
- Develop
- 功能分支
- Feature Branch
- Release Branch
- Hotfix Branch
2. GitHub Flow
- 只有一个长期分支 master ,而且 master 分支上的代码,永远是可发布状态,一般 master 会设置 protected 分支保护,只有有权限的人才能推送代码到 master 分支。
- 如果有新功能开发,可以从 master 分支上检出新分支。
- 在本地分支提交代码,并且保证按时向远程仓库推送。
- 当你需要反馈或者帮助,或者你想合并分支时,可以发起一个 pull request。
- 当 review 或者讨论通过后,代码会合并到目标分支。
- 一旦合并到 master 分支,应该立即发布。
3. GitLab Flow
这个分支策略比较适用服务端,包含了测试环境,预发环境,正式环境,一个环境建一个分支。
要按环境依次推送,确保代码被充分测试过,才会从上游分支合并到下游分支。除非是很紧急的情况,才允许跳过上游分支,直接合并到下游分支。
八、Git 回滚/撤销
1. add 前
撤销修改(revert changes)
使用git checkout -- <file>...
命令可以撤销对指定文件的修改。
使用git checkout .
命令可以撤销所有更改。
假设一个仓库有以下状态:
$ git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be
committed)
(use "git restore <file>..." to discard changes in
working directory)
modified: index.html # 未暂存的修改
deleted: text.txt # 未暂存的删除
Untracked files:
(use "git add <file>..." to include in what will be
committed)
index.css # 未暂存的新增
执行git checkout .
命令来取消所有更改:
$ git checkout .
Updated 2 paths from the index
# 再次查看状态
$ git status
On branch master
Untracked files:
(use "git add <file>..." to include in what will be
committed)
index.css
可以看到未暂存的修改和未暂存的删除已经被抹去了,而未追踪(Untracked)的文件依然存在。
你需要知道
git checkout -- [file]
是一个危险的命令。除非你确实清楚不想要那些变更了,否则不要使用这个命令。
在WS中,可以通过VCS
-> Git
-> Revert...
选项打开撤销更改的对话框:
点击Revert按钮即可。
2. add 后且 commit 前回滚
取消暂存的文件
当不小心暂存了额外的文件,想要取消暂存,可以使用git restore --staged <file>...
命令。
比如:
我们在项目目录下新增了一个index.css
文件,同时修改了index.html
文件;这时要暂存修改,顺手执行了:
# 暂存所有修改和新增
$ git add .
# 查看 本地仓库 状态
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: index.css
modified: index.html
然后我们取消暂存index.css
文件:
$ git restore --staged index.css
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: index.html
Untracked files:
(use "git add <file>..." to include in what will be
committed)
index.css
通过git status
命令我们看到了,index.css
已经是未暂存状态了。
3. commit 后且 push 前回滚
3.1 commit 后追加改动/备注更正
$ git commit --amend
这个命令会将暂存区中的文件重新提交。
如果自上次提交以来你还未做任何修改(例如,在上次提交后马上执行了此命令),那么快照会保持不变,而你所修改的只是提交信息。
下面做个示例:
# 假如某仓库 我们新增了一个文件
# 我们执行了一次提交
$ git add index.html
$ git commit -m "add index.html"
[master a54024b] add index.html
1 file changed, 12 insertions(+)
create mode 100644 index.html
# 查看刚才的提交
$ git log
commit a54024b987f9637c10cbe2a1e35e7cca71eb03e7 (HEAD -> master)
Author: Jehorn <gujh@bbdtek.com>
Date: Thu Nov 14 11:38:41 2019 +0800
add index.html
此时提交完我们又发现有个文件没有添加到暂存区(add)而没有提交:
# 查看当前项目还有那些改动
$ git diff
# 可以看到修改了 text.txt 文件
diff --git a/text.txt b/text.txt
index 2435860..33204a3 100644
--- a/text.txt
+++ b/text.txt
@@ -1 +1,2 @@
Hi~
+How r u?
# 这时候我们执行 commit amend
# 来追加提交这些更改 并且修改 commit 备注
# 到上一次提交中
$ git add text.txt
$ git commit --amend
# 执行完 commit amend 后
# 命令行会自动从 vim 打开一个修改备注
# 如下图所示
通过 vim 修改这个文件,添加了新的备注后,保存并退出(wq)。就成功提交了。
可以通过 git log
来查看:
$ git log
# 可以看到我们追加的更改已经在上一次提交里了
commit f5c34cb3fb00d88e7ab0393837c8fe6f7b8f6d0b (HEAD -> master)
Author: Jehorn <jehorn@gmail.com>
Date: Thu Nov 14 11:38:41 2019 +0800
add index.html; update text.txt
3.2 撤销 commit
Git中 HEAD
指向的版本就是当前版本,所以可以使用 git reset
来重置 HEAD
以及它所指向的 branch
的位置。
使用 git log
可以查看提交历史,可以确定要回退到哪次提交。
使用 git reflog
查看历史命令,可以在回退后再回到回退前的提交。
这里先使用 --hard
模式演示版本回退。需要注意hard模式可能会导致工作区修改丢失。
还是强调一下,这里的回滚是在 push 前;要回滚已经 push 的 commit,请参见《4. push》后回滚 这一节。
假设有一个仓库,已经有了几次提交,可以使用 git log
命令查看:
$ git log
# commit 后面的就是本次提交的 id
# 可以用于 撤销提交操作
# 通常只需要输入前几位 git 就可以自动识别
commit e7e3c0694265835fca12b66bc2605c01474e5767 (HEAD -> master) # 最近一次提交 和当前工作区内容一致
Author: Jehorn <jehorn@gmail.com>
Date: Mon Nov 18 16:08:04 2019 +0800
add index.css
commit f5c34cb3fb00d88e7ab0393837c8fe6f7b8f6d0b
Author: Jehorn <jehorn@gmail.com>
Date: Thu Nov 14 11:38:41 2019 +0800
add index.html; update text.txt
commit b271fcd5023b4dd1379143891835b4a6be005b08
Author: Jehorn <jehorn@gmail.com>
Date: Thu Nov 14 10:57:49 2019 +0800
add gitignore
commit 6925a90efa3ec6d300cd966837154b3733d3c3cc
(origin/master, origin/HEAD)
Author: Jehorn <jehorn@gmail.com>
Date: Wed Nov 13 17:56:02 2019 +0800
add text.txt
回退版本
现在我们想要撤销最近一次提交(意思是回到倒数第二次提交,也可以说丢弃最后一次提交的修改):
$ git reset --hard f5c34cb
# 或者
# $ git reset --hard HEAD^
HEAD is now at f5c34cb add index.html; update text.txt
查看工作区日志(git log
),可以看到提交e7e3c06
已经不见了。
撤销回退版本
此时可能又有新的想法,不想抛弃那一次更改了,想要再回到回退之前版本:
# 首先使用 reflog 查看历史记录
$ git reflog
f5c34cb (HEAD -> master) HEAD@{0}: reset: moving to HEAD^
e7e3c06 HEAD@{5}: commit: add index.css # 这一条记录就是当初 commit 的记录
# 然后再 reset HEAD 到那一次提交
$ git reset --hard e7e3c06
查看工作区目录,可以发现index.css
又回来了。
3.3 关于 git reset
上面我们都是用git reset --hard <id>
来示例的,这个 --hard
是一种模式,还有其它两种模式:--soft
,--mixed(默认)
。
hard
将 HEAD 和 Index 和 workingcopy 重置到某一 commit,中间的 commit 修改记录和 Staging area 的修改文件和 Working copy 的修改文件将全部丢失,并更新到这一 commit 的状态。具有破坏性。
soft
改变 HEAD。将 HEAD 重置到某一 commit,并把中间的 commit 记录放到 Index 中。
mixed
改变 HEAD 和 Index,它将重置 HEAD 到某一 commit,并且将中间的 commit 记录和 Staging area 文件都返回到 Working copy 中。
4. push 后回滚
如果是你自己的分支,完全还可以使用 git reset --hard
来回滚,即便已经 push 到远程仓库。
只不过回滚后再提交 git 会提示你本地仓库版本落后于 remote 仓库,不能提交。这时通过:
# 强制推送
git push -f
强制推送可以将落后与 remote 仓库的本地仓库同步到服务器。
但是如果 git 仓库禁止强推,或者在公共分支上,想要做回滚就需要使用 git revert
来操作了。
git revert
命令意思是撤销某次提交。它会产生一个新的提交,虽然代码回退了,但是版本依然是向前的,所以,当你用revert
回退之后,所有人pull
之后,他们的代码也自动的回退了。
git revert
如果不是撤销的最近一次提交,那么一定会有冲突。一个人解决冲突,总比团队所有人来处理回滚,要好得多……
假设有一个项目,当前有以下提交历史:
$ git log
commit e7e3c0694265835fca12b66bc2605c01474e5767 (HEAD -> master, origin/master, origin/HEAD)) # 最后一次提交已经提交到 remote 仓库
Author: Jehorn <jehorn@gmail.com>
Date: Mon Nov 18 16:08:04 2019 +0800
add index.css
commit f5c34cb3fb00d88e7ab0393837c8fe6f7b8f6d0b
Author: Jehorn <jehorn@gmail.com>
Date: Thu Nov 14 11:38:41 2019 +0800
add index.html; update text.txt
commit b271fcd5023b4dd1379143891835b4a6be005b08
Author: Jehorn <jehorn@gmail.com>
Date: Thu Nov 14 10:57:49 2019 +0800
add gitignore
commit 6925a90efa3ec6d300cd966837154b3733d3c3cc
(origin/master, origin/HEAD)
Author: Jehorn <jehorn@gmail.com>
Date: Wed Nov 13 17:56:02 2019 +0800
add text.txt
然后我们要回滚到 f5c34cb
版本:
# 回滚到 f5c34cb
$ git revert f5c34cb
CONFLICT (modify/delete): index.html deleted in parent of f5c34cb... add index.html; update text.txt and modified in HEAD. Version HEAD of index.html left in tree.
error: could not revert f5c34cb... add index.html; update text.txt
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'
可以看到出现了冲突,这时候就需要解决冲突,在WS中可以通过可视化工具很方便的解决冲突:VCS
-> Git
-> Resolve Conflicts
选择Merge...
进行合并操作。中间是合并结果,左(新的)右(本地)两边是不同版本。
注意 git 会自动尝试一些合并,比如某些新增的文件,会默认保留,这时候建议手动删除,并追加到 revert 生成的提交中。
解决冲突后:
# 查看状态
$ git status
On branch master
Your branch is up to date with 'origin/master'.
You are currently reverting commit f5c34cb.
(all conflicts fixed: run "git revert --continue")
(use "git revert --skip" to skip this patch)
(use "git revert --abort" to cancel the revert operation)
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: text.txt
# 继续操作
$ git revert --continue
Revert "add index.html; update text.txt"
This reverts commit f5c34cb3fb00d88e7ab0393837c8fe6f7b8f6d0b.
# Conflicts:
# index.html
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch master
# Your branch is up to date with 'origin/master'.
#
# You are currently reverting commit f5c34cb.
#
# Changes to be committed:
# modified: text.txt
#
# 修改完后 wq 退出
# 然后就可以推送到远程仓库了
$ git push origin master
可以看出,提交后再回滚会很麻烦。
所以尽量做到多 commit 少 push,可以有效减少失误的操作。
本地操作是非常自由方便的。
九、Git tag
标签可以用于生产分支的版本号管理等。
# 切换到要打标签的分支
$ git checkout release
# 添加标签
$ git tag -a "v1.0.0" -m "release version 1.0.0"
# 查看标签
$ git tag -l
v1.0.0
# 推送标签
$ git push origin release --tags
...
* [new tag] v1.0.0 -> v1.0.0
The end.
Last updated by Gu. 11/18, 2019.