Git权威指南
目录 |
初识Git
- git add -u/-A/-p
- git pull remote-mirror local-master
- 导出提交历史(即从一现有代码生成补丁):
- 初始化git版本库。。。(这一步其实可能 很消耗时间,如果代码量很大的话)
- git tag v1
- git commit -a
- git format-patch v1..HEAD 这要求每次提交都是一个修正,实际中可能更想把所有提交合并成一个补丁。。。
- 重写提交说明:git commit --amend(修补提交,修改上一次提交???)
- 要修改历史提交的说明,需先定位:git rebase -i <commit-id>(怀疑这里rebase不是这么用的吧?)
- git diff
- --word-diff
- --cached
- 保存和恢复工作进度(push/pop):
- git stash(相对于push)
- git checkout <new_branch>
- ...
- git checkout <origin_branch>
- git stash pop
- 本地Git,与服务器端svn协同:
- git svn fetch(这相当于在Git控制的环境中执行svn update?)
- git svn rebase
- git svn dcommit
- Git命令默认分页(less -FRSX)
Git安装
- 获取最新版并丢弃本地修改:
- git fetch
- git clean -fdx
- git reset --hard
- git tag
- git checkout v1.7.4.1
- 命令补齐?/etc/bash_completion
- 中文支持:git config --global core.quotepath false
- Linux上,最好不要使用UTF-8以外的字符集(GBK)
- Mac OS X上安装Git:$ brew install git
- p40 Cygwin默认使用UTF-8字符集,并巧妙地与Windows字符集进行转换(?)
- PuTTY Pageant:提供SSH私钥?
- msysGit环境:
- $ ls --show-control-chars *.txt(显示中文文件名)
- TortoiseGit对中文提交说明的支持有缺陷???扯淡
Git初始化
- git grep
- git rev-parse --git-dir
- git config可以很方便地操作INI文件:
- GIT_CONFIG=test.ini git config a.b.c "Hello"
- 别名:git config --global alias.ci "commmit -s"
Git暂存区(stage)
- 当执行git status(-s是显示stage内容?)时,首先依据.git/index的时间戳等信息判断工作区是否有改动
- (依赖于文件时间戳的设计本质上说不是太可靠。。。)
- Git-diff比较:
- 工作区 vs 暂存区:git diff
- 暂存区 vs HEAD:git diff --cached
- 工作区 vs HEAD:git diff HEAD
- 不要使用git commit -a(绕过stage,直接提交)
- 每次修改需重新git add以增加到stage(这个add确实很容易把人搞糊涂!)
Git对象
- git cat-file [-t -p ...] <SHA1-40bit-ID>
- .git/objects:ID的前2位作为目录名,后38位作为文件名??
- .git/refs/heads/下的引用名称称为分支(引用指向某一个提交id!)# p90 Hg同时使用顺序数字和SHA1??
- HEAD^代表HEAD的上一次提交
- a5~5 相当于 a5^^^^^
- a5:path/to/file
Git重置
- git reset --hard HEAD^ (暂存区里的修改可能丢失)
- git reset --hard 9e8a761
- git reflog show master | head -5
- git reset --hard master@{2} 切换master回到2次修改之前
- 2种reset
- git reset [-q] [<commit>] [--] <paths> ...
- git reset[--soft / --mixed / --hard / --merge / --keep] [-q] [<commit>]
Git检出
- checkout时重置当前HEAD
- p102 当不是从master检出并提交后,由于提交没有被分支跟踪,则有可能被从版本库清除
- 解救:切换到master,然后merge刚才的提交(开发分支?):
- git merge acc2f9
- git log --graph --pretty=online
- *reset的默认是HEAD,而checkout的默认是stage(index)
- 检出并创建新分支:git checkout [-m] [[-b / --orphan] <new_branch>] [<start_point>]
git stash
- git stash(保存当前工作进度,分别对stage、working)可以多次保存进度!(save point?)
- git-<cmd>:Git大部分命令也是用脚本实现的(!),这一点上与Hg类似
- stash内部实现实际上就是一个特殊的分支(refs/stash)而已
- 与SVN的trunk/tags/branch目录相比,Git则把这种状态划分内置到命令里去了(话说这样一来我觉得还是SVN更容易理解的说)
- 把2个SVN串联起来实际上就相当于Git的stage了,哈哈
Git基本操作
- git stash apply(pop的话会删除进度?)
- 本地删除不会影响stage,而git rm则会修改stage,下一次commit生效
- git add 参数
- -u:标记本地所有改动(更新、删除)
- -A:所有改动和增加
- -i:交互式手工选择
- git describe
- .gitignore
- /TODO 只忽略此目录下的TODO
- .svn/ 所有的.svn(子)目录
- git archive --format=tar --prefix=1.0/ v1.0 | gzip > foo-v1.0.tag.gz
历史
- gitk --all
- gitk --since="2 weeks ago" #这种时间描述应该是从Ruby里借鉴来的
- gitg & (基于GTK+的)
- qgit & (基于Qt的)
- git blame -L 6,+5 Readme.txt
- git bisect start/good/bad/reset
- * Hg只能悔一次,Git因为有强大的reset,可以任意多次
- 时间旅行“回到未来”(这个比喻真不恰当!)
- git cherry-pick
- git rebase --onto <newbase> <since> <until>
- git rebase -i
- 丢弃历史,仅保留最近的100次提交:
- 创建根提交:git cat-file commit A^0 | sed -e '/^parent/ d' > tmpfile
- git hash-object -t commit -w --tmpfile
- git rebase --onto <new-root-commit-id> A master
- 分布式情况下修正一个错误提交的方法是反转提交:
- git revert HEAD
Git克隆
- git clone <repos> <new-dir>
- 不带参数(非裸版本库,工作区直接push会导致错误?只允许从备份库pull,不能反向push?)
- --bare(只有.git/,裸版本库,这时可以push)
- --mirror
- 可以与上游git fetch同步
- git push/pull [<remote-repos> [<ref-specs>]]
Git库管理
- git show-ref
- refs/heads/开头的是分支
- refs/remotes/是远程分支的本地映射分支
- refs/tags/是里程碑
- *为什么clone远程版本库会产生对象库打包和引用打包的效果呢?(文件在传输时被压缩了。。。)
- git fsck
- 没有被引用关联的松散对象,如暂存区引入的大文件
- git prune
- git fsck --no-reflogs
- git reflog expire --expire=now --all
- git prune
- git gc
- --prune=now
- 1.6.6+ 部分git命令会自动执行git gc --auto
- git merge
- git receive-pack(当接受到对方的push时)
- git rebase -i
- git am
- 触发条件:.git/objects/17目录下对象超过27个时
Git协议与工作协同
- 智能协议 vs 哑协议(.git/info/refs,git/objects/info/packs)
- ‘快进式’push:推送的push必须基于版本库的相应分支的现有基础
- 强制push(这种情况下会覆盖其他用户的,??)
- merge后push:
- git pull
- git push
- 禁止‘快进式’push:
- git --git-dir=/path/to/repos/shared.git config receive.denyNonFastForwards true
- 通过hooks和授权允许特定用户强制push(。。。)
冲突解决
- git pull = fetch + merge
- 自动合并
- 不同文件
- 相同文件的不同行
- 文件移动 & 文件内容的修改
- 自动合并成功,但有逻辑冲突:测试!!!
- 冲突解决:<<<<<<<=======>>>>>>>
- kdiff3
- 树冲突
- 合并策略:git merge [-s <strategy>] [-X <strategy-options>] <commit> ...
- ours
- theirs
- subtree
- octopus
Git里程碑
- git tag
- 命名规范:v1.0-init
- 不要以-开头
- / .不能出现在最后
- 不能出现2个以上连续的.
- 不能使用特殊字符: ~ ^: ? \
- 不能以.lock结尾
Git分支
- master:随着提交而变化
- 发布分支(Bugfix)
- 特性分支
- Vendor分支(专门创建一个与上游同步的分支)
- git branch dever-name/feature-name(分支命名带/?)
- 合并到主线:
- git checkout master
- git merge user1/funcA
- git cherry
- git push
- git branch -d user1/funcA
- 采用rebase时的分支关系图要比merge简单?
远程版本库
- git branch -r
- .git/refs/remotes/origin/ #see .git/config
- 从远程分支创建跟踪分支:git checkout -b v-1.x origin/v-1.x(如果没有名字冲突,可直接git checkout v-1.x)
- 跟踪分支的好处是可以git pull自动维护与上游分支的一致更新
- 如果希望对本地分支也支持跟踪功能:--track
- 注册新的远程版本库:git remote add new-remote-name <URL-to-remote-repos-git>
- git remote update
- 远程版本库中的tags同步到本地,不会创建新的名字空间!!
- -n(--no-tags)
补丁文件交互
- 创建补丁:
- git format-patch -s HEAD~3..HEAD(最近的3个提交导出为补丁文件)
- 接受邮件:mail -f user1-archive(mbox格式)
- 首先切换到本地的基准点:git checkout -b user1-patch-base HEAD~3
- 应用补丁:git am user1-archive
- 或可:cat *.patch | git am
- git apply:应用一般格式的补丁,但不提交
- StGit(没看出这里的用处到底怎么样的)
- stg uncommit -n 3
- stg pop -a(撤销版本库上的提交?)
- stg ser
- stg push/goto ...
- stg refresh
- stg show
- stg export(quilt格式的?)
- Quilt
经典Git协同模型
- Hg + MQ
- 入栈的补丁转为提交,出栈则是移走最新的提交
Topgit协同模型
- master用于与上游同步,建立本地的特性分支(refs/heads/t/feature_name)
- 为了实现特性分支导出为补丁,引入了特殊的引用(refs/top-bases/*),用于追踪各特性分支的基准分支
- tg create t/new-feature-branch-name [DEPS ...] 可指定多个依赖分支???
- 当依赖分支有更新时,tg info t/feature3 可看到t/feature1上的提交
- tg update
- tg summary
- --graphviz | dot -T png -o topgit-layout.png
- tg remote [--populate] [REMOTE]
- tg export
- --collapse
- .topdeps:删除重复的依赖??
子模块(submodule)协同模型
- svn:externals
- git submodule add /path/to/repos/libA.git lib/lib_a
- clone时默认不包含submodules,如果需要的话,
- git submodule init
- git submodule update
- ???这里的讲解太含糊了。。。fuck
子树合并*
- git read-tree --prefix=lib util-branch
- git checkout -- lib
- git write-tree
- git commit-tree ...
- 反方向:将项目的一个子目录导出为新的git项目,要求导出对应的历史。。。
- #see git filter-branch
- git-subtree插件*
Android式多版本库协同
- repo(相当于用Python脚本把多个Git仓库用目录组织起来。。。,manifest.xml)
- repo init --mirror -u git://.../manifest.git
- repo sync
- repo upload:向Gerrit服务器进行git push
Git与SVN协同模型
- git svn clone <svn-path> <dir-name>
- git svn fetch
- git svn rebase(注意,SVN提交都是线性的!)
- git rebase --continue
- git svn dcommit
- 推送之后,Git里的提交说明中嵌入了git-svn-id:标签(把映射关系加到Git commit log中,聪明!)
- 注意:使用git-svn时尽量不要在不同分支之间进行合并,而是尽量在一个分支上线性提交
- 如果真的需要在SVN不要分支间合并,使用SVN 1.5+,因为这样可以正确记录svn:mergeinfo属性
Git服务器
- 只读的HTTP哑协议
- 智能HTTP:1.6.6+ git-http-backend
- Gitweb:只读访问?
- git instaweb(如果安装了)
- git-daemon
- SSH方式
Gitolite
- 又是Perl开发的?
- gitolite-admin管理库?(Devops?)
- 访问控制及授权:略
Gitosis
- Python开发,SKIP
Gerrit代码审核
- 最早的Gerrit由Python之父Guido van Rossum开发的Rietveld分支而来,现在的Gerrit是用Java实现的
- 特殊引用:
- refs/for
- 创建一个<review-id>,并建立refs/changes/nn/<review-id>/m
- refs/changes
- refs/for
- hooks/commit-msg
- Change-Id
- Gerrit服务器架设:。。。略
- 第一个用户为系统管理员;建立公钥以启动ssh服务
Git托管
迁移到Git
- cvs2svn > cvs2git
- Hg迁移到Git:fast-export(略)
Git的其他应用
- etckeeper
- 作者自己开发的Gistore,shit
- git diff --binary
Git的其他特性
- 属性文件?
- hooks
- applypatch-msg
- pre/post-applypatch
- pre/post-commit
- prepare-commit-msg
- commit-msg
- pre-rebase
- post-checkout/merge
- pre/post-receive
- update
- post-update
- pre-auto-gc
- post-rewrite
- sparse checkout
- skip-worktree
- 浅clone:
- --depth 2
- .git/shadow
- --depth 2
- 提交嫁接:.git/info/grafts
- 提交替换:.git/refs/replace/
- git notes