Git的进一步研究

时间:2023-01-30 13:30:39

参阅: http://mindlee.net/2012/04/27/learning-git/ 【注意文章中所有的-均应为--】

1. git blame 熟悉之后,可以试着用git gui blame, 不过命令行参数有些不一样

E.g $git gui blame --line=44 src\Test.java


git ls-files

打印所有版控的文件列表


git ls-tree branch

打印内容类似

$ git ls-tree HEAD
100644 blob 8dead1626e5854f476b5c6eaa2507150c11605e0    a.txt
100644 blob 3e757656cf36eca53338e520d134963a44f793f8    b.txt

git show [参考http://*.com/questions/610208/how-to-retrieve-a-single-file-from-specific-revision-in-git]

git show $REV:$FILE  显示$REV这个commit的$FILE文件的内容,注意$FILE必须使用full path


git cat-file 用于显示object的内容,object通常包含blob, tree和commit

[参考:http://www-cs-students.stanford.edu/~blynn/gitmagic/intl/zh_cn/ch08.html]

-p 漂亮的打印object内容,可以为blob(文件内容), tree(文件属性,名称大小,修改时间等)

--batch 从标准输入获取object,例如 echo e7eeb5f6fc8d1ee46002ed83e073f2a028d0a7e1 |git cat-file --batch


2. git reset 熟悉之后,学些使用

git reset 之soft, hard

git reset --hard HEAD~1 为完全恢复,即抹掉这条指定commit之前所有的commit记录

git reset --soft HEAD~1  只改变版本库,不改变暂存区和工作区; 【为取消指定这条commit之前的那条commit,还原到将修改保存到stage的状态(绿色),用处不大,完全可以用commit --amend来替换】

git reset --mixed HEAD~1 改变版本库和暂存区,不改变工作区; 【为取消commit, 还原到未将修改保存到stage的状态(红色),即修改内容依旧存在】

git reset .                            将git add 命令更新到暂存区的内容撤出暂存区,++【注意不要和git checkout .弄混!!!】
                                       即用版本库的HEAD重置暂存区;
git reset  filename                    将暂存区中filename撤出暂存区,


git clean: 用于清除未加入(或者不打算加入)版本控制的文件和目录。参数-f用于文件,-d用于目录+++

参考:http://www.dotblogs.com.tw/larrynung/archive/2012/12/20/85834.aspx


git reflog 用于查看历史操作

git reset --hard HEAD@{1} 恢复到该操作,即reset之后的状态和该HEAD@{1}的操作结果是一致的++【可用于从merge冲突中脱身】【可用于在reset之后找回源码】
git reflog -5                          查看最近五次的操作记录,默认是HEAD
git reflog show master –5              查看master最近五次的操作记录

git diff 比较分支或commit差异

git diff HEAD~1 HEAD~2 -- a.txt 比较不同commit之间相同文件的内容+++++

git diff brach1 branch2 -- a.txt 比较不同分支之间相同文件的内容+++++



git checkout

git checkout .                         会用暂存区内容刷新工作区,相当于取消本地所有修改;++++

git checkout branch -- filename         用branch中的文件替换暂存区 + 工作区中相应的文件;++++
git checkout HEAD~1 -- welcome.txt      从历史中恢复文件;++++++
与git diff配合使用,选择正确版本的文件内容


git tag

git tag -a v1.2 9fceb02 -m "Message here"  给某个旧的commit打标签

git push origin --tags 推送所有标签到remote repo,必须要加'--tags',pull的时候同样要加


3. git format-patch 熟悉之后,学习

git cherry-pick 详解:http://yiyingloveart.blogspot.com/2013/04/git-cherry-pick.html

分支之间的commit不需要指定分支名称,只需要提供commit的SHA1值

git cherry-pick certain_commit                从众多的提交中挑选出一个提交应用到当前的工作分支中【理解为将certain_commit重新应用到当前分支】

举例:

alias.hist=log --pretty=format:"%h %ad | %s%d [%an]" --graph --date=short

$ git hist
* edc561a 2013-08-16 | hoho [Bing Wei]
* 7b6abea 2013-08-16 | commit 4 from b2 [Bing Wei]
* 4282054 2013-08-16 | commit 2 from b2 [Bing Wei]
* 56a7fdd 2013-08-16 | commit 3 from b1 (testbb, branch1) [Bing Wei]

$ git cherry-pick 4282054
[master e5b4d82] commit 2 from b2
 1 file changed, 1 insertion(+)

$ git hist
* e5b4d82 2013-08-16 | commit 2 from b2 (HEAD, master) [Bing Wei]
* edc561a 2013-08-16 | hoho [Bing Wei]
* 7b6abea 2013-08-16 | commit 4 from b2 [Bing Wei]
* 4282054 2013-08-16 | commit 2 from b2 [Bing Wei]
* 56a7fdd 2013-08-16 | commit 3 from b1 (testbb, branch1) [Bing Wei]

同样可以从reflog的Sha1中恢复某commit.

参阅:http://blog.csdn.net/cn_chenfeng/article/details/7244615


4. git rebase的高级用法 参阅【http://blog.yorkxin.org/2011/07/29/git-rebase


git show 参阅http://www.open-open.com/lib/view/1328070367499


5. 很难用到的git bisect,感觉就是丫就是为了证明什么,这命令真的太少用到了。。。


Refer to: http://heikezhi.com/yuanyi/git-201-slightly-more-advanced


6. 对象原理

http://git-scm.com/book/zh/Git-%E5%86%85%E9%83%A8%E5%8E%9F%E7%90%86-Git-%E5%AF%B9%E8%B1%A1

对象存储
之前我提到当存储数据内容时,同时会有一个文件头被存储起来。我们花些时间来看看 Git 是如何存储对象的。你将看来如何通过 Ruby 脚本语言存储一个 blob 对象 (这里以字符串 "what is up, doc?" 为例) 。使用 irb 命令进入 Ruby 交互式模式:

$ irb
>> content = "what is up, doc?"
=> "what is up, doc?"
Git 以对象类型为起始内容构造一个文件头,本例中是一个 blob。然后添加一个空格,接着是数据内容的长度,最后是一个空字节 (null byte):

>> header = "blob #{content.length}\0"
=> "blob 16\000"
Git 将文件头与原始数据内容拼接起来,并计算拼接后的新内容的 SHA-1 校验和。可以在 Ruby 中使用 require 语句导入 SHA1 digest 库,然后调用 Digest::SHA1.hexdigest() 方法计算字符串的 SHA-1 值:

>> store = header + content
=> "blob 16\000what is up, doc?"
>> require 'digest/sha1'
=> true
>> sha1 = Digest::SHA1.hexdigest(store)
=> "bd9dbf5aae1a3862dd1526723246b20206e5fc37"
Git 用 zlib 对数据内容进行压缩,在 Ruby 中可以用 zlib 库来实现。首先需要导入该库,然后用 Zlib::Deflate.deflate() 对数据进行压缩:

>> require 'zlib'
=> true
>> zlib_content = Zlib::Deflate.deflate(store)
=> "x\234K\312\311OR04c(\317H,Q\310,V(-\320QH\311O\266\a\000_\034\a\235"
最后将用 zlib 压缩后的内容写入磁盘。需要指定保存对象的路径 (SHA-1 值的头两个字符作为子目录名称,剩余 38 个字符作为文件名保存至该子目录中)。在 Ruby 中,如果子目录不存在可以用 FileUtils.mkdir_p() 函数创建它。接着用 File.open 方法打开文件,并用 write() 方法将之前压缩的内容写入该文件:

>> path = '.git/objects/' + sha1[0,2] + '/' + sha1[2,38]
=> ".git/objects/bd/9dbf5aae1a3862dd1526723246b20206e5fc37"
>> require 'fileutils'
=> true
>> FileUtils.mkdir_p(File.dirname(path))
=> ".git/objects/bd"
>> File.open(path, 'w') { |f| f.write zlib_content }
=> 32
这就行了 ── 你已经创建了一个正确的 blob 对象。所有的 Git 对象都以这种方式存储,惟一的区别是类型不同 ── 除了字符串 blob,文件头起始内容还可以是 commit 或 tree 。不过虽然 blob 几乎可以是任意内容,commit 和 tree 的数据却是有固定格式的。

7. 查找删除文件的commit 

http://www.vogella.com/articles/Git/article.html#retrievefiles_finddeletedfile

git log -1 -- [file path]

8. git submodule

引用一段《Git权威指南》的话: 项目的版本库在某些情况虾需要引用其他版本库中的文件,例如公司积累了一套常用的函数库,被多个项目调用,显然这个函数库的代码不能直接放到某个项目的代码中,而是要独立为一个代码库,那么其他项目要调用公共函数库该如何处理呢?分别把公共函数库的文件拷贝到各自的项目中会造成冗余,丢弃了公共函数库的维护历史,这显然不是好的方法。

Best practise:

http://www.kafeitu.me/git/2012/03/27/git-submodule.html

http://josephjiang.com/entry.php?id=342

一些常见的问题和处理方法:

1. 在依赖的子项目中使用特定的分支或者TAG

ref: http://*.com/questions/1777854/git-submodules-specify-a-branch-tag

操作: 

1.1 进入子项目的目录

1.2 切换到签出你需要使用的分支

1.3 返回父项目/主项目的根目录

1.4 使用git add 和 git commit 保存修改

2. 撤销对子项目分支的切换

ref: http://*.com/questions/12192095/how-to-discard-change-to-git-submodule

操作:

2.1 进入父项目的根目录,执行git submodule update

将签出默认分支,从而撤销掉对其的修改

PS: 如果想撤销代码的修改,则直接进入子项目,使用git checkout -- 进行撤销操作

9. Git中的AutoCRLF与SafeCRLF换行符问题

CR回车 LF换行Windows/Dos CRLF \r\n
Linux/Unix LF \n
MacOS CR \r

#提交时转换为LF,检出时转换为CRLF
git config --global core.autocrlf true   

http://www.cnblogs.com/flying_bat/p/3324769.html


10. Git push的默认行为

http://*.com/questions/948354/git-push-default-behavior

http://longair.net/blog/2011/02/27/an-asymmetry-between-git-pull-and-git-push/

设置push的默认行为

git config --global push.default current

其他选项
nothing : 不进行push操作(除非指定repo) - 最安全
matching : Push所有branch名称匹配的分支,意味着如果当前你有n条分支,并且如果他们都能在repo上被找到的话,会全部被推送 - 默认选项,但是不推荐使用,因为非常容易造成将其他branch上未完成的commits推送到remote repo上
tracking : Push当前分支,不管它在多少个repo上能被找到(derpecated)
current : Push当前分支(待测试和tracking的区别)
simple: (Git 1.7.11新增) 优先推送到upstream,但是如果upstream上的分支名和当前的不一致,则拒绝推送,为git安装后默认使用的选项 - 推荐使用

PS: 我们可以使用git branch --set-upstream-to=[remote repo]/branch 来将我们本地的branch和远程指定仓库的分支的进行绑定,这样的话,当使用push.default = simple选项时, 使用git push就会第一时间到我们设定的remote repo上去寻找是否存在同名本地branch,然后进行推送

PS2: 万恶的upstream,一直以为只是用来追踪upstream的repo的,其实跟那个完全没关系好么!



11. 设置git pull --rebase 为默认操作

默认git pull操作是 git fetch + git merge

但是对于需要经常提交pull request的我们来说,需要的是git pull --rebase, 所以下面的config可以帮助我们将rebase作为默认操作,减少重复操作

参考: https://coderwall.com/p/yf5-0w

$git config branch.autosetuprebase always

这样所有新创建的branch就会使用rebase当你使用git pull的时候

针对已经存在的branch,则需要以下设置

$git config branch.YOUR_BRANCH_NAME.rebase true

最后,如果你需要在git pull的时候使用merge,则使用

$git pull --no-rebase


12. 找到好东西了:) -  教你如何抹掉历史记录

先来看下git rebase --help的说明

We can get this using the following command:

git rebase --onto master next topic

Another example of --onto option is to rebase part of a branch. If we have the following situation:

                            H---I---J topicB
                           /
                  E---F---G  topicA
                 /
    A---B---C---D  master

then the command

git rebase --onto master topicA topicB

would result in:

                 H'--I'--J'  topicB
                /
                | E---F---G  topicA
                |/
    A---B---C---D  master

This is useful when topicB does not depend on topicA.

这里可以分别把master, topicA, topicB设想为3个commit,他们的顺序应该是master是最老的,topicB是最新的

这里如果topicB不依赖topicA,那么我们可以用上面的命令将topicB这个commit直接接到topicA上面

现在用commit的实例来演示一下

设想你有如此的记录

$git status

last-commit

to-be-removed-commit

old-commit

...

你需要将to-be-removed-commit从当前分支中抹掉,那么你可以用如下命令来实现

git rebase --onto HEAD^^ HEAD^ HEAD

这里面HEAD^^意味着倒数第三个commit, 即 old-commit, 别的以此类推

那么git rebase --onto是做什么的呢?

基于上面的演示,我们可以知道--onto可以将最后提供的commit或者分支直接接到第一个提供的commit或者分支上

当执行完这个命令之后,你会跳到一个新的临时分支上,这时候就需要你手动checkout出来。

OK, 当你理解这些之后,那么下面的例子你应该同样可以理解了

A range of commits could also be removed with rebase. If we have the following situation:

    E---F---G---H---I---J  topicA

then the command

git rebase --onto topicA~5 topicA~3 topicA

would result in the removal of commits F and G:

    E---H'---I'---J'  topicA
注意HEAD是J, 而HEAD~1是I,以此类推

不过一定要注意:topicB不能依赖topicA,否则无法成功。


待研究 http://www.worldhello.net/git-quiz/exam02.html

rev-list??
git log ..maint
git show :0:./file > file-2
git describe --tags --always --dirty
git name-rev
git log origin/master..
git diff-tree origin/master..
git request-pull origin/master URL-of-your-repo
git diff --stat origin/master


13. 查看修改的内容

参考: http://*.com/questions/1587846/how-do-i-show-the-changes-which-have-been-staged

git diff

显示那些本地修改的,但尚未加入到缓存区(staged)的内容

git diff --cached

显示那些本地修改的,并且加入到缓存区(staged)的内容

git diff HEAD

显示所有的修改内容,包括加入或者未加入到缓存区(staged)的内容