Git基础学习

基本都参考自 Git-book

什么是版本控制

本地版本控制系统:采用某种简单的数据库来记录文件的历次更新差异。

缺点:无法让不同系统上的开发者协同工作。

集中化的版本控制系统:一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。

优点:每个人都可以在一定程度上看到项目中的其他人正在做些什么。 而管理员也可以轻松掌控每个开发者的权限,并且管理一个 CVCS 要远比在各个客户端上维护本地数据库来得轻松容易。

缺点:中央服务器的单点故障,有丢失所有历史更新记录的风险。

分布式版本控制系统:客户端并不只提取最新版本的文件快照, 而是把代码仓库完整地镜像下来,包括完整的历史记录。这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。

Git 的特点

  1. 直接记录快照,而非差异比较。

    基于差异的版本控制:

    Git 对待数据更像是一个快照流

  2. 近乎所有操作都是本地执行

    速度快,离线的时候也能操作。

  3. Git 保持完整性

    Git 所有数据在存储前都计算校验和,然后以校验和来引用。这意味着不可能在 Git 不知情时更改任何文件内容或目录内容。

    Git 计算校验和的机制是 SHA-1 Hash。

  4. Git 一般只添加数据

    Git 操作几乎只往 Git 数据库中添加数据。 你很难使用 Git 从数据库中删除数据,也就是说 Git 几乎不会执行任何可能导致文件不可恢复的操作。

  5. 三种状态

    文件有三种状态:

    • 已修改(modified):已修改表示修改了文件,但还没保存到数据库中。
    • 已暂存(staged):已暂存表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中。
    • 已提交(committed):已提交表示数据已经安全地保存在本地数据库中。

    因而,Git 项目拥有三个阶段:工作区、暂存区和(本地)Git 仓库。

    基本的 Git 工作流程如下:

    1. 在工作区中修改文件。
    2. 将你想要下次提交的更改选择性地暂存,这样只会将更改的部分添加到暂存区。
    3. 提交更新,找到暂存区的文件,将快照永久性存储到 Git 目录。

Git 配置

可以使用 git config 命令来设置一些配置变量,这些变量存储在:

  • /etc/gitconfig 文件:系统上每一个用户的通用配置。执行 git config 时带上 --system 选项,需要管理员或超级用户权限。
  • ~/.gitconfig~/.config/git/config 文件:只针对当前用户。传递 --global 选项,对系统上所有的仓库生效。
  • 当前使用仓库的 Git 目录中的 config 文件(即 .git/config):针对该仓库。 传递 --local 选项(其实默认就是它)。

每一个级别的设置会覆盖上一级别的设置。

以下命令查看所有的配置以及它们所在的文件:

1
$ git config --list --show-origin


安装完 Git 之后要设置用户名和邮箱,它们会写入到每一次提交中,不可更改

1
2
$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com

这里使用了 --global 选项,因此对系统上所有仓库有效。

获取 Git 仓库

有两种获取 Git 项目的方式:

  • 将尚未进行版本控制的本地目录转换为 Git 仓库:

    1
    $ git init

    该命令创建一个名为 .git 的子目录,含有初始化的 Git 仓库中所有的必须文件。

  • 从其他服务器上 clone 一个已存在的 Git 仓库:

    1
    $ git clone https://github.com/libgit2/libgit2

    可以通过额外的参数在 clone 的时候自定义本地仓库的名字:

    1
    $ git clone https://github.com/libgit2/libgit2 mylibgit

    上面的例子使用的是 https:// 协议,也可以使用 git:// 协议或者 SSH 传输协议。

记录每次更新到仓库

工作目录下的每一个文件都不外乎这两种状态:已跟踪未跟踪。已跟踪的文件是指那些被纳入了版本控制的文件,在上一次快照中有它们的记录,在工作一段时间后, 它们的状态可能是未修改,已修改或已放入暂存区。简而言之,已跟踪的文件就是 Git 已经知道的文件。

检查当前文件状态

使用 git status 命令查看哪些文件处于什么状态,git status -sgit status --short 输出更紧凑的格式。

跟踪新文件

使用 git add 命令跟踪新文件,参数是文件或目录的路径,如果参数是目录的路径,则该命令将递归地跟踪该目录下的所有文件。

暂存已修改文件

git add 还可以用来把已修改的文件放入暂存区。事实上它是一个多功能命令,理解为“精确地将内容添加到下一次提交中”。

忽略文件

文件 .gitignore 列出无需纳入 Git 管理的文件,格式规范如下:

  • 所有空行或者以 # 开头的行都会被 Git 忽略。
  • 可以使用标准的 glob 模式匹配,它会递归地应用在整个工作区中。
  • 匹配模式可以以(/)开头防止递归。
  • 匹配模式可以以(/)结尾指定目录。
  • 要忽略指定模式以外的文件或目录,可以在模式前加上叹号(!)取反。

glob 模式:shell 所使用的简化了的正则表达式

  • 星号(*)匹配零个或多个任意字符;
  • [abc] 匹配任何一个列在方括号中的字符 (这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);
  • 问号(?)只匹配一个任意字符;
  • 如果在方括号中使用短划线分隔两个字符, 表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字);
  • 使用两个星号(**)表示匹配任意中间目录,比如 a/**/z 可以匹配 a/za/b/za/b/c/z 等。

查看修改

使用 git diff 命令(不带任何参数)比较工作目录中当前文件和暂存区快照之间的差异,也即修改之后还为暂存的变化内容。

使用 git diff --staged 或者 git diff --cached--staged--cached 是同义词)比较已暂存文件与上一次提交的文件的差异。

提交更新

使用 git commit 命令会启动文本编辑器来输入提交说明。默认的提交信息包含最后一次运行 git status 的输出(在注释行中)。

也可以使用 -m 选项将提交信息和命令放在同一行。

提交后 Git 会输出,当前是在哪个分支提交的,本次提交的完整 SHA-1 校验和是什么,以及在本次提交中,有多少文件修订过,多少行添加和删改过。

跳过暂存区

git commit 加上 -a 选项,Git 就会自动把所有已跟踪的文件暂存起来一并提交,从而跳过 git add 步骤。

移除文件

使用 git rm 命令可以从工作区暂存区中一并删除指定的文件。

如果只是从工作区手动删除文件,则运行 git status 会在 “Changes not staged for commit” 部分看到相应信息。这时再运行 git rm 可以记录下此次删除文件的操作。如果要删除之前修改过或已经放入暂存区的文件,则必须使用强制删除选项 -f。这是一种安全特性,用于防止误删尚未添加到快照的数据,这样的数据不能被 Git 恢复。

使用 git rm --cached 命令只从暂存区删除指定文件,文件依然保留在工作区。

移动文件

git mv file_from file_to

查看提交历史

git log 命令按时间先后顺序列出所有的提交(SHA-1 校验和、作者名字和邮箱、提交时间以及提交说明)。

git log -pgit log --patch 会显示每次提交所引入的差异,也可以限制显示的日志条目数量,例如使用 -2 选项来只显示最近的两次提交。

git log --stat 可以附带上每次提交的简略统计信息(列出所有被修改的文件、有多少文件被修改了以及被修改的文件的哪些行被移除或是添加了)。

--pretty 选项使用不同于默认格式的方式展示提交历史。它有一些内置的子选项,例如 onelineshortfullfullerformat 等。

--graph 选项能形象地展示分支、合并的历史,与 onelineformat 结合使用更佳。

撤销操作

如果提交完了才发现漏掉了几个文件没有添加,或者提交信息写错了,可以用 git commit --amend 重新提交。这个命令会将暂存区中的文件提交。 如果自上次提交以来你还未做任何修改(例如,在上次提交后马上执行了此命令), 那么快照会保持不变,而你所修改的只是提交信息。

取消暂存的文件:使用 git reset HEAD <file>... 来取消暂存(执行 git status 后有提示)。

撤销对文件的修改:使用 git checkout -- <file>... 恢复工作区文件到上一次提交的样子(执行 git status 后有提示)。

远程仓库的使用

查看远程仓库

使用 git remote 命令查看已经配置的远程仓库服务器,它会列出指定的每一个远程服务器的简写。clone 下来的仓库服务器默认名字为 origin。

使用 git remote -v 可以显示简写和对应的 URL。

添加远程仓库

使用 git remote add <shortname> <url> 添加一个新的远程仓库,同时指定一个简写。

从远程仓库 fetch 与 pull

使用 git fetch <remote> 拉取所有你还没有的数据。执行完成后,你将会拥有那个远程仓库中所有分支的引用,可以随时合并或查看。git fetch 命令只会将数据下载到你的本地仓库——它并不会自动合并或修改你当前的工作。

如果当前分支设置了跟踪远程分支,那么可以用 git pull 命令来自动抓取后合并该远程分支到当前分支。

推送到远程仓库

git push <remote> <branch>

查看某个远程仓库

使用 git remote show <remote> 查看某一个远程仓库的更多信息。

远程仓库的重命名和移除

使用 git remote rename 修改某一个远程仓库的简写名。

使用 git remote removegit remote rm 移除一个远程仓库。

Git 分支

新建分支

为了形象地说明,假设我们暂存了三个文件,则 Git 会为每一个文件计算校验和,然后将当前版本的文件快照用 blob 对象保存下来;commit 时,Git 会计算每一个子目录的校验和,然后将这些校验和保存为 tree 对象。随后 Git 创建一个 commit 对象,包含指向上述 tree 对象的指针。

做一些修改后再次提交,则这次产生的 commit 对象包含指向上一次的 commit 对象(父对象)的指针。

Git 分支的本质是指向提交对象的可变指针。Git 默认分支名字是 master,在多次提交操作之后,你其实已经有一个指向最后那个提交对象的 master 分支。 master 分支会在每次提交时自动向前移动。

使用 git branch <newbranchname> 命令创建一个分支,本质是创建了一个可移动的指针。为了知道当前在哪一个分支上,Git 维护一个特殊的 HEAD 指针,指向当前所在的本地分支。

使用 git log --decorate 可以查看各个分支当前所指的对象。

使用 git checkout 切换到一个已存在的分支,本质是将 HEAD 指针切换到分支指针上。这时再提交内容时,HEAD 指针指向的那个分支指针向前移动,而其他分支指针不移动:

再使用 git checkout 回到 master 分支时,除了 HEAD 指针指回 master 分支,工作区也会恢复成 master 分支指向的快照内容。这时再次修改,则提交历史会产生分叉

git log --oneline --decorate --graph --all 会输出你的提交历史、各个分支的指向以及项目的分支分叉情况。

注意,git branch 只创建分支而不切换,需要再执行 git checkout。如果想创建之后立即切换,可以使用 git checkout -b <newbranchname> 命令。

使用 git branch -d 删除分支。

分支的合并

使用 git merge 命令合并分支,假设当前在 A 分支,则 git merge B 会将 B 分支的内容合并到 A 中。如果 B 是 A 的直接后继,则 Git 要做的事情仅仅是将 A 指针移动到 B 指针相同的地方,这被称做 fast-forward;如果合并的两个分支有分叉,则 Git 会对待合并的两个分支以及它们的公共祖先做一个简单的三方合并:

Git 会对三方合并的结果做一个快照并创建一个新的提交指向它,这被称做一个合并提交,它不只有一个父提交。

有冲突的分支合并

如果两个不同的分支对同一个文件的同一个部分进行了不同的修改,合并时就会产生冲突。此时 Git 做了合并,但是没有自动地创建一个新的合并提交。Git 会暂停下来,等待你去解决合并产生的冲突。你可以在合并冲突后的任意时刻使用 git status 命令来查看那些因包含合并冲突而处于未合并(unmerged)状态的文件。

解决冲突后可以输入 git commit 来完成合并提交。

分支管理

不带任何参数的 git branch 会给出当前所有分支的列表,* 标识当前分支(HEAD 指针指向的分支)。

git branch -v 命令可以查看每一个分支的最后一次提交。

git branch --merge 查看哪些分支已经合并到当前分支,git branch --no-merged 查看尚未合并到当前分支的分支。

远程分支

远程跟踪分支以 <remote>/<branch> 的形式命名。正如前文所说,clone 的远程服务器默认取名为 origin

假设你在本地的 master 分支上做了一些工作,而其他人 push 到 origin 并更新了它的 master 分支。但只要你保持不与 origin 服务器连接(并拉取数据),你的 origin/master 指针就不会移动。

git fetch <remote> 命令抓取 remote 服务器上本地没有的数据并更新本地数据库,移动 origin/master 指针到更新之后的位置:

推送

git push <remote> <branch>

git push <remote> <localbranchname>:<remotebranchname>

删除远程分支

git push <remote> --delete <branch>

变基

在 Git 中整合来自不同分支的修改主要有两种方法:merge 以及 rebase

假设有这样的分支历史:

变基的操作是:首先 checkoutexperiment 分支,然后 git rebase master

它的原理是首先找到这两个分支(即当前分支 experiment、变基操作的目标基底分支 master) 的最近共同祖先 C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件, 然后将当前分支指向目标基底 C3,最后以此将之前另存为临时文件的修改依序应用。


Git基础学习
https://xyfjason.github.io/blog-main/2022/01/10/Git基础学习/
作者
xyfJASON
发布于
2022年1月10日
许可协议