git 版本库
-
.git/
-
config 配置文件(core配置、上游分支[remote “origin”]、分支追踪[branch “分支名字”]等等)
-
HEAD 指向当前分支名
-
FETCH_HEAD ?(分支在远程的最新状态??)
-
MERGE_HEAD 记录合并的提交ID
-
MERGE_MSG 记录合并失败的信息
-
MERGE_MODE 标识合并状态
-
refs/ 引用命名空间
-
heads/ 分支
-
master
-
其他分支名 内容为最近一次的commitID
-
-
remotes/ 远程分支本地映射
- origin/ 远程版本库
-
tags/ 里程碑
-
stash 内存stash的commit id
-
-
git 对象
git有四种对象:commit(提交),tree(目录),blob(文件)tag(里程碑),每个对象都有一个ID, 每个commit对应一个tree,一个tree有若干blob
-
git cat-file -t 对象ID
查看该对象是什么类型 -
git cat-file -p 对象ID
查看这个对象的内容,commit的内容是父提交,treeID,作者等,tree的内容是这个目录的第一层级的文件和目录,blob的内容是文件内容。 -
git show 对象ID
查看这个对象(commit tree blob)的内容, 如果不加对象ID,默认show最近一次提交git show --name-only be097
只列出修改的文件名 -
提交号^
代表一次父提交,如 HEAD^ 代表最近一次提交的父提交 -
提交号~<n>
HEAD^^^ == HEAD~3
-
提交号^{tree}
代表这个提交对应的tree,这里的 ^ 没有父提交的意思
tree
-
顶层的tree 和 commit 一一对应, 一个commit对应一个顶层tree
每个目录是一个tree, tree下面任意层级内容改变, 该tree将改变
-
git ls-tree -l 某个treeID
查看这个treeID(代表一个目录)下的第一层级的文件和目录。参数-l显示文件大小某个treeID 也可以是一个commit id, git 会换算成对应的顶层treeID, 如果是HEAD,就是查看版本库。
-
TODO: stage 的tree id 如何查看?
git ls-files -s
查看暂存区的目录树 -
对于tree id,
git ls-tree treeID
和git cat-file -p treeID
等价
git 配置
-
对配置的读和写都依照三种优先级:当前版本库 > global(当前用户主目录) > system(/etc目录)
global配置是在
~/.gitconfig
project里的配置是在
.git/config
读:
git config <section>.<key>
写:
git config <section>.<key> <value>
删除配置:
git config --unset <section>.<key>
section可以是多级的。
-
git config -l
git config --list
查看所有配置, 可接优先级参数如 –global –system -
git config --global core.pager 'less -+$LESS -FRX'
配置分页器自动换行 -
git config --global color.ui true
设置彩色输出 -
git config --global alias.ci commit
为git操作添加别名,不过我更喜欢修改.bashrc添加别名,这样可以把git后的空格也省去。 -
git config core.whitespace tab-in-indent
git show 和git diff时可以高亮不规则的tab(TODO 其他?)
git init
git init --bare 库名
创建一个bare库
git rev-parse
-
git rev-parse 分支名
显示该分支名对应的commitID -
git rev-parse HEAD@{n}
查看HEAD改变对应的commit id,n 是数字,0代表当前HEAD,1代表上次HEAD依次类推HEAD变更历史可以使用
git reflog
获得 -
在工作区的非根目录下执行git命令,git会自动插在版本库.git,显示版本库位置命令
git rev-parse --git-dir
工作区根目录git rev-parse --show-toplevel
git reflog
获得HEAD的变化记录
git reflog --all Instead of listing <refs> explicitly, prune all refs
?????????
git add
git add 默认只把工作区修改和新增的文件加入index,并不会把在工作区的删除加入index。
-
git rm <path>
在工作区和index区删除该path文件 -
git add -u <path>
将修改,删除加入index, 但是不包括新增 -
git add -A <path>
将修改,新增,删除 都加入index -
git add -p
交互式地询问是否把修改片段添加到index,按照修改片段来区分,不是按照修改文件。
git commit
-
--amend -m "..."
修改最新的一个提交的说明, commit号会被修改, 但是父提交不会改变 -
-a
对工作区和index区修改和删除文件进行提交(对未入版本库的文件不起作用),跳过git add 不赞成使用
git log
-
-n
n是想要显示的提交个数 -
-p
显示出每个提交做的修改(好像对已经删除的文件无效) -
git log --all -- filepath
对已经删除的文件也有效 -
-l 提交号
从哪个提交开始显示log,如果没有的话,默认从HEAD指向的分支的提交号开始 -
--stat
显示出对哪些文件进行了增删以及增删的行数 -
--pretty=oneline
每一行显示一个提交,只包含提交ID和提交信息,统计有提交总数:git log --pretty=oneline | wc -l
-
--oneline
同样每行只显示一个提交,但是提交号更短 -
--pretty=raw
每次提交信息包括:提交ID,目录树ID,父提交ID,作者,提交信息。 -
--graph [提交ID]
显示提交跟踪链,可以和以上参数一起用,可选的参数提交ID。 -
git log --all --source --pretty=oneline --graph | grep 提交号
查看该提交在哪个分支引入(比较有用) -
git log <since>..<until>
查看2个引用之间的提交,常用于比较当前分支和远程该分支的提交差异, 输出可以理解为后面一个减去前面一个(后面一个比前面一个多的commit)-
看看是否有提交没有push:
git log origin/分支名..HEAD
-
看看后面一个分支比前一个分支多的commit:
git log origin/分支A..分支B
-
看看是否本地不是最新(应该在remote updaate之后):
git log HEAD..origin/分支名
-
看看2次HEAD变更之间的所有提交log
git log HEAD@{1}..HEAD@{0}
注意顺序,调换2个head的位置,无输出
-
-
git log <since>...<until>
查看2个引用之间的提交差的绝对值, 注意是三个点
git status
-
-s
精简状态输出,每行代表一个文件,每行的前2列分别代表:-
index区与版本库的比较:M 修改,D 删除,A 新增,?代表工作区新增。
-
工作区与index区比较: M 修改,D 删除,?工作区新增
-
-
-sb
精简模式中展示分支名, 以及本地分支和本地远程分支的差的绝对值使用
git log <local_branch>...<remote_branch>
git diff
-
不带参数:工作区和index比较
-
git diff HEAD
工作区和当前分支版本库比较 -
git diff --cached
git diff --staged
index和版本库比较 -
--word-diff
逐字diff -
git diff $start_commit..$end_commit -- path/to/file
2个提交之间diff, 文件可选, 注意文件路径前有空格
git reset
git reset 不改变HEAD的内容,主要是改变HEAD指向的引用(分支)的commitID,或者仅仅用版本库的文件替换index或者工作区:
-
git reset [commit] --<paths>
带有文件名, 用该commit(默认HEAD)的该文件替换当前index中的文件,通常作为git add的回滚 repository->index -
git reset [方式] [commit]
,有以下几种方式,方式默认是–mixed:-
–soft:只把当前分支指向改为commit(默认HEAD), 一个用例是合并本地多步提交:
-
git reset --soft HEAD^^
工作区和index都没改变 -
git commit -m "这个动作合并了3个提交为一个"
因为index内容没被reset,现在直接commit即实现了修改log(reset),但是不改内容的目的
-
-
–mixed:除了实现soft,还把index区替换为commit指向的目录树
-
–hard:除了实现mixed,还把工作区替换为commit指向的目录树的内容一致 commit -> repository index work 注意index的新文件会被去掉,work的新文件会保留
-
-
回滚到上次HEAD的提交:
git reset --hard HEAD@{1}
git checkout
通常情况下HEAD的内容是一个分支名称,当用了git checkout 提交号,HEAD的内容将是一个提交号,这叫做分离头指针
-
git checkout [commit] -- <paths>
带有path,不会改变HEAD内容。当-
省略 commit: 用index的paths文件替换工作区相应文件 index->work
-
带有 commit:用版本库中的paths文件替换index和工作区相应文件 repository->index and work 注意index和工作区的新文件不会被更改
-
-
git checkout 分支名
将HEAD指向新的分支名(不改变index和工作区) -
git checkout -b 新分支名 [start-point]
基于当前分支创建新分支,创建好以后自动切换到新分支上, start-point 默认是HEAD指向的提交, config 下不会有追踪分支记录 加了track才有 -
git checkout 自己remotes/origin 里的分支
切换到自己有的一个远程分支, config 下会有追踪分支记录如果此时本地有多个remotes含有相同名字的远程分支, 操作将不会成功, 因为git不知道要切哪个
此时可以这样:
git checkout --track origin/feature/tmp_201412230942
其中origin是remote的名字 -
git checkout -b track 新分支名 基于的本地分支名
基于本地另一分支创建新分支,config下会有分支追踪记录,类似:[branch 新的本地分支名] remote = . merge = refs/heads/基于的本地分支名
-
在merge或者pull时, 如果发生冲突, 如果确认不想解决冲突, 可以:
- 确认保留对方的:
git checkout --theirs -- path/to/conflicted-file
- 确认保留自己的:
git checkout --ours -- path/to/conflicted-file
- 确认保留对方的:
git stash
git stash 完全不会理会Untracked文件, 保存的stash在所有分支是通用的。
-
git stash
保存工作区和index的状态 -
git stash list
显示保存列表 -
git stash pop [--index] [stash]
在工作区应用并删掉一个stash(默认是最近一个),参数–index将在index区也应用该stash -
git stash apply [--index] [stash]
基本同上,只是不删掉stash -
git stash drop [stash]
删掉指定stash -
git stash clear
删掉所有stash
.gitignore
-
该文件只对untracked文件有效,对已在版本库中文件无效
如果已经在版本库中的文件, 需要被ignore, 如果本地需要保留文件, 可以执行
git rm -r --cached 文件
然后再commit开发流程通常要求: 每次push前最好检测一下是否误入版本库
git ls-files 文件
扩展阅读: How to delete a file from a Git repository, but not other users’ working copies
-
git不能保留空目录,如果需要保留空目录,但是ignore目录里的所有文件,可以在该目录中新建文件
.gitignore
内容# Ignore everything in this directory * # Except this file !.gitignore
-
一个星号匹配任意文件名,两个星号匹配任意目录或文件名
我不确定我理解了gitignore中关于斜线和路径规则:
-
当规则是一个不包含斜线(目录)的文件,这代表忽略任意路径的该文件
-
当规则中包含斜线(目录)且以一个文件名结尾,代表忽略从.gitignore相对路径的该文件
-
接上条,如果以一个斜线结尾,代表要忽略的是整个目录的所有东西
-
接上条,如果以一个斜线开头,也代表从.gitignore相对路径的该文件
-
logs/
和logs/*
不一样, 前者完全忽略,!.gitignore
对前者没有作用, 对后者可以
git blame
git blame [-L n,[-+]m] 文件名
不带参数将追溯该文件所有的行,带参数,如果m前无符号,m代表一个绝对行号,如果m前正号,代表从n开始的m行,如果m前负号,代表从n开始回退m行
git bisect
git bisect 用于二分法+测试定位bug,大致步骤:
-
git bisect start
-
git bisect bad [commit]
确认bad提交,默认用HEAD -
git bisect good [commit]
找一个good的提交。这时候HEAD(处于分离头指针)会自动二分改为一个commit -
在现有的commit上测试
-
根据测试结果设置good或者bad,HEAD提交号(处于分离头指针)会被二分修改
-
重复第4和5步直到找到第一次bad提交
-
git bisect reset
HEAD将重新设为当前分支名
git clone
<repository>
可以指向版本的根目录,也可以指向版本库的.git目录
-
git clone <repository> <directory>
对等工作区 directory里会有.git/ 也会有所有已经提交的工作区文件。[remote “origin”] 下定义了fetch,url
上游push下游:报错(因为可能导致下游[work and index] 和 版本库不一样)
下游pull上游:OK
-
git clone --bare <repository> <directory.git>
裸版本库 只clone .git 目录到新的版本库,新的版本库目录约定以.git结尾[remote “origin”] 下定义了url, [core]下bare=true
上游push下游:OK
-
git clone --mirror <repository> <directory.git>
镜像裸版本库[remote “origin”] 下定义了fetch,url, mirror=true,[core]下bare=true
-
从文件系统clone:
- git clone file:////home/Zhong/projects/master_go_where
- git clone [email protected]:/home/Zhong/projects/master_go_where
git branch
-
git branch
显示本地分支列表 -
git branch -r
显示远程分支分支列表 -
git branch -a
显示所有远支分支列表(本地和远程) -
git branch 新分支名 [start-point]
基于start-point(提交号,分支名) 新建分支,创建后并未切换分支, 省略start-point,将是HEAD指向的提交 -
git branch -d 分支名
删除本地/远程分支,会检查该分支是否合并到其他分支,如果没有则拒绝删除 -
git branch -D 分支名
强行删除分支 -
git branch -dr <remote>/<branch>
删除本地的远程分支追踪 -
git branch -m 旧分支名 新分支名
重命名分支,如果新分支名已存在,则拒绝 -
git branch -M 旧分支名 新分支名
强行重命名分支 -
git branch --contains 提交号
在本地分钟搜索哪些本地分支有这个提交号(其实没啥用) -
git branch --merged [分支名]
列出所有已经合并到”分支名”(默认当前分支)的(本地/远程-r/所有-a)分支相反的参数
--no-merged
git branch -r --merged remotes/ued_assets/some_branch
检测远程分支中, 哪些合并到了remotes/ued_assets/some_branch
. 做这个之前最好git remote update -p ued_assets
其中ued_assets代表一个remote
有关分支的配置
[remote "origin"] : 定义了一个名为origin的远程版本库,origin是在clone的时候注册的
url 远程版本库的地址,也就是clone时的地址
fetch=+refs/heads/*:refs/remotes/origin/* : 定义fetch时获取的远程分支映射
pushurl=这是作为push的地址,可选,如果没有的话push时使用配置的url
[branch "本地分支名"] :定义一个有远程分支的本地分支
remote: 定义这个分支的远程分支名称
merge: 该分支在远程上的地址
分支追踪
当用git checkout [origin]远程分支
(前提是本地有该远程分支引用)(等同于git checkout -b 同名分支 origin/远程分支
)时会创建同名本地分支并切到该分支上, 显示:
Branch 同名本地分支名 set up to track remote branch 同名分支名 from origin. Switched to a new branch ‘同名本地分支名’
本地分支有了对应的远程分支,如果本地分支有未push的commit,git status 将会显示Your branch is ahead of 'origin/分支名' by 多少 commit.
这时可以用git cherry
看查看哪些commit还没push到上游。
有了对应的远程分支(可能是clone,checkout下来的)的本地分支,在config里,每个这样的分支都有一段用于追踪的配置:
[branch 本地分支名]
remote = origin
merge = refs/heads/分支名
同样如果本地分支提交滞后(可以用reset模拟)git status 将会出现 Your branch is behind 'origin/分支名' by 多少 commit, and can be fast-forwarded.
但是这些比较都是基本已在自己本地的远程分支引用来比较的,不会的去比较真正的远程库。
远程版本库
-
git remote add 新的远程版本库名 对应的地址.git
用于新增远程版本库 -
git remote -v
显示所有的远程版本库,没个版本库有对应的fetch和push版本库对应地址 -
git remote update
获取所有的的远程版本库的分支更新(把所有的remote【实际的remote端】版本库里的本地分支更新下来), 可选参数 -p (prune remotes after fetching)???如果有多个源时,可以指定一个源, 如
git remote update -p ued_assets
-
git remote set-url --push 远程分支名 指定的push的git地址.git
为远程设定push的git仓库地址
git fetch
git fetch [<options>] [<repository> [<refspec>...]]
- 本地最新状态 .git/refs/heads/A1
- 远程 .git/refs/remotes/origin/A1
- 分支在远程的最新状态 .git/FETCH_HEAD
fetch 只改变 分支在远程的最新状态
git fetch
不带参数:创建并更新所有远程分支的本地远程分支,改变#2和#3
git fetch origin
同上,只是指定了远程
git fetch origin branch1
不会新建本地远程分支,只改变#3 (可用于测试远程有无分支branch1)
git fetch origin branch1:branch2
先执行git fetch origin branch1, 然后branch2.exist? 合并branch1到branch2 : 用branch1创建branch2
git fetch origin :branch2
等价于 git fetch origin 远程库的当前分支:branch2
git push
git push [<remote 远程版本库名> [refspec 要推送到的远程分支]]
快进式推送(fast-forward)远程版本库相应分支的最新提交是本地版本库最新提交的祖先提交
-
-f
通常push只允许快递式推送,如果要强制非快进推送,加上参数-f会强制更新远程版本。对于force push后的版本库, 如果另外一台机器代码存在force去掉的代码, 此时此机器pull 代码后, 废旧代码任然可能存在, 这容易造成force更新后, 各个机器commit不一致的情况
解决方案, 其他机器执行
git reset --hard origin/master
-
删除远程分支:
git push <remote 远程版本库名> :分支名
如git push origin :feature/rm29010_20131202_zhonghua_split_guang_deal
引号的意义是将空分支推送到后者git push origin --delete <branch>
# Git version 1.7.0 or newergit push origin :<branch>
# Git versions older than 1.7.0 -
使用非快进推送的例子:发现已推送的最近一个提交有误,评估在这段时间内没有其他人pull,fetch,clone,push,在本地commit amend,然后加参数-f 推送。
第一次push本地创建的分支后, 远程分支的 .git/refs/heads/下会出现该分支,本地的.git/refs/remote/origin/下也有该分支(内存commit号)。但是该分支在配置中还是没有追踪配置
每次push都会更新远程的相应分支,以及自己库里的 .git/refs/remote/origin/下该分支
如果不带参数,如何确定这2个参数:
-
确定远程版本库: 当前分支配置的remote(branch.remote) origin -
确定了远程版本库后,远程的地址这样决定: 远程的pushurl(branch.pushurl) 远程的url(remote.url) -
然后再确定分支在远程上的路径:远程的配置的push(branch.push) 本地同名分支推送到远程同名分支(如果没带参数,将推送所有本地分支)
-
git pull
经命令中的pull换成fetch, 执行之… git merge FETCH_HEAD
git pull [<remote 远程版本库名> [refspec 要pull的远程分支]]
如果不带参数,如何确定这2个参数:
-
确定远程版本库: 当前分支配置的remote(branch.remote) origin -
确定了远程版本库后,远程的地址这样决定: 远程的url(remote.url)
-
然后再确定分支在远程上的路径:远程的配置的fetch(remote.fetch)
-
然后在确定要合并的分支:当前分支配置的merge(branch.merge) 报错 - 不带参数,fetch所有的远程分支,但是只合并了当前分支???(有待验证)
Tag 里程碑
tag 文件都在.git/refs/tags/tag名字
tag分类
-
轻量级tag:
git tag <tagname> [commit]
tag文件里存的是提交号
缺点:不会创建tag对象,无法知道tag的创建者和时间
最佳实践:不使用轻量级tag
-
带说明tag
git tag -a <tagname> [commit]
git tag -m 'message' <tagname> {commit}
tag文件是一个tag对象,有创建者和时间
-
带签名的tag
TODO
tag展示
-
git tag
展示所有的tag -
git show {tag}:{file}
查看指定tag里的指定文件 -
git tag -n数字
最多显示多少条tag -
git tag -l '通配符'
查找符合通配符的tag
tag本地删除
git tag -d tagname
tag远程删除
-
git push origin :tagname
git push origin --delete tag tagname
-
tag无重命名命令
tag 更新commit
- 使用-f参数
git tag -f -m 'new message' old_tagname new_commit
慎用,因为修改过的同名tag不会再次自动pull到下游
tag的共享和推送
-
创建的tag只在本地版本库中,不会因为git push 代码而推送上游
-
需要显示推送本地tag:
git push origin tagname
-
git pull 代码时,本地会得到远程的新tag
-
修改过的tag不会再次自动pull到下游,如需再次pull,需要显示:
git pull origin refs/tags/tagname:refs/tags/tagname
git describe
git describe 提交号:如果没有提交号则默认用当前提交号
展示里程碑,规则为:
- 如果当前提交对于一个tag,则展示
当前tag
- 否则展示
#{最近的一个tag}-#{当前提交距离该tag的提交数量}-#{当前提交号}
- 另外,该命令默认忽略轻量级tag,除非加上参数
git describe --tags
TODO
git ls-remote
展示远程的引用以及其当前commit号,这是实时从远程去读取
-
-h –heads 只展示远程上 refs/heads内的引用
-
-t –tags 只展示远程上refs/tags内的引用
-
git ls-remote 参数
指定某个版本库,点号代表当前本地版本库
git ls-files
-
git ls-files -s
查看暂存区的目录树 -
git ls-files [文件名]
查看该文件状态-c, --cached Show cached files in the output (默认) -d, --deleted Show deleted files in the output -m, --modified Show modified files in the output -o, --others Show other (i.e. untracked) files in the output -i, TODO
-
git ls-files file_name --error-unmatch
查看文件是否被tracked, 是的话返回文件名,否的话返回错误 -
git ls-files --others -i --exclude-standard
查看ignore的本地文件
git cherry-pick
git cherry-pick [commitID]
用于重放某一commit, 该commit一般是另外一个分支的commit, 或者是一个本来在当前分支, 但是已经被reset移除后的commit
其他
-
git grep
TODO -
git clean -fd
删除所有不在版本库的文件-n
展示哪些文件会被删除-nd
展示哪些文件和目录会被删除-f
文件-d
目录-i
交互式 -
git mv <path>
在工作区和index对path文件进行改名 -
git write-tree
会将index区的目录树写入对象库,(这个操作在git commit的时候也会进行) 并返回一个treeID。 -
git revert 提交号
创建一个新的commit, 用以undo 指定的提交, 如果能回滚(即指定提交后, 没有其他提交修改这部分代码), 会直接到commit步骤, 如果不能回滚, 将会发送冲突
git/git flow 自动补全
$git clone git://git.kernel.org/pub/scm/git/git.git
$cp git/contrib/completion/git-completion.bash ~/.git-completion.bash
在 .bashrc 中加入:source ~/.git-completion.bash
git clone https://github.com/bobthecow/git-flow-completion.git
cp git-flow-completion/git-flow-completion.bash ~/.git-flow-completion.sh
在 .bashrc中加入 source ~/.git-flow-completion.sh
-
命令区分ref 还是 path
- ref:
git checkout some_ref --
- path:
git checkout -- some_path
对于checkout, log, show 等都有效
- ref: