Git 入门

一、诞生

很多人都知道,Linus在1991年创建了开源的Linux,从此,Linux系统不断发展,已经成为最大的服务器系统软件了。

Linus虽然创建了Linux,但Linux的壮大是靠全世界热心的志愿者参与的,这么多人在世界各地为Linux编写代码,那Linux的代码是如何管理的呢?

事实是,在2002年以前,世界各地的志愿者把源代码文件通过diff的方式发给Linus,然后由Linus本人通过手工方式合并代码!

你也许会想,为什么Linus不把Linux代码放到版本控制系统里呢?不是有CVS、SVN这些免费的版本控制系统吗?因为Linus坚定地反对CVS和SVN,这些集中式的版本控制系统不但速度慢,而且必须联网才能使用。有一些商用的版本控制系统,虽然比CVS、SVN好用,但那是付费的,和Linux的开源精神不符。

不过,到了2002年,Linux系统已经发展了十年了,代码库之大让Linus很难继续通过手工方式管理了,社区的弟兄们也对这种方式表达了强烈不满,于是Linus选择了一个商业的版本控制系统BitKeeper,BitKeeper的东家BitMover公司出于人道主义精神,授权Linux社区免费使用这个版本控制系统。

安定团结的大好局面在2005年就被打破了,原因是Linux社区牛人聚集,不免沾染了一些梁山好汉的江湖习气。开发Samba的Andrew试图破解BitKeeper的协议(这么干的其实也不只他一个),被BitMover公司发现了(监控工作做得不错!),于是BitMover公司怒了,要收回Linux社区的免费使用权。

Linus可以向BitMover公司道个歉,保证以后严格管教弟兄们,嗯,这是不可能的。实际情况是这样的:

Linus花了两周时间自己用C写了一个分布式版本控制系统,这就是Git!一个月之内,Linux系统的源码已经由Git管理了!牛是怎么定义的呢?大家可以体会一下。

Git迅速成为最流行的分布式版本控制系统,尤其是2008年,GitHub网站上线了,它为开源项目免费提供Git存储,无数开源项目开始迁移至GitHub,包括jQuery,PHP,Ruby等等。

二、安装

请参考 Github 上的教程。

三、配置

Git 提供了 git config 工具,专门用来配置或读取相应的工作环境变量。这些变量可以存放在以下三个不同的地方:

  • /etc/gitconfig 文件:系统中对所有用户都普遍适用的配置。若使用 git config 时用 –system 选项,读写的就是这个文件。
  • ~/.gitconfig 文件:用户目录下的配置文件只适用于该用户。若使用 git config 时用 –global 选项,读写的就是这个文件。
  • 工作目录中的 .git/config 文件:这里的配置仅仅针对当前项目有效。每一个级别的配置都会覆盖上层的相同配置,所以 .git/config 里的配置会覆盖 /etc/gitconfig 中的同名变量。

配置用户信息

$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com
$ git config --global core.editor emacs
$ git config --global merge.tool vimdiff

查看配置信息

要检查已有的配置信息,可以使用 git config –list 命令:

$ git config --list
user.name=Scott Chacon
user.email=schacon@gmail.com
color.status=auto
color.branch=auto
color.interactive=auto
color.diff=auto
...

有时候会看到重复的变量名,那就说明它们来自不同的配置文件(比如 /etc/gitconfig 和 ~/.gitconfig),不过最终 Git 实际采用的是最后一个。

也可以直接查阅某个环境变量的设定。

$ git config user.name
Scott Chacon

设置快捷键

$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.ci commit
$ git config --global alias.st status
$ git config --global alias.unstage 'reset HEAD --'
$ git config --global alias.last 'log -1 HEAD'

自动补全

wget https://raw.github.com/git/git/master/contrib/completion/git-completion.bash
mv git-completion.bash /etc/bash_completion.d/

四、初始化

以下内容过多,详细内容请查看这本教程:《Pro Git 2nd Edition》

配置与 github 的连接

ssh-agent bash
ssh-agent -s
ssh-add ~/.ssh/xxx@xxx.com
ssh -T git@github.com

配置代理

参考这篇文章 —— 《git 国内加速代理》

初始化设置

git config --global user.name "xxx"
git config --global user.email "xxx@xxx.com"
git config --global pull.rebase true # 设置默认使用 rebase 而不用 merge
git branch --set-upstream master origin/master # 设置默认分支
git push --set-upstream origin master # 设置默认分支

拉取全新新项目

git clone xxx.git
cd test
touch README.md
git add README.md
git commit -m "add README"
git push -u origin master

已有文件夹中创建项目

cd existing_folder
git init
git remote add origin xxx.git
git add .
git commit -m "Initial commit"
git push -u origin master

关联已有项目

cd existing_repo
git remote add origin xxx.git
git push -u origin --all
git push -u origin --tags

取消关联

git remote remove origin

重新设置远程仓库并关联新分支

方法一 直接修改
git remote xxx 查看指定远程仓库地址
git remote set-url origin git@github.com:kelvinblood/KeluLinuxKit.git
方法二 先删除再添加
git remote rm origin
git remote add origin git@github.com:kelvinblood/KeluLinuxKit.git
方法三 修改配置文件

进入git_test/.git 文件夹

[core] 
repositoryformatversion = 0 
filemode = true 
logallrefupdates = true 
precomposeunicode = true 
[remote "origin"] 
url = git@github.com:kelvinblood/KeluLinuxKit.git
fetch = +refs/heads/*:refs/remotes/origin/* 
[branch "master"] 
remote = origin 
merge = refs/heads/master
关联本地分支与远程分支
git branch --set-upstream-to=origin/remote_branch  your_branch

.gitignore

# 此为注释 – 将被 Git 忽略
# 忽略所有 .a 结尾的文件
*.a
# 但 lib.a 除外
!lib.a
# 仅仅忽略项目根目录下的 TODO 文件,不包括 subdir/TODO
/TODO
# 忽略 build/ 目录下的所有文件
build/
# 会忽略 doc/notes.txt 但不包括 doc/server/arch.txt
doc/*.txt
# ignore all .txt files in the doc/ directory
doc/**/*.txt

克隆指定深度

git clone --depth=1 git://someserver/somerepo

depth 用于指定克隆深度,为1即表示只克隆最近一次commit.

创建新的空白分支(孤儿分支)

创建为孤儿分支:

git checkout --orphan <branchname>

然后清除仓库中的缓存:

git rm --cached -r .

删除所有文件:

rm -rf *
echo '' > .gitignore

初始化提交

touch readme
git add .
git commit -m "init"

push

git push --set-upstream origin <branchname>

五、编辑操作

拉新分支

git checkout --track -b komachi origin/komachi

追加提交

$ git commit -m 'initial commit'
$ git add forgotten_file
$ git commit --amend

撤销提交

$ git checkout -- benchmarks.rb
$ git reset HEAD benchmarks.rb
$ git reset --soft xxx
$ git reset --hard xxx

文件对比

$ git diff		# 看暂存前后的变化
$ git diff --cached # 查看已经暂存起来的变化:
$ git rm 记录此次移除文件
$ git mv file_from file_to

查看一个文件的修改历史

$ git log [file_name]

查看文件每一行的提交记录

当事情出错时,先去指责别人是人类的天性之一。如果你的产品服务器挂了,使用git blame命令可以很容易找出罪魁祸首。这个命令可以将文件中的每一行的作者、最新的变更提交和提交时间展示出来。

$ git blame [file_name]

提交历史

$ git log -p -2			# 最近的两次更新
$ git log --pretty=format:"%h %cr %s" --graph
	--oneline- 压缩模式,以一行显示
	--graph- 图形模式
	--all- 显示所有
$ git log -n 1 --stat 	# 查看上次提交影响的文件
$ git reflog 			# 本地的历史节点

git reflog列出了head曾经指向过的一系列commit。要明白它们只存在于你本机中;而不是你的版本仓库的一部分,也不包含在push和merge操作中。

log的一些选项以及释义

选项	说明
-p	按补丁格式显示每个更新之间的差异。
--word-diff	按 word diff 格式显示差异。
--stat	显示每次更新的文件修改统计信息。
--shortstat	只显示 --stat 中最后的行数修改添加移除统计。
--name-only	仅在提交信息后显示已修改的文件清单。
--name-status	显示新增、修改、删除的文件清单。
--abbrev-commit	仅显示 SHA-1 的前几个字符,而非所有的 40 个字符。
--relative-date	使用较短的相对时间显示(比如,“2 weeks ago”)。
--graph	显示 ASCII 图形表示的分支合并历史。
--pretty	使用其他格式显示历史提交信息。可用的选项包括 oneline,short,full,fuller 和 format(后跟指定格式)。
--oneline	--pretty=oneline --abbrev-commit 的简化用法。

六、打包导出

标签

$ git tag v1.4-lw # 创建tag
$ git tag -n # 显示已有tag

$ git ls-remote --tags origin # 显示远端tag
$ git fetch --all --tags # 拉取最新tag

$ git checkout tags/v1.20.2 # 切换到tag
$ git checkout -b v1.20.2 tags/v1.20.2 # 切换到tag并创建分支

冲突

如果合并出现冲突,则修改冲突文件,并删除 «««<,======= 和 »»»> 这些行。在解决了所有文件里的所有冲突后,运行 git add 将把它们标记为已解决状态,再运行一次 git status 来确认所有冲突都已解决.

导出

Git提供了archive命令,可以导出一个干净的项目文档,不包括版本控制文件。

git archive --format zip -o kelu.zip HEAD
git archive -–format zip -o site-$(git log –pretty=format:”%h” -1).zip HEAD

git archive --format zip --output /path/to/file.zip master # 将 master 以zip格式打包到指定文件

导出最近一次提交的 Git 变更文件

这个命令是和gpt讨论出来的。

git archive -o "$(basename "$(pwd)").zip" HEAD $(git diff --name-only HEAD^ HEAD)

七、分支相关

本地分支

$ git branch testing 	# 新建分支	
$ git checkout testing 	# 切换分支
$ git checkout -b iss53 # 新建并切换到分支
$ git branch -d hotfix  # 删除分支
$ git merge iss53		# 合并分支到当前分支

远程分支

$ git push origin dev  # 生成远程dev分支
$ git push origin feature
$ git branch -r 		# 查看远程分支
$ git checkout --track origin/dev		# 从远程拉取并切换到dev分支
$ git checkout -b develop origin/dev	# 从远程拉取dev命名为develop,并切换
$ git push origin dev:dev				# 提交本地分支,如果在dev分支下工作,可直接git push
$ git push origin :dev				# 删除远程分支
$ git fetch origin					# 同步本地远程分

现场存储

当你正在开发一个功能时,突然boss让你尽快修改一个bug,此时最紧急的是fix bug. 而正开发的功能尚未完善还不能提交,这个时候就会想到能不能将手头的工作隔离开,去单单解决bug,然后提交bug,然后在进行手头工作。

$ git stash  #把当前工作现场“储藏”起来	
$ git stash pop # 还原成最新的现场,并删除stash列表里的这个存储
$ git stash apply #重新回来原来的工作时,只需把Stash区域的内容取出来应用到当前工作目录就行
$ git stash apply stash@{1} #应用某一个队列
$ git stash list #查看所有stash列表
$ git stash show #显示stash的内容具体是什么,同git stash apply一样,可以选择指定stash的名字。

git stash apply之后再git stash list会发现,apply后的stash还在stash列表中,如果要将其从stash列表中删除可以用:

$ git stash drop	#丢弃这个stash,stash的命令参数都可选择指定stash名字,否则就是最新的stash。
$ git stash pop #是应用与删除的快捷,一个命令即可
$ git stash apply --index #维持原来的样子,原来暂存的文件仍然是暂存状态,可以加上--index参数,否则都将变成未暂存状态

分支衍合merge

把一个分支整合到另一个分支有两种方法:merge和rebase(衍合) merge是把两个分支最新的快照和二者最新的共同祖先进行三方合并,产生一个新的提交对象。

$ git merge issueFix

如果没有冲突的话,merge完成。有冲突的话,git会提示那个文件中有冲突,比如有如下冲突:

	<<<<<<< HEAD:test.c	
	printf (“test1″);
	=======
	printf (“test2″);
	>>>>>>> issueFix:test.c

merge有两个参数,

git merge --no-ff指的是强行关闭fast-forward方式。

fast-forward方式就是当条件允许的时候,git直接把HEAD指针指向合并分支的头,完成合并。属于“快进方式”,有个地方不好的就是不能显示历史信息,在以后开发中我不知道有哪些分支曾经合并过,所以最好使用 no-ff:no fast forward的合并方式,这种方式在合并的同时会生成一个新的commit,这样,从分支历史上就可以看出分支信息。

$ git merge --no-ff -m "merge with no-ff" dev

git merge --squash 是用来把一些不必要commit进行压缩,比如说,你的feature在开发的时候写的commit很乱,那么我们合并的时候不希望把这些历史commit带过来,于是使用–squash进行合并,此时文件已经同合并后一样了,但不移动HEAD,不提交。需要进行一次额外的commit来“总结”一下,然后完成最终的合并。

总结: –no-ff:不使用fast-forward方式合并,保留分支的commit历史 –squash:使用squash方式合并,把多次分支commit历史压缩为一次

image

分支衍合cherry-pick

ps:Cherry-pick的内容转载自Git笔记(三) - 进击的马斯特

cherry-pick其实在工作中还挺常用的,一种常见的场景就是,比如我在A分支做了几次commit以后,发现其实我并不应该在A分支上工作,应该在B分支上工作,这时就需要将这些commit从A分支复制到B分支去了,这时候就需要cherry-pick命令了,B分支指着这些commit说:妈妈,我也要!

比如说,我们在master分支上继续做两次提交,第一次添加一行”test 10”,git commit -am "commit 10",第二次添加“test 11”,到达如下图的状态:

图25

这个时候我们发现,哦NO,我们不应该直接更改master分支,我们应该在自己的分支上做提交。这个时候先新建一个分支git checkout -b branch3 1a222c3,注意这里的最后一个参数是新分支的起点,也就是说,新的分支branch3是从“commit 8,9”开始的,现在我们需要把刚才的两次提交移动到新的分支上。运行git cherry-pick 0bda20e 1a04d5f,命令行会给出提示两个commit被复制到了当前分支上,此时SourceTree的状态如下图:

图26 确定这两个commit被复制到指定分支以后,在master分支上将这两个commit删除。先切回master分支:git checkout master,运行git reset --hard 1a222c3,此时SourceTree的状态图为:

图27 两个commit被成功的从master分支移动到了branch3分支。

分支衍合rebase

rebase是回到两个分支的共同祖先,根据要进行衍合的分支dev的历次提交对象,生成一系列文件补丁,然后以主干分支master的最后一个提交对象为新的出发点,逐个应用dev分支准备好的补丁文件,生成一个新的提交对象,改写dev的提交历史,使dev成为master的直接下游。然后回到master分支,进行一次快进合并。这样能够保持更加清晰的提交记录,就像没有使用过分支一样。

$ git checkout dev
$ git rebase master

分支策略

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

首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;

那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;

你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。

八、常用配置

git config --global user.name "kelu"
git config --global user.email admin@kelu.org
git config --global core.editor vim
git config --global merge.tool vimdiff
git config --global core.quotepath false
git config --global i18n.commitencoding utf-8
git config --global i18n.logoutputencoding utf-8
git config --global http.https://github.com.proxy 'socks5://xxx:1080'

git config --edit
git config --list

ssh的一些介绍 屏幕录制软件LICEcap