Git的一些简介、各种命令、Git本身的实现原理以及一些问题的解决方法

资源:

如何写好commit

在线模拟Git

CS50W的Git介绍

ohshitgit (git中可能遇到的各种问题补救)

Git for Professionals Tutorial - Tools & Concepts for Mastering Version Control with Git

下面的各种图片均来自于cs61b、cs50w等课程和网络

Git的一些简介

Git的用处

因为编程本身是一个循序渐进的过程,会有大量的改变,而在多人乃至个人管理这些改变是可能有hello.java、hello2.java……hello最终版.java、hello最最最终不再修改版.java 的情况出现
而Git可以让我们更方便地管理这些过程,而不是弄一堆文件

如果对git存储的文件感兴趣可以cmd+shift+.查看隐藏文件

Git是怎么运作的

实现方法由简单到复杂
假设我们有v1 v2 v3 三个版本

假设我们有v1 v2 v3 三个版本

  1. 单纯地给V1 V2 V3存储在不同的文件夹

    截屏2022-08-07 15.53.21.png

优点是修改很方便,只需要操作1个文件夹就行了,但会有大量的冗余也就是相同文件重复存储

  1. 只存储修改了文件

    不存储相同的文件

    不存储相同的文件

这样做的好处是相同的文件我们只需要处理一份,
但修改需要跨文件夹,而且有哪份作为最新的文件需要花时间判定

截屏2022-08-07 15.56.39.png
  1. 就像实现LLD时存储了size大小,我们引入一个数据结构告诉我们最新的文件是什么

    截屏2022-08-07 15.58.17.png

但是如果多人协作时我们怎么确定谁是最新的版本呢

可以通过命令行git hash-object 文件名获得一个文件的git-SHA1 hash值

  1. 引入时间作为版本依据替代以前的V1 V2 etc.

    截屏2022-08-07 15.59.59.png

但如果有人再同一时间提交的话又怎么区分呢

  1. 引入git-SHA1 hash值

    截屏2022-08-07 16.02.20.png

拥有各种好处,比如可以判断文件是否被修改过

总结:

对于“???”我们有无限的文件,但hash值只有160位,所以可能遇到不同文件hash值相同的情况,不过出现这样的情况的几率无限低

对于“???”我们有无限的文件,但hash值只有160位,所以可能遇到不同文件hash值相同的情况,不过出现这样的情况的几率无限低

Git Commits

存储的内容:

截屏2022-08-07 16.07.33.png

还包括使用git-SHA1 hash值作为ID、使用Serializable储存Commit

分支 Branching

暂时不解释,大概就是可以有人负责不同的工作,最后还可以合并这些东西,后面会有更详细的介绍

截屏2022-08-07 16.10.12.png

Git入门-by Itai的讲解

git init

使用它就表示你要对当前的文件夹进行版本控制,同时这里就变成了存储库(repository)

截屏2022-08-08 17.16.09.png

git status

告诉我们这个存储库的状态

可以看到现在还没有commits(相当于快照)

告诉我们pbj.txt没有被跟踪

告诉我们pbj.txt没有被跟踪


创建一个commits

**git add 文件名**

成功后就记录了改变,但还没提交如果使用**git add *(git add .)**就表示记录当前目录所有文件

**git commit -m “快照的内容”**

就完成了commit,提交了改变

不添加-m的话就会打开vim以让你输入更多内容 :q 退出

**git commit -am “快照的内容”**

结合git add和commit ,这样就commit了所以改变

**git commit -amend**

可以修改上次commit的描述

**git commit -amend -no-edit**

将新的改变一起覆盖到上次的commit,不改变描述, 如果您已经将commit推送到远程存储库,则需要使用强制推送将更改推送到远程存储库。使用以下命令:git push --force


git log

告诉我们所有的commit

cat 文件名可以看到文件内容
cp 文件名 ./另一个文件名复制命令在当前文件夹复制了一份文件
nano 文件名 终端内置的一个类似vim的编辑器
subl 文件名用sublime打开

截屏2022-08-08 17.25.20.png

git show

和log差不多只是可以显示某次commit的更多内容,不常用

git checkout

ps现在有Git switch可以作为替代品

git checkout "commit id"
粘贴commit id然后粘贴到后面就能恢复那个commit时的文件

git checkout master/main就可以回到最新的commit

截屏2022-08-08 17.41.53.png

本质上就是掌控HEAD指针在什么位置,如果有没有commit的内容就不能进行这个操作

如果在git cheakout "commit id" “文件地址”可以返回具体文件,如果要保存这个状态记得再次**git commit -m “快照的内容”**

gitk

Git 存储库浏览器 (Monterey似乎不能使用)

git reset

恢复到之前某个快照

比如强制恢复到一个commit/分支
git reset --hard <commit>

git reset --hard origin/mater

git reset HEAD [file]

取消暂存(add)的文件
比如在不小心跟踪了不想要的文件时使用

Git 分支

分支允许您同时跟踪工作的多个不同版本。将分支视为替代维度的一种方式。也许一个分支是选择使用链表的结果,而另一个分支是选择使用数组的结果。

默认分支通常称为master/mian分支

https://sp19.datastructur.es/materials/guides/img/graph2.svg

创建一个分支可以让你跟踪代码的多个不同版本,一旦你完成了一个部分并希望它加入你的代码的其余部分,你就可以轻松地在版本之间切换并将分支合并在一起。

在有重大更改、不确定是否并入(尝试)、分开工作等时很有用

  • 例子

    例如,假设到目前为止您已经完成了一半的项目。还有一个困难的部分要做,你不知道该怎么做。也许你对如何做有三种不同的想法,但你不确定哪个会奏效。此时,创建一个分支master 并尝试您的第一个想法可能是一个好主意。

    • 如果您的代码有效,您可以将分支合并回您的主代码(在 master分支上)并提交您的项目。
    • 如果您的代码不起作用,请不要担心还原您的代码并不得不操作 Git 历史记录。您可以简单地切换回master不会有任何更改的 ,创建另一个分支,然后尝试您的第二个想法。

    这可以一直持续到您找到编写代码的最佳方式,并且您只需将最终工作的分支合并到master最后。

比如我想尝试修改一些css代码看看效果就可以创建一个branch来修改,到时候出问题了可以丢掉这些修改,如果修改还不错就可以merge回主线


比如开发某个新功能时搞一个分支,这样就可以放心地尝试,然后原本的代码发现了个bug就在原有的代码上修改

HEAD指针指向的就是你当前的分支

HEAD指针指向的就是你当前的分支

等新功能写完了就可以合并新功能和修改了bug的代码

截屏2022-12-08 13.46.33.png

git branch

会告诉你你目前所在的分支和其他分支

git branch [new-branch-name]

从当前分支创建一个分支

git checkout [destination-branch]

更改HEAD指针引用的分支从一个分支切换到另一个分支。

git checkout -b [new-branch-name]

将前两个命令组合起来创建一个新分支,然后使用这个命令检查它
也就是创建一个新的分支同时切换到新创建的分支

git branch -d [branch-to-delete]

删除分支

git branch -v

确定所在的分支 (-v还将列出每个分支上的最后一次提交)

合并分支

git merge [branch-name]

将这个分支合并到我当前的分支,

合并示例

https://sp19.datastructur.es/materials/guides/img/graph3.svg

fixing-ai-heuristics合并到master

git checkout master
git merge fixing-ai-heuristics

在这之后新的分支就拥有了两个父级

https://sp19.datastructur.es/materials/guides/img/graph4.svg

合并冲突

解除合并冲突

<<<<<<< HEAD
for (int i = 0; i < results.length; i++) {
    println(results[i]);
    println("FIX ME!");
}
=======
int[] final = int[results.length];
for (int i = 0; i < results.length - 1; i++) {
    final[i] = results[i] + 1;
    println(final[i]);
}
>>>>>>> fixing-ai-heuristics

比如有以上冲突,删除不想要的就可以提交了

git rebase

第二种合并分支的方法是 git rebase。Rebase 实际上就是取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个的放下去。Rebase 的优势就是可以创造更线性的提交历史,这听上去有些难以理解。如果只允许使用 Rebase 的话,代码库的提交历史将会变得异常清晰。

使用git rebase main 将C3的bugFix*移动到了main上

提交记录 C3 依然存在(树上那个半透明的节点),而 C3' 是我们 Rebase 到 main 分支上的 C3 的副本

截屏2022-12-08 14.54.46.png

现在我们切换到了 main 上。把它 rebase 到 bugFix 分支上……

git rebase bugFix 后如图

由于 bugFix继承自 main,所以 Git 只是简单的把 main分支的引用向前移动了一下而已。

截屏2022-12-08 14.57.23.png

rebase还可以用于合并commit,比如git rebase -i HEAD~2

git三种合并方式的区别

(来自ChatGPT)

Git 的 mergerebase 命令都可以用来合并多个分支,但是它们与 cherry-pick 命令有着很大的不同。

首先,merge 命令是将两个分支合并为一个分支。例如,假设你在分支 new-branch 上做出了一些改变,然后想要将这些改变合并到 master 分支上,可以使用如下命令:

$ git checkout master
$ git merge new-branch

在这段代码中,首先使用 git checkout 命令切换到了 master 分支上,然后使用 git merge 命令将 new-branch 分支合并到了 master 分支上。

注意,在使用 merge 命令合并分支时,Git 会自动创建一个新的提交,用来记录合并的信息。这个提交称为合并提交,其中会包含所有合并前两个分支的提交。

相比之下,cherry-pick 命令是将单个提交从一个分支弄到另一个分支上。

Git 的 rebase 命令是将一个分支的提交“剪切”并粘贴到另一个分支上。例如,假设你在分支 new-branch 上做出了一些改变,然后想要将这些改变“剪切”并粘贴到 master 分支上,可以使用如下命令:

$ git checkout new-branch
$ git rebase master

在这段代码中,首先使用 git checkout 命令切换到了 new-branch 分支上,然后使用 git rebase 命令将 new-branch 分支的提交“剪切”并粘贴到了 master 分支上。

注意,在使用 rebase 命令时,Git 会将每个提交的内容拆开,然后逐个提交。这意味着,你每次都需要手动提交。这也意味着,在使用 rebase 命令时,你可以选择只提交你想要的提交。

总的来说,merge 命令是将两个分支合并为一个分支,并创建一个合并提交;rebase 命令是将一个分支的提交“剪切”并粘贴到另一个分支上,并逐个提交。而 cherry-pick 命令是将单个提交从一个分支弄到另一个分支上。

其他炫酷的Git功能(仅了解)

There are tons of other cool Git commands. Unfortunately, we need to continue on to discuss remote repositories. Thus, this segment will just list some other interesting features that you are encouraged to explore in your own time:

  • Stashing

    Stashing allows you to save your changes onto a stack without making a more permanent commit. It is equivalent to picking up your work-in-progress and placing it in a box to get back to later. In the meantime, your desk is now clean.

    Why might you want to use this?

    • Your files may be in a disorganized state, and you don’t want to commit yet, but you also don’t want to get rid of your changes.
    • You modified multiple files, but you dislike your changes and you would just like to get things back to how they were after your most recent commit. Then you can stash your code and then drop that stash rather than having to manually revert multiple files. (Be careful with this usage!)
    • You modified files but accidentally modified them on the wrong branch. Then you can stash your changes, switch branches, and unstash your changes so that they are all in the new branch.
  • Rewriting History

    Let’s say you want to do more than change your last commit or drop changes to your files before your most recent commit. What if you want to do something crazy like rewrite history? You can change multiple commit messages, splits one commits into two, and reorder commits.

  • Rebasing

    Rebasing changes the parent commit of a specific commit. In doing this, it changes the commits so that it is no longer the same.

    Rebase can be used as an alternative to merge for integrating changes from one branch to another. It is quite different from merge in that merge creates a new commit that has both parent branch commits as parents. Rebasing takes one set of commits from a branch and places them all at the end of the other branch.

    There are different reasons why you would want to use merge versus rebase. One of these reasons is that rebase leads to a cleaner history when working with many different branches and team members.

  • Reset

    Perhaps you decide that you want things to be how they were a certain number of commits ago. You can use reset if you are absolutely sure that you don’t want the last few commits. Reset is quite a nuanced command, so read carefully before attempting use.

  • Revert

    Revert allows you to reverse the changes introduced by certain commits by recording new commits to undo the changes. This is a safer option that simply throwing away past commits. But again, use this with caution.

  • Cherry Pick

    Cherry pick allows you to apply the changes introduced by some existing commits. For example, if you have two different branches, and your current branch lacks one or two commits that would be helpful but are only in the other branch, then you can cherry pick to grab those commits without merging or rebasing to get all the commits.

There are far more features and commands not mentioned here. Feel free to explore more and search for answers. There most likely exists a Git command for nearly everything you would want to do.

Git Head 在提交树上前后移动的方法

cat .git/HEAD可以看到HEAD的指向,如果是指向的引用还可以用git symbolic-ref HEAD查看它的指向

分离的 HEAD(也就是分离头指针/detached HEAD)

分离的 HEAD 就是让其指向了某个具体的提交记录而不是分支名

现在是这样的

HEAD -> main -> C1

HEAD 指向 main, main 指向 C1

截屏2022-12-08 15.27.51.png

然后执行git checkout C1,变成了

HEAD -> C1

截屏2022-12-08 15.30.30.png

通过指定提交记录哈希值的方式在 Git 中移动不太方便。在实际应用时,并没有像本程序中这么漂亮的可视化提交树供你参考,所以你就不得不用 git log 来查查看提交记录的哈希值。

并且哈希值在真实的 Git 世界中也会更长(译者注:基于 SHA-1,共 40 位)。例如前一关的介绍中的提交记录的哈希值可能是 fed2da64c0efc5293610bdd892f82a58e8cbc5d8。舌头都快打结了吧...

比较令人欣慰的是,Git 对哈希的处理很智能。你只需要提供能够唯一标识提交记录的前几个字符即可。因此我可以仅输入fed2 而不是上面的一长串字符

相对引用

移动HEAD

这样就可以返回到前第x个的commit而不用记他们的哈希值了所以 main^ 相当于“main 的父节点”。main^^ 是 main 的第二个父节点.也可以可以一直使用 HEAD^ 向上移动,(git checkout HEAD^)

当然一直这样也很烦可以用HEAD~数字的方式指定向上几次(git checkout HEAD~4)

截屏2022-12-08 15.35.40.png

移动分支

可以直接使用 -f选项让分支指向另一个提交

git branch -f main HEAD~3

上面的命令会将 main 分支强制指向 HEAD 的第 3 级父提交。

相对引用为我们提供了一种简洁的引用提交记录 C1的方式, 而 -f则容许我们将分支强制移动到那个位置。

截屏2022-12-08 15.40.32.png

或者使用git branch -f 要移动的分支 指定的位置可以把一个分支指向的位置随意移动(移动HEAD的话就直接git checkout 制定的位置 就行了)

正如我前面所说,通过哈希值指定提交记录很不方便,所以 Git 引入了相对引用。这个就很厉害了!

使用相对引用的话,你就可以从一个易于记忆的地方(比如 bugFix 分支或 HEAD)开始计算。

相对引用非常给力,这里我介绍两个简单的用法:

  • 使用 ^ 向上移动 1 个提交记录
  • 使用 ~<num> 向上移动多个提交记录,如 ~3

另外**origin/master**, **origin/HEAD**指针是GitHub远程的状态

撤销变更

git reset

通过把分支记录回退几个提交记录来实现撤销改动。你可以将这想象成“改写历史”。git reset
 向上移动分支,原来指向的提交记录就跟从来没有提交过一样。

使用git reset HEAD~1 Git 把 main 分支移回到 C1;现在我们的本地代码库根本就不知道有 C2 这个提交了。

(注:在reset后, C2 所做的变更还在,但是处于未加入暂存区状态。)

截屏2022-12-08 15.55.25.png

git revert

虽然在你的本地分支中使用 git reset 很方便,但是这种“改写历史”的方法对大家一起使用的远程分支是无效的哦!

为了撤销更改并分享给别人,我们需要使用 git revert

使用了git revert HEAD

在我们要撤销的提交记录后面多了一个新提交!这是因为新提交录 C2' 引入了更改 —— 这些更改刚好是用来撤销 C2 这个提交的。也就是说 C2' 的状态与 C1 是相同的。

revert 之后就可以把你的更改推送到远程仓库与别人分享啦。

C1 = C2’

C1 = C2’

Github 远程仓库

术语:储存库/仓库:Repositories,也会简称为repo

  • git remote

    显示现有的远程仓库

    如果想查看你已经配置的远程仓库服务器,可以运行 git remote
    命令。 它会列出你指定的每一个远程服务器的简写
    所有其他命令都使用其关联的简写

  • git remote -v

    显示现有的远程仓库详细信息

  • git remote add [short-name] [remote-url]

    添加远程仓库with简写和远程地址,这只是相当于一个指针没有添加文件,需要有个仓库来使用它,而git clone则是把远程上所有的东西下载到本地

  • git remote rename [old-name] [new-name]

    重命名简写

  • git remote rm [remote-name]

    删除远程仓库

  • git clone [remote-url]

    git clone [remote-url] [directory-name]//允许为仓库设定不同的名称
    拷贝一个 Git 仓库到本地,让自己能够查看该项目,或者进行修改。
    (相当于把远程仓库的东西拿来并git init)
    在您的本地计算机上制作指定存储库的副本。还会创建一个工作目录,其中的文件排列方式与下载存储库中的最新快照完全相同。还记录远程存储库的 URL,以供后续网络数据传输,并为其指定特殊的远程存储库名称“origin”。

  • git push [remote-name] [remote-branch]

    将本地的修改推送到远程仓库(HEAD指针的commit)

  • git push [remote-repo-name] master

    将文件的最新副本推送到远程存储库名称

    git push -u origin master -f 可以在本地版本低于远程时强制推送

  • git fetch [remote-name]

    类似下载commit,不会合并

    比如别人创建了一个新分支

    $ git fetch origin
    //拉取
    $ git branch review-ai-fix origin/fixing-ai-heuristics
    //命令创建一个名为review-ai-fix的分支
    //来查看远程仓库origin的fixing-ai-heuristics分支
    $ git checkout review-ai-fix
    
  • git pull [remote-name] [remote-branch-name]

    相当于fetch+merge操作,会获得最新的更改并且合并到我的HEAD中

    获取文件的最新副本,如 remote-repo-name 中所示

  • git rebase

    似乎是合并branch的操作,暂时不考虑学习

  • 删除Git仓库

    只需要删除文件夹隐藏的.git就行了

    $ git branch #显示本地所有分支 
    * master
    
    $ git init #初始化仓库 
    $ ls -a #查看内部文件 
    $ rm -rf .git #强删.git
    

更多课程操作相关内容可以看课程Git使用帮助

git的结构

add后就在Staging Area,commit后就进入Commits

add后就在Staging Area,commit后就进入Commits

首先使用add后就会进入staging area,
然后我们commit就会创建一个commit,
同时注意我们有两个指针
HEAD:当前的文件内容
master:最近一个commit
如果两个不一样就会提示进去了HEAD和master分离

Git Intro - Part 4Git Intro - Part 5 介绍了多人合作情况下,commit的自动合并

如果自动合并失败手动修改后再次commit就可以了

Git Intro - Part 6介绍了一个本地仓库多个远程仓库的情况

Git学习游戏
实现自己的Git(非cs61b版)

一些设置

中英文切换

echo "alias git='LANG=en_GB git'" >> ~/.zshrc

如果想要切回中文进入 vim .zshrc 文件中的环境变量配置删除即可

问题

头针分离 xCode不能push

截屏2022-12-04 11.40.18.png

一些小问题

使用git pull 远程和本地不一样的commit应该自动使用merge,但我的却是让我制定哪一个

所以用git config --global --add pull.rebase/merge true修改默认行为

或者使用等命令

git config pull.merge true 使用merge

git config pull.rebase true 使用rebase

git config pull.ff only # fast-forward only

merge冲突的代码

merge冲突的代码

merge冲突的提示
修改后再次commit并push就能同步两边了

merge冲突的提示
修改后再次commit并push就能同步两边了