Git Notes

零、我在学习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)至远程库。

注意⚠️:所有的版本控制系统,包括Git,只能跟踪纯文本文件的改动,比如TXT文件、网页、程序源代码等。图片、视频,以及Microsoft的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只显示第一行

设置用户签名

用于区分不同的本地作者身份,在每一个提交的版本之中可以看到是谁提交的(这里的用户名与GitHub的用户名没有关系)。

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

C:\Users\SDYZZY\.gitconfig文件中可以查看是否成功。

初始化本地库

让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文件自动生成,因此无须让Git跟踪它们,这些文件存储在目录\__pycache__中。

1
2
>>>vim .gitignore
__pycache__/ # 忽略__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 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:

查看已有分支

  • git branch -l :查看本地分支。
  • git branch -v:查看本地分支以及最后一次commit的hash值。
  • git branch -a:查看本地、远程分支。

切换分支

git checkout 分支名。可以看到星号已经转移到了hot-fix分支前。切换分支后,目录内的文件也会跟着切换(例如某个文件在A分支有而B分支没有,则从A分支切换到B分支后目录内该文件会消失,再切换回A分支又会出现)。

image-20231113153541773

分支合并

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

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

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

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

  • 冲突合并:两个分支上的同一文件同一位置有两套不同的修改,不能直接合并。

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

    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
    (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",保存
    (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

需要手动合并:打开文件,手动删除多余的,然后添加、提交(此时提交不能带文件名)。此时切回hot-fix分支,文件没有被修改,只有master分支(合并分支)被修改了:

总结

分支master、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与Github

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

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

该命令会在用户主目录下生成 .ssh 目录,包含 id_rsa (私钥)和 id_rsa.pub (公钥)等文件。

创建远程库

1.1点击New Repository

1.2起远程库名,一般与本地库相同

1.3选择公共库与私有库(公共库读取权限公开)

1.4点击code,可以看到远程库的Https和ssh的地址链接

远程库操作

1
2
3
4
5
6
7
8
9
git remote -v                         #  查看当前所有远程地址别名
git remote add 别名 远程库地址 # 起别名
git remote remove 别名 # 删除别名
git push 远程库地址或地址别名 本地分支名 # 推送本地分支上的内容到远程库
git clone 远程库地址 # 将远程库的内容克隆到本地
git pull 远程库地址或地址别名 远程分支名 #将远程库对于分支最新内容拉下来后与当前本地分支直接合并
# 邀请至项目远程库
git fetch <remote name> # fetch远程仓库中的所有分支
git fetch <remote name> <branch> # fetch远程仓库中的指定分支
  • 创建远程库别名:git remote add 别名 远程地址。链接太长,对链接起别名,以后push和pull就可以直接用别名代替链接,一般就用库名作为别名(默认的别名都是origin)。

  • 推送本地库至远程库:git push 别名或远程库链接 分支

  • 拉取远程库至本地库:git pull 远程库别名或地址 远程分支名。本地库与远程库已经不同步了,将远程库的内容拉取到本地库

  • 克隆远程库至本地库:git clone 远程库地址。(一般使用SSH地址。如果使用https克隆,则不需要init,也不需要登陆账号)。clone做了这三件事:拉取代码;初始化本地库;创建别名。

  • git clonegit pull\fetch的区别:clone通常在第一次看到某个项目时将其下载到本地;pull\fetch则是用于之后的更新。

邀请合作者

点击settings–>collaborators->add people

拉取远程库

拉取过程实际上是git fetch命令完成的。拉取后远程库的内容会变成本地库的一个远端分支,然后再使用git merge <拉取的>,将远端分支与main分支合并。即git pull = git fetch + git merge

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的项目也和原项目进度相同。

六、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 <file name>
  • - 表示第 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。

    解决方法:在链接远程库之后、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:创建远程库后,不要初始化。