安装Git
ubuntu: $ sudo apt-get install git
windows: 镜像安装
创建版本库
a. 选择一个合适的地方,创建一个空目录:
$ mkdir learngit
$ cd learngit
b. 通过git init命令把这个目录变成Git可以管理的仓库:
$ git init
c. 在git的根目录下(子目录也可以)创建文件readme.txt:
d. 把一个文件放到Git仓库只需要两步:
第一步:用命令git add告诉Git,把文件添加到仓库, 可以同时添加多个文件:
$ git add readme.txt
第二步:用命令git commit告诉Git,把文件提交到仓库:
$ git commit -m "this is a readme file!"
-m后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义的,这样你就能从历史记录里方便地找到改动记录。
版本回退
版本回退:
a. 查看仓库的当前状态:
$ git status
b. 查看修改的内容:
$ git diff
c. 查看仓库操作的历史记录:
$ git log
$ git log --pretty=oneline #简化输出历史记录
如下所示:
1094adb7b9b3807259d8cb349e7df1d4d6477073 (HEAD -> master) append GPL
e475afc93c209a690c39c13a46716e8fa000c366 add distributed
eaadf4e385e865d25c48e7ca9c8395c3f7dfaef0 wrote a readme file
# 1094a...是commit id,也就是版本号,是通过SHA1计算出来的十六进制数字。
d. 回退到上一个版本:
$ git reset --hard HEAD^
在Git中,用HEAD表示当前版本,上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100。
也可以直接通过版本号回退到某个版本:
$ git reset --hard 1094ad #版本号没有必要写全,前几位就可以Git会自动查找
f. 从旧版本回到新版本。 只能通过commit id,
$ git reflog #该命令用来记录每一次的命令,可以查找到commit id,
如下所示:
e475afc HEAD@{1}: reset: moving to HEAD^
1094adb (HEAD -> master) HEAD@{2}: commit: append GPL
e475afc HEAD@{3}: commit: add distributed
eaadf4e HEAD@{4}: commit (initial): wrote a readme file
工作区和暂存区
a. 工作区: git目录
b. 版本库: 隐藏目录.git即是版本库
版本库 = 暂存区(stage 或 index) + Git自动创建的第一个分支master + HEAD指针(指向master)+ ...
c. 把文件往Git版本库里添加的时候,是分两步执行的:
第一步是用git add把文件添加进去,实际上就是把文件修改添加到暂存区;
第二步是用git commit提交更改,实际上就是把暂存区的所有内容提交到当前分支。
因为我们创建Git版本库时,Git自动为我们创建了唯一一个master分支,所以,现在,git commit就是往master分支上提交更改。
需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。
管理修改
git比其他版本控制系统设计地优秀的一个很重要的原因就是: Git跟踪的是修改而非文件。每一次修改都要经过add 和 commit才能生效。
a. 撤回工作区的修改
$ git checkout -- readme.txt #意思就是,把readme.txt文件在工作区的修改全部撤销;
这里有两种情况:
一种是readme.txt自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
一种是readme.txt已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。
b. 把暂存区的修改退回工作区
$ git reset HEAD readme.txt
# git reset命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用HEAD时,表示最新的版本。
之后再使用命令 $ git checkout -- readme.txt 撤回工作区的修改
远程仓库
a. 把一个已有的本地仓库与新建的远程新仓库关联。
$ git remote add origin git@github.com:xinhaixufan/git.git
# origin Git默认的远程仓库的名字
b. 把本地库的所有内容推送到远程仓库
$ git push -u origin master # 第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令
$ git push origin master
c. 从远程库克隆:
$ git clone git@github.com:xinhaixufan/git.git
$ git clone https://github.com/xinhaixufan/git.git
# 要克隆一个仓库,首先必须知道仓库的地址,然后使用git clone命令克隆。
# Git支持多种协议,包括https,但通过ssh支持的原生git协议速度最快。
分支管理
a. 创建与合并分支:
每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master分支。HEAD严格来说不是指向提交,而是指向master,master才是指向提交的,所以,HEAD指向的就是当前分支。
一开始的时候,master分支是一条线,Git用master指向最新的提交,再用HEAD指向master,就能确定当前分支,以及当前分支的提交点:
每次提交,master分支都会向前移动一步,这样,随着你不断提交,master分支的线也越来越长。
当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上:
从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变:
假如我们在dev上的工作完成了,就可以把dev合并到master上。Git怎么合并呢?最简单的方法,就是直接把master指向dev的当前提交,就完成了合并:
合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下了一条master分支:
$ git branch dev # 创建dev分支
$ git checkout dev # 切换到dev分支
$ git checkout -b dev # 创建并切换到dev分支
$ git branch # 查看分支
如下:
* dev # *表示当前分支
master
$ git merge dev # 将dev分支合并到当前分支。
$ git merge dev
Updating d46f35e..b17d20e
Fast-forward # Fast-forward 模式表示,这次合并是“快进模式”,也就是直降将master指向dev的当前提交。然而并不是每次提交都可以是Fast-forward。
readme.txt | 1 +
1 file changed, 1 insertion(+)
$ git branch -d dev # 删除dev分支
***查看分支:git branch
***创建分支:git branch <name>
***切换分支:git checkout <name>
***创建+切换分支:git checkout -b <name>
***合并某分支到当前分支:git merge <name>
***删除分支:git branch -d <name>
b. 解决冲突问题:
当master分支和dev分支各有新的提交,这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突:
$ git status $该命令会提示哪个文件存在冲突
需要手动修改冲突后再提交,如下:
Git tracks changes of files.
<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>> feature1
Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容,我们修改如下后保存:
Creating a new branch is quick and simple.
此时可以再进行提交
用带参数的git log也可以看到分支的合并情况:
$ git log --graph --pretty=oneline --abbrev-commit
* cf810e4 (HEAD -> master) conflict fixed
|\
| * 14096d0 (feature1) AND simple
* | 5dc6824 & simple
|/
* b17d20e branch test
* d46f35e (origin/master) remove test.txt
$ git log --graph # 可以查看分支合并图
--abbrev-commit # 只显示部分前缀
c. 分支管理策略:
如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit
$ git merge --no-ff -m "merge with no-ff" dev # --no-ff参数,表示禁用Fast forward
d. Bug分支
当你接到一个修复一个代号101的bug的任务时,很自然地,你想创建一个分支issue-101来修复它,但是,当前正在dev上进行的工作还没有提交:
此时,Git还提供了一个stash功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:
$ git stash
之后可以再master分支上创建临时分支issue-101来进行工作,完成之后恢复dev分支的工作现场;
$ git stash list # 查看工作现场
两种恢复方式:
$ git stash apply # 恢复后stash内容不删除
$ git stash drop # 删除stash
$ git stash pop #恢复的同时删除stash
可以多次stash,恢复的时候:
$ git stash list
$ git stash apply stash@{0} # 恢复到指定的stash
e. Feature分支
添加一个新功能时,你肯定不希望因为一些实验性质的代码,把主分支搞乱了,所以,每添加一个新功能,最好新建一个feature分支,在上面开发,完成后,合并,最后,删除该feature分支。
但是当该功能取消时,就不需要进行合并,直接删除该分支:
$ git branch -D feature-vulcan # -D表示强制删除
f. 多人协作
* 多人协作的工作模式通常是这样:
* 首先,可以试图用git push origin 推送自己的修改;
* 如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;
* 如果合并有冲突,则解决冲突,并在本地提交;
* 没有冲突或者解决掉冲突后,再用git push origin 推送就能成功!
* 如果git pull提示no tracking information,则说明本地分支和远程分支的链接关系没有创建,用命令:
$ git branch --set-upstream-to <branch-name> origin/<branch-name>。
小结:
* 查看远程库信息,使用git remote -v;
* 本地新建的分支如果不推送到远程,对其他人就是不可见的;
* 从本地推送分支,使用git push origin branch-name,如果推送失败,先用git pull抓取远程的新提交;
* 在本地创建和远程分支对应的分支,使用git checkout -b branch-name origin/branch-name,本地和远程分支的名称最好一致;
* 建立本地分支和远程分支的关联,使用git branch --set-upstream branch-name origin/branch-name;
* 从远程抓取分支,使用git pull,如果有冲突,要先处理冲突。
g. Rebase
多人在同一个分支上协作时,很容易出现冲突。即使没有冲突,后push的童鞋不得不先pull,在本地合并,然后才能push成功。
每次合并再push后,分支变成了这样:
$ git log --graph --pretty=oneline --abbrev-commit
* d1be385 (HEAD -> master, origin/master) init hello
* e5e69f1 Merge branch 'dev'
|\
| * 57c53ab (origin/dev, dev) fix env conflict
| |\
| | * 7a5e5dd add env
| * | 7bd91f1 add new env
| |/
* | 12a631b merged bug fix 101
|\ \
| * | 4c805e2 fix bug 101
|/ /
* | e1e9c68 merge with no-ff
|\ \
| |/
| * f52c633 add merge
|/
* cf810e4 conflict fixed
总之看上去很乱,有强迫症的童鞋会问:为什么Git的提交历史不能是一条干净的直线?
$ git rebase #
rebase操作可以把本地未push的分叉提交历史整理成直线;
rebase的目的是使得我们在查看历史提交的变化时更容易,因为分叉的提交需要三方对比。
标签管理
a. 创建标签
* 命令git tag 用于新建一个标签,默认为HEAD,也可以指定一个commit id;
$ git tag v1.0
$ git tag v0.9 cf810
* 命令git tag -a <tagname> -m "blablabla..." <commit id> 可以指定标签信息;
$ git tag -a v1.0 -m "add tag" HEAD
* 命令git tag可以查看所有标签。
b. 操作标签
* 删除标签
$ git tag -d v1.0
推送标签至远程
$ git push origin v1.0 # 推送标签v1.0到远程
$ git push origin --tag # 一次性推送全部尚未推送到远程的本地标签
删除远程标签
** 第一步:先从本地删除
$ git tag -d v0.9
** 第二步:从远程删除
$ git push origin :refs/tags/v0.9
## 命令git push origin <tagname>可以推送一个本地标签;
## 命令git push origin --tags可以推送全部未推送过的本地标签;
## 命令git tag -d <tagname>可以删除一个本地标签;
## 命令git push origin :refs/tags/<tagname>可以删除一个远程标签。
自定义Git
a. 忽略特殊文件:
* 你必须把某些文件放到Git工作目录中,但又不能提交它们,每次git status都会显示Untracked files …
* 在Git工作区的根目录下创建一个特殊的.gitignore文件,然后把要忽略的文件名填进去,Git就会自动忽略这些文件。
* .gitignore文件本身要放到版本库里,并且可以对.gitignore做版本管理!
忽略文件的原则是:[Git文件忽略原则](https://github.com/github/gitignore)
* 忽略操作系统自动生成的文件,比如缩略图等;
* 忽略编译生成的中间文件、可执行文件等,也就是如果一个文件是通过另一个文件自动生成的,那自动生成的文件就没必要放进版本库,比如Java编译产生的.class文件;
* 忽略你自己的带有敏感信息的配置文件,比如存放口令的配置文件。
最后将.gitignore文件提交到git
如果确实想提交该文件可以强制提交到git
$ git add -f a.class
或者你发现,可能是.gitignore写得有问题,需要找出来到底哪个规则写错了,可以用git check-ignore命令检查
$ git check-ignore -v a.class
b. 配置别名:
$ git config --global alias st status # 将status命令配置别名为 st:
配置Git的时候,加上--global是针对当前用户起作用的,如果不加,那只针对当前的仓库起作用。
每个仓库的Git配置文件都放在.git/config文件中:
搭建Git服务器
GitHub就是一个免费托管开源代码的远程仓库。但是对于某些视源代码如生命的商业公司来说,既不想公开源代码,又舍不得给GitHub交保护费,那就只能自己搭建一台Git服务器作为私有仓库使用。
搭建Git服务器需要准备一台运行Linux的机器,强烈推荐用Ubuntu或Debian,这样,通过几条简单的apt命令就可以完成安装。
假设你已经有sudo权限的用户账号,下面,正式开始安装。
第一步,安装git:
$ sudo apt-get install git
第二步,创建一个git用户,用来运行git服务:
$ sudo adduser git
第三步,创建证书登录:
收集所有需要登录的用户的公钥,就是他们自己的id_rsa.pub文件,把所有公钥导入到/home/git/.ssh/authorized_keys文件里,一行一个。
第四步,初始化Git仓库:
先选定一个目录作为Git仓库,假定是/srv/sample.git,在/srv目录下输入命令:
$ sudo git init --bare sample.git
Git就会创建一个裸仓库,裸仓库没有工作区,因为服务器上的Git仓库纯粹是为了共享,所以不让用户直接登录到服务器上去改工作区,并且服务器上的Git仓库通常都以.git结尾。然后,把owner改为git:
$ sudo chown -R git:git sample.git
第五步,禁用shell登录:
出于安全考虑,第二步创建的git用户不允许登录shell,这可以通过编辑/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 clone git@server:/srv/sample.git
Cloning into 'sample'...
warning: You appear to have cloned an empty repository.
剩下的推送就简单了。
本文是自己在参考廖雪峰Git教程学习Git时的一些笔记,如有不详之处请移步至廖雪峰Git教程