Git教程
     分类:Dev     有: 0 条评论

Git教程

     分类:Dev     有: 0 条评论

常用命令

Git大全:https://gitee.com/all-about-git

多仓库合并

场景:以某个项目A作为基础进行二开得到B;几个月后A新增了功能或修复了bug;此时项目B也需要继承A的新功能。于是以A作为更新源对B进行更新;但不影响B(二开的功能),有冲突当然也要适当修改处理。

A 是公开的项目地址(比如Github上的项目)
B 是自己的项目地址(不公开的项目)

# 新建空仓库B
git clone B
cd B
git remote add [A的别名,比如github] A

# 本地分支,一个人开发可以用master,省去切换分支的麻烦;下面表示从A更新到本地的master分支
# 之后每次想要从A合并代码都用此操作
git pull [A的别名] master

# 提交,以下操作是提交到默认的地址的,也就是B的地址(origin中的地址);所以第一步是克隆B仓库,如果不是,可以修改.git/config文件
git add . && git commit -am "first commit"
git push

git与svn共存(客户端共存)

git-svn

git svn clone代码到本地,就可以用git的东西本地操作了,然后git svn rebase更新代码,git svndcommit提交代码,这个好处就是可以用git本地分支管理了,不好的是还是只能操作一个远程代码仓库,还是比较麻烦的。我试了许多次是用 git工具和svn工具一起用,git工具用命令行获sourcetree都可以,svn工具用命令行或界面工具,只是提交代码更新代码用。


第一次创建工程

如果你是第一次创建工程的话,先用svn checkout 代码到本地,然后在代码根目录git init,然后git remote add origin/master  git地址。这样以后就可以git和svn同时使用了,注意增加忽略文件,git忽略svn文件,svn忽略git文件。


svn或git仓库其中一个为空,或者都不为空

如果svn上有工程或者git上有工程,我的情况是svn和git上都有代码,并且代码一样,首先svn checkout 到本地,打开根目录删掉所有文件,以免git pull代码的时候冲突,然后git init,接着git pull代码后,用svn update 代码一下,这样就ok了

注意!如果是git仓库不为空,仓库里不能有.svn和.filelist.txt文件(.project),如果有,先在别处clone下来删除后提交,再进行下面的操作

cd [项目目录]
svn co [仓库地址] --username [账户] --password [密码]
rm -rf *   #此操作不会删除.svn文件夹
git init
git remote add origin [仓库地址]
vim .gitignore

.filelist.txt
.project
.settings/
.svn/

如果本地之前提交时包含了上面的文件,请从版本库中删除git rm --cached -r .svn/
git pull origin master
添加.gitignore和.git到svn的忽略清单
svn update
git push --set-upstream origin master

参考:https://blog.csdn.net/andy_jiangbin/article/details/72287253


git与svn共存(服务端共存)

类似码云这种,服务端同时支持使用git和svn连接。


安装Git

Debian/Ubuntu

apt-get install git

Centos/RedHat

yum install git
指定版本、高版本:https://blog.csdn.net/daerzei/article/details/79282643

Windows

安装包下载:
https://git-for-windows.github.io/
https://git-scm.com/

Mac

安装包下载:https://sourceforge.net/projects/git-osx-installer/

git --version  查看版本


Git配置

ls -als -ah 方便查看隐藏文件
执行git config命令就是调用此文件
/etc/gitconfig 全部用户生效 --system调用的文件
~/.gitcinfig 当前用户有效 --global调用的文件
.git/config 当前项目的配置文件
.git/config的配置会覆盖/etc/gitconfig 中的同名变量。
win中配置文件一般在主目录下的对应用户文件夹里C:Documents and Settings$USER
win中/etc/gitconfig是在安装目录中。

用户信息配置

git config --global user.name "leolan"  #leolan为用户名
git config --global user.email 842632422@qq.com   #邮箱
(用了global选项后当前用户的所有项目默认使用此用户信息,某一项目若使用其它的用户信息。把--global去掉重新配置,会在该项目.git/config中重新生成配置就行了。)
以下可选:
git config --global color.ui true    #让Git显示颜色,会让命令输出看起来更醒目
git config --global core.editor emace   #指定emace为默认编辑器,不指定默认为vim
git config --global merge.tool vimdiff  #差异化分析工具(冲突合并),git可以理解kdiff3 ,tkdiff,meld,xxdiff,emerge,vimdiff,gvimdiff,ecmerge,opendiff等工具的输出信息。

git config user.name   #查看配置的用户名,改为user.email则为邮箱。
git config --list      #查看已配置的用户信息

基本使用方法

一般进入某项目的目录后再执行以下命令。

git init            #设置当前目录为Git仓库
git init 目录名             #指定目录为Git仓库

git clone [源]        #从源拷贝项目到本地的当前目录
git clone [源][目录]    #指定源下载到某个目录
例:git clone git://github.com/schacon/grit.git mygrit

git status        #查看当前目录文件的状态,加 -s 显示简短信息。"A"是已添加到缓存、"M"为文件有改动、"空格"为未缓存的文件,组合显示如:"AM"代表已缓存的文件有改动,"空格M"代表未缓存的文件有改动。
git add 文件名   #添加文件到项目缓存中,没有添加的文件不属于项目文件,也不会痛不到服务器。
git add .          #添加当前目录到项目缓存中,如果添加多个文件,此命令更方便。
git commit -m "项目的版本或简短说明"    #可以指定版本号及简单说明改动了哪些内容,同时会把所有改动同步到服务器上。
git commit -am "项目的版本或简短说明"    #改动了多个文件又不想一个个添加,此命令自动添加所有改动的文件并同步到服务器。

Git的所有工作都是现在本地缓存的,只有执行git commit -m "简短说明" 后才会向服务器提交。

git diff            #未缓存的改动
git diff --cached    #已缓存的改动
git diff HEAD        #已缓存和未缓存的所有改动
git diff --stat        #显示摘要而非整个diff
git reset -- HEAD 文件    #取消文件已缓存的内容,--很重要,没有加--表示切换到另一个分支。
git rm 文件        #从项目中及本地删除文件,可不用执行。
git mv 文件        #从项目中及本地重命名文件,可不用执行。

创建远程仓库

在继续阅读后续内容前,请自行注册GitHub账号。由于你的本地Git仓库和GitHub仓库之间的传输是通过SSH加密的,所以,需要一点设置:

如果一切顺利的话,可以在用户主目录里找到.ssh目录,里面有id_rsa和id_rsa.pub两个文件,这两个就是SSH Key的秘钥对,id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人。

点“Add Key”,你就应该看到已经添加的Key:

为什么GitHub需要SSH Key呢?因为GitHub需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,GitHub只要知道了你的公钥,就可以确认只有你自己才能推送。
当然,GitHub允许你添加多个Key。假定你有若干电脑,你一会儿在公司提交,一会儿在家里提交,只要把每台电脑的Key都添加到GitHub,就可以在每台电脑上往GitHub推送了。
最后友情提示,在GitHub上免费托管的Git仓库,任何人都可以看到喔(但只有你自己才能改)。所以,不要把敏感信息放进去。
`ssh -T git@github.com
提示:Hi MyLeoLan! You've successfully authenticated, but GitHub does not provide shell access.  则成功添加sshkey

在右上角找到“Create a new repo”按钮,创建一个新的仓库:

目前,在GitHub上的这个仓库还是空的,会提示你创建README.md文件。

可以从这个仓库克隆出新的仓库(克隆到本地),也可以把一个已有的本地仓库与之关联,然后,把本地仓库的内容推送到GitHub仓库(本地的项目或下载了别人的项目想要推送到自己的仓库,和fork的方式不同,这种方式要删除隐藏的.git文件夹因为.git文件夹包含了别人的库信息,你是推送不到自己的库上去的,删掉之后添加的才是自己的信息)。


从远程库克隆仓库

远程库已经准备好了,下一步是用命令git clone克隆一个本地库:
git clone git@github.com:MyLeoLan/testgit.git
实际上,Git支持多种协议,默认的git://使用ssh,但也可以使用https等其他协议。
使用https除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用ssh协议而只能用https。

本地仓库推送到远程仓库

把本地仓库的内容推送到远程(GitHub)仓库。在本地的XXX仓库下运行命令:

git remote add origin git@github.com:myleolan/learngit.git
表示把本地的XXX仓库与远程learngit.git库绑定(添加远程库),以后推送都推送到这个库中。地址换成自己的库地址。你的SSH Key公钥不在我的账户列表中是推不上去的。

关联后,使用命令git push -u origin master完成第一次推送master分支的所有内容(一般远程的master分支会自动命名为origin用于区分,而本地的master还是master分支,-u会关联分支,下次推送默认推送到此分支。)

推送时git push origin XXXX (xxxx可以指定本地某分支推送到远程的origin分支)此后,每次本地提交后,只要有必要,就可以使用命令git push origin master推送最新修改;简化命令为:git pull(从远程仓库提取数据并尝试合并到当前分支)和git push

从远程库下载分支和数据的更新到当前项目(不会合并,pull等同于fetch+merge):git fetch origin

查看当前项目的远程库:

git remote 或 git remote -v

origin  git@github.com:myleolan/learngit.git (fetch)
origin  git@github.com:myleolan/learngit.git (push)

上面显示了可以抓取和推送的origin的地址。如果没有推送权限,就看不到push的地址。

本地已有副本和远程合并
注意:如果本地的文件夹是之前clone远程的,只不过本地.git文件夹没了;只要两者差距不是很大也是可以合并的。

git init
git remote add origin git@github.com:myleolan/learnweb.git
git config --global user.email "842632422@qq.com"
git config --global user.name "leolan"
git add . && git commit -am "test001"       #此时本地会新建master分支,未执行前本地没有分支

git branch --set-upstream-to=origin/master master
git pull

此时会提示:
Auto-merging index.html
CONFLICT (add/add): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
这时手动修改index.html文件中的冲突部分,然后再次提交,
git add . && git commit -am "test002"
git push
到此,本地和远程的就同步了,下次就可以直接git pull更新了。

绑定多个远程仓库
先创建一个远程仓库,然后编辑项目目录中的.git文件夹下的config文件(不要编辑全局配置文件,并不是所有项目都要推送到两个仓库中)
删掉fetch那一行,只保留地址,然后再添加一个地址,像下面这样子。
mark
注意,刚刚删掉了fetch,所以第一个地址是默认fetch
mark

另一种方法:
加入要添加的第二个地址是: git@github.com:MyLeoLan/blogpost_backup.git
那么输入:

git remote set-url --add (别名) git@github.com:MyLeoLan/blogpost_backup.git

不过这种方法有时候不是特别灵,可能会把原来的第一个地址替换掉,大家有机会多测试反馈哈。

之后每次提交时用 git push --all 就可以同时提交到绑定的仓库啦。


删除远程仓库:git remote rm [别名或仓库名]

本质上都是同步commit

分布式版本系统的最大好处之一是在本地工作完全不需要考虑远程库的存在,也就是有没有联网都可以正常工作,而SVN在没有联网的时候是拒绝干活的!当有网络的时候,再把本地提交推送一下就完成了同步!


管理修改

第一次修改 -> git add(放入暂存区)-> 第二次修改 ->git commit(提交暂存区,准备推送到远程)

Git管理的是修改,当你用git add命令后,在工作区的第一次修改被放入暂存区,准备提交,但是,在工作区的第二次修改并没有放入暂存区,所以,git commit只负责把暂存区的修改提交了,也就是第一次的修改被提交了,第二次的修改不会被提交。

提交后,用git diff HEAD命令可以查看工作区和版本库里面最新版本的区别。

怎么提交第二次修改呢?你可以继续git add再git commit,也可以别着急提交第一次修改,先git add第二次修改,再git commit,就相当于把两次修改合并后一块提交了:

第一次修改 -> git add -> 第二次修改 -> git add -> git commit
好啦,把第二次修改提交了。


删除文件

一般情况下,你通常直接在文件管理器中把没用的文件删了,或者用rm命令删了,此时文件删除了但版本库里还有记录,文件可能恢复不了。这个时候,Git知道你删除了文件,因此,工作区和版本库就不一致了,git status命令会提示你哪些文件被删除了。

要从版本库中删除该文件,那就用命令git rm删掉,并且git commit (git rm会同时删除版本库及文件)

另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本:git checkout -- filename

git checkout其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。

提交代码分两步:

从工作目录提交到stage,需要用add或者rm命令,只提交到stage,而没有提交到master,是不会自动同步到master的。

从stage提交到master用commit命令。

工作区-->--add,rm-->--stage-->--commit-->--master

退回也是要分两步:

对于已经到达stage的,想把state中的文件状态用master中的覆盖掉,就用reset命令,这样就把stage中修改用master的状态覆盖掉了,完全跟工作目录没关系

工作区--<--checkout--<--stage--<--reset--<--master

要删除某些文件,不能简单用rm删除,远程仓库依然会保留文件(因为它不知道你什么时候就进行版本回退了,需要用到那些文件),要彻底删除,本地git rm filename命令删除文件再git push上去,或者进入github管理界面手动一个一个删除文件。


版本回退

git status        #查看当前状态
git diff             #查看做了哪些修改
git add file         #添加到缓存
git commit -m "xxx"#提交到本地版本库
git log           #可查看所有的历史版本,信息太多加--pretty=oneline参数简化
git log --pretty=oneline  # 查看版本库中的版本号
git rev-parse HEAD        # 查看当前版本号
git reset --hard HEAD^    # 回退到上一版本
git reset --hard HEAD^^   # 回退到上上个版本

首先,Git必须知道当前版本是哪个版本,在Git中,用HEAD表示当前版本,也就是最新的提交过commit的这个版本,HEAD^是上一个版本,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100

此节实验当我没有保存下来,所以引用廖雪峰老师的数据,实验是通过修改readme.txt文件进行的,假设我们要把当前版本“append GPL”回退到上一个版本“add distributed”,就可以使用git reset命令:git reset --hard HEAD^ (退回上一个版本)

cat readme.txt看看readme.txt的内容是不是版本add distributed:

Git is a distributed version control system.
Git is free software.

果然,还可以继续回退到上一个版本wrote a readme file,不过且慢,我们用git log再看看现在版本库的状态:

commit ea34578d5496d7dd233c827ed32a8cd576c5ee85
Author: Michael Liao <askxuefeng@gmail.com>
Date:   Tue Aug 20 14:53:12 2013 +0800

    add distributed

commit cb926e7ea50ad11b8f9e909c05226233bf755030
Author: Michael Liao <askxuefeng@gmail.com>
Date:   Mon Aug 19 17:51:55 2013 +0800

    wrote a readme file

最新的那个版本append GPL已经看不到了!好比你从21世纪坐时光穿梭机来到了19世纪,想再回去已经回不去了,肿么办?只要上面的命令行窗口还没有被关掉,你就可以顺着往上找啊找啊,找到那个append GPL的commit id是3628164...,于是就可以指定回到未来的某个版本:
git reset --hard 3628164 (退回指定版本)。

如果已经关掉了命令行,找不到新版本的commit id怎么办?Git提供了一个命令git reflog用来记录你的每一次命令(历史命令,类似日志),其中就包含了commit id:

git reflog      查询历史版本号
ea34578 HEAD@{0}: reset: moving to HEAD^
3628164 HEAD@{1}: commit: append GPL
ea34578 HEAD@{2}: commit: add distributed
cb926e7 HEAD@{3}: commit (initial): wrote a readme file

第二行显示append GPL的commit id是3628164,现在,你又可以乘坐时光机回到未来了。

版本号没必要写全,前几位就可以了,Git会自动去找。当然也不能只写前一两位,一般7-8位就行了,因为Git可能会找到多个版本号,就无法确定是哪一个了。
Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git仅仅是把HEAD从指向append GPL:

改为指向add distributed:

然后顺便把工作区的文件更新了。所以你让HEAD指向哪个版本号,你就把当前版本定位在哪。

放弃本地所有更改,常用于live服务器上同步代码git checkout . && git clean -xdf


撤销修改

当发现某部分代码需要撤销时git checkout -- filename可以丢弃工作区的修改:git checkout -- readme.txt(把readme.txt文件在工作区的修改全部撤销)

这里有两种情况:

命令中的--很重要,没有--,就变成了“切换到另一个分支”的命令,我们在后面的分支管理中会再次遇到git checkout命令。

现在假定是凌晨3点,你不但写了一些胡话,还git add到暂存区了,庆幸的是,在commit之前,你发现了这个问题。用git status查看一下,修改只是添加到了暂存区,还没有提交。用命令git reset HEAD file(可以把暂存区的修改撤销掉(unstage),不带file则撤销所有文件的更改,重新放回工作区,此时就相当于没有git add的状态,但文件是改动过的)

git reset命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用HEAD时,表示最新的版本。再用git status查看一下,现在暂存区是干净的,工作区有修改。
丢弃工作区的修改:git checkout -- readme.txt 可直接恢复文件并恢复版本库的内容(丢弃所有未git add的内容,回到最近一个最新版本)
注意:要完全撤回修改的代码,要git reset HEAD file 后执行git checkout -- file 才会完全包括工作区的内容也删除。

现在,假设你不但改错了东西,还把暂存区提交到了版本库(执行了git commit),怎么办呢?还可以版本回退到上一个版本。如果还没有把自己的本地版本库推送到远程,可以用:git reset --hard HEAD 恢复到当前版本(改动了但没有commit的那个版本,如果进行了commit,此次commit就是一个新版本,只能通过git reset --hard HEAD^回到上一个commit)。如果已经提交到了远程,只能通过git reset --hard HEAD^版本回退了。


标签管理

发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。
Git的标签虽然是版本库的快照,但其实它就是指向某个commit的指针(跟分支很像对不对?但是分支可以移动,标签不能移动),所以,创建和删除标签都是瞬间完成的。

Git有commit,为什么还要引入tag?

“请把上周一的那个版本打包发布,commit号是6a5819e...” ;“一串乱七八糟的数字不好找!”

如果换一个办法:

“请把上周一的那个版本打包发布,版本号是v1.2” ;“按照tag v1.2查找commit就行!”
tag就是一个让人容易记住的有意义的名字,它跟某个commit绑在一起。

创建标签

在Git中打标签非常简单,首先,切换到需要打标签的分支上:

git branch    #查看有哪些分支
git checkout master  #切换到要打标签的分支上

然后,敲命令git tag <name>就可以在当前分支的最新已提交的状态(HEAD)打一个新标签:git tag v1.0

默认标签是打在最新提交的commit上的。有时候,如果忘了打标签,比如,现在已经是周五了,但应该在周一打的标签没有打,怎么办?

方法是找到历史提交的commit id,然后打上就可以了:

git log --pretty=oneline --abbrev-commit

6a5819e merged bug fix 101
cc17032 fix bug 101
7825a50 merge with no-ff
6224937 add merge
59bc1cb conflict fixed
400b400 & simple
75a857c AND simple
fec145a branch test
d17efd8 remove test.txt
...

比方说要对add merge这次提交打标签,它对应的commit id是6224937,敲入命令:
git tag v0.9 6224937

还可以创建带有说明的标签,用-a指定标签名,-m指定说明文字:
git tag -a v0.1 -m "version 0.1 released" 3628164

查看所有标签:git tag

注意,标签不是按时间顺序列出,而是按字母排序的。可以用git show <tagname>查看标签详细信息。

还可以通过-s用私钥签名一个标签:
git tag -s v0.2 -m "signed version 0.2 released" fec145a(原来的-a换为-s)

签名采用PGP签名,因此,必须首先安装gpg(GnuPG),如果没有找到gpg,或者没有gpg密钥对,就会报错:

gpg: signing failed: secret key not available
error: gpg failed to sign the data
error: unable to sign the tag

如果报错,请参考GnuPG帮助文档配置Key。如果签名不成功可以加-u 参数,详见:http://airk000.github.io/git/2013/09/30/git-tag-with-gpg-key

git show <tagname>也可以看到PGP的签名信息:

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.12 (Darwin)

iQEcBAABAgAGBQJSGpMhAAoJEPUxHyDAhBpT4QQIAKeHfR3bo...
-----END PGP SIGNATURE-----

用PGP签名的标签是不可伪造的,因为可以验证PGP签名。验证签名的方法比较复杂,这里就不介绍了。

操作标签

git tag -d <tagname>可以删除一个本地标签。
git push origin <tagname>可以推送一个本地标签到远程库。
git push origin --tags可以推送全部未推送过的本地标签到远程库。

删除时先删除本地对应的标签,再运行命令git push origin :refs/tags/<tagname>删除一个远程标签。


分支

创建分支可以在分支上工作而不影响master分支,当分支工作完成时再合并到master分支上。

一般流程:

只剩master分支啦。

想要深入了解,参考:

猴子Git  Pro Git  廖雪峰Git

分支策略

在实际开发中,我们应该按照几个基本原则进行分支管理:

  首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;平时干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
所以,团队合作的分支看起来就像这样:

Git分支十分强大,在团队开发中应该充分应用。合并分支时,加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward合并就看不出来曾经做过合并。

分支管理策略

合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。如果要强制禁用Fast forward模式(加参数--no-ff),Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。

下面来测试一下--no-ff方式的git merge

创建分支、修改文件,提交缓存,切换回master分支,准备合并dev分支,因为本次合并要创建一个新的commit,所以加上-m参数,把commit描述写进去。
git merge --no-ff -m "merge with no-ff" dev

git log看看分支历史:

git log --graph --pretty=oneline --abbrev-commit

*   7825a50 merge with no-ff
|\
| * 6224937 add merge
|/
*   59bc1cb conflict fixed
...

冲突解决

当合并出现冲突时,一般要手动解决。
直接查看master分支的readme.txt(冲突文件)的内容并修改:

Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>> feature1

修改完毕,再次提交即可。

Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容。

用带参数的git log也可以看到分支的合并情况:git log --graph --pretty=oneline --abbrev-commit
用git log --graph命令可以看到分支合并图。

详见:廖雪峰Git解决冲突

错误解决

答:先pull 下来 再push上去。

Merge the remote changes before pushing again. See the 'Note about
fast-forwards' section of 'git push --help' for details.
答:是因为远程用web创建的的库和本地不一样。git pull origin master (先pull 下来 再push 上去)

把[remote "origin"]项中https://github.com 改为https://myleolan@github.com
就是加上用户名@之后再次执行git push 输入密码即可,一般用git协议不会报此错误,地址改为git@github.com:myleolan/XXXX.git


Bug分支

当你接到一个修复一个代号101的bug的任务时,很自然地,你想创建一个分支issue-101来修复它,但是,等等,当前正在dev上进行的工作还没有提交;并不是你不想提交,而是工作只进行到一半,还没法提交,预计完成还需1天时间。但是,必须在两个小时内修复该bug,怎么办?

Git还提供了一个stash功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:git stash现在,用git status查看工作区,就是干净的(除非有没有被Git管理的文件),因此可以放心地创建分支来修复bug。

修复Bug一般流程

首先确定要在哪个分支上修复bug,假定需要在master分支上修复,就从master创建临时分支并切换分支:

git checkout master
git checkout -b issue-101   #创建并切换分支

现在修复bug,需要把“Git is free software ...”改为“Git is a free software ...”,然后提交:

git add readme.txt 
git commit -m "fix bug 101"

修复完成后,切换到master分支,并完成分支合并,最后删除issue-101分支

 git checkout master
 git merge --no-ff -m "merged bug fix 101" issue-101  #合并分支
 git branch -d issue-101  #删除分支

bug修复了,现在,可以接着回到dev分支干活了!

git checkout dev
git status   #此时可以看到工作区是干净的
git stash list  #查看已储藏的工作现场

stash@{0}: WIP on dev: 6224937 add merge

工作现场还在,Git把stash内容存在某个地方了,需要恢复,有两个办法:

执行git stash pop恢复,再用git stash list查看,就看不到任何stash内容了
可以多次stash,恢复的时候,先用git stash list查看,然后恢复指定的stash,用命令:git stash apply stash@{0} (stash@{0}是封存的标识)


Feature分支

软件开发中,总有无穷无尽的新的功能要不断添加进来。添加一个新功能时,不希望因为一些实验性质的代码,把主分支搞乱了,所以,每添加一个新功能,最好新建一个feature分支,在上面开发,完成后,合并,最后,删除该feature分支

#当前在工作分支dev上。
git checkout -b feature-vulcan(name)  #创建并切换到该分支
#修改代码完成之后
git add vulcan.py
git status
git commit -m "add feature vulcan"
git checkout dev   #切回dev,准备合并
#一切顺利的话,feature分支和bug分支是类似的,合并,然后删除。
执行以下两句就可以完成任务了:
git merge --no-ff -m "merged feature-vulcan" feature-vulcan #合并分支
git branch -d feature-vulcan   #删除分支

但是,就在此时,接到上级命令,因经费不足,新功能必须取消!虽然白干了,但是这个分支还是必须就地销毁

git branch -d feature-vulcan

error: The branch 'feature-vulcan' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature-vulcan'.

Git提示销毁失败:feature-vulcan分支还没有被合并,如果删除,将丢失掉修改,如果要强行删除,需要使用命令git branch -D feature-vulcan

现在我们强行删除:git branch -D feature-vulcan OK,现在我们切换回dev分支继续工作。


自定义Git配置(高级)

我们已经配置了user.nameuser.email,实际上,Git还有很多可配置项。

忽略特殊文件

有些时候,你必须把某些文件放到Git工作目录中,但又不能提交它们,比如保存了数据库密码的配置文件啦,等等,每次git status都会显示Untracked files ...,有强迫症的童鞋心里肯定不爽。好在Git考虑到了大家的感受,这个问题解决起来也很简单,在Git工作区的根目录下创建一个特殊的.gitignore文件,然后把要忽略的文件名填进去,Git就会自动忽略这些文件。

不需要从头写.gitignore文件,GitHub已经为我们准备了各种配置文件,只需要组合一下就可以使用了。所有配置文件可以直接在线浏览:https://github.com/github/gitignore 已经配置好了要忽略的文件,下载回来要文件名要改为.gitignore放在项目根目录下。

忽略文件的原则是:

  1. 忽略操作系统自动生成的文件,比如缩略图等;
  2. 忽略编译生成的中间文件、可执行文件等,也就是如果一个文件是通过另一个文件自动生成的,那自动生成的文件就没必要放进版本库,比如Java编译产生的.class文件;
  3. 忽略你自己的带有敏感信息的配置文件,比如存放口令的配置文件。

举个例子:

假设你在Windows下进行Python开发,Windows会自动在有图片的目录下生成隐藏的缩略图文件,如果有自定义目录,目录下就会有Desktop.ini文件,因此你需要忽略Windows自动生成的垃圾文件;然后,继续忽略Python编译产生的.pyc、.pyo、dist等文件或目录(忽略文件夹在文件夹名后加/)

加上你自己定义的文件,最终得到一个完整的.gitignore文件,内容如下:

# Windows:
Thumbs.db
ehthumbs.db
Desktop.ini

# Python:
*.py[cod]
*.so
*.egg
*.egg-info
dist
build

# My configurations:
db.ini
deploy_key_rsa
新建文件夹/

最后一步就是把.gitignore也提交到Git,就完成了!当然检验.gitignore是否生效的标准是git status命令是不是说working directory clean。

使用Windows的童鞋注意了,如果你在资源管理器里新建一个.gitignore文件,它会非常弱智地提示你必须输入文件名,在第三方文本编辑器里“保存”或者“另存为”就可以把文件保存为.gitignore了。

有些时候,你想添加一个文件到Git,但发现添加不了,原因是这个文件被.gitignore忽略了:

git add App.class

The following paths are ignored by one of your .gitignore files:
App.class
Use -f if you really want to add them.

如果你确实想添加该文件,可以用-f强制添加到Git:git add -f App.class

或者你发现,可能是.gitignore写得有问题,需要找出来到底哪个规则写错了,可以用git check-ignore命令检查:

git check-ignore -v App.class
.gitignore:3:*.class    App.class

Git会告诉我们,.gitignore的第3行规则忽略了该文件,于是我们就可以知道应该修订哪个规则。

配置命令别名

有没有经常敲错命令?比如git status?status这个单词真心不好记。如果敲git st就表示git status那就简单多了。

git config --global alias.st status   #st表示status
git config --global alias.co checkout #co表示checkout
git config --global alias.ci commit   #ci表示commit
git config --global alias.br branch   #br表示branch

--global参数是全局参数,这些命令在这台电脑的所有Git仓库下都有用。

在"撤销修改"一节中,我们知道,命令git reset HEAD file可以把暂存区的修改撤销掉(unstage),重新放回工作区。既然是一个unstage操作,就可以配置一个unstage别名:
git config --global alias.unstage 'reset HEAD'

当你敲入命令:git unstage test.py实际上Git执行的是:git reset HEAD test.py

配置一个git last,让其显示最后一次提交信息:git config --global alias.last 'log -3' (数字代表最近的几次提交);这样,用git last就能显示最近3次的提交:

甚至还有人丧心病狂地把lg配置成了:

git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"

来看看git lg的效果:

关于配置文件

配置Git的时候,加上--global是针对当前用户起作用的,如果不加,那只针对当前的仓库起作用。

cat .git/config 
[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
    ignorecase = true
    precomposeunicode = true
[remote "origin"]
    url = git@github.com:myleolan/learngit.git
    fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
    remote = origin
    merge = refs/heads/master
[alias]     #命令别名,要删除别名,直接删掉对应的行即可。
    last = log -1
cat .gitconfig
[alias]
    co = checkout
    ci = commit
    br = branch
    st = status
[user]
    name = Your Name
    email = your@email.com

配置别名也可以直接修改这个文件,如果改错了,可以删掉文件重新通过命令配置(参考第2节 “Git配置”)。


团队协作

抓取分支

多人协作时,大家都会往master和dev分支上推送各自的修改。现在,模拟一个你的小伙伴,可以在另一台电脑(注意要把SSH Key添加到GitHub)或者同一台电脑的另一个目录下克隆:
git clone git@github.com:myleolan/learngit.git

当你的小伙伴从远程库clone时,默认情况下,你的小伙伴只能看到本地的master分支。可以用git branch命令看看;现在,你的小伙伴要在dev分支上开发,就必须创建远程origin的dev分支到本地,于是他用命令创建本地dev分支:git checkout -b dev origin/dev (在origin上创建dev,-b切换到dev分支;如果本地已经有dev分支了,直接切换就好)

现在,他就可以在dev上继续修改,然后,时不时地把dev分支push到远程:
git commit -m "add /usr/bin/env"

本地已经有dev分支了就不用创建了,直接执行下面的命令会自动在远程服务器新建dev分支。
git push origin dev (把本地dev推到远程origin上,会自动寻找origin的dev分支)

你的小伙伴已经向origin/dev分支推送了他的提交,而碰巧你也对同样的文件作了修改,并试图推送:

git add hello.py
git commit -m "add coding: utf-8"
git push origin dev

To git@github.com:myleolan/learngit.git
 ! [rejected]        dev -> dev (non-fast-forward)
error: failed to push some refs to 'git@github.com:myleolan/learngit.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
hint: before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

推送失败,因为你的小伙伴的最新提交和你试图推送的提交有冲突,解决办法也很简单,Git已经提示我们,先用git pull把最新的提交从origin/dev抓下来,然后,在本地合并,解决冲突,再推送:

git pull

remote: Counting objects: 5, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 3 (delta 0)
Unpacking objects: 100% (3/3), done.
From github.com:myleolan/learngit
   fc38031..291bea8  dev        -> origin/dev
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details
    git pull <remote> <branch>
If you wish to set tracking information for this branch you can do so with:
    git branch --set-upstream dev origin/<branch>

git pull也失败了,原因是没有指定本地dev分支与远程origin/dev分支的链接,根据提示,设置dev和origin/dev的链接(绑定这两个分支):

git branch --set-upstream dev origin/dev 
如果git pull还是失败,说明绑定不了。
试试官方更改的命令:git branch --set-upstream-to=origin/dev dev

git pull -f origin master 强制覆盖也行,但很危险。

再次git pull成功,但是合并有冲突,需要手动解决,解决的方法和第12节“解决冲突”完全一样。解决后,提交,再push:

git commit -m "merge & fix hello.py"
git push origin dev

多人协作的工作模式通常是这样:

  1. 首先,可以试图用git push origin branch-name推送自己的修改;
  2. 如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;
  3. 如果合并有冲突,则解决冲突,并在本地提交;
  4. 没有冲突或者解决掉冲突后,再用git push origin branch-name推送就能成功!
    如果git pull提示“no tracking information”,则说明本地分支和远程分支的链接关系没有创建,用命令:

git branch --set-upstream branch-name origin/branch-name
这就是多人协作的工作模式,一旦熟悉了,就非常简单。

F&Q

签出远程分支,出现以下错误:
fatal: Cannot update paths and switch to branch 'develop' at the same time.
解决方法:
git fetchgit checkout -b develop origin/develop
因为本地还没有"develop"分支信息,需要先fetch或者pull

假设有人往服务器上推送了一个新的分支,但是我不知道分支的名称是什么,我如何能获取到服务器上的分支列表呢?

  a. 你直接去问他
  b. 如果用GitHub,直接去网站看
  c. `git ls-remote --heads origin`

推送分支

推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上:git push origin master

如果要推送其他分支,比如dev,就改成:git push origin dev

但是,并不是一定要把本地分支往远程推送,那么,哪些分支需要推送,哪些不需要呢?
● master分支是主分支,因此要时刻与远程同步;
● dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;
● bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;
● feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。
总之,就是在Git中,分支完全可以在本地自己藏着玩,是否推送,视你的心情而定!


参与开源项目

如何参与一个开源项目呢?比如人气极高的bootstrap项目,这是一个非常强大的CSS框架,你可以访问它的GitHub项目主页,点“Fork”就在自己的账号下克隆了一个bootstrap仓库,然后,从自己的账号下clone:git clone git@github.com:**your_username**/bootstrap.git

一定要从自己的账号下clone仓库,这样你才能推送修改(在第4节 “创建远程仓库”中结尾就提到了另一种克隆方式)。如果从bootstrap的作者的仓库地址,采用git clone git@github.com:twbs/bootstrap.git的方式克隆,因为没有权限,你将不能推送修改(见第4节 “创建远程仓库”结尾处,删除.git文件夹的方法)。

Bootstrap的官方仓库twbs/bootstrap、你在GitHub上克隆的仓库your_username/bootstrap,以及你自己克隆到本地电脑的仓库,他们的关系就像下图显示的那样:


自己搭建Git服务器

GitHub就是一个免费托管开源代码的远程仓库。但是对于某些视源代码如生命的商业公司来说,既不想公开源代码,又舍不得给GitHub交保护费,那就只能自己搭建一台Git服务器作为私有仓库使用。搭建Git服务器需要准备一台运行Linux的机器,强烈用Ubuntu、Debian或者Centos、redhat,这样,通过几条简单的apt命令就可以完成安装。
root登录

在/data/git/目录下输入命令:
sudo git init --bare learngit.git

Git就会创建一个裸仓库,裸仓库没有工作区,因为服务器上的Git仓库纯粹是为了共享,所以不让用户直接登录到服务器上去改工作区,并且服务器上的Git仓库通常都以.git结尾。
sudo chown -R git:git learngit.git

Git服务器就已经搭得差不多了。下面我们在客户端clone一下远程仓库
git clone git@IP:/data/git/learngit.git
Cloning into 'learngit'...
The authenticity of host '192.168.8.34 (192.168.8.34)' can't be established.
RSA key fingerprint is 2b:55:45:e7:4c:29:cc:05:33:78:03:bd:a8:cd:08:9d.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.8.34' (RSA) to the list of known hosts.
git@192.168.8.34's password:
能连接但要密码,接着下一步。

RSAAuthentication yes     
PubkeyAuthentication yes     
AuthorizedKeysFile  .ssh/authorized_keys

这里我们可以看到公钥存放在.ssh/authorized_keys文件中。所以我们在/home/git下创建.ssh目录,然后创建authorized_keys文件,收集所有需要登录的用户的公钥,就是他们自己的id_rsa.pub文件,把所有公钥导入到/home/git/.ssh/authorized_keys文件里,一行一个。
此时再次连接已经可以免密钥登录啦。

编辑/etc/passwd文件完成。找到类似下面的一行:
git:x:1001:1001:,,,:/home/git:/bin/bash
改为:
git:x:1001:1001:,,,:/home/git:/usr/bin/git-shell

这样,git用户可以正常通过ssh使用git,但无法登录shell,因为我们为git用户指定的git-shell每次一登录就自动退出。

git clone git@IP:/data/git/learngit.git
Cloning into 'sample'...
warning: You appear to have cloned an empty repository.
剩下的推送就简单了。

管理公钥

如果团队很小,把每个人的公钥收集起来放到服务器的/home/git/.ssh/authorized_keys文件里就是可行的。如果团队有几百号人,就没法这么玩了,这时,可以用Gitosis来管理公钥。
这里我们不介绍怎么玩Gitosis了,几百号人的团队基本都在500强了,相信找个高水平的Linux管理员问题不大。管理公钥也可用Gitolite
管理权限
有很多不但视源代码如生命,而且视员工为窃贼的公司,会在版本控制系统里设置一套完善的权限控制,每个人是否有读写权限会精确到每个分支甚至每个目录下。因为Git是为Linux源代码托管而开发的,所以Git也继承了开源社区的精神,不支持权限控制。不过,因为Git支持钩子(hook),所以,可以在服务器端编写一系列脚本来控制提交等操作,达到权限控制的目的。Gitolite就是这个工具。
这里我们也不介绍Gitolite了,不要把有限的生命浪费到权限斗争中。

搭建服务器的同时采用Gitolite来管理权限,参考:
https://my.oschina.net/u/2351685/blog/509322

Gitolite的使用,参考:http://www.uml.org.cn/pzgl/201404092.asp


参考资料:

连猴子都会的git:http://backlogtool.com/git-guide/cn/
Pro Git:http://iissnan.com/progit/
廖雪峰git:http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000
外国友人git手册:https://pan.baidu.com/s/1kU5OCOB#path=%252Fpub%252Fgit
10个迅速提升你 Git 水平的提示:http://www.oschina.net/translate/10-tips-git-next-level
Git使用中的一些奇技淫巧:https://mp.weixin.qq.com/s/-95z7MCleajm0S_zd8lD4g


Git Flow

Git Flow工作流程:https://shuwoom.com/?p=4161

(●゚ω゚●)