SourceTree 使用教程

SourceTree

安装sourceTree之前,需要先安装Git。Git的下载地址:https://git-for-windows.github.io

新版sourceTree内嵌了Git,但是,我们一般都会选择自己安装Git。

安装Git之后,在命令行执行Git命令:

如果有上面的显示,表明安装成功,我的Git版本是2.10.0。

然后安装SourceTree。安装完成后,打开,SourceTree为根据你系统的语言来显示中文还是英文,如果是英文,按如下步骤切换到中文:Tools -> General -> Repo setting -> language -> 汉语 -> OK -> 重启。

SourceTree打开后应该是长这样子,我这里项目列表中已经有了三个项目。SourceTree主要是把Git命令进行了可视化,让我们可以直接通过按钮来操作。下面我们就可以使用SourceTree来管理我们的Git项目了。

从远程服务器上克隆项目

从远程仓库获取项目的时候,会要求你输入用户名和密码,这个用户名和密码就是你刚才注册的那个,等待他加载完成。

这里演示的是公司的远程代码托管服务器。实际上,也可以从Github等托管服务器上克隆项目。注意,本文说的仓库和项目具有对应关系,你可以理解为一个仓库就是一个项目。

加载完成后,默认是这样子。我们可以查看该仓库下的项目文件,现在从远程仓库克隆的默认应该有三个文件,点击“在文件资源管理器中打开”。该操作会打开操作系统的文件管理器。

切换到“日志/历史”面板。

修改并提交我的项目

在本地仓库目录下,去新建一个test.txt文件。

保存,然后在sourceTree的"日志/历史"面板中,查看。

现在会提示你有新的未提交的改动。切换到'文件状态'面板。

该面板也有了变化。这里详细的显示了哪些文件有改动,同时有将改动的文件加入暂存stage、不暂存unstage、提交等选项。

实际上,你点工具栏区的“提交”按钮,也会进入到这个面板。这是因为,sourceTree(Git)会要求你先把修改加入暂存区,然后才能提交。因此,你注意到上面的提交区域的按钮是灰色的吗?

这与Git的运行原理和设定有关,Git存储的是修改,版本的变动,只是一个修改到另一个修改的指针的变化。在这里顺便介绍下Git的工作区和暂存区的概念,详细信息请阅读廖雪峰老师的教程。

工作区:就是我们看到的目录,就是我们实际操作的修改的那些项目文件所在的目录。对应我们这里的LearnGit目录。

版本库:工作区中有一个.Git的目录,通常这是一个隐藏目录。前面已经说过这个文件夹,表示它所在的目录是Git管理的仓库。这个文件夹下,存放就是Git的版本库信息,这就是Git的版本库。

暂存区:打开.git目录,Git版本库中有许多文件,其中最重要的就是称为index(或者stage)的暂存区,还有HEAD指针。而其他分支等信息存放在refs文件夹中。

HEAD指针指向的是我们当前所在的分支,此时,指向的就是默认的master分支。因为,我们只有默认的这一个分支。当然它的指向信息我们是看不到的,但是可以通过sourceTree项目列表中,该项目名称下的分支信息来查看当前HEAD指向的分支。

关于HEAD和分支的详细信息这里不再继续介绍了。下面着重看下暂存区:

暂存区是我们存放修改的地方,所有的修改都必须缓存到这里。当我们执行提交命令时,实际上就是把暂存区的内容提交到HEAD对应的分支上。初始时,暂存区应该是空的,当我们把内容提交到暂存区之后,就像上面这样,暂存区有了内容才能执行commit命令。提交之后,暂存区会被清空,等待下一次的暂存和提交。

从工作区添加修改到暂存区的过程使用的是Git add命令。sourceTree提供了可视化操作,我们直接使用stage ALL 或者stage selected来将修改提交到暂存区。

可以将修改添加到暂存区,当然也可以撤销暂存,对应unStage和unstage selected操作。

下面进行我们的第一次提交。把test.txt文件加入到版本库。

提交成功后,“文件状态”面板又恢复干净了。此时切换到“日志/历史”:

可以看到我们的提交成功了,历史图谱也向前推进了一步。此时,我们的文件已经被Git管理起来了,可以进行版本管理了。

注意,只有加入暂存区的修改才会被提交,因为Git管理的是修改。比如,你先做了第一次修改,然后加入暂存区,接着做了第二次修改,但是第二次修改没有加入暂存区,这时候如果提交,那么你提交的只会是第一次修改的内容。

这是因为Git只是把你第一次放入暂存区的内容情况,而你第二次的修改并没有在暂存区内。要把第二次修改也提交,请重新暂存并提交。

但是,此时我们提交成功,仅仅只是向本地的仓库提交成功了,不信你可以去远程仓库看看有没有最新的提交信息。本地仓库,我们只能在本地玩,虽然也能进行版本管理,但是别人看不到,我们还需要推送到远程仓库,让别人也能看到。

服务端推送,可能也会让你输入用户名和密码。但是,这些都只会输入一次,之后就会被sourceTree记住。

推送成功后,到服务端查看。

我们再做一次修改,在text.txt文件中,添加一句“hello world”,然后提交。

将本次修改推送到远程仓库,然后到远程仓库中去查看是否提交成功。

提交的历史信息图谱中,对应的作者信息,可以自己修改。工具 -> 选项 ->默认用户信息。

同时,保存的用户名及密码等信息在“验证”面板中。可以删除保存的用户密码等信息。
“Git”面板中可以配置SourceTree使用的Git。

创建Dev分支

实际的开发中,我们不会像上面那样直接在master分支上操作。我们应该建立一个Dev开发分支。

这个和svn的trunk和branch是一样的。我们实际的开发应该在Dev分支上进行。这样做是有理由的:

比如,我们只有一个master分支,现在已经发布了一个新版本1.0。然后新功能1.1版本也在开发中,但是1.0版本发现一个BUG,这时候我们要如何修复BUG呢?肯定不能舍弃现在正在开发的功能,也不能将现在的代码提交,从而影响已经发布出去的版本,这时候Dev分支就起作用了。

我们的master分支,应该作为主分支,这是一个稳定分支,这个分支应该是作为稳定版本发布的分支。我们的所有的开发工作只在Dev分支上操作,当Dev分支开发完成一个新功能时,再合并到master分支,由master分支发布。master分支有BUG时,从master新建一个临时temp分支,在temp修复BUG,然后合并到Master分支,这样master就可以继续发布。
修复的BUG再合并到Dev分支,这样也保证Dev分支上的代码是稳定的。最后再删除temp分支。

如果服务端有Dev分支,那么我们只需要检出Dev分支即可。这里很明显没有,所有我们要新建一个分支,然后把这个Dev分支也推送到服务端,这样别人也能使用这个Dev分支。

创建新分支是非常快速的,就是1秒钟的事。新分支与被分支的那个分支(这里的master)所处的状态是一样的。

创建新分支后,SourceTree(Git)会默认把HEAD指向新创建的分支,并且新分支与原来的分支所处的状态一样。

如果要切换分支,只需要在需要切换的分支上双击即可。这比使用Git命令切换分支要快很多。

然后把Dev分支推送到服务端。

推送成功后,在sourceTree远程选项的origin中,就会多了一个Dev分支,表明服务端也已经有了Dev分支。

到服务端查看是否创建Dev分支成功。

好,现在我们可以在Dev分支下工作了。切换到Dev分支,然后修改源文件,添加一句“New Dev branch”。

添加成功后,sourceTree的文件状态面板中会提示你有新的更改可以提交了。

注意,此时如果我们还未提交就切换到其他分支,这种修改也会带到其他分支的。直接双击master分支:

此时,我在Dev分支的修改还没提交。就是不管有没有加入暂存区,只要还没commit,那么这种修改在切换分支时,就会带到其他分支。

所以,可能你也注意打了,在切换分支时,sourceTree会发出警告,确定你是否切换分支。并且,在警告中通知你,可以选择清除修改。如果我们不清除修改,那么切换后它就会提示你有新的未提交的更改。这显然不是我们想要的结果。因此,我们在切换分支前,要确保修改已经提交。(还有另外一种方法贮藏,这个后面介绍)

切换回Dev分支,并提交。

提交成功后,Dev分支下就是我们最新的修改了。然后切换回master分支,双击master分支,查看源文件。

可见,master分支确实还处在上一个版本中。

只有一种情况会把修改带到其他分支,就是当前分支与要切换的分支处于同一个节点上。因为此时两个分支实际上他们的指针都指向的同一位置,所以会把修改带到其他分支。

当我们Dev分支有过一次提交,Dev的指针先前移动了一步之后,Dev的指针与master不在同一位置时,如果再次有过修改,然后再切换分支,这时候是会直接报错的。

因此,在切换分支前,无论如何要确保该分支已经被提交。或者使用后面提到的贮藏功能。

删除分支

我们直接从master新建一个temp分支出来,新建分支时,请保证当前的HEAD指向要被分支(master)的那个分支。

新建分支过程不再重复。

新建的分支,应该与它的源分支处于一样的状态。如果不信的话,你可以试试分别切换到master、Dev和temp分支,比较他们的源文件。

删除分支时,要保证HEAD不在要删除的分支上。比如现在,HEAD指向的就是temp分支,我们在temp分支上,右键 -> 删除分支 -> 弹出对话框 -> 确认 ->报错。此时,就会报如下的错误。

把当前分支HEAD指向其他分支,然后执行删除操作。

此时,删除分支成功。

在删除分支时,有时会报如下错误。

就是说要删除的分支有过新的提交,但是还没有进行合并。所以sourceTree会保证你的删除是可靠的,要求你合并之后再删除。如果,你确定不需要合并,那么在删除分支时选择‘强制删除’。

合并分支

在上面,我们的Dev分支已经超前一个版本。此时,我们确定Dev分支上的修改没有问题,并且可以纳入主分支了,那么我们就要合并分支。

合并分支要到目标分支上去操作,比如我们要把Dev分支上的修改合并到master分支,那么我们就要到master分支上去操作。

合并完成后,我们可以检查master分支下的文件,它已经与Dev分支下的修改一样了。然后将master分支推送到服务端。

注意,这里我的Dev分支我始终没推送到服务端。这只是为了说明,我们在本地的修改是可以不提交到服务端的,这就是我们通常利用分支来修复Bug和开发新功能的方式。

实际情况中,我们的Dev分支还是应该要提交服务端的,因为大家都在Dev分支中工作,而你从Dev分支出去的其他本地分支,是不应该提交到服务器的。

这里不提交Dev分支,只是为了演示可以这么做,而且为后面的功能介绍埋个点。

发布新版本

master合并完之后,就可以发布新版本了,我们使用tag标签来表示版本。Tag其实跟分支类似,只不过它是从某一个提交处建立的一个指针副本,因此它不能进行移动,但是可以添加多个tag,也可以删除。

添加完成后,在标签label中就可以看到新添加的标签了。

还可以将标签推送到服务端。

点击确定之后,在服务端查看版本信息。

标签的更多使用这里就不介绍了,其中一个功能就是根据标签回退版本,其实就跟普通的版本回退差不多。一般根据标签的回退情况较少,因为我们的master分支一般都会对应现在已经发布的版本,即使有Bug修复,也会在当前的发布版本上修复Bug,所以一般不会越过很多版本进行回退。

贮藏和修复Bug

修复Bug的情况,一般出现在对已发布的版本进行Bug修复。也就是对master分支进行Bug修复。

一般的情况是我们工作在Dev分支,然后要切换到master分支进行Bug修复。前面提到过,在切换分支时,要确保该分支已经提交。如果当前Dev分支可以提交,无疑是最好的选择,但是,如果当前不能提交呢?

我们可以使用贮藏功能。贮藏功能就是对现在的更改进行备份,注意仅仅是对更改进行备份。使用贮藏功能后,会让当前分支的工作区恢复到上一次提交的时候,从而使当前工作区变得干净。这叫做贮藏现场。

怎么贮藏现场?

现场贮藏之后,还可以恢复现场:

贮藏功能又分好几种情况。

  • 第一种文件未暂存

出现这种情况的原因是因为,此时demo.txt还没有被Git跟踪管理,所以它默认不会被贮藏,从而一直留在文件系统中。如果我们切换到master分支,这个更改(添加新文件也是更改)会被带到master中,sourceTree也会提醒有未提交的更改,提醒你提交这个文件。

为了避免这个文件在master中会被误提交,从而使该文件出现在不应该出现的版本中,我们应该使用下面这种方法。

  • 第二种文件已暂存

删除文件操作也需要加入暂存区才能贮藏现场。实际开发中,我们在贮藏现场时,都应该先把文件加入暂存区(主要是避免有新添加文件或删除文件)。

  • 第三种保留暂存区

贮藏现场时,如果勾选了“保留暂存的更改”选项,那么已经加入暂存区的内容都会被保留,并且在切换分支时,暂存区的内容也会被带到其他分支去。从而其他分支也能看到所做的更改。

注意,一般情况下,请不要勾选“保留暂存的更改”。因为,这可能会给你带来冲突,除非特殊情况,你确实需要把修改带过去才这么做。

好,现在了解贮藏功能后,就可以进行master分支Bug的修复了。

多人协作

前面演示的都是一个人开发的情况。下面将演示,多人协作。

首先,从远成仓库新克隆一份代码,取名WorkderB,用来模仿同事B的工作区,之前的LearnGit则用来模仿同事A。

同事B检出Dev分支后,现在Dev分支的代码还停留在helloworld这一步。那是因为同事A还没有把Dev分支推送到服务端。

回到同事A的工作区,把Dev分支也推送到服务端。过程不再演示,然后回到同事B的工作区,看看sourceTree有什么变化。

同事A提交之后,同事B的“拉取”(pull)图标上就会显示一些数字,表示当前有更新,显示的数字,就是服务端超前的版本数。

这个“拉取”图标的提示时间视情况而定,有的人很快就有提示,有的人要等很久,这种情况下我们可以进行手动拉取,而不必等它提示。因此,我们应该在每天上班时,都主动的“拉取”(pull)或者“获取”(fetch),以便获得最新的代码。

“拉取”完成之后,Dev分支下的代码应该和服务端,并且和同事A保持一致了。这个过程中,从服务端获取到的代码,和本地的代码,自动进行了合并。

通过“拉取”按钮获取服务端最新的更改时,可能会发生冲突。比如,同事B在自己的工作区中,对test.txt文件进行了修改,同事A也对test.txt文件进行了修改,并且同事A已经提交到服务端了。因此,同事B在提交时,需要先获取服务端最新的更新,然后才能进行提交,同事B通过“拉取”操作获取服务端最新代码后,就可能在test.txt文件发生冲突。然后,需要我们解决冲突之后,再次提交。

Git提供了一个更加可靠的“拉取”操作,来预测上面可能存在的冲突问题。就是sourceTree工具栏中的“获取”按钮。

“拉取”按钮对应 Git pull命令,“获取”按钮对应 Git fetch命令。

实际上pull和fetch并没有什么特别优缺点,这个可以由程序员自己来决定。fetch的好处是在合并之前可以看到你会更新的是哪些内容,最终决定是否进行合并。无论对于pull还是fetch,合并之后,该产生冲突的地方,还是会产生冲突,fetch并不能在合并之前就修改远程的提交,它只能让你看到有哪些修改,然后预测会不会冲突。当然除非你更改你本地的提交。

个人感觉,平常使用中,程序员用pull较多,而对于代码审核者做code review时使用fetch较多。

为了演示“获取”的功能,同事A先进行一次提交,在test.txt文件中增加一句test,提交命令信息为“test”,然后推送到服务端。

同事B,点击“获取”按钮进行获取。

点击“获取”后,弹出框使用默认的选择即可。

点击确定之后,我们来看看sourceTree有什么变化。

解决冲突

冲突发生的情况,主要是同事A与B都对同一文件的同一位置进行了修改。

产生冲突之后,需要我们解决冲突之后才能提交。这里我们使用一个文件比较工具来进行冲突处理。

这里我们配置一个外部文件比较工具,而放弃使用sourceTree默认的diff工具。这个外部文件比较工具是Beyond Compare。

下载安装Beyond Compare。现在beyond compare最新的版本是4.1.9,但是4.x版本都没有破解版,主要是最近beyond compare公司最近抓的严,我现在是直接使用的是试用30天的,买一个激活码差不多280元左右。

冲突解决完毕,就可以把新的提交推送到服务端了。产生的那个垃圾文件直接删除即可(后面会介绍方法来解决这个垃圾文件)。

另外一种会产生冲突的情况。

实际开发中,我们遇到的第二种情况会多一点。因为,我们鼓励每天上班之前都“拉取”(pull)一下,开始开发之前也“拉取”(pull)一下,以免和服务端相差太远。

解决冲突过程会产生一个垃圾文件,这个文件是冲突的备份文件。可以使用如下命令去去掉:

git config --global mergetool.keepBackup false

版本回退

换行符兼容

不同平台使用的换行符不一样,导致win或者mac或者Linux下,同样的代码因为换行符问题而错误识别为有新更新,甚至不能通过编译的情况。

这里建议在git项目根目录下,创建一个.gitattributes文件,并且添加一句:

* text eol=lf

另外,在命令行运行如下命令:

git config --global core.autocrlf false
git config --global core.safecrlf true

并且,建议把编辑器(IDE)的换行符统一更换成Unix格式。

具体问题,这里不演示。

对于,sourceTree的diff文件时,经常显示/No newline at the end of,这个问题的解决方式是,在末尾添加一个空行,这样方便文件比较。

撤销修改

撤销修改其实很少用到,因为,一般的撤销操作都会被编辑器(或者IDE)取代。当然,这并不是说,git的撤销操作就一定不会用到。sourceTree的撤销操作是“丢弃”。

先说什么时候会用到sourceTree(Git)的撤销修改这个操作,一种情况是,自上次提交后,你对源文件做了很多修改,你想一步就退回到初始状态,就是上次提交之后的状态;另外一种情况是,自上次提交之后,你对源文件作了修改,然后添加到了暂存区,但是还没有提交,然后你又做了很多修改,但是你想回到上一次保存到暂存区时的状态。

从上面可以看到,其实sourceTree或者Git的撤销修改虽然很强大,确不太好使用,直接使用命令的话,会容易混淆命令,使用可视化工具sourceTree,又操作麻烦。所以,如果不是特别需要,请直接使用编辑器的撤销操作。

sourceTree的其他操作

HEAD指针

在前面我一直强调Git只是指针的移动,那么Git的指针到底是什么,它到底如果移动的?

前面已经提到过,Git中的指针叫HEAD,它用来指向当前分支。

我们已经知道,git的每一次提交都会把他串成一条线,这条线是一条时间线,一条时间线就是一条分支。所以,一开始时,我们只有一个master分支,也只有一条时间线,所以,我们的HEAD指向master,然后master又指向我们的提交线。

这里有个概念要弄明白,HEAD指向的并不是提交,而是指向分支,master才是指向提交的,所以,HEAD指向的就是当前分支。由此可见,HEAD和master其实都是指针。

当有新提交时,时间线节点增加一个,master向前移动一步,然后HEAD也会跟着移动。

当创建新分支dev时,实际上是新创建了一个dev指针,但是,dev是从master分出去的,所有,初始时,dev和master在同一个节点。

然后HEAD指向了dev。可以看出,创建一个新分支是多么的简单,仅仅只是创建一个新指针,然后HEAD改变下指向,没有文件的拷贝,所以,它在1s内就能完成新分支的创建。

创建新分支后,HEAD指向了新分支,这时候的提交操作,就对应dev了。

在dev上的提交,仅仅只是dev向前移动了一步,而master还在原来的地方没动。

那么,我们的合并操作又是怎么办的呢?合并操作,其实仅仅只是把master指向了dev当前所在的提交,这还是一个指针移动过程。

所以,git的合并同样非常快速,工作区的内容没改变(其实只有很小的改变,因为要切换回master操作,这会使文件发生一些改变),然后就是指针的移动而已。

删除分支,也非常简单。仅仅只是指针的移除,没有文件系统的改变。

删除分支后,已经合并的master分支并没有任何影响,仅仅只是指针的移除。

从SVN迁移到Git

从SVN迁移到Git,不仅需要源代码迁移,同时我们还希望SVN上的commit信息也能迁移。这里我们使用Subgit工具。

进入subgit的官网,下载subgit的zip包。

下载完成后,解压。然后把解压出来的bin目录添加到环境变量。

然后,新建一个空目录,建立一个authors.txt文件。

authors.txt文件的作用是从svn导出commit信息时,将svn用户信息对应到git用户。它的格式如下:

svnUser = Git User <user@example.com>

前面是svn用户的用户名,然后是对应的git用户名和邮箱。我这里已经配好了我们公司的用户信息mapping文件:

liuxb = liuxb <xingbangl@XXX.com>
liaoct = liaoct <liaoct@XXX.com>
kangm = liaoct <liaoct@XXX.com>
zhuangtj = JesseZhuang <XXX.com>
chenlh = chenlh <chenlh@XXX.com>
zhoujia = zhoujia <zhoujia@XXX.com>

上面的邮箱地址,我隐藏掉了,用户可以自己去配。本公司的员工可以来找我分享。

然后在这个目录下执行如下命令:

subgit import --non-interactive --authors-file authors.txt --svn-url https://svnurl/hxycf ycf.git

上面的svnurl请替换成相应的svn地址,ycf.git是我们从svn拷贝过来的源码和一些依赖等文件的存放目录,名字可以自己定义,等待subgit执行完毕。

执行完毕之后,还需要一步操作,修改ycf.git\subgit目录下的authors.txt文件,把我们刚才建立的那个authors.txt文件内容拷贝过来即可。这里主要是为了保证用户信息彻底匹配成功。

然后,还是在刚才那个目录下继续执行如下命令:

git clone ycf.git com.ycf.git

这个操作主要是把刚才下载下来的那些文件,初始化为一个干净的本地git仓库。这个仓库的名字叫com.ycf.git。

执行完毕后,现在的com.ycf.git中的内容就是svn中trunk分支的内容了。有了本地仓库,然后将本地仓库与远程仓库链接起来。

建立一个远程仓库,过程不再重复。然后执行如下命令:

git remote set-url origin http://xxx/ycf_new.git

这一步,是把当前本地仓库的url设置为远程仓库的地址,从而把本地仓库和远程仓库链接起来。然后将本地仓库的代码推送到远程仓库:

git push -u origin master

然后查看远程仓库。

已经迁移成功了,再在sourceTree中查看下。

完美,源代码和提交信息都完整的迁移了,😀 。

推荐阅读更多精彩内容