Len's Study-Log

集中一点,登峰造极!

0%

Git 随笔

参考资料:


起步

Git,分布式版本控制系统。

工作原理

  • Git 保存的是文件的快照。

    • 它会保存当前全部文件的快照的索引,如果有修改的话,就创建修改后文件的快照,保存该快照的索引。如果当前没有修改该文件,就保存上一个版本的索引。

    image-20210808001728982

  • Cvs、Subversion 等其他版本控制系统存储的是每个文件的差异信息。

全球网络存储工业协会SNIA(StorageNetworking Industry Association)对快照(Snapshot)的定义是:关于指定数据集合的一个完全可用拷贝,该拷贝包括相应数据在某个时间点(拷贝开始的时间点)的映像。快照可以是其所表示的数据的一个副本,也可以是数据的一个复制品。

Git 文件有三种状态:已修改(modified)、 已暂存(staged) 和 已提交(committed)

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

这会让我们的 Git 项目拥有三个阶段:工作区、暂存区以及 Git 目录

image-20210808002008189

查看所有的配置以及它们所在的文件

1
git config --list --show-origin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
C:\Users\LEN>git config --list --show-origin
file:"C:\\ProgramData/Git/config" core.symlinks=false
file:"C:\\ProgramData/Git/config" core.autocrlf=true
file:"C:\\ProgramData/Git/config" core.fscache=true
file:"C:\\ProgramData/Git/config" color.diff=auto
file:"C:\\ProgramData/Git/config" color.status=auto
file:"C:\\ProgramData/Git/config" color.branch=auto
file:"C:\\ProgramData/Git/config" color.interactive=true
file:"C:\\ProgramData/Git/config" help.format=html
file:"C:\\ProgramData/Git/config" rebase.autosquash=true
file:C:/Program Files/Git/mingw64/etc/gitconfig http.sslcainfo=C:/Program Files/Git/mingw64/ssl/certs/ca-bundle.crt
file:C:/Program Files/Git/mingw64/etc/gitconfig http.sslbackend=openssl
file:C:/Program Files/Git/mingw64/etc/gitconfig diff.astextplain.textconv=astextplain
file:C:/Program Files/Git/mingw64/etc/gitconfig filter.lfs.clean=git-lfs clean -- %f
file:C:/Program Files/Git/mingw64/etc/gitconfig filter.lfs.smudge=git-lfs smudge -- %f
file:C:/Program Files/Git/mingw64/etc/gitconfig filter.lfs.process=git-lfs filter-process
file:C:/Program Files/Git/mingw64/etc/gitconfig filter.lfs.required=true
file:C:/Program Files/Git/mingw64/etc/gitconfig credential.helper=manager
file:C:/Users/LEN/.gitconfig user.name=len
file:C:/Users/LEN/.gitconfig user.email=891846581@qq.com
file:C:/Users/LEN/.gitconfig difftool.sourcetree.cmd='' "$LOCAL" "$REMOTE"
file:C:/Users/LEN/.gitconfig mergetool.sourcetree.cmd=''
file:C:/Users/LEN/.gitconfig mergetool.sourcetree.trustexitcode=true

Git 基础

获取 Git仓库

通常有两种获取 Git 项目仓库的方式:

  1. 将尚未进行版本控制的本地目录转换为 Git 仓库;

    进入需要版本控制的目录中(cd /c/user/my_project),执行 git init 初始化 Git 仓库。然后通过 git add 命令把相关文件添加到版本控制中,最后 git commit。(todo:这里还差一个链接&上传到远程仓库的操作)

  2. 从其它服务器 克隆 一个已存在的 Git 仓库。

    git clone <url>:例如 git clone https://github.com/libgit2/libgit2

查看仓库中所有文件的状态

git status

git status 命令的输出十分详细,但其用语有些繁琐 。可以使用 git status -s 命令或 git status —short 命令来获得更简洁的输出:

1
2
3
4
5
6
$ git status -s
M README
MM Rakefile
A lib/git.rb
M lib/simplegit.rb
?? LICENSE.txt

image-20210808151543874

说明:

README: 已修改未暂存。

Rakefile: 已暂存,暂存后又有修改,并且最后的修改还没暂存

lib/git.rb: 新添加到暂存区的文件

lib/simplegit.rb: 已暂存

LICENSE.txt: 未跟踪的文件

关于 git add 命令

它可以把新文件添加到版本控制系统中,也可以把已跟踪的文件放到暂存区,还能用于用于合并时把有冲突的文件标记为已解决状态等 。应该将这个命令理解为“精确地将内容添加到下一次提交中”而不是“将一个文件添加到项目中” 。

忽略文件

文件 .gitignore 的格式规范如下:

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

glob 模式

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

.gitignore 文件栗子:

1
2
3
4
5
6
7
8
9
10
11
12
# 忽略所有的 .a 文件
*.a
# 但跟踪所有的 lib.a,即便你在前面忽略了 .a 文件
!lib.a
# 只忽略当前目录下的 TODO 文件,而不忽略 subdir/TODO
/TODO
# 忽略任何目录下名为 build 的文件夹
build/
# 忽略 doc/notes.txt,但不忽略 doc/server/arch.txt
doc/*.txt
# 忽略 doc/ 目录及其所有子目录下的 .pdf 文件
doc/**/*.pdf

如果子目录中有额外的 .gitignore 文件,那ta只作用于ta所在目录。

GitHub 有一个十分详细的针对数十种项找目及 到它。语言的 .gitignore 文件列表,你可以在 https://github.com/github/gitignore 找到ta。

针对 java项目的 .gitignore Demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Compiled class file
*.class

# Log file
*.log

# BlueJ files
*.ctxt

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

查看已暂存和未暂存的修改

git diff 命令显示具体修改了那些地方。

git diff --cached 查看已经暂存起来的变化

提交更新

每次提交前,记得先用 git status 看下,你所需要的文件是不是都已暂存起来了, 然后再运行提交命令 git commit。

默认情况下,用cmd执行 git commit 命令,git会打开默认编辑器,这时候编辑器会显示包含最后一次运行 git status 的输出,放在注释行里。如下:

1
# 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'.                                                           #                                                                                                           # Changes to be committed:                                                                                 #       new file:   test.txt                                                                               #                                                                                                           # Changes not staged for commit:                                                                           #       modified:   "Git\351\232\217\347\254\224.md"                                                       #  

另外开头还有一个空行,供你输入提交说明 。

退出编辑器时,Git 会丢弃注释行,用你输入的提交说明生成一次提交。

当然,你也可以直接一点,在 commit 命令后添加 -m 选项,直接将提交信息与命令放在同一行:git commit -m "commit msg"。这样就不用弹出编辑器了。

给 git commit 加上 -a 选项,Git 会自动的把跟踪过的文件暂存起来(未跟踪过的新文件不会暂存!),这样就不用 git add 了。

删除文件

  1. 删除 Git 中的一个文件 test.txt 。可以有两种操作方式:

    1. 从磁盘中删除该文件。然后执行 git rm test.txt
    2. 【推荐】直接执行 git rm test.txt,Git 会删除磁盘中的 test.txt。
  2. 忘记改 .gitignore 文件导致一大堆数据添加到了暂存区或者 Git仓库。现在想要让这些文件都保存到本地,但是不想让 Git 继续跟踪,可以使用 --cached 选项:git rm --cached README

    git rm 命令后面可以使用 glob 模式来匹配文件:

    1
    git rm log/\*.log # 此命令删除 log/ 目录下扩展名为 .log 的所有文件

    注意到星号 * 之前的反斜杠 \, 因为 Git 有它自己的文件模式扩展匹配方式,所以我们不用 shell 来帮忙展开

移动/重命名文件

重命名:git mv test1.txt test2.txt

移动:git mv test1.txt test/ 把当前目录下的 test1.txt 文件移动到当前目录的 test 子目录下。

运行 git mv 就相当于运行了下面三条命令:

1
2
3
$ mv README.md README
$ git rm README.md
$ git add README

查看提交历史

git log -p -2 --pretty=format:"%h - %an, %ar : %s"

git log —pretty=format 常用的选项:

选项 说明

选项 说明
%H 提交的完整哈希值
%h 提交的简写哈希值
%T 树的完整哈希值
%t 树的简写哈希值
%P 父提交的完整哈希值
%p 父提交的简写哈希值
%an 作者名字
%ae 作者的电子邮件地址
%ad 作者修订日期(可以用 —date=选项 来定制格式)
%ar 作者修订日期,按多久以前的方式显示
%cn 提交者的名字
%ce 提交者的电子邮件地址
%cd 提交日期
%cr 提交日期(距今多长时间)
%s 提交说明

作者指的是实际作出修改的人,提交者指的是最后将此工作成果提交到仓库的人。 所以,当你为某个项目发布补丁,然后某个核心员将你的补丁并入项目时,你就是作者,而那个核心成员就是提交者。 我们会在分布式 Git 再详细介绍两者之间的细微差别。

撤销操作

修正提交命令 amend

git commit --amendgit commit --amend “commit msg” 可以修正暂存区的提交,让旧的提交好像没有发生过一样。

应用场景:提交完之后发现提交信息错了,或者提交完之后漏了几个文件。这样我们可以去修正暂存区的提交

问题:如果我暂存区的提交有多个,那么,这个指令会把所有提交都覆盖吗?还是只会修正最后一个提交?

答案:amend 只会修正最后一个提交。

取消暂存的文件

git reset HEAD <fileName>

还得看一下【重置揭秘】这一章来补充这里的 reset 命令。

用最近提交的版本来覆盖你对文件的修改

git checkout -- <fileName>

使用远程仓库

查看远程仓库

git remote:列出每一个远程仓库的简写。注意,git clone 会把克隆下来的远程仓库创建默认简写“origin”。

git remote -v:显示 git 保存的简写和对应的URL

向本地 git 添加远程仓库

  1. git clone <URL>
  2. git remote add <shortname> <URL>:添加一个新的远程仓库同时指定一个简写(shortname)

从远程仓库中抓取和拉取

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

git pull:如果你的当前分支设置了跟踪远程分支 ,ta会自动抓取后合并该远程分支到当前分支 。

推送到远程仓库

git push <remote> <branch>

当你和其他人在同一时间克隆,他们先推送到上游然后你再推送到上游,你的推送就会毫无疑问地被拒绝。 你必须先抓取他们的工作
并将其合并进你的工作后才能推送。

查看某个远程仓库

git remote show <remote>

本地git中远程分支的删除和别名重命名

重命名:git remote rename 原名 新名称

删除:git remote remove 分支别名

打标签

Git 可以给某个提交打上标签,以表示这个提交的重要性和代表性。一般人们都会用来标记发布节点,如 v1.0、v2.0等。

列出标签 git tag

1
2
3
4
5
6
7
8
9
10
11
$ git tag -l "v1.8.5*"
v1.8.5
v1.8.5-rc0
v1.8.5-rc1
v1.8.5-rc2
v1.8.5-rc3
v1.8.5.1
v1.8.5.2
v1.8.5.3
v1.8.5.4
v1.8.5.5

创建标签

Git 支持两种标签:

  1. 轻量标签(lightweight):本质上是将提交【校验和】存储到一个文件中(没有保存任何其他信息)。

    创建命令:git tag <tagName>

  2. (推荐)附注标签(annotated):包含打标签者的名字、电子邮件地址、日期时间、标签信息。

    创建命令:git tag -a <tagName> -m "description"

git show 命令可以看到标签信息和与之对应的提交信息

对过去的提交打标签

git tag -a <tagName> 检验和(或部分校验和)

eg. git tag -a v1.2 9fceb02

共享标签

默认情况下。git push 不会传送标签到远程仓库上。你需要使用命令:git push origin <tagname>

如果要推送多个标签到远程仓库中,可以使用 git push origin --tags,它会把本地所有的标签都推送上去。

删除标签

  1. git tag -d <tagname>删除本地标签(此时远程仓库中标签还在)
  2. git push origin --delete <tagname> 更新远程仓库标签

检出标签

git checkout <tagName> 查看某个标签所指向的文件版本

注意:如果你检出之后,又做了某些更改然后提交它们,标签不会发生变化, 但你的新提交将不属于任
何分支,并且将无法访问,除非通过确切的提交哈希才能访问。 因此,如果你需要进行更改,比如你要修复旧
版本中的错误,那么通常需要创建一个新分支:

1
2
$ git checkout -b version2 v2.0.0
Switched to a new branch 'version2'

Git 别名

git config 命令可以为每一个命令设置一个别名。

1
2
3
4
$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.ci commit
$ git config --global alias.st status

当要输入 git commit 时,只需要输入 git ci。

我们还可以利用这个机制创建自定义命令:

1
$ git config --global alias.unstage 'reset HEAD --'
1
$ git config --global alias.last 'log -1 HEAD'

分支

Git 是如何保存数据的?

git 保存的是文件不同时刻的快照。在进行提交的时候,git会保存一个提交对象。该对象包含一个指向快照的指针、作者的姓名和邮箱、提交时输入的信息、以及指向父对象的指针。

首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象, 而由多个分支合并产生的提交对象有多个父对象。

Git 的分支,其实本质上仅仅是指向提交对象的可变指针。 Git 的默认分支名字是 master。git init 命令默认创建 master 分支。

创建分支

git branch <branchName>

切换分支

git checkout branchName

如果是切换到一个较旧的分支,你的工作目录会恢复到该分支最后一次提交时的样子。如果 Git 不能干净利落地完成这个任
务,它将禁止切换分支。

创建新分支的同时切换过去

git checkout -b <newbranchname>

分支合并

git merge <branchName>

设置好跟踪分支后,可以用简写@{upstream} 或 @{u} 来代替上游分支:如使用 git merge @{u} 来取代 git merge origin/master

删除分支

git branch -d <branchName>

遇到冲突时的分支合并

执行 git merge,有时候会发生冲突。这时候 Git 没有自动的创建一个合并提交,它会暂停下来,等我们自己解决冲突。

可以用 git status 来查看冲突的文件:

1
2
3
4
5
6
7
8
$ git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: index.html
no changes added to commit (use "git add" and/or "git commit -a")

冲突文件中的 <<<<<<< HEAD: 标注表示该部分是当前分支代码,======= 下面的表示要合并分支的代码。

修改完冲突文件之后,可以使用 git add 命令来暂存这些文件,这时候 Git 也会将这些文件标记为已解决。

合并完成后记得删除无用分支 git branch -d <branchName>

分支管理

git branch --merged(或 --no-merged) 查看已经合并或尚未合并到当前分支的分支。

git branch --merged dev 查看已经合并到 dev 分支的分支。

远程分支

当你执行 git clone <URL> 命令的时候,Git 会从远程仓库中拉取ta的所有数据,并为这个远程仓库创建默认别名 origin。Git也会在你本地仓库创建一个或者多个(与远程仓库对应的)远程分支,这些远程分支通常会这样命名:origin/master。Git 还会给你创建一个本地 master 分支,ta 和origin/master远程分支是指向同一个提交对象的。

在本地仓库中的远程分支是不可修改的,可以通过 git checkout -b <branch> <remote>/<branch> 来检出远程分支代码到本地,这样就可以修改了。

git fetch <remote> 命令用来更新位于本地仓库中的远程分支。

推送

创建一个本地分支,如果你不显式地推送到 Git服务器,那别人就永远不会看到ta,ta就是你的私有分支。

推送分支命令:git push <remote> <branch>

如果并不想让远程仓库上的分支叫做 serverfix,可以运行 git push origin serverfix:awesomebranch 来将本地的 serverfix 分支推送到远程仓库上的 awesomebranch 分支。

跟踪分支

从一个远程分支检出一个本地分支的时候,Git 会自动创建所谓的跟踪分支。如果在一个跟踪分支上 git pull,Git 能自动地识别去哪个服务器上抓取、合并到哪个分支。

拉取

git fetch 命令从服务器上拉取本地没有的数据,然后让你自己合并。

git pull 会从服务器中找到当前分支跟踪的远程分支数据,并尝试合并,相当于 git fetch + git merge

删除远程分支

git push origin --delete serverfix

基本上这个命令做的只是从服务器上移除这个指针。 Git 服务器通常会保留数据一段时间直到垃圾回收运行,所以如果不小心删除掉了,通常是很容易恢复的。

变基

从A分支变基到B分支。就是从A和B的共同祖先开始,将A分支的修改应用到B分支上。

操作指令:

1
2
3
4
5
6
7
8
$ git checkout A
$ git rebase B # 这里的命令也可以使用 git rebase <basebranch> <topicbranch> 代替
First, rewinding head to replay your work on top of it...
Applying: added staged command

# 然后回到 master 分支,进行一次快进合并。
$ git checkout master
$ git merge experiment

变基是为了确保在向远程分支推送时能保持提交历史的整洁,ta和三方合并的结果是一样的,只不过提交历史不同罢了。变基是将一系列提交按照原有次序依次应用到另一分支上,而合并是把最终结果合在一起。

特殊的变基例子:

从master分出A分支,提交一个版本到A,然后再从A分出一个分支B,提交B1、B2版本到B分支。现在想从B分支变基到到master分支:

1
2
3
4
5
git rebase --onto master A B

# 然后回到 master 分支,进行一次快进合并。
$ git checkout master
$ git merge experiment

变基有风险:

如果你只对不会离开你电脑的提交执行变基,那就不会有事。 如果你对已经推送过的提交执行变基,但别人没有基于它的提交,那么也不会有事。 如果你对已经推送至共用仓库的提交上执行变基命令,并因此丢失了一些别人的开发所基于的提交, 那你就有大麻烦了,你的同事也会因此鄙视你。

分布式 Git

1
2
3
4
$ git push origin master
...
To jessica@githost:simplegit.git
1edee6b..fbff5bc master -> master

上方输出信息中最后一行显示的是推送操作执行完毕后返回的一条很有用的消息。 消息的基本格式是 <oldref>..<newref> fromref -> toref, oldref 的含义是推送前所指向的引用, newref 的含义是推送后所指向的引用, fromref 是将要被推送的本地引用的名字, toref 是将要被更新的远程引用的名字。在后面的讨论中你还会看到类似的输出消息,所以对这条消息的含义有一些基础的了解将会帮助你理解仓库的诸多状态。

一些问题

.gitignore规则不生效的解决办法

把某些目录或文件加入忽略规则,按照上述方法定义后发现并未生效,原因是.gitignore只能忽略那些原来没有被追踪的文件,如果某些文件已经被纳入了版本管理中,则修改.gitignore是无效的。那么解决方法就是先把本地缓存删除(改变成未被追踪状态),然后再提交:

git rm -r —cached .
git add .
git commit -m ‘commit detail msg’

Git 在什么时候创建快照?(add? commit? push?)

关于创建一个git仓库的思考:(未解决)

能不能直接在我工作的目录中执行 git init,然后push目录中的文件?而不是通过在gitee创建一个项目,pull 下来,再把文件塞到项目中,再push上去。有这种方法么?

怎么查看暂存区里面提交?

总结

Git 会保存当前全部文件的快照的索引,如果有修改的话,就创建修改后文件的快照,保存该快照的索引。如果当前没有修改该文件,就保存上一个版本的索引。

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

暂存操作:为每一个文件计算校验和(使用我们在 起步 中提到的 SHA-1 哈希算法),然后会把当前版本的文件快照保存到Git 仓库中 (Git 使用 blob 对象来保存它们),最终将校验和加入到暂存区域等待提交:

提交操作: Git 计算每个子目录的校验和,然后在 git 仓库中将这些校验和保存为树对象,然后创建一个提交对象。提交对象包含一个指向快照的指针、作者的姓名和邮箱、提交时输入的信息、指向父对象的指针,以及指向这个树对象的指针。

merge:三方合并(要明白这句话的意思)

从正式分支拉一个开发分支的操作

创建新分支的同时切换过去 git checkout -b <newbranchname>,然后推送到远程服务器中:git push origin testBranch

git diff master…contrib

该命令仅会显示自当前主题分支与 master 分支的共同祖先起,该分支中的工作。 这个语法很有用,应该牢
记。

想法

总结:学过了,但是大部分没记住,用的时候还需要翻查。后面还是得安排时间复习几遍。

感觉一整天的学习学习效率比一个星期零散的学习效率要高很多。