git 的使用过程真心不容易,尤其对于我这样没有其他版本控制器使用经验,且比较容易丢三落四的的初学者。按照网上所给的步骤操作,一旦漏做了一些步骤,或者做错了一些步骤,所引起的问题也是致命的,虽然肯定有解决问题的办法,但往往会引起分支结构混乱,经常做了一次提交然后就回不去了,或者经常不知名的无法进行提交,无法进行回退,pull失败等等。

可见要想真正让git成为你版本控制的工具,git的工作原理必定要相当清楚,版本控制的结构要特别清晰,一单出现问题立马就知道是哪一步做错了才行。

问题总结:

git checkout – filename:

在文件没有stage时可以通过这个命令将该文件撤销回来。一旦文件add后用该命令不再有效。这时需要使用 git reset HEAD – filename 意味着将文件unstage出库,还原到add之前,再使用git checkout – filename撤销文件。而如果你使用git checkout HEAD – filename 则直接会丢弃掉stage中的暂存内容,将文件还原。

git push -u origin master:

远程库的名字就是origin,这是Git默认的叫法,也可以改成别的,但是origin这个名字一看就知道是远程库。把本地库的内容推送到远程,用git push命令,实际上是把当前分支master推送到远程。由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。

从github上面拉取库的方法:

  • 方法1:使用git clone address:如果是其他人的库address必须是一个http地址,如果是自己的库,且本地配置好了ssh,则可以通过ssh协议拉取需要的文件。值得注意这种拉取是完整拉取,会建立一个库名的文件夹。如果有多个人协作开发,那么每个人各自从远程克隆一份就可以了。
  • 方法2:本地已经存在git库的情况下,需要在本地库目录下使用git remote add origin address 将可以把一个已有的本地仓库与之关联。git push -u origin master把当前分支master推送到远程。我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,以后可以简化操作使用 git push origin master。

推荐使用方法1,直接从远程库clone下来,需要工程时直接clone下来避免没必要的合并,每次使用前可以先pull一下远程库。

github的协议:

Git支持多种协议,包括https,但通过ssh支持的原生git协议速度最快。默认的git://使用ssh,但也可以使用https等其他协议。clone他人或者自己的版本库的时候,都可以使用https或者ssh下载,但是配置好git的ssh的话,可以本地推送免密码,https除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令。

git的分支和HEAD:

贯穿git的整个结构的核心就是分支,每个分支都有一个名称,如主分支一般名称为master,master本身类似一个指针名,永远指向了该分支的最后一次提交的版本。而HEAD则是指向了这个指针。因此在创建分支branch test时,master和test指向同一个位置,这时候如果执行checkout test,则HEAD会从指向master变成指向test(因此git的切换高效事实是指针的操作高效),对这个test的分支相关操作会建立起新的分支结构,而master仍然停留在原处,这时如果checkout master对master进一步的操作提交,会发现版本中真正的形成了两条分支结构。

git合并分支:

git merge “branchname”:将两个分支进行合并

  • Fast-forward:分支合并完成后出现Fast-forward,意味着快速合并,即合并分支时分支指针仅仅是直接更改了指向,而没有出现其他增删改查的文件。这种情况一般是需要合并的分支仅仅是建立了一个节点,而没有将分支扩展出去,因此可以直接快速向前合并两个分支指针。
    因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。
  • Automatic merge failed; fix conflicts and then commit the result:这种情况虽然合并失败了,但相关文件已经进行了一些变动修改,用户手动更改好,这些文件,然后add,commit 即可完成提交以及分支合并。

查看分支结构:

  • git log –graph –pretty=oneline –abbrev-commit
  • 使用插件tig,该插件在github上面有托管,可以下载源码安装编译,注意提示需要安装一些依赖库托管,可以下载源码安装编译,注意提示需要安装一些依赖库。
  • 如果使用了checkout的话,使用git log则看到内容有限,这时使用git log –all可以查看所有的提交结果。
  • 值得注意的是使用了git reset之后,使用git log 就无法看到最近的一些提交了,因为reset属于版本回退,相当于整个时光倒流回去了,不会在看到未发生的内容,只能通过git reflog查看库里面的所有备份内容。

stash的用处:

Git还提供了一个stash功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:

  • git stash:在git add之后可以通过stash将文件修改暂存。
  • git stash list: 查看暂存的信息。
    一些不需要的修改也可以存入stash中,相比而言stash不像commit那么严格,可以随便存入,随便删除。

远程库如何操作caozuo:

当你从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin。要查看远程库的信息,用git remote,返回origin。可以看出远程库也是可以有多个的。

实际的远程建立库一般为–bare裸库是不存在实际工程文件的,相当于.git文件直接被展开了,实际使用的时候,需要clone下来才能够看到文件内容。

  • git remote -v:显示更详细的信息,可以抓取和推送的origin的地址。如果没有推送权限,就看不到push的地址。
  • push:就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上git push origin master,可以看到origin为远程库的名称,而master为本地需要推送的分支。在Git中,分支完全可以在本地自己,到底要不要推送则完全由你自己决定。
  • clone:从某一个位置将目录完整克隆下来,注意clone后面可以类似:git clone [email protected]:hard/learngit.git,也可以使用git clone ~/home/gitname但注意gitname必须得是个git的版本库,也就是说在里面执行过git init
  • 推送冲突失败:你的小伙伴已经向origin/dev分支推送了他的提交,而碰巧你也对同样的文件作了修改,并试图推送,则会推送失败,因为你的小伙伴的最新提交和你试图推送的提交有冲突,解决办法也很简单,Git会提示我们:先用git pull把最新的提交从origin/dev抓下来,然后,在本地合并,解决冲突,再推送。git pull也失败了,原因是没有指定本地dev分支与远程origin/dev分支的链接,根据提示,设置dev和origin/dev的链接:git branch --set-upstream dev origin/dev,然后再pull。这边尤其需要每次输完指令后,看提示,有的未必会报错,但也没有执行相关操作,出现git command的时候,往往是你指令执行已经异常了。

同步已有本地库到远程:

大多数时候,本地存在已有的库,而远程并没有,这个时候不能通过git clone来复制,需要通过以下的一些步骤

  • 删除本地的.git残留文件,如果本地已经有了git操作,但不明具体内容,则首先执行rm -rf .git清除掉无关内容,git的设置相当科学,几乎所有的记录信息全在.git目录下。
  • 初始化这个库,git init这是必备步骤,建立一个空的默认库。
  • 随意添加几个文件,例如:echo "hello world" >> test.txt,如果是实验的话可以操作本步骤,如果已有功能则跳过此步骤。
  • 常规git操作,加入stage并提交,git add . && git commit -m "test",必备步骤,该步骤是相当于建立提交,放入到master中。
  • 查看下本地库的状态:git status,一般提示,没有可提交的内容,则代表上面步骤成功了。
  • 一切正常的话,将远程库与本地库建立关系:git remote add origin git@ubuntu_server:/home/git/myRep.git。这边的test为远程库的名称,默认一般为origin,但如果你一个项目中同时有多个库,则建议你将origin修改为其他名称。
  • git remote –v,可查看版本库的网址,以及相关的库名称
  • 推送远程分支很简单:git push origin local_branch_name:remote_branch_name,一般可以省略local分支名,则会自动推送当前分支。

tag和branch的区别:

我们可以认为tag是一个静态标签,tag一定设定后,它的位置就永远不会再改变了,tag就是哪个commit对应的一系列数字英文字符的别名。如正常情况下我们需要checkout某个提交的时候需要git log查看版本名称,然后可以使用类似git checkout a228来切换到这个版本上去。但给这个版本起了名字edition1,则这时,我们可以通过git checkout edition1直接切换这个版本,而无需再查看log。

但一般我们不随便给任何提交打上tag,大多数情况只给某一次或几次特殊重要的提交打tag,比如发行版v1.0,测试版beta1.2等等。

而branch则是分支,最为常见的就是master分支,注意分支实际是一个指针,分支的指向永远指向这条分支结构的最末尾端(及最后的一次提交),所以分支是会不断变动的,而tag是静态的,两者区别很明显。

MDK工程管理出错:

本人在工作中会使用到MDK来做一些MCU的编程开发,而MDK虽然配置简单,调试功能强大,但编辑扩展等其他功能都奇差无比。在使用git进行管理的过程中发现,MDK会对多个工程类文件进行不断修改,而这些文件,如果不进行提交的话,又会出现工程缺失文件,工程错误等现象,另外经常出现无法回退或者无法提交等现象。

经过查看和分析,最终发现:MDK只要打开的情况下,就会不断的对工程文件进行修改,即便你仅仅是查看没有做任何事情,因此下面推荐几种方法和技巧:

  • gitignore中只排除MDK的编译文件。
  • 每次要提交和回退的时候将MDK关闭再执行。
  • 如果忘记关闭或者出现突然没法回退或者提交的情况下,git查看状态,如果仅仅是工程文件出现改动,而源文件和头文件不变的话,则直接丢弃这些修改或者将修改存入stash中,再继续要做的操作。

checkout和reset的区别:

git checkout(该命令的理解贯穿git的始终,如果理解不精准,往往会遇到很多莫名其妙的错误。)用于控制head指针所指向的位置,也就是说控制当前用户的工作位置。该指令尤为重要,一般都是通过checkout找到过去的某个版本,然后在该位置创建分支结构,再进行相应修改。(注意是切换HEAD的位置,此时仅仅用于查看该版本,而如果你没有在该位置建立新的分支,则无法对该版本进行修改,git的所有修改都是基于分支进行的,每条枝干的前后各个版本都是存在强关联性的,所有枝干的后一个节点都是基于前一个节点建立的,如果checkout到前一个节点对它进行了破坏,则后面也必然会受影响,这种情况一般是不允许发生的,因此checkout的每次修改都是要求该节点必须要重新开枝散叶,这样新的修改会在新的枝干上面进行,不会影响原有的内容。)checkout的好处在于不会破坏原有的分支结构,可以任意在整个的目录树上面对head进行移动,方便用户查看各个版本。(再次注意:checkout只是用来控制head的,如果head通过checkout发生了移动,不再指向原有分支的末尾如:master,则必须建立新分支,才可以对文件进行提交修改。另外即便checkout指向原有分支的末尾,但如果是版本号的话也是没法进行修改的,必须要是指向master这个分支名才可以进行修改,类似于HEAD必须指向指针才可以,而不是指向某一个节点,这一点有指针操作经验的话,较容易理解。),checkout – filename:回退某一个文件,注意需要加–,这一步并不是更改HEAD指向,而仅仅是回退某一个错误修改了的文件,这也是撤销修改文件最常用的命令。

git reset “branchname/editionnum”:事实上这个指令很少用的,或者说谨慎使用,一旦使用了就会破坏原有的分支结构,reset是一个回退功能,会将原有的分支剪掉,虽然仍然可以通过reflog查找出版本信息,强制指向某个回退之前的版本,但这并不是该有的操作,且很可能因为误操作造成更大麻烦。建议:只有在确定很清楚自己要进行的操作和上次提交错误的情况下使用该指令,其他时候都不使用。版本回退使用reset后面一般加上–hard彻底回退,否则有些修改了的未建立版本的文件仍然会保留,reset是一剂后悔药,只有在万不得已的情况下才会使用,但往往reset的滥用,也会让你的分支结构混乱。

综上所述,可以看出checkout是用来切换版本和分支的(分支其实是特殊的版本),而reset则是用来回退版本的。通常一般都是使用checkout来操作,只有发生重大错误,无法挽救时,才会使用reset。