1.创建版本库
git init 创建版本库
git add readme.txt 添加到暂存区
git commit -m “xx” 提交到本地仓库
git status 查看状态
git log --pretty=oneline 提交日志
git reflog 命令记录 回退到未来(比如现在回退版本后想回到现在)
2.版本回退
git reset --hard commit_id/HEAD^ 版本回退
git diff 比较不同,具体情况不同
3.撤销修改
撤销工作区修改:
1 没有add过
git checkout -- readme.txt 撤销工作区修改
如果readme没add到暂存区,就回到版本库,如果放到暂存区,回到暂存区状态
即总是回到最后一次git commit或 git add时的状态。
2.已经add过,没commit
git reset HEAD readme.txt 撤销暂存区修改到最近的commit
git checkout -- readme.txt 在丢弃工作区修改
3.已经commit过
版本回退 git reset --hard commit_id/HEAD^
4. 删除文件
工作区删除了某文件,
1.确实要从版本库删除该文件, git rm readme.txt git commit
2.误删 git checkout readme.txt
5.远程仓库
1.创建SSH Key,用户主目录下.ssh目录有没有id_rashe和id_rsa.pub。
若没有 ssh-keygen -t rsa -C "邮箱" 生成 密钥
2.在github设置里放入公钥
5.1添加远程仓库
1.创建好远程仓库,在本地仓库
git remote add origin git@github.com:xxx/xxx.git origin是远程库的名字
2.推送
第一次 git push -u origin master 之后 git push origin master
5.2删除远程库
如果添加的时候地址写错了,或想删远程库
1.建议先 git remote -v 查看远程库信息
2.git remote rm <name> 删除<name> e.g. origin即删除origin。
此处删除仅是解除了本地和远程的绑定关系,并不是物理上删除了远程库。
远程库本身没任何改动。要真正删远程库,需要登录到GitHub再删除。
5.3克隆远程库
git clone git@github.com:xxx/xxx.git
6.分支管理
HEAD指向当前分支,当前分支指向提交,e.g. HEAD指向master,master指向提交
如果创建一个dev分支,则HEAD指向dev,即当前分支。原理见廖雪峰
https://www.liaoxuefeng.com/wiki/896043488029600/900003767775424
6.1创建与合并分支
创建分支并切换 git checkout -b dev = git branch dev + git checkout dev
git switch -c dev = git branch dev + git switch dev
查看分支 git branch 标记*的为当前分支
如果在dev分支做了修改,想合并到master分支
1.切换 git checkout master
2.合并指定分支到当前分支 git merge dev 当前为master
合并采用的是Fast-forward,也就是直接把master指向dev的当前提交
3.删除dev分支 git branch -d dev
6.2解决冲突
假如在feature1和master都对readme做了修改并commit,git无法快速合并,只能试图把各自修改合并起来,但可能冲突
1.将冲突的文件修改之后再进行add commit
git log --graph --pretty=oneline --abbrev-commit 查看分支合并情况
2. 删除feature1分支 git branch -d feature1
6.3分支管理策略
通常,合并分支时,Git会用Fast forward,但这种模式下,删除分支后,会丢掉分支信息。
如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
git merge --no-ff -m "merge with no-ff" dev
实际开发中,分支管理的基本原则:
首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
6.4Bug分支
bug可以通过一个新的临时分支来修复,修复后,合并分支,然后将临时分支删除。
当接到一个修复代号101的bug的任务时,很自然地,创建一个分支issue-101来修复它,但是,等等,当前正在dev上进行的工作还没有提交:
不是不想提交而是还没完成,利用
1.git stash 把当前工作现场“储藏”起来,等以后恢复现场后继续工作。
2.确定在哪个分支修BUG,例如在master分支,git checkout master
3.在master分支创建临时分支 git checkout -b issue-101
4.修复
5.修复完成,切换到master并合并,git switch master + git merge --no-ff -m "merged bug fix 101" issue-101
6.删除issue-101分支 git branch -d issue-101
7.回dev git switch dev。查看dev的stash, git stash list
8.恢复工作现场。git stash apply恢复,git stash drop删除。或者git stash pop恢复并删除
也可以多次stash,先用git stash list查看,然后恢复指定的,git stash apply stash@{0}
9.把master修复的bug复制到dev
cherry-pick命令,复制一个特定的提交到当前分支:git cherry-pick 4c805e2
6.5 Feature分支
每添加一个新功能,最好新建一个feature分支,在上面开发,完成后,合并,最后,删除该feature分支。
例如,开发代号为Vulcan的新功能
1.git switch -c feature-vulcan 开发代码…
2.git add . git commit 开发完毕后提交
3.(1)切换回dev并合并git switch dev, git merge --no-ff -m “merge with no-ff” devfeature-vulcan
(2)取消新feature, git branch -D feature-vulcan 因为未合并需要强行删除
6.6多人协作
查看远程库的信息
git remote
git remote -v 查看详细信息。显示了可以抓取和推送的origin的地址。如果没有推送权限,就看不到push的地址。
推送分支
git push origin master/dev 要指定本地分支,推送master或dev
哪些分支需要推送?
--master分支是主分支,因此要时刻与远程同步;
--dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;
--bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;
--feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。
抓取分支
多人协作,从远程库克隆在本地只有master分支。
如果想在dev分支上开发,需要创建远程origin的dev分支到本地
git checkout -b dev origin/dev。然后开发->add->commit->push
如果同时对某文件作了修改则无法push,有冲突。
解决方法:
1.git pull把最新的提交从origin/dev抓下来,然后,在本地合并,解决冲突,再推送
2.git pull也失败了,原因是没有指定本地dev分支与远程origin/dev分支的链接,根据提示,设置dev和origin/dev的链接:
git branch --set-upstream-to=origin/dev dev
3.git pull 成功,但是合并有冲突,需要手动解决,解决的方法和分支管理中的解决冲突完全一样。解决后,提交commit,再push:
多人协作的工作模式:
1.可以试图用git push origin <branch-name>推送自己的修改;
2.如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;
3.如果合并有冲突,则解决冲突,并在本地提交;
4.没有冲突或者解决掉冲突后,再用git push origin <branch-name>推送就能成功!
5.如果git pull提示no tracking information,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to <branch-name> origin/<branch-name>。
6.7Rebase
直接pull 会分叉,用git rebase 可以把未push的内容放到远程最新commit之后
rebase操作可以把本地未push的分叉提交历史整理成直线;
rebase的目的是使得我们在查看历史提交的变化时更容易,因为分叉的提交需要三方对比。
提交
git commit 生成新的分支(子节点)
分支
git branch bugFix创建名为bugFix的分支。目前有main和bugFix
git commit后,main分支前进了,因为main中的表示当前所在的分支,
如果想将修改提交到新的分支,git checkout bugFix切换分支然后git commit
更简洁的方式,创建新分支同时切换 git checkout -b bugFix
分支与合并
git merge
git merge bugFix 把bugFix合并到main里 (main*)
再把 main 分支合并到 bugFix:git checkout bugFix; git merge main (bugFix*)
因为 main 继承自 bugFix,Git 什么都不用做,只是简单地把 bugFix 移动到 main 所指向的那个提交记录。
git rebase
Rebase 实际上就是取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个的放下去。
git rebase mian (bugFix*) 我们想要把 bugFix 分支里的工作直接移到 main 分支上。
移动以后会使得两个分支的功能看起来像是按顺序开发,但实际上它们是并行开发的。
切换到 main 上。把它 rebase 到 bugFix 分支上 git rebase bugFix (main*)
由于 bugFix 继承自 main,所以 Git 只是简单的把 main 分支的引用向前移动了一下
在提交树上移动
HEAD
HEAD 是一个对当前检出记录的符号引用 —— 也就是指向你正在其基础上进行工作的提交记录。
HEAD 总是指向当前分支上最近一次提交记录。大多数修改提交树的 Git 命令都是从改变 HEAD 的指向开始的。
HEAD 通常情况下是指向分支名的(如 bugFix)。在你提交时,改变了 bugFix 的状态,这一变化通过 HEAD 变得可见。
绝对引用
git checkout <提交号> (通过git log 查看)
相对引用
使用相对引用的话,你就可以从一个易于记忆的地方(比如 bugFix 分支或 HEAD)开始计算。
相对引用非常给力,这里我介绍两个简单的用法:
1.使用 ^ 向上移动 1 个提交记录 git checkout bugFix^ (多个^就是移动多次)
2.使用 ~<num> 向上移动多个提交记录,如 ~3 git checkout HEAD~4 (从HEAD向上移动4次)
强制修改分支位置
git branch -f main HEAD~3 将 main 分支强制指向 HEAD 的第 3 级父提交。
撤销变更
git reset 撤销本地分支(local)
git reset HEAD^ 通过把分支记录回退几个提交记录来实现撤销改动。
可以将这想象成“改写历史”。git reset 向上移动分支,原来指向的提交记录就跟从来没有提交过一样。
git revert HEAD^ 用来撤销远程分支(pushed)
撤销记录成为一个新的节点分支,功能同git reset,但是之气那提交的记录保存了
整理提交记录
开发人员有时会说“我想要把这个提交放到这里, 那个提交放到刚才那个提交的后面”,
而接下来就讲的就是它的实现方式,非常清晰、灵活,还很生动。
git cherry-pick <提交号>...
将一些提交复制到当前所在的位置(HEAD)下面, Cherry-pick 是最直接的方式了。
git cherry-pick C2 C4 将side分支上的C2和C4提交记录复制到main下(main*)
交互式的 rebase
当知道所需要的提交记录(并且还知道这些提交记录的哈希值)时, 用 cherry-pick 再好不过了 —— 没有比这更简单的方式了。
但是如果你不清楚你想要的提交记录的哈希值呢? 我们可以利用交互式的 rebase —— 如果你想从一系列的提交记录中找到想要的记录, 这就是最好的方法了
git rebase -i HEAD~4 出来一个交互框 自己拖动
本地栈式提交
来看一个在开发中经常会遇到的情况:我正在解决某个特别棘手的 Bug,为了便于调试而在代码中添加了一些调试命令并向控制台打印了一些信息。
这些调试和打印语句都在它们各自的提交记录里。最后我终于找到了造成这个 Bug 的根本原因,解决掉以后觉得沾沾自喜!
最后就差把 bugFix 分支里的工作合并回 main 分支了。你可以选择通过 fast-forward 快速合并到 main 分支上,但这样的话 main 分支就会包含我这些调试语句了。你肯定不想这样,应该还有更好的方式……
1.先用 git rebase -i 将提交重新排序,然后把我们想要修改的提交记录挪到最前
2.然后用 git commit --amend 来进行一些小修改
3.接着再用 git rebase -i 来将他们调回原来的顺序
4.最后我们把 main 移到修改的最前端
git tag [标签] [提交号]
git describe [ref] 输出 __g
tag 表示的是离 ref 最近的标签,
numCommits 是表示这个 ref 与 tag 相差有多少个提交记录,
hash 表示的是你所给定的 ref 所表示的提交记录哈希值的前几位。
远程
git fetch
1.从远程仓库下载本地仓库中缺失的提交记录
2.更新远程分支指针(如 o/main)
git fetch 并不会改变你本地仓库的状态。它不会更新你的 main 分支,也不会修改你磁盘上的文件。
git pull = git fetch + git merge 拉取远程并合并到本地
git pull --rebase = git fetch + git rebase
git push 推送到远程
如果远程仓库更新了则不能直接push 应该先git fetch;git rebase o/main 或者 git merge o/main; git push
简化的命令: git pull; git push
git pull --rebase; git push
远程服务器拒绝!(Remote Rejected)
如果你是在一个大的合作团队中工作, 很可能是main被锁定了, 需要一些Pull Request流程来合并修改。
如果你直接提交(commit)到本地main, 然后试图推送(push)修改, 你将会收到这样类似的信息:
! [远程服务器拒绝] main -> main (TF402455: 不允许推送(push)这个分支; 你必须使用pull request来更新这个分支.)
为什么会被拒绝?
远程服务器拒绝直接推送(push)提交到main, 因为策略配置要求 pull requests 来提交更新.
你应该按照流程,新建一个分支, 推送(push)这个分支并申请pull request,但是你忘记并直接提交给了main.现在你卡住并且无法推送你的更新.
解决办法
新建一个分支feature, 推送到远程服务器. 然后reset你的main分支和远程服务器保持一致, 否则下次你pull并且他人的提交和你冲突的时候就会有问题
git checkout -b feature c2; git push origin feature
远程操作分支用rebase还是merge?
在开发社区里,有许多关于 merge 与 rebase 的讨论。以下是关于 rebase 的优缺点:
优点:Rebase 使你的提交树变得很干净, 所有的提交都在一条线上
缺点:Rebase 修改了提交树的历史
远程跟踪分支
在前几节课程中有件事儿挺神奇的,Git 好像知道 main 与 o/main 是相关的。当然这些分支的名字是相似的,可能会让你觉得是依此将远程分支 main 和本地的 main 分支进行了关联。这种关联在以下两种情况下可以清楚地得到展示:
pull 操作时, 提交记录会被先下载到 o/main 上,之后再合并到本地的 main 分支。隐含的合并目标由这个关联确定的。
push 操作时, 我们把工作从 main 推到远程仓库中的 main 分支(同时会更新远程分支 o/main) 。这个推送的目的地也是由这种关联确定的!
远程跟踪
直接了当地讲,main 和 o/main 的关联关系就是由分支的“remote tracking”属性决定的。main 被设定为跟踪 o/main —— 这意味着为 main 分支指定了推送的目的地以及拉取后合并的目标。
你可能想知道 main 分支上这个属性是怎么被设定的,你并没有用任何命令指定过这个属性呀!好吧, 当你克隆仓库的时候, Git 就自动帮你把这个属性设置好了。
当你克隆时, Git 会为远程仓库中的每个分支在本地仓库中创建一个远程分支(比如 o/main)。然后再创建一个跟踪远程仓库中活动分支的本地分支,默认情况下这个本地分支会被命名为 main。
克隆完成后,你会得到一个本地分支(如果没有这个本地分支的话,你的目录就是“空白”的),但是可以查看远程仓库中所有的分支(如果你好奇心很强的话)。这样做对于本地仓库和远程仓库来说,都是最佳选择。
这也解释了为什么会在克隆的时候会看到下面的输出:
local branch "main" set to track remote branch "o/main"
git push 参数
git push
参数是什么意思呢?我们稍后会深入其中的细节, 先看看例子, 这个命令是:
git push origin main
把这个命令翻译过来就是:
切到本地仓库中的“main”分支,获取所有的提交,再到远程仓库“origin”中找到“main”分支,将远程仓库中没有的提交记录都添加上去,搞定之后告诉我。
我们通过“place”参数来告诉 Git 提交记录来自于 main, 要推送到远程仓库中的 main。它实际就是要同步的两个仓库的位置。
需要注意的是,因为我们通过指定参数告诉了 Git 所有它需要的信息, 所以它就忽略了我们所检出的分支的属性!
要同时为源和目的地指定 的话,只需要用冒号 : 将二者连起来就可以了:
git push origin :
git fetch参数和git push相似
如果push空到远程,会删除远程分支 git push origin :foo
如果 fetch 空 到本地,会在本地创建一个新分支 git fetch origin :bar
git pull 参数
以下命令在 Git 中是等效的:
git pull origin foo 相当于:
git fetch origin foo; git merge o/foo
git pull origin bar~1:bugFix 相当于:
git fetch origin bar~1:bugFix; git merge bugFix
git pull 实际上就是 fetch + merge 的缩写, git pull 唯一关注的是提交最终合并到哪里(也就是为 git fetch 所提供的 destination 参数)