Git的一些简介、各种命令、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个文件夹就行了,但会有大量的冗余也就是相同文件重复存储
-
只存储修改了文件
不存储相同的文件
这样做的好处是相同的文件我们只需要处理一份,
但修改需要跨文件夹,而且有哪份作为最新的文件需要花时间判定
-
就像实现LLD时存储了size大小,我们引入一个数据结构告诉我们最新的文件是什么
但是如果多人协作时我们怎么确定谁是最新的版本呢
可以通过命令行git hash-object 文件名
获得一个文件的git-SHA1 hash值
-
引入时间作为版本依据替代以前的V1 V2 etc.
但如果有人再同一时间提交的话又怎么区分呢
-
引入git-SHA1 hash值
拥有各种好处,比如可以判断文件是否被修改过
总结:
对于“???”我们有无限的文件,但hash值只有160位,所以可能遇到不同文件hash值相同的情况,不过出现这样的情况的几率无限低
Git Commits
存储的内容:
还包括使用git-SHA1 hash值作为ID、使用Serializable储存Commit
分支 Branching
暂时不解释,大概就是可以有人负责不同的工作,最后还可以合并这些东西,后面会有更详细的介绍
Git入门-by Itai的讲解
git init
使用它就表示你要对当前的文件夹进行版本控制,同时这里就变成了存储库(repository)
git status
告诉我们这个存储库的状态
可以看到现在还没有commits(相当于快照)
告诉我们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打开
git show
和log差不多只是可以显示某次commit的更多内容,不常用
git checkout
ps现在有Git switch可以作为替代品
git checkout "commit id"
粘贴commit id然后粘贴到后面就能恢复那个commit时的文件
git checkout master/main
就可以回到最新的commit
本质上就是掌控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
分支
创建一个分支可以让你跟踪代码的多个不同版本,一旦你完成了一个部分并希望它加入你的代码的其余部分,你就可以轻松地在版本之间切换并将分支合并在一起。
在有重大更改、不确定是否并入(尝试)、分开工作等时很有用
-
例子
例如,假设到目前为止您已经完成了一半的项目。还有一个困难的部分要做,你不知道该怎么做。也许你对如何做有三种不同的想法,但你不确定哪个会奏效。此时,创建一个分支
master
并尝试您的第一个想法可能是一个好主意。- 如果您的代码有效,您可以将分支合并回您的主代码(在
master
分支上)并提交您的项目。 - 如果您的代码不起作用,请不要担心还原您的代码并不得不操作 Git 历史记录。您可以简单地切换回
master
不会有任何更改的 ,创建另一个分支,然后尝试您的第二个想法。
这可以一直持续到您找到编写代码的最佳方式,并且您只需将最终工作的分支合并到
master
最后。 - 如果您的代码有效,您可以将分支合并回您的主代码(在
比如我想尝试修改一些css代码看看效果就可以创建一个branch来修改,到时候出问题了可以丢掉这些修改,如果修改还不错就可以merge回主线
比如开发某个新功能时搞一个分支,这样就可以放心地尝试,然后原本的代码发现了个bug就在原有的代码上修改
HEAD指针指向的就是你当前的分支
等新功能写完了就可以合并新功能和修改了bug的代码
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]
将这个分支合并到我当前的分支,
合并示例
将fixing-ai-heuristics
合并到master
git checkout master
git merge fixing-ai-heuristics
在这之后新的分支就拥有了两个父级
合并冲突
解除合并冲突
<<<<<<< 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 的副本
现在我们切换到了 main
上。把它 rebase 到 bugFix
分支上……
用git rebase bugFix
后如图
由于 bugFix
继承自 main
,所以 Git 只是简单的把 main
分支的引用向前移动了一下而已。
rebase还可以用于合并commit,比如git rebase -i HEAD~2
git三种合并方式的区别
(来自ChatGPT)
Git 的 merge
和 rebase
命令都可以用来合并多个分支,但是它们与 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 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.
-
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 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 tomerge
for integrating changes from one branch to another. It is quite different frommerge
in thatmerge
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
versusrebase
. One of these reasons is thatrebase
leads to a cleaner history when working with many different branches and team members. -
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
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
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 cancherry 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
然后执行git checkout C1,变成了
HEAD -> C1
通过指定提交记录哈希值的方式在 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
)
移动分支
可以直接使用 -f
选项让分支指向另一个提交
git branch -f main HEAD~3
上面的命令会将 main 分支强制指向 HEAD 的第 3 级父提交。
相对引用为我们提供了一种简洁的引用提交记录 C1
的方式, 而 -f
则容许我们将分支强制移动到那个位置。
或者使用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
所做的变更还在,但是处于未加入暂存区状态。)
git revert
虽然在你的本地分支中使用 git reset
很方便,但是这种“改写历史”的方法对大家一起使用的远程分支是无效的哦!
为了撤销更改并分享给别人,我们需要使用 git revert
。
使用了git revert HEAD
在我们要撤销的提交记录后面多了一个新提交!这是因为新提交录 C2'
引入了更改 —— 这些更改刚好是用来撤销 C2
这个提交的。也就是说 C2'
的状态与 C1
是相同的。
revert 之后就可以把你的更改推送到远程仓库与别人分享啦。
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就会创建一个commit,
同时注意我们有两个指针
HEAD:当前的文件内容
master:最近一个commit
如果两个不一样就会提示进去了HEAD和master分离
Git Intro - Part 4、Git Intro - Part 5 介绍了多人合作情况下,commit的自动合并
如果自动合并失败手动修改后再次commit就可以了
Git Intro - Part 6介绍了一个本地仓库多个远程仓库的情况
一些设置
中英文切换
echo "alias git='LANG=en_GB git'" >> ~/.zshrc
如果想要切回中文进入 vim .zshrc 文件中的环境变量配置删除即可
问题
头针分离 xCode不能push
一些小问题
使用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冲突的提示
修改后再次commit并push就能同步两边了