Git Notes

[toc]

零、我在学习Git过程中用到的资料

尚硅谷Git入门到精通全套教程(涵盖GitHub\Gitee码云\GitLab)

https://learngitbranching.js.org/?locale=zh_CN

ICTP GWData-Bootcamp 2023

其他资料(待更新)

一、Git 概述

  1. Git 是分布式版本控制系统。

  2. 版本控制:记录文件内容变化,最重要的是记录文件修改历史记录。

  3. 版本控制系统有两类:

    • 集中式版本控制系统:CVS、SVN 等,所有的文件修改版本保存在一个单一的集中服务器。用户从服务器上提取最新版文件进行修改,再提交到服务器。
    • 分布式版本控制系统:Git 等,提取的不是最新版本的文件快照,而是镜像整个代码仓库为一个本地库,每一次提取实际上就是对代码库进行备份。修改完后将代码推送至代码托管中心(远程库),远程库的代码是最新的。
    • 主要区别在于:服务器断网也可以开发,且每个客户端都保存着完整代码。
  4. Git运行机制:

    assets/Pasted image 20230525223448.png
    图源:https://walkssi.com/git/
    • 工作区:代码存放的位置。在工作区可以修改代码,且没有历史记录;
    • 暂存区:将工作区的代码添加 (git add) 到暂存区,也可以修改代码并且没有历史记录;
    • 本地库:将暂存区的代码提交 (git commit) 到本地库,就会生成历史版本。在本地库的版本不能修改。若发现代码不好,只能在工作区修改后再次提交,提交后本地库同时存在这两个版本。例如先提交了 v1 版本,发现不好,则只能在 v1 版本基础上修改然后提交为 v2 版本;
    • 远程库(代码托管中心):将本地库的代码推送 (git push) 至远程库。

注意⚠️:所有的版本控制系统都只能跟踪纯文本文件的改动,比如 TXT 文件、网页、源代码等。无法跟踪图片、视频,以及 Word 格式等二进制文件内部的变化。

二、常用的 Git 命令

1
2
3
4
5
6
7
8
9
10
11
git config --global user.name <your name>    # 设置全局用户签名
git config --global user.email <your email> # 设置全局用户邮箱
git init # 初始化本地库
git status # 查看本地库状态
git add <filename> # 添加到暂存区
git add --all # 将整个工作区中所有的文件改动提交至暂存区,包括新增、修改和被删除的文件,不受当前所在目录限制
git rm --cached <filename> # 取消使用git追踪某文件
git commit -m "<log information (or version)>" <filename> # 提交文件到本地库(省略文件名则提交整个暂存区)
git reset --hard <commit_number (七位)> # 版本穿梭
git reflog # 查看简略历史记录
git log [--oneline] # 查看详细历史记录,--oneline只显示第一行

设置用户签名

用于区分不同的本地作者身份,在每一个提交的版本之中可以看到是谁提交的。

1
2
git config --global user.name <your name>    # 设置全局用户签名
git config --global user.email <your email> # 设置全局用户邮箱

C:\Users\SDYZZY\.gitconfig (Windows) 或 /Users/sdyzzy/.gitconfig (MacOS) 文件中可以查看是否修改成功。

初始化本地库

让 Git 获得项目目录的管理权。在项目目录内执行 git init 来初始化本地库,完成后项目目录内会增加一个 .git 隐藏目录。

1
2
(base) sdyzzy@SDYZZY-MacBook-Pro git_study % git init
Initialized empty Git repository in /Users/sdyzzy/study/git_study/.git/

查看本地库状态

项目目录内执行 git status 来查看当前项目的本地库状态,包括目前所处的分支 (branch),有无需要添加暂存区、提交本地库的文件或目录。

1
2
3
4
5
6
(base) sdyzzy@SDYZZY-MacBook-Pro git_study % git status
On branch main

No commits yet

nothing to commit (create/copy files and use "git add" to track)
  • main 指当前分支 (Windows 里面为 master)。
  • no commits yet 指目前没有提交过任何文件。
  • nothing to commit (create/copy files and use "git add" to track) 指当前没有文件需要提交。

在当前目录新建一个文档 hello.txt,再查看本地库状态。输出中的 Untracked files: 表示此时在工作区存在一个叫 hello.txt 的未追踪的文件,可以使用 git add 追踪(即添加到暂存区)。

1
2
3
4
5
6
7
8
9
10
11
(base) sdyzzy@SDYZZY-MacBook-Pro git_study % touch hello.txt
(base) sdyzzy@SDYZZY-MacBook-Pro git_study % git status
On branch main

No commits yet

Untracked files:
(use "git add <file>..." to include in what will be committed)
hello.txt

nothing added to commit but untracked files present (use "git add" to track)

注意⚠️:新建的空目录无法被检测到,因为 git 是对“文件的内容”来计算的。解决方法是在空目录中随意创建一个文件,使得 git 可以检测到该目录的存在。

忽略指定文件或目录

在项目目录内创建一个名为 .gitignore 的文件,在其中添加需要忽略的文件名或目录名(一行只能输入一个名称),可以使用通配符忽略一类文件或目录。

  • # 开始:注释行。
  • / 开始:防止递归(只忽略当前目录下的该文件,而不忽略子目录下的同名文件)。
  • / 结尾:指定目录(忽略项目中所有该名称的目录)。
  • **问号 ? 只匹配一个任意字符。星号 * 匹配零个或多个任意字符。方括号 [abc] 匹配任何一个在方括号中的字符 (这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);如果在方括号中使用短划线分隔两个字符, 表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字)。两个星号 **** 表示匹配任意中间目录,比如 a/**/z 可以匹配 a/za/b/za/b/c/z 等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# .gitignore

*~ # 忽略所有名字以波浪符(~)结尾的文件,许多文本编辑软件(比如 Emacs)都用这样的文件名保存副本。
*.a # 忽略所有的 .a 文件
*.[oa] # 忽略所有以 .o 或 .a 结尾的文件。一般这类对象文件和存档文件都是编译过程中出现的。

!lib.a # 跟踪所有的 lib.a,即便你在前面忽略了 .a 文件

/TODO # 只忽略当前目录下的 TODO 文件,而不忽略 subdir/TODO

build/ # 忽略任何目录下名为 build 的文件夹

doc/*.txt # 忽略 doc/notes.txt,但不忽略 doc/server/arch.txt

doc/**/*.pdf # 忽略 doc/ 目录及其所有子目录下的 .pdf 文件
来源:https://juejin.cn/post/7264920482421260344

例如扩展名为 .pyc 的文件根据 .py 文件自动生成,存储在目录 \__pycache__ 中,无须让 git 跟踪它们。

1
(base) sdyzzy@SDYZZY-MacBook-Pro git_study % echo "__pycache__/" >> .gitignore  # 忽略__pycache__/

添加到暂存区

  • git add <filename1> <filename2> ... 指定添加哪些文件到暂存区。

  • git add --all 将项目目录中所有被修改过的文件添加到暂存区。

1
2
3
4
5
6
7
8
9
(base) sdyzzy@SDYZZY-MacBook-Pro git_study % git add hello.txt
(base) sdyzzy@SDYZZY-MacBook-Pro git_study % git status
On branch main

No commits yet

Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: hello.txt

此时再查看本地库状态,Changes to be committed: 表示已经追踪到了文件,并且等待提交本地库。在暂存区的文件还未形成历史版本,可以用 git rm --cached <filename> 把暂存区内的文件删除(但是工作区里仍然存在,需要手动删除)。

注意⚠️:在 Windows 系统中可能会由于转换行末换行符而产生警告,一般不用管。

提交到本地库

  • git commit -m "commit message" <filename1> <filename2> ...暂存区内指定文件提交到本地库。使用 -m--message 参数添加对本次修改的描述,要求简单具体。如果不指定 <filename>,则会将暂存区中的所有文件提交到本地库。

  • 下面的代码表示在本地库主分支 (main) 提交了一个信息为 “create hello.txt” 的 hello.txt 文件,(精简的)commit number 为 9c84112,1 个文件被改变,0 行内容被插入,0 行内容删除。

    1
    2
    3
    4
    5
    6
    7
    (base) sdyzzy@SDYZZY-MacBook-Pro git_study % git commit -m "create hello.txt" hello.txt
    [main (root-commit) 9c84112] create hello.txt
    1 file changed, 0 insertions(+), 0 deletions(-)
    create mode 100644 hello.txt
    (base) sdyzzy@SDYZZY-MacBook-Pro git_study % git status
    On branch main
    nothing to commit, working tree clean
  • 提交多行信息:两种方法。

    • 如果给出多个 -m 选项,则它们的值被串联为单独的段落(段落之间会空一行)。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      >>> git commit -m "refactor" -m "delete file1"
      >>> git status
      Author: SDYZZY <sdyzzy@mail.ustc.edu.cn>
      Date: Mon Nov 13 23:06:44 2023 +0800

      refactor

      delete f1.md

      end.
    • 先输入一个引号,然后键入内容,按回车键换行,继续输入。信息输入结束后,输入另一半引号,按回车键提交。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      >>> git commit -m "refactor
      >>> delete f5
      >>>
      >>> end."
      >>> git status
      Author: SDYZZY <sdyzzy@mail.ustc.edu.cn>
      Date: Mon Nov 13 23:05:21 2023 +0800

      refactor
      delete f5

      end.
  • commit 规范

    1
    2
    3
    4
    5
    6
    7
    <type>(<scope>): <subject>
    <body>

    # 示例
    feat(新增视图层):添加登录页面
    - 支持自动登录
    登录页面,新增链接参数解析,如果链接中包含from=auto,则表示进行自动登录
    • type: commit 的类型,包括但不限于
      • feat: 新功能;
      • fix: 修复BUG;
      • docs: 文档更新;
      • style: 代码风格相关无影响运行结果的;
      • perf: 优化/性能提升;
      • refactor: 重构;
      • revert: 撤销修改;
      • test: 增加测试内容;
      • undef: 不确定的分类。
    • scope: commit 影响的范围,比如某某组件、某某页面。
    • subject: commit 的简短描述,不超过50个字符。
    • Body: 本次 commit 的详细描述,可以分成多行。

从工作区、暂存区、本地库删除:

  • git rm <filename>:使用该命令删除工作区的文件,然后将“删除”这一修改提交本地库(添加暂存区已经由 git 自动完成,因此这一语句相当于 rm <filename> + git add <filename>),前提是暂存区里面没有该文件(如果有可以用下面的命令)。
  • git rm --cached <filename>:如果不想继续跟踪某个文件,可以使用该语句将该文件从 git 中移除,然后将“删除”这一修改提交本地库(也可以使用该命令删除暂存区的文件)。
  • git restore <filename>:文件在暂存区且在工作区做了修改,执行该命令可以将工作区的文件恢复到最后一次 add 的状态(也就是和暂存区一致)。
  • git restore --staged <filename>:文件在暂存区且在工作区做了修改,执行该命令可以将暂存区文件删除,且工作区文件不变(如果工作区未做修改,那么将暂存区的文件删除就相当于使文件不被追踪)。

查看 commit 记录

  • git log 按时间从新到旧顺序列出项目所有的提交历史。
    • git log --oneline:每次提交的记录只显示 1 行。

    • git log <filename>:查看特定文件的 commit 记录。

    • git log -p <filename>:查看特定文件的 commit 记录以及每次提交所引入的差异

    • git log --graph:可视化 merge 过程。

  • git reflog 显示最近1个月 HEAD 和分支引用的指向。每当 HEAD 所指向的位置发生了变化,git 就会将这个信息存储到引用日志里。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(base) sdyzzy@SDYZZY-MacBook-Pro git_study % vim hello.txt  # 添加一行文本“Hello world”
(base) sdyzzy@SDYZZY-MacBook-Pro git_study % git log
commit 4601fc59c1f07501cafdb800eee443f2b0994116 (HEAD -> main)
Author: SDYZZY <sdyzzy@mail.ustc.edu.cn>
Date: Wed Nov 15 11:44:12 2023 +0800

添加文本Hello world

commit 9c84112655505862db8d53ac79bd8a96ff7b22ab
Author: SDYZZY <sdyzzy@mail.ustc.edu.cn>
Date: Wed Nov 15 11:16:49 2023 +0800

create hello.txt
(base) sdyzzy@SDYZZY-MacBook-Pro git_study % git log --oneline
4601fc5 (HEAD -> main) 添加文本Hello world
9c84112 create hello.txt
(base) sdyzzy@SDYZZY-MacBook-Pro git_study % git reflog
4601fc5 (HEAD -> main) HEAD@{0}: commit: 添加文本Hello world
9c84112 HEAD@{1}: commit (initial): create hello.txt

其中的 (HEAD -> main) 表示 HEAD 指针指向 main 分支的 commit number 为 4601fc5 的版本。

版本穿梭

git reset --hard <commit_number> 用于回退到过去的某个版本。

1
2
3
4
5
6
7
8
9
10
(base) sdyzzy@SDYZZY-MacBook-Pro git_study % git log --oneline 
4601fc5 (HEAD -> main) 添加文本Hello world
9c84112 create hello.txt
(base) sdyzzy@SDYZZY-MacBook-Pro git_study % git reset --hard 9c84112
HEAD is now at 9c84112 create hello.txt
(base) sdyzzy@SDYZZY-MacBook-Pro git_study % git reflog
9c84112 (HEAD -> main) HEAD@{0}: reset: moving to 9c84112
4601fc5 HEAD@{1}: commit: 添加文本Hello world
9c84112 (HEAD -> main) HEAD@{2}: commit (initial): create hello.txt
(base) sdyzzy@SDYZZY-MacBook-Pro git_study % cat hello.txt # 无输出,因为最初是一个空白文档

HEAD 指针已经指向了 commit number 为 9c84112 的历史版本,并且输出文件发现确实回到了该版本。实际上是 HEAD 指针指向了 main ,然后 main 指针改变指向了 9c84112 版本(在 \.git\refs\heads\main 文件内可以查看 main 指向的版本),因此版本穿梭的关键是改变指针指向

修改 commit 记录

  • 修改最后一次 commit 的记录:git commit --amend -m "your new log",只需在原来的 commit 命令加上一个--amend参数,并提供新的记录即可。
  • 修改更早的记录git rebase -i <commit_number>,进入 rebase 命令的交互模式,commit_number 表示从最后一次 commit 到 commit_number(不包括)指定的那一次 commit。此命令会进入一个 vim 编辑器,里面显示了这些 commit 的信息。参数 pick 表示不对 commit 做改动;reword表示修改 commit 信息,将需要修改的记录对应的参数改为 reword 后,保存退出。然后会立即弹出另一个 vim 编辑器,在该编辑器之中修改 commit 信息,然后保存退出。命令执行结束后,所有被修改的 commit 以及它之后的 commit 的 SHA-1 值都会改变(因为历史信息改变了)。
  • git reset 版本穿梭,然后重新提交。
  • 合并多个 commit 为一个commit:git rebase -i <commit_number>,rebase 命令的交互模式,commit_number 表示从最后一次 commit 到 commit_number(不包括)指定的那一次 commit。此命令会进入一个 vim 编辑器,里面显示了这些 commit 的信息。将 pick 修改为 squash,然后保存退出,在弹出的 vim 编辑器里面修改 commit 信息。
  • git rebase -i 实现 commit 合并、分解、删除、修改记录、调整顺序、在指定的 commit 之间添加新的 commit等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
(base)sdyzzy@SDYZZY-MacBook-Pro test % git reflog
167a77c (HEAD -> main) HEAD@{0}: commit (amend): add f9 in main
3d5a58a HEAD@{1}: commit: add f9
507c87e HEAD@{2}: commit: add f8
1e70dee HEAD@{3}: commit: add f7
06f32dc HEAD@{4}: merge cat: Merge made by the 'ort' strategy.
69be8a0 HEAD@{5}: checkout: moving from cat to main
(pycbc_emcee_old_pip) sdyzzy@SDYZZY-MacBook-Pro test % git rebase -i 69be8a0
# 进入vim界面
pick dd3ffd3 add f6_cat
reword 1e70dee add f7
reword 507c87e add f8
pick 167a77c add f9 in main

# Rebase 69be8a0..167a77c onto 69be8a0 (4 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# 保存退出,弹出下一个vim界面
add f7

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Mon Nov 13 15:53:10 2023 +0800
#
# interactive rebase in progress; onto 69be8a0
# Last commands done (2 commands done):
# pick dd3ffd3 add f6_cat
# reword 1e70dee add f7
# Next commands to do (2 remaining commands):
# reword 507c87e add f8
# pick 167a77c add f9 in main
# You are currently editing a commit while rebasing branch 'main' on '69be8a0'.
#
# Changes to be committed:
# new file: f7
#
# 将add f7修改为add f7 in main,保存退出,对f8同理。
# 退出后显示下面内容,修改完成
[detached HEAD 0222224] add f7 in main
Date: Mon Nov 13 15:53:10 2023 +0800
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 f7
[detached HEAD 4f84512] add f8 in main
Date: Mon Nov 13 15:53:37 2023 +0800
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 f8
Successfully rebased and updated refs/heads/main.
(pycbc_emcee_old_pip) sdyzzy@SDYZZY-MacBook-Pro test % git reflog
636351f (HEAD -> main) HEAD@{0}: rebase (finish): returning to refs/heads/main
636351f (HEAD -> main) HEAD@{1}: rebase (pick): add f9 in main
4f84512 HEAD@{2}: rebase (reword): add f8 in main
2db537e HEAD@{3}: rebase (reword): add f8
0222224 HEAD@{4}: rebase (reword): add f7 in main
fb86705 HEAD@{5}: rebase (reword): add f7
f0603ea HEAD@{6}: rebase (pick): add f6_cat

追加文件到最后一次 commit

有时会发现某些文件被漏掉了没有提交,但是又不愿意为此新建一次提交,可以使用该方法将漏掉的文件添加到最近一次的提交中。先添加到暂存区,然后按照上面的方法提交本地库并修改提交记录 git commit --amend -m "your log" 或者只提交本地库不修改记录 git commit --amend --no-edit

取消版本控制

rm -rf .git。版本信息都存储在 .git 目录之中,删除该目录就可以取消版本控制。

版本迭代(修改文件)示例

修改 hello.txt,然后再查看状态,红色表明修改了且还未追踪。

然后进行添加、提交,此时指针已经指向新的版本。

总结

使用 git reflog 或者 git log 查看各个版本情况,修改文件后可以用 git add 文件名 添加到暂存区,然后用 git commit -m "版本信息" 文件名 提交到本地库,若一段时间后不满意当前的版本,可以用 git reset --hard 版本号 回退到过去的某个版本。

三、Git分支

1
2
3
4
5
6
7
8
9
10
11
12
13
git branch -l                 # --list,查看本地分支
git branch -v # --verbose,查看本地分支以及最后一次commit的hash值
git branch -a # --all,查看本地、远程分支

git branch <new branch name> # 创建分支
git branch <new branch name> commit_number # 在指定的commit处创建分支

git branch -d <branch name> # --delete,删除已经合并过的分支
git branch -D <branch name> # 强制删除分支,即使还未合并

git checkout <branch name> # 切换分支
git branch -m <old name> <new name> # --move,分支改名
git merge –no-ff <branch name> # 合并分支(将branch name分支合并入当前分支)

多个分支

  • 用户分支,测试分支,开发分支……不同分支互不干扰,可以同时推进,完成开发后就可以进行分支合并(在用户看来就是更新),如果失败也可以直接删除分支。

  • 分支底层就是指针的引用,当创建一个新的 commit 的时候,当前分支(也就是 HEAD 指向的那个分支)会指向这个新的 commit,而其他分支的指向不会发生任何变化。

  • 切换分支的时候,git 会用新的分支的 commit 内容来更新暂存区和工作目录(最好 commit 了再切换分支)。

创建分支

  • git branch <new branch name>:创建分支。
  • git branch <new branch name> <commit_number>:在指定的 commit 处创建分支。

例如创建热修复分支 hot-fix:

1
2
3
4
5
6
(base) sdyzzy@SDYZZY-MacBook-Pro learn_git % git branch -v
* main 58bd5dc First commit
(base) sdyzzy@SDYZZY-MacBook-Pro learn_git % git branch hot-fix
(base) sdyzzy@SDYZZY-MacBook-Pro learn_git % git branch -v
hot-fix 58bd5dc First commit
* main 58bd5dc First commit

查看已有分支

  • git branch -l :查看本地分支。
  • git branch -v:查看本地分支以及最后一次 commit 的 hash 值。
  • git branch -a:查看本地、远程分支。
1
2
3
(base) sdyzzy@SDYZZY-MacBook-Pro learn_git % git branch -v
hot-fix 58bd5dc First commit
* main 58bd5dc First commit

切换分支

git checkout <branch name>。切换分支后,目录内的文件也会跟着切换(例如某个文件在 A 分支有而 B 分支没有,则从 A 分支切换到 B 分支后目录内该文件会消失,再切换回 A 分支又会出现)。

例如下面可以看到星号已经转移到了 hot-fix 分支前:

1
2
3
4
5
(base) sdyzzy@SDYZZY-MacBook-Pro learn_git % git checkout hot-fix
Switched to branch 'hot-fix'
(base) sdyzzy@SDYZZY-MacBook-Pro learn_git % git branch -v
* hot-fix 58bd5dc First commit
main 58bd5dc First commit
image-20231113153541773

分支合并

git merge –no-ff <branch name>:将 branch name 分支合并入当前分支。相当于用新的分支覆盖了当前分支(分支仅仅是指针,所以合并分支实际上是指的合并 commit,因此分支名也可以用 commit 号代替)。

  • Git 合并两个分支时,如果顺着一个分支走下去可以到达另一个分支的话,那么 Git 在合并两者时,只会简单地把指针右移,叫做“快进”(fast-forward)。这种情况历史里面看不出来曾经做过合并,而且如果删除分支则会丢失 merge 分支的信息。

    加上 --no-ff 参数可以禁止 fast-forward 模式,这样合并后的历史记录能看出来曾经做过合并。

  • 正常合并:由 main 分支的文件创建了 hot-fix 分支,且 main 分支没有修改,但是 hot-fix 分支修改了,因此可以直接合并。

  • 冲突合并:两个分支上的同一文件同一位置有两套不同的修改,不能直接合并,需要手动合并:打开文件,手动删除多余的,然后添加、提交(此时提交不能带文件名)

    例如现在位于 main 分支,新建一个分支切换过去,创建一个 merge.md 文件并输入 “This is BIG”,添加提交。然后回到 main 分支,也创建一个 merge.md 文件并输入 “This is small”,添加提交。接着尝试 git merge,会报错。此时可以 vim 进入该文件看冲突在哪里,并手动修改。然后直接添加提交即可完成 merge。此时切回 hot-fix 分支,发现文件没有被修改,只有 main 分支(合并分支)被修改了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    (base) sdyzzy@SDYZZY-MacBook-Pro test % git branch future
    (base) sdyzzy@SDYZZY-MacBook-Pro test % git checkout future
    (base) sdyzzy@SDYZZY-MacBook-Pro test % echo "This is BIG" >> merge.md
    (base) sdyzzy@SDYZZY-MacBook-Pro test % git add .
    (base) sdyzzy@SDYZZY-MacBook-Pro test % git commit -m "BIG"
    (base) sdyzzy@SDYZZY-MacBook-Pro test % cat merge.md
    This is BIG
    (base) sdyzzy@SDYZZY-MacBook-Pro test % git checkout main
    (base) sdyzzy@SDYZZY-MacBook-Pro test % echo "This is small" >> merge.md
    (base) sdyzzy@SDYZZY-MacBook-Pro test % git add .
    (base) sdyzzy@SDYZZY-MacBook-Pro test % git commit -m "small"
    (base) sdyzzy@SDYZZY-MacBook-Pro test % cat merge.md
    This is small
    (base) sdyzzy@SDYZZY-MacBook-Pro test % git merge future
    Auto-merging merge.md
    CONFLICT (add/add): Merge conflict in merge.md
    Automatic merge failed; fix conflicts and then commit the result.

    (base) sdyzzy@SDYZZY-MacBook-Pro test % git status
    On branch main
    You have unmerged paths.
    (fix conflicts and run "git commit")
    (use "git merge --abort" to abort the merge)

    Unmerged paths:
    (use "git add <file>..." to mark resolution)
    both added: merge.md

    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)
    (commit or discard the untracked or modified content in submodules)
    modified: git_study (untracked content)

    no changes added to commit (use "git add" and/or "git commit -a")
    (base) sdyzzy@SDYZZY-MacBook-Pro test % vim merge.md
    <<<<<<< HEAD
    This is small
    =======
    This is BIG
    >>>>>>> future
    # <<< 和 === 之间的是当前分支内容,=== 和 >>> 之间是被合并进来的分支的内容
    # 手动将所有内容删除,然后键入 " This is medium",wq 保存
    (base) sdyzzy@SDYZZY-MacBook-Pro test % git commit -am "medium"
    [main 3149370] medium
    (base) sdyzzy@SDYZZY-MacBook-Pro test % git log --graph
    * commit 314937084e5fd0805e834071275149bdd6f1b59f (HEAD -> main)
    |\ Merge: 782b132 b0a3a75
    | | Author: SDYZZY <sdyzzy@mail.ustc.edu.cn>
    | | Date: Mon Nov 20 20:05:28 2023 +0800
    | |
    | | medium
    | |
    | * commit b0a3a754dc12d5a23f94d11564934fe09377de89 (future)
    | | Author: SDYZZY <sdyzzy@mail.ustc.edu.cn>
    | | Date: Mon Nov 20 19:52:33 2023 +0800
    | |
    | | BIG
    | |
    * | commit 782b13206ac8a35774f17443c20d9d6009497cb1
    |/ Author: SDYZZY <sdyzzy@mail.ustc.edu.cn>
    | Date: Mon Nov 20 19:53:38 2023 +0800
    |
    | small
    (base) sdyzzy@SDYZZY-MacBook-Pro test % git branch -d future
    Deleted branch future (was b0a3a75).
image-20231113153516852

总结

分支 main、hot-fix 是指向具体版本记录的指针,head 则指向当前分支。创建分支就是多创建一个指针。

四、Git 标签

1
2
3
4
5
6
7
8
9
10
11
git tag <your tag> <commit_number>  # 创建轻量标签
git tag <your tag> <commit_number> -a -m "your annotation" # 创建有附注的标签
git show <your tag> #
git tag -d <your tag> # 删除标签

git tag -l # --list,list tag names
git tag -n<number> # print <n> lines of each tag message, default is 1

# 重命名标签 = 替换 + 删除
git tag -f <new tag> <old tag>
git tag -d <old tag>

标签与分支类似,都是指向某个 commit 的指针。区别在于,分支会随着 commit 的前进而前进,但是标签一直留在那个 commit 上。

  1. 标签分类:轻量标签(lightweight tag)和有附注的标签(annotated tag)。

    • 轻量标签一般用于暂时标记。更推荐使用有附注的标签,一般在开发到阶段性的时候贴上标签,比如版本号 1.0.0。

    • 轻量标签:git tag <your tag> commit_number。查看轻量标签只显示指向的那个 commit 的信息。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      >>> git log --oneline
      70916df (HEAD -> main) refactor
      03d9f85 refactor delete f5
      >>> git tag 测试标签 03d9f85
      >>> git log --oneline
      70916df (HEAD -> main) refactor
      03d9f85 (tag: 测试标签) refactor delete f5
      >>> git show 测试标签
      commit 03d9f850b6f87779940534bf71b7e841d3809e54 (tag: 测试标签)
      Author: SDYZZY <sdyzzy@mail.ustc.edu.cn>
      Date: Mon Nov 13 23:05:21 2023 +0800

      refactor
      delete f5

      end
      ...
    • 有附注的标签:git tag <your tag> commit_number -a -m "your annotation"。参数 -a 让 git 创建有附注的标签,-m 输入附注。查看有附注的标签除了显示 commit 信息以外,还会显示谁在什么时候贴了这张标签以及附注信息。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      >>> git tag 测试有附注的标签 70916df -a -m "这里是一些附注"
      >>> git log --oneline
      70916df (HEAD -> main, tag: 测试有附注的标签) refactor
      03d9f85 (tag: 测试标签) refactor delete f5
      >>> git show 测试有附注的标签
      tag 测试有附注的标签
      Tagger: SDYZZY <sdyzzy@mail.ustc.edu.cn>
      Date: Tue Nov 14 19:44:06 2023 +0800

      这里是一些附注

      commit 70916dfdd42836afbc36cb2e4a973b9545752577 (HEAD -> main, tag: 测试有附注的标签)
      Author: SDYZZY <sdyzzy@mail.ustc.edu.cn>
      Date: Mon Nov 13 23:06:44 2023 +0800

      refactor

      delete f1.md

      end.
      ...
  2. 查看标签:git show <your tag>

  3. 删除标签:git tag -d <your tag>

  4. 例子:

    1
    2
    3
    git tag v1.4-lw # 创建轻量标签
    git tag -a v2.0 -m '描述信息' # 创建附注标签
    git tag -a v1.2 9fceb02 # 后期对指定提交打标签

五、Git 与远程库

1
2
3
4
5
6
7
8
9
10
11
git remote -v                      # 查看当前所有远程地址别名
git remote add 别名 远程库地址 # 添加远程库并起别名
git remote remove 别名 # 删除别名

git push 远程库地址或别名 本地分支名 # 推送本地分支到远程库
git push <remote> <local branch>:<remote branch> # 推送本地 branch 分支,将其作为远程库的 remote branch 分支,用于推送本地分支到一个命名不相同的远程分支

git clone <remote> # 将远程库克隆到本地
git fetch <remote> # fetch 远程仓库中的所有分支
git fetch <remote> <branch> # fetch 远程仓库中的指定分支
git pull <remote> <remote branch> # 将远程库指定分支的最新内容拉取下来,然后自动与本地当前分支合并(一般用 git fetch + git merge 替代)

创建远程库

本地库和远程库之间通过 SSH 通信,因此创建远程库之前需要先设置 RSA 密钥对,执行指令

1
ssh-keygen -t rsa -C "email@example.com"

该命令会在用户主目录下生成 .ssh 目录,包含 id_rsa (私钥)和 id_rsa.pub (公钥)等文件。然后将公钥添加到远程服务器(例如 Github)。

  1. 点击 New Repository。
  2. 起远程库名,一般与本地库相同。
  3. 选择公共库与私有库(公共库读取权限公开)。
  4. 点击 code,可以看到远程库的 Httpsssh 的地址链接。

查看远程库

  • git remote -v 查看已经连接好的远程库的别名和地址。

  • git remote show <remote> 查看有关远程库的更多信息,包括远程库别名、地址、当前分支、远程分支状态(是否被追踪)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    (base) sdyzzy@SDYZZY-MacBook-Pro GWData-Bootcamp % git remote -v
    origin git@github.com:SDYZZY/GWData-Bootcamp.git (fetch)
    origin git@github.com:SDYZZY/GWData-Bootcamp.git (push)
    teacher git@github.com:iphysresearch/GWData-Bootcamp.git (fetch)
    teacher git@github.com:iphysresearch/GWData-Bootcamp.git (push)
    (base) sdyzzy@SDYZZY-MacBook-Pro GWData-Bootcamp % git remote show origin
    * remote origin
    Fetch URL: git@github.com:SDYZZY/GWData-Bootcamp.git
    Push URL: git@github.com:SDYZZY/GWData-Bootcamp.git
    HEAD branch: main
    Remote branches:
    homework tracked
    main tracked
    Local branch configured for 'git pull':
    homework merges with remote homework
    Local refs configured for 'git push':
    homework pushes to homework (up to date)
    main pushes to main (up to date)

添加远程库

  • 克隆远程库以添加git clone <remote url> 将远程库克隆至本地(一般使用 SSH 地址。如果使用 https 克隆,则不需要 init,也不需要登陆账号)。

    • clone 做了这三件事:初始化本地库、初始化本地库、拉取代码、创建别名。clone 会在当前目录下创建一个和远程库同名的目录并切换进去,然后在该目录下初始化本地库,并为这个远程库指定别名(默认为 origin),最后将远程库的数据拉取并存放在该目录下。因此 git clone 相当于 mkdir + git init + git remote add + git fetch + git checkout
    • origin 是 git 给克隆的远程库的默认别名
  • 获取远程库地址以添加git remote add 别名 远程地址。链接太长,对链接起别名,以后 push 和 pull 就可以直接用别名代替链接,一般就用远程库名作为别名。

    1
    2
    3
    4
    5
    (base) sdyzzy@SDYZZY-MacBook-Pro learn_git % git remote -v 
    (base) sdyzzy@SDYZZY-MacBook-Pro learn_git % git remote add testgit https://github.com/SDYZZY/testgit.git
    (base) sdyzzy@SDYZZY-MacBook-Pro learn_git % git remote -v
    testgit https://github.com/SDYZZY/testgit.git (fetch)
    testgit https://github.com/SDYZZY/testgit.git (push)

从远程库拉取数据至本地库

  • git fetch <remote> 拉取指定远程库中本地所没有的数据。它只会下载数据到本地而不会自动合并,所以需要在合适的时候手动合并(git merge)。
  • git pull 远程库别名或地址 远程分支名。本地库与远程库不同步的时候,将远程库的内容拉取到本地库。
  • git pull 的拉取过程实际上是 git fetch 命令完成的。拉取后远程库的内容会变成本地库的一个远端分支,然后再自动使用 git merge <拉取的> 将远端分支与 main 分支合并。即 git pull = git fetch + git merge显式地使用 fetch 与 merge 命令会更好一些
  • git clonegit pull\fetch 的区别:clone 通常在第一次看到某个项目时将其下载到本地;pull\fetch 则是用于之后的更新。

推送本地库数据至远程库

  • git push 远程库别名或地址 本地分支 将指定的本地分支推送到远程库。
    • 只有本地分支和远程分支同步时才可以直接 push。如果别人先推送过一次,然后自己再推送,则自己的推送会被拒绝。需要先 pull 远程库,然后才可以推送。
  • git push <remote> <local branch>:<remote branch> 推送本地 branch 分支,将其作为远程库的 remote branch 分支,可以用于推送本地分支到一个命名不相同的远程分支。

远程库别名的重命名与移除

  • git remote rename <remote> 修改远程库的别名,同时也会自动修改远程追踪的分支名。
  • git remote rm <remote> 移除远程库,所有和这个远程库相关的远程追踪分支以及配置信息也会一起被删除。

远程跟踪分支

  • git branch -vv 查看本地分支正在追踪哪个远程分支,以及本地分支相对于远程分支是领先(ahead,有本地的 commit 还未 push)、落后(behind)还是都有(远程分支有 commit 还没有 pull 到本地且本地也有 commit 还没有 push)。
  • git branch -u <remote>/<remote branch> 修改当前分支所追踪的上游分支为远程库的 remote branch 分支。
1
2
3
4
5
6
7
8
9
10
11
12
13
(base) sdyzzy@SDYZZY-MacBook-Pro GWData-Bootcamp % git branch -vv
* homework 8faef07 feat: hw-20231130
main e782a20 [origin/main] Merge pull request #39 from iphysresearch/iphysresearch-patch-1
(base) sdyzzy@SDYZZY-MacBook-Pro GWData-Bootcamp % git branch -u origin/homework
branch 'homework' set up to track 'origin/homework'.
(base) sdyzzy@SDYZZY-MacBook-Pro GWData-Bootcamp % git checkout main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
(base) sdyzzy@SDYZZY-MacBook-Pro GWData-Bootcamp % git branch -u teacher/main
branch 'main' set up to track 'teacher/main'.
(base) sdyzzy@SDYZZY-MacBook-Pro GWData-Bootcamp % git branch -vv
homework 8faef07 [origin/homework] feat: hw-20231130
* main e782a20 [teacher/main] Merge pull request #39 from iphysresearch/iphysresearch-patch-1

Github 与合作

参考 gitpro p164

  • 邀请合作者:点击 settings–>collaborators->add people

  • PullRequest 过程

    • fork 别人的仓库到自己账户下。

    • git clone 于自己的本地。

    • 本地修改

    • git push 于自己账户下的仓库。

    • 自己的仓库,点击 Contribute,Open pull request, 输入 PR 相关信息,发送。

      image-20231120210641343

有时在自己fork之后,原项目作了更新,此时想让自己本地的和fork的项目跟上原项目,可以将原项目设置为自己的上游项目,fetch 后手动合并。具体步骤如下:

  • git remote add <原项目别名> <原项目地址>,将原项目设置为远程节点。这一步做完后使用 git remote -v 可以发现现在有两个远程节点,一个是 fork 之后在自己账号下那个仓库的,另一个就是刚设置的原项目仓库。
  • git fetch <原项目别名> <原项目分支>,将最新的原项目抓取下来。
  • git merge FETCH_HEAD 或者 git merge <原项目别名>/<原项目分支>,将抓取的内容和本地合并。此时本地项目已经和原项目进度相同。
  • git push <远程库别名>/<远程分支>,将本地项目推送至远程库,此时 fork 的项目也和原项目进度相同。

==待看:第 4 次课 2h25m,如何接受 PR==

六、Git 原理

使用 git add 将文件添加到暂存区后,git 会生成一个 Blob 对象,这个 Blob 对象存储了文件的内容(不是文件本身,而是压缩后的内容)。然后 git 根据这个 Blob 的内容计算出一个 hash,并以 hash 的前两个字节为目录名,后 38 个字节为文件名,将这个 Blob 对象存储在 .git/objects 目录下(只要内容一样就是同一个 Blob 对象,比如两个不同名的空文件)。

  • 文件的内容以 Blob 对象的形式存放;

  • 文件及目录的名称以 Tree 对象的形式存放,Tree 对象的内容是某些 Blob 对象或者其它 Tree 对象的 hash 及名称(如果 Tree 对象里面还有其它 Tree 对象,那么该 Tree 对象就是个目录)

  • commit 对象指向某个 Tree 对象,并且还对指向前一次的 commit 对象(除了第一次 commit 以外)

  • Tag对象指向某个 commit 对象。

实际上除了 Blob 对象是存储的文件内容,其他对象都可以看作是存储的指针(hash),通过指针来判断归属关系。

Git flow

image-20231120203531511

附录1:其他命令

查看文件和最后一次 commit 的区别

1
git diff <filename>
  • - 表示第 1 个文件。
  • + 表示第 2 个文件
  • @@ -1,2 +1,3@@ 表示比较的区块:
    • 第 1 个文件的第 1 行起的连续 2 行
    • 第 2 个文件的第 1 行起的连续 3 行

附录2:使用过程中遇到的一些问题

  1. 提示 fatal: detected dubious ownership in repository

    原因:根源在于文件夹的所有人和当前用户不一致。

    解决方法:在文件夹的属性-安全-高级里面,更改所有者,并应用到所有的子目录和文件。

  1. Git 报错:

    1
    Updates were rejected because the remote contains work that you do not have locally.

    问题描述:在存在本地库且创建新的远程库后用 README 或者 LICENSE 初始化过的情况下,如果直接 push 就会出现上述报错。问题在于初始化过程使得远程库具有了一些本地库所没有的文件(比如 README),这时直接 push 就会被 git 给 reject。

    解决方法1:在链接远程库之后、push 之前,先 git pull 远程库到本地库(or optionally, git pull origin master --allow-unrelated-histories if you have initialized repo in github and also committed locally),然后再进行 push。

    解决方法2:直接 git push -f <远程分支> <本地分支>,强行 push。

    解决方法3:创建远程库后,不要初始化。