Jun 24, 2011

git

请阅读 Pro Git

Good practice

关于建立repository,参见下面“建立...”一节。

分支
master
develop
topic
issue

$ git pull --rebase 可以及时解决冲突
$ git push

git push 不允许非 fast forward 的push,因此必须先pull-merge/或者 pull-rebase。推荐使用 pull --rebase

$git reflog 找回删除的commit

$git gc 垃圾回收,执行这步要想清楚,回收以后,就找不回来了


github 现有本地仓库再有远程仓库,因此要手动添加 origin remote
并且push时,加 -u,让git知道本地分支和远程分支的关系


git remote add origin https://github.com/rivuletish/tempo.git
git push -u origin master


首次commit时可能有很多文件
git add -all
全部添加
注意先创建 .gitignore 文件

Config

config file 查找顺序

  1. /etc/gitconfig
  2. ~/.gitconfig
  3. .git/config

$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com

Help

git help <command>

建立和克隆 Repository, 远程 Repository

在对自己的代码进行版本管理时,可以在把工作目录作为管理目录,建立 repository
cd my_project

git init
git add --all
git commit -m "My initial commit message"

这样做有一点不保险,要是工作目录遭到物理损坏,则会丢失信息。保险做法是另外建立一个远程 bare repository。bare repository 只包含version control files,没有working files。能够被push和pull,不能commit。

mkdir my_project.git
cd my_project.git
git init --bare

然后在工作目录增加 origin remote,并执行 push -u,告知 branch push/pull关系
cd my_project
git remote add origin git@example.com:my_project.git  (或 file:///j/my_project.git)
git push -u origin master

此后,在其他目录下或别人可以克隆该远程仓库,实现共享和协同合作
cd other_dir
git clone git@example.com:my_project.git        (会在 other_dir 下建立目录 my_project)
git clone git@example.com:my_project.git .        (本命令多一个点,直接将my_project的内容克隆进other_dir,不建立目录 my_project


注意首次commit时可能有很多文件,先创建 .gitignore 文件
git status 确认需要的文件是否都显示了,然后
git add --all
全部添加

.gitignore

告诉 git 别管某些文件,比如

  • .a
  • .lib
  • .exe
  • .bak
  • *~
  • 等临时文件和无需被版本管理的文件


# a comment - this is ignored
*.a       # no .a files
!lib.a    # but do track lib.a, even though you're ignoring .a files above;! 特例
/TODO     # 以/开头,意味着从 project 主目录开始算;only ignore the root TODO file, not subdir/TODO
build/    # 以/结尾,意味着这是一个目录;ignore all files in the build/ directory
doc/*.txt # ignore doc/notes.txt, but not doc/server/arch.txt
.gitignore 文件接受一种类似于正则表达式的 file-glob pattern,但是不同于正则表达式,没有 repetition 规则

  • * 任意字符,不需要正则表达式的 .*
  • ? 一个字符,不需要正则表达式的 .?
  • [abc] 匹配 a 或 b 或 c
  • [0-9]

.gitattributes

可以告诉git哪些文件是二进制的
*.doc -crlf -diff

-crlf 不要管换行
-diff 不要做diff

记录修改

文件状态

  • untracked
  • unmodified
  • modified
  • staged: 记录在案,要 commit 到 repository 的
最好理解这儿的修改是广义的,包括修改内容,也包括删除文件

add untracked file, 应该变为 staged
因此有三个地方

  • repository
  • working directory:目录的当前内容
  • staging area: 记录在案的,要被 commit 的改变

staging 实际上是给文件做了一个快照,只不过这个快照还没存到 repository 中(还没 commit)
你可以修改文件,staging 文件,再修改文件,这样文件既是 modified,又是 staged 的,而且它们还不同

$ git status
$ git add file (这个命令 stage modified file or untracked file),即修改的文件也用 add file

$ git diff: working directory - staging area 即 staging 以后又做了哪些修改, 不包含 untracked files
$ git diff --staged (or --cached) staging area - last commit,即记录在案的内容和仓库里的有什么不同 $ git diff HEAD: working directory - last commit



保存修改

git commit -m 'message'
如果觉得先staging再commit太繁琐,可以省略 staging 步骤,直接 commit working directory 的内容:
$ git -a -m "message"

删除文件,不跟踪文件,移动文件

rm file 或 DOS del file: 删除文件(从硬盘中删除),没有staged
git rm file: 删除文件(从硬盘中删除),并staged
git rm --cached file: 不跟踪文件,可以使用 .gitignore 的 file-glob pattern,注意用在命令中,特殊符号要加\,如\*
$ git mv file_from file_to 修改文件名
例如
$ git mv  README.txt README
相当于
$ mv README.txt README
$ git rm README.txt
$ git add README

Undoing,放弃变动

Changing Your Last Commit,commit 以后发现忘了啥
比如有 untracked 的文件忘了加,或者有个文件要修改一下
那就把untracked 的文件加入到 staging area,修改文件,并加入 staging area
$ git commit --amend 用 staging area来代替上次的commit

Unstaging a Staged File,记录在案后悔了
$ git reset HEAD file-name

Unmodifying a Modified File,作出的修改不想要了
$ git checkout -- benchmarks.rb

查看 Log

查看所有 commit tree
有多种方式
git log --graph --oneline --all
推荐使用
gitk --all
一目了然

git log [...]
参数如 --author=Kevin
过滤器,过滤输出符合要求的 commit
-(n) Show only the last n commits
--since, --after Limit the commits to those made after the specified date.
--until, --before Limit the commits to those made before the specified date.
--author Only show commits in which the author entry matches the specified string.
--committer Only show commits in which the committer entry matches the specified string. (author 和 comitter 是不同的人)
--grep          match keywords in commit message
如果 --author --grep 合在一起用,必须加 --all-match

控制每一个 commit 的输出内容
-p Show the patch introduced with each commit.
--stat Show statistics for files modified in each commit.
--shortstat Display only the changed/insertions/deletions line from the --stat command.
--name-only Show the list of files modified after the commit information.
--name-status Show the list of files affected with added/modified/deleted information as well.
--abbrev-commit Show only the first few characters of the SHA-1 checksum instead of all 40.
--relative-date Display the date in a relative format (for example, “2 weeks ago”) instead of using the full date format.
--graph Display an ASCII graph of the branch and merge history beside the log output.
--pretty Show commits in an alternate format. Options include oneline, short, full, fuller, and format (where you specify your own format).
git log [...] --graph 图形方式,显示树形父子关系

gitk 额外的一个图形查看程序,可以用过滤器作为参数

和 remotes 一起工作

clone 的仓库默认remote 为 origin
列出 remote
$ git remote [-v]
$ git remote -a
$ git remote -r
添加 remote
git remote add remote-name remote-url
$ git remote add pb git://github.com/paulboone/ticgit.git

git clone 后本地就是一个仓库,并且仓库没有等级的关系(确认?)
假如你从A那边clone仓库,B,C 又从你这儿克隆仓库,则 B,C 也可以作为你的remote

$ git fetch remote-name 从 remote 那里获取他有你没有的信息
将远程仓库的所有分支提取到本地,成为远程分支。远程分支名字为 remote-name/branch-name
From git://github.com/paulboone/ticgit
 * [new branch]      master     -> pb/master
 * [new branch]      ticgit     -> pb/ticgit
git fetch 不尝试 merge,而 git pull 则尝试 merge
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.

git push origin master
将你的branch master提交给 origin

  1. 注意如果远程仓库的管理员 checkout 了 master分支,git 不允许你 push。因为他可能有没commit的改动,你的push会造成混乱。
  2. 不允许非fast forward的push,你可以pull-merge 或者pull --rebase,再push
git remote show remote-name

branch

git 的工作原理,请看 What a Branch Is
  • git commit 保存的是变化的文件的快照(称为blob),而不是Delta(变化)
  • 如果文件没有变化,commit 就不保存它的快照
  • 快照组成一棵树,commit 就是一个 struct,包含指向这棵树的指针
  • commit 按照历史依赖关系,也组成一棵树,比如B是A修改得到的,则 A<-B,B是A的子节点 
merge 依赖于多个 commit,因此有多个父亲节点(只有两个?) 
three way merge: 依赖的两个 commit 以及它们的最小共同祖宗节点,三方来merge 

branch 是 commit 树的一个分支,从root开始算起 
从实现上来说,branch 是一个指针,指向该分支最后的 commit(但是不一定是树叶) 
commit 一次,branch指针就向后移动,指向最新的 commit 

默认的 branch 为 master
HEAD 也是一个指针,指向当前checked out 的 branch,也就是你在上面进行工作的分支

$git branch branch-name 建立一个branch指针 
$git checkout branch-name 将HEAD转移到branch,并提取它最新的commit 
checkout 才算是在该branch工作 

merge: 要先checkout到要 merged into 的branch


用A merge B

git checkout A
git merge B

fast forwarding: B依赖A,将A push upstream(commit树的箭头方向为downstream),fast forwarding 只是指针移动
three way merging: A,B及其最小公共祖先节点



删除 branch
git branch -d branch-name

处理 merge conflict 流程

  1. git merge branch-name 发生 conflict,git 在冲突文件中自动加入冲突说明
  2. git status 查看冲突文件
  3. 修改文件,并且删除git加入的注释
  4. git add file
  5. git commit -m "message"

由于 merge 是新建一个commit,解决冲突并不会改变被merge 的两个commits

Branch 管理


git branch 查看 branch(不包含 remote branch)
git branch -v verbose
git branch -a all(包含 remote branch)
git branch -r 只查看远程分支
git branch --merged (在当前分支下被merged的分支,这些分支的信息都已经被当前分支整合了,可以被删除)
git branch --no-merged (这些分支包含当前分支没有的信息,不能被删除)

git branch -d branch-name 删除分支

分支工作流程

Branching Workflows 多读几遍


master branch: stable release
develop: 1. 用来测试稳定性,稳定了就merged into master 2. merge topic branch
topic: 开发功能,消除bug,成功了就merged into develop。不成功就删除。topic branches 都是短期存在的分支

topic branches
图中的master为develop更加符合上面一张图

topic branches 可以很随意的,想到什么点子或者需要消除bug就开一个分支,甚至分支再开分支(iss91v2),成功了就merged 到develop中,不成功就删掉

remote branch

remote branch名字为 remote-name/branch-name
如 origin/master

remote branch 只是一个 reference,它让你了解远程仓库的信息,是只读的,你不能移动它,不能commit.

你可以checkout remote branch 到本地,这样本地的branch 跟踪 remote branch
你可以使用 pull, push, git 会自动找到对应的 remote branch

如果使用Pull,会将remote branch merge 到 Local branch

一般工作流程

  • git clone url #获取远程仓库的所有信息,包含远程仓库所有的branches,作为remote branches,并且git 自动把 origin/master checkout 到本地 master;此时本地只有 master 一个 branch
  • 你在本地master分支工作,可以另开本地分支
  • git fetch remote-name 获取远程仓库信息,remote branches 会根据远程仓库的变化而移动
  • 你可以查看这些远程分支 $git checkout remote-branch-name; 这时候你在前面提到过的 detached head state; 
  • 你可以 merge 远程分支到你的本地分支
  • 你可以checkout 远程分支,在此基础上工作 $git checkout -b local-branch-name remote-name/branch-name (or $git checkout --track remote-name/branch-name)
  • checkout 以后你可以 pull 和 push
  • $git pull 或者 git pull --rebase
  • 你有工作要分享 $git push remote-name local-branch-name:branch-name-on-remote or $git push remote-name branch-name
删除 remote branches;记住 remote branch 是只读的,删除远程分支是删除远程仓库的分支
$ git push remote-name :branch-name

Rebasing

$ git checkout topic
$ do stuff, commit
$ git rebase master
rebasing 不同于 merge,它将公共祖宗节点到iss91 head 的变动,逐一的加在 master head 上
A---B---C topic
 /
D---E---F---G master

如果是 merge topic

  A---B---C  topic
 /         \
D--E--F--G --H master

而rebase执行后

              A'--B'--C' topic
             /
D---E---F---G master
这样做的好处是,历史变成线性的,没有分叉,很整洁。管理员只要把master fast forward 到 topic,或者丢弃topic
注意。管理员不用再做merge的工作。
Do not rebase commits that you have pushed to a public repository.

Good practice:
1. 在本地 rebase 清除分叉的工作,push 到远程
2. 不rebase已经push过的commit
3. $ git pull --rebase

处理历史

在commit 62be,你碰到问题,作出了改动,并commmit了。然后你想跳回到62be看看,也许换个角度解决问题更好呢。
$git checkout 62be
这时你不在任何branch上,而是在一种“detached HEAD”状态,你可以做一些修改,甚至commit

你觉得新的想法行不通,可以checkout到其他branch,你刚才在detached head状态的commit会被抛弃掉。

或者你觉得新的想法不错,要保留下来,那就新建一个branch
$git checkout -b new_branch #in detached head state

好吧,或许英明的你从开始就知道要从commit 62be新建一个branch
$git checkout -b new_branch 62be #in other branch

换个情形,在commit 62be,你碰到问题,作出了改动,并多次commmit了。但是你倒霉的发现你作出的改动是错的,所以你想回到62be,抛弃后面的commit(这些commmit还在,也有办法找到它们,除非你执行了git -gc)
$git reset --hard 62be

最后一种情况, 9924 <- 62be <- cb78 <- 1228(HEAD),你发现从9924 到 62be的改动是错的,要抛弃这个改动。你可以把9924 到 62be的改动“反作用” HEAD
$git revert 62be
原理上 git 新建一次commit,注意并不修改 cb78 和 1228
9924 <- 62be <- cb78 <- 1228 <- 97c2(HEAD)

0 comments: