Add New Notes

This commit is contained in:
geekard
2012-08-08 14:26:04 +08:00
commit 5ef7c20052
2374 changed files with 276187 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-08-21T23:03:09+08:00
====== An asymmetry between git pull and push ======
Created Sunday 21 August 2011
http://longair.net/blog/2011/02/27/an-asymmetry-between-git-pull-and-git-push/
Although git is an excellent system, which has certainly changed my way of working for the better, occasionally one comes across an inconsistency that seems bizarre. In case you dont want to read the whole of this post, the one sentence summary would be, “By default, git push origin will update branches on the destination with one with the same name on the source, instead of using the association defined by git branch --track, which git pull origin would use — the config option push.default can change this behaviour.” However, for a more detailed explanation, read on…
Suppose someone has told you that theyve pushed a topic branch to GitHub that theyd like you to work on. Lets say that youve set up a remote called github for that repository, and the branch there is called new-feature2. With a recent git (>= 1.6.1) you can just do git fetch and then:
git checkout -t github/new-feature2
… which will create a branch in your repository called new-feature2 based on github/new-feature2, and set various config options to associate your new-feature2 branch with github/new-feature2. It will also checkout that new branch so that you can start working on it. However, lets suppose that you want to give your branch a more helpful name lets say thats “add-menu”. Then you might instead do:
git checkout -t -b add-menu github/new-feature2
… which has the same effects to the previous command, except for giving the branch a different name locally. The config options that will have been set by that command are:
branch.add-menu.remote=github
branch.add-menu.merge=refs/heads/new-feature2
The detailed semantics of these config options are given in the branch.<name>.remote and branch.<name> merge sections of git configs documentation, but, for the moment, just understand that this sets up an association between your local add-menu branch, and the new-feature2 branch on GitHub.
This association makes various helpful features of git possible for example, this is how you get this nice information from git status:
$ git status
# On branch add-menu
# Your branch is ahead of 'github/new-feature2' by 5 commits.
Its also the mechanism by which, when youre on the add-menu branch, typing:
$ git pull github
… will cause git to run a git fetch, and then merge github/new-feature2 into your add-menu branch. Thats all very helpful.
So, what happens when you want to push your changes back to the upstream branch? You might hope that because this association exists in your config, then typing any of the following three commands while youre on the add-menu branch would work:
git push github add-menu
git push github
git push
git push github HEAD
However, with the default git setup, none of these commands will result in new-feature2 being updated with your new commits on add-menu. What does happen instead?
1. [wrong] git push github add-menu
In this case git push parses add-menu as a refspec. “refspecs” are usually of the form <src>:<dst>, telling you which local branch (src) youd like to update the remote branch (def) with. However, the default behaviour if you dont add :<dst>, as in this example, is explained in here:
If :<dst> is omitted, the same ref as <src> will be updated.
So the command is equivalent to git push github add-menu:add-menu, which will create a new branch called add-menu on GitHub rather than updating new-feature2.
2. [wrong] git push github
In this case, the refspec is omitted. The documentation for git push again explains what happens in this case:
The special refspec : (or +: to allow non-fast-forward updates) directs git to push “matching” branches: for every branch that exists on the local side, the remote side is updated if a branch of the same name already exists on the remote side. This is the default operation mode if no explicit refspec is found (that is neither on the command line nor in any Push line of the corresponding remotes file—see below).
… so the new commits on your add-menu branch wont be pushed. However, the changes for every other branch for which theres a matching name in your repository on GitHub will be!
2. [wrong] git push
Again, we can find in the documentation for git push what happens if we miss out the remote as well:
git push: Works like git push <remote>, where <remote> is the current branchs remote (or origin, if no remote is configured for the current branch).
In our example case, branch.add-menu.remote is set to github, so the behaviour in this case will be the same as in the previous one, i.e. probably not what you want.
4. [wrong] git push github HEAD
Thanks to David Ongaro for suggesting adding this fourth wrong command. The git push documentation explains that this is:
A handy way to push the current branch to the same name on the remote.
In other words, in this example, that will end up being the same as git push github add-menu:add-menu, again creating an unwanted add-menu branch in the remote repository.
So how should you push?
The simplest option, which will work everywhere, is just to specify both the source and destination parts of the refspec, i.e.:
git push github add-menu:new-feature2
That means that you have to remember what the remote name should be, but its the least ambiguous way to push a branch, and in any case its a good idea to understand how to use refspecs more generally.
However, another alternative (available since git version 1.6.3) is to set the push.default config variable. The documentation for this in the git config man page is:
push.default: Defines the action git push should take if no refspec is given on the command line, no refspec is configured in the remote, and no refspec is implied by any of the options given on the command line. Possible values are:
nothing do not push anything.
matching push all matching branches. All branches having the same name in both ends are considered to be matching. This is the default.
tracking push the current branch to its upstream branch.
current push the current branch to a branch of the same name.
So if you set push.default to tracking with one of:
$ git config push.default tracking # just for the current repository
$ git config --global push.default tracking # globally for your account
… then when youre on the add-menu branch, git push github will update new-feature2 on GitHub with your changes in add-menu, and no other branches will be affected.
The commit message that introduced this change suggests that the reason that this option was introduced was exactly to avoid the kind of confusion Ive described above:
When “git push” is not told what refspecs to push, it pushes all matching branches to the current remote. For some workflows this default is not useful, and surprises new users. Some have even found that this default behaviour is too easy to trigger by accident with unwanted consequences.
Personally, I dont actually use this option, since I use git on so many different systems it would be more confusing to have different settings for push.default on some of them. However, I hope its useful for some people, and its a shame that this behaviour couldnt reasonably be made the default at this stage.
Update: Thanks to David Ongaro, who points out below that since git 1.7.4.2, the recommended value for the push.default option is upstream rather than tracking, although tracking can still be used as a deprecated synonym. The commit message that describes that change is nice, since it suggests that there is an effort underway to deprecate the term “track” in the context of setting this association with the upstream branch in a remote repository. (The totally different meanings of “track” in git branch --track and “remote-tracking branches” has long irritated me when trying to introduce git to people.)

View File

@@ -0,0 +1,244 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2012-02-19T14:35:18+08:00
====== GIT基本概念和用法总结 ======
Created Sunday 19 February 2012
http://guibin.iteye.com/blog/1014369
guibin.beijing@gmail.com
在日常使用GIT过程中经常会出错比如无意间丢失了未提交的数据回退版本时丢失了工作目录等等。经过思考发现所有这些错误都是因为对GIT中一些基本的概念模糊而导致因为对一些基本概念不清晰导致对GIT每一条命令将会产生的结果不符合预期。下面我就梳理以下我经常碰到的问题相关的基本概念。
==== 1. Working Directory工作目录 ====
Git的工作目录是保存当前正在工作的文件所在的目录和working tree是相同的意思。在这个目录中的文件可能会**在切换branch时被GIT删除或者替换**。__这个目录是个临时目录临时存储你从GIT库中取出的文件__这些文件一直会被保存直到下次提交。
==== 2. GIT DirectoryGIT库目录, 也是对象目录) ====
项目的所有历史提交都被保存在了GIT库目录中只要你不作回滚操作它应该不会丢失。
==== 3. GIT IndexGit索引 ====
Git index 可以看作是工作目录和Git库目录之间的__暂存区__和staging area是相同的意思。可以**使用Git index构建一组你准备一起提交的改变**。Git Index和Git Staging area是同一个意思都是指已经__被add__的但尚未commit的那些内容所在的区域。最简单的查看目前什么内容在index中的方法是使用git status命令。
* 命令中”Changes to be committed“中所列的内容是在Index中的内容commit之后进入Git Directory。
* 命令中“Changed but not updated”中所列的内容是在Working Directory中的内容__add之后将进入Index__。
* 命令中“Untracked files”中所列的内容是尚未被Git跟踪的内容__add之后进入Index__。
哪些操作能够改变git index中的内容
A). git add <path>...会将working directory中的内容添加进入git index。
B). git reset HEAD <path>...__暂存区的目录树会被重写被 master 分支指向的目录树所替换但是工作区不受影响__
==== 4. git diff ====
git diff可以比较working tree同index之间index和git directory之间working tree和git directory之间**git directory中不同commit**之间的差异,
* git diff [<path>...]这个命令最常用在每次add进入index前会运行这个命令查看即将add进入index时所做的内容修改即working directory和index的差异。
* git diff --cached [<path>...]这个命令初学者不太常用却非常有用它表示查看已经add进入index但是尚未commit的内容同最后一次commit时的内容的差异。**即index和git directory的差异**。
* __git diff --cached [<commit>] [<path>...]__这个命令初学者用的更少也非常有用它表示查看**已经add进入index但是尚未commit的内容同指定的<commit>之间的差异**,和上面一条很相似,差别仅仅<commit>即index和git directory中指定版本的差异。
* __git diff <commit> [<path>...]__这个命令用来查看**工作目录**和指定<commit>的commit之间的差别如果要和Git directory中最新版比较差别则<commit>=HEAD。如果要和某一个branch比较差别<commit>=分支名字
* __git diff <commit> <commit> [<path>...]__这个命令用来比较**git directory中**任意两个<commit>之间的差别,如果想比较任意一个<commit>和最新版的差别,把其中一个<commit>换成HEAD即可。
==== 5. 如何merge不同的分支 ====
在git中在执行任何命令时你一定要清楚你在哪对谁执行这个命令
比如在创建新的branch时执行命令git branch 1.0-beta这个命令是说在当前branch上__以当前branch为基准创建一个新的branch__名叫1.0-beta。
在比如当merge不同的branch时
git checkout 1.0-beta
git merge master
首先切换到1.0-beta branch上然后将主干master上的代码合并到当前1.0-beta分支上。
merge完后可能会由冲突按照git的提示**编辑标识为"CONFLICT (content)"的文件**解决冲突后再次将冲突的文件__add__commit后merge完毕。
==== 6. git reset ====
在一般使用中如果发现错误的将不想staging的文件add进入index之后想回退取消(stage中相应文件被HEAD中的替换工作区中的文件不变)则可以使用命令git reset HEAD <file>...同时git add完毕之后git也会做相应的提示比如
引用
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: Test.scala
git reset __[--hard|soft|mixed|merge|keep]__ [<commit>或HEAD]将当前的分支重设reset到指定的<commit>或者HEAD默认如果不显示指定commit默认是HEAD即最新的一次提交并且根据[mode]有可能更新index和working directory。mode的取值可以是hard、soft、mixed、merged、keep。下面来详细说明每种模式的意义和效果。
A). --hard重设reset index和working directory自从<commit>以来在working directory中的任何改变都被**丢弃**并把HEAD指向<commit>。
B).__ --soft__index和working directory中的内容不作任何改变**仅仅把HEAD指向<commit>**。这个模式的效果是,执行完毕后,自从<commit>以来的所有改变都会显示在git status的"Changes to be committed"中。
C). --mixed__仅reset index但是不reset working directory。这个模式是默认模式__即当不显示告知git reset模式时会使用mixed模式。这个模式的效果是working directory中文件的修改都会被保留不会丢弃但是也不会被标记成"Changes to be committed",但是会打出什么还未被更新的报告。报告如下:
Unstaged changes after reset:
M Test.Scala
M test.txt
BC常合用于**打断工作流**的情况。
D). --merge和--keep用的不多在下面的例子中说明。
===== 下面列出一些git reset的典型的应用场景 =====
==== A) 回滚add操纵 ====
引用
$ edit (1)
$ git add frotz.c filfre.c
$ mailx (2)
$ __git reset__ (3)
$ git pull git://info.example.com/ nitfol (4)
(1) 编辑文件frotz.c, filfre.c做了些更改并把更改添加到了index
(2) 查看邮件发现某人要你pull有一些改变需要你merge下来
(3) 然而你已经把index搞乱了因为index同HEAD commit不匹配了但是你知道即将pull的东西**不会影响已经修改的frotz.c和filfre.c**因此你可以revert这两个文件的改变。revert后那些改变应该依旧在working directory中因此执行git reset。
(4) 然后执行了pull之后自动mergefrotz.c和filfre.c这些改变依然在working directory中。
==== B) 回滚最近一次commit ====
引用
$ git commit ...
$ git reset --soft HEAD^ (1)
$ edit (2)
$ git commit -a -c ORIG_HEAD (3)
(1) 当提交了之后,你又发现代码没有提交完整,或者你想**重新编辑一下提交的comment**执行git reset --soft HEAD^__让working tree还跟reset之前一样__不作任何改变。
HEAD^指向HEAD之前最近的一次commit。
(2) 对working tree下的文件做修改
(3) 然后使用reset之前那次commit的注释、作者、日期等信息重新提交。注意当执行git reset命令时git会把老的HEAD拷贝到文件.git/__ORIG_HEAD__中在命令中可以使用ORIG_HEAD引用这个commit。commit 命令中 -a 参数的意思是告诉git自动把所有修改的和删除的文件都放进stage area未被git跟踪的新建的文件不受影响。commit命令中-c <commit> 或者 -C <commit>意思是拿__已经提交的commit对象中的信息作者提交者注释时间戳等提交__那么这条commit命令的意思就非常清晰了把所有更改的文件加入stage area并使用上次的提交信息重新提交。
==== C) 回滚最近几次commit并把这几次commit放到叫做topic的branch上去。 ====
引用
$ git branch topic/wip (1)
$ git reset --hard HEAD~3 (2)
$ git checkout topic/wip (3)
(1) 你已经提交了一些commit但是此时发**现这些commit还不够成熟不能进入master分支但你希望在新的branch上润色这些commit改动**。因此执行了git branch命令在当前的HEAD上建立了新的叫做 topic/wip的分支。
(2) 然后回滚master branch上的最近三次提交。HEAD~3指向当前HEAD-3个commit的commitgit reset --hard HEAD~3即删除最近的三个commit删除HEAD, HEAD^, HEAD~2将HEAD指向HEAD~3。
==== D) 永久删除最后几个commit ====
引用
$ git commit ...
$ git reset --hard HEAD~3 (1)
(1) 最后三个commit即HEAD, HEAD^和HEAD~2提交有问题你想永久删除这三个commit。
==== E) 回滚merge和pull操作 ====
引用
$ git pull (1)
Auto-merging nitfol
CONFLICT (content): Merge conflict in nitfol
Automatic merge failed; fix conflicts and then commit the result.
$ git reset --hard (2)
$ git pull . topic/branch (3)
Updating from 41223... to 13134...
Fast-forward
$ git reset --hard ORIG_HEAD (4)
(1) 从origin拉下来一些更新但是产生了很多冲突你暂时没有这么多时间去解决这些冲突因此你决定稍候有空的时候再重新pull。
(2) 由于pull操作产生了冲突因此所有pull下来的改变__尚未提交__仍然再stage area中这种情况下git reset --hard 与 git reset --hard HEAD意思相同即都是清除index和working tree中被搞乱的东西。
(3) __将topic/branch合并到当前的branch__这次没有产生冲突并且合并后的更改自动提交。
(4) 但是此时你又发现将topic/branch合并过来为时尚早因此决定退滚merge执行git reset --hard ORIG_HEAD回滚刚才的pull/merge操作。说明前面讲过执行git reset时git会把__reset__之前的HEAD放入.git/ORIG_HEAD文件中命令行中使用ORIG_HEAD引用这个commit。同样的__执行pull和merge操作时git都会把执行操作前的HEAD放入ORIG_HEAD中以防回滚操作__。
F) 在被污染的working tree中回滚merge或者pull
引用
$ git pull (1)
Auto-merging nitfol
Merge made by recursive.
nitfol | 20 +++++----
...
$ git reset --merge ORIG_HEAD (2)
(1) 即便你已经在本地更改了一些你的working tree你也可安全的git pull前提是你知道__将要pull的内容不会覆盖你的working tree中的内容__。
(2) git pull完后你发现这次pull下来的修改不满意想要回滚到pull之前的状态从前面的介绍知道我们可以执行git reset --hard ORIG_HEAD但是这个命令有个副作用就是__清空你的working tree__即丢弃你的本地未add的那些改变。为了避免丢弃working tree中的内容可以使用git reset __--merge__ ORIG_HEAD注意其中的--hard 换成了 --merge这样就可以避免在回滚时清除working tree。
==== G) 被中断的工作流程 ====
在实际开发中经常出现这样的情形你正在开发一个大的feature此时来了一个紧急的bug需要修复但是目前在working tree中的内容还没有成型还不足以commit但是你又必须切换的另外的branch去fix bug。请看下面的例子
引用
$ git checkout feature ;# you were working in "feature" branch and
$ work work work ;# got interrupted
$ git commit -a -m "snapshot WIP" (1)
$ git checkout master
$ fix fix fix
$ git commit ;# commit with real log
$ git checkout feature
$ git reset --soft HEAD^ ;# go back to WIP state (2) #只改变HEAD
$ git reset (3) #只改变INDEX
(1) 这次属于**临时提交**,因此随便添加一个临时注释即可。
(2) 这次reset删除了WIP commit并且把working tree设置成提交WIP快照之前的状态。
(3) 此时在index中依然遗留着“snapshot WIP”提交时所做的uncommit changesgit reset将会清理index成为尚未提交"snapshot WIP"时的状态便于接下来继续工作。
==== (H) Reset单独的一个文件 ====
假设你已经添加了一个文件进入index但是而后又不打算把这个文件提交此时可以使用git reset把**这个文件从index中去除**。
引用
$ git reset -- frotz.c (1)
$ git commit -m "Commit files in index" (2)
$ git add frotz.c (3)
(1) 把文件frotz.c从index中去除
(2) 把index中的文件提交
(3) 再次把frotz.c加入index
==== (I) 保留working tree并丢弃一些之前的commit ====
假设你正在编辑一些文件并且已经提交接着继续工作但是现在你发现当前在working tree中的内容应该属于另一个branch与这之前的commit没有什么关系。此时你可以开启一个新的branch并且保留着working tree中的内容。
引用
$ git tag start
$ git checkout -b branch1
$ edit
$ git commit ... (1)
$ edit
$ git checkout -b branch2 (2)
$ git reset --keep start (3)
(1) 这次是把在branch1中的改变提交了。
(2) 此时发现之前的提交不属于这个branch此时你新建了branch2并切换到了branch2上。
(3) 此时你可以用reset --keep把在start之后的commit清除掉但是保持working tree不变。
==== 7. git revert ====
git revert用于回滚一些commit。对于一个或者多个已经存在的commit去除由这些commit引入的改变并且用一个__新的commit来记录这个回滚操作__。这个命令要求working tree必须是干净的。
git revert和git reset的功能很相似但是有区别具体如下。
git revert用于用一个commit来记录并回滚早前的commit经常是一些错误的提交。如果你想干脆扔掉working tree中的东西可以使用git reset --hard
比如
A) git revert HEAD~3**丢弃最近的三个commit**把状态恢复到最近的第四个commit并且提交一个新的commit来记录这次改变。
B) git revert -n master~5..master~2丢弃从最近的第五个commit包含到第二个不包含,但是**不自动生成commit**这个revert仅仅修改working tree和index。
==== 8. git revert 和 git reset的区别 ====
1. git revert是用一次新的commit来回滚之前的commitgit reset是直接删除指定的commit。
2. 在回滚这一操作上看效果差不多。但是在日后继续merge以前的老版本时有区别。因为git revert是用一次逆向的commit“中和”之前的提交因此日后合并老的branch时导致这部分改变不会再次出现但是git reset是之间把某些commit在某个branch上删除因而和老的branch再次merge时这些被回滚的commit应该还会被引入。
3. __git reset 是把HEAD向后移动了一下而git revert是HEAD继续前进__只是新的commit的内容和要revert的内容正好相反能够抵消要被revert的内容。
==== 9. 如何删除远程分支 ====
删除远程分支就是将本地的空分支push到远程即可。
#查看远程分支
$ git ls-remote idc
Password:
fa7dc3cd254c6fff683e20722284565b92d869ff HEAD
14a62709ecadd11a266d234d19955f4679fa95ab refs/heads/cpp-1.0
34b38625bce0aa4d4a4e266e20bba3e0ccd1b97e refs/heads/cpp-1.0.RC1
3f40a21f20f51aaa74e2a6954b64d82506cd4adf refs/heads/cpp-1.1
2f795085d57b6784a6358d97dbd0d1227891b01a refs/heads/distri
#删除远程叫做diftri的分支
$ git push idc__ :distri__
Password:
To xxx@192.168.4.40:Project.git
- [deleted] distri
#确认远程分支被删除
$ git ls-remote idc
Password:
fa7dc3cd254c6fff683e20722284565b92d869ff HEAD
14a62709ecadd11a266d234d19955f4679fa95ab refs/heads/cpp-1.0
34b38625bce0aa4d4a4e266e20bba3e0ccd1b97e refs/heads/cpp-1.0.RC1
3f40a21f20f51aaa74e2a6954b64d82506cd4adf refs/heads/cpp-1.1
==== 9. 如何删除本地分支 ====
使用git branch命令就可以删除本地分支比如
git branch -d toBeDelBranch
==== 10. 如何clone克隆远程仓库中的指定分支而非默认的master分支 ====
在git clone 命令中使用__-b参数__指定分支名字即可比如将远端aiotrade.git上的levelIISZ-1.1分支克隆下来:
引用
git clone -b levelIISZ-1.1 username@192.168.4.40:aiotrade.git
参考文献:
1. http://book.git-scm.com/3_basic_branching_and_merging.html
2. git reset --help
3. git revert --help

View File

@@ -0,0 +1,56 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2012-02-19T14:11:23+08:00
====== Git 工作区、暂存区和版本库 ======
Created Sunday 19 February 2012
http://www.worldhello.net/2010/11/30/2166.html#comment-426
暂存区stage, index是 Git 最重要的概念之一,理解了这个概念很多 Git 命令就不再那么神秘了。 今天在写这部分的内容,画了一个图,看看有没有什么问题。 理解 Git 暂存区stage 把上面的“实践二”从头至尾走一遍,不知道您的感想如何?
—— “被眼花缭乱的 Git 魔法彻底搞糊涂了?”
—— “Git 为什么这么折磨人,修改的文件直接提交不就完了么?”
—— “看不出 Git 这么做有什么好处?”
在“实践二”的过程中,我有意无意的透漏了“暂存区”的概念,为了避免用户被新概念吓坏,在暂存区出现的地方用同时使用了“提交任务”这一更易理解 的概念但是暂存区stage, 或称为 index才是其真正的名称。我认为__ Git 暂存区stage, 或称为 index的设计是 Git 最成功的设计之一__也是最难理解的一个设计。 在版本库(.git目录下有一个 index 文件,我们针对这个文件做一个有趣的试验。 首先我们执行 "git checkout" 命令撤销工作区中 welcome.txt 文件尚未提交的修改。
$ git checkout -- welcome.txt
$ git status -s
我们通过状态输出,看以看到工作区已经没有改动了。我们查看一下 .git/index 文件注意该文件的时间戳19:37:44
$ ls --full-time .git/index
-rw-r--r-- 1 jiangxin jiangxin 112 2010-11-29 19:37:44.625246224 +0800 .git/index
我们再次执行 "git status" 命令,然后显示 .git/index 文件的时间戳19:37:44和上面的一样。
$ git status -s
$ ls --full-time .git/index
-rw-r--r-- 1 jiangxin jiangxin 112 2010-11-29 19:37:44.625246224 +0800 .git/index
现在我们**更改一下 welcome.txt 的时间戳**,但是不改变它的内容。然后再执行 "git status" 命令,然后查看 .git/index 文件时间戳19:42:06
$ touch welcome.txt
$ git status -s
$ ls --full-time .git/index
-rw-r--r-- 1 jiangxin jiangxin 112 2010-11-29 19:42:06.980243216 +0800 .git/index
看到了么,时间戳改变了! 这个试验说明__当执行 "git status" 命令扫描工作区改动的时候,先依据 .git/index 文件中记录的工作区跟踪文件的时间戳、长度等信息判断工作区文件是否改变__。如果工作区的文件时间戳改变说明**文件的内容可能被改变了**需要要打开文件读取文件内容和更改前的原始文件相比较判断文件内容是否被更改。如果文件内容没有改变则__将该文件新的时间戳记录到 .git/index 文件中__。
因为判断文件是否更改,**使用时间戳、文件长度等信息进行比较要比通过文件内容比较要快的多**,所以 Git 这样的实现方式可以让工作区状态扫描更快速的执行,这也是 Git 高效的因素之一。
__文件 .git/index 实际上就是一个包含文件索引的目录树像是一个虚拟的工作区。__在这个虚拟工作区的目录树中记录了**文件名、文件的状态信息(时间戳、文件长度等)**,文件的内容并不存储其中,而是保存在 Git 对象库(.git/objects有**git add**命令添加__文件索引建立了文件和对象库中对象实体之间的对应__。下面这个图展示了工作区、版本库中的暂存区和版本库之间的关系。
{{./git-stage.png}}
工作区、版本库、暂存区原理图
在这个图中,我们可以看到部分 Git 命令是如何影响工作区和暂存区stage, index的。
* 图中左侧为工作区,右侧为版本库。在版本库中标记为 "index" 的区域是__暂存区__stage, index标记为 "master" 的是 master 分支所代表的**目录树由commit对象代表**。
* 图中我们可以看出此时 __"HEAD" 实际是指向 master 分支的一个“游标”__。所以图示的命令中出现 HEAD 的地方可以用 master 来替换。
* 图中的 objects 标识的区域为__ Git 的对象库__实际位于 ".git/objects" 目录下,我们会在后面的章节重点介绍。
* 当对工作区修改(或新增)的文件**执行 "git add" 命令时暂存区的目录树被更新同时工作区修改或新增的文件内容被写入到对象库中的一个新的对象中而该对象的ID 被记录在暂存区的文件索引中。**
* 当执行提交操作git commit**暂存区的目录树写到版本库对象库master 分支会做相应的更新。即 master 指向的目录树就是提交时暂存区的目录树**。
* 当执行 "git reset HEAD" 命令时__暂存区的目录树会被重写被 master 分支指向的目录树所替换但是工作区不受影响__。
* 当执行 "git rm --cached <file>" 命令时会直接__从暂存区删除文件工作区则不做出改变__。
* 当执行 "git checkout ." 或者 "git checkout -- <file>" 命令时会__用暂存区全部或指定的文件替换工作区的文件__。这个操作很危险会清除工作区中未添加到暂存区的改动。
* 当执行 "git checkout HEAD ." 或者 "git checkout HEAD <file>" 命令时,会用 HEAD 指向的 master 分支中的全部或者部分文件__替换暂存区和以及工作区中的文件__。这个命令也是极具危险性的因为不但会清除工作区中未提交的改动也会清除暂存区中未提交的改 动。

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -0,0 +1,296 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2012-02-19T13:01:51+08:00
====== 做一次git的观众 ======
Created Sunday 19 February 2012
http://www.iteye.com/topic/732199
Git这个强大的版本管理系统工作的时候默默注视着你的代码目录所有的操作几乎都在.git目录中完成。今天我们来做一次git的观众以便深入了解git的各个操作。
首先新建一个目录git-monitor进入目录后用下面的命令初始化一个git仓库
$> git init --bare git-monitor.git
然后创建一个工作目录wp1意思为working_copy_1进入该目录运行git init以创建.git目录。
进入.git目录会发现下列文件和目录
HEAD config description hooks/ info/ objects/ refs/
这些都是git的演员。我们当观众的就从监控这些文件开始。但是演员分主角、配角和跑龙套的在这些文件中config是配置文件内容不会变的hooks中的文件是一些__回调程序的例子__删掉都没关系description文件只为某些git的__web应用提供描述信息__。它们都是跑龙套的剩下的文件和目录有
HEAD info/ objects/ refs/
在后续的操作中还有两位要上场分别是index文件和logs目录至此主要演员表为
HEAD index info/ objects/ refs/ logs/
要用肉眼盯着它们看实在不容易于是我写了个ruby小程序下载链接在最后用于监控这些目录一旦目录和文件有变化就在控制台上向我们报告。我把这个程序放到.git目录下并把它跑起来。接下来好戏就开演了。
回到wp1目录。新建一个文件file1.txt然后看看监控程序发现没有任何输出说明git对刚才的操作没有响应。既然没反应那我就接着操作在file1.txt中加一行内容
content added by wp1, 1st time
再看看监控还是没有反应。看来__只要我们不调用git命令它就不会有反应__。那我就调一个看看
$> git add .
再看看监控,终于有反应了:
Created file: **index** (in dir: git-monitor/wp1/.git)
Created file: __c2__/a04aa8cba9ba9a7a2fb8c9ecf74a3a0fc5e3fc (in dir: git-monitor/wp1/.git/**objects**)
git add这个命令根据`man git-add`的解释是__把某个文件加入到index。这个index实际上就是工作目录下所有文件和目录状态的一个快照(stage)每一次git提交所产生的tree对象就是依据index文件产生的__对index同志的详细采访可以参考[urlhttp://progit.org/book/zh/ch9-2.html]这里[/url]。__git add 实际调用的是git-update-index他们 只是刷新了 git 的跟踪信息__
我们来看看产生的那个object到底是什么根据git的规则__object下的的目录名加文件名和起来是一个40字符的字符串__它是对文件内容进行SHA1 digest之后用16进制编码得到的结果。此文件的内容是二进制的要查看它就要用下面的命令
$> git __cat-file -t__ c2a04aa8cba9ba9a7a2fb8c9ecf74a3a0fc5e3fc
blob
$> git __cat-file -p__ c2a04aa8cba9ba9a7a2fb8c9ecf74a3a0fc5e3fc
content added by wp1, 1st time
其中,-t这个参数是为了显示object类型-p这个参数是为了显示object的内容。显然这个object就是刚才加进去的file1.txt它是**一个blob类型的对象只存储文件内容和长度**。
接下来我把这次添加的内容提交一下git commit -m 'commit by wp1, 1st time'),再看看监控,又有输出了,这次的内容还真丰富啊:
Changed file: index
Created file: 16/71ae856c149673436da08f1ba026469c3a918d (in dir: git-monitor/wp1/.git/objects)
Created file: 30/c64c3a55b02f4c251565ef057d402f84751b56 (in dir: git-monitor/wp1/.git/objects)
Created file: heads/master (in dir: git-monitor/wp1/.git/refs)
Created file: HEAD (in dir: git-monitor/wp1/.git/logs)
Created file: refs/heads/master (in dir: git-monitor/wp1/.git/logs)
首先我们发现index文件被改变了。但是经过我仔细比对两次的index文件的二进制字节码后发现它的__内容并没有改变__所以可能是它的修改时间发生了改变。对此我想说的是请高人指点
再看后面新生成的两个文件,用我们上面的办法看看内容:
$> git cat-file -p 1671ae856c149673436da08f1ba026469c3a918d
**tree 30c64c3a55b02f4c251565ef057d402f84751b56**
author Kevin Fu <corntrace@email.com> 1281230735 +0800
committer Kevin Fu <corntrace@email.com> 1281230735 +0800
commit by wp1, 1st time
$> git cat-file -p 30c64c3a55b02f4c251565ef057d402f84751b56
**100644 blob c2a04aa8cba9ba9a7a2fb8c9ecf74a3a0fc5e3fc file1.txt **
显然第一个文件是个commit对象第二个文件是个tree对象从引用关系来看是__先生成的tree对象再生成的commit对象__。注意这个commit对象没有parent引用。
再看看后面生成的refs用__git show-ref__可以查看所有refs的内容
$> git show-refs
1671ae856c149673436da08f1ba026469c3a918d refs/heads/master
master当然指的是master分支它的值__指向刚才看到的commit对象__
最后就是两个log文件。log文件虽然只是供人查看但在git中的地位非同一般。先看看其内容
$ cat logs/HEAD
0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu <corntrace@email.com> 1281230735 +0800 commit (initial): commit by wp1, 1st time
$ cat logs/refs/heads/master
0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu <corntrace@email.com> 1281230735 +0800 commit (initial): commit by wp1, 1st time
可以看到,这两个文件的内容**目前是一样的**。这个文件记录了什么呢__它记录的是工作目录的状态变化__。其中那一串0表示一起是从零开始的因为git在初始化工作目录时并没有创建任何对象要表示初始状态只好用40个0来表示了。后面那串就是指向刚才的**本次的commit对象**。这条记录解释出来就是在1281230735 +0800时刻由Kevin Fu做了一次提交工作目录从初始状态转到commit 1671ae8对应的状态。
这个时候用过git的人可能会生疑问用git log看到的输出不是这个样子的呀。说对了git log的输出并不是由这里的log文件产生的我认为它的内容是根据commit之间的关联关系实时计算并显示出来的。而这里的logs是git中的reflog用git reflog命令可以查看
$> git reflog
1671ae8 HEAD@{0}: commit (initial): commit by wp1, 1st time
这个内容就跟上面的对应上了吧。为什么说这个log文件很重要呢因为在git中__objects其实都是死的绝大多数情况下一旦创建就不会被修改也不会被删除__当版本控制的内容发生变化时只有新的objects被创建出来没有旧的objects被改变。那么一堆死东西如何实现灵活的版本变化呢第一个就是靠**不断变化的版本指针**比如HEAD以及refs/heads/master文件第二个就是靠**记录工作目录变化情况的日志文件**。
__有了日志文件你想查看谁就查看谁想往哪个版本跳就往哪个版本跳想合并谁就合并谁。__许多git命令都是基于这个思想而设计的。
接下来我再添加一个文件file2.txt但不是在master branch中而是新开一个branch: advanced我们看看开分支的时候监控有何变化
Bash代码 收藏代码
$> git checkout -b advanced
(monitor outputs)
Changed file: index
Changed file: HEAD
Created file: heads/advanced (in dir: /Users/corntrace/git-monitor/wp1/.git/refs)
Created file: refs/heads/advanced (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)
Changed file: HEAD (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)
除去那个index文件的变化看看其他的文件。首先是HEAD文件其内容变成了refs/heads/advanced说明__HEAD已经移到了advanced分支__上。再看看新产生的refs/heads/advanced文件
$> git show-ref
1671ae856c149673436da08f1ba026469c3a918d refs/heads/advanced
1671ae856c149673436da08f1ba026469c3a918d refs/heads/master
可见__目前它与master分支指向同一个commit__。再来看看两个日志文件
$ cat logs/__HEAD __
0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu <corntrace@email.com> 1281230735 +0800 commit (initial): commit by wp1, 1st time
1671ae856c149673436da08f1ba026469c3a918d 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu <corntrace@email.com> 1281236136 +0800 **checkout:** **moving from master to advanced **
它果然把我的一举一动都记录下来了。在第二条记录里两个sha1值是一样的__说明没有提交只有指针的创建或改变__。
再看看另一个log
0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu <corntrace@email.com> 1281236136 +0800 **branch: Created from HEAD **
它说明了advanced分支是**从零开始**转到commit 671ae8的。看来它一点都不含糊啊。
现在我增加文件file2.txt并添加以下内容但分两次提交第一次提交前两行第二次提交后两行
content added by wp1, 1st time
additional content added by wp1, 1st time too
# TODO: implement a feature
# I plan to do ...
显然第一次提交将会产生3个objects**一个commit对象一个file2.txt的blob对象还有一个tree对象**,另外,**refs/heads/advanced会指向目前的这个commit对象****然后两个log文件logs/HEAD和logs/refs/heads/advanced会添加一些内容**,我全部列在这里:
$> git show-ref
35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666 refs/heads/advanced
1671ae856c149673436da08f1ba026469c3a918d refs/heads/master
$> git cat-file -p 35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666
tree 9c1c4549a869ede4d5f85c93594c1c23c311122f
__parent__ **1671ae856c149673436da08f1ba026469c3a918d **
author Kevin Fu <corntrace@email.com> 1281251100 +0800
committer Kevin Fu <corntrace@email.com> 1281251100 +0800
commit by wp1 for file2.txt, 1st time
$> git cat-file -p 9c1c4549a869ede4d5f85c93594c1c23c311122f
100644 blob c2a04aa8cba9ba9a7a2fb8c9ecf74a3a0fc5e3fc file1.txt
100644 blob baa4a1630ce88a9198b5eda885884aadab795806 file2.txt
$> git cat-file -p baa4a1630ce88a9198b5eda885884aadab795806
content added by wp1, 1st time
additional content added by wp1, 1st time
$> cat logs/HEAD
0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu <corntrace@email.com> 1281230735 +0800 commit (initial): commit by wp1, 1st time
1671ae856c149673436da08f1ba026469c3a918d 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu <corntrace@email.com> 1281236136 +0800 checkout: **moving from master to advanced **
1671ae856c149673436da08f1ba026469c3a918d 35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666 Kevin Fu <corntrace@email.com> 1281251100 +0800 commit: commit by wp1 for file2.txt, 1st time
$> cat logs/refs/heads/advanced
0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu <corntrace@email.com> 1281236136 +0800 branch: Created from HEAD
1671ae856c149673436da08f1ba026469c3a918d 35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666 Kevin Fu <corntrace@email.com> 1281251100 +0800 commit: commit by wp1 for file2.txt, 1st time
可以看到这次的commit对象中多了parent的引用就是指向上一次的commit。
接下来我提交第二部分的内容,其结果与刚才的分析相似,就不写了。提交之后,**我转回master分支将advanced分支中的内容合并进来然后将master分支推送出去**,我就可以下班了。
我们看看转回master分支时监控都有哪些输出
Changed file: index
Changed file: HEAD
Changed file: HEAD (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)
可见除了那个index文件HEAD文件的内容被该为ref: refs/heads/masterlogs/HEAD文件中添加了一行__记录分支跳转的日志__。
接下来我运行git merge advanced。合并完成后看看监控的输出
Changed file: index
Changed file: heads/master (in dir: /Users/corntrace/git-monitor/wp1/.git/refs)
Changed file: HEAD (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)
Changed file: refs/heads/master (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)
这里列出后三项的内容:
$> git show-ref
8227ae64f1f651669c6445d4c37909c8443df209 refs/heads/advanced
8227ae64f1f651669c6445d4c37909c8443df209 refs/heads/master
$> cat logs/HEAD
(猜都能猜出来,就省了吧)
$> cat logs/refs/heads/master
0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu <corntrace@email.com> 1281230735 +0800 commit (initial): commit by wp1, 1st time
1671ae856c149673436da08f1ba026469c3a918d 8227ae64f1f651669c6445d4c37909c8443df209 Kevin Fu <corntrace@email.com> 1281252267 +0800 __merge advanced: Fast-forward __
从分支的指向可以看到,它**指到advanced分支对应的commit上去了**从logs的内容可以看到本次的操作称为merge advanced: Fast-forword。除此之外并__没有产生任何object__连commit都没有这是因为在合并之后git分析出当前的目录树结构与advanced分支中的目录树是一样的所以只是简单的把master的指针指向advanced分支。
OK在下班之前我还是要看看master分支中的代码确保没有什么坏代码被交上去。但是我的神file2.txt中有一个TODO这要是被老板看见了还不要我晚上加班啊我能把它删掉再提交吗不能啊老板要是往回看一个版本不就找出来了因此当务之急就是__把master的版本指针退回去__让它指向原来的1671ae8这个版本。git reset这个命令可以帮到我。
$>** git reset --hard 1671ae8 **
(monitor outputs)
Changed file: index
Changed file: heads/master (in dir: /Users/corntrace/git-monitor/wp1/.git/refs)
Changed file: __HEAD __ (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)
Changed file: refs/heads/master (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)
$> git show-ref
8227ae64f1f651669c6445d4c37909c8443df209 refs/heads/advanced
1671ae856c149673436da08f1ba026469c3a918d refs/heads/master
$> cat logs/refs/heads/master
0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu <corntrace@email.com> 1281230735 +0800 commit (initial): commit by wp1, 1st time
**1671ae856c149673436da08f1ba026469c3a918d** 8227ae64f1f651669c6445d4c37909c8443df209 Kevin Fu <corntrace@email.com> 1281252267 +0800 merge advanced: Fast-forward
8227ae64f1f651669c6445d4c37909c8443df209 **1671ae856c149673436da08f1ba026469c3a918d **Kevin Fu <corntrace@email.com> 1281253859 +0800 1671ae8: updating HEAD
可以看到master的head是真的退回去了reflog中的记录稍候再说。现在只要把advanced中想要的那个提交弄过来就万事大吉了。git cherry-pick这个命令这是用来做这个的。
$> **git cherry-pick --ff 35ba29e **
(此时这里居然没有任何输出git的作者们太高估用户了来看看monitor outputs)
Changed file: index
Changed file: heads/master (in dir: /Users/corntrace/git-monitor/wp1/.git/refs)
Changed file: HEAD (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)
Changed file: refs/heads/master (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)
$> git show-ref
8227ae64f1f651669c6445d4c37909c8443df209 refs/heads/advanced
35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666 refs/heads/master
$> cat file2.txt
content added by wp1, 1st time
additional content added by wp1, 1st time
TODO果然是没有了在这里我用的--ff参数是要告诉git只需要做fast-forword就可以了因为35ba29e这个commit的parent正是先前的master的head(1671ae8)。
OK我可以把master分支推送到远程仓库里去了。在推之前我需要__把远程仓库加进来__。这里我就用一个本地的仓库来代替远程的吧。
$> git remote add origin /path/to/git-monitor.git
(这一步monitor无输出)
$> git push origin master
(monitor outputs)
Created file: remotes/origin/master (in dir: /Users/corntrace/git-monitor/wp1/.git/refs)
Created file: refs/remotes/origin/master (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)
$> git show-ref
8227ae64f1f651669c6445d4c37909c8443df209 refs/heads/advanced
35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666 refs/heads/master
35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666** refs/remotes/origin/master **
$> cat logs/refs/remotes/origin/master
0000000000000000000000000000000000000000 35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666 Kevin Fu <corntrace@email.com> 1281255166 +0800 update by push
可见这一步的操作就是新建了一个remote master分支的ref然后添加了一个logs文件。看来所有的事情都办妥了那就下班吧。
但是老板还没有下班而是正准备做code review。他把代码checkout下来
$> git co /path/to/git-monitor.git && cd git-monitor
$> git reflog
35ba29e HEAD@{0}: clone: from /Users/corntrace/git-monitor/git-monitor.git
他的reflog文件中完全没有我刚才操作的内容。看来__git是不会提交reflog到仓库中的__。
OK写到这里今天的git剧就要谢幕了。相关文件在后面下载大家可以重播。如果你有使用git的好的case也可以分享给大家Thank you!
-------------------------
楼上所说的.gitignore问题能否说得详细一些
如果是通配符匹配的问题,可以看看别人的.gitignore文件比如这个http://github.com/facebook/three20/blob/master/.gitignore
如果是某个文件已经在git版本控制中了又想ignore它就__需要先 git rm /path/to/the/file.txt --cached__ 把它删掉,然后再写入.gitignore中
-----------------------------------

View File

@@ -0,0 +1,236 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-08-27T14:49:17+08:00
====== Git - Revision Control Perfected ======
Created Saturday 27 August 2011
http://www.linuxjournal.com/content/git-revision-control-perfected
Aug 24, 2011 By Henry Van Styn
In 2005, after just __two weeks__, Linus Torvalds completed the first version of Git, an open-source version control system. Unlike typical centralized systems, Git is based on a __distributed__ model. It is extremely flexible and guarantees data integrity while being powerful, fast and efficient. With widespread and growing rates of adoption, and the increasing popularity of services like GitHub, many consider Git to be the best version control tool ever created.
Surprisingly, Linus had little interest in writing a version control tool before this endeavor. He created Git out of necessity and frustration. The Linux Kernel Project needed an open-source tool to manage its massively distributed development effectively, and no existing tools were up to the task.
Many aspects of Git's design are radical departures from the approach of tools like CVS and Subversion, and they even differ significantly from more modern tools like Mercurial. This is one of the reasons Git is intimidating to many prospective users. But, if you throw away your assumptions of how version control should work, you'll find that Git is actually simpler than most systems, but capable of more.
In this article, I cover some of the fundamentals of__ how__ Git works and stores data before moving on to discuss basic usage and work flow. I found that __knowing what is going on behind the scenes makes it much easier to understand__ Git's many features and capabilities. Certain parts of Git that I previously had found complicated suddenly were easy and straightforward after spending a little time learning how it worked.
I find Git's design to be fascinating in and of itself. I peered behind the curtain, expecting to find a massively complex machine, and instead saw only a little hamster running in a wheel. Then I realized a complicated design not only wasn't needed, but also wouldn't add any value.
===== Git Object Repository =====
Git, at its core, is a simple __indexed name/value database__. It stores pieces of data (values) in "objects" with unique names. But, it does this somewhat differently from most systems. Git operates on the principle of "__content-addressed storage__", which means the names are derived from the values. An object's name is chosen automatically by its content's SHA1 checksum—a 40-character string like this:
1da177e4c3f41524e886b7f1b8a0c1fc7321cac2
SHA1 is cryptographically strong, which guarantees a different checksum for different data (the actual risk of two different pieces of data sharing the same SHA1 checksum is infinitesimally small). The same chunk of data always will have the same SHA1 checksum, which always will identify only that chunk of data. Because object names are SHA1 checksums, they identify the object's content while being truly __globally unique__—not just to one repository, but to all repositories everywhere, forever.
To put this into perspective, the example SHA1 listed above happens to be the ID of the first commit of the Linux kernel into a Git repository by Linus Torvalds in 2005 (2.6.12-rc2). This is a lot more useful than some arbitrary revision number with no real meaning. Nothing except that commit ever will have the same ID, and you can use those 40 characters to verify the data in every file throughout that version of Linux. Pretty cool, huh?
Git stores all the data for a repository in four types of objects: __blobs, trees, commits and tags__. They are all just objects with an SHA1 name and some content. The only difference between them is the type of information they contain.
===== Blobs and Trees =====
A blob stores the__ raw data content__ of a file. This is the simplest of the four object types.
A tree stores the contents of a directory. This is a flat list of file/directory names, each with a corresponding SHA1 representing its content. These SHA1s are the names of other objects in the repository. This __referencing technique__ is used throughout Git to link all kinds of information together. For file entries, the referenced object is a blob. For directory entries, the referenced object is a tree that can contain more directory entries, in turn referencing more trees to define a complete and potentially unlimited hierarchy.
It's important to recognize that blobs and trees are not themselves files and directories; they are just the contents of files and directories. They don't know about anything outside their own content, including the existence of any references in other objects that point to them. References are__ one-way__ only.
{{./10971f1.png}}
Figure 1. An example directory structure and how it might be stored in Git as tree and blob objects (I truncated the SHA1 names to six characters for readability).
In the example shown in Figure 1, I'm assuming that the files MyApp.pm and MyApp1.pm have the same contents, and so by definition, they must reference the same blob object. This behavior is implicit in Git because of its content-addressable design and works equally well for directories with the same content.
As you can see, directory structures are defined by __chains of references__ stored in trees. A tree is able to represent all of the data in the files and directories under it even though it contains only one level of names and references. Because SHA1s of the referenced objects are within its content, a tree's SHA1 exactly identifies and verifies the data throughout the structure; a checksum resulting from a series of checksums verifies all the underlying data regardless of the number of levels.
**Consider **storing a change to the file README illustrated in Figure 1. When committed, this would create a new blob (with a new SHA1), which would require a new tree to represent "foo" (with a new SHA1), which would require a new tree for the top directory (with a new SHA1).
While creating three new objects to store one change might seem inefficient, keep in mind that aside from the critical path of tree objects from changed file to root, every other object in the hierarchy remains __identical__. If you have a gigantic hierarchy of 10,000 files and you change the text of one file ten directories deep, __11 __new objects allow you to describe both the old and the new state of the tree.
**Note:**
One potential problem of the content-addressed design is that two large files with minor differences must be stored as different objects. However, Git optimizes these cases by using deltas to eliminate duplicate data between objects wherever possible. The size-reduced data is stored in a highly efficient manner in "__pack files__", which also are further compressed. This operates transparently underneath the object repository layer.
===== Commits =====
A commit is meant to record a set of changes introduced to a project. What it really does is__ associate a tree object__—representing a complete __snapshot __of a directory structure at a moment in time—with contextual information about it, such as who made the change and when, a description, and its parent commit(s).
A commit __doesn't__ actually store a list of changes (a "diff") directly, but it doesn't need to. What changed can be calculated on-demand by comparing the current __commit's tree__ to that of its parent. Comparing two trees is a lightweight operation, so there is no need to store this information. Because there actually is nothing special about the parent commit other than chronology, one commit can be compared to any other just as easily regardless of how many commits are in between.
PS:提交操作会生成一个commit类型对象该对象和一个tree对象关联。关联的tree对象会对当前整个工程的所有文件做一次“快照”然后将其SHA1 name存在相应的
tree对象中。
All commits should have a__ parent__ except the first one. Commits usually have a single parent, but they will have more if they are the result of a merge (I explain branching and merging later in this article). A commit from a merge still is just a snapshot in time like any other, but its history has more than one lineage.
By following the __chain of parent references__ backward from the current commit, the entire history of a project can be reconstructed and browsed all the way back to the first commit.
A commit is__ expanded recursively__ into a project history in exactly the same manner as a tree is expanded into a directory structure. More important, just as the SHA1 of a tree is a fingerprint of all the data in all the trees and blobs below it, the __SHA1 of a commit__ is a fingerprint of all the data in its tree, as well as all of the data in all the commits that preceded it.
PS每次commit前都会添加或修改一些文件因此每次commit对应的tree都不同。
This happens automatically because references are part of an object's overall content.对与树对象其内容就是对其代表的目录中的各文件的blob对象或子目录的tree对象的引用 The SHA1 of each object is computed, in part, from the SHA1s of any objects it references, which in turn were computed from the SHA1s they referenced and so on.
===== Tags =====
A tag is just a __named reference__ to an object—usually a commit. Tags typically are used to associate a particular version number with a commit. The 40-character SHA1 names are many things, but human-friendly isn't one of them. Tags solve this problem by letting you give an object an additional name.
There are two types of tags: __object tags and lightweight tags__. Lightweight tags are not objects in the repository, but instead are __simple refs like branches__, except that they don't change. (I explain branches in more detail in the Branching and Merging section below.)
PSlightweight tags 和 branches 一样并不是一个对象只是一个变量用于指向一个object通常是commit对象
===== Setting Up Git =====
If you don't already have Git on your system, install it with your package manager. Because Git is primarily a simple command-line tool, installing it is quick and easy under any modern distro.
You'll want to set the name and e-mail address that will be recorded in new commits:
//git config --global user.name "John Doe"//
//git config --global user.email john@example.com//
This just sets these parameters in the config file__ ~/.gitconfig__. The config has a simple syntax and could be edited by hand just as easily.
===== User Interface =====
Git's interface consists of the "__working copy__" (the files you directly interact with when working on the project), a local repository stored in a hidden __.git subdirectory__ at the root of the working copy, and __commands __to move data back and forth between them, or between remote repositories.
The advantages of this design are many, but right away you'll notice that there aren't pesky version control files scattered throughout the working copy, and that you can work off-line without any loss of features. In fact, Git doesn't have any concept of a central authority, so you always are "working off-line" unless you specifically ask Git to exchange commits with your peers.
The repository is made up of files that are manipulated by invoking the git command from within the working copy. There is no special server process or extra overhead, and you can have as many repositories on your system as you like.
You can turn any directory into a working copy/repository just by running this command from within it:
//git init//
Next, add all the files within the working copy to be tracked and commit them:
//git add . //递归地将当前目录下的所有文件加到“临时存储区”即Index。//
//git commit -m "My first commit"//
You can commit additional changes as frequently or infrequently as you like by calling git add followed by git commit after each modification you want to record.
If you're new to Git, you may be wondering why you need to call git add each time. It has to do with the process of "__staging__" a set of changes before committing them, and it's one of the most common sources of confusion. When you call git add on one or more files, they are added to the __Index__. The __files in the Index__—not the working copy—are what get committed when you call git commit.
PS只有在index里的文件才能被commit。commit后该commit便对应一个index(即存在commit对象包含的tree对象中的各文件)经过多次commit后当前的index可能与以前(包括最近一个)commit时的index不同。
Think of the Index as what will become the next commit. It simply provides an extra layer of granularity(粒度) and control in the commit process. It allows you to__ commit some of the differences__ in your working copy,(即通过index用户可以指定需要commit的对象而不是所有修改的对象) but not others, which is useful in many situations.
You don't have to take advantage of the Index if you don't want to, and you're not doing anything "wrong" if you don't. If you want to pretend it doesn't exist, just remember to call __git add . __from the root of the working copy (which will update the Index to match) each time and immediately before git commit. You also can use the -a option with git commit to add changes automatically; however, it will__ not add new__ files, only changes to existing files. Running git add. always will add everything.
PS 添加到index包括以前commit中包含的文件的文件的状态才能被git追踪(track), git add . 的作用有两个
1. 递归地将当前目录下的所有文件添加到index中即被追踪并设状态为staged。
2.以追踪的文件将被放到暂存区域中即状态被设为staged
The exact work flow and specific style of commands largely are left up to you as long as you follow the basic rules.
The git status command shows you all the differences between your__ working copy and the Index__, (哪些文件还没有被追踪,哪些被追踪且修改的文件还没有被放到暂存区(unstaged))and the Index and the most recent commit (the current HEAD)(当前修改了且放到暂存区中但还未commit的文件):
//git status//
This lets you see pending changes easily at any given time, and it even reminds you of relevant commands like git add to stage pending changes into the Index, or __git reset HEAD <file>__ to remove (unstage) changes that were added previously.
reset命令有3种方式
git reset mixed此为默认方式不带任何参数的git reset即为这种方式它回退到某个commit版本(同时删除此版本后的所有commit对象)保留工作树中所有源码同时__Index也回退__为该commit对应的index. 这会使当前工作树中所有与回退的commit index不同的文件的状态变为__修改但未暂存或未追踪__(所以需要 git add .将所有修改提交到回退的index中)。
git reset soft回退到某个版本只回退了commit的信息__index并不回退__当前目录树中与回退的commit对应的index不同的文件会被放到暂存区域所以如果还要提交自回退来的更改直接commit即可
git reset hard彻底回退到某个版本本地的源码也会变为上一个版本的内容。
=== 示例: ===
现假设我们git软件仓库中的分支情况如下
a-->b-->c
也就是说我们的代码从状态a修改到状态b进行一次提交然后再修改到状态c进行一次提交。这时我们已经肯定由a到c的修改是正确的不再需要状态b了并且要把从a到c的变化生成一个patch发送给别人。如果直接打包的话会生成两个path那么如何生成一个patch呢这时就需要git-reset命令。
首先给状态a创建一个tag假设名称为A然后执行git-reset --soft A 这样我们的软件仓库就变为a状态b和状态c都已经被删除了但是当前的代码并没有被改变**还是状态c的代码**这时我们做一次提交软件仓库变成下面的样子a-->d 状态d和状态c所对应的代码是完全相同的只是名字不同。现在就可以生成一个patch打包发给别人了。
===== Branching and Merging =====
The work you do in Git is specific to the current branch. A branch is simply a __moving reference to a commit__ (SHA1 object name). Every time you create a new commit, the reference is updated to point to it—this is how Git knows where to find the most recent commit, which is also known as the tip, or __head__, of the branch.
By default, there is only one branch ("__master__"), but you can have as many as you want. You create branches with **git branch** and switch between them with **git checkout**//.// This may seem odd at first, but the reason it's called "checkout" is that you are "checking out" __the head of that branch__ into your working copy. __This alters the files in your working copy to match the commit at the head of the branch__.
Branches are super-fast and easy, and they're a great way to try out new ideas, even for trivial things. If you are used to other systems like CVS/SVN, you might have negative thoughts associated with branches—forget all that. Branching and merging are free in Git and can be used without a second thought.
Run the following commands to create and switch to a new local branch named "myidea":
**git branch myidea working copy**
**git checkout myidea**
All commits now will be// tracked// in the new branch until you switch to another. You can work on more than one branch at a time by switching back and forth between them with git checkout.
Branches are really useful only because they can be merged back together later. If you decide that you like the changes in myidea, you can merge them back into master:
**git checkout master**
**git merge myidea**
Unless there are conflicts, this operation will merge all the changes from myidea into your __working copy__ and __automatically commit__ the result to master in one fell swoop. The new commit will have the previous commits from both myidea and master listed as parents.
However, if there are conflicts—places where the __same part __of a file was changed differently in each branch—Git will warn you and update the affected files with "__conflict markers__" and__ not commit__ the merge automatically. When this happens, it's up to you to edit the files by hand, make decisions between the versions from each branch, and then __remove the conflict markers__. To complete the merge, use **git add on** each formerly conflicted file, and then git commit.
After you merge from a branch, you don't need it anymore and can delete it:
**git branch -d myidea**
If you decide you want to throw myidea away without merging it, use an uppercase** -D** instead of a lowercase -d as listed above. As a safety feature, the lowercase switch won't let you delete a branch that hasn't been merged.
To list all __local branches__, simply run:
**git branch**
===== Viewing Changes =====
Git provides a number of tools to examine the history and differences between commits and branches. Use **git log **to view commit histories and **git diff **to view the differences between specific commits.
These are text-based tools, but graphical tools also are available, such as the **gitk** repository browser, which essentially is a GUI version of **git log --graph** to visualize branch history. See Figure 2 for a screenshot.
{{./10971f2.png}}
Figure 2. gitk
===== Remote Repositories =====
Git can merge from a branch in a remote repository simply by transferring__ needed objects__ and then running a__ local merge__. Thanks to the content-addressed storage design, Git knows which objects to transfer based on which object __names in the new commit __are missing from the local repository.
PS: git 从远方仓库获取某一分支的更新依靠的是远方该分支最近一次commit的内容如果该commit中包含的对象的SHA1值本地仓库没有那么git就会从远方仓库下载其对应文件到本地仓库中。
The** git pull** command performs __both the transfer step (the "fetch") and the merge step together__. It accepts the URL of the remote repository (the "Git URL") and a branch name (or a full "__refspec__") as arguments. The Git URL can be a local filesystem path, or an SSH, HTTP, rsync or Git-specific URL. For instance, this would perform a pull using SSH:
**git pull user@host:/some/repo/path master**
Git provides some useful mechanisms for setting up relationships with remote repositories and their branches so you don't have to type them out each time. A saved URL of a remote repository is called a "**remote**", which can be configured along with "tracking branches" to __map the remote branches into the local repository__.
A remote named "**origin**" is configured automatically when a repository is created using **git clone**. Consider a clone of Linus Torvald's Kernel Tree mirrored on GitHub:
**git clone https://github.com/mirrors/linux-2.6.git**
If you look inside the new repository's config file (__.git/config__), you'll see these lines set up:
[remote "origin"]
fetch = +refs/heads/*:refs/remotes/origin/*
url = https://github.com/mirrors/linux-2.6.git
[branch "master"] //定义git fetch 或 git pull 的缺省参数
remote = origin
merge = refs/heads/master
The fetch line above defines the __remote tracking branches__. This "__refspec__" specifies that all branches in the remote repository under "refs/heads" (the default path for branches) should be transferred to the local repository under "__refs/remotes/origin__". For example, the remote branch named "master" will become a tracking branch named "origin/master" in the local repository.
The lines under the branch section__ provide defaults__—specific to the master branch in this example—so that **git pull** can be called with no arguments to fetch and merge from the remote master branch into the local master branch.
The git pull command is actually a__ combination of the __**git fetch**__ and __**git merge**__ commands.__ If you do a **git fetch** instead, the tracking branches will be updated and you can compare them to see what changed. Then you can merge as a separate step:
**git merge origin/master**
Git also provides the **git push **command for uploading to a remote repository. The push operation is essentially the inverse of the pull operation, but since it won't do a remote "__checkout__" operation, it is usually used with "__bare__" repositories. A bare repository is just the git database __without a working copy__. It is most useful for servers where there is no reason to have editable files checked out.
For safety, **git push** will allow only a "__fast-forward__" merge where the local commits derive from the remote head. If the local head and remote head have __both__ changed, you must perform a full merge (which will create a new commit deriving from both heads). Full merges must be done locally, so all this really means is you must call__ git pull before git push__ if someone else committed something first.
===== Conclusion =====
This article is meant only to provide an introduction to some of Git's most basic features and usage. Git is incredibly powerful and has a lot more capabilities beyond what I had space to cover here. But, once you realize all the features are based on the same core concepts, it becomes straightforward to learn the rest.
Check out the Resources section for some sites where you can learn more. Also, don't forget to read the git man page.
Resources
Git Home Page: http://git-scm.com
Git Community Book: http://book.git-scm.com
Why Git Is Better Than X: http://whygitisbetterthanx.com
Google Tech Talk: Linus Torvalds on Git: http://www.youtube.com/watch?v=4XpnKHJAok8

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

View File

@@ -0,0 +1,57 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-05-16T21:57:29+08:00
====== Git sync via USB ======
Created Monday 16 May 2011
http://blog.ekynoxe.com/2010/11/17/git-sync-via-usb/
Right, suppose you need to sneakernet some git repository changes, Maybe your network is down and you want to send changes to co-workers or another machine from which you have a full network access. Maybe youre somewhere without access to the a network and/or the correct ports/protocols for security reasons. Maybe all network connections are down and you really need to transfer your changes to someone else. How do you go about that?
Of course, there is the **git-bundle** solution, but I feel it is probably more suited to large projects where you only need to sync changes about small parts of the code base, rather than the whole repo, and it involves creation of bundle files which I was not too happy about doing.
I therefore went instead on the route of **creating a normal repo on my USB stick and using it as the transport mean**. This involves **creating a bare repo on the usb stick and adding the correct remote branches on machines either side of the transfer.**
Im working on a mac, so youll have to adapt the paths to your own situation. My USB stick is mounted on
/Volumes/USB_STICK/
===== On USB stick, from machine A =====
All you need to do is set up bare repo (and thats all!)
**machineA$ cd /Volumes/USB_STICK/desired_repo_path/**
**USB_STICK$ git init --bare**
===== On machine A (holding the initial code) =====
First, set up your git repository normally if its **not already** done:
**machineA$ cd /my/files/path/**
**machineA$ git init**
**machineA$ git add .**
**machineA$ git commit -m "initial commit"**
Then simply __add a remote branch pointing at the usb stick repo__
**machineA$ git remote add **__stick__** /Volumes/USB_STICK_NAME/desired_repo_path/**
**machineA$ git push -u stick master**
You have now pushed your complete repository (the master branch to be more precise) on the usb stick. All you need to do is to pull it from your second machine!
===== On machine B (Where you want to transfer the code to) =====
Set up a new repository to receive the code, add a remote branch from the stick and pull the changes as you would normally do
**machineB$ cd /path/where/IWant/My/Files/**
**machineB$ git init**
**machineB$ git remote add stick /Volumes/USB_STICK_NAME/desired_repo_path/**
**machineB$ git **__pull -u__** stick master**
You now have transferred your code from MAchine A to Machine B, and you can work normally on this machine, committing as you normally would. Then, to transfer the changes back to machine A. Nothing more simple:
**On machine B**
__machineB$ git push stick__
**On machine A**
__machineA$ git pull stick__
From there, if you have set up a remote tracking branch on a network server that co-workers pull from of from which you checkout code for deployment, you can now do a simple
__machineA$ git push__
**PS:**上面一条语句将本地仓库中的代码push到所有的远程服务器上(包括USB设备)
And youre good!

View File

@@ -0,0 +1,755 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2012-02-19T13:50:58+08:00
====== Git 中文教程 ======
Created Sunday 19 February 2012
http://www.linuxsir.org/main/doc/git/gittutorcn.htm
===== 介绍 =====
Git --- The stupid content tracker, __傻瓜内容跟踪器__。Linus 是这样给我们介绍 Git 的。
Git 是用于 Linux 内核开发的版本控制工具。与常用的版本控制工具 CVS, Subversion 等不同它采用了__分布式版本库__的方式不必服务器端软件支持使源代码的发布和交流极其方便。 Git 的速度很快,这对于诸如 Linux kernel 这样的大项目来说自然很重要。 Git 最为出色的是它的__合并跟踪__merge tracing能力。
实际上内核开发团队决定开始开发和使用 Git 来作为内核开发的版本控制系统的时候,世界开源社群的反对声音不少,最大的理由是 Git 太艰涩难懂,从 Git 的内部工作机制来说的确是这样。但是随着开发的深入Git 的正常使用都由一些友好的脚本命令来执行,使 Git 变得非常好用即使是用来管理我们自己的开发项目Git 都是一个友好,有力的工具。现在,越来越多的著名项目采用 Git 来管理项目开发例如wine, U-boot 等,详情看 http://www.kernel.org/git
作为开源自由原教旨主义项目Git 没有对版本库的浏览和修改做任何的权限限制。它只适用于 Linux / Unix 平台,没有 Windows 版本,目前也没有这样的开发计划。
本文将以 Git 官方文档 Tutorial core-tutorial 和 Everyday GIT 作为蓝本翻译整理,但是暂时去掉了对 Git 内部工作机制的阐述,力求简明扼要,并加入了作者使用 Git 的过程中的一些心得体会,注意事项,以及更多的例子。建议你最好通过你所使用的 Unix / Linux 发行版的安装包来安装 Git, 你可以在线浏览本文 ,也可以通过下面的命令来得到本文最新的版本库,并且通过后面的学习用 Git 作为工具参加到本文的创作中来。
$ git-clone http://www.bitsun.com/git/gittutorcn.git
创建一个版本库git init
创建一个 Git 版本库是很容易的,只要用命令 git init 就可以了。现在我们来为本文的写作创建一个版本库:
$ mkdir gittutorcn
$ cd gittutorcn
$ git init
git 将会作出以下的回应
defaulting to local storage area
这样,一个空的版本库就创建好了,并在当前目录中创建一个叫 .git 的子目录。你可以用 ls -a 查看一下,并请注意其中的三项内容:
* 一个叫 HEAD 的文件,我们现在来查看一下它的内容:
$ cat .git/HEAD
现在 HEAD 的内容应该是这样:
ref: refs/heads/master
我们可以看到__HEAD 文件中的内容其实只是包含了一个索引信息并且这个索引将总是指向你的项目中的当前开发分支__。
* 一个叫 objects 的子目录它包含了你的__项目中的所有对象__我们不必直接地了解到这些对象内容我们应该关心是存放在这些对象中的项目的数据。
* 一个叫 refs 的子目录它__用来保存指向对象的索引__。
具体地说,子目录 refs 包含着两个子目录叫** heads 和 tags**就像他们的名字所表达的意味一样他们__存放了不同的开发分支的头的索引__, 或者是你用来标定版本的标签的索引。
请注意__master 是默认的分支__这也是为什么 .git/HEAD 创建的时候就指向 master 的原因,尽管目前它其实并不存在。 git 将假设你会在 master 上开始并展开你以后的工作,除非你自己创建你自己的分支。
另外,这只是一个约定俗成的习惯而已,实际上你可以将你的工作分支叫任何名字,而不必在版本库中一定要有一个叫 master 的分支,尽管很多 git 工具都认为 master 分支是存在的。
现在已经创建好了一个 git 版本库,但是它是空的,还不能做任何事情,下一步就是怎么向版本库植入数据了。
植入内容跟踪信息git add
为了简明起见,我们创建两个文件作为练习:
$ echo "Hello world" > hello
$ echo "Silly example" > example
我们再用 git add 命令将这两个文件加入到版本库文件索引当中:
$ git add hello example
git add 实际上是个脚本命令__它是对 git 内核命令 git-update-index 的调用__。因此上面的命令和下面的命令其实是等价的
$ git-update-index --add hello example
如果你要将某个文件从 git 的目录跟踪系统中清除出去,同样可以用 git-update-index 命令。例如:
$ git-update-index --force-remove foo.c
git add 可以将__某个目录下的所有内容__全都纳入内容跟踪之下例如 git-add ./path/to/your/wanted 。但是在这样做之前应该注意先将一些我们不希望跟踪的文件清理掉例如gcc 编译出来的 *.o 文件vim 的交换文件 .*.swp 之类。
应该建立一个清晰的概念就是__git add 和 git-update-index 只是刷新了 git 的跟踪信息hello 和 example 这两个文件中的内容并没有提交到 git 的内容跟踪范畴之内__。
提交内容到版本库git-commit
既然我们刷新了 Git 的跟踪信息,现在我们看看版本库的状态:
$ git-status
我们能看到 git 的状态提示:
#
# Initial commit
#
#
# Updated but not checked in:
# (will commit)
#
# new file: example
# new file: hello
#
提示信息告诉我们版本库中加入了两个新的文件,并且 git 提示我们提交这些文件,我们可以通过 git-commit 命令来提交:
$ git-commit -m "Initial commit of gittutor reposistory"
查看当前的工作git-diff
git-diff 命令将比较当前的工作目录和版本库数据库中的差异。现在我们编辑一些文件来体验一下 git 的跟踪功能。
$ echo "It's a new day for git" >> hello
我们再来比较一下,当前的工作目录和版本库中的数据的差别。
$ git-diff
差异将以典型的 patch 方式表示出来:
diff --git a/hello b/hello
index a5c1966..bd9212c 100644
--- a/hello
+++ b/hello
@@ -1 +1,2 @@
Hello, world
+It's a new day for git
此时,我们可以再次使用组合命令 git-update-index 和 git-commit 将我们的工作提交到版本库中。
$ git-update-index hello
$ git-commit -m "new day for git"
实际上,如果要提交的文件都是已经纳入 git 版本库的文件,那么不必为这些文件都应用 git-update-index 命令之后再进行提交,下面的命令更简捷并且和上面的命令是等价的。
$ git-commit -a -m "new day for git"
===== 管理分支git-branch =====
直至现在为止,我们的项目版本库一直都是只有一个分支 master。在 git 版本库中创建分支的成本几乎为零,所以,不必吝啬多创建几个分支。下面列举一些常见的分支策略,仅供大家参考:
创建一个属于自己的个人工作分支,以避免对主分支 master 造成太多的干扰,也方便与他人交流协作。
当进行高风险的工作时,创建一个试验性的分支,扔掉一个烂摊子总比收拾一个烂摊子好得多。
合并别人的工作的时候,最好是创建一个临时的分支,关于如何用临时分支合并别人的工作的技巧,将会在后面讲述。
创建分支
下面的命令将创建我自己的工作分支,名叫 robin并且将以后的工作转移到这个分支上开展。
$ git-branch robin
$ git-checkout robin
删除分支
要删除版本库中的某个分支,使用 git-branch -D 命令就可以了,例如:
$ git-branch -D branch-name
查看项目的发展变化和比较差异
这一节介绍几个查看项目的版本库的发展变化以及比较差异的很有用的命令:
git-show-branch
git-diff
git-whatchanged
我们现在为 robin, master 两个分支都增加一些内容。
$ git-checkout robin
$ echo "Work, work, workd" >> hello
$ git-commit -m "Some workd" -i hello
$ git-checkout master
$ echo "Play, play, play" >> hello
$ echo "Lots of fun" >> example
$ git-commit -m "Some fun" -i hello example
git-show-branch 命令可以使我们看到版本库中每个分支的世系发展状态,并且可以看到每次提交的内容是否已进入每个分支。
$ git-show-branch
这个命令让我们看到版本库的发展记录。
* [master] Some fun
! [robin] some work
--
* [master] Some fun
+ [robin] some work
*+ [master^] a new day for git
譬如我们要查看世系标号为 master^ 和 robin 的版本的差异情况,我们可以使用这样的命令:
$ git-diff master^ robin
我们可以看到这两个版本的差异:
diff --git a/hello b/hello
index 263414f..cc44c73 100644
--- a/hello
+++ b/hello
@@ -1,2 +1,3 @@
Hello World
It's a new day for git
+Work, work, work
Note
关于 GIT 版本世系编号的定义,请参看 git-rev-parse 。
我们现在再用 git-whatchanged 命令来看看 master 分支是怎么发展的。
$ git-checkout master
$ git-whatchanged
diff-tree 1d2fa05... (from 3ecebc0...)
Author: Vortune.Robin
Date: Tue Mar 21 02:24:31 2006 +0800
Some fun
:100644 100644 f24c74a... 7f8b141... M example
:100644 100644 263414f... 06fa6a2... M hello
diff-tree 3ecebc0... (from 895f09a...)
Author: Vortune.Robin
Date: Tue Mar 21 02:17:23 2006 +0800
a new day for git
:100644 100644 557db03... 263414f... M hello
从上面的内容中我们可以看到,在 robin 分支中的日志为 "Some work" 的内容, 并没有在 master 分支中出现。
合并两个分支git-merge
既然我们为项目创建了不同的分支,那么我们就要经常地将自己或者是别人在一个分支上的工作合并到其他的分支上去。现在我们看看怎么将 robin 分支上的工作合并到 master 分支中。现在转移我们当前的工作分支到 master并且将 robin 分支上的工作合并进来。
$ git-checkout master
$ git-merge "Merge work in robin" HEAD robin
合并两个分支,还有一个更简便的方式,下面的命令和上面的命令是等价的。
$ git-checkout master
$ git-pull . robin
但是,此时 git 会出现合并冲突提示:
Trying really trivial in-index merge...
fatal: Merge requires file-level merging
Nope.
Merging HEAD with d2659fcf690ec693c04c82b03202fc5530d50960
Merging:
1d2fa05b13b63e39f621d8ee911817df0662d9b7 Some fun
d2659fcf690ec693c04c82b03202fc5530d50960 some work
found 1 common ancestor(s):
3ecebc0cb4894a33208dfa7c7c6fc8b5f9da0eda a new day for git
Auto-merging hello
CONFLICT (content): Merge conflict in hello
Automatic merge failed; fix up by hand
git 的提示指出,在合并作用于文件 hello 的 'Some fun' 和 'some work' 这两个对象时有冲突,具体通俗点说,就是在 master, robin 这两个分支中的 hello 文件的某些相同的行中的内容不一样。我们需要手动解决这些冲突,现在先让我们看看现在的 hello 文件中的内容。
$ cat hello
此时的 hello 文件应是这样的,用过其他的版本控制系统的朋友应该很容易看出这个典型的冲突表示格式:
Hello World
It's a new day for git
<<<<<<< HEAD/hello
Play, play, play
=======
Work, work, work
>>>>>>> d2659fcf690ec693c04c82b03202fc5530d50960/hello
我们用编辑器将 hello 文件改为:
Hello World
It's a new day for git
Play, play, play
Work, work, work
现在可以将手动解决了冲突的文件提交了。
$ git-commit -i hello
以上是典型的两路合并2-way merge算法绝大多数情况下已经够用。但是还有更复杂的三路合并和多内容树合并的情况。详情可参看 git-read-tree git-merge 等文档。
逆转与恢复git-reset
项目跟踪工具的一个重要任务之一就是使我们能够随时逆转Undo和恢复Redo某一阶段的工作。
git-reset 命令就是为这样的任务准备的。它将当前的工作分支的 头 定位到以前提交的任何版本中,它有三个重置的算法选项。
命令形式:
git-reset [--mixed | --soft | --hard] [<commit-ish>]
命令的选项:
--mixed
仅是重置索引的位置,而不改变你的工作树中的任何东西(即,文件中的所有变化都会被保留,也不标记他们为待提交状态),并且提示什么内容还没有被更新了。这个是默认的选项。
--soft
既不触动索引的位置也不改变工作树中的任何内容我们只是要求这些内容成为一份好的内容之后才成为真正的提交内容。这个选项使你可以将已经提交的东西重新逆转至“已更新但未提交Updated but not Check in”的状态。就像已经执行过 git-update-index 命令,但是还没有执行 git-commit 命令一样。
--hard
将工作树中的内容和头索引都切换至指定的版本位置中,也就是说自 <commit-ish> 之后的所有的跟踪内容和工作树中的内容都会全部丢失。因此,这个选项要慎用,除非你已经非常确定你的确不想再看到那些东西了。
一个重要技巧--逆转提交与恢复
可能有人会问,--soft 选项既不重置头索引的位置,也不改变工作树中的内容,那么它有什么用呢?现在我们介绍一个 --soft 选项的使用技巧。下面我们用例子来说明:
$ git-checkout master
$ git-checkout -b softreset
$ git-show-branch
这里我们创建了一个 master 的拷贝分支 softreset现在我们可以看到两个分支是在同一起跑线上的。
! [master] Merge branch 'robin'
! [robin] some work
* [softreset] Merge branch 'robin'
---
- - [master] Merge branch 'robin'
+ * [master^] Some fun
++* [robin] some work
我们为 文件增加一些内容并提交。
$ echo "Botch, botch, botch" >> hello
$ git-commit -a -m "some botch"
$ git-show-branch
我们可以看到此时 softreset 比 master 推进了一个版本 "some botch" 。
! [master] Merge branch 'robin'
! [robin] some work
* [softreset] some botch
---
* [softreset] some botch
- - [master] Merge branch 'robin'
+ * [master^] Some fun
++* [robin] some work
现在让我们来考虑这样的一种情况,假如我们现在对刚刚提交的内容不满意,那么我们再编辑项目的内容,再提交的话,那么 "some botch" 的内容就会留在版本库中了。我们当然不希望将有明显问题的内容留在版本库中,这个时候 --soft 选项就很有用了。为了深入了解 --soft 的机制,我们看看现在 softreset 分支的头和 ORIG_HEAD 保存的索引。
$ cat .git/refs/heads/softreset .git/ORIG_HEAD
结果如下:
5e7cf906233e052bdca8c598cad2cb5478f9540a
7bbd1370e2c667d955b6f6652bf8274efdc1fbd3
现在用 --soft 选项逆转刚才提交的内容:
git-reset --soft HEAD^
现在让我们再看看 .git/ORIG_HEAD 的中保存了什么?
$ cat .git/ORIG_HEAD
结果如下:
5e7cf906233e052bdca8c598cad2cb5478f9540a
看!现在的 .git/ORIG_HEAD 等于逆转前的 .git/refs/heads/softreset 。也就是说git-reset --soft HEAD^ 命令逆转了刚才提交的版本进度,但是它将那次提交的对象的索引拷贝到了 .git/ORIG_HEAD 中。
我们再编辑 hello 文件成为下面的内容:
Hello World
It's a new day for git
Play, play, play
Work, work, work
Nice, nice, nice
我们甚至可以比较一下现在的工作树中的内容和被取消了的那次提交的内容有什么差异:
$ git-diff ORIG_HEAD
结果如下:
diff --git a/hello b/hello
index f978676..dd02c32 100644
--- a/hello
+++ b/hello
@@ -2,4 +2,4 @@ Hello World
It's a new day for git
Play, play, play
Work, work, work
-Botch, botch, botch
+Nice, nice, nice
接着,我们可以恢复刚才被取消了的那次提交了。
$ git-commit -a -c ORIG_HEAD
注意,这个命令会打开默认的文本编辑器以编辑原来提交的版本日志信息,我们改为 "nice work" 。大家可以自行用 git-show-branch 命令来查看一下现在的分支状态。并且我们还可以不断地重复上述的步骤,一直修改到你对这个版本进度满意为止。
git-reset 命令还有很多的用途和技巧,请参考 git-reset ,以及 Everyday GIT with 20 commands or So 。
提取版本库中的数据
这是个很有用的小技巧,如果你对你现在的工作目录下的东西已经不耐烦了,随时可以取出你提交过的东西覆盖掉当前的文件,譬如:
$ git-checkout -f foo.c
标定版本
在 git 中,有两种类型的标签,“轻标签”和“署名标签”。
技术上说,一个“轻标签”和一个分支没有任何区别,只不过我们将它放在了 .git/refs/tags/ 目录,而不是 heads 目录。因此,打一个“轻标签”再简单不过了。
$ git-tag my-first-tag
“署名标签”是一个真正的 git 对象,它不但包含指向你想标记的状态的指针,还有一个标记名和信息,可选的 PGP 签名。你可以通过 -a 或者是 -s 选项来创建“署名标签”。
$ git-tag -s <tag-name>
合并外部工作
通常的情况下,合并其他的人的工作的情况会比合并自己的分支的情况要多,这在 git 中是非常容易的事情,和你运行 git-merge 命令没有什么区别。事实上远程合并的无非就是“抓取fetch一个远程的版本库中的工作到一个临时的标签中”然后再使用 git-merge 命令。
可以通过下面的命令来抓取远程版本库:
$ git-fetch <remote-repository>
根据不同的远程版本库所使用的通讯协议的路径来替代上面的 remoted-repository 就可以了。
Rsync
rsync://remote.machine/patch/to/repo.git/
SSH
remote.machine:/path/to/repo.git
or
ssh://remote.machine/patch/to/repo.git/
这是可以上传和下载的双向传输协议,当然,你要有通过 ssh 协议登录远程机器的权限。它可以找出两端的机器提交过的对象集之中相互缺少了那些对象,从而得到需要传输的最小对象集。这是最高效地交换两个版本库之间的对象的方式(在 git 兼容的所有传输协议当中)。
下面是个取得 SSH 远程版本库的命令例子:
$ git-fetch robin@192.168.1.168:/path/to/gittutorcn.git (1)
(1) 这里 robin 是登录的用户名192.168.1.168 是保存着主版本库的机器的 IP 地址。
Local directory
/path/to/repo.git/
本地目录的情况和 SSH 情况是一样的。
git Native
git://remote.machine/path/to/repo.git/
git 自然协议是设计来用于匿名下载的,它的工作方式类似于 SSH 协议的交换方式。
HTTP(S)
http://remote.machine/path/to/repo.git/
到这里可能有些朋友已经想到,实际上,我们可以通过 Rsync, SSH 之类的双向传输方式来建立类似 CVSSVN 这样的中心版本库模式的开发组织形式。
通过电子邮件交换工作
读过上一节之后,有的朋友可能要问,如果版本库是通过单向的下载协议发布的,如 HTTP我们就无法将工作上传到公共的版本库中。别人也不能访问我的机器来抓取我的工作那怎么办呢
不必担心,我们还有 email !别忘了 git 本来就是为了管理 Linux 的内核开发而设计的。所以,它非常适合像 Linux Kernel 这样的开发组织形式高度分散,严重依赖 email 来进行交流的项目。
下面模拟你参加到《Git 中文教程》的编写工作中来,看看我们可以怎么通过 email 进行工作交流。你可以通过下面的命令下载这个项目的版本库。
$ git-clone http://www.bitsun.com/git/gittutorcn.git
之后,你会在当前目录下得到一个叫 gittutorcn 的目录,这就是你的项目的工作目录了。默认地,它会有两个分支: master 和 origin你可以直接在 master 下展开工作,也可以创建你自己的工作分支,但是千万不要修改 origin 分支,切记!因为它是公共版本库的镜像,如果你修改了它,那么就不能生成正确的对公共版本库的 patch 文件了。
Note
如果你的确修改过 origin 分支的内容,那么在生成 patch 文件之前,请用 git-reset --hard 命令将它逆转到最原始的,没经过任何修改的状态。
你可以直接在 master 下开展工作,也可以创建你自己的工作分支。当你对项目做了一定的工作,并提交到库中。我们用 git-show-branch 命令先看下库的状态。
* [master] your buddy's contribution
! [origin] degining of git-format-patch example
--
* [master] your buddy's contribution
*+ [origin] degining of git-format-patch example
上面就假设你已经提交了一个叫 "your buddy's contribution" 的工作。现在我们来看看怎么通过 email 来交流工作了。
$ git-fetch origin (1)
$ git-rebase origin (2)
$ git-format-patch origin (3)
(1)更新 origin 分支,防止 origin 分支不是最新的公共版本,产生错误的补丁文件;
(2)将你在 master 上提交的工作迁移到新的源版本库的状态的基础上;
(3)生成补丁文件;
上面的几个命令,会在当前目录下生成一个大概名为 0001-your-buddy-s-contribution.txt 补丁文件, 建议你用文本工具查看一下这个文件的具体形式,然后将这个文件以附件的形式发送到项目维护者的邮箱: vortune@gmail.com
当项目的维护者收到你的邮件后,只需要用 git-am 命令,就可以将你的工作合并到项目中来。
$ git-checkout -b buddy-incomming
$ git-am /path/to/0001-your-buddy-s-contribution.txt
用 Git 协同工作
假设 Alice 在一部机器上自己的个人目录中创建了一个项目 /home/alice/project, Bob 想在同一部机器自己的个人目录中为这个项目做点什么。
Bob 首先这样开始:
$ git-clone /home/alice/project myrepo
这样就创建了一个保存着 Alice 的版本库的镜像的新目录 "myrepo"。这个镜像保存着原始项目的起点和它的发展历程。
接着 Bob 对项目做了些更改并提交了这些更改:
(编辑一些文件)
$ git-commit -a
(如果需要的话再重复这个步骤)
当他搞定之后,他告诉 Alice 将他的东西从 /home/bob/myrepo 中引入,她只需要这样:
$ cd /home/alice/project
$ git pull /home/bob/myrepo
这样就将 Bob 的版本库中的 "master" 分支的变化引入了。 Alice 也可以通过在 pull 命令的后面加入参数的方式来引入其他的分支。
在导入了 Bob 的工作之后,用 "git-whatchanged" 命令可以查看有什么信的提交对象。如果这段时间里以来Alice 也对项目做过自己的修改,当 Bob 的修改被合并进来的时候,那么她需要手动修复所有的合并冲突。
谨慎的 Alice 在导入 Bob 的工作之前,希望先检查一下。那么她可以先将 Bob 的工作导入到一个新创建的临时分支中,以方便研究 Bob 的工作:
$ git fetch /home/bob/myrepo master:bob-incoming
这个命令将 Bob 的 master 分支的导入到名为 bob-incoming 的分支中(不同于 git-pull 命令git-fetch 命令只是取得 Bob 的开发工作的拷贝,而不是合并经来)。接着:
$ git whatchanged -p master..bob-incoming
这会列出 Bob 自取得 Alice 的 master 分支之后开始工作的所有变化。检查过这些工作,并做过必须的调整之后, Alice 就可以将变化导入到她的 master 分支中:
$ git-checkout master
$git-pull . bob-incoming
最后的命令就是将 "bob-incoming" 分支的东西导入到 Alice 自己的版本库中的稍后Bob 就可以通过下面的命令同步 Alice 的最新变化。
$ git-pull
注意不需为这个命令加入 Alice 的版本库的路径,因为当 Bob 克隆 Alice 的版本库的时候, git 已经将这个路径保存到 .git/remote/origin 文件中,它将会是所以的导入操作的默认路径。
Bob 可能已经注意到他并没有在他的版本库中创建过分支(但是分支已经存在了):
$ git branch
* master
origin
"origin" 分支,它是运行 "git-clone" 的时候自动创建的,他是 Alice 的 master 分支的原始镜像, Bob 应该永远不要向这个分支提交任何东西。
如果 Bob 以后决定在另外一部主机上开展工作,那么他仍然需要通过 SSH 协议从新克隆和导入( Alice 的版本库):
$ git-clone alice.org:/home/alice/project/ myrepo
我们可以使用 git 自然协议,或者是 rsync, http 等协议的任何一种,详情请参考 git-pull。
Git 同样可以建立类似 CVS 那样的开发模式,也就是所有开发者都向中心版本库提交工作的方式,详情参考 git_push 和 git for CVS users 。
为版本库打包
在前面,我们已经看到在 .git/objects/??/ 目录中保存着我们创建的每一个 git 对象。这样的方式对于自动和安全地创建对象很有效,但是对于网络传输则不方便。 git 对象一旦创建了,就不能被改变,但有一个方法可以优化对象的存储,就是将他们“打包到一起”。
$ git repack
上面的命令让你做到这点,如果你一直是做着我们的例子过来的,你现在大约会在 .git/objects/??/ 目录下积累了17个对象。 git-repack 会告诉你有几个对象被打包了,并且将他们保存在 .git/objects/pack 目录当中。
Note
你将会看到两个文件pack-*.pack and pack-*.idx 在 .git/objects/pack 目录。他们的关系是很密切的,如果你手动将他们拷贝到别的版本库中的话,你要决定将他们一起拷贝。前者是保存着所有被打包的数据的文件,后者是随机访问的索引。
如果你是个偏执狂,就运行一下 git-verity-pack 命令来检查一下有缺陷的包吧,不过,其实你无须太多担心,我们的程序非常出色 ;-).
一旦你已经对那些对象打包了,那么那些已经被打过包的原始的对象,就没有必要保留了。
$ git prune-packed
会帮你清楚他们。
如果你好奇的话,你可以在执行 git-prune-repacked 命令之前和之后,都运行一下 find .git/objects -type f这样你就能看到有多少没有打包的对象以及节省了多少磁盘空间。
Note
git pull git-pull 对于 HTTP 传输来说,一个打包过的版本库会将一定数量的相关联的对象放进一个有关联性的打包中。如果你设想多次从 HTTP 公共版本库中导入数据,你也许要频繁地 reapck & prune要么就干脆从不这样做。
如果你此时再次运行 git-repack它就会说 "Nothing to pack"。要是你继续开发,并且积累了一定数量的变迁,再运行 git-repack 将会创建一个新的包,它会包含你自上次对库打包以来创建的对象。我们建议你尽快在初始化提交之后打包一下你的版本库(除非你现在的项目是个涂鸦式的草稿项目),并且在项目经历过一段很活跃的时期时,再运行 git-repack 一下。
当一个版本库通过 git-push 和 git-pull 命令来同步源版本库中打包过的对像的时候,通常保存到目标版本库中的是解包了的对象,除非你使用的是 rsync远程同步协议协议的传输方式。正是这种容许你在两头的版本库中有不同的打包策略的方式他意味着你也许在过一段时间之后需要在两头的版本库中都重新打包一下。
将工作捆绑到一起
通过 git 的分支功能,你可以非常容易地做到好像在同一时间进行许多“相关-或-无关”的工作一样。
我们已经通过前面的 "fun and work" 使用两个分支的例子,看到分支是怎么工作的。这样的思想在多于两个的分支的时候也是一样的,比方说,你现在在 master 的头,并有些新的代码在 master 中,另外还有两个互不相关的补丁分别在 "commit-fix" 和 "diff-fix" 两个分支中。
$ git show-branch
! [commit-fix] Fix commit message normalization.
! [diff-fix] Fix rename detection.
* [master] Release candidate #1
---
+ [diff-fix] Fix rename detection.
+ [diff-fix~1] Better common substring algorithm.
+ [commit-fix] Fix commit message normalization.
* [master] Release candidate #1
++* [diff-fix~2] Pretty-print messages.
两个补丁我们都测试好了,到这里,你想将他们俩合并起来,于是你可以先合并 diff-fix ,然后再合并 commit-fix像这样
$ git merge 'Merge fix in diff-fix' master diff-fix
$ git merge 'Merge fix in commit-fix' master commit-fix
结果如下:
$ git show-branch
! [commit-fix] Fix commit message normalization.
! [diff-fix] Fix rename detection.
* [master] Merge fix in commit-fix
---
- [master] Merge fix in commit-fix
+ * [commit-fix] Fix commit message normalization.
- [master~1] Merge fix in diff-fix
+* [diff-fix] Fix rename detection.
+* [diff-fix~1] Better common substring algorithm.
* [master~2] Release candidate #1
++* [master~3] Pretty-print messages.
然而,当你确信你手头上的确是一堆互不相关的项目变化时,就没有任何理由将这堆东西一个个地合并(假如他们的先后顺序很重要,那么他们就不应该被定以为无关的变化),你可以一次性将那两个分支合并到当前的分支中,首先我们将我们刚刚做过的事情逆转一下,我们需要通过将 master 分支重置到 master~2 位置的方法来将它逆转到合并那两个分支之前的状态。
$ git reset --hard master~2
你可以用 git-show-branch 来确认一下的确是回到了两次 git-merge 的状态了。现在你可以用一行命令将那两个分支导入的方式来替代两次运行(也就是所谓的 炮制章鱼 -- making an Octopusgit-merge
$ git pull . commit-fix diff-fix
$ git show-branch
! [commit-fix] Fix commit message normalization.
! [diff-fix] Fix rename detection.
* [master] Octopus merge of branches 'diff-fix' and 'commit-fix'
---
- [master] Octopus merge of branches 'diff-fix' and 'commit-fix'
+ * [commit-fix] Fix commit message normalization.
+* [diff-fix] Fix rename detection.
+* [diff-fix~1] Better common substring algorithm.
* [master~1] Release candidate #1
++* [master~2] Pretty-print messages.
注意那些不适合制作章鱼的场合,尽管你可以那样做。一只“章鱼”往往可以使项目的提交历史更具可读性,前提是你在同一时间导入的两份以上的变更是互不关联的。然而,如果你在合并任何分支的过程中出现合并冲突,并且需要手工解决的话,那意味着这些分支当中有相互干涉的开发工作在进行,那么你就应该将这个两个冲突先合并,并且记录下你是如何解决这个冲突,以及你首先处理他们的理由。(译者按:处理完冲突之后,你就可以放心制作“章鱼”了)否则的话将会造成项目的发展历史很难跟踪。
管理版本库
版本库的管理员可以用下面的工具来建立和维护版本库。
git-daemon(1) 容许匿名下载版本库。
git-shell(1) 面向中心版本库模式的用户的类似 受限的 shell 的命令。
update hook howto 一个很好的管理中心版本库的例子。
例子
在 /pub/scm 上运行 git 守护进程
$ grep git /etc/inet.conf
git stream tcp nowait nobody \
/usr/bin/git-daemon git-daemon --inetd --syslog --export-all /pub/scm
这个配置行应该在配置文件中用一行来写完。
仅给开发者 push/pull 的访问权限。
$ grep git /etc/passwd (1)
alice:x:1000:1000::/home/alice:/usr/bin/git-shell
bob:x:1001:1001::/home/bob:/usr/bin/git-shell
cindy:x:1002:1002::/home/cindy:/usr/bin/git-shell
david:x:1003:1003::/home/david:/usr/bin/git-shell
$ grep git /etc/shells (2)
/usr/bin/git-shell
(1) 将用户的登录 shell 设定为 /usr/bin/git-shell,
它除了运行 "git-push" 和 "git-pull" 不能做任何事。
这样用户就可以通过 ssh 来访问机器。
(2) 许多的发行版需要在 /etc/shells 配置文件中列明要用什么 shell 来作为登录 shell。
CVS - 模式的公共库。
$ grep git /etc/group (1)
git:x:9418:alice,bob,cindy,david
$ cd /home/devo.git
$ ls -l (2)
lrwxrwxrwx 1 david git 17 Dec 4 22:40 HEAD -> refs/heads/master
drwxrwsr-x 2 david git 4096 Dec 4 22:40 branches
-rw-rw-r-- 1 david git 84 Dec 4 22:40 config
-rw-rw-r-- 1 david git 58 Dec 4 22:40 description
drwxrwsr-x 2 david git 4096 Dec 4 22:40 hooks
-rw-rw-r-- 1 david git 37504 Dec 4 22:40 index
drwxrwsr-x 2 david git 4096 Dec 4 22:40 info
drwxrwsr-x 4 david git 4096 Dec 4 22:40 objects
drwxrwsr-x 4 david git 4096 Nov 7 14:58 refs
drwxrwsr-x 2 david git 4096 Dec 4 22:40 remotes
$ ls -l hooks/update (3)
-r-xr-xr-x 1 david git 3536 Dec 4 22:40 update
$ cat info/allowed-users (4)
refs/heads/master alice\|cindy
refs/heads/doc-update bob
refs/tags/v[0-9]* david
(1) 将所有的开发人员都作为 git 组的成员。
(2) 并且给予他们公共版本库的写权限。
(3) 用一个在 Documentation/howto/ 中的 Carl 写的例子来实现版本库的分支控制策略。
(4) Alice 和 Cindy 可以提交入 master 分支,只有 Bob 能提交入 doc-update 分支,
David 则是发行经理只有他能创建并且 push 版本标签。
支持默协议传输的 HTTP 服务器。
dev$ git update-server-info (1)
dev$ ftp user@isp.example.com (2)
ftp> cp -r .git /home/user/myproject.git
(1) 保证 info/refs 和 object/info/packs 是最新的。
(2) 上传到你的 HTTP 服务器主机。
项目开发的模式推介
尽管 git 是一个正式项目发布系统,它却可以方便地将你的项目建立在松散的开发人员组织形式上。 Linux 内核的开发,就是按这样的模式进行的。在 Randy Dunlap 的著作中("Merge to Mainline" 第17页就有很好的介绍http://tinyurl.com/a2jdg
需要强调的是正真的非常规的开发组织形式, git 这种组织形式,意味着对于工作流程的约束,没有任何强迫性的原则。你不必从唯一一个远程版本库中导入(工作目录)。
项目领导人project lead的工作推介
在你自己的本地机器上准备好主版本库。你的所有工作都在这里完成。
准备一个能让大家访问的公共版本库。
如果其他人是通过默协议的方式http来导入版本库的那么你有必要保持这个 默协议的友好性。 git-init-db 之后,复制自标准模板库的 $GIT_DIR/hooks/post-update 将包含一个对 git-update-server-info 的调用,但是 post-update 默认是不能唤起它自身的。通过 chmod +x post-update 命令使能它。这样让 git-update-server-info 保证那些必要的文件是最新的。
将你的主版本库推入公共版本库。
git-repack 公共版本库。这将建立一个包含初始化提交对象集的打包作为项目的起始线,可能的话,执行一下 git-prune要是你的公共库是通过 pull 操作来从你打包过的版本库中导入的。
在你的主版本库中开展工作,这些工作可能是你自己的最项目的编辑,可能是你由 email 收到的一个补丁,也可能是你从这个项目的“子系统负责人” 的公共库中导入的工作等等。
你可以在任何你喜欢的时候重新打包你的这个私人的版本库。
将项目的进度推入公共库中,并给大家公布一下。
尽管一段时间以后,"git-repack" 公共库。并回到第5步继续工作。
项目的子系统负责人subsystem maintainer也有自己的公共库工作流程大致如下
准被一个你自己的工作目录,它通过 git-clone 克隆自项目领导人的公共库。原始的克隆地址URL将被保存在 .git/remotes/origin 中。
准备一个可以给大家访问的公共库,就像项目领导人所做的那样。
复制项目领导人的公共库中的打包文件到你的公共库中,除非你的公共库和项目领导人的公共库是在同一部主机上。以后你就可以通过 objects/info/alternates 文件的指向来浏览它所指向的版本库了。
将你的主版本库推入你的公共版本库,并运行 git-repack如果你的公共库是通过的公共库是通过 pull 来导入的数据的话,再执行一下 git-prune 。
在你的主版本库中开展工作。这些工作可能包括你自己的编辑,来自 email 的补丁,从项目领导人,“下一级子项目负责人”的公共库哪里导入的工作等等。
你可以在任何时候重新打包你的私人版本库。
将你的变更推入公共库中,并且请“项目领导人”和“下级子系统负责人”导入这些变更。
每隔一段时间之后git-repack 公共库。回到第 5 步继续工作。
“一般开发人员”无须自己的公共库,大致的工作方式是:
准备你的工作库,它应该用 git-clone 克隆自“项目领导人”的公共库如果你只是开发子项目那么就克隆“子项目负责人”的。克隆的源地址URL会被保存到 .git/remotes/origin 中。
在你的个人版本库中的 master 分支中开展工作。
每隔一段时间,向上游的版本库运行一下 git-fetch origin 。这样只会做 git-pull 一半的操作,即只克隆不合并。公共版本库的新的头就会被保存到 .git/refs/heads/origins 。
用 git-cherry origin 命令,看一下你有什么补丁被接纳了。并用 git-rebase origin 命令将你以往的变更迁移到最新的上游版本库的状态中。(关于 git-rebase 命令,请参考 git-rebase
用 git-format-patch origin 生成 email 形式的补丁并发给上游的维护者。回到第二步接着工作。

View File

@@ -0,0 +1,59 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-08-27T16:50:24+08:00
====== Branches, HEAD, and fast-forwards ======
Created Saturday 27 August 2011
http://jagregory.com/writings/gits-guts-branches-head-and-fast-forwards/
November 25, 2009
Lets get some learning done. There are a few questions that keep cropping up when I introduce people to Git, so I thought Id post some answers as a mini-series of blog posts. Ill cover some fundamentals, while trying not to retread too much ground that the fantastic Git community book already covers so well. Instead Im going to talk about things that should help you understand what you and Git are doing day-to-day.
===== Whats a branch? =====
I know what youre thinking. “Cmon, we know what a branch is”. A branch is a copy of a source tree, thats maintained separately from its parent; thats what we perceive a branch to be, and thats how were used to dealing with them. Sometimes theyre physical copies (VSS and TFS), other times theyre lightweight copies (SVN), but theyre copies non-the-less. Or are they?
Lets look at it a different way. //The Git way.//
Git works a little differently than most other version control systems. It doesnt store changes using **delta encoding**, where complete files are built up by stacking differences contained in each commit. Instead, in Git each commit stores __a snapshot of how the repository looked__ when the commit occurred; a commit also contains a bit of **meta-data**, author, date, but more importantly **a reference to the parent** of the commit (the previous commit, usually).
Thats a bit weird, I know, but bare with me.
So what is a branch? __Nothing more than a pointer to a commit (with a name)__. Theres nothing physical about it, nothing is created, moved, copied, nothing. A branch contains no history, and has no idea of what it consists of beyond the reference to a single commit.
Given a stack of commits:
{{./GitGuts1_Figure1.png}}
The branch references the __newest __commit. If you were to make another commit in this branch, the branchs reference would be updated to point at the new commit.
{{./GitGuts1_Figure2.png}}
The history is built up by recursing over the commits through eachs parent.
===== Whats HEAD? =====
Now that you know what a branch is, this one is easy. __HEAD is a reference to the latest commit in the branch youre in__.
Given these two branches:
{{./GitGuts1_Figure3.png}}
If you had **master checked out**, HEAD would reference e34fa33, the exact same commit that the master branch itself references. If you had **feature checked out**, HEAD would reference dde3e1. With that in mind, as **both HEAD and a branch is just a reference to a commit**, it is sometimes said that HEAD points to the current branch youre on; while this is not strictly true, in most circumstances its close enough.
===== Whats a fast-forward? =====
__A fast-forward is what Git does when you merge or rebase against a branch that is simply ahead the one you have checked-out__.
Given the following branch setup:
{{./GitGuts1_Figure4.png}}
Youve got both branches referencing the same commit. Theyve both got exactly the same history. Now commit something to feature.
{{./GitGuts1_Figure5.png}}
The master branch is still referencing 7ddac6c, while feature has__ advanced__ by two commits. The feature branch can now be considered__ ahead of master__.
Its now quite easy to see whatll happen when Git does a fast-forward. It simply updates the master branch to reference the same commit that feature does. No changes are made to the repository itself, as the commits from feature already contain all the necessary changes.
Your repository history would now look like this:
{{./GitGuts1_Figure6.png}}
===== When doesnt a fast-forward happen? =====
Fast-forwards dont happen in situations where changes have been made in the original branch and the new branch.
{{./GitGuts1_Figure7.png}}
If you were to merge or rebase feature onto master, Git would be unable to do a fast-forward because __the trees have both diverged__. Considering Git commits are immutable, theres no way for Git to get the commits from feature into master __without changing their parent references.__
For more info on all this object malarky, Id recommend reading the Git community book.

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1,89 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-08-27T17:19:12+08:00
====== Merging and rebasing ======
Created Saturday 27 August 2011
Here we go again, explaining the internals of Git with the intention of helping you understand what youre doing day-to-day. Last time I covered branches, HEAD, and fast-forwarding. Today well dive into the guts of merging and rebasing.
===== Merging branches =====
Youve probably merged before. You do it when you want the changes from one branch in another. The principal is the same in Git as it is most other source control systems, but the implementation differs.
Given the following commit structure, consisting of two branches created__ from the same commit,__ each with two commits after the branching occurred.
{{./GitGuts2_Figure1.png}}
When these two branches are merged together, this is the structure that results:
{{./GitGuts2_Figure2.png}}
The top-most commit, the red one, is a new commit made by the merge; the __merge commit __is what reminds Git that **a merge occurred** next time its showing the history. This commit is special, as it contains __multiple parent__s in its meta-data; these multiple parents allow Git to follow the two trees of commits that constituted the branches that were merged.
One difference in how Git handles merges compared to many other SCMs is that it __preserves the commits__ that were made in both branches. In other systems merges are often represented as a single commit containing the squashed contents of all the commits that were made in the branch being merged in. Git doesnt do this (by default, you can tell it to if you want), and therefore preserves all the commits just as they were made; this is quite nice, as it proves incredibly useful to be able to __track the origin of changes__ beyond the point of a merge.
When you merge two branches, its interesting to know that__ none of the commits are altered in the process.__ Just bare this in mind for now, Ill explain why this is good to know later.
After a merge, if you were to view the history, youd see it shown like the previous example, commits in __chronological order__; the feature branch commits are__ interspersed__ between the master commits.
{{./GitGuts2_Figure2.png}}
Yet __no commits have been altered__ in the merge, so how are the commits in a different order? Well, theyre not, Gits just showing you it in the order you expect it to be in. Internally the structure is still as below:
{{./GitGuts2_Figure3.png}}
The merge commit instructs Git to __walk the two trees__ while building the history, and it just displays the results in chronological order. This makes more sense if you recall that Git commits dont hold differences like other SCM systems, instead they each contain a snapshot of the complete repository; while in another SCM the ordering of commits is vital — otherwise the diffs wouldnt build a valid file — Git is able to infer order without affecting the repository contents.
Looking at it in commit order, you can quite easily see how Git __flattens the history__ to be perceived as linear without ever having to touch any of the original commits.
===== What happens if theres a merge conflict? =====
Weve all dealt with conflicts in merging before. They typically happen when __changes are made to the same file in two branches__, in a way that cannot be easily merged (two people edit the same line, for example).
Gits commits are immutable though, so how are the changes that you need to make to resolve these conflicts saved? Simple. The merge commit is a regular commit with some extra meta-data, and so it capable of containing changes itself; __merge conflict changes are stored in the merge commit__. Again, no changes necessary to the original commits.
===== Git objects, immutability, and rewriting history =====
A Git repository is comprised of objects.
* A file is a blob object with a name attached to it; if you have two files with the same content, thats just two names to a single blob.
* A directory is a tree object, which is comprised of other trees and blobs.
* A commit is an object that __references a tree object__, which is the __state of the repository__ at the time of committing.
To read more about git objects, Id definitely recommend you read the Git community book.
Git objects are__ immutable__. To change an object after its been created is impossible, you have to __recreate the object__ with any changes made. Even operations that seem to modify objects actually dont; commit --amend is a typical example, that deletes and re-creates the commit rather than actually amending it.
I mentioned that merges dont rewrite history, and that its a good thing. Now Ill explain why. When you rewrite history, you do so by making changes to commits that __ripple up__ the commit tree; when this happens, it can cause complications when others merge from you. Given a series of commits, like so:
{{./GitGuts2_Figure4.png}}
You then share these commits with another user.
{{./GitGuts2_Figure5.png}}
John now has Michaels commits in his repository; however, Michael notices hes made a typo in the first commit message, so he amends the commit message. The change in the message requires the commit be recreated. With that first commit recreated, the second commit now has an invalid parent reference, so that __commit has to be recreated with the new reference__; this recreation ripples its way up the tree, recreating each commit with a new parent. Michael has completely rewritten his trees history.
Notice all the commit hashes have changed in Michaels repository, and Johns now dont match. If Michael was then to make a new commit to his repository, and John tried to merge that change into his repository, Git would get very upset because the new commit would reference a commit that doesnt exist in Johns repository.
**The golden rule is**: rewriting history is fine as long as the commits that will be affected havent been made public.
===== Rebasing =====
The purpose of a rebase is the same as a merge, to bring two trees of commits together. It differs in its approach. Rebasing is a seriously sharp tool. Very powerful, but pretty easy to cut yourself with it.
When you rebase one branch onto another, Git undoes any changes youve made in the__ target branch__, brings it up to date with the changes made in the source branch, then replays your commits on top. This sounds quite strange, so Ill go over it step-by-step.
You start with your diverged branches:
{{./GitGuts2_Figure7.png}}
If you then rebase __feature onto master__, Git undoes the changes in master.
{{./GitGuts2_Figure8.png}}
The history of both branches is now the same, __master has been updated to reflect feature__; the new commits that were made in master are now detached, floating in the repository without anything referencing them.
The next step is to __replay the master commits onto the new structure__. This is done one-by-one, and can sometimes result in conflicts that will need to be handled like any merge.
After replaying the repository will look like this:
{{./GitGuts2_Figure9.png}}
The master branch commits are now on the top of the stack, after the commits from the feature branch.
You should recall that commits are immutable, and for changes to be made commits need to be recreated. A rebase is a destructive operation, as it has to rewrite commits to be able to work. In this case, the commits from feature have been __unaffected__, but the master commits have been assigned __new parents__ (and thus rewritten). Whats also noticeable is theres a lack of a merge commit, which isnt needed because the commits have been integrated into the tree; any conflicts are stored in the __amended commits__, rather than in a merge commit.
The rewriting of commits in a rebase is what makes it a dangerous operation to perform on any branch that has already been pushed to the public (or specifically, that the changes affected by the rebase have already been pushed to the public). A rebase can cause problems __upstream__, like mentioned in the previous section.
Rebase has its place though. If youre working locally and havent yet pushed your changes public, it can be a useful tool. Rebase can be used to pull in changes from upstream in the order that the upstream repository has them, and your local changes (that can be rewritten because youre the only one with them) can be replayed on-top; this is a really easy way to keep your repository up-to-date with an authoritative source. You can also use Rebase to manage local branches that you dont necessarily want polluting the history with merge markers.
===== When to rebase and when to merge? =====
Merge when youve already made changes public, and when you want to indicate that two trees have converged. Rebase pretty much any other time.
Thats it for this time. Same deal as last time, if you have anything youd like me to cover Ill nail it in the next one.

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,70 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-08-27T22:00:21+08:00
====== Remotes, contributions, and the letter N ======
Created Saturday 27 August 2011
Heres a few ways to think about Git and its distributed nature.
* You deal with multiples of repositories, not a single central repository
* Updates come from a remote repository, and changes are pushed to a remote; none of these repositories have to be the same
* **Origin** is the canonical name for the repository you cloned from
* **Upstream** is the canonical name for the original project repository you forked from
===== General pushing and pulling =====
Pushing your changes to a remote: **git push remote_name**
{{./remote-1.png}}
Pulling changes from a remote: **git pull remote_name**
{{./remote-2.png}}
Or if you want to rebase:
**git fetch remote_name**
**git rebase remote_name/branch**
You can change your branch.autosetuprebase to always, to make this the default git pull behaviour.
Thats all there is to moving commits around in Git repositories. Any other operations you perform are all combinations of the above.
Github — personal repositories
When youre dealing directly with Github, on a personal project or as the project owner, your repositories will look like this:
{{./remote-3.png}}
To push and pull changes between your local and your github repositories, just issue the push and pull commands with the origin remote:
**git push origin**
**git pull origin**
You can set the defaults for these commands too, so the origin isnt even necessary in a lot of cases.
===== Github — receiving contributions =====
As a project owner, youll sometimes have to deal with contributions from other people. Each contributor will have their own github repository, and theyll issue you with a** pull request.**
{{./remote-4.png}}
Theres __no direct link __to push between these two repositories; theyre unmanned. To manage changes from contributors, you need to involve your __local__ repository.
You can think of this as taking the shape of a V.
{{./remote-5.png}}
You need to __register __their github repository as a remote on your local, pull in their changes, merge them, and push them up to your github. This can be done as follows:
**git remote add contributor contributor_repository.git**
**git pull contributor branch**
**git push**
===== Github — providing contributions =====
Do exactly as you would your own personal project. Local changes, pushed up to** your github **fork; then issue a pull request. Thats all there is to it.
===== Github — the big picture =====
Heres how to imagine the whole process, think of it as an N shape.
{{./remote-6.png}}
On the left is the contributor, and the right is the project. Flow goes from bottom left, along the lines to the top right.
* Contributor makes a commit in their local repository
* Contributor pushes that commit to their github
* Contributor issues a** pull request **to the project
* Project lead pulls the contributors change into their local repository
* Project lead pushes the change up to the project github
Thats as complicated as it gets.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@@ -0,0 +1,96 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-08-22T23:15:11+08:00
====== Git使用基础篇 ======
Created Monday 22 August 2011
Git是一个分布式的版本控制工具本篇文章从介绍Git开始重点在于介绍Git的基本命令和使用技巧让你尝试使用Git的同时体验到原来一个版本控制工具可以对开发产生如此之多的影响文章分为两部分第一部分介绍Git的一些常用命令其中穿插介绍Git的基本概念和原理第二篇重点介绍 Git的使用技巧最后会在Git Hub上创建一个开源项目开启你的Git实战之旅
  Git是什么
  Git在Wikipedia上的定义它是一个免费的、分布式的版本控制工具或是一个强调了速度快的源代码管理工具。Git最初被Linus Torvalds开发出来用于管理Linux内核的开发。每一个Git的工作目录都是一个完全独立的代码库并拥有完整的历史记录和版本追踪能力不依赖于网络和中心服务器。
  Git的出现减轻了许多开发者和开源项目对于管理分支代码的压力由于对分支的良好控制更鼓励开发者对自己感兴趣的项目做出贡献。其实许多开源项目包括Linux kernel, Samba, X.org Server, Ruby on Rails都已经过渡到使用Git作为自己的版本控制工具。对于我们这些喜欢写代码的开发者嘛有两点最大的好处我们可以在任何地点(在上班的地铁上)提交自己的代码和查看代码版本;我们可以开许许多多个分支来实践我们的想法,而合并这些分支的开销几乎可以忽略不计。
  Git 1+1
  现在进入本篇文章真正的主题介绍一下Git的基本命令和操作会从Git的版本库的初始化基本操作和独有的常用命令三部分着手让大家能够开始使用Git。
  Git通常有两种方式来进行初始化:
  git clone: 这是较为简单的一种初始化方式当你已经有一个远程的Git版本库只需要在本地克隆一份例如'git clone git://github.com/someone/some_project.git some_project'命令就是将'git://github.com/someone/some_project.git'这个URL地址的远程版本库完全克隆到本地some_project目录下面
  git init和git remote这种方式稍微复杂一些当你本地创建了一个工作目录你可以进入这个目录使用'git init'命令进行初始化Git以后就会对该目录下的文件进行版本控制这时候如果你需要将它放到远程服务器上可以在远程服务器上创建一个目录并把可访问的URL记录下来此时你就可以利用'git remote add'命令来增加一个远程服务器端,例如'git remote add origin git://github.com/someone/another_project.git'这条命令就会增加URL地址为'git: //github.com/someone/another_project.git'名称为origin的远程服务器以后提交代码的时候只需要使用 origin别名即可
  现在我们有了本地和远程的版本库让我们来试着用用Git的基本命令吧
  git pull从其他的版本库(既可以是远程的也可以是本地的)将代码更新到本地,例如:'git pull origin master'就是将origin这个版本库的代码更新到本地的master主枝该功能类似于SVN的update
  git add是将当前更改或者新增的文件加入到Git的索引中加入到Git的索引中就表示记入了版本历史中这也是提交之前所需要执行的一步例如'git add app/model/user.rb'就会增加app/model/user.rb文件到Git的索引中
  git rm从当前的工作空间中和索引中删除文件例如'git rm app/model/user.rb'
  git commit提交当前工作空间的修改内容类似于SVN的commit命令例如'git commit -m "story #3, add user model"',提交的时候必须用-m来输入一条提交信息
  git push将本地commit的代码更新到远程版本库中例如'git push origin'就会将本地的代码更新到名为orgin的远程版本库中
  git log查看历史日志
  git revert还原一个版本的修改必须提供一个具体的Git版本号例如'git revert bbaf6fb5060b4875b18ff9ff637ce118256d6f20'Git的版本号都是生成的一个哈希值
  上面的命令几乎都是每个版本控制工具所公有的下面就开始尝试一下Git独有的一些命令
  git branch对分支的增、删、查等操作例如'git branch new_branch'会从当前的工作版本创建一个叫做new_branch的新分支'git branch -D new_branch'就会强制删除叫做new_branch的分支'git branch'就会列出本地所有的分支
  git checkoutGit的checkout有两个作用其一是在不同的branch之间进行切换例如'git checkout new_branch'就会切换到new_branch的分支上去;另一个功能是还原代码的作用,例如'git checkout app/model/user.rb'就会将user.rb文件从上一个已提交的版本中更新回来未提交的内容全部会回滚
  git rebase用下面两幅图解释会比较清楚一些rebase命令执行后实际上是将分支点从C移到了G这样分支也就具有了从C到G的功能
  
Git使用基础篇图一
  git reset将当前的工作目录完全回滚到指定的版本号假设如下图我们有A-G五次提交的版本其中C的版本号是 bbaf6fb5060b4875b18ff9ff637ce118256d6f20我们执行了'git reset bbaf6fb5060b4875b18ff9ff637ce118256d6f20'那么结果就只剩下了A-C三个提交的版本
  
Git使用基础篇图二
  git stash将当前未提交的工作存入Git工作栈中时机成熟的时候再应用回来这里暂时提一下这个命令的用法后面在技巧篇会重点讲解
  git config利用这个命令可以新增、更改Git的各种设置例如'git config branch.master.remote origin'就将master的远程版本库设置为别名叫做origin版本库后面在技巧篇会利用这个命令个性化设置你的Git为你打造独一无二的 Git
  git tag可以将某个具体的版本打上一个标签这样你就不需要记忆复杂的版本号哈希值了例如你可以使用'git tag revert_version bbaf6fb5060b4875b18ff9ff637ce118256d6f20'来标记这个被你还原的版本,那么以后你想查看该版本时,就可以使用 revert_version标签名而不是哈希值了
  Git之所以能够提供方便的本地分支等特性是与它的文件存储机制有关的。Git存储版本控制信息时使用它自己定义的一套文件系统存储机制在代码根目录下有一个.git文件夹会有如下这样的目录结构
  
Git使用基础篇图三
  有几个比较重要的文件和目录需要解释一下HEAD文件存放根节点的信息其实目录结构就表示一个树型结构Git采用这种树形结构来存储版本信息那么HEAD就表示根;refs目录存储了你在当前版本控制目录下的各种不同引用(引用指的是你本地和远程所用到的各个树分支的信息),它有 heads、remotes、stash、tags四个子目录分别存储对不同的根、远程版本库、Git栈和标签的四种引用你可以通过命令'git show-ref'更清晰地查看引用信息;logs目录根据不同的引用存储了日志信息。因此Git只需要代码根目录下的这一个.git目录就可以记录完整的版本控制信息而不是像SVN那样根目录和子目录下都有.svn目录。那么下面就来看一下Git与SVN的区别吧
  Git与SVN的不同
  SVN(Subversion)是当前使用最多的版本控制工具。与它相比较Git最大的优势在于两点易于本地增加分支和分布式的特性。
  下面两幅图可以形象的展示Git与SVN的不同之处
  
Git使用基础篇图四
  
Git使用基础篇图五
  对于易于本地增加分支图中Git本地和服务器端结构都很灵活所有版本都存储在一个目录中你只需要进行分支的切换即可达到在某个分支工作的效果。而SVN则完全不同如果你需要在本地试验一些自己的代码只能本地维护多个不同的拷贝每个拷贝对应一个SVN服务器地址。举一个实际的例子以前我所在的小组使用SVN作为版本控制工具当我正在试图增强一个模块工作做到一半由于会改变原模块的行为导致代码服务器上许多测试的失败所以并没有提交代码。这时候上级对我说现在有一个很紧急的Bug需要处理必须在两个小时内完成。我只好将本地的所有修改diff并输出成为一个patch文件然后回滚有关当前任务的所有代码再开始修改Bug的任务等到修改好后在将patch应用回来。前前后后要完成多个繁琐的步骤这还不计中间代码发生冲突所要进行的工作量。可是如果使用Git我们只需要开一个分支或者转回到主分支上就可以随时开始Bug修改的任务完成之后只要切换到原来的分支就可以优雅的继续以前的任务。只要你愿意每一个新的任务都可以开一个分支完成后再将它合并到主分支上轻松而优雅。
  分布式对于Git而言你可以本地提交代码所以在上面的图中Git有利于将一个大任务分解进行本地的多次提交而SVN只能在本地进行大量的一次性更改导致将来合并到主干上造成巨大的风险。Git的代码日志是在本地的可以随时查看。SVN的日志在服务器上的每次查看日志需要先从服务器上下载下来。我工作的小组代码服务器在美国每次查看小组几年前所做的工作时日志下载就需要十分钟这不能不说是一个痛苦。后来我们迁移到Git 上利用Git日志在本地的特性我用Ruby编写了一个Rake脚本可以查看某个具体任务的所有代码历史每次只需要几秒钟大大方便我的工作。当然分布式并不是说用了Git就不需要一个代码中心服务器如果你工作在一个团队里还是需要一个服务器来保存所有的代码的。

View File

@@ -0,0 +1,77 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-08-22T23:16:08+08:00
====== Git使用技巧篇 ======
Created Monday 22 August 2011
上一篇介绍了Git的基本概念和一些基本命令本篇的重点在如下三个部分个性化定制你的Git更酷更巧妙的使用Git以及如何在Git Hub上开启你自己的开源项目。在所有技巧中最重要的技巧是学会查看Git的帮助因为Git是一个相对复杂的版本控制工具如果你熟悉它的命令那么给你带来的价值是不言而喻的所以要学会掌握那根金手指─查看Git的帮助在任何Git命令后加上'--help'就会显示该命令的帮助文档,例如 'git log --help'你就可以看到命令'git log'的所有使用方法。接下来从打扮Git开始吧。
  Git梳妆
  我们可以利用Git的config命令或者直接编辑~/.gitconfig文件(如果没有的话创建它)来为自己打造独一无二的Git。我建议直接编辑用户目录下得.gitconfig文件拿我本地的文件为例一一为大家解说完整的文件内容如下
  [user]
  name = Phoenix
  email = phoenixtoday@gmail.com
  [alias]
  co = checkout
  ci = commit -a
  st = status
  br = branch
  oneline = log --pretty=oneline --since='2 days ago'
  onelog = log -p -1
  [color]
  status = auto
  branch = auto
  ui = auto
  该文件主要包含三个部分的内容:
  用户基本信息可以设置你的名字和email这样在你提交代码的时候就会显示出你的名字
  命令别名:这是.gitconfig文件中我最喜欢的部分它可以大大减少你敲击键盘的次数(俗话说优秀的程序员都很懒么)。在该文件中我将co设置为checkout的别名那么下次我只要用'git co new_branch'就可以切换到new_branch分支下了简洁而优雅;将ci设置为commit -a的别名-a选项表示我不需要将修改和删除的文件通过'git add'命令来加入索引,这样设置在使用'git ci -m"message"'这样的命令时,相当于连续执行了'git add 被修改和删除的文件'和'git commit -m"message"'两条命令,再一次节省了我们宝贵的时间;最酷的是最后两行后面的章节会一一介绍。Git提供许多优雅、人性化的选项我们如果再结合别名的设置可以发挥你最大的想象力真的让你自己的Git活起来
  颜色每次看diff时是不是挺痛苦的?那么为什么不给我们的Git加上颜色呢?只需要加上那三行,就可以让红色和绿色的提示出现在你的控制台中
  Git灵动
  现在来讲讲'git log''git stash''git formate-patch'三个命令的用法和技巧:
  git log不同于SVNGit将代码的历史记录全部在本地克隆了一份所以这就使得'git log'这样的命令使用起来非常的迅速也是我最常使用的Git命令之一。在使用'git log'的时候,你可以加入很多的后缀。'-p'表示查看修改的具体内容,例如'git log -p'它不但会打印出提交的时间、版本号、人员等,还会将具体的代码修改部分打印出来;'-n'其中n表示一个数字这表示打印出具体的几个日志例如 'git -p -1'正如我的Git配置文件中设置的onelog别名的内容一样就表示打印出当前最新的一次日志记录及具体修改内容;'--since="时间/日期"''--until="时间/日期"'表示你希望查找某个日期段的日志记录,例如'git log --since="2 days ago" --until="1 hour ago"'就表示你希望查找两天前到一小时前的日志记录Git是足够聪明的它可以将类似于'2 days ago'和'1 hour ago'这种表示时间的英语转化为具体的时间数字;有的时候你不希望翻很多页才可以看到所有的日志你只希望看到简短的说明那么Git为你提供打印格式的定制'git --pretty=格式种类'其中格式种类有full、short、oneline等例如'git log pretty=oneline'就会将每条代码历史记录放在一行里,看起来简单明了
  git stash在第一篇中我举了一个使用branch解决紧急任务切换的问题其实stash命令也可以很好的解决这样的问题。当你不想提交当前完成了一半的代码但是却不得不修改一个紧急Bug那么使用'git stash'就可以将你当前未提交到本地(和服务器)的代码推入到Git的栈中这时候你的工作区间和上一次提交的内容是完全一样的所以你可以放心的修 Bug等到修完Bug提交到服务器上后再使用'git stash apply'将以前一半的工作应用回来。也许有的人会说,那我可不可以多次将未提交的代码压入到栈中?答案是可以的。当你多次使用'git stash'命令后,你的栈里将充满了未提交的代码,这时候你会对将哪个版本应用回来有些困惑,'git stash list'命令可以将当前的Git栈信息打印出来你只需要将找到对应的版本号例如使用'git stash apply stash@{1}'就可以将你指定版本号为stash@{1}的工作取出来,当你将所有的栈都应用回来的时候,可以使用'git stash clear'来将栈清空
  git format-patch当你想给一个开源项目(例如Rails)提交一段代码的时候,或者你想给小组成员展示一段你并不想提交的代码,那么你还是需要 patch的Git的'format-patch'命令良好的支持了这个功能。我来基本描述一下使用这个命令的步骤和方法第一利用branch命令创建一个分支;第二,修改你的代码;第三,在该分支上提交你的修改;第四,使用'git format-patch'命令来生成一个patch文件例如'git format-patch master --stdout > ~/Desktop/tmp.patch'就是将工作分支与master主干的不同存放在'~/Desktop'文件夹下,生成一个叫做 tmp.patch的文件(另一种简单的版本是利用diff命令例如'git diff ..master > ~/Desktop/tmp.patch')这样就生成了patch文件。那么别人就可以使用'git apply'命令来应用patch例如'git apply ~/Desktop/tmp.patch'就是将patch打在当前的工作分支上
  Git亲友团
  Git的使用技巧还包括利用Git包含的和附加的一些强大工具这些工具主要包括git svn、git citool、gitk和Git的自动提示脚本
  git svnGit和SVN可以很方便的集成在一起这就大大减少了从SVN向Git迁移的学习成本这也是我特别建议大家首次接触Git的使用方式。git svn是一个Git内置的工具你安装了Git也就安装了它譬如说你们团队有一个SVN服务器但是你想利用Git本地的一些强大特性那么你依然可以安装Git使用Git的branch功能只不过再更新代码和提交代码的时候使用git svn命令即可。在这里我简单的讲讲最常使用和需要注意的两个命令其余的命令读者可以通过'git svn --help'来查看:'git svn rebase'命令取代了'svn update'用于将服务器代码更新到本地;'git svn dcommit'取代了'svn ci'需要注意的是本地必须用Git提交了代码之后再使用'git svn dcommit'。只需要这样你就可以轻松地从SVN转向Git了。
  git citool这是我个人使用率最频繁的一个工具上一篇文章也提到了Git可以本地提交代码那么你自然可以本地修改你的提交了这个工具就是可视化界面用于修改你本地的提交。只要在你的工作区间输入'git citool',就会出现如下的界面
  
Git使用技巧篇图一
  你可以用它来提交代码可以用它来将你本地的修改追加在上一次提交的代码中你还可以用它来修改你上次提交的信息等等。这个工具可以大大帮助你完成以前SVN不可能完成的任务
  gitk是一个查看主干/分支情况的工具,它主要用于观察整个项目的分支状况,使用'gitk'命令就会出现一个图形化界面供你查看,本篇就简单的说一下,大家回去试试就知道了
  Git 的自动提示脚本它是Shawn O. Pearce为了让Git使用起来更方便而写得Shell脚本你可以在http://gitweb.hawaga.org.uk/ 找到一个叫做gitcompletion的脚本下载下来并按照该脚本中指导的方式进行配置你就具有了Git自动提示(敲入部分Git命令再按 Tab键)的功能而且有了这个脚本你也可以看到你当前工作在哪个branch下。惟一的不足是它只支持Linux、Unix、Mac操作系统(推荐大家都用Mac进行开发)

View File

@@ -0,0 +1,41 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-05-16T21:56:22+08:00
====== Synchronizing Git repositories without a server ======
Created Monday 16 May 2011
See this blog post "Synchronizing Git repositories without a server " (by Victor Costan).
This post describes a method for pushing changes between two repositories without using a server with network connections to both hosts having repositories
Start up by creating a repository on the USB stick.
mkdir /path/to/usb/stick/repository.git
git clone --local --bare . /path/to/usb/stick/repository.git
Then register the repository on the USB stick as a remote repository, and push the desired branch to it (if you don't want to push master, substitute your desired branch).
git remote add usb file:///path/to/usb/stick/repository.git
git push usb master
In the future, you can treat the USB repository as any other remote repository. Just make sure it's mounted :) For instance, the following pushes new changes to the USB repository.
git push usb
On the receiving end, mount the USB stick, and use a file URL for the repository
file:///path/to/usb/stick/repository.git
A few handy commands:
# cloning the repository on the USB stick
git clone file:///path/to/usb/stick/repository.git
# updating a repository cloned from the USB stick using the above command
git pull origin
# adding the USB stick repository as a remote for an existing repository
git remote add usb file:///path/to/usb/stick/repository.git
# updating from a remote repository configured using the above command
git pull usb master

View File

@@ -0,0 +1,134 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2012-02-19T17:01:16+08:00
====== Whats The Deal With The Git Index ======
Created Sunday 19 February 2012
http://www.gitguys.com/topics/whats-the-deal-with-the-git-index/
Commands discussed in this section:
git add
git commit
git ls-files
git status
git mv
git rm
===== The git index defined in 2 sentences =====
* The git “index” is where you** place files **you want committed to the git repository.
* Before you “commit” (checkin) files to the git repository, you need to** first** place the files in the git “index”.
===== An index by any other name =====
The git index goes by many names. But they all refer to the same thing. Some of the names you may have heard:
* **Index**
* Cache
* Directory cache
* Current directory cache
* **Staging area**
* Staged files
Why so many names for the same thing? Good question! We dont cover linguistics questions in this section, but you can be sure the answer would start with __“For historical reasons…”__
===== The Index Isnt The Working Directory =====
The working directory is where you do your work: You add, remove, and modify files in the working directory.
You can type a command such as __git status__ and git will tell you:
* What files have been added to the git index (for example by using the git add filename command).
* What files git notices that exist in the working directory, but arent in the git index.
* What files have different contents between the version in the git index and the version in the working directory.
===== The Index Isnt The Git Repository =====
Files in the git index are files that git **would commit to the git repository** if you used the__ git commit__ command.
However, files in the git index dont get committed to the repository until you use the git commit command.
===== The Index: Compared with Legacy VCSs =====
Lets say you have just created a new file named README. How do you add the file to your repository?
In a legacy Version Control System (VCS):
You tell the VCS what file you want added to the repository (Perforce: p4 add README; Subversion: svn add README)
Then tell the VCS to save the file to the repository (Perforce: p4 submit; Subversion: svn commit …)
Git behaves differently. With git, to add a new file to the git repository, you:
* Use “git add README”. This causes git to:
Copy the contents of the README file to the **git object** store and store its contents as a **blob.**
Record in the** git index**: Filename README and the hash of the README blob in the object store.
Mark (remember) the README file in the users working directory is now **“tracked”.**
* Use “git commit” which takes the files that are referred to in the “git index” and saves them to the git repository.
===== Git Index Example =====
In the below example:
* A git session is shown in the upper right portion of the diagrams.
* The result of the session is shown in the lower right portion (words) and in the left portion (pictures).
* The most recent command typed is shown in a brown font
* The Session begins in a new, empty, directory immediately after a new git repository has been created (e.g. after “git init“).
You can click on the thumbnails in the lower left hand corner to choose which image to display.
{{./img0.png}}
{{./img1.png}}
{{./img2.png}}
{{./img3.png}}
{{./img4.png}}
{{./img5.png}}
{{./img6.png}}
{{./img7.png}}
{{./img8.png}}
You can click on the thumbnails in the lower left hand corner to choose which image to display.
===== A couple helpful index-related commands =====
A couple of git commands can be very helpful in showing you what files are in the index, whether or not the filenames in the index have been changed in the working directory, etc.
The git commands are:
git status: Show what git determines are differences between the working directory, index and the most recent commit. For example:
git ls-files: Show what files are in the git index
See Whats in the index? Whats Changed? for examples of the git status and git ls-files commands.
You can see examples of git diff which can show differences between the working directory, index, and repository.
Renaming and Removing files
To rename a file in the index and the working directory:
$ git mv README readme
$ git ls-files
readme
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD ..." to unstage)
#
# renamed: README -> readme
#
And to remove a file from the working directory and the index:
$ rm README
$ git rm README
Next: Whats In The Index? Whats Changed? git status and git ls-files
Previous: The Git Tag Object
Related:
git diff
The Git Tag Object
The Git Blob Object
The Git Tree Object
The Git Commit Object
All Git Object Types: Blob, Tree, Commit And Tag
What Is The Format Of A Git Blob?

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

View File

@@ -0,0 +1,130 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-08-21T22:40:33+08:00
====== all about bare repos ======
Created Sunday 21 August 2011
http://sitaramc.github.com/concepts/bare.html
all about "bare" repos -- what, why, and how to fix a non-bare push
what is a bare repo?
yeah yeah, but why do I need a bare repo?
how do I fix such a non-bare push?
how do I prevent the problem from happening again?
Update for git 1.7.0 and above
As of git 1.7.0, the default value for receive.denyCurrentBranch has changed from "warn" to "refuse". As a result, on 1.7.0 and above, by default, you will not get into this sort of trouble. However, this document is still useful as a detailed explanation of what precisely this problem is. Just pretend, as you read along, that you're on a pre-1.7.0 system or that someone set receive.denyCurrentBranch to "warn", "ignore" or 'false'.
what is a bare repo?
A bare repository is a concept that is sort of unique to a Distributed VCS like git (and, I presume, other such DVCSs like Hg/Bzr/etc also).
A normal git repository is a directory that contains
project directories and files (the "working tree" mentioned above)
a single directory called .git containing all of git's administrative and control files; we'll call it the magic directory because git can't do any magic without it :-)
When you do a git status inside such a directory, git looks inside the "magic" directory, compares your current working tree with the "current branch" as recorded in the magic directory, and tells you what files have changed, etc etc.
A "bare" repo, as the git glossary says, is a repository that does not contain a "working tree" at all. It doesn't contain the special .git sub-directory either; instead, it contains all the contents of the .git subdirectory right in the main directory itself.
yeah yeah, but why do I need a bare repo?
ok; demo time...
Let's try creating a small repo, adding a file, committing, and checking git status:
mkdir a; cd a; git init
echo hi > a;git add a; git commit -m a
git status
This should respond
# On branch master
# nothing to commit (working directory clean)
So far so good. Now someone clones our repository, adds a new file, commits, and pushes his changes back to our repository:
cd ..;git clone a b
cd b; echo there >> b; git add b; git commit -m b
git push
The git push above sends your new commits to the "origin" repository. More specifically, it updates the "magic" directory on repo "a" with this new commit.
Now you go back to the main repo and check git status
cd ../a
git status
which responds
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# deleted: b
Whoa! What happened here? We added a file called b in the cloned repository and pushed to the "origin". But your origin now claims you deleted that file...?
To understand this, you need to realise that the "magic" directory is always assumed to be correct; it is the "standard" against which your working tree is compared to determine what changes you made in your working tree.
So when you asked for a status, git first looked inside the magic directory. The magic directory said you should have two files, "a" and "b", but your work tree has only file "a". So git status concludes that you have deleted the file "b"!
In other words, when someone changes the "magic" directory behind your back, your locally checked out copy (your working tree) appears to have the opposite changes made by you.
All this confusion can (and should) be avoided by using a "bare" repository to clone and pull from, and push to. It doesn't have a checked out tree, so it just does what the "server" notionally does in a centralised VCS -- records commits, branches, etc when you push to it, and gives you the latest versions when you clone or pull from it.
how do I fix such a non-bare push?
If you did push into a non-bare repo, the first thing to do is make sure that no one is working on that checked out work tree, and no local changes have been made since the last checkout.
Once you've made sure of that, you should log onto that machine, cd to that repo, and do this:
git reset --hard HEAD
If you had local changes on that work tree, or you are not sure if you had any, then you have a bit of a challenge. There are ways to deal with it quite nicely in git, but I haven't tried them yet. Ideas:
see if any of the HEADs in reflog match your current work tree exactly, in which case you really do NOT have any local changes, and you can safely do that reset. Use git reflog show your-branch-name to find the topmost commit after the most recent push or set of pushes (in case there were more than one) and compare your work tree against that.
Let's say your reflog output looks like this:
$ git reflog show master
fcbb5a7 master@{0}: push
e41b4ce master@{1}: push
6ef3360 master@{2}: commit: blah blah more blah
a48b324 master@{3}: commit: blah blah blah
48e4b98 master@{4}: commit (initial): first commit
As you can see, there were two "behind your back" pushes, and it is very likely that master@{2} is the "base" for your current work tree. So you compare against that:
$ git diff --stat --exit-code master@{2}
If there are no differences, you're probably OK to do that git reset --hard. If you're really paranoid, you'll check to make sure that master@{2} is an ancestor of HEAD:
git rev-list HEAD..master@{2} # should be empty
anyway, if the diff above showed differences, you have a merge of some sort coming up. Here's what you do:
make a temporary branch off that same commit
git checkout -b temp master@{2}
now you have two alternatives:
The safe way: commit those changes. If the work is not complete, complete it -- you have all the time in the world now that you have created a temp branch, and any number of pushes can now happen on 'master' without affecting you, since it isn't the checked out branch.
The quicker way: stash those changes using git stash. I am 99% sure stashing will work as well here, but would appreciate hearing from any git.experts reading this (email me at sitaramc@gmail.com)
now checkout master and either merge temp or git stash pop depending on which option you chose above; it's just a normal branch now!
how do I prevent the problem from happening again?
The best option is upgrade to git 1.7.0 or later!
If you cannot upgrade, and if you or others will continue to push into this repo, you have to make it a bare repo.
To make it a "real" bare repo, just delete all the files except .git, then mv .git/* .; rmdir .git. Finally, edit the file called config and change bare = false to bare = true.
If you're not comfortable playing with git at that level, there is another alternative. You may have guessed by now that you can make a non-bare repo look like a bare repo for push purposes by creating and checking out a branch that your users will not push to. The entire repo does not have to be bare, really...
So just run git checkout -b DUMMY_BRANCH_DONT_USE (or some such name) and keep this branch checked out forever in this repo. This is a cheap way to make a non-bare repo into a bare repo for all practical purposes.

View File

@@ -0,0 +1,154 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-08-24T00:26:43+08:00
====== git使用文档 ======
Created Wednesday 24 August 2011
通过昨天的资料搜集、对比、安装、阅读文档和使用对git有了初步的了解这些内容在我的上一篇文章中都有就不在赘述了今天老大让我结合我们的项目和使用细节写一个文档给实验室的同学。一直工作在linux下就把文档先写在我的blog里吧
go……
Git安装以及使用Git 管理个人文档
1.1 Git 安装
Git的最新版本可以在http://git-scm.com/%E4%B8%8B%E8%BD%BD%EF%BC%8C%E5%AE%83%E6%98%AF%E5%9F%BA%E4%BA%8E%E5%91%BD%E4%BB%A4%E8%A1%8C%E6%93%8D%E4%BD%9C%E7%9A%84%EF%BC%8C%E7%BD%91%E4%B8%8A%E4%B9%9F%E6%9C%89%E7%AC%AC%E4%B8%89%E6%96%B9%E5%BC%80%E5%8F%91%E7%9A%84%E7%9B%B8%E5%BA%94GUI%E5%8F%AF%E4%BE%9B%E4%B8%8B%E8%BD%BD%EF%BC%8C%E5%9B%A0%E4%B8%BA%E6%88%91%E6%AF%94%E8%BE%83%E5%96%9C%E6%AC%A2%E5%91%BD%E4%BB%A4%E8%A1%8C%E6%93%8D%E4%BD%9C%EF%BC%8C%E6%89%80%E4%BB%A5%E6%B2%A1%E6%9C%89%E5%AF%B9GUi下载和安装有兴趣的同学可以自己试试。网上也有相应的文章和资料可供参考
安装之前首先确保相应的依赖包已经安装,主要有以下几个:
zlib
libcurl
libcryptoOpenSSL
rsync2.6.0 或更高版本)
这些条件满足之后就可以对Git进行安装了
1. tar -xzvf git-1.6.1.tar.gz
2. cd git-1.6.1
3. ./configure --prefix=/usr/local
4. make
5. make install
安装成功可以通过git --vertion 查看版本。
1.2 项目仓库的建立
欲使用Git 对现有文档进行版本控制,首先要基于现有文档建立项目仓库。创建一个 Git 项目仓库是很容易的,只要用命令 git-init-db 就可以了。
$ mkdir project
$ cd project
$ git-init-dbgit 将会作出以下的回应
defaulting to local storage area
或者Initialized empty Git repository in project/.git/
这样,一个空的版本库就创建好了,并在当前目录中创建一个叫 .git 的子目录。你可以用 ls -a 查看一下,并请注意其中的三项内容:
一个叫 HEAD 的文件,我们现在来查看一下它的内容:
$ cat .git/HEAD现在 HEAD 的内容应该是这样:
ref: refs/heads/master
我们可以看到HEAD 文件中的内容其实只是包含了一个索引信息,并且,这个索引将总是指向你的项目中的当前开发分支。
一个叫 objects 的子目录,它包含了你的项目中的所有对象,我们不必直接地了解到这些对象内容,我们应该关心是存放在这些对象中的项目的数据。
另外project目录也不再是普通的文档目录了今后我们将其称为工作树。因为我们主要是linux内核的开发所以下面我举的例子主要是对内核文件的操作所以project目录等同于源代码的根目录亦即linux-2.6-vertex。
下面应当有选择地将工作树中的一些文档存储至Git 仓库中。由于Git 在向仓库中添加文档时并非是简单地文档复制过去势必要将所添加文档进行一番处理生成Git 仓库所能接受的数据格式Git 称这个过程为"take a snapshot" 生成快照)。若将工作树下所有文档(包含子目录)生成快照,可采用以下命令:
$ cd project
$ git add .
所生成的快照被存放到一个临时的存储区域Git 称该区域为索引。使用git-commit 命令可将索引提交至仓库中,这个过程称为提交,每一次提交都意味着版本在进行一次更新。
$ git commit
执行上述git-commit 命令时Git 会自动调用系统默认的文本编辑器,要求你输入版本更新说明并保存。请记住,输入简约而又意义明确的版本更新说明是非常有必要的,可以帮助你快速回忆起对项目的重大改动。
对于简短的版本更新信息可以使用git-commit 的“-m”选项如下
$ git commit -m "你的版本更新信息"
git-commit 命令在后面会详细讲解。
上述过程即为建立Git 仓库的一般过程我将其总结为图1.1所示之流程:
图1.1 Git 仓库建立流程
另外在git 项目仓库建立中还要注意一下两个问题:
第一个问题是在使用Git 之前你需要面对Git 来一番自我介绍。Git 不喜欢不愿透漏姓名的人因为它要求每个人在向仓库提交数据时都应当承担一定的责任。要向Git 进行自我介绍,请使用以下命令:
$ git config --global user.name "Your Name Comes Here"
$ git config --global user.email you@yourdomain.example.com
第二个问题是在生成文档内容快照时工作树中有一些文档是你不希望接受Git 管理的,譬如程序编译时生成的中间文件,对于这样的文件如何避免为之生成快照?
譬如在工作树中存在以下子目录:
doc tmp ipc drivers fs
其中的tmp 目录存放着文档编译时生成的中间文件因此该目录不应该被Git 所管理。为解决此类问题Git 提供了文档忽略机制可以将工作树中你不希望接受Git 管理的文档信息写到同一目录下的.gitignore 文件中。对于本例中的tmp 目录采用如下操作可将其排除仓库之外然后再对project 生成快照即可。
$ cd project
$ echo "tmp" > .gitignore
$ git add .
有关gitignore 文件的诸多细节知识可阅读其使用手册:
$ man gitignore
1.3 项目仓库与工作树
按照前文的说法Git 仓库就是那个.git 目录其中存放的是我们所提交的文档索引内容Git 可基于文档索引内容对其所管理的文档进行内容追踪,从而实现文档的版本控制。工作树是包含.git 的目录在前文示例中即project 目录。
为了更加明确仓库与工作树的概念,下面做一个实验:
$ cp -R project/.git /tmp/test.git
$ cd /tmp
$ git clone test.git test-copy
首先我们将project 目录中的.git 目录复制到/tmp 目录下并进行重命名为test.git 然后使用git-clone 命令从test.git 中生成test-copy 目录。若进入test-copy 目录观察一下就会发现该目录所包含的内容是等同于project 目录的。
上述实验意味着只要我们拥有仓库即test.git 那么就可以很容易地生成工作树而这个工作树又包含着一个仓库即test-copy/.git 。所以我们可以这样理解在Git 中,仓库与工作树之间无需分的很清楚。
1.4 文件操作
在工作树中我们日常所进行的工作无非是对Git 仓库所管理的文档进行修改或者添加删除一些文件。这些操作与采用Git 管理我们的文档之前没有任何差异只是在你认为一个工作阶段完成之时要记得通知Git命令它记下你所进行更新这一步骤是通过生成文档快照并将其加入到索引中来实现的。下面举例说明。
譬如我向project 目录添加了一个新文件 fs/binfmt_hwt.c 我需要通知Git 记住我的这一更新:
$ cd project
$ git add fs/binfmt_hwt.c
这样Git 就会将有关fs/binfmt_hwt.c 的更新添加到索引中。然后我又对其它文档进行了一些修改譬如修改了ipc/msg.c 继续使用git-add 命令将它们的更新添加到索引中:
$ git add ipc/msg.c
这里也可以使用以下命令:
$ git-update-index
晚上这一天的工作告以段落我觉得有必要将今天所做的提交到仓库中于是执行git-commit 操作,将索引内容添加到仓库中。
可能一天下来,你对工作树中的许多文档都进行了更新(文档添加、修改、删除),但是我忘记了它们的名字,此时若将所做的全部更新添加到索引中,比较轻省的做法就是:
1 工作树克隆命令,在后文中将会对其详细讲述。
$ cd project
$ git add .
$ git commit -a
... 输入日志信息...
最后这一步-a是通用的方法我个人比较喜欢使用
git-commit m “版本信息” a ,这样就不用对版本文件操作了。
git-add 命令通常能够判断出当前目录包括其子目录下用户所添加的新文档并将其信息追加到索引中。git-commit 命令的-a 选项可将所有被修改的文档或者已删除的文档的当前状态提交倒仓库中。记住如果只是修改或者删除了已被Git 管理的文档是没必要使用git-add 命令的。
本节并未讲述新的Git 命令完全是前面所讲过的一些命令的重复介绍只是它们出现的场景有所区别而已。另外要注意的问题是Git 不会主动记录你对文档进行的更新,除非你对它发号施令。
1.5 查看版本历史
在工作树中使用git-log 命令可以查看当前项目的日志也就是你在使用git-commit 向仓库提交新版本时所属如的版本更新信息。
$ git log
如果你想看一下每一次版本的大致变动情况,可使用以下命令:
$ git log --stat --summary
下面分析一下git-log 命令的回应信息。如下是我对内核修改后提交的几个版本版本标记分别为first、second、third ,最下面的那个为原始版本。
每一个版本都对应着一次项目版本更新提交。在项目日志信息中每条日志的首行就是那一串莫名奇妙的数字为版本更新提交所进行的命名我们可以将该命名理解为项目版本号。项目版本号应该是唯一的默认由Git 自动生成用以标示项目的某一次更新。如果我们将项目版本号用作git-show 命令的参数,即可查看该次项目版本的更新细节:
$ git show 版本号(比较长我就不输了)
除了使用完整的版本号查看项目版本更新细节之外,也还可以使用以下方式:
$ git show ddea091 # 一般只使用版本号的前几个字符即可
$ git show HEAD # 显示当前分支的最新版本的更新细节
每一个项目版本号通常都对应存在一个父版本号,也就是项目的前一次版本状态。可使用如下命令查看当前项目版本的父版本更新细节:
$ git show HEAD^ # 查看HEAD 的父版本更新细节
$ git show HEAD^^ # 查看HEAD 的祖父版本更新细节
$ git show HEAD~4 # 查看HEAD 的祖父之祖父的版本更新细节
1.6 撤销与恢复
版本控制系统的一个重要任务就是提供撤销和恢复某一阶段工作的功能。
git-reset 命令就是为这样的任务而准备的,它可以将项目当前版本定位到之前提交的任何版本中。
git-reset 命令有三个选项:--mixed 、--soft 和--hard 。我们在日常使用中仅使用前两个选项;第三个选项由于杀伤力太大,容易损坏项目仓库,需谨慎使用。
--mixed
仅是重置索引的位置,而不改变你的工作树中的任何东西(即,文件中的所有变化都会被保留,也不标记他们为待提交状态),并且提示什么内容还没有被更新了。这个是默认的选项。
--soft
既不触动索引的位置也不改变工作树中的任何内容我们只是要求这些内容成为一份好的内容之后才成为真正的提交内容。这个选项使你可以将已经提交的东西重新逆转至“已更新但未提交Updated but not Check in”的状态。就像已经执行过 git-update-index 命令,但是还没有执行 git-commit 命令一样。
--hard
将工作树中的内容和头索引都切换至指定的版本位置中,也就是说自 <commit-ish> 之后的所有的跟踪内容和工作树中的内容都会全部丢失。因此这个选项要慎用除非你已经非常确定你的确不想再看到那些东西了。关于git-reset 命令的具体如何使用可留作本章的练习题你可以随便创建一个Git 仓库并向其提交一些版本更新,然后测试--mixed 与--soft 选项的效果。
如果欲查看git-reset 命令对工作树的影响可使用git-status 命令。这是我们工作中的重点和难点!
1.7 Git命令详解
分支管理git-branch
直至现在为止,我们的项目版本库一直都是只有一个分支 master。在 git 版本库中创建分支的成本几乎为零,所以不必吝啬多创建几个分支。下面列举一些常见的分支策略,仅供大家参考:
创建一个属于自己的个人工作分支,以避免对主分支 master 造成太多的干扰,也方便与他人交流协作。
当进行高风险的工作时,创建一个试验性的分支,扔掉一个烂摊子总比收拾一个烂摊子好得多。
合并别人的工作的时候,最好是创建一个临时的分支,关于如何用临时分支合并别人的工作的技巧,将会在后面讲述。
创建分支
下面的命令将创建我自己的工作分支,名叫 litary并且将以后的工作转移到这个分支上开展。
$ git-branch litary
$ git-checkout litary
删除分支
要删除版本库中的某个分支,使用 git-branch -D 命令就可以了,例如: $ git-branch -D branch-name
查看分支
运行下面的命令可以得到你当前工作目录的分支列表:
$ git-branch
输出的分支中前面带*的就是你现在所在的分支,如果你忘记了你现在工作在哪个分支上,可以这样查看,而且运行下面的命令也可以告诉你:
$ cat .git/HEAD
查看项目的发展变化和比较差异
生成补丁git-format-patch
应用由git-format-patch生成的补丁git-am
这一节介绍几个查看项目的版本库的发展变化以及比较差异的很有用的命令:
git-show-branch
git-diff
git-whatchanged
git-show-branch 命令可以使我们看到版本库中每个分支的世系发展状态,并且可以看到每次提交的内容是否已进入每个分支。让我们看到版本库的发展记录。
譬如我们要查看世系标号为 master^ 和 litary 的版本的差异情况,我们可以使用这样的命令: $ git-diff master^ litary
合并两个分支git-merge
既然我们为项目创建了不同的分支,那么我们就要经常地将自己或者是别人在一个分支上的工作合并到其他的分支上去。现在我们看看怎么将 litary 分支上的工作合并到 master 分支中。现在转移我们当前的工作分支到 master并且将 litary 分支上的工作合并进来。
$ git-checkout master
$ git-merge "Merge work in litary" HEAD litary合并两个分支还有一个更简便的方式下面的命令和上面的命令是等价的。
$ git-checkout master
$ git-pull . litary
但是,此时 git 会出现合并冲突提示,就要根据具体的情况和需求对它修改。
一个关于git的挺好的文章http://progit.org/book/zh/ch5-3.html

View File

@@ -0,0 +1,21 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-08-21T22:41:48+08:00
====== push to only bare repositories ======
Created Sunday 21 August 2011
http://gitready.com/advanced/2009/02/01/push-to-only-bare-repositories.html
Typically Git allows you to build whatever kind of workflow youd like. Instead of saying “you must use it this way”, Git lets you figure out what best works for you and your organization. Like every system though, there are caveats and gotchas, and this tip goes over one of them. Lets go over some definitions about the subject first:
bare repository: a repository cloned using the --bare option, only includes the files/folders inside of the .git directory
non-bare repository: a normal clone, has a working directory with checked out files
Theres easy ways to share your changes with Git, but sometimes you want to push and pull from a repository instead. The best practice here is when you want to push changes, dont push to a non-bare repository. In other words, from the GitFaq:
A quick rule of thumb is to never push into a repository that has a work tree attached to it, until you know what you are doing.
The lesson here is that if youre going to push changes, make a bare repository clone with git clone --bare. From this point, you could host the changes from with git daemon so others can access them. (Tips on this command will be coming!) The reason why Git was built to be unhappy with pushing to non-bare repositories is because one of its main goals is to not lose your data.
If you do want to for some reason, measures are in place so you dont accidentally lose your changes. Once you have pushed to a non-bare repository, youd have to git reset --hard HEAD to throw your changes out, and then do a git checkout -f to bring the pushed changes in. Even if you did lose some work that was committed, you could use git reflog to pull the objects back out of Gits datastore.
Now that you know what it takes to push to a non-bare repository, hopefully youll take the easier route and just use the --bare option. If for some reason you dont want to, now you know what can be done to work around it.

View File

@@ -0,0 +1,16 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2012-02-19T16:58:52+08:00
====== 使用浏览器查看项目 ======
Created Sunday 19 February 2012
进入项目的根目录运行
git instaweb --httpd webrick -p1234
Instance already running. Restarting...
/usr/local/git/libexec/git-core/git-instaweb: line 112: kill: (1424) - No such process
[2011-01-22 11:03:12] INFO WEBrick 1.3.1
[2011-01-22 11:03:12] INFO ruby 1.8.6 (2009-06-08) [universal-darwin9.0]
查看diff时通过浏览器访问http://127.0.0.1:1234/既可。点击“summary”就能查看每次commit的comments点击"commitdiff"就能直观的看到diff。
这个工具实际上是启动了一个ruby的http server然后把所有的diff以web页面的形式展现出来因此它依赖于ruby。