Maven 从陌生到入门

基本介绍

官网: http://maven.apache.org/

主要功能:

  • 工程管理(工程创建、编译、测试、发布),其中工程依赖管理功能非常强大,也是我们要使用它的主要目的

版本说明:

  • 3.3.+: JDK1.7 及以上
  • 3.2.+: JDK1.6 及以上

POM (Project Object Model)

Maven 的运行是基于一个 XML 格式的 POM 文件的,下面是一个简单的 POM 文件的例子:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zhilehuo</groupId>
    <artifactId>zlhcommon</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.4</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <distributionManagement>
        <repository>
            <id>user-deploy</id>
            <name>Releases</name>
            <url>http://bjoffice1/nexus/repository/maven-releases/</url>
        </repository>
        <snapshotRepository>
            <id>user-deploy</id>
            <name>Snapshot</name>
            <url>http://bjoffice1/nexus/repository/maven-snapshots/</url>
        </snapshotRepository>
    </distributionManagement>

</project>

中央库(Central Repository)

每个 Maven 项目都可以将自己发布到中央库,发布后其它人就可以在自己的项目中依赖你发布的项目。另外可以自己建私服,将项目发布至自己的私服上。私服是合法的,后边会介绍如何设置仓库地址 [点我直接跳过去]。

项目坐标(Coordinates)

  • groupId: 组织或工程的标识,全局唯一
  • artifactId: 项目名字
  • version: 版本
  • packaging: 打包格式,比如 jar/war/...
  • classifier: 类型?翻译不准确,比如一个项目打出来多个 jar 包,分别适配 jdk1.5 和 jdk1.7,就可以定义两个 classifier 分别叫 jdk5 和 jdk7(比如net.sf.json就是这么干的),还有常见的项目发布时,同时发布了 source 和 doc 包,也是通过不同的 classifier 来实现。

项目依赖(Depencencies)

Maven 一个非常重要的功能就是定义项目之间的依赖关系,这给我们带来的好处就是,当我们引用一个库时,只需要在 POM 中配置要引用这个库即可,而不需要再去手动下载这个库依赖的其它库。
定义项目依赖时,需要用到如下参数:

  • groupId,artifactId,version: 必填的 3 项,定位一个依赖
  • classifier,type: 不一定每个项目都需要,type 相当于前面的 packaging
  • scope: 定义依赖的有效范围,比如 javax-servlet-api 只在编译时使用,部署时不需要,那么 scope 就要设置为 compile,以免打 war 包时将这个包也打进去

发布项目(Deploy)

我们自己的项目可以发布到 Maven 库中(中央库,或是私服),发布时有两种版本类型:SNAPSHOT版本和Release版本,SNAPSHOT就是开发中的版本,可能会被频繁更新,Release则是正式发布的版本,且相同版本不允许覆盖。
定义项目发布时,需要在 POM 中做如下配置:

<distributionManagement>
    <repository>
        <id>user-deploy</id>
        <name>Releases</name>
        <url>http://bjoffice1/nexus/repository/maven-releases/</url>
    </repository>
    <snapshotRepository>
        <id>user-deploy</id>
        <name>Snapshot</name>
        <url>http://bjoffice1/nexus/repository/maven-snapshots/</url>
    </snapshotRepository>
</distributionManagement>

解释几个字段:

  • id: 引用的用户认证 ID,这是在 settings.xml 文件中配置的,下边详细说明。
  • name: 名字而已,没啥用
  • url: 要提交的仓库地址

发布时,如果当前版本号带有-SNAPSHOT后缀,则自动发布至 snapshot 仓库,否则就发布至 release 仓库。
再来看下 settings.xml 中的认证配置:

<servers>
  <server>
    <id>user-deploy</id>
    <username>deploy</username>
    <password>123456</password>
  </server>
</servers>

无需太多解释,id就是前边pom.xml引用的字段。

Maven 的配置文件

Maven 常用的配置文件有两个:setting.xmlpom.xml

  • setting.xml有一个全局的配置,和一个可选的用户自己的配置。全局的 setting 一般在 Maven 的安装目录中,比如在我的 Mac 中,他存在于/Users/nianxingyan/work/develop/tools/apache-maven-3.2.5/conf/settings.xml。该配置文件中要是关于 maven 的一些全局设定,比如仓库(Repository)的位置,项目部署时使用的认证信息。
  • pom.xml一般位于每个工程的根目录下,都是关于项目本身的配置,比如坐标、依赖关系等。

设置仓库位置

setting.xml文件中,涉及到两个设置,一个是mirror,用来配置一个仓库镜像地址,一个是profile,用来配置ReleaseSNAPSHOT版本使用的镜像。
先来看 mirror 的配置:

<mirrors>
  <mirror>
    <id>nexus</id>
    <mirrorOf>*</mirrorOf>
    <name>Zhilehuo Inc. Maven Central</name>
    <url>http://bjoffice1/nexus/repository/maven-public/</url>
  </mirror>
</mirrors>

我来试图解释一下几个字段:

  • id: 镜像的标识
  • mirrorOf: 可以写个特定的名字,用来后面被 profile 引用,也可以是*,意思就是全部
  • name: 没啥用,说明而已
  • url: 仓库的地址

再来看看 prifile 的配置:

<profiles>
  <profile>
      <id>development</id>
      <repositories>
          <repository>
              <id>central</id>
              <url>http://central</url>
              <releases><enabled>true</enabled></releases>
              <snapshots>
                  <enabled>true</enabled>
                  <updatePolicy>always</updatePolicy>
              </snapshots>
          </repository>
      </repositories>
      <pluginRepositories>
          <pluginRepository>
              <id>central</id>
              <url>http://central</url>
              <releases><enabled>true</enabled></releases>
              <snapshots><enabled>true</enabled></snapshots>
          </pluginRepository>
      </pluginRepositories>
  </profile>
</profiles>

挑重点解释一下:

  • repositoriespluginRepositories: 分别代表ReleaseSNAPSHOT版本的仓库配置
  • url: 这里可以写一个实际的仓库地址,也可以引用上边 mirror 中配置的地址。http://central就是引用一个叫central的镜像,这就是上边的mirrorOf字段,所以这里其实就是命中了*这个镜像。
  • release.enablesnapshots.enable: 配置这个仓库中哪些类型的版本是有效的。
  • snapshots.updatePolicy: SNAPSHOT版本的更新频率,always就是每次编译都检查新版本,此外还有daily等。SNAPSHOT的默认频率好像是dailyRelease默认的更新频率应该是never,就是一旦依赖关系下载完成后,只要本地有缓存,就不会去仓库中检查更新。

此外,prifile 还要配置一个 activeProfile:

<activeProfiles>
  <activeProfile>development</activeProfile>
</activeProfiles>

这里的development就是刚才profile中的id字段。


常用 maven 命令

maven 的可执行程序叫mvn,命令格式为 mvn [options](https://hacpai.com/tag/options) [<goal(s)>] [<phase(s)>]

我用得也不多,就简单说一下我理解到的吧,如果不对请告诉我:
goal里边包含phase,可以理解为phasegoal的子任务。有些任务执行时会将goalphase直接写成goal:phase的形式,比如mvn jetty:run。下边写到的几个命令应该是goal级别的。

clean

用来清理项目。mvn 编译时生成的中间文件会放在target目录,这个命令其实就是将target目录清空。

compile

编译项目,这中间就会进行依赖关系的检查和下载。

package

打包项目,这个步骤包含了compile,并且会将当前项目打包成一个jar(也有可能是其它格式),放在target目录中。

deploy

发布项目,根据项目版本号来决定发布至Release或是SNAPSHOT仓库。


Nexus 介绍

Nexus 是一个开源的 maven 仓库服务,我们可以用它来搭建我们自己的“私服”。我已经在 192.168.10.8 上将 Nexus 环境搭建好了,一会可以试一下。

Nexus 中可以创建多个仓库,仓库在创建时有 3 种类型:
proxy
这是一个代理,一般情况下,我们会创建一个仓库来代理 Maven 的中央仓库,这样我们在公司内部就可以通过 Nexus 来访问中央仓库了,带来的好处就是,别人在中央仓库下载过的包,下次再下载时就不用再去远程下了,节省了带宽和时间。

hosted
本地仓库,相当于是一个完事的仓库,我们自己发布的项目,如果不想发布到 Maven 中央库,主可以发布到我们自己的hosted仓库中。

group
这是一个仓库集合,当公司内部拥有不只一个仓库时(同时拥有proxyhosted仓库的情况还是很常见的),为了不让客户端将所有仓库都设置一遍,就可以创建一个group类型的仓库,将其它的仓库都集成进去,这样客户端只需要设置这个group仓库就可以了。

Nexus 安装后,默认创建了 4 个仓库:

  • maven-central: Maven 中央库的代理 (proxy)
  • maven-release: 用来存放私有Release版本的仓库 (hosted)
  • maven-snapshot: 用来存储私有SNAPSHOT版本的仓库 (hosted)
  • maven-public: 上边 3 个仓库的集合 (group)

注意: maven-releasemaven-snapshot是为了管理方便,人为分出来的两个库,其实ReleaseSNAPSHOT版本的库是可以同时发布到一个仓库中的。

在我们的 Nexus 库中,我还创建了一个3rd-party-repo仓库,用来存放那些公共的、没有被发布至中央库的第 3 方库(实际上是存在这种库的,虽然目前这个库里还是空的)。同时将3rd-party-repo也加到了maven-public集合中。

将第三方库手动发布至 Nexus

上边说到,我们自己创建了一个叫3rd-party-repo的仓库,用来存放第 3 方库,下面就来说一下具体如何操作。
Step 1
找到要发布的第 3 方库(应该就是一个 Jar 包),然后为它创建一个 POM 文件。文件名随意,因为一会发布时会指定 POM 文件。另外,其实 maven 也支持通过命令行直接发布一个 Jar 包,但那样的话定义依赖关系就是很麻烦,所以在没有依赖的时候,可以考虑不创建 POM 文件。这里以 POM 文件示例。
Step 2
编辑 POM 文件,需要定义的内容主要包括坐标定义依赖关系。比如:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.3rd-party</groupId>
    <artifactId>3rd-party-lib</artifactId>
    <version>1.5.3</version>

    <dependencies>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>4.1.3</version>
        </dependency>
    </dependencies>
</project>

Step 3
执行命令:
mvn deploy:deploy-file -Dfile=[jarfile] -DpomFile=[pomfile] -Durl=http://bjoffice1/nexus/repository/3rd-party-repo/ -DrepositoryId=user-deploy
上面参数中,url就是要发布到的仓库地址,repositoryId就是之前说到的用户认证。

能不能更简单一点?
上面这种方式写在命令行上的参数还是有点多,所以为了简化,我试过在 POM 文件中像正常项目一样配置一个distributionManagement来定义urlrepositoryId,但是失败了,似乎在部署时 maven 并没有去关心我配的distributionManagement这部分,依然还是去读了命令行里的参数。

上面提到的也可以不使用 POM 文件进行发布,即通过命令行指定项目坐标,命令是这样的:
mvn deploy:deploy-file -Dfile=[jarfile] -DgroupId=com.3rd-party -DartifactId=3rd-party-lib -Dversion=1.5.3 -DrepositoryId=user-deploy -Durl=http://bjoffice1/nexus/repository/3rd-party-repo/

Nexus 管理后台

  • bjoffice1 这是一个配置在公司内网的域名,将被解释至192.168.10.8,Nexus 就装在这台公司内网的机器上。
  • 因为 Nexus 被安装在了公司内网,所以南京的同事是访问不了的,如果以后南京的同事有访问需求,我们再想办法解决。

Nexus 仓库地址

仓库类型 地址
依赖下载地址 http://bjoffice1/nexus/repository/maven-public/
内部 Release 版本发布 http://bjoffice1/nexus/repository/maven-release/
内部 SNAPSHOT 版本发布 http://bjoffice1/nexus/repository/maven-snapshots/
第 3 方库发布 http://bjoffice1/nexus/repository/3rd-party-repo/

实际项目处理

下面我根据工作中实际遇到的一些项目类型,来讲一下如何将让现有项目支持 Maven。
我先来列举一下我们工作中遇到的项目类型:独立项目(比如 ISAS)、公共库(比如 isasclient)、Web项目(比如 peanut_baby)、可独立运行的Jar包(比如 serveralarm.jar)和Android项目

注意:公司内部的项目在定义坐标时,groupId统一为com.zhilehuoartifactId全部由小写字母横线组成,比如isassum-dem

独立项目

在 Idea 中,在工程上添加 Framework,选中 maven,IDE 会自动在工程根目录创建 pom.xml,并更新目录结构:
ucb38a785ed9a4900b4ed26102660e054-1490759582075.png
6c850ff713324d9d86ad1e4144a4bb2d-1490759618007.png

IDEA 会主动修改源码目录结构:

  • 原来的工程默认源码目录是src,默认测试代码目录是test
  • 新的工程默认源码目录是src/main/java,测试代码目录是src/test/java;同时,新的工程中还多出来了一种资源目录,我现在不太知道这个目录是干嘛的,猜测是不是打包的时候,资源目录里的东西会被打进去?

之前我们的工程一般会配置一个第 3 方库的引用目录lib,改为 Maven 后,这个目录不再需要,可以在工程设置中删除相关配置,同时删除这个目录,以免混淆视听。

虽然开发阶段不再需要 lib 目录,但是项目部署时是需要将依赖库上传至线上服务器的,所以在部署阶段,可以将项目依赖的库拷贝出来,这可以通过一个 Maven 插件来自动实现。

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
        <executions>
          <execution>
            <id>copy-dependencies</id>
            <phase>package</phase>
            <goals>
              <goal>copy-dependencies</goal>
            </goals>
            <configuration>
              <outputDirectory>${build.directory}/lib/</outputDirectory>
              <overWriteReleases>false</overWriteReleases>
              <overWriteSnapshots>false</overWriteSnapshots>
              <overWriteIfNewer>true</overWriteIfNewer>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

现在工程已经配置好了,编译、运行、调试和以前没有差别。

导出 Jar 包

打包时稍有差别,之前是通过在工程配置中增加 Artifact 配置来实现导出 jar 包的,现在可以换个方式了(虽然之前的方式也能用,但是既然用了 Maven,就可以抛弃之前的方式了)。
在 Idea 右侧栏上找到Maven工具,点开,在LifeCycle下会发现里边列出了一系列的指令,其中包括package
6c3642fbcfb94b4397bf8092f79da280-1490760761843.png

双击package,就相当于执行了mvn package命令,这时 Maven 就会编译项目,并在 target 目录中生成 Jar 包。这个 Jar 包的名字是由项目坐标决定的。

公共库

公共库项目与独立运行的项目唯一的不同点就是需要发布。另外,公共库编译时尽量使用 jdk1.6,因为依赖的工程可能是 jdk1.6 的。
关于项目发布的配置可以参考项目发布

如何使用 SNAPSHOT 版本?
: 发布时,如果版本号带有后缀-SNAPSHOT,则发布至 shapshot 库。Snapshot 一般是开发中的版本,可能会被频繁更新,比如被一个工程依赖时,为了修改后在那个工程中进行测试,就可以发布一个 shapshot 版本,然后在那个工程中依赖配置为使用 shapshot:

<dependency>
  <groupId>com.zhilehuo</groupId>
  <artifactId>zlhredis</artifactId>
  <version>1.0-SNAPSHOT</version>
</dependency>

Web 项目

在独立运行项目的基础上,需要添加一个 jetty 插件。不过,jetty 插件在运行时不认识 Maven 的依赖关系,所以需要将依赖的库拷贝至 WEB-INF/lib 目录下。最终配置是这样的:

<build>
  <plugins>
    <plugin>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-maven-plugin</artifactId>
      <version>9.2.21.v20170120</version>
      <configuration>
        <httpConnector><port>8090</port></httpConnector>
        <webAppSourceDirectory>${basedir}/WebContent/</webAppSourceDirectory>
        <webApp>
          <contextPath>/peanut_baby</contextPath>
        </webApp>
      </configuration>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-dependency-plugin</artifactId>
      <executions>
        <execution>
          <id>copy-dependencies</id>
          <phase>compile</phase>
          <goals>
            <goal>copy-dependencies</goal>
          </goals>
          <configuration>
            <outputDirectory>${basedir}/WebContent/WEB-INF/lib/</outputDirectory>
            <overWriteReleases>false</overWriteReleases>
            <overWriteSnapshots>false</overWriteSnapshots>
            <overWriteIfNewer>true</overWriteIfNewer>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

还要增加一个运行配置:之前启动 Jetty 时,配置的是 Jetty 运行模式,现在要修改为使用 Maven 来启动 Jetty,如下图:
50fef16f5ce74e4b9dfef91abe9e620f-1490761779740.png

之后在运行时,选择jetty:run,再启动运行或是调试就可以了:
1e0fa1f3e8114c1ba73d4d6b91dd597e-1490761823137.png

注意:使用 Maven 后,依赖库的管理就都要在 Maven 的管理下,也就是说,如果要增加一个引用库时,不能直接拷贝至 lib 目录,而是要在 Maven 的依赖配置中进行配置,直接拷贝是不好使的。

补充说明:
使用 Maven 管理 Web 项目时,为了使用 Servlet 和 JSP,通常要在依赖中添加servlet-apijsp-api,而在项目发布时,我们又不需要将这两个包拷贝至目标目录,所以,在引入这两个包时可以加上 Scope 为 privoded

可独立运行的 Jar 包

和独立运行工程的区别是:独立运行工程在运行时一般还需要其它库的支持,自己无法运行;而可独立运行的 Jar 包,不需要其它库支持,自己即可执行,比如 serveralarm.jar。要达到这种效果,就要求在打包时,将所有依赖库一并打出到 Jar 包中,并修改 MANIFAST 文件,设置运行时的入口的类。
要完成这个任务,还要用到一个 Maven 插件:maven-assembly-plugin
先看一个配置:

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-assembly-plugin</artifactId>
      <executions>
        <execution>
          <phase>package</phase>
          <goals><goal>single</goal></goals>
        </execution>
      </executions>
      <configuration>
        <archive>
          <manifest>
            <mainClass>com.message.Send</mainClass>
          </manifest>
        </archive>
        <descriptorRefs>
          <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
        <finalName>${artifactId}-${version}-executable</finalName>
        <appendAssemblyId>false</appendAssemblyId>
        <attach>false</attach>
      </configuration>
    </plugin>
  </plugins>
</build>

解释一下上边的配置(只解释我可能知道的):

  • mainClass:设置运行时的入口类
  • descriptorRef:定义了打包的方式,除了jar-with-dependencies这个好像还有source,bin啥的,总之这个就是把依赖的 Jar 包进行合并。
  • finalName,apendAssemblyId,attach:有点复杂,下边单独解释。

关于finalName,apendAssemblyId,attach这三个标签,我来尝试解释一下吧。

首先,如果不加这 3 个标签,则package时会打出来两个包,一个是不带要依赖关系的serveralarm-1.0.jar,和一个带依赖关系的serveralarm-1.0-jar-with-dependencies.jar,而且在deploy时,这两个包都将上传至仓库中。如果不是因为第 2 个包的名字比较奇怪,那这个结果是比较理想的。

但是这个名字我不能忍……所以就发现了finalName这个标签,它可以给生成的包改名,于是我加了这个标签,可是结果的包名是这样的:serveralarm-1.0-executable-jar-with-dependencies.jar
竟然还是带着后缀,于是下一步,将appendAssemblyId配置为false,再package,OK,这次生成了两个包的名字是完美的,分别是serveralarm-1.0.jarserveralarm-1.0-executable.jar

可是,当我deploy的时候,却发现仓库中只有一个版本的 jar 包,就是serveralarm-1.2.1.jar,并没有serveralarm-1.2.1-executable.jar,而且更奇怪的是,仓库中的serveralarm-1.2.1.jar,并不是我刚打出来的那个,而是serveralarm-1.2.1-executable.jar(通过文件大小看出来的,也可以下载下来看)!

下面开始排查问题,我发现了在打包过程中有一个警告:

[WARNING] Configuration options: 'appendAssemblyId' is set to false, and 'classifier' is missing.
Instead of attaching the assembly file: /Users/nianxingyan/work/develop/svn/server/ServerNotification/SendSDK/target/serveralarm-1.2.1-executable.jar, it will become the file for main project artifact.
NOTE: If multiple descriptors or descriptor-formats are provided for this project, the value of this file will be non-deterministic!
[WARNING] Replacing pre-existing project main-artifact file: /Users/nianxingyan/work/develop/svn/server/ServerNotification/SendSDK/target/serveralarm-1.2.1.jar
with assembly file: /Users/nianxingyan/work/develop/svn/server/ServerNotification/SendSDK/target/serveralarm-1.2.1-executable.jar

人家说得很明白,用serveralarm-1.2.1-executable.jar替换了serveralarm-1.2.1.jar!为什么这样子……

百度了一顿饭的时间,说说我的理解:
appendAssemblyIdtrue时,相当于创建了一个名为jar-with-dependenciesclassifier,而我们将其设置为false后,就没有这个classifier了,虽然我们用finalName修改了文件名,但 Maven 仍然认为这个包的classifier没有被设置,所以新生成的两个包的classifier都是空,但是jar-with-dependences是后生成的,所以在逻辑上覆盖了之前生成的包,这样虽然我们能看到两个文件,但是 Maven 只认识后生成的那个包。

如何解决呢?我试了很多办法,没有成功,最后只能设置了attach参数为false,意思就是这个包和项目没啥关系,所以项目发布的时候也不会理它。反正这个可执行的 Jar 包放到依赖库里也没啥用,所以就这样吧 ~

Android 项目(Gradle)

使用 Gradle 构建的 Android 项目,只需要修改一个仓库位置即可:

buildscript {
  repositories {
    //mavenCentral()
    maven{ url 'http://bjoffice1/nexus/repository/maven-public/'}
  }
}

dependencies如果要加我们自己的包的话,也是一样,格式是<groupid>:<artifactid>:<version>,比如:

buildscript {
  dependencies {
    classpath 'com.zhilehuo:zlhcommon:1.2.+'
  }
}

现有项目 SVN 的处理

最后说一个 SVN 的处理。因为目录结构发生了改变,所以 SVN 处理起来要小心一点。处理流程建议是这样的:

  1. update工程,保证本地没有修改
  2. 在 Idea 中添加 Maven Framework
  3. 将 Idea 自动转移至新目录的源码(比如src/main/java)整体移动回原来的位置(比如src),包括测试代码
  4. 在 SVN 中将新的目录结构进行add操作,原来一些没有用的目录(比如lib)执行remove
  5. 在 SVN 中将原来的代码目录整体move到新的目录中
  6. 提交 SVN 之前,记得将target目录设置为ignore

以上步骤能保证转移过去的代码还能看到历史记录。如果不用move指令,而是在 SVN 中将转移过去的代码进行add操作的话,历史记录就全部丢失了。