这年头git基本都是项目开发的标配,之前刚好碰到了就花了两天时间系统学习了下。本文内容基本来自以下tutorial:Learn Git(建议直接去看原文,因为这个网站是有更新的)。这个是我看过对git进行版本控制和项目协作原理讲解最清楚的文档,就记下比较加深理解和记忆。
1.git是啥
Git是一种分布式版本控制系统(Distributed Version Control System),这是与之前流行的CVS,SVN之类的存在*库的系统明显差异,即在git中每个开发者的库都是完整的。git相比之前的VCS具有以下性能更强也更灵活,更安全(SHA1哈希算法)。在之后的why git for your organization模块里面大力安利了一波git给包括开发者,市场,管理,设计师,客户支持,人力资源一大波吃瓜群众。这里就不细说了,可以自己去看。
2.安装git
git在windows,linux,mac os平台上都可用,因此不管你在哪个平台上进行开发,只要使用git能对源码进行很好的管理。以ubuntu系统为例:
$ sudo apt-get update
$ sudo apt-get install git
至于windows系统的话直接下载安装包安装即可。
一般在安装完git之后会顺便把自己的git账号和邮箱设为默认值,简化以后的操作。
$ git config --global user.name "Emma Paris"
$ git config --global user.email "eparis@atlassian.com"
3.1新建代码库--git init
git init
命令用于建立新的库。这既可以将已经存在但是并未版本化的项目转化为git库,也可以新建一个新的空库。因此这个命令是开始新项目的时候的第一个git命令,并且一个项目这个命令只需要一次(之后会用git clone将现存的库复制到本机的库中)。执行这个命令以后在项目的根目录下会产生一个.git的子目录,这里包含了一切关于这个库元数据(metadata)。比如:
$ git init <directory>
这个命令将会在指定的目下下新建一个git库,这个文件夹下面除了.git没有别的内容。
$ git init --bare <directory>
而指定--bare参数则会新建一个空的git库但是省略了工作目录(没有.git文件夹,原本git中的内容直接在新产生的文件夹中,因此一般文件名中会自带.git后缀,比如my-project.git)。共享库一般都是用带--bare标签新建的。
为什么共享库需要用--bare建立
对于需要共享的*库,如果库是non-bare的,那么在push branch时可能会覆盖库中在本机上做的一些修改;而如果时bare的,因为没有工作目录,所以不可能在库中编辑文件以及commit(之后会讲这个git的核心命令),也就不存在这个问题。所以一般*库时bare的,而各开发者的本地库时non-bare的。
3.2复制代码库--git clone
之前也提到了,这个命令用于复制现存的库。复制后的库的操作将完全独立于原来的库,而在复制的同时会自动产生一个名为origin的远程链接指向原来的库,方便以后两个库之间的一些交互操作。于git init类似,这个命令在一个项目中也一般只需要执行一次。
$ git clone <repo> # 指定将库复制到当前文件夹
$ git clone <repo> <directory> # 将指定库复制到指定文件夹
$ git clone ssh://john@example.com/path/to/my-project.git # john是当前开发者的用户名
原始库可以在本地也可以在远程机器上通过http或者ssh链接获取。(ssh的端口可能会被某些防火墙block,所以一般会用http;但是用ssh来连接github时不用输入密码,更方便一些)。
git的项目协作模式:每个开发者的库都是完全的库,不需要像SVN那样通过中间库而互相之间可以push和pull commit。
3.3配置代码库--git config
这个命令可以让你直接通过命令行配置当前库的一些参数。
$ git config user.name <name> # 定义当前库所有commit所用的用户名
$ git config --global user.name <name> # 定义当前用户所有commit使用的用户名
$ git config --global user.email <email> # 定义当前用户所有commit使用的email
$ git config --global alias.<alias-name> <git-command> # 创建git命令别称(懒无止境)
$ git config --system core.editor <editor> # 定义文本编辑器来给当前机器用户调用git commit之类的命令时使用
$ git config --global --edit # 直接用文本编辑器打开全局配置文件
PS:这些参数也可以在一下文件中找到:
- (repo)/.git/config文件储存当前库的设置
- ~/.gitconfig 保存当前用户的设置。这些设置需要用--global标签才能更改。
- $(prefix)/etc/gitconfig储存当前系统的设置(没找到,不过感觉一般也用不到)
而当各级配置出现冲突的时候,库本地设置会覆盖用户设置,用户设置能覆盖系统设置。
4.保存修改--git add & git commit
项目开发必然包含是编辑-阶段缓存-提交这一流程。git add命令将当前工作目录中的一些改动保存到缓存区域,在正式提交(git commit)之前并不会影响库中的是实质内容(相当于是工作目录和项目历史之间的缓存区)。因此这一命令的使用非常常态化,每编辑修改一个或几个互相相关文件就可以git add,然后用git commit生成一个高度相关的快照。
$ git add <file> # 缓存所有指定文件中的变动
$ git add <directory> # 缓存指定文件夹中的变动
$ git add -p # 开启交互式缓存环节允许选择文件中具体那些变动存入缓存区
最后一个命令的操作方式在开启交互环境的时候会展示:y:缓存当前变动块;n:忽略当前变动块;s:将当前变动块分的更小;e:手动编辑变动块;q:退出交互环境。
而git commit命令则是提交缓存区中的快照到项目历史中,每一个提交的快照都可以认为是项目的一个安全版本。除了显式命令之外,git是不会主动修改任何一个提交的快照。
$ git commit # 打开文本编辑器让你输入本次提交的快照的描述信息
$ git commit -m <message> # 直接输入的字符串作为提交的描述信息
$ git commit -a # 提交工作目录下所有改动的快照。注意:这里只包括之前用git add追踪过的文件
在git中commit的快照是保存在本地库中的,于之前的SVN保存在*库这一点是明显的差异。与git add的缓存区类似,可以将本地库视为要编辑的代码和*库之间的缓冲区。这种方式带来的优势有很多:比如可以将一次功能更新分解成多个commit,让相关的commit聚在一起,方便提交*库之前对本地的commit历史进行整理。
git的一大核心特征就是commit的是快照而非SVN中的差异。git每次commit都将文件的所有内容都存快照中;而SVN每次保存的是文件中改动的地方。因此git对各commit快照的操作不需要整合之类的步骤,直接对目标commit进行操作即可。
具体操作实例:
$ git add hello.py
$ git commit
# 在编辑器中输入以下信息
#Change the message displayed by hello.py
#
#- Update the sayHello() function to output the user's name
#- Change the sayGoodbye() function to a friendlier message
一般输入信息的格式为第一行是不超过50各字母的总结,空一行,然后是具体改动的说明。
5.1 隐藏改动--git stash
如果当你一个功能开发到一半突然有一个紧急任务要马上做(码农日常),那么这个时候就需要用git stash这个命令把上次commit到目前的改动先隐藏起来使其不影响别的开发任务,等到你完成之后调出这个隐藏部分然后继续开发直至下次commit为止。
$ git status # 显示上次commit与当前状态下的有差异文件路径(有改动的,新添加的,以及未被追踪的)
On branch master # 有改动的分支
Changes to be committed:
new file: style.css
Changes not staged for commit:
modified: index.html
$ git stash
Saved working directory and index state WIP on master: 5002d47 our new homepage #保存stash到本地,将HEAD指向最近一次commit
HEAD is now at 5002d47 our new homepage
$ git status # 这种状态下就可以进行随意操作-比如新建commit,切换branch等,之后有需要再切回到stash的分支继续操作即可
On branch master
nothing to commit, working tree clean
$ git stash pop # 弹出之前最近的stash
On branch master
Changes to be committed:
new file: style.css
Changes not staged for commit:
modified: index.html
Dropped refs/stash@{0} (32b3aa1d185dfe6d57b3c3cc3b32cbf3e380cc6a)
$git stash apply # 与pop类似,只是这边弹出的stash只是一份拷贝。因此可以将这个stash用于多个分支。
On branch master
Changes to be committed:
new file: style.css
Changes not staged for commit:
modified: index.html
在默认情况下,git stash只会stash已经在缓存区域的变动(staged changes)和正在被git追踪的的文件中的变动(Unstaged changes),并不会stash工作副本下unstaged文件和被git定义忽略的文件(之后会在gitigore部分有介绍)。所以如果在之前的栗子中添加一个新文件但是没有用git add命令stage,那么这个就是untracked文件不会被stash。但是如果在stash命令中加入-u(--include-untracked)标签,那么未被追踪的文件也会被stash;而用-a(--all)标签则会把被git忽略的文件也stash。
$ script.js
$ git status
On branch master
Changes to be committed:
new file: style.css
Changes not staged for commit:
modified: index.html
Untracked files:
script.js
# ————————————————————————————————————————
$ git stash
Saved working directory and index state WIP on master: 5002d47 our new homepage
HEAD is now at 5002d47 our new homepage
$ git status
On branch master
Untracked files:
script.js
# -------------------------------------------------------------------------------
$ git stash -u
Saved working directory and index state WIP on master: 5002d47 our new homepage
HEAD is now at 5002d47 our new homepage
$ git status
On branch master
nothing to commit, working tree clean
对多个stash进行操作
stash的使用次数并没有限制,因此可以有建立多个stash。默认状态下stash被认为是建立stash时分支和commit顶部的WIP(work in process),通过git stash list可以查看所有的stash。如果stash个数比较多,可以用git stash save在stash时添加注释信息以便后续调用时查看。需要回到哪个stash状态的时候用git stash pop + stash id即可。
$ git stash list
stash@{0}: WIP on master: 5002d47 our new homepage # 5002d47是建立stash时最近的commmit id,master是stash时的分支名,stash@{0}就是stash的id
stash@{1}: WIP on master: 5002d47 our new homepage
stash@{2}: WIP on master: 5002d47 our new homepage
$ git stash save "add style to our site"
Saved working directory and index state On master: add style to our site
HEAD is now at 5002d47 our new homepage
$ git stash list
stash@{0}: On master: add style to our site
stash@{1}: WIP on master: 5002d47 our new homepage
stash@{2}: WIP on master: 5002d47 our new homepage
$ git stash pop stash@{2}
在不确定stash到底做了哪些改动的时候可以用git stash show来查看具体改动信息。
$ git stash show # 差异汇总
index.html | 1 +
style.css | 3 +++
2 files changed, 4 insertions(+)
$ git stash -p # -p/--patch标签用于显示具体差异
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..d92368b
--- /dev/null
+++ b/style.css
@@ -0,0 +1,3 @@
+* {
+ text-decoration: blink;
+}
diff --git a/index.html b/index.html
index 9daeafb..ebdcbd2 100644
--- a/index.html
+++ b/index.html
@@ -1 +1,2 @@
+<link rel="stylesheet" href="style.css"/>
部分stash
用git stash -p/--patch命令将会迭代文件中的每一处改动块来决定是否要stash这个部分。
$ git stash -p
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..d92368b
--- /dev/null
+++ b/style.css
@@ -0,0 +1,3 @@
+* {
+ text-decoration: blink;
+}
Stash this hunk [y,n,q,a,d,/,e,?]? y
diff --git a/index.html b/index.html
index 9daeafb..ebdcbd2 100644
--- a/index.html
+++ b/index.html
@@ -1 +1,2 @@
+<link rel="stylesheet" href="style.css"/>
Stash this hunk [y,n,q,a,d,/,e,?]? n
操作模式与之前的git add相同。
从stash处创建新分支
如果当前分支的变动偏离了stash中的变动,那么在调用stash时代时候就很可能产生冲突。这个时候就需要用git stash branch创建新的分支来加载stash中的变动。
$ git stash branch add-style stash@{1} # 从建立stash时的commit处建立并切换到新的分支add-style,然后载入stash中的变动。
Switched to a new branch 'add-stylesheet'
On branch add-stylesheet
Changes to be committed:
new file: style.css
Changes not staged for commit:
modified: index.html
Dropped refs/stash@{1} (32b3aa1d185dfe6d57b3c3cc3b32cbf3e380cc6a)
删除stash
如果之前储存的stash不要了可以用git stash drop/clear来清楚。
git stash drop stash@{1} # 删除stash@{1}分支
Dropped stash@{1} (17e2697fd8251df6163117cb3d58c1f62a5e7cdb)
git stash clear # 删除所有stash
5.2 git stash工作原理
stash其实是特殊的commit。.git/refs/stash中储存了一个特殊的标签指向最近新建的stash,而之前建立的stash则是通过当前stash标签的引用日志(reflog)进行指向。所以当指向stash@{n}的时候其实是指向了当前stash标签的第n条引用日志。而当stash命令执行的时候,会根据情况新建2或3条commit,同时.git/refs/stash文件中会更新一个指针用于指向新建立的commit。此外由于是commit,所以可以通过git log(后面会讲)来查看stash的历史记录。
$ git log --oneline --graph stash@{0}
*-. 953ddde WIP on master: 5002d47 our new homepage # 新的commit用于存储当前工作目录下被追踪的文件
|\ \
| | * 24b35a1 untracked files on master: 5002d47 our new homepage # 新的commit用于存储当前工作目录下未追踪的文件(这个commit只有在工作目录下存在未追踪的文件,同时--include-untracked或--all标签也同时使用的时候才会被建立)
| * 7023dd4 index on master: 5002d47 our new homepage # 新的commit用于存储缓存区(git add)
|/ * 5002d47 our new homepage # HEAD指针指向的之前存在的最近的一次commit
在stash之前工作目录的状态一般如下所示:
而当stash命令执后的状态如下:
带--include-untracked标签
带--all标签
6. 忽略文件配置--.gitignore文件
git把工作目录下的文件分为3类:tracked(追踪的)-之前有stage(git add)或者commit记录的文件;untracked(未追踪的)-尚未stage和commit的文件;ignored(忽略的)-被git特意忽略的文件。一般被忽略的文件包括第三方文件(各种包),源文件能够产生的文件或文件夹,编译后的文件(.pyc),运行过程中产生的日志文件(.log),隐藏的系统文件(.DB_store),个人的IDE配置文件等。而这些被忽略的文件的模式则是存储在.gitignore文件中。.gitignore文件可以存在多个,发生冲突的时候本地文件夹的.gitignore的配置会覆盖库根目录下的,根目录下的则会覆盖系统层面的,因此为了方便期间一般都是只在库的根目录下放一个。忽略文件的模式采用正则表达式规则,具体可以看这里,下面举几个常用的栗子:
**/logs # 当前库下所有名为logs目录下的文件(双*用于匹配当前库下面的任何文件夹),比如logs/debug.log,/build/logs/foo.log
*.log # 当前库下所有log后缀的文件,比如logs/debug.log, foo.log
!important.log # 除此以外的所有文件,与前一命令一起使用时则是表示除了important.log文件以外的所有log文件都会被忽略
需要注意的时.gitignore文件位于根目录下,因此可以被stage和commit到版本迭代中。如果只是想定义自己所需的忽略文件并不想commit到代码库中,可以将忽略规则添加到.git/info/exclude文件中,这个文件不会被stage,因此避免了可能被commit到公共库的情况。
如果需要将本机下所有库都设置相同的忽略规则,那么就可以用$ git config --global core.excludesFile .gitignore
命令,而这同时也就不需要考虑把.gitignore 文件放在哪的问题了。
如果需要忽视之前commit的文件,需要用git rm命令将文件重库中删除然后再添加相应规则到.gitignore文件中。
$ echo debug.log >> .gitignore
$ git rm --cached debug.log # --cached表示只删除库里的文件而保留工作目录下的。如果不用则两个地方都会被删掉
rm 'debug.log'
$ git commit -m "Start ignoring debug.log"
如果需要commit被忽略的文件,可以在git add的时候加上-f/--force标签;也可以用之前提到的!来指定进行例外文件。
$ cat .gitignore
*.log
$ git add -f debug.log
$ echo !debug.log >> .gitignore
#-----------------------------
$ cat .gitignore
*.log
!debug.log
$ git add debug.log
如果需要stash被忽略的文件,如之前所说,需要在git stash时添加--all标签。
而当你不确定某个文件由于哪个规则被忽略时,可以用git check-ignore命令查看忽略文件的规则。
$ git check-ignore -v debug.log
.gitignore:3:*.log debug.log # 格式未忽略文件的规则:规则所在行;规则;忽略的文件
7.1 查看工作目录和缓存区状态--git status
工作目录和缓存区的状态包括staged变动(存到缓存区),还未staged的变动以及尚未被追踪的文件,而那些被忽略的文件信息并不会有所显示。所以git status的输出一般如下:
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
#modified: hello.py
#
# 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: main.py
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
#hello.pyc
在commit之前及时git status可以防止一些偶然操作导致commit一些想要的变动。
7.2 产看commit历史记录--git log
git log只能用来查看commit的历史记录,然后选择需要炒作的commit快照。
```shell
$ git log #以默认格式输出commit历史
$ git log -n # 限制输出最近个commit
$ git log --oneline # 将每一个commit的信息压缩到一行中显示,很方便查看大量commit
$ git log --stat # 增加显示具体变动的文件和每个文件增减的行数
$ git log -p # -p/--patch具体显示每个commit的改动信息
$ git log --author="" # 显示某位开发者的commit,匹配信息可以是字符串也可以是正则表达式
$ git log --grep="" # 显示某种模式的commit,匹配信息可以是字符串也可以是正则表达式。
$ git log .. # 显示从since到until之间的commit,可以是commit ID,分支名称(master..some-brance),HEAD指针(HEAD~2:当前commit的往前数第二个commit)
$ git log # 显示某个文件相关的commit
$ git log --graph # 用文字图的形式显示commit
$ git log --decorate # 增加显示commit的分支名或者标签名
```
注意commit ID为40为的SHA1检验和(比如3157ee3718e180a9476bf2e5cab8e3f1e78a73b7),这样用于确保ID的唯一性和检验commit的完整性。
8.消除变动--git checkout/git revert/git reset/git clean
这些命令都能用来对历史commit进行处理,但是原理和效果都不太相同。
git checkout
这个命令的对象可以有3种:文件,commit,分支(checkout分支主要可以是用来切换分支用,在之后有具体介绍在这里就先跳过)。checkout commit时使整个工作目录都回归到选择的commit的状态且并不对目前的状态有任何影响;而checkout文件则是将具体文件回到特定commit时的状态而不影响工作目录下的其他文件。要注意的时checkout commit命令只是看一个只读操作,在查看旧版本或者之前的commit的时候不会对当前库有任何不良影响,因此是一个十分安全的操作。
$ git checkout <commit> <file>
commit可以用commit哈希码或者标签(tag)作为指定变量,但此时HEAD指针将会处于分离的状态(在正常状态时HEAD指针是指向某一个分支的最前端,而当checkout命令调用时,HEAD指针将会指向给定的commit)。
$ git log --oneline # 假设当前工作分支为master
b7119f2 Continue doing crazy things
872fa7e Try something crazy
a1e8fb5 Make some important changes to hello.py
435b61d Create hello.py
9773e52 Initial import
$ git checkout a1e8fb5 # 切换到a1e8fb5的commit状态,此时查看编译甚至修改文件都不会对之前分支的状态有任何影响
$ git checkout master # 切回之前的分支继续开发
而当checkout的对象为文件时,则会确实影响当前库的状态。当你重新commit到文件之前的版本时,也就将文件的状态退回到了特定版本。
$ git checkout a1e8fb5 hello.py # 将hello.py文件的状态切换到a1e8fb5 commit状态,文件确实发生改动
$ git checkout HEAD hello.py # 将hello.py文件状态切回到最近的状态
git revert
git revert回到之前的状态的工作方式是新建一个commit然后把需要回归的commit的状态快照复制到这个commit。这样做的好处就是之前commit的历史记录仍旧完整,使协作时不至于commit历史记录改变出现冲突。
# Edit some tracked files
# Commit a snapshot
$ git commit -m "Make some changes that will be undone"
# Revert the commit we just created
$ git revert HEAD # 回到了commit之前的状态
git reset
相比于git revert能保存commit历史的这种相对安全的方法,git set的工作方式是指向要回归的commit,并将现在的状态到需要回归的commit之间所有的commit快照都删除,因此调用git reset时候是直接改动了commit的历史记录,使用的时候(尤其是御别人协作的时候)需要格外小心。一般情况下只在本地库中使用git reset命令,而避免在共享的公共库上面进行这项操作。
$ git reset <file> # 移除缓存区中的特定文件,但并不会改变工作目录的状态。
$ git reset # 将缓存区状态回归到最近一次的commit状态,但不会改变工作目录的状态(也就是有机会git add回去之前的状态)
$ git reset --hard # 将缓存区和工作目录都回归到之前commit的状态(消去上次commit之后的所有变动)
$ git reset <commit> # 将缓存区状态回归到指定的commit状态,但并不改变工作目录的状态
$ git reset --hard <commit> # 将缓存区和工作目录都回归到特定commit的状态(消去指定commit之后发生的所有变动)
基于git reset 和git revert的操作方式,一般前者用于本地的撤销操作,而后者用于共享的commit的撤销操作。
如果在公共库中进行reset操作就会出现以下状况,git会认为你新建了分支,需要用merge操作进行整合。
下面两个栗子讲以下git reset最常见的用途:
1.删除缓冲区的特定文件(git add的时候不小心加错了)
# Edit both hello.py and main.py
# Stage everything in the current directory
$ git add .
# Realize that the changes in hello.py and main.py
# should be committed in different snapshots
# Unstage main.py
$ git reset main.py
# Commit only hello.py
$ git commit -m "Make some changes to hello.py"
# Commit main.py in a separate snapshot
$ git add main.py
$ git commit -m "Edit main.py"
2.删除本地commit历史(掩盖自己曾经做过的一些蠢到没朋友的一些测试)
# Create a new file called `foo.py` and add some code to it
# Commit it to the project history
$ git add foo.py
$ git commit -m "Start developing a crazy feature"
# Edit `foo.py` again and change some other tracked files, too
# Commit another snapshot
$ git commit -a -m "Continue my crazy feature"
# Decide to scrap the feature and remove the associated commits
$ git reset --hard HEAD~2 # 回归开发crazy feature之前的commit状态,并销毁痕迹
git clean
git clean会移除工作目录下未被追踪的文件(避免了用git status查看状态来一一确认需要删除的未追踪文件,非常方便)。需要主要的是这个命令跟rm命令一样是不可撤销的,使用的时候需要考虑清楚。而git clean命令也经常御git reset --hard命令一起使用,因为后者能只能对追踪的文件进行操作,两者一起使用能够确保搞那个钱工作目录回到特定的版本状态,这在项目编译后需要把编译文件去掉进行打包操作时特别有用。
$ git clean -n # 显示会被清除的文件,但并没有实际运行。(方便确认)
$ git clean -f # 删除为追踪的文件(除非把用git config --global 命令把clean.requireForce设置为false,-f是必须加的),但并不会删除未追踪的文件夹以及忽略的文件
$ git clean -f <path> # 删除指定文件夹下的未追踪文件
$ git clean -df # 删除未追踪的文件和文件夹
$ git clean -xf # 删除当前目录下未追踪和忽略的文件
9.重写历史--git commit --amend/git rebase/git rebase -i /git reflog
与之前的删除操作类似,git也提供了一些修改commit历史的命令,虽然这可能会导致一些内容的损失。
git commit --amned
这个命令直接用新的commit快照来替换之前的commit(注意时替换而非修改),这让我们能够很方便的修正一些有问题的commit快照。
正如git reset一样,这个命令也不要用来修正已经共享的commit,因为这相当于之前的commit被删掉了,如果别人的新功能依赖之前共享的commit那就很容易产生冲突。
# Edit hello.py and main.py
$ git add hello.py
$ git commit
# Realize you forgot to add the changes from main.py
$ git add main.py
$ git commit --amend --no-edit # 不改变之前的commit的信息,在忘了添加文件到缓存区的时候特别有用。
git rebase
这个命令是把某个分支以新的commit的形式整个移到另一个分支的最前端。由于有新建commit的操作,也就对分支的历史有所改动。效果如下:
git rebase <base> # 对象可以是分支ID,标签(tag),HEAD指针
这个命令主要用于保持一个线性的项目开发历史。当你需要把一个分支的新功能merge到master分支上,用rebase命令产生的历史就会如上图所示保持线性,以后查看的时候非常清晰。rebase也是把上游公共库的变动整合到本地库的常见做法(直接用merge可能会产生打乱原有commit的顺序,当你查看的时候可能会不明所以),但是与之前git commit --amend 和git reset命令一样,git rebase命令也需要避免在已经共享的commit上面。
# Start a new feature
$ git checkout -b new-feature master # 从master分支新建并切换到该分支
# Edit files
$ git commit -a -m "Start developing a feature"
# Create a hotfix branch based off of master
$ git checkout -b hotfix master # 发现有个问题需要hotfix下,就从master新建了hotfix分支
# Edit files
$ git commit -a -m "Fix security hole"
# Merge back into master
$ git checkout master
$ git merge hotfix # 修复完成后把hotfix分支整合到master分支
$ git branch -d hotfix # 删除hotfix分支
$ git checkout new-feature # 切换到new-feature分支
$ git rebase master # 把new-feature分支移动到master分支的最前端保持线性历史(因为之前插入了hotfix的commit,如果这届合并的话new-featrue的历史就会在hotfi之前了)
$ git checkout master
$ git merge new-feature
git rebase -i
-i标签是指进行交互式的rebase操作,可以对commit历史进行分割,改动,删除等操作,而不是一股脑把所有commit都移动到一个分支最前端。这就给了开发者很大的*,在开发的时候在本机上可以随便commit,只要在提交公共库之前用这个命令把commit调整好、去掉过时的commit、保持每个commit都是有意义的(装逼神器)。
# Start a new feature
$ git checkout -b new-feature master
# Edit files
$ git commit -a -m "Start developing a feature"
# Edit more files
$ git commit -a -m "Fix something from the previous commit"
# Add a commit directly to master
$ git checkout master
# Edit files
$ git commit -a -m "Fix security hole"
# Begin an interactive rebasing session
$ git checkout new-feature
$ git rebase -i master
# 在new-feature分支上有两个commit
pick 32618c4 Start developing a feature # pick是命令,如果需要执行别的操作用别的命令即可
pick 62eed47 Fix something from the previous commit
pick 32618c4 Start developing a feature
squash 62eed47 Fix something from the previous commit # squash命令即表示这个commit在rebase的时候会被去掉(在git log的时候可以查看到)
# 保存关闭编辑器后开始rebase
$ git checkout master
$ git merge new-feature
git reflog
git用名为reflog的机制追踪每个分支最前端的更新状态,因此每当HEAD指针的状态有变化(比如切换分支,pull变动,重写历史,增加commit)就有新的条目被加到reflog中。在重写历史以后,reflog包含了关于分支的旧状态,让你在需要的时候回到这些状态。注意的是git reflog只是8追踪状态变更操作。
$ git reflog # 查看本地库的reflog
0a2e358 HEAD@{0}: reset: moving to HEAD~2 # 最近的活动
0254ea7 HEAD@{1}: checkout: moving from 2.2 to master
c10f740 HEAD@{2}: checkout: moving from master to 2.2
$ git reflog --relative-date # 增加显示reflog的相对日期信息(比如2 days ago)
$ git reset --hard 0254ea7 # 使用git reset命令就可以回到之前的commit状态
到这里为止就是一些在git上建立项目,对项目进行基本的开发操作所需的最常见的命令。下一步就需要讲git进行项目协作时所需要的一些常用命令。