|
david@0
|
1 |
#!/usr/bin/python |
|
david@0
|
2 |
|
|
david@0
|
3 |
# Apply a git diff to a subversion repository directly. Its possible |
|
david@0
|
4 |
# to do a similar thing using git-svn, but for someone who doesn't |
|
david@0
|
5 |
# want the pain or learning git, this is a lot easier. |
|
david@0
|
6 |
# This could easily be adapted to any version control system that |
|
david@0
|
7 |
# supports rename. |
|
david@0
|
8 |
# |
|
david@0
|
9 |
# This could probably be made much faster if it |
|
david@0
|
10 |
# used the svn library, instead of the command line - its really |
|
david@0
|
11 |
# very slow. |
|
david@0
|
12 |
|
|
david@0
|
13 |
import sys |
|
david@0
|
14 |
import string |
|
david@0
|
15 |
from popen2 import popen3 |
|
david@0
|
16 |
import os |
|
david@0
|
17 |
from os import path |
|
david@0
|
18 |
from optparse import OptionParser |
|
david@0
|
19 |
|
|
david@0
|
20 |
optionsParser = OptionParser() |
|
david@0
|
21 |
optionsParser.add_option("-p", "--strip", dest="strip", |
|
david@0
|
22 |
help="""Like patch -p: Strip the smallest prefix |
|
david@0
|
23 |
containing num leading slashes from each |
|
david@0
|
24 |
file name found in the patch file""", |
|
david@0
|
25 |
metavar="STRIP") |
|
david@0
|
26 |
|
|
david@0
|
27 |
(options, args) = optionsParser.parse_args() |
|
david@0
|
28 |
|
|
david@0
|
29 |
if options.has_key("strip"): |
|
david@0
|
30 |
STRIP = int(options["strip"]) + 1 |
|
david@0
|
31 |
else: |
|
david@0
|
32 |
STRIP = 1 |
|
david@0
|
33 |
|
|
david@0
|
34 |
|
|
david@0
|
35 |
|
|
david@0
|
36 |
def error(msg): |
|
david@0
|
37 |
print 'Error: %s' % msg |
|
david@0
|
38 |
|
|
david@0
|
39 |
|
|
david@0
|
40 |
def mode(chunk): |
|
david@0
|
41 |
if chunk and chunk.has_key('mode'): |
|
david@0
|
42 |
return chunk['mode'] |
|
david@0
|
43 |
else: |
|
david@0
|
44 |
return "default" |
|
david@0
|
45 |
|
|
david@0
|
46 |
############################################################### |
|
david@0
|
47 |
# Chunk processing |
|
david@0
|
48 |
|
|
david@0
|
49 |
# How we handle the various types of chunk |
|
david@0
|
50 |
modes = { |
|
david@0
|
51 |
"remove": { "pre" : [ ], |
|
david@0
|
52 |
"patch": [ "--- a/%(src)s", |
|
david@0
|
53 |
"+++ /dev/null" ], |
|
david@0
|
54 |
"post" : [ "svn rm --non-interactive %(src)s" ] }, |
|
david@0
|
55 |
"add" : { "pre" : [ ], |
|
david@0
|
56 |
"patch": [ "--- /dev/null", |
|
david@0
|
57 |
"+++ b/%(dest)s" ], |
|
david@0
|
58 |
"post" : [ "svn add %(dest)s" ] }, |
|
david@0
|
59 |
"rename": { "pre" : [ "svn rename --non-interactive %(src)s %(dest)s" ], |
|
david@0
|
60 |
"patch": [ "--- a/%(dest)s", |
|
david@0
|
61 |
"+++ b/%(dest)s" ], |
|
david@0
|
62 |
"post" : [ ] }, |
|
david@0
|
63 |
"default":{ "pre" : [ ], |
|
david@0
|
64 |
"patch": [ "--- a/%(src)s", |
|
david@0
|
65 |
"+++ b/%(src)s" ], |
|
david@0
|
66 |
"post" : [ ] } |
|
david@0
|
67 |
} |
|
david@0
|
68 |
|
|
david@0
|
69 |
# Execute external commands |
|
david@0
|
70 |
def system(cmd): |
|
david@0
|
71 |
# make sure any output happens in a consistent order |
|
david@0
|
72 |
sys.stdout.write(cmd + '\n') |
|
david@0
|
73 |
sys.stdout.flush() |
|
david@0
|
74 |
sys.stderr.flush() |
|
david@0
|
75 |
os.system(cmd) |
|
david@0
|
76 |
|
|
david@0
|
77 |
# Svn expects all parent directories to exist before a |
|
david@0
|
78 |
# rename. Hg for example doesn't need this at all |
|
david@0
|
79 |
def svnmakedirs(dest): |
|
david@0
|
80 |
destdir = path.dirname(dest) |
|
david@0
|
81 |
if len(destdir) and not(path.exists(destdir)): |
|
david@0
|
82 |
svnmakedirs(destdir) |
|
david@0
|
83 |
os.mkdir(destdir) |
|
david@0
|
84 |
system("svn add %s" % destdir) |
|
david@0
|
85 |
|
|
david@0
|
86 |
# Process a chunk. A chunk is actually a dictionary of properties we |
|
david@0
|
87 |
# determined during parsing: |
|
david@0
|
88 |
# mode: ( remove | add | rename | None ) None := default |
|
david@0
|
89 |
# src: the src file (or '/dev/null') |
|
david@0
|
90 |
# dest: the dest file (or '/dev/null') |
|
david@0
|
91 |
# lines: these are the lines of the patch |
|
david@0
|
92 |
def processChunk(chunk): |
|
david@0
|
93 |
svnmakedirs(chunk['dest']) |
|
david@0
|
94 |
|
|
david@0
|
95 |
for command in modes[mode(chunk)]["pre"]: |
|
david@0
|
96 |
system(command % chunk) |
|
david@0
|
97 |
|
|
david@0
|
98 |
if len(chunk['lines']): |
|
david@0
|
99 |
sys.stdout.write("patch -p1\n") |
|
david@0
|
100 |
sys.stdout.writelines((line % chunk) + '\n' for line in modes[mode(chunk)]["patch"]) |
|
david@0
|
101 |
(stdout, stdin, stderr) = popen3("patch -p1") |
|
david@0
|
102 |
stdin.writelines((line % chunk) + '\n' for line in modes[mode(chunk)]["patch"]) |
|
david@0
|
103 |
stdin.writelines(line + '\n' for line in chunk['lines']) |
|
david@0
|
104 |
stdin.close() |
|
david@0
|
105 |
sys.stdout.write(stdout.read()) |
|
david@0
|
106 |
sys.stderr.write(stderr.read()) |
|
david@0
|
107 |
stdout.close() |
|
david@0
|
108 |
stderr.close() |
|
david@0
|
109 |
|
|
david@0
|
110 |
for command in modes[mode(chunk)]["post"]: |
|
david@0
|
111 |
system(command % chunk) |
|
david@0
|
112 |
|
|
david@0
|
113 |
def stripInput(pre, path): |
|
david@0
|
114 |
if path.startswith('/'): |
|
david@0
|
115 |
return path |
|
david@0
|
116 |
elif len(path.split('/')) > STRIP: |
|
david@0
|
117 |
return string.join(path.split('/')[STRIP:], '/') |
|
david@0
|
118 |
else: |
|
david@0
|
119 |
return path |
|
david@0
|
120 |
|
|
david@0
|
121 |
def chunkLine(line): |
|
david@0
|
122 |
return [ '\\', '@', '+', '-', ' ' ].count(line[0]) |
|
david@0
|
123 |
|
|
david@0
|
124 |
# The parser, which processes lines, and when it has a chunk, calling |
|
david@0
|
125 |
# processChunk above |
|
david@0
|
126 |
class Parser: |
|
david@0
|
127 |
def __init__(self): |
|
david@0
|
128 |
self.chunk = None |
|
david@0
|
129 |
|
|
david@0
|
130 |
def newChunk(self, a, b): |
|
david@0
|
131 |
if self.chunk: |
|
david@0
|
132 |
slef.processChunk(chunk) |
|
david@0
|
133 |
self.chunk = { 'src': a, 'dest': b, 'lines': []} |
|
david@0
|
134 |
|
|
david@0
|
135 |
def setChunkProperties(props): |
|
david@0
|
136 |
for key in props.keys(): |
|
david@0
|
137 |
if self.chunk.has_key(key) and props[key] != self.chunk[key]: |
|
david@0
|
138 |
error('Values for %s dont match: %s expected %s in chunk %s' % |
|
david@0
|
139 |
(key, props[key], chunk[key], chunk['src'])) |
|
david@0
|
140 |
self.chunk.update(props) |
|
david@0
|
141 |
|
|
david@0
|
142 |
def addChunkLine(line): |
|
david@0
|
143 |
self.chunk['lines'].append(line) |
|
david@0
|
144 |
|
|
david@0
|
145 |
|
|
david@0
|
146 |
def processLine(self, line): |
|
david@0
|
147 |
if not(len(line)): |
|
david@0
|
148 |
pass |
|
david@0
|
149 |
elif line.startswith('+++ '): |
|
david@0
|
150 |
dest = stripInput('b', line.split()[1]) |
|
david@0
|
151 |
if mode(self.chunk) == 'remove' and dest == '/dev/null': |
|
david@0
|
152 |
pass |
|
david@0
|
153 |
else: |
|
david@0
|
154 |
self.setChunkProperties({ 'dest': dest }) |
|
david@0
|
155 |
elif line.startswith('--- '): |
|
david@0
|
156 |
src = stripInput('a', line.split()[1]) |
|
david@0
|
157 |
if mode(self.chunk) == 'add' and src == '/dev/null': |
|
david@0
|
158 |
pass |
|
david@0
|
159 |
else: |
|
david@0
|
160 |
self.setChunkProperties({ 'src': src }) |
|
david@0
|
161 |
elif chunkLine(line): |
|
david@0
|
162 |
self.addChunkLine(line) |
|
david@0
|
163 |
else: |
|
david@0
|
164 |
command = line.split() |
|
david@0
|
165 |
if command[0] == 'diff': |
|
david@0
|
166 |
self.newChunk(stripInput('a', command[2]), stripInput('b', command[3])) |
|
david@0
|
167 |
elif command[0] == 'rename': |
|
david@0
|
168 |
if command[1] == 'from': |
|
david@0
|
169 |
self.setChunkProperties({'src': command[2], 'mode': 'rename' }) |
|
david@0
|
170 |
elif command[1] == 'to': |
|
david@0
|
171 |
self.setChunkProperties({'dest': command[2], 'mode': 'rename' }) |
|
david@0
|
172 |
else: |
|
david@0
|
173 |
error('invalid rename: ' + string.join(command, ' ')) |
|
david@0
|
174 |
elif command[0] == 'new': |
|
david@0
|
175 |
self.setChunkProperties({'mode': 'add' }) |
|
david@0
|
176 |
elif command[0] == 'deleted': |
|
david@0
|
177 |
self.setChunkProperties({'mode': 'remove'}) |
|
david@0
|
178 |
elif command[0] == 'index': |
|
david@0
|
179 |
# don't know what this does, but git diff emits it |
|
david@0
|
180 |
pass |
|
david@0
|
181 |
else: |
|
david@0
|
182 |
error('unknown command: ' + string.join(command, ' ')) |
|
david@0
|
183 |
|
|
david@0
|
184 |
|
|
david@0
|
185 |
# Create a parser |
|
david@0
|
186 |
parser = Parser() |
|
david@0
|
187 |
# parse the lines from stdin |
|
david@0
|
188 |
for line in sys.stdin.read().split('\n'): |
|
david@0
|
189 |
parser.processLine(line) |
|
david@0
|
190 |
|
|
david@0
|
191 |
# Make sure we get the last chunk: |
|
david@0
|
192 |
parser.newChunk() |
|
david@0
|
193 |
|