工具资源管理-技术贴iOS开发之常用技术点

学Git,为自己

2018-12-19  本文已影响642人  刘小泽

刘小泽写于18.12.18-19

了解到Git其实就是因为很红的Github,但Git包含的东西比Github多多了。Git的魔力一直吸引着我,但目前还是只看到了Git的冰山一角,再次接触希望有新收获。

关于设定

开始使用git,先要设置使用者的email和名称,比如在终端中输入

 git config --global user.name "Bioplanet"
 git config --global user.email "Bioplanet520@outlook.com"

可以用git config --list查看当前的设定

当然使用--global是针对整个环境来讲,如果想要根据不同的项目设定不同作者,就可以在项目目录下替换为--local设定

我们都知道,工程师的美德之一就是懒惰,抛弃重复性工作,因此可以设置一些快捷方式帮助git更快处理


全新的开始

第一次使用最重要的就是git init ,想对哪个目录进行版本控制(简称“版控”),就在哪个目录下进行该操作,结果就是在该目录下生成了.git的隐藏目录。

别看我们平时看不到它,它可储存了整个Git的精华(也许正因如此,才让目录隐藏,避免用户删除)。因此,如果有一天,我们不想对这个目录进行版控了,那么只需要移除.git目录就好

豆知识 Doodle Tip:针对mac系统,建议练习文件都放在一个名为/tmp的目录下进行,这个目录有个最大的好处:在电脑重启后里面的东西都会被清空,不需要手动清理。但是,切记,重要内容不要放在这个目录下


文件放心交给Git吧

以下内容都是作为新手的我踩过的坑,并且不止一次踩过,以至于多个文件丢失,好在不是什么重要的。后来结合搜索的知识总结了一些重点,感觉还是很有用的

先把文件添加进暂存区

首先看看git status这个命令,它是用来查询目录当前的状态,如果是一个新的目录执行这个指令,会返回nothing to commit 表示当前没有什么东西可以提交;
但是如果新建了一个文件,那么就会提示Untraced files,就是说这个文件还没有被加入Git版控系统中,并没有被Git正式追踪,仅仅是检查到有这个东西;

我们有了Untracked文件,下面就是把新建的文件交给Git,让它来管理、追踪,也就是使用git add + 文件名称或整个目录,随后不会提示任何信息,但是可以再次使用git status来查看当前状态。

只要git add后,文件的状态就会变成new file,而不再是Untracked,表示此时文件被放在了暂存区(Staging Area),随后看看有没有一起做伴的,去到储存库中。

豆问题1: 如果在git add之后,我们又修改了文件的内容,会发生什么?

【例如:我们之前新增一个文件叫a.txt,然后我们使用git add a.txt将文件添加到暂存区,突然想起来有个地方需要修改,于是又在终端中修改了a.txt

作为刚接触Git的我们,一般会在修改完直接Commmit提交,把刚做的修改保存下来(因为默认commit命令是时刻跟随我们的脚步的,我们修改到哪里,它就帮我们保存到哪里。其实并不是,它并没有这么智能,还是需要我们的指令)

回到上面👆的问题,我们对文件做了修改,再git status一下(这里是不是就发现了:最常用的是status命令,我们是需要时刻提醒的,自己做到了哪一步)。得到的结果就变成了两个a.txt文件了,一个new file: a.txt,一个modified: a.txt

这里的原因就是:我们在第二步添加文件到暂存区是没错的,Git也帮我们记录下来了;但是问题出现在重新编辑后,这里编辑的内容其实并没有再次被加入暂存区。因此,实际上暂存区的内容还是我们刚才第二步加入的原始内容

当然,也不是不能保存修改。如果确认无误,可以再次使用git add a.txt,将修改后的文件添加到暂存区

豆问题2:在添加文件时,如果想全部添加,是使用git add .还是git add -all呢?

这二者在git 2.0以上版本中确实都表示全部文件;但是还有有其他的差别,例如在git主目录下有这几个目录,其中main.css和index.html都做了改动。如果在根目录下执行git add .,那么这两个改动的文件都会被放如暂存区;但是如果在css目录下执行git add.,那么就只有mian.css会被放入暂存区。

但是,我们用git add -all就不用担心这个问题,因为即便是在css目录下执行这个操作,还是会把两个修改文件都加入

再从暂存区提交到仓库中

上面的一大段文字都是在描述:如何将文件加入暂存区,但是仅仅这样是不够的。暂存暂存,顾名思义它不会长久。如果想让暂存区的东西永久保存下来,我们还需要用到git commit

一般用法就是:git commit -m "first commit" ,其中的-m以及后面的字符都是在说明:你在这次commit的操作中干了啥。比如第一次提交这个文件就可以用first commit,简单清楚,让别人和自己都能看懂就行,不限中英文。

这个操作结束后,Git就把暂存区的东西储存到了仓库(Repository)中,也就是说:我Git完成了一个文件的存档/备份,也就是做了第一份的版本建立。

直到Commit结束,才算完成了整个流程!

关于commit,还有一点知识需要了解:


三大重要空间

Git中的三大重要空间就是:工作区(Working directory)、暂存区(Staging area)、仓库(Repository),它们的关系是:

那么就有一个问题:难道所有的流程都要通过addcommit后才能完成吗?能不能简化流程?

其实可以在commit的时候加上一个参数,如: git commit -a -m "update",但是这个参数只对已经在仓库中的文件有效,对新加入的文件(即Untracked)的文件无效

其实用分段式管理还是不麻烦的,并且对所有文件通用:可以想象有个仓库,仓库门口是个中转站,中转站上有货物有进有出,要存放到仓库的货物可以先放到中转站(git add),然后等待货物达到一定数量了,就打开仓库门,把中转站的货物一起运进仓库(git commit),并且对每一批货物都做好记录(干嘛的、谁送的)

为什么要攒一段时间再开一次仓库门呢?

其实来一件货物开一次门是可以的,但是问题就是记录次数太多,让Commit记录变得太零散,如果让别人看的时候,大家希望看到一个比较完整的内容,而不是一个Commit接着一个看

因此,问题又来了:什么时候选择进行Commit比较好?

没什么硬性规定,当完成一个任务时或者一天紧张的工作学习时光结束时,可以备份也作为记录


保持看记录的习惯

回顾一般的操作流程:

touch a.txt                             # 建立文件 a.txt   
git add a.txt                           # 把 a.txt  加进暂存区
git commit -m "first submit"            # 进行 Commit

查看之前的记录是用git log ,其中会包含几项重要信息

另外,还会看到commit cc797cdb7c7a337824a25075e0dbe0bc7c703a1e 这种信息,看似乱码,其实是非常关键的内容:它其实是SHA-1 (Secure Hash Algorithm 1)演算得到的结果,特点就是重复率极低,因此可以当做Commit的ID号【一般使用6-8位就足以区分不同的Commit了】

当然,如果在git log后加上--oneline --graph,结果就会变得更精简,一次显示的结果更多

利用log信息,我们可以做的事情可以包括:


不可避免的修改

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

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

1 删除文件

方案一 自己删除自己提交

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

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

Changes to be committed:
  (use "git reset HEAD <file>..." 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中掺杂了个人情感,事后感到后悔想修改下,一般有这几种方法(严重性从大到小排列):

修修补补最后一次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,但完成后发现忘了一个,这时情况就比较尴尬:不想花一份同样的时间却只为了一个孤零零的文件;但是这个文件不添加又怕后来忘记。于是可以。。。

还是一样,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 撤销三兄弟

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

删除标签使用-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)过程就是一个相互交流、共同开发的过程,事情是这样的:

想要删除远端的分支

比如本地的分支是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


欢迎关注我们的公众号~_~  
我们是两个农转生信的小硕,打造生信星球,想让它成为一个不拽术语、通俗易懂的生信知识平台。需要帮助或提出意见请后台留言或发送邮件到Bioplanet520@outlook.com

Welcome to our bioinfoplanet!
上一篇下一篇

猜你喜欢

热点阅读