comparison mercurial/patch.py @ 10384:832f35386067

import: import each patch in a file or stream as a separate change Supports hg export <revrange>, mail messages, and mailboxes. Does not support multiple patches in a single MIME attachment. Closes issue167.
author Brendan Cully <brendan@kublai.com>
date Sun, 07 Feb 2010 18:06:52 +0100
parents 08a0f04b56bd
children d1f209bb9564
comparison
equal deleted inserted replaced
10383:f83291e5643e 10384:832f35386067
38 % dst) 38 % dst)
39 39
40 util.copyfile(abssrc, absdst) 40 util.copyfile(abssrc, absdst)
41 41
42 # public functions 42 # public functions
43
44 def split(stream):
45 '''return an iterator of individual patches from a stream'''
46 def isheader(line, inheader):
47 if inheader and line[0] in (' ', '\t'):
48 # continuation
49 return True
50 l = line.split(': ', 1)
51 return len(l) == 2 and ' ' not in l[0]
52
53 def chunk(lines):
54 return cStringIO.StringIO(''.join(lines))
55
56 def hgsplit(stream, cur):
57 inheader = True
58
59 for line in stream:
60 if not line.strip():
61 inheader = False
62 if not inheader and line.startswith('# HG changeset patch'):
63 yield chunk(cur)
64 cur = []
65 inheader = True
66
67 cur.append(line)
68
69 if cur:
70 yield chunk(cur)
71
72 def mboxsplit(stream, cur):
73 for line in stream:
74 if line.startswith('From '):
75 for c in split(chunk(cur[1:])):
76 yield c
77 cur = []
78
79 cur.append(line)
80
81 if cur:
82 for c in split(chunk(cur[1:])):
83 yield c
84
85 def mimesplit(stream, cur):
86 def msgfp(m):
87 fp = cStringIO.StringIO()
88 g = email.Generator.Generator(fp, mangle_from_=False)
89 g.flatten(m)
90 fp.seek(0)
91 return fp
92
93 for line in stream:
94 cur.append(line)
95 c = chunk(cur)
96
97 m = email.Parser.Parser().parse(c)
98 if not m.is_multipart():
99 yield msgfp(m)
100 else:
101 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
102 for part in m.walk():
103 ct = part.get_content_type()
104 if ct not in ok_types:
105 continue
106 yield msgfp(part)
107
108 def headersplit(stream, cur):
109 inheader = False
110
111 for line in stream:
112 if not inheader and isheader(line, inheader):
113 yield chunk(cur)
114 cur = []
115 inheader = True
116 if inheader and not isheader(line, inheader):
117 inheader = False
118
119 cur.append(line)
120
121 if cur:
122 yield chunk(cur)
123
124 def remainder(cur):
125 yield chunk(cur)
126
127 class fiter(object):
128 def __init__(self, fp):
129 self.fp = fp
130
131 def __iter__(self):
132 return self
133
134 def next(self):
135 l = self.fp.readline()
136 if not l:
137 raise StopIteration
138 return l
139
140 inheader = False
141 cur = []
142
143 mimeheaders = ['content-type']
144
145 if not hasattr(stream, 'next'):
146 # http responses, for example, have readline but not next
147 stream = fiter(stream)
148
149 for line in stream:
150 cur.append(line)
151 if line.startswith('# HG changeset patch'):
152 return hgsplit(stream, cur)
153 elif line.startswith('From '):
154 return mboxsplit(stream, cur)
155 elif isheader(line, inheader):
156 inheader = True
157 if line.split(':', 1)[0].lower() in mimeheaders:
158 # let email parser handle this
159 return mimesplit(stream, cur)
160 elif inheader:
161 # No evil headers seen, split by hand
162 return headersplit(stream, cur)
163 # Not enough info, keep reading
164
165 # if we are here, we have a very plain patch
166 return remainder(cur)
43 167
44 def extract(ui, fileobj): 168 def extract(ui, fileobj):
45 '''extract patch from data read from fileobj. 169 '''extract patch from data read from fileobj.
46 170
47 patch can be a normal patch or contained in an email message. 171 patch can be a normal patch or contained in an email message.