Skip to content

Instantly share code, notes, and snippets.

@litefeel
Created May 10, 2017 05:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save litefeel/98a28dc06505eae4547d740fe4d18df4 to your computer and use it in GitHub Desktop.
Save litefeel/98a28dc06505eae4547d740fe4d18df4 to your computer and use it in GitHub Desktop.
快速合并某个人的两个不相关的svn分支

使用

svnpatch.py config

将从config.json的 from 分支上的修改从新应用到 to 分支,类似于git rebase
但是只合并 authorreversion 开始的修改

流程

  1. 拉取 from 分支上 authorreversion 版本号及以后的所有版本好
  2. 将这些版本号的修改分别保持为补丁文件
  3. to 分支应用这些补丁
  4. 打开 BComp 软件比对修改过的文件 (这里手动处理不能被正确合并的文件)
  5. 打开 TortoiseSVN 软件提交修改 (这里再次验证要提交的内容)
{
"author" : "myname",
"from" : "E:/work/fox/dev/dev_b/Tianyu/Assets",
"to" : "E:/work/fox/dev/dev_dlc1/Tianyu/Assets",
"__comment": "begin merge reversion of $from",
"reversion":98846
}
#!/usr/bin/env python
# encoding=utf-8
import os
import sys
import shlex
import subprocess
import re
import tempfile
import argparse
import json
SVN = r"C:/Program Files/TortoiseSVN/bin/svn.exe"
TortoiseSVN = r"C:/Program Files/TortoiseSVN/bin/TortoiseProc.exe"
BComp = r"C:/Program Files/Beyond Compare 4/BComp.exe"
AUTHOR = None
REVISION = None
PATH_FROM = None
PATH_TO = None
# ------------------------------------------------------------------------
# r32074 | 张小庆 | 2016-07-07 09:37:50 +0800 (周四, 07 七月 2016)
# ------------------------------------------------------------------------
P_ONELOG = r'r(\d+) \| (\S+) \| .*'
# Index: arena/test.lua
# ===================================================================
# --- arena/test.lua (revision 62120)
# +++ arena/test.lua (revision 62121)
# @@ -63,7 +63,7 @@
P_ONEPATCH = r'Index:\s(.*)\r?\n={30,}\r?\n'
class Commit:
"""docstring for Commit"""
__slots__ = ('revision ', 'author', 'date', 'message')
def __init__(self, arg):
self.revision = int(arg[0])
self.author = arg[1]
# self.date = datetime.strptime(arg[2], '%Y-%m-%d').date()
# self.message = arg[7].strip()
# self.message = None
class Patch:
"""docstring for Patch"""
__slots__ = ('path', 'content', 'revision', 'names')
def __init__(self, content, revision):
# self.path = arg[0]
# self.names = arg[1]
self.content = content
self.revision = revision
# return (output, isOk)
def call(cmd, worddir = None, printOutput=False):
# print("call %s" % cmd)
output = None
isOk = True
if sys.platform == 'win32':
args = cmd
else:
# linux must split arguments
args = shlex.split(cmd)
if printOutput:
popen = subprocess.Popen(args, cwd = worddir)
popen.wait()
isOk = popen.returncode == 0
else:
popen = subprocess.Popen(args, cwd = worddir, universal_newlines = True, stdout=subprocess.PIPE)
outData, errorData = popen.communicate()
isOk = popen.returncode == 0
output = outData if isOk else errorData
return (output, isOk)
# return <string> or None(没有匹配)
# 读取模式列表
def readCommit(s, pattern):
res = re.match(pattern, s.strip(), re.DOTALL)
if res is None:
return None
commit = Commit(res.groups())
return commit
def getRevisions():
cmd = "svn log -q -r %d:HEAD --search %s" % (REVISION, AUTHOR)
output, isOk = call(cmd, PATH_FROM)
if not isOk:
# print(output)
raise Exception('svnerror', output)
arr = re.split('^-{6,}$', output, flags=re.MULTILINE)
# print len(arr)
revs = []
for one in arr:
if one and one.strip():
commit = readCommit(one, P_ONELOG)
if commit.author == AUTHOR:
revs.append(commit.revision)
return revs
def createPatch(content, rev):
# print(content)
patch = Patch(content, rev)
patch.names = re.findall(P_ONEPATCH, content)
f = tempfile.NamedTemporaryFile(delete=False)
f.write(content)
f.close()
patch.path = f.name
# print(f.name)
return patch
def getPatchs(revisions):
patchs = []
for rev in revisions:
output, isOk = call('"%s" diff -c %d' % (SVN, rev), PATH_FROM)
if not isOk:
raise Exception('svn diff', output)
patchs.append(createPatch(output, rev))
return patchs
def applyPatchs(patchs):
for patch in patchs:
output, isOk = call('"%s" patch %s' % (SVN, patch.path), PATH_TO)
if not isOk:
raise Exception('svn patch', output)
else:
print(output)
def openCampare(patchs):
names = set()
for patch in patchs:
names.update(patch.names)
filters = ';'.join(names)
output, isOk = call('%s /iu /filters="%s" %s %s' % (BComp, filters, PATH_FROM, PATH_TO))
print(output)
def commitPath(patchs):
revisions = ','.join([str(p.revision) for p in patchs])
logmsg = "merge from dev %s" % revisions
output, isOk = call('"%s" /command:commit /closeonend:0 /path:"%s" /logmsg:"%s"' % (TortoiseSVN, PATH_TO, logmsg))
print(output, isOk)
return isOk
def updateRepository(path):
call('%s update "%s"' % (SVN, path), printOutput = True)
def readfile(filename):
if not os.path.exists(filename):
raise Exception('can not found file: %s' % filename)
with open(filename) as f:
data = f.read()
f.close()
return data
def writefile(filename, data):
with open(filename, 'w') as f:
f.write(data)
f.close()
def readConfig(path):
global AUTHOR, PATH_FROM, PATH_TO, REVISION
j = json.loads(readfile(path))
AUTHOR = j['author']
PATH_FROM = j['from']
PATH_TO = j['to']
REVISION = j['reversion']
return j
def writeConfig(path, config):
data = json.dumps(config, indent = 4)
writefile(path, data)
# -------------- main ----------------
if __name__ == '__main__':
parser = argparse.ArgumentParser(usage='%(prog)s [options] config',
description='merge svn from $from to $to')
parser.add_argument('config',
help='config file')
args = parser.parse_args()
# print(args.config)
configPath = os.path.abspath(args.config)
if not configPath.endswith('.json'):
configPath = configPath + '.json'
config = readConfig(configPath)
updateRepository(config['to'])
revs = getRevisions()
print(revs)
if len(revs) == 0:
print('have not new reversion')
else:
patchs = getPatchs(revs)
applyPatchs(patchs)
openCampare(patchs)
if commitPath(patchs):
lastReversion = revs[-1]
config['reversion'] = lastReversion + 1
config['lastReversion'] = lastReversion
writeConfig(configPath, config)
# commitPath(None)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment