1. 安装、Git 环境配置
1.1 安装 Git
官方版本可以在 Git 官方网站下载:打开 https://git-scm.com/download/win,选择相应版本即可。
Git 安装完成后,可以在开始菜单中看到 Git 的三个启动图标(Git Bash、Git CMD、Git GUI)。Git Bash 是 Git 配套的一个控制台,在空白处右键,点击 Open Git Bash here 即可开始使用 Git。
1.2 Git 环境配置:通过 git config 命令配置用户信息
安装完 Git 之后,要做的第一件事就是设置你的用户名和邮件地址。 这一点很重要,因为每一个 Git 提交都会使用这些信息,它们会写入到你的每一次提交中,不可更改。
1.2.1 配置用户名和邮件地址
git config --global user.name "abigailqin"
git config --global user.email abigailqin@csdn.net
注意:git config --global user.name "xxxx",要有双引号。如果使用了 --global 选项,那么该命令只需要运行一次,因为之后无论你在该系统上做任何事情, Git 都会使用那些信息。
如果想针对特定项目使用不同的用户名称与邮件地址时,可以在那个项目目录下运行没有 --global 选项的命令来配置。
1.2.2 查看配置
(1)使用 git config --list 命令列出 Git 当前能找到的所有配置
(2)使用 git config <key> 检查 Git 的某一项配置。
1.3 SSH Key
SSH Key 是一种用于在 Git 版本控制系统中进行安全身份验证的机制。它是一对密钥,包括公钥和私钥,公钥存储在 Git 服务器上,而私钥则存储在用户的本地计算机上。Git SSH Key 通过使用加密技术,确保只有具有正确私钥的用户才能访问 Git 仓库。SSH Key 允许用户通过 SSH 连接到 Git 服务器,而不需要输入密码,这种身份验证方式比传统的基于用户名和密码的身份验证更加安全和可靠。
向 Git 服务器提供 SSH 公钥的过程在所有操作系统上都是相似的:首先,需要确认自己是否已经拥有密钥。 默认情况下,用户的 SSH 密钥存储在其 ~/.ssh 目录下,进入该目录并列出其中内容,便可以快速确认自己是否已拥有密钥。
我们需要寻找一对以 id_dsa 或 id_rsa 命名的文件,其中一个带有 .pub 扩展名。 .pub 文件是公钥,另 一个则是私钥。 如果找不到这样的文件(或者根本没有 .ssh 目录),可以通过运行 ssh-keygen 程序来创建它们。首先 ssh-keygen 会确认密钥的存储位置(默认是 .ssh/id_rsa),然后它会要求你输入两次密钥口令,如果你不想在使用密钥时输入口令,将其留空即可。
现在,进行了上述操作的用户需要将各自的公钥发送给 Git 服务器管理员(假设服务器正在使用基于公钥的 SSH 验证设置),复制 .pub 文件内容,并将其通过邮件发送即可。
1.4 定制 Git 环境
Git 自带 git config 工具来帮助设置控制 Git 外观和行为的配置变量。
这些 Git 配置变量存储在三个不同的位置:
1. /etc/gitconfig 文件(Git 安装目录): 包含系统上每一个用户及他们仓库的通用配置。 如果在执行 git config 时带上 --system 选项,那么它就会读写该文件中的配置变量 (由于它是系统配置文件,因此你需要管理员或超级用户权限来修改它)。
2. ~/.gitconfig 或 ~/.config/git/config 文件:只针对当前用户。 可以传递 --global 选项让 Git 读写此文件,这会对你系统上所有的仓库生效。
3. 当前使用仓库的 Git 目录中的 config 文件(即 .git/config):针对该仓库。 可以传递 --local 选项让 Git 强制读写此文件,虽然默认情况下用的就是它(当然,你需要进入这个 Git 仓库中才能让该选项生效)。
每一个级别会覆盖上一级别的配置,所以 .git/config 的配置变量会覆盖 /etc/gitconfig 中的配置变量。
使用 git config --list --show-origin 查看所有的配置以及它们所在的文件。
1.3.1 忽略文件 .gitignore
在 Git 工作区的根目录下(与.git 目录在同一级别)创建一个特殊的 .gitignore 文件,然后把要忽略的文件名填进去,Git 在每次进行提交的时候就会自动忽略这些文件。我们一般不需要从头开始编辑 .gitignore 文件,已经有各种现成的种配置文件,只需要组合一下就可以使用了。所有配置文件可以直接在线浏览:
Python.gitignore · master · GitCode / Gitignore · GitCode
示例:
忽略文件的原则:
- 忽略操作系统自动生成的文件,比如缩略图等;
- 忽略编译生成的中间文件、可执行文件等,也就是如果一个文件是通过另一个文件自动生成的,那自动生成的文件就没必要放进版本库,比如Java编译产生的.class文件;
- 忽略带有敏感信息的配置文件,比如存放口令的配置文件。
假设你在 Windows 下进行 Python 开发:
Windows 会自动在有图片的目录下生成隐藏的缩略图文件,如果有自定义目录,目录下会有 Desktop.ini 文件,需要忽略上述 Windows 自动生成的垃圾文件;此外,还需忽略 Python 编译产生的.pyc、.pyo、dist等文件或目录;再加上你自己定义的文件,得到一个完整的 .gitignore 文件(内容如下),最后一步就是把 .gitignore 也提交到 Git,就完成了。
<.gitignore>
# Windows:
Thumbs.db
ehthumbs.db
Desktop.ini
# Python:
*.py[cod]
*.so
*.egg
*.egg-info
dist
build
# My configurations:
db.ini
deploy_key_rsa
1.3.2 强制添加被忽略文件 git add -f
有时候可能想添加一个文件到 Git,但发现添加不了,因为这个文件被 .gitignore 忽略了。如果你确实想添加该文件,可以用 -f 强制添加到 Git。
$ git add -f App.class
尽管可以用 git add -f 强制将此文件添加进去,但建议你添加一条例外规则:把指定文件排除在 .gitignore 规则外,写法是: !+文件名。
<.gitignore>
# 排除所有 .class 文件
*.class
# 不排除 .App.class 文件
!App.class
如果觉得添加不了是 .gitignore 写得有问题,需要找出来到底哪个规则写错了,可以用 git check-ignore 命令检查。
$ git check-ignore -v App.class
.gitignore:3:*.class App.class
# 返回结果说明:.gitignore 的第3行规则忽略了该文件,因此修订第3行规则即可。
1.3.3 配置别名 git config --global alias.xx yyyy
使用 Git 命令时,可以通过配置别名的方式简化命令。比如,给 git status 命令配置一个别名: git st,输入 git st 就比输入 git status 简单方便很多。下述这行命令就是告诉 Git,以后 st 就表示 status,--global参数是全局参数,表示这个命令在这台电脑的所有 Git 仓库下都可以使用。
$ git config --global alias.st status
示例-1:用 ci 表示 commit
$ git config --global alias.ci commit
$ git ci -m "sth."
示例-2:用 last 表示 log -1,git last 表示 git log -1,即显示最近一次的提交
$ git config --global alias.last 'log -1'
$ git last
示例-3:用 unstage 表示 reset HEAD,git unstage <file> 即为 git reset HEAD <file>,撤销暂存区的修改。
1.5 获取帮助
如果在使用 Git 时需要获取帮助,可以使用 help 命令找到 Git 命令的使用手册:
$ git help <verb>
$ git <verb> --help
例如,要想获得 config 命令的手册,执行 git help config 命令或 git config --help 命令后将跳转到使用手册对应页:
2. Git 仓库
2.1 工作目录(working tree\working directory)、暂存区域(staging area\index)和 Git 仓库(repository\git repository)
Git 项目有三个工作区域的概念:工作目录、暂存区域和 Git 仓库。
- Git 仓库目录是 Git 用来保存项目的元数据和对象数据库的地方。 这是 Git 中最重要的部分,从其它计算机克隆仓库时,拷贝的就是这里的数据。
- 工作目录是对项目的某个版本独立提取出来的内容,是你在电脑里能看到的目录。 这些从 Git 仓库的压缩数据库中提取出来的文件,放在磁盘上供你使用或修改。
- 暂存区域是一个文件,保存了下次将提交的文件列表信息,一般在 Git 仓库目录中,有时候也被称作 ‘索引’(index),不过一般说法还是叫暂存区域(staging area)。
基本的 Git 工作流程如下:
- 在工作目录中修改文件。
- 暂存文件,将文件的快照放入暂存区域。
- 提交更新,找到暂存区域的文件,将快照永久性存储到 Git 仓库目录。
如果 Git 目录中保存着的特定版本文件,就属于已提交状态。 如果作了修改并已放入暂存区域,就属于已暂存状态。 如果自上次取出后,作了修改但还没有放到暂存区域,就是已修改状态。
在你的工作目录下的每一个文件都不外乎这两种状态:已跟踪或未跟踪。 已跟踪的文件是指那些被纳入了版本控制的文件,在上一次快照中有它们的记录,在工作一段时间后,它们的状态可能处于未修改,已修改或已放入暂存区。 工作目录中除已跟踪文件以外的所有其它文件都属于未跟踪文件,它们既不存在于上次快照的记录中,也没有放入暂存区。 初次克隆某个仓库的时候,工作目录中的所有文件都属于已跟踪文件,并处于未修改状态。
2.2 使用 git init 在现有目录中初始化仓库
git init 用于在当前目录下初始化一个新的 Git 仓库。初始化后,会在当前目录下创建一个名为 .git的隐藏文件夹,其中包含了 Git 仓库的所有元数据和历史记录。执行该命令后,当前目录下的所有文件和子目录都会被纳入 Git 的版本控制中,你可以开始添加、修改和提交文件到 Git 仓库。
2.3 使用 git clone 克隆现有的仓库
语法:git clone <远程仓库地址>,例如,如果要将 GitHub 上的一个仓库复制到本地,可以使用以下命令:git clone https://github.com/username/repo.git,其中,username 是 GitHub 用户名,repo 是仓库名称。
3. 基础操作
3.1 检查文件状态 git status
要查看哪些文件处于什么状态,可以用 git status 命令。
该命令显示了当前所在分支(当前的分支名是 master,这是默认的分支名),并告诉你当前工作目录中哪些文件被修改了、哪些文件被添加或删除了、哪些文件已经被暂存了等等。
git status 命令的输出十分详细,但其显示结果有些繁琐。 可以使用 git status -s 命令或 git status --short 命令,得到更为紧凑的格式输出,示例:
- 新添加的未跟踪文件前面有 ?? 标记;
- 新添加到暂存区中的文件前面有 A 标记;
- 修改过的文件前面有 M 标记,M 有两个可以出现的位置,出现在右边的 M 表示该文件在工作区被修改了且没放入暂存区,出现在靠左边的 M 表示该文件在工作区被修改了并放入了暂存区,MM表示此文件在工作区被修改并提交到暂存区后又在工作区中被修改了,所以在暂存区和工作区都有该文件被修改了的记录。
3.2 跟踪新文件、暂存已修改文件:git add
git add 是一个多功能命令:可以用它开始跟踪新文件,或者把已跟踪的文件放到暂存区,还能用于合并时把有冲突的文件标记为已解决状态等。 将这个命令理解为 “添加内容到下一次提交中” 而不是 “将一个文件添加到项目中” 要更加合适。
3.2.1 跟踪新文件 git add <pathspec>…
git add 命令使用文件或目录的路径作为参数;如果参数是目录的路径,该命令将递归地跟踪该目录下的所有文件。使用 add 后可以再用 git status 查看一下文件状态,只要在 Changes to be committed 这行下面的,就说明是已暂存状态。
可以在 git add 命令后面跟上多个文件名,用空格隔开。
3.2.2 暂存已修改的文件 git add <pathspec>…
出现在 Changes not staged for commit 下面的文件,已跟踪但内容发生了变化,还没有放到暂存区,需要运行 git add 命令暂存这次更新。
如果在运行了 git add 之后又作了修改,需要重新运行 git add 把最新版本重新暂存起来。
3.3 撤销工作区修改或撤销暂存操作 git restore <pathspec>…
3.3.1 撤销工作区未暂存的修改:git restore <pathspec>…
git restore <pathspec>... 可以将工作区指定的文件或目录恢复成和暂存区一致的状态。如果指定的文件或目录在暂存区中已经有了修改,那么这些修改也会被覆盖掉,恢复成暂存区中的版本,<pathspec>… 表示需要恢复的文件或目录。使用 git restore 命令会直接覆盖当前工作区的文件,因此请谨慎使用。
假如你在工作区修改了 temp.txt 文件的内容,但还没有将修改暂存到暂存区,如果想撤销修改,可以使用 git restore 命令:
如果想要将文件或目录恢复到指定的提交状态,可以使用 git restore 命令加上 --source 参数:
git restore --source=<commit> <pathspec>…
3.3.2 保留工作区修改撤销暂存(可以用 git reset 实现一样的功能):git restore --staged <pathspec>…
git restore --staged <pathspec>… 命令可将暂存区指定的文件恢复到最新的提交状态(HEAD),撤销已暂存但未提交的更改,但不会不修改工作区文件内容。
在 Git 中,用 HEAD 表示当前仓库版本,也就是最新的提交,上一个版本是 HEAD^,上上一个版本是 HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成 HEAD~100。
使用 --source 将暂存区恢复到指定的提交状态,不改动工作区:
git restore --source=<commit> --staged <pathspec>…
下面的场景中,先对两个文件使用 mv 命令执行重命名操作,并把两次操作记录到暂存区。
如果想把这两次操作分两次提交,想撤销暂存区中的 renamed temp2.txt -> temp.txt,可使用 --staged,恢复暂存区内容。HEAD 对应的暂存区中没有 temp.txt,执行 restore temp.txt 命令后 temp.txt 变成 untracked;HEAD 对应的暂存区中存在 temp2.txt 文件,但工作区目录删除了此文件,因此执行 restore temp2.txt 命令后后显示 deleted temp2.txt 未加入暂存区。
3.3.3 撤销暂存以及工作区修改(可以用 git reset --hard HEAD 实现一样的功能):git restore --staged --worktree <pathspec>… 回到最近一次提交状态
3.3.4 使用 git reset 撤销工作区修改或撤销暂存
参考 3.7 & 3.8
3.3.5 使用 git checkout 恢复文件
git checkout <commit> <file> 命令会将文件恢复到最近一次提交的状态,且会丢失未提交的修改。
3.4 删除文件 git rm
3.4.1 删除文件 git rm <pathspec>…
使用 git rm 命令从已跟踪文件清单中移除文件(确切地说,是从暂存区域移除),并连带从工作目录中删除指定的文件,执行 git rm 后再执行 git commit,即可从 Git 中移除文件。
如果只是简单地从工作目录中手工删除文件,运行 git status 时就会在 “Changes not staged for commit” 部分(也就是未暂存清单)看到:"deleted: filename",运行 git rm 记录此次移除文件的操作,下一次执行 git commit 后,该文件就不再纳入版本管理了。
3.4.2 强制删除文件 git rm -f <pathspec>…
如果文件没有被添加到 Git 仓库中,git rm 命令会报错;如果你尝试删除一个修改过并且已经把修改放到暂存区的文件,git rm 命令会提示你需要先将修改提交到 Git 仓库中。
如果一定要删除之前修改过并且已经放到暂存区的文件,或者仓库中不存在的文件,要用强制删除选项 -f(force 的首字母)。git rm -f 命令可以强制删除已经被 Git 跟踪的文件,即使该文件被修改过并且没有被提交到 Git 仓库中,使用该命令时需要小心,它会永久删除文件,无法恢复。
3.4.3 从暂存区和 Git 仓库中删除但本地不删除 git rm -cached <pathspec>…
当想把文件从 Git 仓库中删除(亦从暂存区域移除),但仍然希望保留在当前工作目录中时,使用 git rm --cached <pathspec>… 。 换句话说,你想让文件保留在磁盘,但是并不想让 Git 继续跟踪。
$ git rm log/\*.log
# 表示删除 log/ 目录下扩展名为 .log 的所有文件
# 星号 * 之前的反斜杠 \,这是 Git 自己的文件模式扩展匹配方式。
$ git rm \*~、
# 表示删除以 ~ 结尾的所有文件。
3.5 移动文件 git mv <source> <destination>
$ git mv README.md README
命令 git mv README.md README,将对文件改名,运行此命令相当于运行了下面三条命令:
$ mv README.md README
$ git rm README.md
$ git add README
3.6 比较文件内容 git diff
比较工作目录中的文件与最新提交之间的差异:git diff
比较指定文件在工作目录和最新提交之间的差异:git diff <file>
比较工作目录中的文件与指定的提交之间的差异:git diff <commit>
比较两个提交之间的差异:git diff <commit1> <commit2>
比较指定文件在两个提交之间的差异:git diff <commit1> <commit2> <file>
比较两个分支之间的差异:git diff <branch1> <branch2>
比较指定文件在两个分支之间的差异:git diff <branch1> <branch2> <file>
3.7 提交更新 git commit
3.7.1 提交更新-编辑器输入提交信息 git commit
如果暂存区域已经准备妥当可以提交了,就可以使用 git commit 命令将修改提交到仓库。 请一定要确认还有什么修改过的或新建的文件还没有 git add 过,否则提交的时候不会记录这些还没暂存起来的变化,这些修改过的文件只保留在本地磁盘。 所以,每次准备提交前,先用 git status 看下,是不是都已暂存起来了, 然后再运行提交命令 git commit。
这种方式将会启动文本编辑器以便输入本次提交的说明。 (默认会启用 shell 的环境变量 EDITOR 所指定的软件, 一般都是 vim 或 emacs。编辑器显示的内容示例:
第一行是空行,供你输入提交说明。提交消息的注释行里包含最后一次运行 git status 的输出,其实完全可以去掉这些注释行,不过留着也没关系,多少能帮你回想起这次更新的内容有哪些。 如果想要更详细的,关于修改了哪些内容的提示,可以用 -v 选项,这会将你所做的改变的 diff 输出放到编辑器中,使你知道本次提交具体做了哪些修改。退出编辑器时,Git 会丢掉注释行,用你输入提交附带信息生成一次提交。
注:Vim 使用
正常模式 (:)-> 命令模式(内置命令如 w wq)
正常模式 (i)-> 插入模式(编辑功能)(Esc)-> 正常模式
先在键盘点击 i 按键,进入插入模式;然后在首行填写提交描述,完成后点击 Esc 退出插入模式;接下来按 : 进入命令模式,输入 wq 保存并退出。
3.7.2 提交更新-直接输入提交信息 git commit -m <msg>
可以在 commit 命令后添加 -m 选项,将提交信息与命令放在同一行,这样就不必通过 vim 添加提交信息了。
如果你只需要提交一些小的更改,并且提交信息比较简单,可以使用 git commit -m 命令;如果你需要提交一些比较大的更改,并且需要详细描述这些更改的内容,那么可以使用 git commit 命令,并在编辑器中输入详细的提交信息。
3.7.3 重新提交 git commit --amend -m <msg>
如果在提交完才发现漏掉了几个文件没有添加,或者提交信息写错了,可以运行带有 --amend 选项的提交命令尝试重新提交(最终将只有一个提交)。
修补提交最明显的价值是,可以稍微改进你最后的提交,而不会让 “啊,忘了添加一个文件”或者 “小修补,修正笔误” 这种提交信息弄乱你的仓库历史。
例如,你提交后发现忘记了暂存某些需要的修改,可以像下面这样操作:
$ git commit -m 'initial commit'
$ git add forgotten_file
$ git commit --amend -m 'initial commit with supplements'
文本编辑器启动后,可以看到之前的提交信息, 编辑后保存会覆盖原来的提交信息。
3.7.4 撤销提交 git reset [--soft/mixed/hard] [<commit>]
参考 3.7、3.8
3.7.5 为提交打标签 git tag
标签可以用来标识特定的提交或版本,方便用户在未来快速找到这个提交或版本。Git 主要有两种类型的标签:轻量标签(lightweight)与附注标签(annotated)。
轻量级标签只是一个指向特定提交的引用,它不包含任何其他元数据,因此比带注释的标签(annotated tag)更轻量级,通常用于临时标记某个特定的提交。附注标签是存储在 Git 数据库中的一个完整对象,是可以被校验的,其中包含打标签者的名字、电子邮件地址、日期时间以及标签信息。 通常建议创建附注标签,这样你可以拥有以上所有信息;但如果你只是想用一个临时的标签,或者因为某些原因不想保存那些信息,用轻量标签也是可以的。
查看标签和删除标签
使用 git tag 查看标签,这个命令以字母顺序列出标签,注意,标签不是按时间顺序列出,而是按字母排序的;使用 git show <tagname> 查看指定标签的详细信息,包括标签的提交信息和提交内容;使用 git tag -d <tagname> 删除指定的标签。
创建标签
在 Git 中创建标签非常简单:切换到需要打标签的分支上,然后运行 git tag 命令即可。默认标签是打在最新提交的 commit 上的。
- 创建轻量标签时只需要提供标签名字,如:git tag v1.4-lw。
- 创建附注标签需要在运行 tag 命令时指定 -a 选项,如:git tag -a v1.4 -m 'my version 1.4',-a 选项表示创建一个附注标签;-m 选项指定了一条将会存储在标签中的信息,如果没有为附注标签指定一条信息,Git 会运行编辑器要求你输入信息。
后期打标签
如果想对过去的提交打标签,可以先运行 git reflog 查看历史提交信息,找到目标提交的校验和(或部分校验和),在命令末尾加上它,如:git tag -a v1.2 9fceb02。
共享标签
默认情况下,git push 命令并不会传送标签到远程仓库服务器上。 在创建完标签后,你必须显式地推送标签到共享服务器上。 这个过程就像共享远程分支一样,运行 git push origin [tagname]。如果标签已经推送到远程,要删除远程标签就麻烦一点,比如,想删除 remove 标签,需要先从本地删除:git tag -d remove,然后,从远程删除,删除命令也是 push,但是格式如下:git push origin :refs/tags/remove。
3.8 查看提交历史 git log
3.8.1 git log
默认不用任何参数的话,git log 会按提交时间列出所有的更新,最近的更新排在最上面。 这个命令会列出每个提交的哈希值、作者的名字和电子邮件地址、提交时间以及提交说明。
3.8.2 git log --oneline
--oneline:将每个提交压缩为一行,是 "--pretty=oneline --abbrev-commit" 的简写形式。
3.8.3 其他常用选项(-p、-n、--stat、--pretty、--graph)
-p 选项:用来显示每次提交的内容差异。
-(n) 仅显示最近的 n 条提交。
--stat 选项:查看每次提交的简略的统计信息。--stat 选项在每次提交的下面列出所有被修改过的文件、有多少文件被修改了以及被修改过 的文件的哪些行被移除或是添加了。 在每次提交的最后有一个总结。
--pretty:指定使用不同于默认格式的方式展示提交历史。
--pretty 选项有 一些内建的子选项可供使用,比如 oneline 表示将每个提交放在一行显示,格式为 <hash> <title-line>;format:<format-string> 定制要显示的记录格式(如 %h 代表提交的简短哈希值,%s 代表提交说明消息,%an 代表作者名字,%cn 代表提交者名字,%cd 代表提交日期等);还有 short、medium、full、fuller 等内置格式。
如果要将输出保存到文件中,可以使用以下命令:
git log --pretty=format:"%H - %an: %s" > log.txt
-S:仅显示添加或移除了某个关键字的提交。
可以使用命令:git log -Sfunction_name,找出添加或移除了某一个特定函数的引用的提交。
3.8.4 git reflog
git log 显示的是提交历史,包括每个提交的作者、提交时间、提交信息等等。它可以用于查看项目的提交历史,了解项目的演进情况,以及查找特定提交的信息等等。
Git 会在后台保存一个引用日志 reflog,每当你的 HEAD 所指向的位置发生了变化,Git 就会将这个信息存储到引用日志这个历史记录里。 通过这些数据,你可以很方便地获取之前的提交历史,使用 git reflog 来查看引用日志。
git reflog 显示的是 Git 引用的历史,包括分支、标签、HEAD 等等。它记录了 Git 引用的变更历史,可以用于恢复误删的分支、标签等引用,或者查找误操作的历史记录等等。
3.9 撤销提交 git reset
git reset 可用于:
- 取消暂存,不影响工作区内容,对应下图 SYNOPSIS 1~3 语句。
- 取消提交:将当前分支的 HEAD 指针移动到指定的提交,同时可以选择是否修改工作区和暂存区的内容。对应下图 SYNOOSIS 第4条语句。
3.9.1 保留工作区修改撤销暂存(tree-ish 默认为 HEAD,即最近一次的提交)
应用场景:假设你对项目中的 frotz.c 文件做了一些修改,已经改好了,接下来需要修改其他文件,你不想在运行 git diff 时看到关于 frotz.c 的内容,这会分散你的注意力,所以你使用 add 命令把这部分修改暂存到暂存区。
这时有人告诉你其他项目成员做了一些有价值的修改,建议你拉取最新项目。不过你现在已经 ‘弄脏了’ 暂存区,即你当前的暂存区内容与 HEAD 不匹配(执行 git pull 之前,需要先将暂存区和 HEAD 同步,否则会出现冲突),而且你知道将要进行的 pull 操作不会改变 frotz.c 文件,因此你可以考虑先恢复 frotz.c 文件在暂存区的状态(撤销之前的暂存操作,但保留工作区文件的改动),然后再pull、merge。
git reset 也可以仅对一个文件撤销暂存操作:
3.9.2 撤销提交(commit 默认为 HEAD,即最近一次的提交)
-soft | 仅撤销提交,不撤销修改。 | 将 HEAD 指针移动到指定的提交,并将暂存区和工作区的修改保留下来,因此可以直接重新提交。 |
--mixed | 撤销提交和暂存区的修改,但不撤销工作区的修改。 | 将 HEAD 指针移动到指定的提交,并将暂存区的修改撤销,但不影响工作区的修改,需要手动重新标记为暂存后再次提交。 |
--hard | 彻底撤销提交和修改,回到指定提交的状态。 | 将 HEAD 指针、暂存区和工作区都移动到指定的提交,所有修改都会被撤销,慎用。 |
--merge | 撤销合并提交,回到合并前的状态。 | 撤销合并提交,回到合并前的状态,需要指定要回到的提交。 |
--keep | 撤销提交,但保留工作区和暂存区的修改。 |
可以通过回退实现撤销提交,这需要知道回到哪个版本:用 HEAD 表示当前版本,即最新的提交,上一个版本用 HEAD^ 表示,往上100个版本用 HEAD~100 表示;此外,如前所述 Git 会记录每一次提交的信息,可通过 git reflog 查看索引值,用索引值指定要回退的版本。概括来说:
- 索引值(前进后退都可以):使用 HEAD 或局部索引值(提交对象的简短哈希字符串,可通过 git reflog 或 git log --pretty=oneline 查看)如:git reset --hard 49e25b6
- ^ (只能后退):HEAD^^ 表示后退 2 步,如:git reset --hard HEAD^^
- ~ (只能后退):HEAD~n 表示后退 n 步,如:git reset --hard HEAD~n
--hard 永久删除(本地内容也会删除)
3.9.3 git restore 与 git reset
git restore:撤销工作区中未提交的修改(从暂存区或另一个提交恢复工作区中的文件);从另一次提交中恢复暂存区或\和工作区中的文件。
git reset:撤销已提交的修改。将 HEAD 指针和当前分支指向指定的提交,可选择将暂存区或\和工作区恢复到该提交的状态。
4. 分支管理
使用分支意味着你可以把你的工作从开发主线上分离开来,以免影响开发主线。
Git 处理分支的方式可谓是难以置信的轻量,创建新分支这一操作几乎能在瞬间完成,并且在不同分支之间的切换操作也是一样便捷。 与许多其它版本控制系统不同,Git 鼓励在工作流程中频繁地使用分支与合并,哪怕一天之内进行许多次。
4.1 分支简介
4.1.1 文件快照
Git 并不是像其他版本控制系统一样,把保存的信息看作是一组基本文件和每个文件随时间逐步累积的差异,Git 更像是把数据看作是对小型文件系统的一组快照,你可以理解为每个快照都包含了代码库中的所有文件和目录的完整内容。Git 对待数据更像是一个快照流,使用这些快照来跟踪文件的历史记录,并允许不同版本之间进行比较和回滚。
说明:Git 的快照是通过将文件的内容转换为一些称为 “blob” 的二进制数据对象来实现的。Git 中的每个文件都被视为一系列 blob 对象,每个 blob 对象都包含文件的内容,当你在 Git 中提交一个更改时,Git 会将这些更改的内容转换为新的 blob 对象,并将其存储在 Git 数据库中。Git 还会创建一个称为 “tree” 的对象,该对象包含指向每个 blob 对象的指针,以及目录结构信息。 这些 blob 和 tree 对象都可以被视为 Git 中的 “快照”,因为它们表示特定时间点的文件和目录结构。
在你每次提交更新,或在 Git 中保存项目状态时,Git 会对当时的全部文件制作一个快照并保存这个快照的索引。 为了高效, 如果文件没有修改,Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。
Git 中的绝大多数操作都只需要访问本地文件和资源,比如,如果 Git 要浏览项目的历史,它不需外连到服务器去获取历史,然后再显示出来,只需直接从本地数据 库中读取,因此你能立即看到项目历史。
4.1.2 验证文件
哈希是一种算法,它将输入的数据映射到一个固定长度的输出,通常是一个哈希值。在计算机科学中,哈希经常用于快速查找和存储数据。密码学中,哈希算法可以把明文加密成密文。
哈希函数如下特点:
(1)同一个哈希算法得到的加密结果长度固定。
(2)哈希算法确定、输入数据确定,输出数据不变;如果哈希算法有变化,或者输入数据变化(即便是很微小的变化),输出数据一定有变化,而且变化很大。输入数据的任何细微变化都会导致输出数据的巨大变化。
(3)哈希算法不可逆,不能通过哈希值恢复出原始数据。
哈希算法可以用于验证文件,Git 中,每个文件和每个提交都有一个唯一的校验和,是一个 SHA-1 哈希值(一个由 40 个十六进制字符(0-9 和 a-f)组成字符串,示例:24b9da6552252987aa493b52f8696cd6d3b00373),这个校验和是由文件内容和目录结构计算出来的,可以用来验证文件的完整性和一致性。Git 数据库中保存的信息都是以文件内容的哈希值来索引,而不是文件名。
4.1.3 Git 分支管理机制
提交对象 commit:包含指向前述树对象的指针和所有提交信息。
树对象 tree:树对象包含了每个文件和子目录的名称、类型、权限和 SHA-1 校验和等元数据信息。当我们提交一个新版本的代码时,Git 会将所有修改的文件和目录打包成一个新的树对象,并将其保存到 Git 数据库中。
文件快照 blob 对象:当前版本的文件快照。
在 Git 中,每次提交都会生成一个校验和,这个校验和是由 Git 对该提交中所有文件的内容计算出来的。如果你修改了某些文件并提交了这些修改,Git 会重新计算该提交的校验和,并将其与之前的校验和进行比较。如果校验和不同,Git 就会认为文件已经被修改过了,并提示你进行提交。
当你做了一些修改后再次提交,这次产生的提交对象会包含一个指向上次提交对象(父对象)的指针。
Git 的分支本质上是指向提交对象的可变指针。 Git 的默认分支名字是 master。 在多次提交操作之 后,你其实已经有一个指向最后那个提交对象的 master 分支,它会在每次的提交操作中自动向前移动。
Git 的 master 分支并不是一个特殊分支。 它就跟其它分支完全没有区别。 之所以几乎每一个仓库都有 master 分支,是因为 git init 命令默认创建它,并且大多数人都懒得去改动它。
Git 怎么知道当前在哪一个分支上呢? 也很简单,它有一个名为 HEAD 的特殊指针。 在 Git 中,HEAD 是一个指针,指向当前所在的本地分支(注:可以将 HEAD 想象为当前分支的别名)。
首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象,由多个分支合并产生的提交对象有多个父对象。
4.2 分支的查看、创建、切换、删除
4.2.1 查看分支 git branch (选项 -v、-r、-a、--merged、--no-merged)
果不加任何参数运行 git branch,会得到当前所有分支的一个列表,* 字符代表当前所处的那一个分支(当前 HEAD 指针所指向的分支)。
If --list is given, or if there are no non-option arguments, existing branches are listed; the current branch will be highlighted in green and marked with an asterisk.
使用 git branch -v 显示本地分支列表,并显示每个分支的最新提交的 SHA-1 值和提交信息。
git branch -r 显示远程跟踪分支列表。
git branch -a 显示所有分支列表,包括本地分支和远程跟踪分支。
使用 git branch --merged 查看哪些分支已经合并到当前分支。在这个列表中分支名字前没有 * 号的分支通常可 以使用 git branch -d 删除掉;你已经将它们的工作整合到了另一个分支,所以并不会失去任何东西。
使用 git branch --unmerged 查看所有包含未合并工作的分支。因为这个列表中的分支包含了还未合并的工作,尝试使用 git branch -d 命令删除这些分支时会失败。
下面的例子中,没有所有分支都已合入到 master 分支。
4.2.2 创建分支 git branch <branchname>
Git 中,创建分支,即为创建一个可以移动的新的指针。比如,创建一个 testing 分支, 只需要使用 git branch 命令:git branch testing,这会在当前所在的提交对象上创建一个指针。git branch 命令仅仅创建一个新分支,并不会自动切换到新分支中去,因此当前你仍然在 master 分支上。
4.2.3 切换分支 git checkout 和 git switch
(1)git checkout
使用 git checkout 命令要切换到一个已存在的分支,比如,现在要切换到新创建的 testing 分支 去: git checkout testing,这样 HEAD 就指向 testing 分支了。
如果现在对项目中某文件做些修改,然后再提交一次,testing 分支将会向前移动,但 master 分支不动,仍然指向运行 git checkout 时所指的对象。
使用命令 git checkout master,切换回 master 分支。这条命令做了两件事:(1)使 HEAD 指回 master 分支;(2)是将工作目录恢复成 master 分支所指向的快照内容。也就是说,如果现在做修改,是始于一个较旧的版本,本质上来讲,这就是忽略 testing 分支所做的修改,便于向另一个方向进行开发。
分支切换会改变你工作目录中的文件。在切换分支时,一定要注意你工作目录里的文件会被改变。 如果是切换到一个较旧的分支,工作目录会恢复到该分支最后一次提交时的样子。 如果 Git 不能干净利落地完成这个任务, 它将禁止切换分支。
你可以简单地使用 git log 命令查看分叉历史。 运行 git log --oneline --decorate --graph --all,它会输出你的提交历史、各个分支的指向以及项目的分支分叉情况。
(2)git switch
The command "git branch <branchname>" will create the new branch, but it will not switch the working tree to it; use "git switch <newbranch>" to switch to the new branch.
在 Git 2.23 版本之前,git checkout 用于切换分支、恢复文件、创建新分支等多种操作的命令。但是,从 Git 2.23 版本开始,它被拆分成了多个命令,以避免使用时的混淆。现在,git checkout 命令主要用于切换分支和恢复文件。例如,git checkout master 将当前分支切换到 master 分支,git checkout -- file.txt 将 file.txt 文件恢复到最近一次提交的状态。
从 Git 2.23 版本开始,git switch 命令被引入用于切换分支。它的使用方式与 git checkout 类似,但是更加直观和简洁。例如,git switch master 将当前分支切换到 master 分支。
如果你使用的是 Git 2.23 及以上版本,建议使用 git switch 命令来切换分支,以避免混淆。如果你使用的是较老的 Git 版本,可以继续使用 git checkout 命令,但需要注意其多种用法可能会导致一些意外情况。
4.2.4 创建并切换到此分支 git checkout -b <branchname>
git checkout -b 命令用于创建一个新的分支并切换到该分支(-b 参数表示创建一个新的分支)。比如,带有 -b 参数的 git checkout 命令:git checkout -b iss53,实际上是下面两条命令的简写:
$ git branch iss53
$ git checkout iss53
执行该命令后,Git 会将当前分支的代码复制到新的分支中,并将 HEAD 指向新的分支,你可以在新的分支上继续开发代码,而不影响当前分支的代码。
4.2.5 删除分支 git branch -d <branchname>
删除未合并的分支需要使用 git branch -D;删除已合并的分支需要使用 git branch -d。
4.3 分支的合并 git merge、git cherry-pick
使用 git merge 合并分支:先 check out 到想合并入的分支,然后执行 git merge。
Case 1:把 hotfix 分支合并入 master 分支
假设在当前项目中,先从 master 分支创建了一个 hotfix 分支,并在在 hotfix 分支提交了一些修改,现在想把 hotfix 分支修改的内容合并到 master。
提交历史(checkout 到 master,然后执行 git merge):
你应该注意到了运行结果中的 "快进(fast-forward)"这个词。 由于当前 master 分支所指向的提交是 hotfix 分支的提交的直接上游,所以 Git 只是简单的将指针向前移动。 即:当你试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候,只会简单的将指针向前推 进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward)”。
Case 2:把 iss53 分支合并入 master 分支
hotfix 分支上所做的工作并没有包含到 iss53 分支中,如果需要拉取 hotfix 所做的修改,可以使用 git merge master 命令将 master 分支合并入 iss53 分支;或者你也可以等到 iss53 分支完成其使命, 再将其合并回 master 分支。
在 Case 1 中,master 已经合入了 hotfix 分支的内容,快照流图示如下:
如果在 iss53 分支上的工作完成了,可以考虑将其合并入 master 分支 。合并 iss53 分支到 master 分支与之前合并 hotfix 分支所做的工作差不多,只需要 checkout 到你想合并入的分支,然后运行 git merge 命令。
可以注意到,返回结果与之前合并 hotfix 分支的时候看起来有一点不一样,这是因为,iss53 分支是从更早的地方分叉开的(diverged),master 分支已经前进,master 所在提交已经不是 iss53 分支所在提交的直接祖先。 出现这种情况的时候,Git 会使用两个分支的末端所指的快照(C4 和 C5 )以及这两个分支的工作祖先(C2),做一个简单的三方合并。
Case 3:存在冲突
如果在两个不同的分支中,对同一个文件的同一个部分进行了不同的修改,Git 就没法干净的合并它们。 比如,在上述例子中,如果 iss53 和 hotfix 两个分支对同一个文件的同一处做了修改,合并它们的时候就会产生合并冲突。
看一个简单的例子,项目的提交历史如下:
# 在 master 分支新建一个 file1.txt 文件
$ git add file1.txt
$ git commit -m "master create file1.txt"
# 新建 file2 分支,在这个分支上新建了 file2.txt 文件,同时对 file1.txt 追加了一行内容
$ git checkout -b file2
$ git add file2.txt
$ git commit -a -m "create file2 and file1 add a line"
# 回到 master分支,对 file1.txt 文件追加一行内容
$ git checkout master
$ git commit -a -m "file1 add a line"
# 把 file2 分支的修改内容并入到 master 分支
$ git branch -v
$ git merge file2
在当前的项目中,有 master 和 file2 两个分支,都在 file2.txt 的末尾追加了一段文字,现在想合并这两个分支:先 checkout 到 master,然后运行 git merge poem-1 命令。 由于两个分支在相同的位置做了修改,存在冲突,合并时不知道采用哪一个,合并失败:
任何因存在合并冲突而有待解决的文件,都会以未合并状态标识出来,你可以在合并冲突后的任意时刻使用 git status 命令,查看那些因存在合并冲突而处于未合并 (unmerged)状态的文件。Git 会在有冲突的文件中加入标准的冲突解决标记,这样你可以打开这些包含冲突的文件,然后手动解决冲突。
当前的例子中,两个分支都对 file2.txt 文件做了修改,打开这个文件,显示内容如下:
Git 用<<<<<<<,=======,>>>>>>> 标记出不同分支的内容,上述信息表示:HEAD 指示的版本,在 ======= 的上半部分,而 poem-1 指示的版本在 ======= 的下半部分。
为了解决冲突,你可以选择使用由 ======= 分割的两部分中的一个,或者自行合并这些内容。根据实际需要确定冲突解决方案,完成修改后把 >>>>>>,======= 和 >>>>>>> 这些行完全删除。
在解决了所有文件里的冲突之后,对每个文件使用 git add 命令来将其标记为冲突已解决。 一旦暂存这些原本有冲突的文件,Git 就会将它们标记为冲突已解决,然后再执行 commit 提交即可。
Case 4:储藏现场 git stash
git stash 命令可以将当前工作目录中未提交的修改 “储藏” 起来,具体使用方法如下:
- 在工作目录中进行一些修改;
- 运行 git stash 命令,将修改储藏起来;
- 在进行其他操作之前,可以运行 git stash list 命令查看所有储藏;
- 使用 git stash apply 命令恢复储藏内容;
- 使用 git stash drop 命令删除储藏。
假设你当前有 master 和 dev 两个分支,你临时接到紧急任务,准备新建一个 issue-101 分支修复一个 bug,但是你在 dev 上进行的工作还没有做完,还不能提交,这时,你可以用 git stash,把当前的工作现场储藏起来,等以后恢复现场继续工作。
运行过程:
# 保存现场
$ git stash
Saved working directory and index state WIP on dev: f52c633 add merge
# 现在,用 git status查看工作区,是干净的(除非有没有被Git管理的文件)
# 接下来创建分支修复 bug
$ git checkout master
$ git checkout -b issue-101
$ git commit -m "fix bug 101"
$ git switch master
$ git merge --no-ff -m "merged bug fix 101" issue-101
# 恢复现场
$ git switch dev
$ git stash list
stash@{0}: WIP on dev: f52c633 add merge
# 工作现场还在,Git 把 stash 内容存在某个地方了,需要恢复:
# 方法1:用 git stash apply 恢复,恢复后 stash 内容并不删除,需要用 git stash drop 删除;
# 方法2:用 git stash pop,恢复的同时把 stash 内容删除。
$ git stash pop
$ git stash list
# 此时将看不到任何 stash 内容
Case 5:挑选提交合并 git cherry-pick <commitHash>
git merge 可以将两个或多个分支合并成一个新的分支,它会将两个分支的修改内容合并到一起,并生成一个新的提交记录。git merge 会自动解决冲突,但有时会出现无法自动解决的冲突,需要手动解决。
如果你想将一个分支上的某个或某几个提交记录,复制到当前分支上,比如,要解决某个 bug 或者应用某个特定的功能,可以使用 git cherry-pick。
git cherry-pick 可以选择性地将某个提交记录合并到当前分支,而不会像 git merge 一样将整个分支合并。git cherry-pick 将一个或多个提交应用到当前分支。它的作用是将指定的提交复制一份到当前分支上,相当于手动合并某个提交。
更多细节参考链接:Git 操作补充:cherry-pick、变基-CSDN博客
Case 6:变基 git rebase
在 Git 中,整合来自不同分支的修改,除了 merge,还有一种方法,变基 rebase。它可以将一个分支上的修改合并到另一个分支上。具体来说,Git 变基的操作是将当前分支的提交 “复制” 到目标分支上,然后将当前分支的指针指向目标分支的最新提交。这个过程中,Git 会尝试将当前分支上的提交应用到目标分支上,如果发现冲突,需要手动解决冲突。
与 Git 合并 merge 不同,Git 变基可以重新定义提交历史,使得提交历史更加整洁和易于理解。Git 变基的优点是可以保持提交历史的清晰,减少不必要的合并提交,从而更好地追踪代码的变化。不过,由于 Git 变基会修改提交历史,因此需要谨慎使用,避免对已经共享的分支造成影响。
更多细节参考链接:Git 操作补充:cherry-pick、变基-CSDN博客
4.5 分支开发工作流
* 长期分支
Git 使用简单的三方合并,所以就算在一段较长的时间内,反复把一个分支合并入另一个分支,也不是什么难事。 也就是说,在项目开发周期内,你可以同时拥有多个开放的分支,定期地把某分支合并入其他分支中。
使用多个长期分支的方法并非必要,但是这么做通常很有帮助,尤其是当你在一 个非常庞大或者复杂的项目中工作时,这样做可以使你的分支具有不同级别的稳定性,当其具有一定程度的稳定性后,再把它合并入具有更高级别稳定性的分支中。
例子:只在 master 分支上保留完全稳定的代码——有可能仅仅是已经发布或即将发布的代码。 他们还有一些名为 develop 或者 next 的平行分支,被用来做后续开发或者测试稳定性——这些分支不必保持绝对稳定,但是一旦达到稳定状态,它们就可以被合并入 master 分支了。
* 特性分支(短期分支)
特性分支对任何规模的项目都适用。 特性分支是一种短期分支,它被用来实现单一特性或其相关工作。
例子:在 master 分支上工作到 C1,这时为了解决一个问题 A 而新建 iss91 分支,在 iss91 分 支上工作到 C4,然而对于问题 A 你又有了新的想法,于是你再新建一个 iss91v2 分支试图用另一种方法解决它,接着你回到 master 分支工作了一会儿,你又冒出了一个不太确定的想法,想为项目增加了新功能,便在 C10 的时候新建一个 dumbidea 分支,并在上面做些实验。你的提交历史看起来像下面这个样子:
你最终决定使用在 iss91v2 分支中方案解决问题 A,同时,你将 dumbidea 分支拿给你的同事看,大家都觉得这部分内容值得保留,这时你可以抛弃 iss91 分支(丢弃 C5 和 C6 提交),然后把另外两个分支合并入主干分支。最终你的提交历史看起来像下面这个样子:
5. 远程仓库 remote repository
5.1 相关概念
5.1.1 远程仓库 remote repository
Git 的远程仓库是指位于网络上的 Git 仓库(托管在因特网或其他网络中的项目版本库)。
它可以是位于本地局域网内的另一台计算机,也可以是位于云端的服务器。Git 的远程仓库可以让多个开发者在不同的地方协同开发同一个项目,通过上传和下载代码来实现代码的共享和同步。
你可以有好几个远程仓库,通常有些仓库对你只读,有些则可以读写。 与他人协作涉及管理远程仓库以及根据需要推送(push)或拉取(fetch)数据。
5.1.2 远程分支 remote branch 和远程跟踪分支 remote-tracking branch
* 远程分支
远程分支即远程仓库上的分支,要在 Git 中创建远程分支,需要使用 git push 命令。以下是创建远程分支的步骤如下:
- 在本地创建一个新的分支,可以使用 git checkout -b 命令创建并切换到新分支。
- 在本地分支上进行了所需的更改。
- 使用 git push 命令将本地分支推送到远程仓库。例如,远程仓库名为 origin,可以使用以下命令将本地分支 mybranch 推送到远程分支:git push origin mybranch,如果远程仓库不存在此分支,Git 会自动创建它。
- 使用 git branch -r 命令查看远程分支列表,新远程分支应该出现在列表中。
注意: 在推送分支之前,需要确保你有足够的权限在远程仓库中创建分支,如果没有权限,需要联系仓库管理员以获取帮助。
git push 的语法规则可概括为:git push <远程仓库名> <本地分支名>:<远程分支名>
如果你的本地分支与远程分支同名,则可以省略远程分支名。可以使用带冒号 “:” 的格式来推送本地分支到一个命名不相同的远程分支,比如,不想让远程仓库上的分支叫做 serverfix,可以运行 git push origin serverfix:awesomebranch,将本地的 serverfix 分支推送到远程仓库上的 awesomebranch分支。
可以运行带有 --delete 选项的 git push 命令来删除一个远程分支,比如,想要从服务器上删除 serverfix 分支,运行命令:git push origin --delete serverfix
* 远程跟踪分支
执行 clone 后,远程仓库的分支会被克隆到本地,但是本地仓库中的分支与远程仓库中的分支的发展不是完全同步的。为了跟踪远程仓库中的分支,Git 会在本地仓库中创建一个名为 remotes/remote-name/branch-name 的分支,这个分支就是 remote-tracking branch。
remote branch 是指远程仓库上的分支。
remote-tracking branch 远程跟踪分支是远程分支状态的引用。它们是你不能移动的本地引用,当你做任何网络通信操作时,它们会自动移动。 远程跟踪分支像是你上次连接到远程仓库时,那些分支所处状态的书签。它们以 (remote)/(branch) 形式命名。 比如,如果你想要看你最后一次与远程仓库 origin 通信时 master 分支的状态,你可以查看 origin/master 分支。
可以通过 git branch -r 命令来查看所有远程仓库的分支,通过 git branch -a 命令来查看所有本地分支和远程分支。
clone 远程仓库:使用 clone 命令克隆了一个仓库,命令会自动将其添加为远程仓库并默认以 origin 为简写。即 Git 的 clone 命令会自动将这个远程仓库命名为 origin,拉取它的所有数据,创建一个指向此远程仓库 master 分支的指针,并且在本地将其命名为 origin/master(远程跟踪分支),Git 也会给你一个与 origin 的 master 分支在指向同一个地方的本地 master 分支(跟踪分支),这样你就有工作的基础。
远程仓库的名字 origin 与分支名字 master 一样,在 Git 中并没有任何特别的含义。 master 是运行 git init 时默认的起始分支名字, origin 是运行 git clone 时默认的远程仓库名字。 如果运行 git clone -o booyah,那么默认的远程分支名字将会是 booyah/master。
如果你在本地的 master 分支做了一些工作,然而在同一时间,其他人推送提交到远程仓库,更新了它的 master 分支,那么你的提交历史将向不同的方向前进,但只要你不与 origin 服务器连接,你的 origin/master 指针就不会移动。
5.1.3 跟踪分支
从一个远程跟踪分支检出一个本地分支会自动创建一个 “跟踪分支”(有时候也叫做 “上游分支”)。 跟踪分支是与远程分支有直接关系的本地分支。 如果在一个跟踪分支上输入 git pull,Git 能自动地识别去哪个服务器上抓取、合并到哪个分支。 当克隆一个仓库时,它通常会自动地创建一个跟踪 origin/master 的 master 分支。
如果需要的话,可以通过运行 git checkout -b [branch] [remotename]/[branch] 设置其他跟踪分支、跟踪此仓库的其他分支或者为其他仓库设置跟踪分支,这是一个十分常用的操作所以 Git 提供了 --track 快捷方式。
$ git checkout -b serverfix origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.Switched to a new branch 'serverfix'
此命令将创建一个用于工作的本地分支 serverfix,并且起点位于 origin/serverfix。
$ git checkout --track origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'
本地分支与远程分支设置为不同名字,本地分支 sf 会自动从 origin/serverfix 拉取
$ git checkout -b sf origin/serverfix
Branch sf set up to track remote branch serverfix from origin.
Switched to a new branch 'sf'
可以使用带有 -u 或 --set-upstream-to 选项的 git branch 显式地设置已有的本地分支跟踪一个刚刚拉取下来的远程分支,或者修改正在跟踪的上游分支。
$ git branch -u origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
总结:Git 跟踪分支是指本地仓库中的分支,是基于本地仓库中的提交记录创建的,可以在本地进行修改、合并等操作。远程跟踪分支是指远程仓库中的分支,它们是基于远程仓库中的提交记录创建的,不能直接在本地进行修改、合并等操作,需要通过拉取或推送远程分支来进行操作。
5.2 查看远程仓库 git remote -v
git remote 显示当前仓库所关联的远程仓库。
git remote -v 显示详细的远程仓库信息,包括远程仓库的名称、fetch 和 push 的URL地址。
git remote show <remote-name> 查看某一个远程仓库的更多信息。
5.3 添加新的远程仓库 git remote add <shortname> <url>
可以使用 git remote add <shortname> <url> 命令添加一个新的远程 Git 仓库,并为其指定一个方便引用的简称。
可以使用 git remote rename 修改远程仓库的简写名,即远程仓库重命名,比如,想要将 pb 重命名为 paul,可以运行:git remote rename pb paul,值得注意的是,这同样也会修改你的远程分支名字,过去那些引用 pb/master 的现在会引用 paul/master。
可以使用 git remote rm 移除远程仓库,如:git remote rm paul
5.4 从远程仓库拉取 git fetch <remote-name> 和 git pull <remote-name>
5.4.1 git fetch
如果要同步你的工作,运行 git fetch origin 命令。 这个命令查找 origin 这个名字对应哪一个服务器,从中抓取本地没有的数据,并且更新本地数据库,移动 origin/master 指针指向新的、更新后的位置。
$ git remote
origin
$ git remote add pb https://github.com/paulboone/ticgit
$ git remote -v
origin https://github.com/schacon/ticgit (fetch)
origin https://github.com/schacon/ticgit (push)
pb https://github.com/paulboone/ticgit (fetch)
pb https://github.com/paulboone/ticgit (push)
$ git fetch pb
remote: Counting objects: 43, done.
remote: Compressing objects: 100% (36/36), done.
remote: Total 43 (delta 10), reused 31 (delta 5)
Unpacking objects: 100% (43/43), done.
From https://github.com/paulboone/ticgit
* [new branch] master -> pb/master
* [new branch] ticgit -> pb/ticgit
5.4.2 git pull
当 git fetch 命令从服务器上抓取本地没有的数据时,git fetch 会将远程仓库的最新代码下载到本地,但它并不会修改工作目录中的内容,它只会获取数据然后让你自己合并,想要更新本地分支,你需要手动执行 git merge 或者 git rebase 命令来合并最新代码。
通常来讲,git pull 的含义是一个 git fetch 紧接着一个 git merge 命令。 如果有一个设置好的跟踪分支(不管它是显式设置的还是通过 clone 或 checkout 命令创建的),git pull 会查找当前分支所跟踪的服务器与分支,从服务器上抓取数据然后尝试合并入那个远程分支。
总的来说,git fetch 更加安全,因为它不会自动合并代码,你可以在合适的时候手动合并;而 git pull 更加方便,因为它可以自动合并代码,让你更快地获取最新的代码。
5.5 本地分支推送到远程仓库 git push
推送分支,就是把该分支上的所有本地提交推送到远程库。建议在 git push 之前先执行 git pull 命令,以确保本地代码库与远程代码库同步。
如果远程代码库中已经存在一些新的提交,而你本地的代码库没有进行更新,那么你的 push 操作会被拒绝,因为你的本地代码库与远程代码库不一致。
推送时,要指定本地分支,比如:运行 git push origin master,Git 会把 master 分支推送到远程库对应的远程分支上。如果要推送其他分支,比如 dev,就用:git push origin dev。
多人协作小结:
- 首先,可以试图用 git push origin <branch-name> 推送自己的修改;
- 如果推送失败,则因为远程分支比你的本地更新,需要先用 git pull 试图合并;
- 如果合并有冲突,则解决冲突,并在本地提交;
- 没有冲突或者解决掉冲突后,再用 git push origin <branch-name> 推送。
如果 git pull 提示 no tracking information,则说明本地分支和远程分支的链接关系没有创建,用命令 git branch --set-upstream-to <branch-name> origin/<branch-name>。
6. 问题记录
6.1 添加 remote 信息时报错:unable to get local isser certificate(服务器上SSL证书未经过第三方机构认证)
git config --global http.sslverify false
6.2 git pull fatal: refusing to merge unrelaterd histories
git pull origin master --allow-unrelated-histories
6.3 Pycharm 侧边栏没有 Commit
Settings -> Version Control -> Commit -> 勾选 use non-modal commit interface -> Apply
7. 参考链接
Git安装-Git入门-CSDNGit技能树
Pro Git:Git - Book