Git : Distributed version control system

Srcs :
Git Reference
http://git-scm.com/docs
http://www.kernel.org/pub/software/scm/git/docs/
Pro Git
http://git-scm.com/book
“Tracking Branches” And “Remote-Tracking Branches”:
http://www.gitguys.com/topics/tracking-branches-and-remote-tracking-branches/
引用
在项目的 .git/config 文件里 存在 [branch <name>] 记录的 local branches 就是 Tracking Branches;
Remote-Tracking Branches 就好说了,就是型如 remotes/remoteName/remoteBranchName 的 branches。
https://felipec.wordpress.com/2013/09/01/advanced-git-concepts-the-upstream-tracking-branch/
引用
“Remote-Tracking Branches” 是对应其的 “Tracking Branches” 的 upstream,如:
Even if you have never heard of the concept, you probably already have at least one upstream tracking branch: master -> origin/master. When you clone a repository the current HEAD (usually ‘master’) is checked out for you, but also, it’s setup to track ‘origin/master’, and thus ‘origin/master’ is the “upstream” of ‘master’.
pushing and pulling:
http://gitready.com/beginner/2009/01/21/pushing-and-pulling.html
what's inside your .git directory:
http://gitready.com/advanced/2009/03/23/whats-inside-your-git-directory.html

gerrit:
http://wuaner.iteye.com/admin/blogs/1683282


HEAD:a special pointer refer to the branch you’re currently on。
origin:the default name Git gives to the server you cloned from
master:clone一个Remote Repo时,remote repo的master分支在本地checkout出的本地分支的默认名字,是个trcking branch,对应的remote-tracking branch是 origin/master。


Git has three main states that your files can reside in: committed, modified, and staged.
Committed means that the data is safely stored in your local database.
Modified means that you have changed the file but have not committed it to your database yet.
Staged means that you have marked a modified file in its current version to go into your next commit snapshot.
Generally speaking:
If a particular version of a file is in the git directory, it’s considered committed. If it’s modified but has been added to the staging area, it is staged. And if it was changed since it was checked out but has not been staged, it is modified. In Chapter 2, you’ll learn more about these states and how you can either take advantage of them or skip the staged part entirely.

This leads us to the three main sections of a Git project: the Git directory, the working directory, and the staging area.:
Git : Distributed version control system
The Git directory(即Repository,ABBR repo or rep) is where Git stores the metadata and object database for your project. This is the most important part of Git, and it is what is copied when you clone a repository from another computer.
The working directory(有些文章中称为Working tree) is a single checkout of one version of the project. These files are pulled out of the compressed database in the Git directory and placed on disk for you to use or modify.
The staging area(有些文章中称为Index) is a simple file, generally contained in your Git directory, that stores information about what will go into your next commit. It’s sometimes referred to as the index, but it’s becoming standard to refer to it as the staging area.
引用
git是个分布式,不需要服务器的版本控制工具,视所有拥有给定仓库 (repository)复本的电脑是一样的。所以项目的这三个部分都是存储在你本地机器上的。Git directory & Staging area在 .git 目录下,Working directory就是顶层目录下不包含 .git 目录的其他所有文件&文件夹。

The basic Git workflow goes something like this:
1. You modify files in your working directory.
2. You stage the files, adding snapshots of them to your staging area.
3. You do a commit, which takes the files as they are in the staging area and stores that snapshot permanently to your Git directory.


Remember that each file in your working directory can be in one of two states: tracked or untracked.
Tracked files are files that were in the last snapshot; they can be unmodified(译注:即committed), modified, or staged.
Untracked files are everything else — any files in your working directory that were not in your last snapshot and are not in your staging area:
Git : Distributed version control system
When you first clone a repository, all of your files will be tracked and unmodified because you just checked them out and haven’t edited anything.As you edit files, Git sees them as modified, because you’ve changed them since your last commit. You stage these modified files and then commit all your staged changes, and the cycle repeats.



常用命令:
一般说的repo,都指的是current HEAD commit。
config:
# 查看当前的git配置项;如果出现了某个key(如user.name)重复出现的现象,是因为你在不同 level 的git配置文件中都设置了该key,前面的会被后面的重写掉,所以不用担心
$ git config --list
$ git config --global user.name "John Doe" 
$ git config --global user.email [email protected]
# 项目中文件权限的变更不作为文件被更改的判断依据
$ git config --global core.filemode false
#编辑当前项目对应的git配置文件 projectDir/.git/config
$ git config -e
#编辑 global level 的git配置文件 ~/.gitconfig
$ git config --global -e
#编辑 system level 的git配置文件 /etc/gitconfig
$ git config --system -e
$ git config --global core.editor "vim"
$ git config --global merge.tool vimdiff
$ git config --list
git 有三种级别的配置文件,分别是:
project level:对应 projectDir/.git/config 文件;设置命令为在项目根目录下执行 git config (no --global or --system).
global level: 对应 ~/.gitconfig 文件;设置命令为任意目录下执行 git config --global;只有在做了 git config --global 设置后 ~/.gitconfig 文件才会存在。
system level: 对应 /etc/gitconfig 文件;设置命令为任意目录下执行 git config --system (如果当前不是root用户,注意权限问题);只有在做了 git config --system 设置后 /etc/gitconfig 文件才会存在。
三种级别配置文件之间的关系是 project level 重写 global level,global level 重写 system level,参见:
http://www.codinginahurry.com/2011/02/05/three-levels-of-git-config/
所以,添加 --global 参数的 git config 修改的是全局的 git 配置文件 ~/.gitconfig,也就是说会对所有的项目有效的;如果只是想修改特定某个项目的 git 配置项,则在该项目根目录下执行不带 --global 参数的 git config 即可,此时修改的是该项目下的 .git/config 文件。参考资料:
http://www.thebuzzmedia.com/git-tip-git-config-user-name-and-user-email-for-local-not-global-config/


init:
Initializing a Repository in an Existing Directory.This creates a new subdirectory named .git that contains all of your necessary repository files — a Git repository skeleton.
$ git init
#Shared Repositories Should Be Bare Repositores 
#http://www.gitguys.com/topics/shared-repositories-should-be-bare-repositories/
$ git init --bare project1.git
Creating a Shared Repository; Users Sharing The Repository:
http://www.gitguys.com/topics/creating-a-shared-repository-users-sharing-the-repository/

clone:
git clone will clone a repo int a newly created directory ,and give a default shortname "origin" for the repo。
by default,the git clone command automatically sets up your local master branch to track the remote master branch on the server you cloned from (assuming the remote has a master branch).
#creates a directory named grit, initializes a .git directory inside it, pulls down all the data for that repository, and checks out a working copy of the latest version
$ git clone git://github.com/schacon/grit.git
#That command does the same thing as the previous one, but the target directory is called mygrit
$ git clone git://github.com/schacon/grit.git mygrit


add:
Add file contents to the index (或表述为:stage file to staging area)。细说的话add可以:
1 tracking new file(或者说,staging untracked file)
2 staging modified files
3 marking merge-conflicted files as resolved
#staging 指定的文件
$ git add [fileName] [fileName2] ...
#staging all modified&untracked files in current working directory & its sub directories(staging当前目录和子目录的文件,不包括上级目录的):
$ git add . 
#staging only modified files(iow,not staging the untracked files)。后补:一个重要的作用见下面
$ git add -u
git add -u 的一个重要作用:一次性将所有在 working directory 中删除的文件从 staging area中移除:
-u 最重要的一个作用在于:当你删除了多个文件时,使用 git add . 不能 staging 删除的文件,而 git rm <file> 只能从 staging area 中移除单个文件!一个个的将被删除的文件从 staging area 中移除显然是太麻烦,这时候就可以用 git add -u,一次性将所有 working directory 中删除的文件从 staging area 中移除,参见 http://stackoverflow.com/questions/1402776/how-do-i-commit-all-deleted-files-in-git



status:
Show the working tree status:Viewing your staged and unstaged changes.
Displays paths that have differences between the index file and the current HEAD commit(即关注staged files), paths that have differences between the working tree and the index file(即关注modified files), and paths in the working tree that are not tracked by git (and are not ignored by .gitignore)(也关注untracked files).
$ git status
#详细信息
$ git status -v(or --verbose)


commit:
Commit all files in The staging area to the repo. Remember that anything that is still unstaged — any files you have created or modified that you haven’t run git add on since you edited them — won’t go into this commit.
When you commit in Git, Git stores a commit object that contains a pointer to the snapshot of the content you staged, the author and message metadata, and zero or more pointers to the commit or commits that were the direct parents of this commit: zero parents for the first commit, one parent for a normal commit, and multiple parents for a commit that results from a merge of two or more branches.
#提交必须写注释,所以不加任何参数会打开编辑器,让你添加注释
$ git commit
#提交时使用 -m 添加注释(commit message)
$ git commit -m "fix some problem"
# -a makes Git automatically stage every file that is already tracked before doing the commit(即 -a 可以让你提交未使用 add 命令添加到staging area的tracked文件;注意不包括untracked文件)
$ git commit -a -m "fix some brobs"
#使用 --amend,会新增一个commit对象,并置该新增commit对象的父commit为amend前当前分支所指向commit对象的父commit,并将amend前当前分支所指向commit对象所涉及的文件变更blob信息等拷贝给新增的commit对象;这里要注意的是,amend前当前分支所指向commit对象并没有被删除,只是当前分支不再经过它;没有任何分支经过的commit对象被认为还是可达的,因为reglog有对它的引用(commits referred to from your reflog are considered reachable.);超过gc.reflogExpire规定的时间点后,它变的不可达(Unreachable);不可达对象超过gc.pruneExpire规定的时间点后,被gc掉(即真正的被删除掉)。详见下面关于DAG和gc的描述。因为这一特性,我们可以使用做些诸如“Changing your last commit”的事情
$ git commit --amend -m "amended commit"
#使用 --amend 修改上次commit对象的author信息
$ git commit --amend --author="Author xxx <[email protected]>" -m "Add more ignores in .gitignore"


diff:
Show changes between commits, commit and working tree, etc
注意这里只去比较tracked files的不同!untracked files是不会展现在diff里的。本条目下的working directory指untracked files之外的文件&文件夹。
http://stackoverflow.com/questions/8544211/git-diff-gives-no-output

1 Comparing(show changes between) branches
#Show changes between the tips of topic and master branches.
$ git diff topic master
#Same as above.
$ git diff topic..master 
#Show changes that occurred on the master branch since when the topic branch was started off it.
$ git diff topic...master

2 Compareing(show changes between) project's sections(项目的不同区域)
#Show changes between working directory and staging area(即只关注modified files)
$ git diff
#Show changes between staging area and repo.(即只关注staged files)
$ git diff --cached(or staged)
#Show changes between working directory and repo.(这里的HEAD可以是任何一个commit点,如一个branch的名字)(关注modified & staged files)
$ git diff HEAD

3 Compareing single files
#show superfile's change between working directory and staging area
$ git diff superfile
#show superfile's change between staging area and repo.
$ git diff --cached superfile
#show readme & superfile 's change between working directory and staging area
$ git diff readme superfile


branch:
A branch in Git is simply a lightweight movable pointer refer to a commit object. The default branch name in Git is master. As you initially make commits, you’re given a master branch that points to the last commit you made. Every time you commit, it moves forward automatically.
Git use a special pointer called HEAD refer to the branch you’re currently on。
http://www.gitguys.com/topics/tracking-branches-and-remote-tracking-branches/
在一个典型的拥有Remote Repo的项目中(我们需要版本控制,本就是团队协同的需要;只要一个项目参与的不止一个人,每个人肯定会有Remote Repo):
在你本地的Git Repo中,有两种不同类型的分支:local branches和remote-tracking branches(常简称其为Remote branches,但这个简称容易让人混淆,误以为就是Remote Repo的分支,其实不是)
remote-tracking branch 是 Remote Repo的某一分支在某一时期(确切说自上次你fetch/pull以来)的状态在你本地Repo中的体现;它的特殊性在于:它是只读的,你在本地无法移动它,它会在你与Remote Repo发生网络交互(push/pull/fetch)时自动移动。
而local branches是随着你本地的commit/merge等等操作自动移动的;另外有一种特殊的local branches:tracking branch(跟踪分支)。tracking branch是从remote-tracking branch checkout出来的本地分支;因为tracking branch是和remote-tracking branch有直接联系的,所以:在跟踪分支里输入 git push,Git 会自行推断应该向哪个服务器的哪个分支推送数据;在跟踪分支里运行 git pull 会获取Remote repo所有(确切说是自你上次fetch/pull以来的)的commit对象及分支,用它们更新本地的remote-tracking branches,然后将与当前跟踪分支对应的remote-tracking branch的内容merge入当前分支。
需要注意的是:如果当前在tracking branch上(如本地的master),且该tracking branch与其对应的remote-tracking branch(如origin/master)没有出现分叉,且此时你使用git pull拿remote repo的最新更新,pull的“先fetch,然后自动merge入当前分支”的功能,可能会给你造成“tracking branch也随着与remote repo的pull网络交互而自动移动”的假象。谨记tracking branch在这种情况下的移动,是因为pull的merge入当前分支,而当前分支就是该tracking branch,又没有分叉,发生了“Fast-forward”类型的merge引起的,这点必须注意。
#查看项目的所有分支数(确切的说,是local branches)
$ git branch(其中前面有*的为当前使用的分支,亦即HEAD指向的分支)
#新建一个分支(only created a new branch — it didn’t switch to that branch)
$ git branch branchName
#Switch to an existing branch
$ git checkout branchName
#Rename a existing branch
$ git branch -m <oldname> <newname>
#If you want to rename the current branch, you can simply do:
$ git branch -m <newname>
#create a branch and switch to it at the same time  
$ git checkout -b newBranchName  
#查看HEAD当前指向哪个分支:
$ cat .git/HEAD
#delete a branch
$ git branch -d branchName
#To see the last commit on each branch
$ git branch -v
#To see which branches are already merged into the branch you’re on(Branches on this list without the * in front of them are generally fine to delete with [git branch -d])
$ git branch --merged
#To see all the branches that contain work you haven’t yet merged in
$ git branch --no-merged
#show all local and remote-tacking branches
$ git branch -a
#show only remote-tracking branches(exclusive local branches)
$ git branch -r


tag:
http://git-scm.com/docs/git-tag
创建一个标签,用来标记项目历史中的关键点,如打版本号等。需要注意的是,默认情况下,'git push'命令不会将标签上传到远程服务器上。为了共享这些标签,你必须在'git push'命令后明确添加 --tags 选项。同理对 git pull 也是这样:如果你想获取/更新已有的本地tag使其与远程仓库保持一直,需使用 git pull --tags
#创建一个(lightweight)tag
$ git tag v1
#删除一个tag
$ git tag -d v1
#列出所有tag 
$ git tag -l (or --list)
#创建一个annotated tag(If one of -a, -s, or -u <key-id> is passed, the command creates a tag object, and requires a tag message. Unless -m <msg> or -F <file> is given, an editor is started for the user to type in the tag message | 所谓的annotated,与lightweight的不同在于,lightweight不会创建tag对象,同branch一样只是个指向commit对象的指针;而annotated会创建一个tag对象,并含有一个指向commit对象的指针)
$ git tag -a -m "v2 tag" v2


checkout:
1 Switch between branches.
note that if your working directory or staging area has uncommitted changes that conflict with the branch you're checking out, Git won't let you switch branches.此时可以使用:namely 、stashing、commit amending
when you type checkout switch to a branch,Git resets your working directory to look like the snapshot of the commit that the branch you check out points to. It adds, removes, and modifies files automatically to make sure your working copy is what the branch looked like last commit to it.
#Switch to an existing branch
$ git checkout branchName
#create a branch and switch to it at the same time
$ git checkout -b newBranchName
#新建一个同remote-tracking branck:origin/serverfix指向同一个commit对象的本地分支sf并切换到这个本地分支(注意此时只是checkout出本地Git Repo中的origin/serverfix这个remote-tracking branch,并不存在与Remote Repo的serverfix分支的网络交互)
$ git checkout -b sf origin/serverfix
#功能同上,新建一个同remote-tracking branck:origin/serverfix指向同一个commit对象,名字也与Remote Repo:origin的serverfix分支同名的Local branch并切换至这个新建的名为serverfix的local branch:
$ git checkout --track origin/serverfix

2 Unmodifying a Modified File(restore file from the staging area,即用staging area中的文件覆盖working directory里的文件)
引用
for example:
$ git checkout hello.c
If you have an unfortunate branch that is named hello.c, this step would be confused as an instruction to switch to that branch. You should instead write:
$ git checkout -- hello.c
# check out a particular version of a file ( http://stackoverflow.com/questions/692246/how-do-i-revert-one-file-to-the-last-commit-in-git ):
git checkout v1.2.3 -- filename         # tag v1.2.3
git checkout stable -- filename         # stable branch
git checkout origin/master -- filename  # upstream master
git checkout HEAD -- filename           # the version from the most recent commit
git checkout HEAD^ -- filename          # the version before the most recent commit


reset(重置):
http://git-scm.com/docs/git-reset
http://git-scm.com/2011/07/11/reset.html
1 格式为 git reset [<commit>] [<paths>], []表示可选,copy entries from <commit> to the index(Unstaging a Staged File)(即用所用分支如HEAD中的文件覆盖staging area中的文件,Working directory中的该文件不会变)
可以理解成是 git add <paths> 的反操作。
$ git reset HEAD readme

2 git reset [--<mode>] [<commit>]resets the current branch head to <commit>,可以用来在当前分支Undo committes、redo committes等。注意undo committes时这些committes并没有被删除!使用git reflog仍然可以看到这些commit对象,只是当前分支不再经过他们(not referenced by current branch);假设c893324为一个commit对象且只有当前分支经过它,则它被git reset命令undo掉后,该commit仍然是可达的,因为reflog有对它的引用,被reflog引用了的对象被认为是可达的;你使用git checkout c893324,将进入所谓的“detached HEAD”状态,这是因为目前这个commit对象没有任何分支经过它,关于 “commits are not referenced by any branch”:
http://stackoverflow.com/questions/2541545/is-there-a-difference-between-git-reset-hard-hash-and-git-checkout-hash
引用
Those are protected for (by default) 30 days by reflog; they would ultimately be pruned (removed).
关于DETACHED HEAD详见官方 ref的 checkout条目:
http://git-scm.com/docs/git-checkout
同rebase一样,不要使用reset将已经pushed到remote repo的commits undo掉。
<mode>可以取以下之一:
--soft | Does not touch the index file nor the working tree at all (but resets the head to <commit>, just like all modes do).只是undo指定<commit>后的committes,这些committes涉及的文件该在staging area还在staging area,该在working dir还在woring dir,可以理解成是git commit的反操作
--mixed | Resets the index but not the working tree (i.e., the changed files are preserved but not marked for commit) and reports what has not been updated. This is the default action.这是不加--<mode>情况下的默认操作,不光undo指定<commit>后的committes,这些committes涉及的文件也全部从staging area中移除。
--hard | Resets the index and working tree. Any changes to tracked files in the working tree since <commit> are discarded.即重置staging area,也重置working dir,效果上等同于git checkout <commit>(只是效果等同,因为checkout是切换分支,不会影响现分支的指向;而reset --hard是重置当前分支的指向,将其指到<commit>处)。(注意working dir中的untracked files是不受影响的,git checkout <commit>也不会影响working dir中的untracked files)
--merge | Resets the index and updates the files in the working tree that are different between <commit> and HEAD, but keeps those which are different between the index and working tree (i.e. which have changes which have not been added). If a file that is different between <commit> and the index has unstaged changes, reset is aborted.
--keep | Resets index entries and updates files in the working tree that are different between <commit> and HEAD. If a file that is different between <commit> and HEAD has local changes, reset is aborted.
关于Git中的 ~(波浪号tilde) 和 ^(脱字号caret):
http://paulboxley.com/blog/2011/06/git-caret-and-tilde
引用
ref~ is shorthand for ref~1 and means the commit's first parent. ref~2 means the commit's first parent's first parent. ref~3 means the commit's first parent's first parent's first parent. And so on.
ref^ is shorthand for ref^1 and means the commit's first parent. But where the two differ is that ref^2 means the commit's second parent (remember, commits can have two parents when they are a merge).
#Undo a commit
$ git commit ...
$ git reset --soft HEAD^
#Undo a commit, making it a topic branch
$ git branch topic/wip     
$ git reset --hard HEAD~3  
$ git checkout topic/wip   
#先undo 一个commit对象,再redo这个commit对象(当前为master分支)
$ git reset --soft HEAD~1 //undo了一个commit对象
$ git reflog //看到被undo的commit的对象的SHA1为c893324
$ git reset c893324  //再redo这个commit对象,master分支再次指向最初的 commit对象 c893324



rm:
remove,从当前的工作目录中和索引中删除文件
#从工作目录和索引中删除
$ git rm readme
#只从索引中删除(即工作目录下的该文件不做物理删除);执行完该操作后,工作目录下的readme文件将将变为untracked file
$ git rm --cached readme
#某些文件可能在之前被提交入了local repo,但现在想要将其加入 .gitignore 中。可以使用以下命令将这些文件从 local repo 和 index 中全部移除(参见 http://stackoverflow.com/a/21477287/1635855):
$ git rm --cached `git ls-files -i -X .gitignore`


mv:
move,移动一个文件。git mv其实是通过bash mv + git rm + git add作为实现,so:
$ git mv README.txt README 
is equivalent to:
$ mv README.txt README
$ git rm README.txt
$ git add README


log:
Viewing Commit History.
#什么参数都不加的时候,查看的只是当前分支的提交历史(即当前分支经过的commit对象)
$ git log
#查看提交历史,并显示每次commit与上次的diff
$ git log -p
#查看commit对象树
$ git log --graph


reflog:
Reflog is a mechanism to record when the tip of branches are updated. This command is to manage the information recorded in it.
The reflog will cover all recent actions (HEAD reflog records branch switching as well). It is an alias for
git log -g --abbrev-commit --pretty=oneline

reflog里记录的是所有导致branches的tip变更、branch切换等操作的历史。它能做的,git log 加上参数也完全可以做到。
只要是在reflog里记录的commit对象,都被认为是可达的( commits referred to from your reflog are considered reachable.),即使是一个不被任何branch和tag经过的dangling commits。

rebase:
http://git-scm.com/docs/git-rebase
http://www.progit.org/book/id/Branching-Pada-Git-Rebasing
http://blog.yorkxin.org/2011/07/29/git-rebase/
In Git, there are two main ways to integrate changes from one branch into another: the merge and the rebase.
记住: Do not rebase commits that you have pushed to a public repository.
rebase是重新re定义基准点base,抛开参数的简单格式为 git rebase [<commit>] [<branch>]
意思是: git rebase 基准点(BASE) 被重新定义基准点的分支(RE)
以给定的<commit>为基准点BASE,拷贝<branch>上不同于基准点<commit>所经由commits的所有commit对象,(这里的不同具体指:若<commit>和<branch>不在一条路径上,就是自分叉以来的<branch>上commits;若在一条路径上且<commit>在<branch>前,就是<branch>上<commit>后的commits,同一路径且<commit>在<branch>后不会创建新的commits) 生成等数量的新的commits对象,并将这些新生成的commits对象以<commit>为嫁接点(默认是以基准点BASE为嫁接点,通过rebase的子命令--onto可以指定任意的嫁接点) 逐个嫁接到一个全新的commits路径链上,并将<branch>指向这个全新commits链的tip。(原来的那些<branch>经由的自和基准点<commit>分叉以来的所有commit对象,还是存在的,只是<branch>不再经过它们(抛弃了它们);如果也没有任何的其他分支经过它们的话,它们就是dangling commits;经过一段时间(git config中gc.pruneExpire,默认2周)后,dangling commits就会被gc彻底的删除掉。)
若<commit>未指定,则默认使用<branch>在git-config中的branch.<name>.remote这个remote-tracking branch作为基准点; 一般情况下,基准点BASE都会指定(也就是说常见的 git rebase master 里,master是充当BASE的角色)
若<branch>未指定,则默认使用当前分支(即当前分支被重构、被重新定义基准点)。指定<branch>的情况下,git内部处理为先 git checkout <branch>,再 git rebase <commit>。
[<commit>] 和 [<branch>] 是可选的是因为它们存在如上的默认值;但如果
1 当前不在任何一个分支上  -  缺被RE的<branch>
2 未指定<branch>且当前分支未配置branch.<name>.remote  -  缺作为BASE的<commit>
则 git rebase 会被中止abort掉。
使用 -i(or --interactive)参数,可以让你自由的修改rebase所涉及的commits。使用交互式rebase可以用来做:
1 更改branch中commit的提交顺序;
2 合并若干个commits,将他们变为一个commit
3 实现诸如“将当前分支回退若干步commits”的功能
等等.参见 rebase -i 的帮助:
引用
# Rebase df23917..a931ac7 onto df23917
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#
Git做rebase的内部实现机制,同merge大同小异(只考虑未加--onto参数的情况):
BASE <commit> 和 被RE的<branch> 在一条路径上:
如果BASE是被RE的<branch>的直接前驱,则非交互式的(即未加-i参数)rebase会不做任何改变地退出,并告诉你“Current branch is up to date”;交互式的因为你可以任何修改pick的commit,当然就随你意的来了。如果想强制rebase一个非交互式rebase,可以加 -f(or --force,该参数与-i不可同时使用)参数,当然这样做其实没什么太大意义,因为你只是将<branch>上<commit>后的commits全盘复制后依然嫁接到了<commit>下,并将<branch>指向新生成的commits链的tip,与rebase前是没有任何区别的,只是徒添了dangling commits,浪费了存储空间而已;
如果被RE的<branch>是BASE的直接前驱,则不会创建新的commit对象,只是将<branch>下移,使其指向作为BASE的<commit>,这种rebase称为 Fast-forward
BASE <commit> 和 被RE的<branch> 在两条不同的路径上:
参照上面的定义,不再赘述。
rebase冲突:
每一个新commit对象涉及文件的内容rebase都会尝试合并入working dir中;若合并的过程中出现冲突,rebase会暂停,需要你手动解决冲突后用 git add <file>(不要commit!commit是rebase帮你干的)标记该冲突为已解决,再执行 git rebase --continue 来继续rebase操作。这是一个反复的过程,将每个新commit对象导致的冲突都 ”手动解决 > git add 标记为已解决 > git rebase --continue“后,整个rebase过程才算完成。另外如果你觉得某个commit无关紧要,可以使用 rebase --skip丢弃该commit(即新的commit链里不含这个commit对象);使用 rebase --abort,将中止当前rebase的执行(意味着执行失败)。详见:
http://stackoverflow.com/questions/2498458/why-did-git-set-us-on-no-branch
另外,如果rebase过程中HEAD如果指向了(no branch),变成了detached HEAD,rebase --continue将无法进行下去,此时你需要将HEAD所指向的commit merge到 被RE的<branch>中:
http://markpasc.livejournal.com/189329.html
# 以当前分支的branch.<currentBranch>.remote这个remote-tracking branch作为BASE,RE 当前分支(若当前分支不是tracking branch(即没有remote-tracking branch与之对应),则rebase不成功)
$ git rebase
#当前分支为topic,下面两个命令是一样的,都是以tip of master作为BASE,RE topic
$ git rebase master
$ git rebase master topic //a short-hand of git checkout topic followed by git rebase master
#以server分支为BASE,RE client分支,onto master分支(拷贝client分支上和server分支不同的所有commits,生成新的commits对象,并将这些新生成的commits对象嫁接到master分支)
$ git rebase --onto master server client
#删除(只是效果上的删除!)当前分支上的任何一个commit对象
$ git rebase --onto <commit-SHA1>^ <commit-SHA1>


merge:
http://git-scm.com/docs/git-merge
Incorporates changes from the named commits (since the time their histories diverged from the current branch) into the current branch。如果两个分支自分叉开始有对相同文件的修改,则会出现冲突conflict。
根据分支指向commit对象的情况,可分为:
两个分支在一条路径上:
如果被合并分支(merged-in branch,广义上可以是任何一个commit对象)是目标分支(即当前分支)的直接前驱,则merge不做任何改变地退出,并告诉你"Already up-to-date."。(merge当前分支路经的commits入当前分支,本来就是没有意义的)
如果目标分支(即当前分支)是被合并分支(merged-in branch)的直接前驱,则使用 Fast-forward 的方式合并 。此时目标分支会移至和被合并分支同样的位置(分支即指向commit对象的指针,所以这里说的“移至”指的就是目标分支也指向被合并分支指向的commit对象)。其中的任何一个分支,都可以安全的删除(因为指向同一个commit对象,所以有一个分支指针是多余的。)
两个分支在两条不同的路径上:
此时会采用 Merge made by recursive 的方式合并。会使用两条路径共同的commit对象作为合并操作的基础(Git determines the best common ancestor to use for its merge base),并在合并后创建一个新的commit对象(这个新的commit对象有两个父对象:合并前两个分支所指向的commit对象),并将目标分支改为指向这个新的commit对象。这种合并难免存在冲突,解决冲突的方法不外乎编辑冲突文件后add,或checkout -- <file> 恢复覆盖修改。
#merge aBranch into current branch. 注意触发的是目标分支,也就是当前分支的移动(指向新生成的commit对象,或前移至aBranch的位置)!
$ git merge aBranch
#将remote-tracking branch:origin/serverfix合并入当前工作的本地分支(注意此时只是将本地Git Repo中的origin/serverfix这个remote-tracking branch合并入当前分支,并不存在与Remote Repo的网络交互)
$ git merge origin/serverfix


revert(恢复):
http://git-scm.com/docs/git-revert.html
git revert [--edit | --no-edit] [-n] [-m parent-number] [-s] <commit>...
所谓“恢复”,指恢复指定的几个<commit>所修改的文件的内容。需要注意的是原来的<commit>...对象是不做任何改变的,“恢复文件内容”只是通过“自动创建同原来的<commit>...数量相等的新的commit对象,来记录原来<commit>...其所修改的文件在被其修改前的内容”这样的方式实现的。This command creates some new commit that undo the changes from previous commits;git revert is used to record some new commits to reverse the effect of some earlier commits (often only a faulty one).
Given one or more existing commits, revert the changes that the related patches introduce, and record some new commits that record them. This requires your working tree to be clean (no modifications from the HEAD commit).
如果指定了 -n (or --no-commit)参数,则只将 <commit>... 所涉及文件的内容恢复到Working dir and Staging area中,,而不创建新的commit对象。
引用
Usually the command automatically creates some commits with commit log messages stating which commits were reverted. -n (or --no-commit) flag applies the changes necessary to revert the named commits to your working tree and the index, but does not make the commits. In addition, when this option is used, your index does not have to match the HEAD commit. The revert is done against the beginning state of your index.
This is useful when reverting more than one commits' effect to your index in a row.
如果被revert的commit对象和当前分支指向的commit对象有对同一个文件的修改,则会产生冲突,不会再自动创建新的commit对象,需手工解决冲突后自己做commit提交操作。
#恢复最近一个commit所修改的文件的内容
$ git revert HEAD
#恢复“parent of parent of HEAD”这一个commit对象所修改的文件的内容
$ git revert HEAD~2
#按顺序恢复HEAD、HEAD~1、HEAD~2三个commit对象所修改文件的内容,并创建三个新的commit对象(第一个对应HEAD,第二个对应HEAD~1,第三个对应HEAD~2)分别来记录这三个被恢复的commit对象所修改的文件在被其修改前的内容
$ git revert HEAD~3..HEAD
#Revert the changes done by commits from the fifth last commit in master (included) to the third last commit in master (included), but do not create any commit with the reverted changes. The revert only modifies the working tree and the index.
$ git revert -n master~5..master~2


stash:
http://git-scm.com/docs/git-stash
Use git stash when you want to record the current state of the working directory and the index, but want to go back to a clean working directory. The command saves your local modifications away and reverts the working directory to match the HEAD commit.
# List all the stashes
$ git stash list
# Apply the latest stash, and remove it from the stack
$ git stash pop
# Apply a named patch, but leave it on the stack
$ git stash apply stash@{2} 
# Drop a stash
$ git stash drop stash@{3} 
# Clear the entire stash stack (almost never needed)
$ git stash clear
# A better way to purge the stash
$ git reflog expire --expire=30.days refs/stas


clean:
Remove untracked files from the working tree
http://git-scm.com/docs/git-clean
http://stackoverflow.com/questions/61212/removing-untracked-files-from-your-git-working-copy
# Remove 掉所有的 untracked files(不包含 untracked dirs)
$ git clean -f
# Remove 掉所有的 untracked files and dirs:
$ git clean -f -d


remote:
http://gitready.com/beginner/2009/01/21/pushing-and-pulling.html
Git : Distributed version control system
To be able to collaborate on any Git project, you need to know how to manage your remote repositories. You can have several of them, each of which generally is either read-only or read/write for you. Collaborating with others involves managing these remote repositories and pushing and pulling data to and from them when you need to share work.
If you clone from a remote Git Reop, Git automatically names it origin for you, pulls down all its data, creates a pointer to where its master branch is, and names it origin/master locally; and you can’t move it. Git also gives you your own master branch starting at the same place as origin’s master branch, so you have something to work from
#查看所有remote repositories
$ git remote
#同上,列出详情
$ git remote -v(or --verbose)
#添加远程仓库:git remote add [shortname] [url];需要注意的是必须指定shortname(且不能与当前已有的任何一个远程仓库的shortname重名),否则添加不成功
$ git remote add pb git://github.com/paulboone/ticgit.git
#see more information about a particular remote:git remote show [remote-name]
$ $ git remote show origin
#重命名一个远程仓库
$ git remote rename paulboone pb
#删除一个远程仓库:
$ git remote rm remoteRepShortName
#Deletes all stale remote-tracking branches under <remoteRepoName>. These stale branches have already been removed from the remote repository referenced by <remoteRepoName>, but are still locally available in "remotes/<remoteRepoName>".
#With --dry-run option, report what branches will be pruned, but do not actually prune them; 还有几个其他的命令可以做这个事,参见 http://stackoverflow.com/questions/1072171/how-do-you-remove-an-invalid-remote-branch-reference-from-git
$ git remote prune origin


fetch:
获取 Remote repository 中的项目文件更新至你本地 Local repository 中。fetch操作会触发 Local repository 中 Remote-tracking branches 的自动移动。
It’s important to note that when you do a fetch that brings down new remote branches, you don’t automatically have local, editable copies of them.
#Update the remote-tracking branches:copies all branches from the remote refs/heads/ namespace and stores them to the local refs/remotes/origin/ namespace,unless the branch.<name>.fetch option is used to specify a non-default refspec.
$ git fetch origin
#updates (or creates, as necessary) branchdees pu and tmp in the local repository by fetching from the branches (respectively) pu and maint from the remote repository.
#The pu branch will be updated even if it is does not fast-forward, because it is prefixed with a plus sign; tmp will not be.
$ git fetch origin +pu:pu maint:tmp


pull:
Incorporates changes from a remote repository into the current branch. In its default mode, git pull is shorthand for git fetch followed by git merge FETCH_HEAD.
More precisely, git pull runs git fetch with the given parameters and calls git merge to merge the retrieved branch heads into the current branch. With --rebase, it runs git rebase instead of git merge.
#Update the remote-tracking branches for the repository you cloned from, then merge one of them into your current branch:
#Normally the branch merged in is the HEAD of the remote repository, but the choice is determined by the branch.<name>.remote and branch.<name>.merge options
$ git pull
$ git pull origin


push:
This command works only if you cloned from a server to which you have write access and if nobody has pushed in the meantime. If you and someone else clone at the same time and they push upstream and then you push upstream, your push will rightly be rejected. You’ll have to pull down their work first and incorporate it into yours before you’ll be allowed to push。
1 将本地分支并入(推送)到远程仓库
#push changes from all local branches to matching(对应的,即tracking branch和remote-tracking branch之间有直接联系的这种对应关系)branches the origin remote.
$ git push origin
#push changes from the local master branch to the matching remote master branch.
$ git push origin master

2 用来在remote上新建或删除分支/tag。
#格式:git push [remotename] [localbranch]:[remotebranch];下面为使用名为aBranch的local branch在remote repo -> origin上新建一个名为aRemoteBranch的branch(如果名为aRemoteBranch的分支在Remote Repo不存在的话,才是新建,否则是将aBranch的changes push到Remote Repo的aRemoteBranch);执行完后,本地多了一个名为origin/aRemoteBranch的remote-tracking branch,其对应的tracking branch为aBranch;Remote Repo里多了一个名为aRemoteBranch的分支
$ git push origin aBranch:aRemoteBranch
#删除origin上名为aRemoteBranch的分支(you leave off the [localbranch] portion, then you’re basically saying, “Take nothing on my side and make it be [remotebranch].”)
$ git push origin :aRemoteBranch
# 将本地所有 tags 推送到远程仓库
$ git push --tags
# 将本地一个特定的 tag 推送到远程仓库
$ git push <reponame> <tagname>




Git DAG & gc & Unreachable:
http://eagain.net/articles/git-for-computer-scientists/
http://git-scm.com/docs/git-gc
引用
git gc [--aggressive] [--auto] [--quiet] [--prune=<date> | --no-prune]
--prune=<date>: Prune loose objects older than date (default is 2 weeks ago, overridable by the config variable gc.pruneExpire). This option is on by default.
The optional configuration variable gc.reflogExpire can be set to indicate how long historical entries within each branch's reflog should remain available in this repository. The setting is expressed as a length of time, for example 90 days or 3 months. It defaults to 90 days.
The optional configuration variable gc.reflogExpireUnreachable can be set to indicate how long historical reflog entries which are not part of the current branch should remain available in this repository. These types of entries are generally created as a result of using git commit --amend or git rebase and are the commits prior to the amend or rebase occurring. Since these changes are not part of the current project most users will want to expire them sooner. This option defaults to 30 days.
The optional configuration variable gc.pruneExpire controls how old the unreferenced loose objects have to be before they are pruned. The default is "2 weeks ago".
http://git-scm.com/docs/git-fsck
http://git-scm.com/docs/git-config
http://stackoverflow.com/questions/3765234/listing-and-deleting-git-commits-that-are-under-no-branch-dangling
Git的提交历史,本质是个DAG(Directed Acyclic Graph.有向无环图)。
Git中的commit对象是这个有向无环图的关键节点。
在你做任何的基本操作时,commit对象都不会被真正的删除(这在一定程度上是为了保证有向无环图的连通性);当然,积累到一定时间后,由于 git commit --amend 、git rebase、git reset等操作,会使一些commit对象变得不再被任何的branch&tag所经过,这样的commit对象称为dangling commits;dangling commits原则上还是可达的,因为它们还被reflog引用着( commits referred to from your reflog are considered reachable.);如果这些对象在reflog中的引用记录超过了gc.reflogExpire(可通过git config配置,默认为90天),reflog中关于其的记录将会被删除,从而这些commit对象变为不可达(Unreachable,或叫做unreferenced),但这些不可达的commit对象此时还未被真正的删除;再经过 gc.pruneExpire 时间(默认2周)后,不可达的commit对象才会被gc真正的删除掉。
#显示所有的dangling commits(fsck的subcommand no-reflogs,Do not consider commits that are referenced only by an entry in a reflog to be reachable. This option is meant only to search for commits that used to be in a ref, but now aren't, but are still in that corresponding reflog,所以可以说dangling commits指的是:不考虑被reflog引用导致的commit对象可达性,只要在整个DAG树中不被任何的branch&tag可达(即没有任何一个经过它),那它就是dangling commits)
$ git fsck --no-reflogs 
#清空reflog里的记录(做完这一步后,dangling commits变成了真正的Unreachable/unreferenced的commit对象)
$ git reflog expire --expire=now --all
#显式调用gc,立马进行prune。执行完这一步后,Unreachable的commit对象被真正的删除掉
$ git gc --prune=now





易混淆的几个命令的区别:
都是用来从Remote repo.拿的:fetch/pull:
http://blog.mikepearce.net/2010/05/18/the-difference-between-git-pull-git-fetch-and-git-clone-and-git-rebase/
http://longair.net/blog/2009/04/16/git-fetch-and-merge/
http://stackoverflow.com/questions/292357/whats-the-difference-between-git-pull-and-git-fetch
引用
When you use pull, Git tries to automatically do your work for you. It is context sensitive, so Git will merge any pulled commits into the branch you are currently working in. pull automatically merges the commits without letting you review them first. If you don’t closely manage your branches you may run into frequent conflicts.
When you fetch, Git gathers any commits from the target branch that do not exist in your current branch and stores them in your local repo. However, it does not merge them with your current branch. This is particularly useful if you need to keep your repo up to date but are working on something that might break if you update your files. To integrate the commits into your master branch, you use merge.

都是用来给的:commit/push:
commit是本地操作,只是提交到你本地的Git repo中,会使(当前的)local branch自动移动;
push是用来将本地的tracking branches推送到与其有联系的remote-tracking branches对应的Remote Repo中的分支的,会使本地的remote-tracking branches自动移动。

都是用来比较文件异同的:status & diff
status既关注trucked files的变化(modified & staged files),也关注untrucked files;diff的功能更加强大,在它比较文件异同的功能点上,它只关注trucked files的变化,untrucked files是不列入diff的比较范围的。

都可以用来undo已提交的commit对象:reset/revert/rebase -i:
首先,他们都不会改变/删除已存在的commit对象。
reset只是重置当前分支的指向,最简格式为 git reset [<commit>] (让当前分支指向[<commit>]对象;[]表示可选,若为指定<commit>,则默认为HEAD,相当于不做任何移动);
revert是 恢复指定的几个commit对象 所修改的文件的内容,最简格式为 git reset <commit>... (n个<commit>对所涉及文件的修改将会在n个新的commit对象里被重置为修改之前的内容;...表示可以有多个<commit>被恢复)。“恢复文件内容”是通过“自动创建同原来的<commit>...数量相等的新的commit对象,来记录原来<commit>...其所修改的文件在被其修改前的内容”这样的方式实现的。
rebase命令,本质是 "BASE <commit>“ 之后 “REED <branch>”(被RE 的branch) 的commits完全被<branch>丢弃了,rebase之后<branch>经由的都是全新的拷贝对象所构成的崭新的commit链,不管你加什么参数这个本质是不变的;使用-i参数的交互式rebase,只是给了用户一个”选择哪些commits你希望被拷贝入崭新的commit链,哪些commits你希望不做拷贝不入崭新的commit链“的机会。
需要谨记的是:不要在已pushed到remote repo的commits上做rebase和reset操作,因为你push后其他人就可能pull到这些commits,并在上面展开工作;其他人若再push而你pull下来,这些commits将会再回到你本地的git repo。
对已pushed的commits做undo,最好的办法就是使用不会对分支提交历史有任何变更,只是新增commit对象的git revert。

http://stackoverflow.com/questions/495345/git-removing-selected-commit-log-entries-for-a-repository
http://stackoverflow.com/questions/1338728/how-to-delete-a-git-commit

都是用来查看历史的:log & reflog:
不加任何参数的默认的git log只是查看当前分支的commit记录。
reflog里记录的是所有导致branches的tip变更、branch切换等操作的历史;
但需要注意的是,reflog的功能,git log加上参数也完全做的到,如 git reflog就完全等价于
git log -g --abbrev-commit --pretty=oneline
但reflog的特殊性在于,它有指向被它记录在内的commit对象的引用,所以reflog记录里的commit对象是可达的(reachable),即使是dangling commits。

同样可以用来合并分支 rebase & merge:
merge在非fast forwad的情况下只是新建一个commit对象,其两个前驱父节点为merge前两个分支所指向的commit对象,并将当前分支改为指向这个新的commit对象;而rebase,会将被重新定义基准点的<branch>自和基准点<commit>在分叉以来的所有commit对象,重新生成一遍,以<commit>为嫁接点,逐个的将这些新的commit对象嫁接上去(默认嫁接到基准点<commit>上,通过子命令--onto可以指定任何的嫁接点),并将<branch>指向重造的commits链的tip。举例说明一下:假设Local Repo 中有 master 和 topic 两个 Local branches,当前分支为topic分支(A、B、C...代表commit对象):
            [master]
           /
A <- B <- C    
^
 \
  D <- E
        \
        [*topic]
git merge master 后:
            [master]
           /
A <- B <- C
^         ^
 \         \
  D <- E <- F
             \
             [*topic]
git rebase master 后:
//rebase会将被重新定义基准点的分支(未指定的情况下就是当前分支)自和基准点(这里就是master分支指向的commit对象)分叉以来的所有commit对象,重新生成一遍,以基准点为嫁接点,逐个的将这些新的commit对象嫁接上去(默认嫁接到基准点<commit>上,通过子命令--onto可以指定任何的嫁接点),并将被重新定义基准点的分支指向重造的commits链的tip。原来的D和E两个commit对象,还是存在于 DAG 中的,只是 master & topic 分支都不再经过它们;如果也没有任何其他branch or tag经过它们,则它们就是dangling commits;dangling commits在一定时间后会变为真正的Unreachable commits,Unreachable commits再经过一定时间后,会被Git的gc机制回收释放掉。
            [master]
           /
A <- B <- C  <- D' <- E'
^                      \
 \                     [*topic]     
  D <- E   





注意事项及功能性(组合)命令:
Viewing commit history of remote branch(不是查看本地branch的commit历史):
http://stackoverflow.com/questions/10736412/fetch-remote-log-not-the-commits
git fetch origin master
git log FETCH_HEAD

关于linux下 chmod命令对git项目文件的影响:
如果在linux下执行了chmod命令,并且chmod涉及到了git项目文件,如我这里执行了下面命令:
$chmod -R 777 /home
而我本地的git项目目录是位于/home/curLinuxUserName/Projects下的,结果再进入git目录,执行 $ git status 则会看到所有的项目文件都变成了modified:
curLinuxUserName@ubuntu:~/Projects/newsletter$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
#
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#	modified:   .gitignore
#	modified:   newsletter-compose-service/pom.xml
...
执行 git diff . 会发现所有的所谓变更其实都只是linux mode的变更:
curLinuxUserName@ubuntu:~/Projects/newsletter$ git diff .
diff --git a/.gitignore b/.gitignore
old mode 100644
new mode 100755
diff --git a/newsletter-compose-service/pom.xml b/newsletter-compose-service/pom.xml
old mode 100644
new mode 100755
...
原因:因为默认Git是将文件访问权限的变更也作为考量“文件是否被修改”的依据的。如果不想将文件访问权限的变更列入考量范围,可以通过修改 .git/config 文件,将其中的参数 filemode 的默认值 true 改为 false:
引用
[core]
          repositoryformatversion = 0
          filemode = false
或直接执行 git config 命令来做:
$ git config core.filemode false

三. git commit --amend 只能修改最新的commit对象(即HEAD),怎么修改任一commit对象?
http://stackoverflow.com/questions/1186535/how-to-modify-a-specified-commit
引用
假设你需要修改HEAD前的那个commit对象(即倒数第二个commit对象):首先 rebase -i:
git rebase -i HEAD~2
将交互式rebase中对应倒数第二个commit对象的命令由默认的pick改为edit,保存并退出,此时rebase过程已经在你需要editing的commit对象处停了下来;接下来,修改你希望在该倒数第二个commit上提交的项目文件,并使用
git add .
git commit --amend
将这些修改保存入当前这个倒数第二个commit对象;
最后,使用
git rebase --continue
继续完成整个rebase过程。

四. 怎么将一个 commit 对象拆分为多个 commit 对象?
引用
方法一:
结合使用 git reset & git add -p。见:
http://stackoverflow.com/questions/1440050/how-to-split-last-commit-into-two-in-git
如需要将最后提交的拆分为两个,则:
git reset HEAD~
git add -p 需要在part-commit-1提交的文件
git commit -m ‘this is part-commit-1’
git add -p 需要在part-commit-2提交的文件
git commit -m ‘this is part-commit-2’
方法二:
使用 git rebase -i。参加:
http://stackoverflow.com/questions/6217156/break-a-previous-commit-into-multiple-commits





技巧:
Auto Completion & Git Aliases:
http://git-scm.com/book/en/Git-Basics-Tips-and-Tricks
引用
Download the Git source code, and look in the contrib/completion directory; there should be a file called git-completion.bash. Copy this file to your home directory, and add this to your .bashrc file:
source ~/.git-completion.bash
If you don’t want to type the entire text of each of the Git commands, you can easily set up an alias for each command using git config. Here are a couple of examples you may want to set up:
$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.ci commit
$ git config --global alias.st status

Java + Maven常用.giteignore:
https://github.com/github/gitignore
#注意所有匹配的过滤格式后面不能有空格
# maven ignore
target/
*.releaseBackup
release.properties

# eclipse ignore
.project
.classpath
.settings/
*.metadata
.pydevproject

# idea ignore
.idea/
*.ipr
*.iml
*.iws

# temp ignore
*~
*.swp
*.bak
logs/
*.log
*.cache
*.diff
*.patch
*.tmp

# system ignore
.DS_Store
Thumbs.db

你可能感兴趣的:(git,scm)