分享

学Git,为自己(下篇)

 生物_医药_科研 2018-12-22

今天是生信星球陪你的第225天


   大神一句话,菜鸟跑半年。我不是大神,但我可以缩短你走弯路的半年~

   就像歌儿唱的那样,如果你不知道该往哪儿走,就留在这学点生信好不好~

   这里有豆豆和花花的学习历程,从新手到进阶,生信路上有你有我!

豆豆写于2018.12.18-19,继续上一次学Git,为自己(上篇)

不可避免的修改

在Git中,不管是删除文件还是重命名,对Git都是一种 【修改】

关于修改的部分,比较零散,我在前面都加了编号,表示不同的操作

1 删除文件

方案一 自己删除自己提交

可以把原来文件直接rm a.txt删除,看下状态,就会提示deleted:  a.txt。但这只是第一步,只是说明我们做了更改,并不是本地删除Git存档中就删除。但是还是要继续往下走

# 还是先将做的修改加到暂存区
git add a.txt
# 看一下状态
git status

Changes to be committed:
  (use 'git reset HEAD ...' to unstage)

    deleted:    a.txt
# 说明现在已经加入了暂存区,下面就要commit就可以了
方案二 Git帮忙删

使用git rm a.txt可以省去add的步骤,然后直接commit就好了

2 只想把文件打入冷宫?

上面两种办法都是直接删除文件(直接kill掉),万一我们只是想把文件从Git中移出来,打入冷宫,并不kill它呢?

使用--cached参数即可

例如:git rm a.txt --cached ,提示信息会显示:rm a.txt。实际上,并不是真的把文件删了,而是把文件从Git中剔除,如果此时再查看一下状态,就会发现变成了Untracked

3 想变个名字时尚一下?

例如想把a.txt改为b.txt,也是可以用两种方案:

方案一 自己改名

mv a.txt b.txt ,注意:虽然只有一个更改的动作,但是Git完成了两个:一个是删除a.txt文件,另一个是新建了b.txt文件。因此最后的状态变成了Untracked。接下来,只需要用git add --all,就会发现现在的状态变成了renamed: a.txt -> b.txt

方案二 Git改名

利用git mv a.txt b.txt,也是想删除操作一样,可以少一步操作直接变成renamed状态

事实上,Git并不在乎你取什么名字,它是根据文件内容计算得到SHA-1值,然后当我们更改文件名时,Git的第一步只是指向了原来的文件,当文件名改变后,Git才又为它重新生成一个新的编号/名称

4 想修改Commit记录?

如果有时因为情绪向Commit中掺杂了个人情感,事后感到后悔想修改下,一般有这几种方法(严重性从大到小排列):

  • 整个删除.git

  • 使用git rebase修改

  • 先用git reset拆除commit,整理后再重新commit

  • 利用--amend参数修改最后一次Commit

修修补补最后一次Commit amend

加入原来的log中有一句:

$ git log --oneline
3879515 GUN
5dbc437 add a.txt
357fce7 add container
adb4f43 update index 

现在感觉第一句不太恰当,想要做下修改,于是可以

$ git commit --amend -m 'Good Ur Night'
[master 414a91c] Good Ur Night

当然,如果不加-m参数,就会弹出VIM窗口,让你从里面编辑

想要修改更早的Commit =》 Rebase

amend只能修改最后一次的记录,但是Rebase可以修改更早的

切记: 上面的例子可以看到,虽然我们只是改了下语言,但是最后的SHA-1值就发生了变化,于是就相当于改变了时间线【看过”闪电侠”或者“蝴蝶效应”的朋友都知道改变时间线的后果】。因此,尽量不要在push后重新修改

想要撤销Commit =》Reset

例如现在的log包含了以下信息

$ git log --oneline
f12d8ef (HEAD -> master) add c.txt
83e7e30 add b.txt
687fce7 add a.txt

现在想要撤销最后一次的Commit,就可以用git reset f12d8ef^【其中这个^表示前一次的Commit,如果是要到前两次就^^,次数再多就直接f12d8ef~5 表示5次】。但是如果是返回HEAD这个Commit的话,还有种简单的方法可以表示:就是git reset master^或者git reset HEAD^  。

因此,如果想回到687fce7,就可以:git reset 687fce7git reset HEAD~3git reset master~3 。第一种就像绝对路径,而后两种有点相对路径的意思。

另外,reset还有三个参数,它们还是有一些不同的,主要区别就是对从Commit中撤销回来的文件怎么处理

模式--mixed(默认)--soft--hard
从Commit撤销的文件去向丢回工作目录丢回暂存区直接丢弃

虽然我们知道,reset的英文含义是“重置”,但是在Git中更像“become”或者“go to”的意思,表示“去到哪里”。因此即便用了reset也不要有什么心理压力,既然表示“去到哪里/变成什么样子”,那我们也可以撤回,都不是事。不要认为用了reset就和恢复出厂一样的严重

例如:这里如果使用—hard参数从f12d8ef退回到687fce7,git reset HEAD~2 --hard ,那么暂时的结果就是只保留了687fce7的文件,其余全部消失。如果想恢复到reset之前的状态,把所有的文件再重新恢复,可以看一下Git中的git reflog(里面存放了对HEAD的每一次改动信息),看看之前的SHA-1值是多少,查到是f12d8ef,于是就可以用git reset f12d8ef --hard 把刚才reset的文件都捡回来

5 已经Commit完,却发现还有一个文件忘了加

一般会碰到多个文件一起Commit,但完成后发现忘了一个,这时情况就比较尴尬:不想花一份同样的时间却只为了一个孤零零的文件;但是这个文件不添加又怕后来忘记。于是可以。。。

  • 使用git reset撤掉最后一次的Commit,加入新文件后重新Commit

  • 使用--amend进行Commit

    例如:文件c.txt是被落下的,可以先git add c.txt,然后用git commit --amend --no-edit 就可以吧c.txt并入最后一次Commit【--no-edit表示我不想编辑Commit信息,所以也不要跳出来VIM编辑器了】

还是一样,push前需要检查清楚,尽量不在push后重新修改

6 可以只Commit文件的一部分吗

git add时可以就加上-p参数,然后Git就问你需不需要把这个区块(hunk)加入暂存区,接下来选择y就表示将整个档案加入;选择e就弹出编辑器,让你自己删除那些不想添加的区域,保存后离开,这部分就被加到暂存区了

7 Git对空目录是不认识的

比如新建一个目录,想要Commit,但是git status 始终显示工作目录没有发生任何修改,这是因为Git是针对文件内容进行计算的,只有空目录没有内容,它感受不到目录存在。解决办法就是:touch DIR/.keep 在新目录下新建一个隐藏的文件就好,然后就继续add +commit 就可以

8 误删了文件怎么办?

很多情况下(尤其精神不好的时候),容易冲动用rm搞砸一切,假入不小心删除了Git目录下的一些文件,此时的状态都应该是deleted。通过git checkout c.txt 可以救活一个,想全部救活就用git checkout .

上面的操作全都是恢复到上一次commit操作,如果想恢复更远(例如两个版本以前),就可以加个参数:git checkout HEAD~2 c.txt

9 Git中经常见到的HEAD是什么东西

在Git的逻辑中,HEAD相当于一个风向标,指向某个分支,而分支只想某个Commit一般可以认为HEAD是“目前所在的分支” 。HEAD信息存放在.git目录下 ,cat .git/HEAD得到ref: refs/heads/master 信息。其中可以看到,目前HEAD指向master分支,看一下master分支中都有什么:cat .git/refs/heads/master ,结果就是这样的字符:ecdfbac9f5fe463e078fb4f737ce39773895e121  

因此,如果我们有多个分支(例如有master、doudou、huahau),想切换的话,可以用git checkout 。在变动的时候,随之改变的还有.git/HEAD文件和Reflog文件

10 修改历史信息

感觉就像坐着时光机穿越似的,利用rebase这个时光机可以回到过去,修改多个历史的Commit信息

先看看当前的记录:git log --oneline ,然后选择一个时间节点git rebase -i bc08d2 (其中-i是互动模式),之后会打开一个编辑器,其中开头的pick 意思是保留Commit,不做修改。找到想改的Commit行,把pick改成reword,保存离开。接着就会跳到我们要改的那个Commit的编辑器中,更改完信息后,保存退出;进行下一个…【需要注意的是:在互动模式中,文件从上到下是由旧到新,正好与log记录中相反】

如果改着改着,中途不小心退出了,不用担心。其实Git是为你保留了记录的,接下来选择git rebase --continue就是继续,选择--abort 就是彻底退出之前的编辑。

我们改完后,如果想回到修改之前,需要用到git reset ORIG_HEAD --hard

11 合并臃肿的Commit信息

有时候,我们在提交Commit信息时,难免有些复杂,比如一个文件写add doodle 1 ,另一个又写add doodle 2 ,一个Commit的利用率不高(这里的一个Commit只对一个文件有效,造成了最后log文件的繁琐)。如果可以将多个相似的Commit进行合并,是不是能清爽一些?

像上面一样,还是先git log --oneline ,再git rebase -i bc08d2 ,只不过对于要合并的Commit的信息将pick改成squash ,最后在弹出的编辑器中进行编辑,保存就结束了

12 撤销三兄弟

  • Reset : 改变历史记录【把目前状态设成某个指定的Commit状态,一般用于未push的Commit】

  • Rebase: 改变历史记录【新增、修改、删除Commit都很方便,一般用于未push的Commit】

  • Revert:不改变历史记录【新增一个Commit来对之前的Commit做反转,当然原来的Commit还是会留在历史记录中,一般用于push过的Commit】

13 打个Tag

一般当我们认为自己的代码完成了里程碑式的发展时,比如开发版本1.0.0或者beta发布,一般会打个tag

分为轻量标签(lightweight tag)和附注标签(annotated tag)

官方说明:Annotated tags are meant for release while lightweight tags are meant for private or temporary object labels

  • 轻量标签:相当于一个贴纸,直接粘上就完事,例如git tag douhua 347dfe

  • 附注标签:含有的信息会多一些,像一个简短的介绍,例如git tag douhua 347dfe -a -m 'Doudou and Huahua' (-a 表示 annotation;-m 和Commit中的一个意思)

删除标签使用-d参数,例如git tag -d douhua

既然标签和分支都充当风向标,那么有什么不同吗?

当Commit修改时,分支是随之移动的,而标签还在原地,牢牢贴在那里


分支?分身?

假入你有一个分身,你要让他干嘛呢?

答:帮你吓退敌人;帮你学生信;帮你写作业;帮你做实验。。。这些确实都是不错的选择,但是分身有个最大的作用就是:一旦任务执行失败,本体并不受影响

如果我们的任务比较复杂,尤其一个package需要多个人共同完成时,就不能随便Commit。我们可以在另一个分支中来测试某些新功能或者修复某些bug,当效果满意时我们可以选择合并到主线;即便效果不好,也不会对主线造成影响

新接触一个分支

查看分支很简单,直接git branch就好,预先设置的都是master分支,当前分支前面有*标明

想要增加一个分支,可以直接加上名字git branch doodle ,这样就增加了一个doodle的分支

分支名字不够霸气?改!例如有一个分支叫紫薯,我们完全可以用git branch -m 紫薯 紫薯侠 ,即便是预先安装的master也可以改

删除分支也是可以的:使用git branch -d test ,但是这里如果test分支还没有合并到主线,删除时会提醒你。如果真的不想要他了,并且不要合并,可以用-D强制删除。因此,所有的分支都能删,包括master,只不过不能删当前的分支(就像conda删除环境一样,不能删除当前所在的)

想要切换分支? 直接git checkout doodle ,随后星号就移到了doodle上面

什么?切换分支文件丢失!

加入我们在doodle分支下新建了两个文件,并且add+commit 后也确实看到文件在列表中;但是如果切回原来的master分支,发现新建的两个文件没了,其实不用担心,文件还在,只不过在当前分支显示不出来。我们只需要切换回原来的doodle分支就好了

切换到不存在的分支可以么?

一般来说,我们都要先建立一个分支,然后再切换。但是如果想切换到一个之前没有建立过的分支,有一个快速的办法就是git checkout -b 新分支名字  【-b参数的作用就是:如果切换的分支存在,就直接切换;如果不存在,就新建一个分支,然后切换过去】

或者可以利用这个命令,在之前的某个Commit来新建一个分支,感觉就好像 ”回到年轻时,做点不一样的事“,例如之前有一个Commit 347dfe4值,我们想回到这里新建一个huahau的分支:git branch huahau 347dfe4

分支合并

如果我们之前新建了分支doodle,并且在doodle中Commit了两个文件,然后想要回到master分支,把doodle中的文件合并过来,可以用:git checkout master + git merge doodle ,这时master中就会出现doodle中的两个文件。

那么合并完的分支还要继续留着么?

其实按道理讲,主线master已经拥有了doodle的一切,那么doodle就没有作用了。但是如果不想让自己对亲分身有”过河拆桥“的愧疚的话🥺,留着也不是不行。完全看自己

没有合并的分支不小心被砍掉,怎么办?

合并完的我们可以随心意,但是如果使用了git branch -D强行删除没有合并的分支,就会给你一个信息(Deleted branch doodle (was c274a5a).),其中那串Commit字符SHA-1值很重要,就是你的”后悔药“。我们知道,分支只是一个指向某一Commit的风向标,删除这个风向标并不会导致Commit消失

于是想要恢复分支,可以利用原来的Commit SHA-1值,用git branch new_doodle c274a5a 来迎接老朋友【还是那句话,SHA-1值不需要刻意记忆,需要去到Reflog中查询】

只要某个分支的某几个Commit

使用cherry-pick 加上想要的Commit号,就把那个Commit内容复制过来,于是在当前的分支上就多了一个commit。加多个Commit就在命令后多写几个Commit号。

默认情况是复制过来就合并到当前分支的,如果不想合并只放在暂存区,就用git cherry-pick --no-commit


什么?记录到一半,又有新任务?

我们的学习过程都是并行的,可能同时手头有多个任务,如果自己在做一项,同时又有新任务来,并且很紧急,那么就需要切换到重要级优先的任务中去。

先不管那么多,先保存目前的所有修改git add -all ,然后Commitgit commit -m 'not finish yet' ,接下来就可以切换到其他分支去工作,做完后再切换回之前的分支,最后Reset一下git reset HEAD^


了解了Git,那GitHub呢?

首先说GitHub的G和H是大写哦,它是一个商业网站,目前是全球最大的Git Server。为什么这么多人在用,并且代码都放上面?因为在GitHub上造假是非常麻烦的事情,而且我们知道,开发者都有懒惰的美德。因此,自己做过什么贡献,开发过什么项目一目了然,可以说,GitHub是开发者最好的简历。

让本地与远端保持同步

有时我们会在网站上直接更改,如果本地想保持最新,可以用git pull --rebase 。我们知道,这也算是一个合并的任务,因此也会在本地默认加上Commit,加上rebase的目的就是不要加这个关于合并的Commit

本地推不上去什么鬼?

有时本地push会报错

$ git push
To https://github.com/eddiekao/dummy-git.git
 ! [rejected]        master -> master (fetch first)

这是因为远端比本地的版本还新,Git不想这么做,解决办法如下:

先拉再推,就是先从远端pull到最新版git pull --rebase ,如果合并没有冲突,再往上推

甚至还有一种:git push -f使用蛮力往上推,造成的影响就是可能会把共同开发者的版本覆盖掉,导致他们资料丢失【一般不用!

网上发现某人写的不错,想拷贝到本地看看

使用git clone,可以选择HTTPS或者SSH,如果clone下来想保存不同的名称,可以在命令后面加目录名

git clone xxx doodle ,于是就把xxx上的整个内容(包括历史记录、分支、标签等)复制到本地一份,保存为doodle

一般来讲,clone命令只需要用一次,并且命好名字,之后想追更新,直接用pull就好

小叉子Fork用起来

Pull Request (PR)过程就是一个相互交流、共同开发的过程,事情是这样的:

  • 先是从GitHub上看到一个作者写的东西,自己感兴趣,于是自己先Fork一份原作者的文档到自己的账户下

  • 自己拥有所有修改权限,想怎么动就怎么动

  • 改完后,push回自己的账号,然后给原作者发通知,说你帮他搞了一点新功能,请查收

  • 作者如果满意,就会把你的工作merge到他的文档中

想要删除远端的分支

比如本地的分支是master,远端服务器地址是origin表示【如果要本地修改远端仓库名称,可以用git remote rename; 或者修改远端仓库的URL: git remote set-url origin url

复习一下:如果我们要将本地的文件推到远端,并且建立远端分支doodle,可以用git push -u origin master:doodle    就是说,将本地的master推上去后,本来默认是在远端建立一个相同名字的分支,但现在指定了叫doodle

这个origin怎么来的呢?是利用git remote add得到的,如果我们命名为huahua,那么也可以用huahua表示远端服务器的地址

现在我们有了远端分支doodle,那么怎么删除它?

其实我们只需要把原来push操作中的本地分支变成空,就相当于删除了远端分支。也就是说,上传一个空的分支到远端git push origin :doodle


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多