git相关记录
mkdir git-init-bare
cd git-init-bare/
git init --bare
ls -alh git-init-bare
mkdir git-init
cd git-init/
git init
ls -alh git-init
git config
git config --system user.name 'luffing' --> /etc/gitconfig
git config --global user.name 'luffing' --> ~/.gitconfig
git config user.name 'luffing' --> gitproject/.git/config
git config --list :show all configuration
.gitignore
文件 .gitignore
的格式规范如下:
- 所有空行或者以注释符号
#
开头的行都会被 Git 忽略。 - 可以使用标准的 glob 模式匹配。
- 匹配模式最后跟反斜杠(
/
)说明要忽略的是目录。 - 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(
!
)取反。
所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。星号(*
)匹配零个或多个任意字符;[abc]
匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);问号(?
)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如 [0-9]
表示匹配所有 0 到 9 的数字)。
另外一种情况是,我们想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。换句话说,仅是从跟踪清单中删除。比如一些大型日志文件或者一堆 .a
编译文件,不小心纳入仓库后,要移除跟踪但不删除文件,以便稍后在 .gitignore
文件中补上,用 --cached
选项即可:
$ git rm --cached readme.txt
git log
有许多选项可以帮助你搜寻感兴趣的提交,接下来我们介绍些最常用的。
git log
-p 选项展开显示每次提交的内容差异
-2 用 -2
则仅显示最近的两次更新
--stat 许多摘要选项可以用,比如 --stat
,仅显示简要的增改行数统计
--pretty
oneline
将每个提交放在一行显示,这在提交数很大时非常有用。另外还有 short
,full
和 fuller
可以用,展示的信息或多或少有些不同。--pretty=format:"%h - %an, %ar : %s" 可以定制要显示的记录格式。
--graph
可以看到开头多出一些 ASCII 字符串表示的简单图形,形象地展示了每个提交所在的分支及其分化衍合情况。
--since
--until
另外还有按照时间作限制的选项,比如 --since
和 --until
。
例:git log --pretty="%h - %s" --author=gitster --since="2008-10-01" --before="2008-11-01"
git remote 管理远程分支
git remote -v 查看远程分支
git remote add luffing git@github.com:luffing/git-remote-test.git 新建远程分支,名字为luffing 分支地址为 git@github.com:luffing/git-remote-test.git
git push luffing master 把master分支推送至远程分支,推送至远程分支,需要使用ssh私钥,配置github.com的私钥来源,需要配置~/.ssh/config
git remote rename luffing luff 重命名远程分支名
git remote rm luffing 删除远程分支
git tag 打标签,一般用来标注版本
Git 使用的标签有两种类型:轻量级的(lightweight)和含附注的(annotated)。轻量级标签就像是个不会变化的分支,实际上它就是个指向特 定提交对象的引用。而含附注标签,实际上是存储在仓库中的一个独立对象,它有自身的校验和信息,包含着标签的名字,电子邮件地址和日期,以及标签说明,标签本身也允许使用 GNU Privacy Guard (GPG) 来签署或验证。一般我们都建议使用含附注型的标签,以便保留相关信息;当然,如果只是临时性加注标签,或者不需要旁注额外信息,用轻量级标签也没问题。
git tag -a v1.0 -m 'version 1.0' 含附注的标签
git tag -a 1.2 -m 'version 1.1' 9fceb02 给版本号打标签
git tag v1.1 简单标签
默认情况下,git push
并不会把标签传送到远端服务器上,只有通过显式命令才能分享标签到远端仓库。其命令格式如同推送分支,运行 git push origin [tagname]
即可
git push origin v1.2或
如果要一次推送所有本地新增的标签上去,可以使用 --tags
选项
git push origin --tags
git命令技巧
git config --global alias.st status 设置别名
git config --global alias.unstage 'reset HEAD --'
我们希望运行某个外部命令,而非 Git 的子命令,这个好办,只需要在命令前加上 !
就行。如果你自己写了些处理 Git 仓库信息的脚本的话,就可以用这种技术包装起来。作为演示,我们可以设置用 git visual
启动 gitk
:
git分支模型
我们假设在工作目录中有三个文件,准备将它们暂存后提交。暂存操作会对每一个文件计算校验和(即第一章中提到的 SHA-1 哈希字串),然后把当前版本的文件快照保存到 Git 仓库中(Git 使用 blob 类型的对象存储这些快照),并将校验和加入暂存区域:
$ git add README test.rb LICENSE
$ git commit -m 'initial commit of my project'
当使用 git commit
新建一个提交对象前,Git 会先计算每一个子目录(本例中就是项目根目录)的校验和,然后在 Git 仓库中将这些目录保存为树(tree)对象。之后 Git 创建的提交对象,除了包含相关提交信息以外,还包含着指向这个树对象(项目根目录)的指针,如此它就可以在将来需要的时候,重现此次快照的内容了。
现在,Git 仓库中有五个对象:三个表示文件快照内容的 blob 对象;一个记录着目录树内容及其中各个文件对应 blob 对象索引的 tree 对象;以及一个包含指向 tree 对象(根目录)的索引和其他提交信息元数据的 commit 对象。概念上来说,仓库中的各个对象保存的数据和相互关系看起来如图 3-1 所示:
![](http://git.oschina.net/progit/figures/18333fig0301-tn.png)
Git 是如何知道你当前在哪个分支上工作的呢?其实答案也很简单,它保存着一个名为 HEAD 的特别指针。请注意它和你熟知的许多其他版本控制系统(比如 Subversion 或 CVS)里的 HEAD 概念大不相同。在 Git 中,它是一个指向你正在工作中的本地分支的指针(译注:将 HEAD 想象为当前分支的别名。)
合并时出现了“Fast forward”的提示。由于当前 master
分支所在的提交对象是要并入的 hotfix
分支的直接上游,Git 只需把 master
分支指针直接右移。换句话说,如果顺着一个分支走下去可以到达另一个分支的话,那么 Git 在合并两者时,只会简单地把指针右移,因为这种单线的历史分支不存在任何需要解决的分歧,所以这种合并过程可以称为快进(Fast forward)。
有时候合并操作并不会如此顺利。如果在不同的分支中都修改了同一个文件的同一部分,Git 就无法干净地把两者合到一起(译注:逻辑上说,这种问题只能由人来裁决。)
在解决了所有文件里的所有冲突后,运行 git add
将把它们标记为已解决状态(译注:实际上就是来一次快照保存到暂存区域。)。因为一旦暂存,就表示冲突已经解决。
如果觉得满意了,并且确认所有冲突都已解决,也就是进入了暂存区,就可以用 git commit
来完成这次合并提交。提交的记录差不多是这样:
Merge branch 'iss53'
Conflicts:
index.html
#
# It looks like you may be committing a MERGE.
# If this is not correct, please remove the file
# .git/MERGE_HEAD
# and try again.
#
如果想给将来看这次合并的人一些方便,可以修改该信息,提供更多合并细节。比如你都作了哪些改动,以及这么做的原因。有时候裁决冲突的理由并不直接或明显,有必要略加注解。
如果你有个叫 serverfix
的分支需要和他人一起开发,可以运行 git push (远程仓库名) (分支名)
:
$ git push origin serverfix
Counting objects: 20, done.
Compressing objects: 100% (14/14), done.
Writing objects: 100% (15/15), 1.74 KiB, done.
Total 15 (delta 5), reused 0 (delta 0)
To git@github.com:schacon/simplegit.git
* [new branch] serverfix -> serverfix
这里其实走了一点捷径。Git 自动把 serverfix
分支名扩展为 refs/heads/serverfix:refs/heads/serverfix
,意为“取出我在本地的 serverfix 分支,推送到远程仓库的 serverfix 分支中去”。我们将在第九章进一步介绍 refs/heads/
部分的细节,不过一般使用的时候都可以省略它。也可以运行 git push origin serverfix:serverfix
来实现相同的效果,它的意思是“上传我本地的 serverfix 分支到远程仓库中去,仍旧称它为 serverfix 分支”。通过此语法,你可以把本地分支推送到某个命名不同的远程分支:若想把远程分支叫作 awesomebranch
,可以用 git push origin serverfix:awesomebranch
来推送数据。
值得注意的是,在 fetch
操作下载好新的远程分支之后,你仍然无法在本地编辑该远程仓库中的分支。换句话说,在本例中,你不会有一个新的 serverfix
分支,有的只是一个你无法移动的 origin/serverfix
指针。
如果要把该远程分支的内容合并到当前分支,可以运行 git merge origin/serverfix
。如果想要一份自己的 serverfix
来开发,可以在远程分支的基础上分化出一个新的分支来:
$ git checkout -b serverfix origin/serverfix
Branch serverfix set up to track remote branch refs/remotes/origin/serverfix.
Switched to a new branch "serverfix"
这会切换到新建的 serverfix
本地分支,其内容同远程分支 origin/serverfix
一致,这样你就可以在里面继续开发了。
从远程分支 checkout
出来的本地分支,称为 跟踪分支 (tracking branch)。跟踪分支是一种和某个远程分支有直接联系的本地分支。在跟踪分支里输入 git push
,Git 会自行推断应该向哪个服务器的哪个分支推送数据。同样,在这些分支里运行 git pull
会获取所有远程索引,并把它们的数据都合并到本地分支中来。
删除远程分支
如果不再需要某个远程分支了,比如搞定了某个特性并把它合并进了远程的 master
分支(或任何其他存放稳定代码的分支),可以用这个非常无厘头的语法来删除它:git push [远程名] :[分支名]
。如果想在服务器上删除 serverfix
分支,运行下面的命令:
$ git push origin :serverfix
To git@github.com:schacon/simplegit.git
- [deleted] serverfix
咚!服务器上的分支没了。你最好特别留心这一页,因为你一定会用到那个命令,而且你很可能会忘掉它的语法。有种方便记忆这条命令的方法:记住我们不久前见过的 git push [远程名] [本地分支]:[远程分支]
语法,如果省略 [本地分支]
,那就等于是在说“在这里提取空白然后把它变成[远程分支]
”。
一般我们使用衍合的目的,是想要得到一个能在远程分支上干净应用的补丁 — 比如某些项目你不是维护者,但想帮点忙的话,最好用衍合:先在自己的一个分支里进行开发,当准备向主项目提交补丁的时候,根据最新的 origin/master
进行一次衍合操作然后再提交,这样维护者就不需要做任何整合工作(译注:实际上是把解决分支补丁同最新主干代码之间冲突的责任,化转为由提交补丁的人来解决。),只需根据你提供的仓库地址作一次快进合并,或者直接采纳你提交的补丁。
衍合也可以放到其他分支进行,并不一定非得根据分化之前的分支。以图 3-31 的历史为例,我们为了给服务器端代码添加一些功能而创建了特性分支 server
,然后提交 C3 和 C4。然后又从 C3 的地方再增加一个 client
分支来对客户端代码进行一些相应修改,所以提交了 C8 和 C9。最后,又回到 server
分支提交了 C10。
![](http://git.oschina.net/progit/figures/18333fig0331-tn.png)
图 3-31. 从一个特性分支里再分出一个特性分支的历史。
假设在接下来的一次软件发布中,我们决定先把客户端的修改并到主线中,而暂缓并入服务端软件的修改(因为还需要进一步测试)。这个时候,我们就可以把基于 server
分支而非 master
分支的改变(即 C8 和 C9),跳过 server
直接放到 master
分支中重演一遍,但这需要用 git rebase
的 --onto
选项指定新的基底分支 master
:
$ git rebase --onto master server client
这好比在说:“取出 client
分支,找出 client
分支和 server
分支的共同祖先之后的变化,然后把它们在 master
上重演一遍”。是不是有点复杂?不过它的结果如图 3-32 所示,非常酷(译注:虽然 client
里的 C8, C9 在 C3 之后,但这仅表明时间上的先后,而非在 C3 修改的基础上进一步改动,因为 server
和 client
这两个分支对应的代码应该是两套文件,虽然这么说不是很严格,但应理解为在 C3 时间点之后,对另外的文件所做的 C8,C9 修改,放到主干重演。):
![](http://git.oschina.net/progit/figures/18333fig0332-tn.png)
图 3-33. 快进 master 分支,使之包含 client 分支的变化。
现在我们决定把 server
分支的变化也包含进来。我们可以直接把 server
分支衍合到 master
,而不用手工切换到 server
分支后再执行衍合操作 — git rebase [主分支] [特性分支]
命令会先取出特性分支 server
,然后在主分支 master
上重演:
$ git rebase master server
于是,server
的进度应用到 master
的基础上,如图 3-34 所示:
![](http://git.oschina.net/progit/figures/18333fig0334-tn.png)
图 3-34. 在 master 分支上衍合 server 分支。
然后就可以快进主干分支 master
了:
一旦分支中的提交对象发布到公共仓库,就千万不要对该分支进行衍合操作。
如果你遵循这条金科玉律,就不会出差错。否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你。
在进行衍合的时候,实际上抛弃了一些现存的提交对象而创造了一些类似但不同的新的提交对象。如果你把原来分支中的提交对象发布出去,并且其他人更新下载后在其基础上开展工作,而稍后你又用 git rebase
抛弃这些提交对象,把新的重演后的提交对象发布出去的话,你的合作者就不得不重新合并他们的工作,这样当你再次从他们那里获取内容时,提交历史就会变得一团糟。