gollum增加unicode文件名支持

文件路径

修改$gems/gitlab-grit-2.8.1/lib/grit/index.rb中write_tree函数,增加unicode解析支持

def write_tree(tree = nil, now_tree = nil)
  tree = self.tree if !tree
  tree_contents = {}

  # fill in original tree
  now_tree = read_tree(now_tree) if(now_tree && now_tree.is_a?(String))
  now_tree.contents.each do |obj|
    sha = [obj.id].pack("H*")
    k = obj.name
    k += '/' if (obj.class == Grit::Tree)
    tmode = obj.mode.to_i.to_s  ## remove zero-padding
    tree_contents[k] = "%s %s\0%s" % [tmode, obj.name.force_encoding('ASCII-8BIT'), sha]      
  end if now_tree

异地同步构建

背景

公司研发团队AB两市异地协作,源码服务器在B,构建服务器也在B。A的同胞们需要得到完整的产品包需要等待B的构建服务器构建完产品后ftp上传到A的一个服务器上 。由于网络问题,通常需要等待1小时甚至更长的时间A才能得到最新的产品包。这就造成了A研发同事处理某些问题会滞后很多。

早期处理

2年前终于有人无法忍受不能及时获取到产品包处理客户问题。因此提出了在A市购置一台构建服务器,和B市服务器分别进行构建。

由于更新源码是增量的,每次仅需要从B市获取最近改动过的代码即可进行构建并且构建的时间远小于网络传输产品包的时间。因此A市的滞后问题得到了大幅减少,现在仅需要20分钟左右进行构建即可得到产品包。

但是这样也存在问题,由于是不同的服务器构建,因此两地的产品包很难保持一致。

新的处理方案

本次大版本启动,引入了一系列的项目管理方案,加入了单元测试、静态代码分析等,构建时间大幅增加。老的构建模式已经不大适用了。

因此顺便也改造了整个构建流程。

构建流程

工程使用maven管理。为了支持各功能模块实现独立发布,进行了各工程间解耦,使用接口进行交互。 新的构建流程大约是这样的

平台构建->按需构建模块并执行单元测试->各模块聚合->打包完整的产品

新的构建,老的同步方案

就两地都能在最短的时间内获取到最新的完整产品包,重新评估了下之前的两种构建方案:

  • 由于构建时间增加,网络传输完整的产品包已经和构建时间差不多。该方案简单方便,仅需执行一次ftp上传即可。
  • 依然进行两地分别从源码服务器取得源码后使用相同的流程配置进行构建。该方案依然比拷贝产品包优势,因为上班时间网络状况比较差,最多的时候甚至3个小时没能拷贝完成产品包。

新的构建,新的同步方案

然而两种方案都会让A地的人员在B地有完整产品包以后1个小时以上才能得到产品包,因此都无法接受。

我们分析下A、B两种方案的优势:

  • A方案不需要进行一次多余的构建流程
  • B方案可以进行增量更新,网络传输量小

为了不需要进行一次多余的完整构建,也可以增量更新,经过考虑,使用了一种新的方案。

将同步的内容从源码变为编译聚合后的代码,B的服务器将构建过程中产生的最终产品文件在打包前全部同步到A的构建服务器上,A仅需要执行打包操作即可。

新方案的具体实施

构建环境

使用Jenkins作为CI工具,A、B两市分别一台linux服务器,源码服务器依然在B市。

执行流程

在B的Jenkins上增加一个rsync任务,其流程位于各模块聚合后,产品打包前。

其内容就一个shell命令,将2地linux服务器的构建文件夹同步(两个linux服务器需要建立ssh信任,不然需要录入账号密码)。

rsync -az --delete -progress -e ssh $buildDir $user@$remoteServer:$buildDir

rsync命令用于linux间同步,我们使用-az来保持所有文件的属性也进行同步,–delete删除B服务器上存在而A服务器上不存在的文件。

除了第一次同步需要较多时间外,往后每次同步都只会更新修改的文件,只需要很少的时间。

现在两地都有相同的完整产品文件了,只需要分别对文件进行打包就行了。

我们在A的Jenkins上配置一份和B的jenkins上打包流程相同的任务即可。然后使用* Parameterized Remote Trigger Plugin * 插件在B的jenkins执行完同步任务后进行远程触发A的jenkins上的打包任务。

如无法在A的jenkins打包任务中配置出构建相同的包名,也可以在B的触发任务中将包名作为参数进行传递到A的打包任务中。

结果

经测试,使用新方案后,平均同步时间能缩到到30秒左右。 由于打包是基本上同步进行,因此两地都能在大约相同的时间得到产品包,并且能保持两地的产品包内容完全一致。

为gollum增加plantuml支持

之前为gollum增加了graphviz支持,后来考虑了下其实plantuml支持的流程格式更多,并且完全兼容graphviz。因此顺便也把plantuml的支持增加上了。

做完后发现其实增加plantuml的支持更简单些。

示例的gollum版本为4.0.0,操作系统为centos

gollum直接使用gem安装,gems目录路径使用$gems代替,该路径通常在ruby安装目录下。

##1、下载一份plantuml的jar包

可能需要翻墙。下载完成后放到一个目录下。如:/root/plantuml/plantuml.jar。

##2、为_gollum-lib_增加一个plantuml的filter

新建文件:$gems/gollum-lib-4.0.X/lib/gollum-lib/filter/plantuml.rb

内容如下:

    # ~*~ encoding: utf-8 ~*~
    require 'net/http'
    require 'uri'
    require 'open-uri'
    require File.expand_path '../../helpers', __FILE__

    # PlantUML Diagrams
    #
    # Render an inline plantuml diagram by generating a PNG image using the
    # plantuml.jar tool.
    #
    class Gollum::Filter::PlantUML < Gollum::Filter

      #path of plantuml.jar
      JAR = "/root/plantuml/plantuml.jar"
      #path of java
      JAVA= "java"

      # Extract all sequence diagram blocks into the map and replace with
      # placeholders.
      def extract(data)
        return data if @markup.format == :txt
        data.gsub(/(@startuml[\s\S]*[email protected])/m) do
          id       = Digest::SHA1.hexdigest($1)
          @map[id] = { :code => $1 }
          id
        end
      end

      # Process all diagrams from the map and replace the placeholders with
      # the final HTML.
      #
      def process(data)
        out_path_dir = ::File.expand_path ::File.join(@markup.page.wiki.path, 'tmp')
        Dir.mkdir out_path_dir unless ::File.exists? out_path_dir
        @map.each do |id, spec|
          data.gsub!(id) do
            render_plantuml(id, spec[:code],out_path_dir)
          end
        end
        data
      end

      private

      def render_plantuml(id, code,filepath)
        out_path = ::File.join(filepath, id)
        unless File::exists?(filepath+"/"+id)
          File.open(filepath+"/"+id, "w") do |file|
                file << code
          end
        end
        unless File::exists?( filepath+"/"+id+".png" )
          puts("#{JAVA} -jar #{JAR} #{filepath}/#{id} -o '#{filepath}'")
          system("#{JAVA} -jar #{JAR} #{filepath}/#{id} -o '#{filepath}'")
            unless $?.success?
              html_error("failed to generate uml image")
            end
        end
        "<img src=\"tmp/#{id}.png\" />"
      end

    end

##3、让wiki格式化内容时使用该filter

修改_$gems/gollum-lib-4.0.X/lib/gollum-lib/wiki.rb,为属性[email protected]_chain_增加PlantUML_对象,修改后的该属性为:

@filter_chain         = options.fetch :filter_chain,[:Metadata, :PlainText, :TOC, :RemoteCode, :Code, :Macro, :Sanitize, :WSD, :Tags,:PlantUML, :Render]

OK,搞定。重启gollum,现在新建一个page,录入以下内容:

@startuml
    digraph G {
        main -> parse -> execute;
        main -> init;
        main -> cleanup;
        execute -> make_string;
        execute -> printf;
        init -> make_string;
        main -> printf;
        execute -> compare;
    }
@enduml

预览或保存,就可以看到流程图了。

生成的流程图文件会存放在$wikidir/wiki/tmp下