版本控制工具有很多,比如 CVS、SVN、Git 等,但目前用得最广泛的应该就是 Git 了。
下面介绍一些常用的 Git 命令。
一、全局、本地必要配置
挺有必要的,会更清晰的跟踪提交者的信息。
# 全局配置
$ git config --global user.name "your_name"
$ git config --global user.email "your_email"
# 本地配置
git config user.name "your_name"
git config user.email "your_email"
二、初始化仓库
# 初始化
$ git init
# 与远程仓库建立连接
$ git remote add origin <repo_addr>
# 修改远程仓库地址(一)
$ git remote set-url origin <repo_addr>
# 修改远程仓库地址(二)
$ git remote rm origin
$ git remote add origin <repo_addr>
# 修改远程仓库地址(三)直接修改配置文件
$ vim .git/config
三、关于克隆仓库
$ git clone <repo_addr>
# 使用 dir_name 作为克隆仓库的目录名称
$ git clone <repo_addr> <dir_name>
以上方式仅会克隆仓库的「默认分支」,通过 git branch --list
能看到本地只有一个分支。
自 2020 年 10 月起,在 GitHub 平台新创建的仓库,其默认分支名称正式调整为
main
(详见)。如果本地通过git init
创建的仓库,其默认分支仍是master
的话,可通过命令调整默认分支:
git config --global init.defaultBranch main
如果再通过新建分支再拉取指定分支,甚至可能还需要解决冲突,太繁琐了。
那么,如何快速有效的直接克隆远程指定分支?
$ git clone -b <指定分支名> <远程仓库地址>
# 指定目录名称
$ git clone -b <指定分支名> <远程仓库地址> <dir_name>
比如,我远程有 master
和 develop
两个分支,通过 git clone -b develop git@github.com:toFrankie/git_dev_demo.git
会克隆 develop
分支到本地,而且本地只有 develop
一个分支。
*部分内容源自 Git 克隆远程仓库的指定分支。
四、分支操作
4.1 列出所有分支
# 列出所有本地分支
$ git branch
# 列出所有远程分支
$ git branch -r
# 列出所有本地分支和远程分支
$ git branch -a
# 如果分支太多,可以使用模糊查找
$ git branch | grep 'branch-name'
4.2 新建分支
若分支已存在,以下命令都会失败。
# 新建分支,但不会切换至新分支
$ git branch <branch-name>
# 新建分支,并切换至该分支。
$ git checkout -b <branch-name>
# 根据已有分支创建新的分支
$ git checkout -b <branch-name> origin/<远程分支名称>
# 例如,创建一个远程 develop 分支的本地分支
# git checkout -b develop origin/develop
# 重命名本地分支名称
$ git branch -m <old-branch-name> <new-branch-name>
有时候,你可能需要取出某个历史版本,可以理解为基于某个 Commit 来创建一个新的分支,就能看到历史版本了。可以使用以下命令:
# 基于 <SHA1> 新建一个新分支
$ git checkout -b <branch-name> <SHA1>
# 若不打算修改,只是想 checkout 的话
$ git checkout <SHA1>
举个例子,我们有以下 Commit Log,我需要基于 1b90fceff0f3e4f16b1b850019573d3103b69a96
的历史版本,新创建一个分支:
4.3 合并分支
# 合并指定分支到当前分支
$ git merge <branch-name>
# 工作中,特性分支合并至主干分支,通常会使用以下这个
$ git merge --no-ff <branch-name>
--no-ff
(not fast forward)的作用是:要求 git merge
即使在 fast forward 条件下也要产生一个新的 Merge Commit。采用 --no-ff
的方式进行分支合并目的在于:希望保持原有「特性分支」整个提交链的完整性。
关于分支合并,有使用
git merge
的,也有使用git rebase
的,而且不同派别还很容易争吵起来。就像缩进是采用 Tab 好还是 Space 好的争论一样,那么实际中如何权衡,根据团队而定吧。
4.4 删除分支
# 删除本地指定分支
$ git branch -d <branch-name>
# 删除远程分支
git push origin --detele <branch-name>
# 删除远程分支(方法二:向远程分支推送一个空分支)
$ git push origin :<远程分支名>
4.5 重命名远程分支
要重命名远程分支名称,其实就是先删除远程分支,然后重命名本地分支,再重新推送一个远程分支的过程。
例如,将远程分支 dev
重命名为 develop
,可以这样操作:
# 1. 删除远程分支 dev
$ git push origin --delete dev
# 2. 重命名本地分支 dev 为 develop
$ git branch -m dev develop
# 3. 推送本地分支 develop
$ git push origin develop
五、暂存提交推送
5.1 暂存更改
关于 git add
命令,需要注意一下 1.x
版本与之有些不同,最后有标注。
# 提交新文件(new file)、被修改(modified)文件以及被删除(deleted)文件。
$ git add .
# 提交新文件(new file)、被修改(modified)文件以及被删除(deleted)文件。
$ git add -A
# 提交被修改(modified)和被删除(deleted)文件,不包括新文件(new file)
$ git add -u
# 提交新文件(new file)和被修改(modified),不包括被删除(deleted)文件
$ git add --ignore-removal .
# 获取帮助
$ git add -h
# 以上为 2.x 版本的标准。
# 以下为 1.x 版本的区别之处:
# 1.x 的 `git add .` 不包括被删除文件(deleted)
# 1.x 不存在 `git add --ignore-removal .` 命令,但其作用等同于 1.x 版本 `git add .`
5.2 提交暂存
# 提交已暂存文件
$ git commit -m 'commit message'
# 提交已暂存文件,以及跟踪过但未被添加到暂存的文件
# 注意:git commit -am 可以写成 git commit -a -m,但不能写成 git commit -m -a
$ git commit -am 'commit message'
# 修改最后一次的提交说明(但 commitId 会改变哦,因为它是一次全新的提交)
$ git commit --amend
注意一下,
git commit --amend
命令,只能修改最新一次的提交说明 ,执行命令进入 vim 模式(具体如何插入编辑,保存退出不展开赘述了,你们都会的)。亦可通过git commit --amend -m 'Commit Message'
直接覆盖不用进入 vim 编辑器。需要注意的是,该命令会导致
commitId
(快照唯一标识)发生改变。可通过git log
(查看历史提交记录)前后对比发现。其实呢,修改过的提交实际上是全新的提交,而先前的提交将不再位于您当前的分支上。该命令其实还有很多选项,还能更改提交文件等,想了解请看这里。
5.3 推送至远程分支
主要用于将本地更新推送到远程主机,但不同简化形式、命令参数产生延申效果。
一般地,远程主机名 remote 都为
origin
。
# 不省略本地分支名和远程分支名情况下,冒号(:)前后是没有空格的
$ git push <远程主机名> <本地分支名>:<远程分支名>
# git push origin master:master
还有几种简化形式的写法:
- 省略远程分支(多分支情况下,本人常用这种形式)
# 将本地分支推送到远程主机上的同名分支。如果远程分支不存在,则会自动创建一个远程分支。
$ git push <远程主机名> <本地分支名>
# git push origin master
- (慎用)省略本地分支(相当于删除远程分支)
# 将一个“空的本地分支”推送至远程分支,即表示删除指定的远程分支。
$ git push <远程主机名> :<远程分支名>
# git push origin :master
# 等同于
$ git push origin --delete <远程分支名>
- (不推荐)省略本地分支、远程分支
# 将当前分支推送至远程主机上的对应分支
$ git push <远程主机名>
# git push origin
这种形式要求当前本地分支和远程分支之间存在追踪关系。
怎么理解?
首先我不推荐,而且平常也不用这种形式的进行推送的。假如有存在
master
和develop
两个分支,当与远程分支建立追踪关系的是master
分支,那么处于develop
分支时,使用git push origin
形式推送至远程主机时就会提示:The current branch test has no upstream branch.
,然后再执行git push --set-upstream origin develop
即可使用这种形式的推送。
- 省略远程主机、本地分支、远程分支
# 将当前分支推送至远程主机对应分支
$ git push
这种形式,除了要求当前本地分支和远程分支之间存在追踪关系之外,还要求当前当前分支只有一个追踪分支。
- 省略远程分支,添加参数 -u
# 将本地分支推送到远程主机上的同名分支。如果远程分支不存在,则会自动创建一个远程分支。
$ git push -u <远程主机名> <本地分支名>
# git push -u origin master
这种形式适用于当前分支与多个主机存在追踪关系,可以利用
-u
指定一个默认的主机,这样后面就可以不加任何参数适用git push
推送对应的分支了。
以上指令执行之后,在 .git/config
配置文件下会有分支的对应:
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = git@github.com:toFrankie/git_dev_demo.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
[branch "develop"]
remote = origin
merge = refs/heads/develop
六、标签操作
# 列出所有 tag
$ git tag
# 查看 tag 信息
$ git show <tag-name>
# 轻量标签
$ git tag <tag-name>
# 附注标签
$ git tag -a <tag-name> -m <message>
# 例如,打一个 v1.0.0 的标签
# git tag -a v1.0.0 -m 'v1.0.0 release'
# 后期打标签
$ git tag -a <tag-name> <version>
# 提交指定 tag
$ git push origin <tag-name>
# 提交所有 tag
$ git push origin --tags
# 删除本地 tag
$ git tag -d <tag-name>
# 删除远程 tag
$ git push origin --detele tag <tag-name>
# 还可以向远程推送一个“空标签”,等同于删除远程标签
$ git push origin :refs/tags/<tag-name>
# 将本地所有不在远程仓库服务器上的标签推送至服务器
git push origin –tags
七、拉取
这里主要有两条命令 git fetch
和 git pull
,区别如下:
-
git fetch
:将远程主机的最新内容拉到本地,用户在检查了以后决定是否合并到「本地分支」中。 -
git pull
:将远程主机的最新内容拉下来后直接合并,相当于:git fetch
+git merge FETCH_HEAD
。
git fetch origin branch-name
虽说会将远程的更新拉取到本地,它会更新至本地的origin/branch-name
的分支下,换句话说:此时origin/branch-name
与远程的branch-name
分支是一致的,都是最新的(请注意,本地的origin/branch-name
和branch-name
是两个独立的分支)。如果你 Fetch 完成之后,不进行 Merge 或 Rebase 等合并操作,它是不会更新
branch-name
分支内容的。在合并之前,可以利用git diff branch-name origin/branch-name
去对比两个分支的内容,最终决定是否要将远程的更新合并至本地的branch-name
分支中。
7.1 git fetch
# 将 git remote 中所有关联的远程仓库包含的所有分支的更新拉取到本地
$ git fetch
# 将远程所有分支的更新拉取至本地
$ git fetch <远程主机名>
# 将远程特定分支的更新拉取至本地
$ git fetch <远程主机名> <分支名>
# git fetch origin main
当拉取成功后,会返回一个 FETCH_HEAD
,指的是某个分支在服务器上的最新状态。通过以下方式可以查看刚拉取回来的更新信息:
$ git log -p FETCH_HEAD
我们来试一下,如下图:
图中可以看到一些文件更新信息,包括文件名、更新作者、更新时间、更新代码等,并通过这些信息来判断是否产生冲突。
接着,可以通过 git merge
来将这些拉取下来的最新内容合并到当前分支中:
$ git merge FETCH_HEAD
# 或者使用以下这个,其中 <branch-name> 表示要合并的分支名称,例如 origin/main
$ git merge origin/<branch-name>
7.2 git pull
# 拉取远程某分支的更新,并与本地指定分支合并
$ git pull <远程主机名> <远程分支名>:<本地分支名>
# git pull origin
# 如果远程分支与当前本地分支进行合并,则冒号及后面部分可以省略
$ git pull <远程主机名> <远程分支名>
# git pull origin <branch-name>
因此
$ git pull origin main
# 相当于
$ git fetch origin main
$ git merge FETCH_HEAD
7.3 (建议)少用 Push,多用 Fetch 和 Merge
将拉取和合并独立开来是一个较好的方法,即少用 Push,多用 Fetch 和 Merge。
git pull
的问题是它把过程的细节都隐藏了起来,以至于你不用去了解 Git 中各种类型分支的区别和使用方法。当然,多数时候这是没问题的,但一旦代码有问题,你很难找到出错的地方。看起来git pull
的用法会使你吃惊,简单看一下 Git 的使用文档应该就能说服你。将下载(Fetch)和合并(Merge)放到一个命令里的另外一个弊端是,你的本地工作目录在未经确认的情况下就会被远程分支更新。当然,除非你关闭所有的安全选项,否则
git pull
在你本地工作目录还不至于造成不可挽回的损失,但很多时候我们宁愿做的慢一些,也不愿意返工重来。
尤其在多人协作的时候,可能是一个复杂的合并操作,我们可以这样(以 main
分支为例):
# 1. 拉取
$ git fetch origin main
# 2. 比较
$ git diff main origin/main
# 3. 合并
$ git merge origin/main
况且,有些团队的合并操作要求使用 git rebase
,而不是 git merge
。当然了也有 git pull --rebase
可使用啦。
可参考文章:
未完待续...