将Subversion diff转换为JSON的最佳方法是什么?

时间:2013-12-04 17:05:14

标签: json svn sed

我有一堆Sed / unix fu,我开始怀疑不会是完成任务的最佳方式,因为出现的线条不一致svn diff'...

svn diff -r 1:9 | 
expand | 
sed -e 's/^Index: \(.*\)/]}, { "index":"\1", /g' | 
sed -e 's/^--- \(.*\)/"from":"\1", /g' | 
sed -e 's/^+++ \(.*\)/"to":"\1", "chunks":[/g' | 
sed -e 's/^@@ \(.*\) @@/]},{"locn":"\1", "lines": [/g' | 
sed -e 's/^-\(.*\)/"-\1",/g' | 
sed -e 's/^+\(.*\)/"+\1",/g' | 
sed -e 's/^ \(.*\)/" \1",/g' | 
sed -e 's/^==============.*//g' | 
tr -d '\n' | 
sed -e 's/"chunks":\[\]},{/"chunks":\[{/g' | 
sed -e 's/^]}, \(.*\)/{"changes":[ \1]}]}]}/g' | 
sed -e 's/,\]}/]}/g' |
jshon

它可靠地转变......

Index: file1.txt
===================================================================
--- file1.txt   (revision 8)
+++ file1.txt   (revision 9)
@@ -1,3 +1,5 @@
+zzz
+
 aaa

 Efficiently Blah blah
@@ -7,3 +9,5 @@
 functional solutions.

 bbb
+
+www   

进入......

{
 "changes": [
  {
   "index": "file1.txt",
   "to": "file1.txt   (revision 9)",
   "from": "file1.txt   (revision 8)",
   "chunks": [
    {
     "locn": "-1,3 +1,5",
     "lines": [
      "+zzz",
      "+",
      " aaa",
      " ",
      " Efficiently blah blah"
     ]
    },
    {
     "locn": "-7,3 +9,5",
     "lines": [
      " functional solutions.",
      " ",
      " bbb",
      "+",
      "+www"
     ]
    }
   ]
  }
 ]
}

但是'svn diff'可能会出现的问题比我处理的更多,我想知道在这个方向上进行是否愚蠢。

1 个答案:

答案 0 :(得分:2)

我可能会使用diff parser in libsvn_diff。我不确定它是否被绑定包装,但它很可能是从Python绑定开始的。

从svn_diff_open_patch_file()开始,然后通过调用svn_diff_parse_next_patch()迭代文件中的补丁,直到它为svn_patch_t提供NULL。

一旦你有了每个文件的结构,生成你的JSON应该是微不足道的。

公平警告,diff解析器中可能存在错误。它是为svn补丁编写的,我发现了错误(虽然我认为大多数错误都在补丁应用程序中而不是解析)。另一方面,这样做应该意味着即使我们调整补丁格式输出,你也应该总是有一个好的解析器。当然,您的错误报告(如果您最终拥有)可以改进我们的解析器。

只有我发生的其他事情是API不流畅(它适用于文件),这可能不是你想要的。此外,如果你真的想要走下兔子洞,你可以直接驱动WC / RA层,作为编辑器驱动器的接收器,生成你的json输出而不是统一的差异。但这可能比你想要的更多,因为有大量的代码只是为了处理差异目标类型的所有不同变化(本地到本地,回购到回购,本地到回购,回购到本地)。

示例

所以我决定玩diff解析器。我最终编写了以下python脚本来使用它,并生成与您的示例几乎相同的JSON输出。请注意,解析器会抛弃索引行,因此我的输出中没有。

我遇到了一个小的改动我必须对Python SWIG绑定做这个工作(svn_patch_t的hunks字段没有正确转换为python列表),我在r1548379上修复了Subversion trunk(我怀疑补丁会干净地适用于1.8)。

请注意,svn_diff_hunk_readline_diff_text()的文档说第一行将是hunk标题,但它似乎不是真的。虽然您可以使用svn_diff_hunk_get_ {original,modified} _ {start,length}函数重建所需的hunk标头数据。

我没有费心去处理属性更改解析或操作解析(我不认为对此的支持是完全的,但如果你想要它,我会把它作为一个练习给你)。

如果这不是最Pythonic代码我的appologies。其中一部分原因是被包装的C API不利于这一点,部分原因是我对Python不太满意。我是用Python做的,因为这些绑定在这方面更接近完成。

您只需运行以下脚本:python scriptname.py patchfile

import sys
from svn import diff, core
import json

class UDiff:
  def convert_svn_patch_t(self, patch, pool):
    data = {}
    data['from'] = patch.old_filename
    data['to'] = patch.new_filename
    iter_pool = core.Pool(pool);
    chunks = []
    for hunk in patch.hunks:
      iter_pool.clear()
      chunk = {}
      orig_start = diff.svn_diff_hunk_get_original_start(hunk)
      orig_len = diff.svn_diff_hunk_get_original_length(hunk)
      mod_start = diff.svn_diff_hunk_get_modified_start(hunk)
      mod_len = diff.svn_diff_hunk_get_modified_length(hunk)
      chunk['locn'] = "-%d,%d +%d,%d" % \
                      (orig_start, orig_len, mod_start, mod_len)
      lines = []
      while True:
        text, eol, eof = diff.svn_diff_hunk_readline_diff_text(hunk,
                                                               iter_pool,
                                                               iter_pool)
        if eof:
          break;
        lines.append("%s%s" % (text, eol))
      chunk['lines'] = lines
      chunks.append(chunk)
    data['chunks'] = chunks
    self.data = data

  def as_dict(self):
    return self.data

  def __init__(self, patch, pool):
    self.convert_svn_patch_t(patch, pool)

class UDiffAsJson:
  def __init__(self):
    self.pool = core.Pool()

  def convert(self, fname):
    patch_file = diff.svn_diff_open_patch_file(fname, self.pool)
    iter_pool = core.Pool(self.pool)
    changes = []
    while True:
      iter_pool.clear()
      patch = diff.svn_diff_parse_next_patch(patch_file,
                                             False, # reverse
                                             False, # ignore_whitespace
                                             iter_pool, iter_pool)
      if not patch:
        break
      udiff = UDiff(patch, iter_pool)
      changes.append(udiff.as_dict())
    data = {}
    data['changes'] = changes
    diff.svn_diff_close_patch_file(patch_file, iter_pool)
    return json.dumps(data, indent=True)

if __name__ == "__main__":
  udiffasjson = UDiffAsJson()
  sys.stdout.write(udiffasjson.convert(sys.argv[1]))