git

Git 使用总结

Posted on April 1, 2013

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 treeIDgit 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), 一个用例是合并本地多步提交:

      1. git reset --soft HEAD^^ 工作区和index都没改变

      2. 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,大致步骤:

  1. git bisect start

  2. git bisect bad [commit] 确认bad提交,默认用HEAD

  3. git bisect good [commit] 找一个good的提交。这时候HEAD(处于分离头指针)会自动二分改为一个commit

  4. 在现有的commit上测试

  5. 根据测试结果设置good或者bad,HEAD提交号(处于分离头指针)会被二分修改

  6. 重复第4和5步直到找到第一次bad提交

  7. 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>...]]
  1. 本地最新状态 .git/refs/heads/A1
  2. 远程 .git/refs/remotes/origin/A1
  3. 分支在远程的最新状态 .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 newer git 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分类

  1. 轻量级tag:

    git tag <tagname> [commit]

    tag文件里存的是提交号

    缺点:不会创建tag对象,无法知道tag的创建者和时间

    最佳实践:不使用轻量级tag

  2. 带说明tag

    git tag -a <tagname> [commit]

    git tag -m 'message' <tagname> {commit}

    tag文件是一个tag对象,有创建者和时间

  3. 带签名的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