##// END OF EJS Templates
remove various unused import
Benoit Boissinot -
r3963:ba450418 default
parent child Browse files
Show More
@@ -1,178 +1,178 b''
1 1 # churn.py - create a graph showing who changed the most lines
2 2 #
3 3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7 #
8 8 #
9 9 # Aliases map file format is simple one alias per line in the following
10 10 # format:
11 11 #
12 12 # <alias email> <actual email>
13 13
14 import time, sys, signal, os
14 import sys
15 15 from mercurial.i18n import gettext as _
16 16 from mercurial import hg, mdiff, cmdutil, ui, util, templater, node
17 17
18 18 def __gather(ui, repo, node1, node2):
19 19 def dirtywork(f, mmap1, mmap2):
20 20 lines = 0
21 21
22 22 to = mmap1 and repo.file(f).read(mmap1[f]) or None
23 23 tn = mmap2 and repo.file(f).read(mmap2[f]) or None
24 24
25 25 diff = mdiff.unidiff(to, "", tn, "", f).split("\n")
26 26
27 27 for line in diff:
28 28 if not line:
29 29 continue # skip EOF
30 30 if line.startswith(" "):
31 31 continue # context line
32 32 if line.startswith("--- ") or line.startswith("+++ "):
33 33 continue # begining of diff
34 34 if line.startswith("@@ "):
35 35 continue # info line
36 36
37 37 # changed lines
38 38 lines += 1
39 39
40 40 return lines
41 41
42 42 ##
43 43
44 44 lines = 0
45 45
46 46 changes = repo.status(node1, node2, None, util.always)[:5]
47 47
48 48 modified, added, removed, deleted, unknown = changes
49 49
50 50 who = repo.changelog.read(node2)[1]
51 51 who = templater.email(who) # get the email of the person
52 52
53 53 mmap1 = repo.manifest.read(repo.changelog.read(node1)[0])
54 54 mmap2 = repo.manifest.read(repo.changelog.read(node2)[0])
55 55 for f in modified:
56 56 lines += dirtywork(f, mmap1, mmap2)
57 57
58 58 for f in added:
59 59 lines += dirtywork(f, None, mmap2)
60 60
61 61 for f in removed:
62 62 lines += dirtywork(f, mmap1, None)
63 63
64 64 for f in deleted:
65 65 lines += dirtywork(f, mmap1, mmap2)
66 66
67 67 for f in unknown:
68 68 lines += dirtywork(f, mmap1, mmap2)
69 69
70 70 return (who, lines)
71 71
72 72 def gather_stats(ui, repo, amap, revs=None, progress=False):
73 73 stats = {}
74 74
75 75 cl = repo.changelog
76 76
77 77 if not revs:
78 78 revs = range(0, cl.count())
79 79
80 80 nr_revs = len(revs)
81 81 cur_rev = 0
82 82
83 83 for rev in revs:
84 84 cur_rev += 1 # next revision
85 85
86 86 node2 = cl.node(rev)
87 87 node1 = cl.parents(node2)[0]
88 88
89 89 if cl.parents(node2)[1] != node.nullid:
90 90 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
91 91 continue
92 92
93 93 who, lines = __gather(ui, repo, node1, node2)
94 94
95 95 # remap the owner if possible
96 96 if amap.has_key(who):
97 97 ui.note("using '%s' alias for '%s'\n" % (amap[who], who))
98 98 who = amap[who]
99 99
100 100 if not stats.has_key(who):
101 101 stats[who] = 0
102 102 stats[who] += lines
103 103
104 104 ui.note("rev %d: %d lines by %s\n" % (rev, lines, who))
105 105
106 106 if progress:
107 107 if int(100.0*(cur_rev - 1)/nr_revs) < int(100.0*cur_rev/nr_revs):
108 108 ui.write("%d%%.." % (int(100.0*cur_rev/nr_revs),))
109 109 sys.stdout.flush()
110 110
111 111 if progress:
112 112 ui.write("done\n")
113 113 sys.stdout.flush()
114 114
115 115 return stats
116 116
117 117 def churn(ui, repo, **opts):
118 118 "Graphs the number of lines changed"
119 119
120 120 def pad(s, l):
121 121 if len(s) < l:
122 122 return s + " " * (l-len(s))
123 123 return s[0:l]
124 124
125 125 def graph(n, maximum, width, char):
126 126 n = int(n * width / float(maximum))
127 127
128 128 return char * (n)
129 129
130 130 def get_aliases(f):
131 131 aliases = {}
132 132
133 133 for l in f.readlines():
134 134 l = l.strip()
135 135 alias, actual = l.split(" ")
136 136 aliases[alias] = actual
137 137
138 138 return aliases
139 139
140 140 amap = {}
141 141 aliases = opts.get('aliases')
142 142 if aliases:
143 143 try:
144 144 f = open(aliases,"r")
145 145 except OSError, e:
146 146 print "Error: " + e
147 147 return
148 148
149 149 amap = get_aliases(f)
150 150 f.close()
151 151
152 152 revs = [int(r) for r in cmdutil.revrange(repo, opts['rev'])]
153 153 revs.sort()
154 154 stats = gather_stats(ui, repo, amap, revs, opts.get('progress'))
155 155
156 156 # make a list of tuples (name, lines) and sort it in descending order
157 157 ordered = stats.items()
158 158 ordered.sort(lambda x, y: cmp(y[1], x[1]))
159 159
160 160 maximum = ordered[0][1]
161 161
162 162 ui.note("Assuming 80 character terminal\n")
163 163 width = 80 - 1
164 164
165 165 for i in ordered:
166 166 person = i[0]
167 167 lines = i[1]
168 168 print "%s %6d %s" % (pad(person, 20), lines,
169 169 graph(lines, maximum, width - 20 - 1 - 6 - 2 - 2, '*'))
170 170
171 171 cmdtable = {
172 172 "churn":
173 173 (churn,
174 174 [('r', 'rev', [], _('limit statistics to the specified revisions')),
175 175 ('', 'aliases', '', _('file with email aliases')),
176 176 ('', 'progress', None, _('show progress'))],
177 177 'hg churn [-r revision range] [-a file] [--progress]'),
178 178 }
@@ -1,308 +1,308 b''
1 1 # Minimal support for git commands on an hg repository
2 2 #
3 3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 import time, sys, signal, os
8 import sys, os
9 9 from mercurial import hg, fancyopts, commands, ui, util, patch, revlog
10 10
11 11 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
12 12 """diff trees from two commits"""
13 13 def __difftree(repo, node1, node2, files=[]):
14 14 if node2:
15 15 change = repo.changelog.read(node2)
16 16 mmap2 = repo.manifest.read(change[0])
17 17 status = repo.status(node1, node2, files=files)[:5]
18 18 modified, added, removed, deleted, unknown = status
19 19 else:
20 20 status = repo.status(node1, files=files)[:5]
21 21 modified, added, removed, deleted, unknown = status
22 22 if not node1:
23 23 node1 = repo.dirstate.parents()[0]
24 24
25 25 change = repo.changelog.read(node1)
26 26 mmap = repo.manifest.read(change[0])
27 27 empty = hg.short(hg.nullid)
28 28
29 29 for f in modified:
30 30 # TODO get file permissions
31 31 print ":100664 100664 %s %s M\t%s\t%s" % (hg.short(mmap[f]),
32 32 hg.short(mmap2[f]),
33 33 f, f)
34 34 for f in added:
35 35 print ":000000 100664 %s %s N\t%s\t%s" % (empty,
36 36 hg.short(mmap2[f]),
37 37 f, f)
38 38 for f in removed:
39 39 print ":100664 000000 %s %s D\t%s\t%s" % (hg.short(mmap[f]),
40 40 empty,
41 41 f, f)
42 42 ##
43 43
44 44 while True:
45 45 if opts['stdin']:
46 46 try:
47 47 line = raw_input().split(' ')
48 48 node1 = line[0]
49 49 if len(line) > 1:
50 50 node2 = line[1]
51 51 else:
52 52 node2 = None
53 53 except EOFError:
54 54 break
55 55 node1 = repo.lookup(node1)
56 56 if node2:
57 57 node2 = repo.lookup(node2)
58 58 else:
59 59 node2 = node1
60 60 node1 = repo.changelog.parents(node1)[0]
61 61 if opts['patch']:
62 62 if opts['pretty']:
63 63 catcommit(repo, node2, "")
64 64 patch.diff(repo, node1, node2,
65 65 files=files,
66 66 opts=patch.diffopts(ui, {'git': True}))
67 67 else:
68 68 __difftree(repo, node1, node2, files=files)
69 69 if not opts['stdin']:
70 70 break
71 71
72 72 def catcommit(repo, n, prefix, changes=None):
73 73 nlprefix = '\n' + prefix;
74 74 (p1, p2) = repo.changelog.parents(n)
75 75 (h, h1, h2) = map(hg.short, (n, p1, p2))
76 76 (i1, i2) = map(repo.changelog.rev, (p1, p2))
77 77 if not changes:
78 78 changes = repo.changelog.read(n)
79 79 print "tree %s" % (hg.short(changes[0]))
80 80 if i1 != hg.nullrev: print "parent %s" % (h1)
81 81 if i2 != hg.nullrev: print "parent %s" % (h2)
82 82 date_ar = changes[2]
83 83 date = int(float(date_ar[0]))
84 84 lines = changes[4].splitlines()
85 85 if lines and lines[-1].startswith('committer:'):
86 86 committer = lines[-1].split(': ')[1].rstrip()
87 87 else:
88 88 committer = changes[1]
89 89
90 90 print "author %s %s %s" % (changes[1], date, date_ar[1])
91 91 print "committer %s %s %s" % (committer, date, date_ar[1])
92 92 print "revision %d" % repo.changelog.rev(n)
93 93 print ""
94 94 if prefix != "":
95 95 print "%s%s" % (prefix, changes[4].replace('\n', nlprefix).strip())
96 96 else:
97 97 print changes[4]
98 98 if prefix:
99 99 sys.stdout.write('\0')
100 100
101 101 def base(ui, repo, node1, node2):
102 102 """Output common ancestor information"""
103 103 node1 = repo.lookup(node1)
104 104 node2 = repo.lookup(node2)
105 105 n = repo.changelog.ancestor(node1, node2)
106 106 print hg.short(n)
107 107
108 108 def catfile(ui, repo, type=None, r=None, **opts):
109 109 """cat a specific revision"""
110 110 # in stdin mode, every line except the commit is prefixed with two
111 111 # spaces. This way the our caller can find the commit without magic
112 112 # strings
113 113 #
114 114 prefix = ""
115 115 if opts['stdin']:
116 116 try:
117 117 (type, r) = raw_input().split(' ');
118 118 prefix = " "
119 119 except EOFError:
120 120 return
121 121
122 122 else:
123 123 if not type or not r:
124 124 ui.warn("cat-file: type or revision not supplied\n")
125 125 commands.help_(ui, 'cat-file')
126 126
127 127 while r:
128 128 if type != "commit":
129 129 sys.stderr.write("aborting hg cat-file only understands commits\n")
130 130 sys.exit(1);
131 131 n = repo.lookup(r)
132 132 catcommit(repo, n, prefix)
133 133 if opts['stdin']:
134 134 try:
135 135 (type, r) = raw_input().split(' ');
136 136 except EOFError:
137 137 break
138 138 else:
139 139 break
140 140
141 141 # git rev-tree is a confusing thing. You can supply a number of
142 142 # commit sha1s on the command line, and it walks the commit history
143 143 # telling you which commits are reachable from the supplied ones via
144 144 # a bitmask based on arg position.
145 145 # you can specify a commit to stop at by starting the sha1 with ^
146 146 def revtree(args, repo, full="tree", maxnr=0, parents=False):
147 147 def chlogwalk():
148 148 ch = repo.changelog
149 149 count = ch.count()
150 150 i = count
151 151 l = [0] * 100
152 152 chunk = 100
153 153 while True:
154 154 if chunk > i:
155 155 chunk = i
156 156 i = 0
157 157 else:
158 158 i -= chunk
159 159
160 160 for x in xrange(0, chunk):
161 161 if i + x >= count:
162 162 l[chunk - x:] = [0] * (chunk - x)
163 163 break
164 164 if full != None:
165 165 l[x] = ch.read(ch.node(i + x))
166 166 else:
167 167 l[x] = 1
168 168 for x in xrange(chunk-1, -1, -1):
169 169 if l[x] != 0:
170 170 yield (i + x, full != None and l[x] or None)
171 171 if i == 0:
172 172 break
173 173
174 174 # calculate and return the reachability bitmask for sha
175 175 def is_reachable(ar, reachable, sha):
176 176 if len(ar) == 0:
177 177 return 1
178 178 mask = 0
179 179 for i in xrange(len(ar)):
180 180 if sha in reachable[i]:
181 181 mask |= 1 << i
182 182
183 183 return mask
184 184
185 185 reachable = []
186 186 stop_sha1 = []
187 187 want_sha1 = []
188 188 count = 0
189 189
190 190 # figure out which commits they are asking for and which ones they
191 191 # want us to stop on
192 192 for i in xrange(len(args)):
193 193 if args[i].startswith('^'):
194 194 s = repo.lookup(args[i][1:])
195 195 stop_sha1.append(s)
196 196 want_sha1.append(s)
197 197 elif args[i] != 'HEAD':
198 198 want_sha1.append(repo.lookup(args[i]))
199 199
200 200 # calculate the graph for the supplied commits
201 201 for i in xrange(len(want_sha1)):
202 202 reachable.append({});
203 203 n = want_sha1[i];
204 204 visit = [n];
205 205 reachable[i][n] = 1
206 206 while visit:
207 207 n = visit.pop(0)
208 208 if n in stop_sha1:
209 209 continue
210 210 for p in repo.changelog.parents(n):
211 211 if p not in reachable[i]:
212 212 reachable[i][p] = 1
213 213 visit.append(p)
214 214 if p in stop_sha1:
215 215 continue
216 216
217 217 # walk the repository looking for commits that are in our
218 218 # reachability graph
219 219 for i, changes in chlogwalk():
220 220 n = repo.changelog.node(i)
221 221 mask = is_reachable(want_sha1, reachable, n)
222 222 if mask:
223 223 parentstr = ""
224 224 if parents:
225 225 pp = repo.changelog.parents(n)
226 226 if pp[0] != hg.nullid:
227 227 parentstr += " " + hg.short(pp[0])
228 228 if pp[1] != hg.nullid:
229 229 parentstr += " " + hg.short(pp[1])
230 230 if not full:
231 231 print hg.short(n) + parentstr
232 232 elif full == "commit":
233 233 print hg.short(n) + parentstr
234 234 catcommit(repo, n, ' ', changes)
235 235 else:
236 236 (p1, p2) = repo.changelog.parents(n)
237 237 (h, h1, h2) = map(hg.short, (n, p1, p2))
238 238 (i1, i2) = map(repo.changelog.rev, (p1, p2))
239 239
240 240 date = changes[2][0]
241 241 print "%s %s:%s" % (date, h, mask),
242 242 mask = is_reachable(want_sha1, reachable, p1)
243 243 if i1 != hg.nullrev and mask > 0:
244 244 print "%s:%s " % (h1, mask),
245 245 mask = is_reachable(want_sha1, reachable, p2)
246 246 if i2 != hg.nullrev and mask > 0:
247 247 print "%s:%s " % (h2, mask),
248 248 print ""
249 249 if maxnr and count >= maxnr:
250 250 break
251 251 count += 1
252 252
253 253 def revparse(ui, repo, *revs, **opts):
254 254 """Parse given revisions"""
255 255 def revstr(rev):
256 256 if rev == 'HEAD':
257 257 rev = 'tip'
258 258 return revlog.hex(repo.lookup(rev))
259 259
260 260 for r in revs:
261 261 revrange = r.split(':', 1)
262 262 ui.write('%s\n' % revstr(revrange[0]))
263 263 if len(revrange) == 2:
264 264 ui.write('^%s\n' % revstr(revrange[1]))
265 265
266 266 # git rev-list tries to order things by date, and has the ability to stop
267 267 # at a given commit without walking the whole repo. TODO add the stop
268 268 # parameter
269 269 def revlist(ui, repo, *revs, **opts):
270 270 """print revisions"""
271 271 if opts['header']:
272 272 full = "commit"
273 273 else:
274 274 full = None
275 275 copy = [x for x in revs]
276 276 revtree(copy, repo, full, opts['max_count'], opts['parents'])
277 277
278 278 def view(ui, repo, *etc, **opts):
279 279 "start interactive history viewer"
280 280 os.chdir(repo.root)
281 281 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
282 282 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
283 283 ui.debug("running %s\n" % cmd)
284 284 os.system(cmd)
285 285
286 286 cmdtable = {
287 287 "^view": (view,
288 288 [('l', 'limit', '', 'limit number of changes displayed')],
289 289 'hg view [-l LIMIT] [REVRANGE]'),
290 290 "debug-diff-tree": (difftree, [('p', 'patch', None, 'generate patch'),
291 291 ('r', 'recursive', None, 'recursive'),
292 292 ('P', 'pretty', None, 'pretty'),
293 293 ('s', 'stdin', None, 'stdin'),
294 294 ('C', 'copy', None, 'detect copies'),
295 295 ('S', 'search', "", 'search')],
296 296 "hg git-diff-tree [options] node1 node2 [files...]"),
297 297 "debug-cat-file": (catfile, [('s', 'stdin', None, 'stdin')],
298 298 "hg debug-cat-file [options] type file"),
299 299 "debug-merge-base": (base, [], "hg debug-merge-base node node"),
300 300 'debug-rev-parse': (revparse,
301 301 [('', 'default', '', 'ignored')],
302 302 "hg debug-rev-parse rev"),
303 303 "debug-rev-list": (revlist, [('H', 'header', None, 'header'),
304 304 ('t', 'topo-order', None, 'topo-order'),
305 305 ('p', 'parents', None, 'parents'),
306 306 ('n', 'max-count', 0, 'max-count')],
307 307 "hg debug-rev-list [options] revs"),
308 308 }
@@ -1,2190 +1,2190 b''
1 1 # queue.py - patch queues for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 '''patch management and development
9 9
10 10 This extension lets you work with a stack of patches in a Mercurial
11 11 repository. It manages two stacks of patches - all known patches, and
12 12 applied patches (subset of known patches).
13 13
14 14 Known patches are represented as patch files in the .hg/patches
15 15 directory. Applied patches are both patch files and changesets.
16 16
17 17 Common tasks (use "hg help command" for more details):
18 18
19 19 prepare repository to work with patches qinit
20 20 create new patch qnew
21 21 import existing patch qimport
22 22
23 23 print patch series qseries
24 24 print applied patches qapplied
25 25 print name of top applied patch qtop
26 26
27 27 add known patch to applied stack qpush
28 28 remove patch from applied stack qpop
29 29 refresh contents of top applied patch qrefresh
30 30 '''
31 31
32 32 from mercurial.i18n import _
33 33 from mercurial import commands, cmdutil, hg, patch, revlog, util, changegroup
34 import os, sys, re, struct, traceback, errno, bz2
34 import os, sys, re, errno
35 35
36 36 commands.norepo += " qclone qversion"
37 37
38 38 class statusentry:
39 39 def __init__(self, rev, name=None):
40 40 if not name:
41 41 fields = rev.split(':', 1)
42 42 if len(fields) == 2:
43 43 self.rev, self.name = fields
44 44 else:
45 45 self.rev, self.name = None, None
46 46 else:
47 47 self.rev, self.name = rev, name
48 48
49 49 def __str__(self):
50 50 return self.rev + ':' + self.name
51 51
52 52 class queue:
53 53 def __init__(self, ui, path, patchdir=None):
54 54 self.basepath = path
55 55 self.path = patchdir or os.path.join(path, "patches")
56 56 self.opener = util.opener(self.path)
57 57 self.ui = ui
58 58 self.applied = []
59 59 self.full_series = []
60 60 self.applied_dirty = 0
61 61 self.series_dirty = 0
62 62 self.series_path = "series"
63 63 self.status_path = "status"
64 64 self.guards_path = "guards"
65 65 self.active_guards = None
66 66 self.guards_dirty = False
67 67 self._diffopts = None
68 68
69 69 if os.path.exists(self.join(self.series_path)):
70 70 self.full_series = self.opener(self.series_path).read().splitlines()
71 71 self.parse_series()
72 72
73 73 if os.path.exists(self.join(self.status_path)):
74 74 lines = self.opener(self.status_path).read().splitlines()
75 75 self.applied = [statusentry(l) for l in lines]
76 76
77 77 def diffopts(self):
78 78 if self._diffopts is None:
79 79 self._diffopts = patch.diffopts(self.ui)
80 80 return self._diffopts
81 81
82 82 def join(self, *p):
83 83 return os.path.join(self.path, *p)
84 84
85 85 def find_series(self, patch):
86 86 pre = re.compile("(\s*)([^#]+)")
87 87 index = 0
88 88 for l in self.full_series:
89 89 m = pre.match(l)
90 90 if m:
91 91 s = m.group(2)
92 92 s = s.rstrip()
93 93 if s == patch:
94 94 return index
95 95 index += 1
96 96 return None
97 97
98 98 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
99 99
100 100 def parse_series(self):
101 101 self.series = []
102 102 self.series_guards = []
103 103 for l in self.full_series:
104 104 h = l.find('#')
105 105 if h == -1:
106 106 patch = l
107 107 comment = ''
108 108 elif h == 0:
109 109 continue
110 110 else:
111 111 patch = l[:h]
112 112 comment = l[h:]
113 113 patch = patch.strip()
114 114 if patch:
115 115 if patch in self.series:
116 116 raise util.Abort(_('%s appears more than once in %s') %
117 117 (patch, self.join(self.series_path)))
118 118 self.series.append(patch)
119 119 self.series_guards.append(self.guard_re.findall(comment))
120 120
121 121 def check_guard(self, guard):
122 122 bad_chars = '# \t\r\n\f'
123 123 first = guard[0]
124 124 for c in '-+':
125 125 if first == c:
126 126 return (_('guard %r starts with invalid character: %r') %
127 127 (guard, c))
128 128 for c in bad_chars:
129 129 if c in guard:
130 130 return _('invalid character in guard %r: %r') % (guard, c)
131 131
132 132 def set_active(self, guards):
133 133 for guard in guards:
134 134 bad = self.check_guard(guard)
135 135 if bad:
136 136 raise util.Abort(bad)
137 137 guards = dict.fromkeys(guards).keys()
138 138 guards.sort()
139 139 self.ui.debug('active guards: %s\n' % ' '.join(guards))
140 140 self.active_guards = guards
141 141 self.guards_dirty = True
142 142
143 143 def active(self):
144 144 if self.active_guards is None:
145 145 self.active_guards = []
146 146 try:
147 147 guards = self.opener(self.guards_path).read().split()
148 148 except IOError, err:
149 149 if err.errno != errno.ENOENT: raise
150 150 guards = []
151 151 for i, guard in enumerate(guards):
152 152 bad = self.check_guard(guard)
153 153 if bad:
154 154 self.ui.warn('%s:%d: %s\n' %
155 155 (self.join(self.guards_path), i + 1, bad))
156 156 else:
157 157 self.active_guards.append(guard)
158 158 return self.active_guards
159 159
160 160 def set_guards(self, idx, guards):
161 161 for g in guards:
162 162 if len(g) < 2:
163 163 raise util.Abort(_('guard %r too short') % g)
164 164 if g[0] not in '-+':
165 165 raise util.Abort(_('guard %r starts with invalid char') % g)
166 166 bad = self.check_guard(g[1:])
167 167 if bad:
168 168 raise util.Abort(bad)
169 169 drop = self.guard_re.sub('', self.full_series[idx])
170 170 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
171 171 self.parse_series()
172 172 self.series_dirty = True
173 173
174 174 def pushable(self, idx):
175 175 if isinstance(idx, str):
176 176 idx = self.series.index(idx)
177 177 patchguards = self.series_guards[idx]
178 178 if not patchguards:
179 179 return True, None
180 180 default = False
181 181 guards = self.active()
182 182 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
183 183 if exactneg:
184 184 return False, exactneg[0]
185 185 pos = [g for g in patchguards if g[0] == '+']
186 186 exactpos = [g for g in pos if g[1:] in guards]
187 187 if pos:
188 188 if exactpos:
189 189 return True, exactpos[0]
190 190 return False, pos
191 191 return True, ''
192 192
193 193 def explain_pushable(self, idx, all_patches=False):
194 194 write = all_patches and self.ui.write or self.ui.warn
195 195 if all_patches or self.ui.verbose:
196 196 if isinstance(idx, str):
197 197 idx = self.series.index(idx)
198 198 pushable, why = self.pushable(idx)
199 199 if all_patches and pushable:
200 200 if why is None:
201 201 write(_('allowing %s - no guards in effect\n') %
202 202 self.series[idx])
203 203 else:
204 204 if not why:
205 205 write(_('allowing %s - no matching negative guards\n') %
206 206 self.series[idx])
207 207 else:
208 208 write(_('allowing %s - guarded by %r\n') %
209 209 (self.series[idx], why))
210 210 if not pushable:
211 211 if why:
212 212 write(_('skipping %s - guarded by %r\n') %
213 213 (self.series[idx], why))
214 214 else:
215 215 write(_('skipping %s - no matching guards\n') %
216 216 self.series[idx])
217 217
218 218 def save_dirty(self):
219 219 def write_list(items, path):
220 220 fp = self.opener(path, 'w')
221 221 for i in items:
222 222 print >> fp, i
223 223 fp.close()
224 224 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
225 225 if self.series_dirty: write_list(self.full_series, self.series_path)
226 226 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
227 227
228 228 def readheaders(self, patch):
229 229 def eatdiff(lines):
230 230 while lines:
231 231 l = lines[-1]
232 232 if (l.startswith("diff -") or
233 233 l.startswith("Index:") or
234 234 l.startswith("===========")):
235 235 del lines[-1]
236 236 else:
237 237 break
238 238 def eatempty(lines):
239 239 while lines:
240 240 l = lines[-1]
241 241 if re.match('\s*$', l):
242 242 del lines[-1]
243 243 else:
244 244 break
245 245
246 246 pf = self.join(patch)
247 247 message = []
248 248 comments = []
249 249 user = None
250 250 date = None
251 251 format = None
252 252 subject = None
253 253 diffstart = 0
254 254
255 255 for line in file(pf):
256 256 line = line.rstrip()
257 257 if line.startswith('diff --git'):
258 258 diffstart = 2
259 259 break
260 260 if diffstart:
261 261 if line.startswith('+++ '):
262 262 diffstart = 2
263 263 break
264 264 if line.startswith("--- "):
265 265 diffstart = 1
266 266 continue
267 267 elif format == "hgpatch":
268 268 # parse values when importing the result of an hg export
269 269 if line.startswith("# User "):
270 270 user = line[7:]
271 271 elif line.startswith("# Date "):
272 272 date = line[7:]
273 273 elif not line.startswith("# ") and line:
274 274 message.append(line)
275 275 format = None
276 276 elif line == '# HG changeset patch':
277 277 format = "hgpatch"
278 278 elif (format != "tagdone" and (line.startswith("Subject: ") or
279 279 line.startswith("subject: "))):
280 280 subject = line[9:]
281 281 format = "tag"
282 282 elif (format != "tagdone" and (line.startswith("From: ") or
283 283 line.startswith("from: "))):
284 284 user = line[6:]
285 285 format = "tag"
286 286 elif format == "tag" and line == "":
287 287 # when looking for tags (subject: from: etc) they
288 288 # end once you find a blank line in the source
289 289 format = "tagdone"
290 290 elif message or line:
291 291 message.append(line)
292 292 comments.append(line)
293 293
294 294 eatdiff(message)
295 295 eatdiff(comments)
296 296 eatempty(message)
297 297 eatempty(comments)
298 298
299 299 # make sure message isn't empty
300 300 if format and format.startswith("tag") and subject:
301 301 message.insert(0, "")
302 302 message.insert(0, subject)
303 303 return (message, comments, user, date, diffstart > 1)
304 304
305 305 def printdiff(self, repo, node1, node2=None, files=None,
306 306 fp=None, changes=None, opts={}):
307 307 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
308 308
309 309 patch.diff(repo, node1, node2, fns, match=matchfn,
310 310 fp=fp, changes=changes, opts=self.diffopts())
311 311
312 312 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
313 313 # first try just applying the patch
314 314 (err, n) = self.apply(repo, [ patch ], update_status=False,
315 315 strict=True, merge=rev, wlock=wlock)
316 316
317 317 if err == 0:
318 318 return (err, n)
319 319
320 320 if n is None:
321 321 raise util.Abort(_("apply failed for patch %s") % patch)
322 322
323 323 self.ui.warn("patch didn't work out, merging %s\n" % patch)
324 324
325 325 # apply failed, strip away that rev and merge.
326 326 hg.clean(repo, head, wlock=wlock)
327 327 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
328 328
329 329 c = repo.changelog.read(rev)
330 330 ret = hg.merge(repo, rev, wlock=wlock)
331 331 if ret:
332 332 raise util.Abort(_("update returned %d") % ret)
333 333 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
334 334 if n == None:
335 335 raise util.Abort(_("repo commit failed"))
336 336 try:
337 337 message, comments, user, date, patchfound = mergeq.readheaders(patch)
338 338 except:
339 339 raise util.Abort(_("unable to read %s") % patch)
340 340
341 341 patchf = self.opener(patch, "w")
342 342 if comments:
343 343 comments = "\n".join(comments) + '\n\n'
344 344 patchf.write(comments)
345 345 self.printdiff(repo, head, n, fp=patchf)
346 346 patchf.close()
347 347 return (0, n)
348 348
349 349 def qparents(self, repo, rev=None):
350 350 if rev is None:
351 351 (p1, p2) = repo.dirstate.parents()
352 352 if p2 == revlog.nullid:
353 353 return p1
354 354 if len(self.applied) == 0:
355 355 return None
356 356 return revlog.bin(self.applied[-1].rev)
357 357 pp = repo.changelog.parents(rev)
358 358 if pp[1] != revlog.nullid:
359 359 arevs = [ x.rev for x in self.applied ]
360 360 p0 = revlog.hex(pp[0])
361 361 p1 = revlog.hex(pp[1])
362 362 if p0 in arevs:
363 363 return pp[0]
364 364 if p1 in arevs:
365 365 return pp[1]
366 366 return pp[0]
367 367
368 368 def mergepatch(self, repo, mergeq, series, wlock):
369 369 if len(self.applied) == 0:
370 370 # each of the patches merged in will have two parents. This
371 371 # can confuse the qrefresh, qdiff, and strip code because it
372 372 # needs to know which parent is actually in the patch queue.
373 373 # so, we insert a merge marker with only one parent. This way
374 374 # the first patch in the queue is never a merge patch
375 375 #
376 376 pname = ".hg.patches.merge.marker"
377 377 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
378 378 wlock=wlock)
379 379 self.applied.append(statusentry(revlog.hex(n), pname))
380 380 self.applied_dirty = 1
381 381
382 382 head = self.qparents(repo)
383 383
384 384 for patch in series:
385 385 patch = mergeq.lookup(patch, strict=True)
386 386 if not patch:
387 387 self.ui.warn("patch %s does not exist\n" % patch)
388 388 return (1, None)
389 389 pushable, reason = self.pushable(patch)
390 390 if not pushable:
391 391 self.explain_pushable(patch, all_patches=True)
392 392 continue
393 393 info = mergeq.isapplied(patch)
394 394 if not info:
395 395 self.ui.warn("patch %s is not applied\n" % patch)
396 396 return (1, None)
397 397 rev = revlog.bin(info[1])
398 398 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
399 399 if head:
400 400 self.applied.append(statusentry(revlog.hex(head), patch))
401 401 self.applied_dirty = 1
402 402 if err:
403 403 return (err, head)
404 404 return (0, head)
405 405
406 406 def patch(self, repo, patchfile):
407 407 '''Apply patchfile to the working directory.
408 408 patchfile: file name of patch'''
409 409 files = {}
410 410 try:
411 411 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
412 412 files=files)
413 413 except Exception, inst:
414 414 self.ui.note(str(inst) + '\n')
415 415 if not self.ui.verbose:
416 416 self.ui.warn("patch failed, unable to continue (try -v)\n")
417 417 return (False, files, False)
418 418
419 419 return (True, files, fuzz)
420 420
421 421 def apply(self, repo, series, list=False, update_status=True,
422 422 strict=False, patchdir=None, merge=None, wlock=None):
423 423 # TODO unify with commands.py
424 424 if not patchdir:
425 425 patchdir = self.path
426 426 err = 0
427 427 if not wlock:
428 428 wlock = repo.wlock()
429 429 lock = repo.lock()
430 430 tr = repo.transaction()
431 431 n = None
432 432 for patchname in series:
433 433 pushable, reason = self.pushable(patchname)
434 434 if not pushable:
435 435 self.explain_pushable(patchname, all_patches=True)
436 436 continue
437 437 self.ui.warn("applying %s\n" % patchname)
438 438 pf = os.path.join(patchdir, patchname)
439 439
440 440 try:
441 441 message, comments, user, date, patchfound = self.readheaders(patchname)
442 442 except:
443 443 self.ui.warn("Unable to read %s\n" % patchname)
444 444 err = 1
445 445 break
446 446
447 447 if not message:
448 448 message = "imported patch %s\n" % patchname
449 449 else:
450 450 if list:
451 451 message.append("\nimported patch %s" % patchname)
452 452 message = '\n'.join(message)
453 453
454 454 (patcherr, files, fuzz) = self.patch(repo, pf)
455 455 patcherr = not patcherr
456 456
457 457 if merge and files:
458 458 # Mark as merged and update dirstate parent info
459 459 repo.dirstate.update(repo.dirstate.filterfiles(files.keys()), 'm')
460 460 p1, p2 = repo.dirstate.parents()
461 461 repo.dirstate.setparents(p1, merge)
462 462 files = patch.updatedir(self.ui, repo, files, wlock=wlock)
463 463 n = repo.commit(files, message, user, date, force=1, lock=lock,
464 464 wlock=wlock)
465 465
466 466 if n == None:
467 467 raise util.Abort(_("repo commit failed"))
468 468
469 469 if update_status:
470 470 self.applied.append(statusentry(revlog.hex(n), patchname))
471 471
472 472 if patcherr:
473 473 if not patchfound:
474 474 self.ui.warn("patch %s is empty\n" % patchname)
475 475 err = 0
476 476 else:
477 477 self.ui.warn("patch failed, rejects left in working dir\n")
478 478 err = 1
479 479 break
480 480
481 481 if fuzz and strict:
482 482 self.ui.warn("fuzz found when applying patch, stopping\n")
483 483 err = 1
484 484 break
485 485 tr.close()
486 486 return (err, n)
487 487
488 488 def delete(self, repo, patches, opts):
489 489 realpatches = []
490 490 for patch in patches:
491 491 patch = self.lookup(patch, strict=True)
492 492 info = self.isapplied(patch)
493 493 if info:
494 494 raise util.Abort(_("cannot delete applied patch %s") % patch)
495 495 if patch not in self.series:
496 496 raise util.Abort(_("patch %s not in series file") % patch)
497 497 realpatches.append(patch)
498 498
499 499 appliedbase = 0
500 500 if opts.get('rev'):
501 501 if not self.applied:
502 502 raise util.Abort(_('no patches applied'))
503 503 revs = cmdutil.revrange(repo, opts['rev'])
504 504 if len(revs) > 1 and revs[0] > revs[1]:
505 505 revs.reverse()
506 506 for rev in revs:
507 507 if appliedbase >= len(self.applied):
508 508 raise util.Abort(_("revision %d is not managed") % rev)
509 509
510 510 base = revlog.bin(self.applied[appliedbase].rev)
511 511 node = repo.changelog.node(rev)
512 512 if node != base:
513 513 raise util.Abort(_("cannot delete revision %d above "
514 514 "applied patches") % rev)
515 515 realpatches.append(self.applied[appliedbase].name)
516 516 appliedbase += 1
517 517
518 518 if not opts.get('keep'):
519 519 r = self.qrepo()
520 520 if r:
521 521 r.remove(realpatches, True)
522 522 else:
523 523 for p in realpatches:
524 524 os.unlink(self.join(p))
525 525
526 526 if appliedbase:
527 527 del self.applied[:appliedbase]
528 528 self.applied_dirty = 1
529 529 indices = [self.find_series(p) for p in realpatches]
530 530 indices.sort()
531 531 for i in indices[-1::-1]:
532 532 del self.full_series[i]
533 533 self.parse_series()
534 534 self.series_dirty = 1
535 535
536 536 def check_toppatch(self, repo):
537 537 if len(self.applied) > 0:
538 538 top = revlog.bin(self.applied[-1].rev)
539 539 pp = repo.dirstate.parents()
540 540 if top not in pp:
541 541 raise util.Abort(_("queue top not at same revision as working directory"))
542 542 return top
543 543 return None
544 544 def check_localchanges(self, repo, force=False, refresh=True):
545 545 m, a, r, d = repo.status()[:4]
546 546 if m or a or r or d:
547 547 if not force:
548 548 if refresh:
549 549 raise util.Abort(_("local changes found, refresh first"))
550 550 else:
551 551 raise util.Abort(_("local changes found"))
552 552 return m, a, r, d
553 553 def new(self, repo, patch, msg=None, force=None):
554 554 if os.path.exists(self.join(patch)):
555 555 raise util.Abort(_('patch "%s" already exists') % patch)
556 556 m, a, r, d = self.check_localchanges(repo, force)
557 557 commitfiles = m + a + r
558 558 self.check_toppatch(repo)
559 559 wlock = repo.wlock()
560 560 insert = self.full_series_end()
561 561 if msg:
562 562 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
563 563 wlock=wlock)
564 564 else:
565 565 n = repo.commit(commitfiles,
566 566 "New patch: %s" % patch, force=True, wlock=wlock)
567 567 if n == None:
568 568 raise util.Abort(_("repo commit failed"))
569 569 self.full_series[insert:insert] = [patch]
570 570 self.applied.append(statusentry(revlog.hex(n), patch))
571 571 self.parse_series()
572 572 self.series_dirty = 1
573 573 self.applied_dirty = 1
574 574 p = self.opener(patch, "w")
575 575 if msg:
576 576 msg = msg + "\n"
577 577 p.write(msg)
578 578 p.close()
579 579 wlock = None
580 580 r = self.qrepo()
581 581 if r: r.add([patch])
582 582 if commitfiles:
583 583 self.refresh(repo, short=True)
584 584
585 585 def strip(self, repo, rev, update=True, backup="all", wlock=None):
586 586 def limitheads(chlog, stop):
587 587 """return the list of all nodes that have no children"""
588 588 p = {}
589 589 h = []
590 590 stoprev = 0
591 591 if stop in chlog.nodemap:
592 592 stoprev = chlog.rev(stop)
593 593
594 594 for r in xrange(chlog.count() - 1, -1, -1):
595 595 n = chlog.node(r)
596 596 if n not in p:
597 597 h.append(n)
598 598 if n == stop:
599 599 break
600 600 if r < stoprev:
601 601 break
602 602 for pn in chlog.parents(n):
603 603 p[pn] = 1
604 604 return h
605 605
606 606 def bundle(cg):
607 607 backupdir = repo.join("strip-backup")
608 608 if not os.path.isdir(backupdir):
609 609 os.mkdir(backupdir)
610 610 name = os.path.join(backupdir, "%s" % revlog.short(rev))
611 611 name = savename(name)
612 612 self.ui.warn("saving bundle to %s\n" % name)
613 613 return changegroup.writebundle(cg, name, "HG10BZ")
614 614
615 615 def stripall(rev, revnum):
616 616 cl = repo.changelog
617 617 c = cl.read(rev)
618 618 mm = repo.manifest.read(c[0])
619 619 seen = {}
620 620
621 621 for x in xrange(revnum, cl.count()):
622 622 c = cl.read(cl.node(x))
623 623 for f in c[3]:
624 624 if f in seen:
625 625 continue
626 626 seen[f] = 1
627 627 if f in mm:
628 628 filerev = mm[f]
629 629 else:
630 630 filerev = 0
631 631 seen[f] = filerev
632 632 # we go in two steps here so the strip loop happens in a
633 633 # sensible order. When stripping many files, this helps keep
634 634 # our disk access patterns under control.
635 635 seen_list = seen.keys()
636 636 seen_list.sort()
637 637 for f in seen_list:
638 638 ff = repo.file(f)
639 639 filerev = seen[f]
640 640 if filerev != 0:
641 641 if filerev in ff.nodemap:
642 642 filerev = ff.rev(filerev)
643 643 else:
644 644 filerev = 0
645 645 ff.strip(filerev, revnum)
646 646
647 647 if not wlock:
648 648 wlock = repo.wlock()
649 649 lock = repo.lock()
650 650 chlog = repo.changelog
651 651 # TODO delete the undo files, and handle undo of merge sets
652 652 pp = chlog.parents(rev)
653 653 revnum = chlog.rev(rev)
654 654
655 655 if update:
656 656 self.check_localchanges(repo, refresh=False)
657 657 urev = self.qparents(repo, rev)
658 658 hg.clean(repo, urev, wlock=wlock)
659 659 repo.dirstate.write()
660 660
661 661 # save is a list of all the branches we are truncating away
662 662 # that we actually want to keep. changegroup will be used
663 663 # to preserve them and add them back after the truncate
664 664 saveheads = []
665 665 savebases = {}
666 666
667 667 heads = limitheads(chlog, rev)
668 668 seen = {}
669 669
670 670 # search through all the heads, finding those where the revision
671 671 # we want to strip away is an ancestor. Also look for merges
672 672 # that might be turned into new heads by the strip.
673 673 while heads:
674 674 h = heads.pop()
675 675 n = h
676 676 while True:
677 677 seen[n] = 1
678 678 pp = chlog.parents(n)
679 679 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
680 680 if pp[1] not in seen:
681 681 heads.append(pp[1])
682 682 if pp[0] == revlog.nullid:
683 683 break
684 684 if chlog.rev(pp[0]) < revnum:
685 685 break
686 686 n = pp[0]
687 687 if n == rev:
688 688 break
689 689 r = chlog.reachable(h, rev)
690 690 if rev not in r:
691 691 saveheads.append(h)
692 692 for x in r:
693 693 if chlog.rev(x) > revnum:
694 694 savebases[x] = 1
695 695
696 696 # create a changegroup for all the branches we need to keep
697 697 if backup == "all":
698 698 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
699 699 bundle(backupch)
700 700 if saveheads:
701 701 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
702 702 chgrpfile = bundle(backupch)
703 703 chgrpfile = 'file:%s' % chgrpfile
704 704
705 705 stripall(rev, revnum)
706 706
707 707 change = chlog.read(rev)
708 708 chlog.strip(revnum, revnum)
709 709 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
710 710 if saveheads:
711 711 self.ui.status("adding branch\n")
712 712 commands.unbundle(self.ui, repo, chgrpfile, update=False)
713 713 if backup != "strip":
714 714 os.unlink(chgrpfile)
715 715
716 716 def isapplied(self, patch):
717 717 """returns (index, rev, patch)"""
718 718 for i in xrange(len(self.applied)):
719 719 a = self.applied[i]
720 720 if a.name == patch:
721 721 return (i, a.rev, a.name)
722 722 return None
723 723
724 724 # if the exact patch name does not exist, we try a few
725 725 # variations. If strict is passed, we try only #1
726 726 #
727 727 # 1) a number to indicate an offset in the series file
728 728 # 2) a unique substring of the patch name was given
729 729 # 3) patchname[-+]num to indicate an offset in the series file
730 730 def lookup(self, patch, strict=False):
731 731 patch = patch and str(patch)
732 732
733 733 def partial_name(s):
734 734 if s in self.series:
735 735 return s
736 736 matches = [x for x in self.series if s in x]
737 737 if len(matches) > 1:
738 738 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
739 739 for m in matches:
740 740 self.ui.warn(' %s\n' % m)
741 741 return None
742 742 if matches:
743 743 return matches[0]
744 744 if len(self.series) > 0 and len(self.applied) > 0:
745 745 if s == 'qtip':
746 746 return self.series[self.series_end(True)-1]
747 747 if s == 'qbase':
748 748 return self.series[0]
749 749 return None
750 750 if patch == None:
751 751 return None
752 752
753 753 # we don't want to return a partial match until we make
754 754 # sure the file name passed in does not exist (checked below)
755 755 res = partial_name(patch)
756 756 if res and res == patch:
757 757 return res
758 758
759 759 if not os.path.isfile(self.join(patch)):
760 760 try:
761 761 sno = int(patch)
762 762 except(ValueError, OverflowError):
763 763 pass
764 764 else:
765 765 if sno < len(self.series):
766 766 return self.series[sno]
767 767 if not strict:
768 768 # return any partial match made above
769 769 if res:
770 770 return res
771 771 minus = patch.rfind('-')
772 772 if minus >= 0:
773 773 res = partial_name(patch[:minus])
774 774 if res:
775 775 i = self.series.index(res)
776 776 try:
777 777 off = int(patch[minus+1:] or 1)
778 778 except(ValueError, OverflowError):
779 779 pass
780 780 else:
781 781 if i - off >= 0:
782 782 return self.series[i - off]
783 783 plus = patch.rfind('+')
784 784 if plus >= 0:
785 785 res = partial_name(patch[:plus])
786 786 if res:
787 787 i = self.series.index(res)
788 788 try:
789 789 off = int(patch[plus+1:] or 1)
790 790 except(ValueError, OverflowError):
791 791 pass
792 792 else:
793 793 if i + off < len(self.series):
794 794 return self.series[i + off]
795 795 raise util.Abort(_("patch %s not in series") % patch)
796 796
797 797 def push(self, repo, patch=None, force=False, list=False,
798 798 mergeq=None, wlock=None):
799 799 if not wlock:
800 800 wlock = repo.wlock()
801 801 patch = self.lookup(patch)
802 802 if patch and self.isapplied(patch):
803 803 raise util.Abort(_("patch %s is already applied") % patch)
804 804 if self.series_end() == len(self.series):
805 805 raise util.Abort(_("patch series fully applied"))
806 806 if not force:
807 807 self.check_localchanges(repo)
808 808
809 809 self.applied_dirty = 1;
810 810 start = self.series_end()
811 811 if start > 0:
812 812 self.check_toppatch(repo)
813 813 if not patch:
814 814 patch = self.series[start]
815 815 end = start + 1
816 816 else:
817 817 end = self.series.index(patch, start) + 1
818 818 s = self.series[start:end]
819 819 if mergeq:
820 820 ret = self.mergepatch(repo, mergeq, s, wlock)
821 821 else:
822 822 ret = self.apply(repo, s, list, wlock=wlock)
823 823 top = self.applied[-1].name
824 824 if ret[0]:
825 825 self.ui.write("Errors during apply, please fix and refresh %s\n" %
826 826 top)
827 827 else:
828 828 self.ui.write("Now at: %s\n" % top)
829 829 return ret[0]
830 830
831 831 def pop(self, repo, patch=None, force=False, update=True, all=False,
832 832 wlock=None):
833 833 def getfile(f, rev):
834 834 t = repo.file(f).read(rev)
835 835 try:
836 836 repo.wfile(f, "w").write(t)
837 837 except IOError:
838 838 try:
839 839 os.makedirs(os.path.dirname(repo.wjoin(f)))
840 840 except OSError, err:
841 841 if err.errno != errno.EEXIST: raise
842 842 repo.wfile(f, "w").write(t)
843 843
844 844 if not wlock:
845 845 wlock = repo.wlock()
846 846 if patch:
847 847 # index, rev, patch
848 848 info = self.isapplied(patch)
849 849 if not info:
850 850 patch = self.lookup(patch)
851 851 info = self.isapplied(patch)
852 852 if not info:
853 853 raise util.Abort(_("patch %s is not applied") % patch)
854 854 if len(self.applied) == 0:
855 855 raise util.Abort(_("no patches applied"))
856 856
857 857 if not update:
858 858 parents = repo.dirstate.parents()
859 859 rr = [ revlog.bin(x.rev) for x in self.applied ]
860 860 for p in parents:
861 861 if p in rr:
862 862 self.ui.warn("qpop: forcing dirstate update\n")
863 863 update = True
864 864
865 865 if not force and update:
866 866 self.check_localchanges(repo)
867 867
868 868 self.applied_dirty = 1;
869 869 end = len(self.applied)
870 870 if not patch:
871 871 if all:
872 872 popi = 0
873 873 else:
874 874 popi = len(self.applied) - 1
875 875 else:
876 876 popi = info[0] + 1
877 877 if popi >= end:
878 878 self.ui.warn("qpop: %s is already at the top\n" % patch)
879 879 return
880 880 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
881 881
882 882 start = info[0]
883 883 rev = revlog.bin(info[1])
884 884
885 885 # we know there are no local changes, so we can make a simplified
886 886 # form of hg.update.
887 887 if update:
888 888 top = self.check_toppatch(repo)
889 889 qp = self.qparents(repo, rev)
890 890 changes = repo.changelog.read(qp)
891 891 mmap = repo.manifest.read(changes[0])
892 892 m, a, r, d, u = repo.status(qp, top)[:5]
893 893 if d:
894 894 raise util.Abort("deletions found between repo revs")
895 895 for f in m:
896 896 getfile(f, mmap[f])
897 897 for f in r:
898 898 getfile(f, mmap[f])
899 899 util.set_exec(repo.wjoin(f), mmap.execf(f))
900 900 repo.dirstate.update(m + r, 'n')
901 901 for f in a:
902 902 try:
903 903 os.unlink(repo.wjoin(f))
904 904 except OSError, e:
905 905 if e.errno != errno.ENOENT:
906 906 raise
907 907 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
908 908 except: pass
909 909 if a:
910 910 repo.dirstate.forget(a)
911 911 repo.dirstate.setparents(qp, revlog.nullid)
912 912 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
913 913 del self.applied[start:end]
914 914 if len(self.applied):
915 915 self.ui.write("Now at: %s\n" % self.applied[-1].name)
916 916 else:
917 917 self.ui.write("Patch queue now empty\n")
918 918
919 919 def diff(self, repo, pats, opts):
920 920 top = self.check_toppatch(repo)
921 921 if not top:
922 922 self.ui.write("No patches applied\n")
923 923 return
924 924 qp = self.qparents(repo, top)
925 925 if opts.get('git'):
926 926 self.diffopts().git = True
927 927 self.printdiff(repo, qp, files=pats, opts=opts)
928 928
929 929 def refresh(self, repo, pats=None, **opts):
930 930 if len(self.applied) == 0:
931 931 self.ui.write("No patches applied\n")
932 932 return 1
933 933 wlock = repo.wlock()
934 934 self.check_toppatch(repo)
935 935 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
936 936 top = revlog.bin(top)
937 937 cparents = repo.changelog.parents(top)
938 938 patchparent = self.qparents(repo, top)
939 939 message, comments, user, date, patchfound = self.readheaders(patchfn)
940 940
941 941 patchf = self.opener(patchfn, "w")
942 942 msg = opts.get('msg', '').rstrip()
943 943 if msg:
944 944 if comments:
945 945 # Remove existing message.
946 946 ci = 0
947 947 for mi in xrange(len(message)):
948 948 while message[mi] != comments[ci]:
949 949 ci += 1
950 950 del comments[ci]
951 951 comments.append(msg)
952 952 if comments:
953 953 comments = "\n".join(comments) + '\n\n'
954 954 patchf.write(comments)
955 955
956 956 if opts.get('git'):
957 957 self.diffopts().git = True
958 958 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
959 959 tip = repo.changelog.tip()
960 960 if top == tip:
961 961 # if the top of our patch queue is also the tip, there is an
962 962 # optimization here. We update the dirstate in place and strip
963 963 # off the tip commit. Then just commit the current directory
964 964 # tree. We can also send repo.commit the list of files
965 965 # changed to speed up the diff
966 966 #
967 967 # in short mode, we only diff the files included in the
968 968 # patch already
969 969 #
970 970 # this should really read:
971 971 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
972 972 # but we do it backwards to take advantage of manifest/chlog
973 973 # caching against the next repo.status call
974 974 #
975 975 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
976 976 changes = repo.changelog.read(tip)
977 977 man = repo.manifest.read(changes[0])
978 978 aaa = aa[:]
979 979 if opts.get('short'):
980 980 filelist = mm + aa + dd
981 981 else:
982 982 filelist = None
983 983 m, a, r, d, u = repo.status(files=filelist)[:5]
984 984
985 985 # we might end up with files that were added between tip and
986 986 # the dirstate parent, but then changed in the local dirstate.
987 987 # in this case, we want them to only show up in the added section
988 988 for x in m:
989 989 if x not in aa:
990 990 mm.append(x)
991 991 # we might end up with files added by the local dirstate that
992 992 # were deleted by the patch. In this case, they should only
993 993 # show up in the changed section.
994 994 for x in a:
995 995 if x in dd:
996 996 del dd[dd.index(x)]
997 997 mm.append(x)
998 998 else:
999 999 aa.append(x)
1000 1000 # make sure any files deleted in the local dirstate
1001 1001 # are not in the add or change column of the patch
1002 1002 forget = []
1003 1003 for x in d + r:
1004 1004 if x in aa:
1005 1005 del aa[aa.index(x)]
1006 1006 forget.append(x)
1007 1007 continue
1008 1008 elif x in mm:
1009 1009 del mm[mm.index(x)]
1010 1010 dd.append(x)
1011 1011
1012 1012 m = util.unique(mm)
1013 1013 r = util.unique(dd)
1014 1014 a = util.unique(aa)
1015 1015 filelist = filter(matchfn, util.unique(m + r + a))
1016 1016 patch.diff(repo, patchparent, files=filelist, match=matchfn,
1017 1017 fp=patchf, changes=(m, a, r, [], u),
1018 1018 opts=self.diffopts())
1019 1019 patchf.close()
1020 1020
1021 1021 repo.dirstate.setparents(*cparents)
1022 1022 copies = {}
1023 1023 for dst in a:
1024 1024 src = repo.dirstate.copied(dst)
1025 1025 if src is None:
1026 1026 continue
1027 1027 copies.setdefault(src, []).append(dst)
1028 1028 repo.dirstate.update(a, 'a')
1029 1029 # remember the copies between patchparent and tip
1030 1030 # this may be slow, so don't do it if we're not tracking copies
1031 1031 if self.diffopts().git:
1032 1032 for dst in aaa:
1033 1033 f = repo.file(dst)
1034 1034 src = f.renamed(man[dst])
1035 1035 if src:
1036 1036 copies[src[0]] = copies.get(dst, [])
1037 1037 if dst in a:
1038 1038 copies[src[0]].append(dst)
1039 1039 # we can't copy a file created by the patch itself
1040 1040 if dst in copies:
1041 1041 del copies[dst]
1042 1042 for src, dsts in copies.iteritems():
1043 1043 for dst in dsts:
1044 1044 repo.dirstate.copy(src, dst)
1045 1045 repo.dirstate.update(r, 'r')
1046 1046 # if the patch excludes a modified file, mark that file with mtime=0
1047 1047 # so status can see it.
1048 1048 mm = []
1049 1049 for i in xrange(len(m)-1, -1, -1):
1050 1050 if not matchfn(m[i]):
1051 1051 mm.append(m[i])
1052 1052 del m[i]
1053 1053 repo.dirstate.update(m, 'n')
1054 1054 repo.dirstate.update(mm, 'n', st_mtime=0)
1055 1055 repo.dirstate.forget(forget)
1056 1056
1057 1057 if not msg:
1058 1058 if not message:
1059 1059 message = "patch queue: %s\n" % patchfn
1060 1060 else:
1061 1061 message = "\n".join(message)
1062 1062 else:
1063 1063 message = msg
1064 1064
1065 1065 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
1066 1066 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
1067 1067 self.applied[-1] = statusentry(revlog.hex(n), patchfn)
1068 1068 self.applied_dirty = 1
1069 1069 else:
1070 1070 self.printdiff(repo, patchparent, fp=patchf)
1071 1071 patchf.close()
1072 1072 added = repo.status()[1]
1073 1073 for a in added:
1074 1074 f = repo.wjoin(a)
1075 1075 try:
1076 1076 os.unlink(f)
1077 1077 except OSError, e:
1078 1078 if e.errno != errno.ENOENT:
1079 1079 raise
1080 1080 try: os.removedirs(os.path.dirname(f))
1081 1081 except: pass
1082 1082 # forget the file copies in the dirstate
1083 1083 # push should readd the files later on
1084 1084 repo.dirstate.forget(added)
1085 1085 self.pop(repo, force=True, wlock=wlock)
1086 1086 self.push(repo, force=True, wlock=wlock)
1087 1087
1088 1088 def init(self, repo, create=False):
1089 1089 if os.path.isdir(self.path):
1090 1090 raise util.Abort(_("patch queue directory already exists"))
1091 1091 os.mkdir(self.path)
1092 1092 if create:
1093 1093 return self.qrepo(create=True)
1094 1094
1095 1095 def unapplied(self, repo, patch=None):
1096 1096 if patch and patch not in self.series:
1097 1097 raise util.Abort(_("patch %s is not in series file") % patch)
1098 1098 if not patch:
1099 1099 start = self.series_end()
1100 1100 else:
1101 1101 start = self.series.index(patch) + 1
1102 1102 unapplied = []
1103 1103 for i in xrange(start, len(self.series)):
1104 1104 pushable, reason = self.pushable(i)
1105 1105 if pushable:
1106 1106 unapplied.append((i, self.series[i]))
1107 1107 self.explain_pushable(i)
1108 1108 return unapplied
1109 1109
1110 1110 def qseries(self, repo, missing=None, start=0, length=0, status=None,
1111 1111 summary=False):
1112 1112 def displayname(patchname):
1113 1113 if summary:
1114 1114 msg = self.readheaders(patchname)[0]
1115 1115 msg = msg and ': ' + msg[0] or ': '
1116 1116 else:
1117 1117 msg = ''
1118 1118 return '%s%s' % (patchname, msg)
1119 1119
1120 1120 def pname(i):
1121 1121 if status == 'A':
1122 1122 return self.applied[i].name
1123 1123 else:
1124 1124 return self.series[i]
1125 1125
1126 1126 applied = dict.fromkeys([p.name for p in self.applied])
1127 1127 if not length:
1128 1128 length = len(self.series) - start
1129 1129 if not missing:
1130 1130 for i in xrange(start, start+length):
1131 1131 pfx = ''
1132 1132 patch = pname(i)
1133 1133 if self.ui.verbose:
1134 1134 if patch in applied:
1135 1135 stat = 'A'
1136 1136 elif self.pushable(i)[0]:
1137 1137 stat = 'U'
1138 1138 else:
1139 1139 stat = 'G'
1140 1140 pfx = '%d %s ' % (i, stat)
1141 1141 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1142 1142 else:
1143 1143 msng_list = []
1144 1144 for root, dirs, files in os.walk(self.path):
1145 1145 d = root[len(self.path) + 1:]
1146 1146 for f in files:
1147 1147 fl = os.path.join(d, f)
1148 1148 if (fl not in self.series and
1149 1149 fl not in (self.status_path, self.series_path)
1150 1150 and not fl.startswith('.')):
1151 1151 msng_list.append(fl)
1152 1152 msng_list.sort()
1153 1153 for x in msng_list:
1154 1154 pfx = self.ui.verbose and ('D ') or ''
1155 1155 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1156 1156
1157 1157 def issaveline(self, l):
1158 1158 if l.name == '.hg.patches.save.line':
1159 1159 return True
1160 1160
1161 1161 def qrepo(self, create=False):
1162 1162 if create or os.path.isdir(self.join(".hg")):
1163 1163 return hg.repository(self.ui, path=self.path, create=create)
1164 1164
1165 1165 def restore(self, repo, rev, delete=None, qupdate=None):
1166 1166 c = repo.changelog.read(rev)
1167 1167 desc = c[4].strip()
1168 1168 lines = desc.splitlines()
1169 1169 i = 0
1170 1170 datastart = None
1171 1171 series = []
1172 1172 applied = []
1173 1173 qpp = None
1174 1174 for i in xrange(0, len(lines)):
1175 1175 if lines[i] == 'Patch Data:':
1176 1176 datastart = i + 1
1177 1177 elif lines[i].startswith('Dirstate:'):
1178 1178 l = lines[i].rstrip()
1179 1179 l = l[10:].split(' ')
1180 1180 qpp = [ hg.bin(x) for x in l ]
1181 1181 elif datastart != None:
1182 1182 l = lines[i].rstrip()
1183 1183 se = statusentry(l)
1184 1184 file_ = se.name
1185 1185 if se.rev:
1186 1186 applied.append(se)
1187 1187 else:
1188 1188 series.append(file_)
1189 1189 if datastart == None:
1190 1190 self.ui.warn("No saved patch data found\n")
1191 1191 return 1
1192 1192 self.ui.warn("restoring status: %s\n" % lines[0])
1193 1193 self.full_series = series
1194 1194 self.applied = applied
1195 1195 self.parse_series()
1196 1196 self.series_dirty = 1
1197 1197 self.applied_dirty = 1
1198 1198 heads = repo.changelog.heads()
1199 1199 if delete:
1200 1200 if rev not in heads:
1201 1201 self.ui.warn("save entry has children, leaving it alone\n")
1202 1202 else:
1203 1203 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1204 1204 pp = repo.dirstate.parents()
1205 1205 if rev in pp:
1206 1206 update = True
1207 1207 else:
1208 1208 update = False
1209 1209 self.strip(repo, rev, update=update, backup='strip')
1210 1210 if qpp:
1211 1211 self.ui.warn("saved queue repository parents: %s %s\n" %
1212 1212 (hg.short(qpp[0]), hg.short(qpp[1])))
1213 1213 if qupdate:
1214 1214 print "queue directory updating"
1215 1215 r = self.qrepo()
1216 1216 if not r:
1217 1217 self.ui.warn("Unable to load queue repository\n")
1218 1218 return 1
1219 1219 hg.clean(r, qpp[0])
1220 1220
1221 1221 def save(self, repo, msg=None):
1222 1222 if len(self.applied) == 0:
1223 1223 self.ui.warn("save: no patches applied, exiting\n")
1224 1224 return 1
1225 1225 if self.issaveline(self.applied[-1]):
1226 1226 self.ui.warn("status is already saved\n")
1227 1227 return 1
1228 1228
1229 1229 ar = [ ':' + x for x in self.full_series ]
1230 1230 if not msg:
1231 1231 msg = "hg patches saved state"
1232 1232 else:
1233 1233 msg = "hg patches: " + msg.rstrip('\r\n')
1234 1234 r = self.qrepo()
1235 1235 if r:
1236 1236 pp = r.dirstate.parents()
1237 1237 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1238 1238 msg += "\n\nPatch Data:\n"
1239 1239 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1240 1240 "\n".join(ar) + '\n' or "")
1241 1241 n = repo.commit(None, text, user=None, force=1)
1242 1242 if not n:
1243 1243 self.ui.warn("repo commit failed\n")
1244 1244 return 1
1245 1245 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1246 1246 self.applied_dirty = 1
1247 1247
1248 1248 def full_series_end(self):
1249 1249 if len(self.applied) > 0:
1250 1250 p = self.applied[-1].name
1251 1251 end = self.find_series(p)
1252 1252 if end == None:
1253 1253 return len(self.full_series)
1254 1254 return end + 1
1255 1255 return 0
1256 1256
1257 1257 def series_end(self, all_patches=False):
1258 1258 end = 0
1259 1259 def next(start):
1260 1260 if all_patches:
1261 1261 return start
1262 1262 i = start
1263 1263 while i < len(self.series):
1264 1264 p, reason = self.pushable(i)
1265 1265 if p:
1266 1266 break
1267 1267 self.explain_pushable(i)
1268 1268 i += 1
1269 1269 return i
1270 1270 if len(self.applied) > 0:
1271 1271 p = self.applied[-1].name
1272 1272 try:
1273 1273 end = self.series.index(p)
1274 1274 except ValueError:
1275 1275 return 0
1276 1276 return next(end + 1)
1277 1277 return next(end)
1278 1278
1279 1279 def appliedname(self, index):
1280 1280 pname = self.applied[index].name
1281 1281 if not self.ui.verbose:
1282 1282 p = pname
1283 1283 else:
1284 1284 p = str(self.series.index(pname)) + " " + pname
1285 1285 return p
1286 1286
1287 1287 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1288 1288 force=None, git=False):
1289 1289 def checkseries(patchname):
1290 1290 if patchname in self.series:
1291 1291 raise util.Abort(_('patch %s is already in the series file')
1292 1292 % patchname)
1293 1293 def checkfile(patchname):
1294 1294 if not force and os.path.exists(self.join(patchname)):
1295 1295 raise util.Abort(_('patch "%s" already exists')
1296 1296 % patchname)
1297 1297
1298 1298 if rev:
1299 1299 if files:
1300 1300 raise util.Abort(_('option "-r" not valid when importing '
1301 1301 'files'))
1302 1302 rev = cmdutil.revrange(repo, rev)
1303 1303 rev.sort(lambda x, y: cmp(y, x))
1304 1304 if (len(files) > 1 or len(rev) > 1) and patchname:
1305 1305 raise util.Abort(_('option "-n" not valid when importing multiple '
1306 1306 'patches'))
1307 1307 i = 0
1308 1308 added = []
1309 1309 if rev:
1310 1310 # If mq patches are applied, we can only import revisions
1311 1311 # that form a linear path to qbase.
1312 1312 # Otherwise, they should form a linear path to a head.
1313 1313 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1314 1314 if len(heads) > 1:
1315 1315 raise util.Abort(_('revision %d is the root of more than one '
1316 1316 'branch') % rev[-1])
1317 1317 if self.applied:
1318 1318 base = revlog.hex(repo.changelog.node(rev[0]))
1319 1319 if base in [n.rev for n in self.applied]:
1320 1320 raise util.Abort(_('revision %d is already managed')
1321 1321 % rev[0])
1322 1322 if heads != [revlog.bin(self.applied[-1].rev)]:
1323 1323 raise util.Abort(_('revision %d is not the parent of '
1324 1324 'the queue') % rev[0])
1325 1325 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1326 1326 lastparent = repo.changelog.parentrevs(base)[0]
1327 1327 else:
1328 1328 if heads != [repo.changelog.node(rev[0])]:
1329 1329 raise util.Abort(_('revision %d has unmanaged children')
1330 1330 % rev[0])
1331 1331 lastparent = None
1332 1332
1333 1333 if git:
1334 1334 self.diffopts().git = True
1335 1335
1336 1336 for r in rev:
1337 1337 p1, p2 = repo.changelog.parentrevs(r)
1338 1338 n = repo.changelog.node(r)
1339 1339 if p2 != revlog.nullrev:
1340 1340 raise util.Abort(_('cannot import merge revision %d') % r)
1341 1341 if lastparent and lastparent != r:
1342 1342 raise util.Abort(_('revision %d is not the parent of %d')
1343 1343 % (r, lastparent))
1344 1344 lastparent = p1
1345 1345
1346 1346 if not patchname:
1347 1347 patchname = '%d.diff' % r
1348 1348 checkseries(patchname)
1349 1349 checkfile(patchname)
1350 1350 self.full_series.insert(0, patchname)
1351 1351
1352 1352 patchf = self.opener(patchname, "w")
1353 1353 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1354 1354 patchf.close()
1355 1355
1356 1356 se = statusentry(revlog.hex(n), patchname)
1357 1357 self.applied.insert(0, se)
1358 1358
1359 1359 added.append(patchname)
1360 1360 patchname = None
1361 1361 self.parse_series()
1362 1362 self.applied_dirty = 1
1363 1363
1364 1364 for filename in files:
1365 1365 if existing:
1366 1366 if filename == '-':
1367 1367 raise util.Abort(_('-e is incompatible with import from -'))
1368 1368 if not patchname:
1369 1369 patchname = filename
1370 1370 if not os.path.isfile(self.join(patchname)):
1371 1371 raise util.Abort(_("patch %s does not exist") % patchname)
1372 1372 else:
1373 1373 try:
1374 1374 if filename == '-':
1375 1375 if not patchname:
1376 1376 raise util.Abort(_('need --name to import a patch from -'))
1377 1377 text = sys.stdin.read()
1378 1378 else:
1379 1379 text = file(filename).read()
1380 1380 except IOError:
1381 1381 raise util.Abort(_("unable to read %s") % patchname)
1382 1382 if not patchname:
1383 1383 patchname = os.path.basename(filename)
1384 1384 checkfile(patchname)
1385 1385 patchf = self.opener(patchname, "w")
1386 1386 patchf.write(text)
1387 1387 checkseries(patchname)
1388 1388 index = self.full_series_end() + i
1389 1389 self.full_series[index:index] = [patchname]
1390 1390 self.parse_series()
1391 1391 self.ui.warn("adding %s to series file\n" % patchname)
1392 1392 i += 1
1393 1393 added.append(patchname)
1394 1394 patchname = None
1395 1395 self.series_dirty = 1
1396 1396 qrepo = self.qrepo()
1397 1397 if qrepo:
1398 1398 qrepo.add(added)
1399 1399
1400 1400 def delete(ui, repo, *patches, **opts):
1401 1401 """remove patches from queue
1402 1402
1403 1403 With --rev, mq will stop managing the named revisions. The
1404 1404 patches must be applied and at the base of the stack. This option
1405 1405 is useful when the patches have been applied upstream.
1406 1406
1407 1407 Otherwise, the patches must not be applied.
1408 1408
1409 1409 With --keep, the patch files are preserved in the patch directory."""
1410 1410 q = repo.mq
1411 1411 q.delete(repo, patches, opts)
1412 1412 q.save_dirty()
1413 1413 return 0
1414 1414
1415 1415 def applied(ui, repo, patch=None, **opts):
1416 1416 """print the patches already applied"""
1417 1417 q = repo.mq
1418 1418 if patch:
1419 1419 if patch not in q.series:
1420 1420 raise util.Abort(_("patch %s is not in series file") % patch)
1421 1421 end = q.series.index(patch) + 1
1422 1422 else:
1423 1423 end = len(q.applied)
1424 1424 if not end:
1425 1425 return
1426 1426
1427 1427 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1428 1428
1429 1429 def unapplied(ui, repo, patch=None, **opts):
1430 1430 """print the patches not yet applied"""
1431 1431 q = repo.mq
1432 1432 if patch:
1433 1433 if patch not in q.series:
1434 1434 raise util.Abort(_("patch %s is not in series file") % patch)
1435 1435 start = q.series.index(patch) + 1
1436 1436 else:
1437 1437 start = q.series_end()
1438 1438 q.qseries(repo, start=start, summary=opts.get('summary'))
1439 1439
1440 1440 def qimport(ui, repo, *filename, **opts):
1441 1441 """import a patch
1442 1442
1443 1443 The patch will have the same name as its source file unless you
1444 1444 give it a new one with --name.
1445 1445
1446 1446 You can register an existing patch inside the patch directory
1447 1447 with the --existing flag.
1448 1448
1449 1449 With --force, an existing patch of the same name will be overwritten.
1450 1450
1451 1451 An existing changeset may be placed under mq control with --rev
1452 1452 (e.g. qimport --rev tip -n patch will place tip under mq control).
1453 1453 With --git, patches imported with --rev will use the git diff
1454 1454 format.
1455 1455 """
1456 1456 q = repo.mq
1457 1457 q.qimport(repo, filename, patchname=opts['name'],
1458 1458 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1459 1459 git=opts['git'])
1460 1460 q.save_dirty()
1461 1461 return 0
1462 1462
1463 1463 def init(ui, repo, **opts):
1464 1464 """init a new queue repository
1465 1465
1466 1466 The queue repository is unversioned by default. If -c is
1467 1467 specified, qinit will create a separate nested repository
1468 1468 for patches. Use qcommit to commit changes to this queue
1469 1469 repository."""
1470 1470 q = repo.mq
1471 1471 r = q.init(repo, create=opts['create_repo'])
1472 1472 q.save_dirty()
1473 1473 if r:
1474 1474 fp = r.wopener('.hgignore', 'w')
1475 1475 print >> fp, 'syntax: glob'
1476 1476 print >> fp, 'status'
1477 1477 print >> fp, 'guards'
1478 1478 fp.close()
1479 1479 r.wopener('series', 'w').close()
1480 1480 r.add(['.hgignore', 'series'])
1481 1481 return 0
1482 1482
1483 1483 def clone(ui, source, dest=None, **opts):
1484 1484 '''clone main and patch repository at same time
1485 1485
1486 1486 If source is local, destination will have no patches applied. If
1487 1487 source is remote, this command can not check if patches are
1488 1488 applied in source, so cannot guarantee that patches are not
1489 1489 applied in destination. If you clone remote repository, be sure
1490 1490 before that it has no patches applied.
1491 1491
1492 1492 Source patch repository is looked for in <src>/.hg/patches by
1493 1493 default. Use -p <url> to change.
1494 1494 '''
1495 1495 commands.setremoteconfig(ui, opts)
1496 1496 if dest is None:
1497 1497 dest = hg.defaultdest(source)
1498 1498 sr = hg.repository(ui, ui.expandpath(source))
1499 1499 qbase, destrev = None, None
1500 1500 if sr.local():
1501 1501 reposetup(ui, sr)
1502 1502 if sr.mq.applied:
1503 1503 qbase = revlog.bin(sr.mq.applied[0].rev)
1504 1504 if not hg.islocal(dest):
1505 1505 destrev = sr.parents(qbase)[0]
1506 1506 ui.note(_('cloning main repo\n'))
1507 1507 sr, dr = hg.clone(ui, sr, dest,
1508 1508 pull=opts['pull'],
1509 1509 rev=destrev,
1510 1510 update=False,
1511 1511 stream=opts['uncompressed'])
1512 1512 ui.note(_('cloning patch repo\n'))
1513 1513 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1514 1514 dr.url() + '/.hg/patches',
1515 1515 pull=opts['pull'],
1516 1516 update=not opts['noupdate'],
1517 1517 stream=opts['uncompressed'])
1518 1518 if dr.local():
1519 1519 if qbase:
1520 1520 ui.note(_('stripping applied patches from destination repo\n'))
1521 1521 reposetup(ui, dr)
1522 1522 dr.mq.strip(dr, qbase, update=False, backup=None)
1523 1523 if not opts['noupdate']:
1524 1524 ui.note(_('updating destination repo\n'))
1525 1525 hg.update(dr, dr.changelog.tip())
1526 1526
1527 1527 def commit(ui, repo, *pats, **opts):
1528 1528 """commit changes in the queue repository"""
1529 1529 q = repo.mq
1530 1530 r = q.qrepo()
1531 1531 if not r: raise util.Abort('no queue repository')
1532 1532 commands.commit(r.ui, r, *pats, **opts)
1533 1533
1534 1534 def series(ui, repo, **opts):
1535 1535 """print the entire series file"""
1536 1536 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1537 1537 return 0
1538 1538
1539 1539 def top(ui, repo, **opts):
1540 1540 """print the name of the current patch"""
1541 1541 q = repo.mq
1542 1542 t = len(q.applied)
1543 1543 if t:
1544 1544 return q.qseries(repo, start=t-1, length=1, status='A',
1545 1545 summary=opts.get('summary'))
1546 1546 else:
1547 1547 ui.write("No patches applied\n")
1548 1548 return 1
1549 1549
1550 1550 def next(ui, repo, **opts):
1551 1551 """print the name of the next patch"""
1552 1552 q = repo.mq
1553 1553 end = q.series_end()
1554 1554 if end == len(q.series):
1555 1555 ui.write("All patches applied\n")
1556 1556 return 1
1557 1557 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1558 1558
1559 1559 def prev(ui, repo, **opts):
1560 1560 """print the name of the previous patch"""
1561 1561 q = repo.mq
1562 1562 l = len(q.applied)
1563 1563 if l == 1:
1564 1564 ui.write("Only one patch applied\n")
1565 1565 return 1
1566 1566 if not l:
1567 1567 ui.write("No patches applied\n")
1568 1568 return 1
1569 1569 return q.qseries(repo, start=l-2, length=1, status='A',
1570 1570 summary=opts.get('summary'))
1571 1571
1572 1572 def new(ui, repo, patch, **opts):
1573 1573 """create a new patch
1574 1574
1575 1575 qnew creates a new patch on top of the currently-applied patch
1576 1576 (if any). It will refuse to run if there are any outstanding
1577 1577 changes unless -f is specified, in which case the patch will
1578 1578 be initialised with them.
1579 1579
1580 1580 -e, -m or -l set the patch header as well as the commit message.
1581 1581 If none is specified, the patch header is empty and the
1582 1582 commit message is 'New patch: PATCH'"""
1583 1583 q = repo.mq
1584 1584 message = commands.logmessage(opts)
1585 1585 if opts['edit']:
1586 1586 message = ui.edit(message, ui.username())
1587 1587 q.new(repo, patch, msg=message, force=opts['force'])
1588 1588 q.save_dirty()
1589 1589 return 0
1590 1590
1591 1591 def refresh(ui, repo, *pats, **opts):
1592 1592 """update the current patch
1593 1593
1594 1594 If any file patterns are provided, the refreshed patch will contain only
1595 1595 the modifications that match those patterns; the remaining modifications
1596 1596 will remain in the working directory.
1597 1597 """
1598 1598 q = repo.mq
1599 1599 message = commands.logmessage(opts)
1600 1600 if opts['edit']:
1601 1601 if message:
1602 1602 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1603 1603 patch = q.applied[-1].name
1604 1604 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1605 1605 message = ui.edit('\n'.join(message), user or ui.username())
1606 1606 ret = q.refresh(repo, pats, msg=message, **opts)
1607 1607 q.save_dirty()
1608 1608 return ret
1609 1609
1610 1610 def diff(ui, repo, *pats, **opts):
1611 1611 """diff of the current patch"""
1612 1612 repo.mq.diff(repo, pats, opts)
1613 1613 return 0
1614 1614
1615 1615 def fold(ui, repo, *files, **opts):
1616 1616 """fold the named patches into the current patch
1617 1617
1618 1618 Patches must not yet be applied. Each patch will be successively
1619 1619 applied to the current patch in the order given. If all the
1620 1620 patches apply successfully, the current patch will be refreshed
1621 1621 with the new cumulative patch, and the folded patches will
1622 1622 be deleted. With -k/--keep, the folded patch files will not
1623 1623 be removed afterwards.
1624 1624
1625 1625 The header for each folded patch will be concatenated with
1626 1626 the current patch header, separated by a line of '* * *'."""
1627 1627
1628 1628 q = repo.mq
1629 1629
1630 1630 if not files:
1631 1631 raise util.Abort(_('qfold requires at least one patch name'))
1632 1632 if not q.check_toppatch(repo):
1633 1633 raise util.Abort(_('No patches applied'))
1634 1634
1635 1635 message = commands.logmessage(opts)
1636 1636 if opts['edit']:
1637 1637 if message:
1638 1638 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1639 1639
1640 1640 parent = q.lookup('qtip')
1641 1641 patches = []
1642 1642 messages = []
1643 1643 for f in files:
1644 1644 p = q.lookup(f)
1645 1645 if p in patches or p == parent:
1646 1646 ui.warn(_('Skipping already folded patch %s') % p)
1647 1647 if q.isapplied(p):
1648 1648 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1649 1649 patches.append(p)
1650 1650
1651 1651 for p in patches:
1652 1652 if not message:
1653 1653 messages.append(q.readheaders(p)[0])
1654 1654 pf = q.join(p)
1655 1655 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1656 1656 if not patchsuccess:
1657 1657 raise util.Abort(_('Error folding patch %s') % p)
1658 1658 patch.updatedir(ui, repo, files)
1659 1659
1660 1660 if not message:
1661 1661 message, comments, user = q.readheaders(parent)[0:3]
1662 1662 for msg in messages:
1663 1663 message.append('* * *')
1664 1664 message.extend(msg)
1665 1665 message = '\n'.join(message)
1666 1666
1667 1667 if opts['edit']:
1668 1668 message = ui.edit(message, user or ui.username())
1669 1669
1670 1670 q.refresh(repo, msg=message)
1671 1671 q.delete(repo, patches, opts)
1672 1672 q.save_dirty()
1673 1673
1674 1674 def guard(ui, repo, *args, **opts):
1675 1675 '''set or print guards for a patch
1676 1676
1677 1677 Guards control whether a patch can be pushed. A patch with no
1678 1678 guards is always pushed. A patch with a positive guard ("+foo") is
1679 1679 pushed only if the qselect command has activated it. A patch with
1680 1680 a negative guard ("-foo") is never pushed if the qselect command
1681 1681 has activated it.
1682 1682
1683 1683 With no arguments, print the currently active guards.
1684 1684 With arguments, set guards for the named patch.
1685 1685
1686 1686 To set a negative guard "-foo" on topmost patch ("--" is needed so
1687 1687 hg will not interpret "-foo" as an option):
1688 1688 hg qguard -- -foo
1689 1689
1690 1690 To set guards on another patch:
1691 1691 hg qguard other.patch +2.6.17 -stable
1692 1692 '''
1693 1693 def status(idx):
1694 1694 guards = q.series_guards[idx] or ['unguarded']
1695 1695 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1696 1696 q = repo.mq
1697 1697 patch = None
1698 1698 args = list(args)
1699 1699 if opts['list']:
1700 1700 if args or opts['none']:
1701 1701 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1702 1702 for i in xrange(len(q.series)):
1703 1703 status(i)
1704 1704 return
1705 1705 if not args or args[0][0:1] in '-+':
1706 1706 if not q.applied:
1707 1707 raise util.Abort(_('no patches applied'))
1708 1708 patch = q.applied[-1].name
1709 1709 if patch is None and args[0][0:1] not in '-+':
1710 1710 patch = args.pop(0)
1711 1711 if patch is None:
1712 1712 raise util.Abort(_('no patch to work with'))
1713 1713 if args or opts['none']:
1714 1714 q.set_guards(q.find_series(patch), args)
1715 1715 q.save_dirty()
1716 1716 else:
1717 1717 status(q.series.index(q.lookup(patch)))
1718 1718
1719 1719 def header(ui, repo, patch=None):
1720 1720 """Print the header of the topmost or specified patch"""
1721 1721 q = repo.mq
1722 1722
1723 1723 if patch:
1724 1724 patch = q.lookup(patch)
1725 1725 else:
1726 1726 if not q.applied:
1727 1727 ui.write('No patches applied\n')
1728 1728 return 1
1729 1729 patch = q.lookup('qtip')
1730 1730 message = repo.mq.readheaders(patch)[0]
1731 1731
1732 1732 ui.write('\n'.join(message) + '\n')
1733 1733
1734 1734 def lastsavename(path):
1735 1735 (directory, base) = os.path.split(path)
1736 1736 names = os.listdir(directory)
1737 1737 namere = re.compile("%s.([0-9]+)" % base)
1738 1738 maxindex = None
1739 1739 maxname = None
1740 1740 for f in names:
1741 1741 m = namere.match(f)
1742 1742 if m:
1743 1743 index = int(m.group(1))
1744 1744 if maxindex == None or index > maxindex:
1745 1745 maxindex = index
1746 1746 maxname = f
1747 1747 if maxname:
1748 1748 return (os.path.join(directory, maxname), maxindex)
1749 1749 return (None, None)
1750 1750
1751 1751 def savename(path):
1752 1752 (last, index) = lastsavename(path)
1753 1753 if last is None:
1754 1754 index = 0
1755 1755 newpath = path + ".%d" % (index + 1)
1756 1756 return newpath
1757 1757
1758 1758 def push(ui, repo, patch=None, **opts):
1759 1759 """push the next patch onto the stack"""
1760 1760 q = repo.mq
1761 1761 mergeq = None
1762 1762
1763 1763 if opts['all']:
1764 1764 if not q.series:
1765 1765 raise util.Abort(_('no patches in series'))
1766 1766 patch = q.series[-1]
1767 1767 if opts['merge']:
1768 1768 if opts['name']:
1769 1769 newpath = opts['name']
1770 1770 else:
1771 1771 newpath, i = lastsavename(q.path)
1772 1772 if not newpath:
1773 1773 ui.warn("no saved queues found, please use -n\n")
1774 1774 return 1
1775 1775 mergeq = queue(ui, repo.join(""), newpath)
1776 1776 ui.warn("merging with queue at: %s\n" % mergeq.path)
1777 1777 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1778 1778 mergeq=mergeq)
1779 1779 q.save_dirty()
1780 1780 return ret
1781 1781
1782 1782 def pop(ui, repo, patch=None, **opts):
1783 1783 """pop the current patch off the stack"""
1784 1784 localupdate = True
1785 1785 if opts['name']:
1786 1786 q = queue(ui, repo.join(""), repo.join(opts['name']))
1787 1787 ui.warn('using patch queue: %s\n' % q.path)
1788 1788 localupdate = False
1789 1789 else:
1790 1790 q = repo.mq
1791 1791 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1792 1792 q.save_dirty()
1793 1793 return 0
1794 1794
1795 1795 def rename(ui, repo, patch, name=None, **opts):
1796 1796 """rename a patch
1797 1797
1798 1798 With one argument, renames the current patch to PATCH1.
1799 1799 With two arguments, renames PATCH1 to PATCH2."""
1800 1800
1801 1801 q = repo.mq
1802 1802
1803 1803 if not name:
1804 1804 name = patch
1805 1805 patch = None
1806 1806
1807 1807 if patch:
1808 1808 patch = q.lookup(patch)
1809 1809 else:
1810 1810 if not q.applied:
1811 1811 ui.write(_('No patches applied\n'))
1812 1812 return
1813 1813 patch = q.lookup('qtip')
1814 1814 absdest = q.join(name)
1815 1815 if os.path.isdir(absdest):
1816 1816 name = os.path.join(name, os.path.basename(patch))
1817 1817 absdest = q.join(name)
1818 1818 if os.path.exists(absdest):
1819 1819 raise util.Abort(_('%s already exists') % absdest)
1820 1820
1821 1821 if name in q.series:
1822 1822 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1823 1823
1824 1824 if ui.verbose:
1825 1825 ui.write('Renaming %s to %s\n' % (patch, name))
1826 1826 i = q.find_series(patch)
1827 1827 guards = q.guard_re.findall(q.full_series[i])
1828 1828 q.full_series[i] = name + ''.join([' #' + g for g in guards])
1829 1829 q.parse_series()
1830 1830 q.series_dirty = 1
1831 1831
1832 1832 info = q.isapplied(patch)
1833 1833 if info:
1834 1834 q.applied[info[0]] = statusentry(info[1], name)
1835 1835 q.applied_dirty = 1
1836 1836
1837 1837 util.rename(q.join(patch), absdest)
1838 1838 r = q.qrepo()
1839 1839 if r:
1840 1840 wlock = r.wlock()
1841 1841 if r.dirstate.state(name) == 'r':
1842 1842 r.undelete([name], wlock)
1843 1843 r.copy(patch, name, wlock)
1844 1844 r.remove([patch], False, wlock)
1845 1845
1846 1846 q.save_dirty()
1847 1847
1848 1848 def restore(ui, repo, rev, **opts):
1849 1849 """restore the queue state saved by a rev"""
1850 1850 rev = repo.lookup(rev)
1851 1851 q = repo.mq
1852 1852 q.restore(repo, rev, delete=opts['delete'],
1853 1853 qupdate=opts['update'])
1854 1854 q.save_dirty()
1855 1855 return 0
1856 1856
1857 1857 def save(ui, repo, **opts):
1858 1858 """save current queue state"""
1859 1859 q = repo.mq
1860 1860 message = commands.logmessage(opts)
1861 1861 ret = q.save(repo, msg=message)
1862 1862 if ret:
1863 1863 return ret
1864 1864 q.save_dirty()
1865 1865 if opts['copy']:
1866 1866 path = q.path
1867 1867 if opts['name']:
1868 1868 newpath = os.path.join(q.basepath, opts['name'])
1869 1869 if os.path.exists(newpath):
1870 1870 if not os.path.isdir(newpath):
1871 1871 raise util.Abort(_('destination %s exists and is not '
1872 1872 'a directory') % newpath)
1873 1873 if not opts['force']:
1874 1874 raise util.Abort(_('destination %s exists, '
1875 1875 'use -f to force') % newpath)
1876 1876 else:
1877 1877 newpath = savename(path)
1878 1878 ui.warn("copy %s to %s\n" % (path, newpath))
1879 1879 util.copyfiles(path, newpath)
1880 1880 if opts['empty']:
1881 1881 try:
1882 1882 os.unlink(q.join(q.status_path))
1883 1883 except:
1884 1884 pass
1885 1885 return 0
1886 1886
1887 1887 def strip(ui, repo, rev, **opts):
1888 1888 """strip a revision and all later revs on the same branch"""
1889 1889 rev = repo.lookup(rev)
1890 1890 backup = 'all'
1891 1891 if opts['backup']:
1892 1892 backup = 'strip'
1893 1893 elif opts['nobackup']:
1894 1894 backup = 'none'
1895 1895 update = repo.dirstate.parents()[0] != revlog.nullid
1896 1896 repo.mq.strip(repo, rev, backup=backup, update=update)
1897 1897 return 0
1898 1898
1899 1899 def select(ui, repo, *args, **opts):
1900 1900 '''set or print guarded patches to push
1901 1901
1902 1902 Use the qguard command to set or print guards on patch, then use
1903 1903 qselect to tell mq which guards to use. A patch will be pushed if it
1904 1904 has no guards or any positive guards match the currently selected guard,
1905 1905 but will not be pushed if any negative guards match the current guard.
1906 1906 For example:
1907 1907
1908 1908 qguard foo.patch -stable (negative guard)
1909 1909 qguard bar.patch +stable (positive guard)
1910 1910 qselect stable
1911 1911
1912 1912 This activates the "stable" guard. mq will skip foo.patch (because
1913 1913 it has a negative match) but push bar.patch (because it
1914 1914 has a positive match).
1915 1915
1916 1916 With no arguments, prints the currently active guards.
1917 1917 With one argument, sets the active guard.
1918 1918
1919 1919 Use -n/--none to deactivate guards (no other arguments needed).
1920 1920 When no guards are active, patches with positive guards are skipped
1921 1921 and patches with negative guards are pushed.
1922 1922
1923 1923 qselect can change the guards on applied patches. It does not pop
1924 1924 guarded patches by default. Use --pop to pop back to the last applied
1925 1925 patch that is not guarded. Use --reapply (which implies --pop) to push
1926 1926 back to the current patch afterwards, but skip guarded patches.
1927 1927
1928 1928 Use -s/--series to print a list of all guards in the series file (no
1929 1929 other arguments needed). Use -v for more information.'''
1930 1930
1931 1931 q = repo.mq
1932 1932 guards = q.active()
1933 1933 if args or opts['none']:
1934 1934 old_unapplied = q.unapplied(repo)
1935 1935 old_guarded = [i for i in xrange(len(q.applied)) if
1936 1936 not q.pushable(i)[0]]
1937 1937 q.set_active(args)
1938 1938 q.save_dirty()
1939 1939 if not args:
1940 1940 ui.status(_('guards deactivated\n'))
1941 1941 if not opts['pop'] and not opts['reapply']:
1942 1942 unapplied = q.unapplied(repo)
1943 1943 guarded = [i for i in xrange(len(q.applied))
1944 1944 if not q.pushable(i)[0]]
1945 1945 if len(unapplied) != len(old_unapplied):
1946 1946 ui.status(_('number of unguarded, unapplied patches has '
1947 1947 'changed from %d to %d\n') %
1948 1948 (len(old_unapplied), len(unapplied)))
1949 1949 if len(guarded) != len(old_guarded):
1950 1950 ui.status(_('number of guarded, applied patches has changed '
1951 1951 'from %d to %d\n') %
1952 1952 (len(old_guarded), len(guarded)))
1953 1953 elif opts['series']:
1954 1954 guards = {}
1955 1955 noguards = 0
1956 1956 for gs in q.series_guards:
1957 1957 if not gs:
1958 1958 noguards += 1
1959 1959 for g in gs:
1960 1960 guards.setdefault(g, 0)
1961 1961 guards[g] += 1
1962 1962 if ui.verbose:
1963 1963 guards['NONE'] = noguards
1964 1964 guards = guards.items()
1965 1965 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
1966 1966 if guards:
1967 1967 ui.note(_('guards in series file:\n'))
1968 1968 for guard, count in guards:
1969 1969 ui.note('%2d ' % count)
1970 1970 ui.write(guard, '\n')
1971 1971 else:
1972 1972 ui.note(_('no guards in series file\n'))
1973 1973 else:
1974 1974 if guards:
1975 1975 ui.note(_('active guards:\n'))
1976 1976 for g in guards:
1977 1977 ui.write(g, '\n')
1978 1978 else:
1979 1979 ui.write(_('no active guards\n'))
1980 1980 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
1981 1981 popped = False
1982 1982 if opts['pop'] or opts['reapply']:
1983 1983 for i in xrange(len(q.applied)):
1984 1984 pushable, reason = q.pushable(i)
1985 1985 if not pushable:
1986 1986 ui.status(_('popping guarded patches\n'))
1987 1987 popped = True
1988 1988 if i == 0:
1989 1989 q.pop(repo, all=True)
1990 1990 else:
1991 1991 q.pop(repo, i-1)
1992 1992 break
1993 1993 if popped:
1994 1994 try:
1995 1995 if reapply:
1996 1996 ui.status(_('reapplying unguarded patches\n'))
1997 1997 q.push(repo, reapply)
1998 1998 finally:
1999 1999 q.save_dirty()
2000 2000
2001 2001 def reposetup(ui, repo):
2002 2002 class mqrepo(repo.__class__):
2003 2003 def abort_if_wdir_patched(self, errmsg, force=False):
2004 2004 if self.mq.applied and not force:
2005 2005 parent = revlog.hex(self.dirstate.parents()[0])
2006 2006 if parent in [s.rev for s in self.mq.applied]:
2007 2007 raise util.Abort(errmsg)
2008 2008
2009 2009 def commit(self, *args, **opts):
2010 2010 if len(args) >= 6:
2011 2011 force = args[5]
2012 2012 else:
2013 2013 force = opts.get('force')
2014 2014 self.abort_if_wdir_patched(
2015 2015 _('cannot commit over an applied mq patch'),
2016 2016 force)
2017 2017
2018 2018 return super(mqrepo, self).commit(*args, **opts)
2019 2019
2020 2020 def push(self, remote, force=False, revs=None):
2021 2021 if self.mq.applied and not force:
2022 2022 raise util.Abort(_('source has mq patches applied'))
2023 2023 return super(mqrepo, self).push(remote, force, revs)
2024 2024
2025 2025 def tags(self):
2026 2026 if self.tagscache:
2027 2027 return self.tagscache
2028 2028
2029 2029 tagscache = super(mqrepo, self).tags()
2030 2030
2031 2031 q = self.mq
2032 2032 if not q.applied:
2033 2033 return tagscache
2034 2034
2035 2035 mqtags = [(patch.rev, patch.name) for patch in q.applied]
2036 2036 mqtags.append((mqtags[-1][0], 'qtip'))
2037 2037 mqtags.append((mqtags[0][0], 'qbase'))
2038 2038 for patch in mqtags:
2039 2039 if patch[1] in tagscache:
2040 2040 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2041 2041 else:
2042 2042 tagscache[patch[1]] = revlog.bin(patch[0])
2043 2043
2044 2044 return tagscache
2045 2045
2046 2046 def _branchtags(self):
2047 2047 q = self.mq
2048 2048 if not q.applied:
2049 2049 return super(mqrepo, self)._branchtags()
2050 2050
2051 2051 self.branchcache = {} # avoid recursion in changectx
2052 2052 cl = self.changelog
2053 2053 partial, last, lrev = self._readbranchcache()
2054 2054
2055 2055 qbase = cl.rev(revlog.bin(q.applied[0].rev))
2056 2056 start = lrev + 1
2057 2057 if start < qbase:
2058 2058 # update the cache (excluding the patches) and save it
2059 2059 self._updatebranchcache(partial, lrev+1, qbase)
2060 2060 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2061 2061 start = qbase
2062 2062 # if start = qbase, the cache is as updated as it should be.
2063 2063 # if start > qbase, the cache includes (part of) the patches.
2064 2064 # we might as well use it, but we won't save it.
2065 2065
2066 2066 # update the cache up to the tip
2067 2067 self._updatebranchcache(partial, start, cl.count())
2068 2068
2069 2069 return partial
2070 2070
2071 2071 if repo.local():
2072 2072 repo.__class__ = mqrepo
2073 2073 repo.mq = queue(ui, repo.join(""))
2074 2074
2075 2075 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2076 2076
2077 2077 cmdtable = {
2078 2078 "qapplied": (applied, [] + seriesopts, 'hg qapplied [-s] [PATCH]'),
2079 2079 "qclone": (clone,
2080 2080 [('', 'pull', None, _('use pull protocol to copy metadata')),
2081 2081 ('U', 'noupdate', None, _('do not update the new working directories')),
2082 2082 ('', 'uncompressed', None,
2083 2083 _('use uncompressed transfer (fast over LAN)')),
2084 2084 ('e', 'ssh', '', _('specify ssh command to use')),
2085 2085 ('p', 'patches', '', _('location of source patch repo')),
2086 2086 ('', 'remotecmd', '',
2087 2087 _('specify hg command to run on the remote side'))],
2088 2088 'hg qclone [OPTION]... SOURCE [DEST]'),
2089 2089 "qcommit|qci":
2090 2090 (commit,
2091 2091 commands.table["^commit|ci"][1],
2092 2092 'hg qcommit [OPTION]... [FILE]...'),
2093 2093 "^qdiff": (diff,
2094 2094 [('g', 'git', None, _('use git extended diff format')),
2095 2095 ('I', 'include', [], _('include names matching the given patterns')),
2096 2096 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2097 2097 'hg qdiff [-I] [-X] [FILE]...'),
2098 2098 "qdelete|qremove|qrm":
2099 2099 (delete,
2100 2100 [('k', 'keep', None, _('keep patch file')),
2101 2101 ('r', 'rev', [], _('stop managing a revision'))],
2102 2102 'hg qdelete [-k] [-r REV]... PATCH...'),
2103 2103 'qfold':
2104 2104 (fold,
2105 2105 [('e', 'edit', None, _('edit patch header')),
2106 2106 ('k', 'keep', None, _('keep folded patch files'))
2107 2107 ] + commands.commitopts,
2108 2108 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
2109 2109 'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
2110 2110 ('n', 'none', None, _('drop all guards'))],
2111 2111 'hg qguard [PATCH] [+GUARD...] [-GUARD...]'),
2112 2112 'qheader': (header, [],
2113 2113 _('hg qheader [PATCH]')),
2114 2114 "^qimport":
2115 2115 (qimport,
2116 2116 [('e', 'existing', None, 'import file in patch dir'),
2117 2117 ('n', 'name', '', 'patch file name'),
2118 2118 ('f', 'force', None, 'overwrite existing files'),
2119 2119 ('r', 'rev', [], 'place existing revisions under mq control'),
2120 2120 ('g', 'git', None, _('use git extended diff format'))],
2121 2121 'hg qimport [-e] [-n NAME] [-f] [-g] [-r REV]... FILE...'),
2122 2122 "^qinit":
2123 2123 (init,
2124 2124 [('c', 'create-repo', None, 'create queue repository')],
2125 2125 'hg qinit [-c]'),
2126 2126 "qnew":
2127 2127 (new,
2128 2128 [('e', 'edit', None, _('edit commit message')),
2129 2129 ('f', 'force', None, _('import uncommitted changes into patch'))
2130 2130 ] + commands.commitopts,
2131 2131 'hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH'),
2132 2132 "qnext": (next, [] + seriesopts, 'hg qnext [-s]'),
2133 2133 "qprev": (prev, [] + seriesopts, 'hg qprev [-s]'),
2134 2134 "^qpop":
2135 2135 (pop,
2136 2136 [('a', 'all', None, 'pop all patches'),
2137 2137 ('n', 'name', '', 'queue name to pop'),
2138 2138 ('f', 'force', None, 'forget any local changes')],
2139 2139 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
2140 2140 "^qpush":
2141 2141 (push,
2142 2142 [('f', 'force', None, 'apply if the patch has rejects'),
2143 2143 ('l', 'list', None, 'list patch name in commit text'),
2144 2144 ('a', 'all', None, 'apply all patches'),
2145 2145 ('m', 'merge', None, 'merge from another queue'),
2146 2146 ('n', 'name', '', 'merge queue name')],
2147 2147 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
2148 2148 "^qrefresh":
2149 2149 (refresh,
2150 2150 [('e', 'edit', None, _('edit commit message')),
2151 2151 ('g', 'git', None, _('use git extended diff format')),
2152 2152 ('s', 'short', None, 'refresh only files already in the patch'),
2153 2153 ('I', 'include', [], _('include names matching the given patterns')),
2154 2154 ('X', 'exclude', [], _('exclude names matching the given patterns'))
2155 2155 ] + commands.commitopts,
2156 2156 'hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] FILES...'),
2157 2157 'qrename|qmv':
2158 2158 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
2159 2159 "qrestore":
2160 2160 (restore,
2161 2161 [('d', 'delete', None, 'delete save entry'),
2162 2162 ('u', 'update', None, 'update queue working dir')],
2163 2163 'hg qrestore [-d] [-u] REV'),
2164 2164 "qsave":
2165 2165 (save,
2166 2166 [('c', 'copy', None, 'copy patch directory'),
2167 2167 ('n', 'name', '', 'copy directory name'),
2168 2168 ('e', 'empty', None, 'clear queue status file'),
2169 2169 ('f', 'force', None, 'force copy')] + commands.commitopts,
2170 2170 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
2171 2171 "qselect": (select,
2172 2172 [('n', 'none', None, _('disable all guards')),
2173 2173 ('s', 'series', None, _('list all guards in series file')),
2174 2174 ('', 'pop', None,
2175 2175 _('pop to before first guarded applied patch')),
2176 2176 ('', 'reapply', None, _('pop, then reapply patches'))],
2177 2177 'hg qselect [OPTION...] [GUARD...]'),
2178 2178 "qseries":
2179 2179 (series,
2180 2180 [('m', 'missing', None, 'print patches not in series')] + seriesopts,
2181 2181 'hg qseries [-ms]'),
2182 2182 "^strip":
2183 2183 (strip,
2184 2184 [('f', 'force', None, 'force multi-head removal'),
2185 2185 ('b', 'backup', None, 'bundle unrelated changesets'),
2186 2186 ('n', 'nobackup', None, 'no backups')],
2187 2187 'hg strip [-f] [-b] [-n] REV'),
2188 2188 "qtop": (top, [] + seriesopts, 'hg qtop [-s]'),
2189 2189 "qunapplied": (unapplied, [] + seriesopts, 'hg qunapplied [-s] [PATCH]'),
2190 2190 }
@@ -1,309 +1,309 b''
1 1 # Command for sending a collection of Mercurial changesets as a series
2 2 # of patch emails.
3 3 #
4 4 # The series is started off with a "[PATCH 0 of N]" introduction,
5 5 # which describes the series as a whole.
6 6 #
7 7 # Each patch email has a Subject line of "[PATCH M of N] ...", using
8 8 # the first line of the changeset description as the subject text.
9 9 # The message contains two or three body parts:
10 10 #
11 11 # The remainder of the changeset description.
12 12 #
13 13 # [Optional] If the diffstat program is installed, the result of
14 14 # running diffstat on the patch.
15 15 #
16 16 # The patch itself, as generated by "hg export".
17 17 #
18 18 # Each message refers to all of its predecessors using the In-Reply-To
19 19 # and References headers, so they will show up as a sequence in
20 20 # threaded mail and news readers, and in mail archives.
21 21 #
22 22 # For each changeset, you will be prompted with a diffstat summary and
23 23 # the changeset summary, so you can be sure you are sending the right
24 24 # changes.
25 25 #
26 26 # To enable this extension:
27 27 #
28 28 # [extensions]
29 29 # hgext.patchbomb =
30 30 #
31 31 # To configure other defaults, add a section like this to your hgrc
32 32 # file:
33 33 #
34 34 # [email]
35 35 # from = My Name <my@email>
36 36 # to = recipient1, recipient2, ...
37 37 # cc = cc1, cc2, ...
38 38 # bcc = bcc1, bcc2, ...
39 39 #
40 40 # Then you can use the "hg email" command to mail a series of changesets
41 41 # as a patchbomb.
42 42 #
43 43 # To avoid sending patches prematurely, it is a good idea to first run
44 44 # the "email" command with the "-n" option (test only). You will be
45 45 # prompted for an email recipient address, a subject an an introductory
46 46 # message describing the patches of your patchbomb. Then when all is
47 47 # done, your pager will be fired up once for each patchbomb message, so
48 48 # you can verify everything is alright.
49 49 #
50 50 # The "-m" (mbox) option is also very useful. Instead of previewing
51 51 # each patchbomb message in a pager or sending the messages directly,
52 52 # it will create a UNIX mailbox file with the patch emails. This
53 53 # mailbox file can be previewed with any mail user agent which supports
54 54 # UNIX mbox files, i.e. with mutt:
55 55 #
56 56 # % mutt -R -f mbox
57 57 #
58 58 # When you are previewing the patchbomb messages, you can use `formail'
59 59 # (a utility that is commonly installed as part of the procmail package),
60 60 # to send each message out:
61 61 #
62 62 # % formail -s sendmail -bm -t < mbox
63 63 #
64 64 # That should be all. Now your patchbomb is on its way out.
65 65
66 import os, errno, popen2, socket, sys, tempfile, time
66 import os, errno, socket, time
67 67 import email.MIMEMultipart, email.MIMEText, email.Utils
68 68 from mercurial import cmdutil, commands, hg, mail, ui, patch
69 69 from mercurial.i18n import _
70 70 from mercurial.node import *
71 71
72 72 try:
73 73 # readline gives raw_input editing capabilities, but is not
74 74 # present on windows
75 75 import readline
76 76 except ImportError: pass
77 77
78 78 def patchbomb(ui, repo, *revs, **opts):
79 79 '''send changesets as a series of patch emails
80 80
81 81 The series starts with a "[PATCH 0 of N]" introduction, which
82 82 describes the series as a whole.
83 83
84 84 Each patch email has a Subject line of "[PATCH M of N] ...", using
85 85 the first line of the changeset description as the subject text.
86 86 The message contains two or three body parts. First, the rest of
87 87 the changeset description. Next, (optionally) if the diffstat
88 88 program is installed, the result of running diffstat on the patch.
89 89 Finally, the patch itself, as generated by "hg export".'''
90 90 def prompt(prompt, default = None, rest = ': ', empty_ok = False):
91 91 if default: prompt += ' [%s]' % default
92 92 prompt += rest
93 93 while True:
94 94 r = raw_input(prompt)
95 95 if r: return r
96 96 if default is not None: return default
97 97 if empty_ok: return r
98 98 ui.warn(_('Please enter a valid value.\n'))
99 99
100 100 def confirm(s):
101 101 if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'):
102 102 raise ValueError
103 103
104 104 def cdiffstat(summary, patchlines):
105 105 s = patch.diffstat(patchlines)
106 106 if s:
107 107 if summary:
108 108 ui.write(summary, '\n')
109 109 ui.write(s, '\n')
110 110 confirm(_('Does the diffstat above look okay'))
111 111 return s
112 112
113 113 def makepatch(patch, idx, total):
114 114 desc = []
115 115 node = None
116 116 body = ''
117 117 for line in patch:
118 118 if line.startswith('#'):
119 119 if line.startswith('# Node ID'): node = line.split()[-1]
120 120 continue
121 121 if (line.startswith('diff -r')
122 122 or line.startswith('diff --git')):
123 123 break
124 124 desc.append(line)
125 125 if not node: raise ValueError
126 126
127 127 #body = ('\n'.join(desc[1:]).strip() or
128 128 # 'Patch subject is complete summary.')
129 129 #body += '\n\n\n'
130 130
131 131 if opts['plain']:
132 132 while patch and patch[0].startswith('# '): patch.pop(0)
133 133 if patch: patch.pop(0)
134 134 while patch and not patch[0].strip(): patch.pop(0)
135 135 if opts['diffstat']:
136 136 body += cdiffstat('\n'.join(desc), patch) + '\n\n'
137 137 if opts['attach']:
138 138 msg = email.MIMEMultipart.MIMEMultipart()
139 139 if body: msg.attach(email.MIMEText.MIMEText(body, 'plain'))
140 140 p = email.MIMEText.MIMEText('\n'.join(patch), 'x-patch')
141 141 binnode = bin(node)
142 142 # if node is mq patch, it will have patch file name as tag
143 143 patchname = [t for t in repo.nodetags(binnode)
144 144 if t.endswith('.patch') or t.endswith('.diff')]
145 145 if patchname:
146 146 patchname = patchname[0]
147 147 elif total > 1:
148 148 patchname = cmdutil.make_filename(repo, '%b-%n.patch',
149 149 binnode, idx, total)
150 150 else:
151 151 patchname = cmdutil.make_filename(repo, '%b.patch', binnode)
152 152 p['Content-Disposition'] = 'inline; filename=' + patchname
153 153 msg.attach(p)
154 154 else:
155 155 body += '\n'.join(patch)
156 156 msg = email.MIMEText.MIMEText(body)
157 157 if total == 1:
158 158 subj = '[PATCH] ' + desc[0].strip()
159 159 else:
160 160 tlen = len(str(total))
161 161 subj = '[PATCH %0*d of %d] %s' % (tlen, idx, total, desc[0].strip())
162 162 if subj.endswith('.'): subj = subj[:-1]
163 163 msg['Subject'] = subj
164 164 msg['X-Mercurial-Node'] = node
165 165 return msg
166 166
167 167 start_time = int(time.time())
168 168
169 169 def genmsgid(id):
170 170 return '<%s.%s@%s>' % (id[:20], start_time, socket.getfqdn())
171 171
172 172 patches = []
173 173
174 174 class exportee:
175 175 def __init__(self, container):
176 176 self.lines = []
177 177 self.container = container
178 178 self.name = 'email'
179 179
180 180 def write(self, data):
181 181 self.lines.append(data)
182 182
183 183 def close(self):
184 184 self.container.append(''.join(self.lines).split('\n'))
185 185 self.lines = []
186 186
187 187 commands.export(ui, repo, *revs, **{'output': exportee(patches),
188 188 'switch_parent': False,
189 189 'text': None,
190 190 'git': opts.get('git')})
191 191
192 192 jumbo = []
193 193 msgs = []
194 194
195 195 ui.write(_('This patch series consists of %d patches.\n\n') % len(patches))
196 196
197 197 for p, i in zip(patches, xrange(len(patches))):
198 198 jumbo.extend(p)
199 199 msgs.append(makepatch(p, i + 1, len(patches)))
200 200
201 201 sender = (opts['from'] or ui.config('email', 'from') or
202 202 ui.config('patchbomb', 'from') or
203 203 prompt('From', ui.username()))
204 204
205 205 def getaddrs(opt, prpt, default = None):
206 206 addrs = opts[opt] or (ui.config('email', opt) or
207 207 ui.config('patchbomb', opt) or
208 208 prompt(prpt, default = default)).split(',')
209 209 return [a.strip() for a in addrs if a.strip()]
210 210 to = getaddrs('to', 'To')
211 211 cc = getaddrs('cc', 'Cc', '')
212 212
213 213 bcc = opts['bcc'] or (ui.config('email', 'bcc') or
214 214 ui.config('patchbomb', 'bcc') or '').split(',')
215 215 bcc = [a.strip() for a in bcc if a.strip()]
216 216
217 217 if len(patches) > 1:
218 218 tlen = len(str(len(patches)))
219 219
220 220 subj = '[PATCH %0*d of %d] %s' % (
221 221 tlen, 0,
222 222 len(patches),
223 223 opts['subject'] or
224 224 prompt('Subject:', rest = ' [PATCH %0*d of %d] ' % (tlen, 0,
225 225 len(patches))))
226 226
227 227 body = ''
228 228 if opts['diffstat']:
229 229 d = cdiffstat(_('Final summary:\n'), jumbo)
230 230 if d: body = '\n' + d
231 231
232 232 ui.write(_('\nWrite the introductory message for the patch series.\n\n'))
233 233 body = ui.edit(body, sender)
234 234
235 235 msg = email.MIMEText.MIMEText(body)
236 236 msg['Subject'] = subj
237 237
238 238 msgs.insert(0, msg)
239 239
240 240 ui.write('\n')
241 241
242 242 if not opts['test'] and not opts['mbox']:
243 243 mailer = mail.connect(ui)
244 244 parent = None
245 245
246 246 # Calculate UTC offset
247 247 if time.daylight: offset = time.altzone
248 248 else: offset = time.timezone
249 249 if offset <= 0: sign, offset = '+', -offset
250 250 else: sign = '-'
251 251 offset = '%s%02d%02d' % (sign, offset / 3600, (offset % 3600) / 60)
252 252
253 253 sender_addr = email.Utils.parseaddr(sender)[1]
254 254 for m in msgs:
255 255 try:
256 256 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
257 257 except TypeError:
258 258 m['Message-Id'] = genmsgid('patchbomb')
259 259 if parent:
260 260 m['In-Reply-To'] = parent
261 261 else:
262 262 parent = m['Message-Id']
263 263 m['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime(start_time)) + ' ' + offset
264 264
265 265 start_time += 1
266 266 m['From'] = sender
267 267 m['To'] = ', '.join(to)
268 268 if cc: m['Cc'] = ', '.join(cc)
269 269 if bcc: m['Bcc'] = ', '.join(bcc)
270 270 if opts['test']:
271 271 ui.status('Displaying ', m['Subject'], ' ...\n')
272 272 fp = os.popen(os.getenv('PAGER', 'more'), 'w')
273 273 try:
274 274 fp.write(m.as_string(0))
275 275 fp.write('\n')
276 276 except IOError, inst:
277 277 if inst.errno != errno.EPIPE:
278 278 raise
279 279 fp.close()
280 280 elif opts['mbox']:
281 281 ui.status('Writing ', m['Subject'], ' ...\n')
282 282 fp = open(opts['mbox'], m.has_key('In-Reply-To') and 'ab+' or 'wb+')
283 283 date = time.asctime(time.localtime(start_time))
284 284 fp.write('From %s %s\n' % (sender_addr, date))
285 285 fp.write(m.as_string(0))
286 286 fp.write('\n\n')
287 287 fp.close()
288 288 else:
289 289 ui.status('Sending ', m['Subject'], ' ...\n')
290 290 # Exim does not remove the Bcc field
291 291 del m['Bcc']
292 292 mailer.sendmail(sender, to + bcc + cc, m.as_string(0))
293 293
294 294 cmdtable = {
295 295 'email':
296 296 (patchbomb,
297 297 [('a', 'attach', None, 'send patches as inline attachments'),
298 298 ('', 'bcc', [], 'email addresses of blind copy recipients'),
299 299 ('c', 'cc', [], 'email addresses of copy recipients'),
300 300 ('d', 'diffstat', None, 'add diffstat output to messages'),
301 301 ('g', 'git', None, _('use git extended diff format')),
302 302 ('f', 'from', '', 'email address of sender'),
303 303 ('', 'plain', None, 'omit hg patch header'),
304 304 ('n', 'test', None, 'print messages that would be sent'),
305 305 ('m', 'mbox', '', 'write messages to mbox file instead of sending them'),
306 306 ('s', 'subject', '', 'subject of introductory message'),
307 307 ('t', 'to', [], 'email addresses of recipients')],
308 308 "hg email [OPTION]... [REV]...")
309 309 }
@@ -1,89 +1,89 b''
1 1 # hgweb/request.py - An http request from either CGI or the standalone server.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 import socket, sys, cgi, os, errno
9 import socket, cgi, errno
10 10 from mercurial.i18n import gettext as _
11 11
12 12 class wsgiapplication(object):
13 13 def __init__(self, destmaker):
14 14 self.destmaker = destmaker
15 15
16 16 def __call__(self, wsgienv, start_response):
17 17 return _wsgirequest(self.destmaker(), wsgienv, start_response)
18 18
19 19 class _wsgioutputfile(object):
20 20 def __init__(self, request):
21 21 self.request = request
22 22
23 23 def write(self, data):
24 24 self.request.write(data)
25 25 def writelines(self, lines):
26 26 for line in lines:
27 27 self.write(line)
28 28 def flush(self):
29 29 return None
30 30 def close(self):
31 31 return None
32 32
33 33 class _wsgirequest(object):
34 34 def __init__(self, destination, wsgienv, start_response):
35 35 version = wsgienv['wsgi.version']
36 36 if (version < (1, 0)) or (version >= (2, 0)):
37 37 raise RuntimeError("Unknown and unsupported WSGI version %d.%d" \
38 38 % version)
39 39 self.inp = wsgienv['wsgi.input']
40 40 self.out = _wsgioutputfile(self)
41 41 self.server_write = None
42 42 self.err = wsgienv['wsgi.errors']
43 43 self.threaded = wsgienv['wsgi.multithread']
44 44 self.multiprocess = wsgienv['wsgi.multiprocess']
45 45 self.run_once = wsgienv['wsgi.run_once']
46 46 self.env = wsgienv
47 47 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
48 48 self.start_response = start_response
49 49 self.headers = []
50 50 destination.run_wsgi(self)
51 51
52 52 def __iter__(self):
53 53 return iter([])
54 54
55 55 def read(self, count=-1):
56 56 return self.inp.read(count)
57 57
58 58 def write(self, *things):
59 59 for thing in things:
60 60 if hasattr(thing, "__iter__"):
61 61 for part in thing:
62 62 self.write(part)
63 63 else:
64 64 thing = str(thing)
65 65 if self.server_write is None:
66 66 if not self.headers:
67 67 raise RuntimeError("request.write called before headers sent (%s)." % thing)
68 68 self.server_write = self.start_response('200 Script output follows',
69 69 self.headers)
70 70 self.start_response = None
71 71 self.headers = None
72 72 try:
73 73 self.server_write(thing)
74 74 except socket.error, inst:
75 75 if inst[0] != errno.ECONNRESET:
76 76 raise
77 77
78 78 def header(self, headers=[('Content-type','text/html')]):
79 79 self.headers.extend(headers)
80 80
81 81 def httphdr(self, type, filename=None, length=0, headers={}):
82 82 headers = headers.items()
83 83 headers.append(('Content-type', type))
84 84 if filename:
85 85 headers.append(('Content-disposition', 'attachment; filename=%s' %
86 86 filename))
87 87 if length:
88 88 headers.append(('Content-length', str(length)))
89 89 self.header(headers)
@@ -1,67 +1,67 b''
1 1 # mail.py - mail sending bits for mercurial
2 2 #
3 3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from i18n import _
9 import os, re, smtplib, templater, util
9 import os, smtplib, templater, util
10 10
11 11 def _smtp(ui):
12 12 '''send mail using smtp.'''
13 13
14 14 local_hostname = ui.config('smtp', 'local_hostname')
15 15 s = smtplib.SMTP(local_hostname=local_hostname)
16 16 mailhost = ui.config('smtp', 'host')
17 17 if not mailhost:
18 18 raise util.Abort(_('no [smtp]host in hgrc - cannot send mail'))
19 19 mailport = int(ui.config('smtp', 'port', 25))
20 20 ui.note(_('sending mail: smtp host %s, port %s\n') %
21 21 (mailhost, mailport))
22 22 s.connect(host=mailhost, port=mailport)
23 23 if ui.configbool('smtp', 'tls'):
24 24 ui.note(_('(using tls)\n'))
25 25 s.ehlo()
26 26 s.starttls()
27 27 s.ehlo()
28 28 username = ui.config('smtp', 'username')
29 29 password = ui.config('smtp', 'password')
30 30 if username and password:
31 31 ui.note(_('(authenticating to mail server as %s)\n') %
32 32 (username))
33 33 s.login(username, password)
34 34 return s
35 35
36 36 class _sendmail(object):
37 37 '''send mail using sendmail.'''
38 38
39 39 def __init__(self, ui, program):
40 40 self.ui = ui
41 41 self.program = program
42 42
43 43 def sendmail(self, sender, recipients, msg):
44 44 cmdline = '%s -f %s %s' % (
45 45 self.program, templater.email(sender),
46 46 ' '.join(map(templater.email, recipients)))
47 47 self.ui.note(_('sending mail: %s\n') % cmdline)
48 48 fp = os.popen(cmdline, 'w')
49 49 fp.write(msg)
50 50 ret = fp.close()
51 51 if ret:
52 52 raise util.Abort('%s %s' % (
53 53 os.path.basename(self.program.split(None, 1)[0]),
54 54 util.explain_exit(ret)[0]))
55 55
56 56 def connect(ui):
57 57 '''make a mail connection. object returned has one method, sendmail.
58 58 call as sendmail(sender, list-of-recipients, msg).'''
59 59
60 60 method = ui.config('email', 'method', 'smtp')
61 61 if method == 'smtp':
62 62 return _smtp(ui)
63 63
64 64 return _sendmail(ui, method)
65 65
66 66 def sendmail(ui, sender, recipients, msg):
67 67 return connect(ui).sendmail(sender, recipients, msg)
@@ -1,679 +1,679 b''
1 1 # patch.py - patch file parsing routines
2 2 #
3 3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9 from node import *
10 10 import base85, cmdutil, mdiff, util
11 import cStringIO, email.Parser, errno, os, popen2, re, shutil, sha
11 import cStringIO, email.Parser, os, popen2, re, sha
12 12 import sys, tempfile, zlib
13 13
14 14 # helper functions
15 15
16 16 def copyfile(src, dst, basedir=None):
17 17 if not basedir:
18 18 basedir = os.getcwd()
19 19
20 20 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
21 21 if os.path.exists(absdst):
22 22 raise util.Abort(_("cannot create %s: destination already exists") %
23 23 dst)
24 24
25 25 targetdir = os.path.dirname(absdst)
26 26 if not os.path.isdir(targetdir):
27 27 os.makedirs(targetdir)
28 28
29 29 util.copyfile(abssrc, absdst)
30 30
31 31 # public functions
32 32
33 33 def extract(ui, fileobj):
34 34 '''extract patch from data read from fileobj.
35 35
36 36 patch can be normal patch or contained in email message.
37 37
38 38 return tuple (filename, message, user, date). any item in returned
39 39 tuple can be None. if filename is None, fileobj did not contain
40 40 patch. caller must unlink filename when done.'''
41 41
42 42 # attempt to detect the start of a patch
43 43 # (this heuristic is borrowed from quilt)
44 44 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
45 45 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
46 46 '(---|\*\*\*)[ \t])', re.MULTILINE)
47 47
48 48 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
49 49 tmpfp = os.fdopen(fd, 'w')
50 50 try:
51 51 hgpatch = False
52 52
53 53 msg = email.Parser.Parser().parse(fileobj)
54 54
55 55 message = msg['Subject']
56 56 user = msg['From']
57 57 # should try to parse msg['Date']
58 58 date = None
59 59
60 60 if message:
61 61 message = message.replace('\n\t', ' ')
62 62 ui.debug('Subject: %s\n' % message)
63 63 if user:
64 64 ui.debug('From: %s\n' % user)
65 65 diffs_seen = 0
66 66 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
67 67
68 68 for part in msg.walk():
69 69 content_type = part.get_content_type()
70 70 ui.debug('Content-Type: %s\n' % content_type)
71 71 if content_type not in ok_types:
72 72 continue
73 73 payload = part.get_payload(decode=True)
74 74 m = diffre.search(payload)
75 75 if m:
76 76 ui.debug(_('found patch at byte %d\n') % m.start(0))
77 77 diffs_seen += 1
78 78 cfp = cStringIO.StringIO()
79 79 if message:
80 80 cfp.write(message)
81 81 cfp.write('\n')
82 82 for line in payload[:m.start(0)].splitlines():
83 83 if line.startswith('# HG changeset patch'):
84 84 ui.debug(_('patch generated by hg export\n'))
85 85 hgpatch = True
86 86 # drop earlier commit message content
87 87 cfp.seek(0)
88 88 cfp.truncate()
89 89 elif hgpatch:
90 90 if line.startswith('# User '):
91 91 user = line[7:]
92 92 ui.debug('From: %s\n' % user)
93 93 elif line.startswith("# Date "):
94 94 date = line[7:]
95 95 if not line.startswith('# '):
96 96 cfp.write(line)
97 97 cfp.write('\n')
98 98 message = cfp.getvalue()
99 99 if tmpfp:
100 100 tmpfp.write(payload)
101 101 if not payload.endswith('\n'):
102 102 tmpfp.write('\n')
103 103 elif not diffs_seen and message and content_type == 'text/plain':
104 104 message += '\n' + payload
105 105 except:
106 106 tmpfp.close()
107 107 os.unlink(tmpname)
108 108 raise
109 109
110 110 tmpfp.close()
111 111 if not diffs_seen:
112 112 os.unlink(tmpname)
113 113 return None, message, user, date
114 114 return tmpname, message, user, date
115 115
116 116 GP_PATCH = 1 << 0 # we have to run patch
117 117 GP_FILTER = 1 << 1 # there's some copy/rename operation
118 118 GP_BINARY = 1 << 2 # there's a binary patch
119 119
120 120 def readgitpatch(patchname):
121 121 """extract git-style metadata about patches from <patchname>"""
122 122 class gitpatch:
123 123 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
124 124 def __init__(self, path):
125 125 self.path = path
126 126 self.oldpath = None
127 127 self.mode = None
128 128 self.op = 'MODIFY'
129 129 self.copymod = False
130 130 self.lineno = 0
131 131 self.binary = False
132 132
133 133 # Filter patch for git information
134 134 gitre = re.compile('diff --git a/(.*) b/(.*)')
135 135 pf = file(patchname)
136 136 gp = None
137 137 gitpatches = []
138 138 # Can have a git patch with only metadata, causing patch to complain
139 139 dopatch = 0
140 140
141 141 lineno = 0
142 142 for line in pf:
143 143 lineno += 1
144 144 if line.startswith('diff --git'):
145 145 m = gitre.match(line)
146 146 if m:
147 147 if gp:
148 148 gitpatches.append(gp)
149 149 src, dst = m.group(1, 2)
150 150 gp = gitpatch(dst)
151 151 gp.lineno = lineno
152 152 elif gp:
153 153 if line.startswith('--- '):
154 154 if gp.op in ('COPY', 'RENAME'):
155 155 gp.copymod = True
156 156 dopatch |= GP_FILTER
157 157 gitpatches.append(gp)
158 158 gp = None
159 159 dopatch |= GP_PATCH
160 160 continue
161 161 if line.startswith('rename from '):
162 162 gp.op = 'RENAME'
163 163 gp.oldpath = line[12:].rstrip()
164 164 elif line.startswith('rename to '):
165 165 gp.path = line[10:].rstrip()
166 166 elif line.startswith('copy from '):
167 167 gp.op = 'COPY'
168 168 gp.oldpath = line[10:].rstrip()
169 169 elif line.startswith('copy to '):
170 170 gp.path = line[8:].rstrip()
171 171 elif line.startswith('deleted file'):
172 172 gp.op = 'DELETE'
173 173 elif line.startswith('new file mode '):
174 174 gp.op = 'ADD'
175 175 gp.mode = int(line.rstrip()[-3:], 8)
176 176 elif line.startswith('new mode '):
177 177 gp.mode = int(line.rstrip()[-3:], 8)
178 178 elif line.startswith('GIT binary patch'):
179 179 dopatch |= GP_BINARY
180 180 gp.binary = True
181 181 if gp:
182 182 gitpatches.append(gp)
183 183
184 184 if not gitpatches:
185 185 dopatch = GP_PATCH
186 186
187 187 return (dopatch, gitpatches)
188 188
189 189 def dogitpatch(patchname, gitpatches, cwd=None):
190 190 """Preprocess git patch so that vanilla patch can handle it"""
191 191 def extractbin(fp):
192 192 i = [0] # yuck
193 193 def readline():
194 194 i[0] += 1
195 195 return fp.readline().rstrip()
196 196 line = readline()
197 197 while line and not line.startswith('literal '):
198 198 line = readline()
199 199 if not line:
200 200 return None, i[0]
201 201 size = int(line[8:])
202 202 dec = []
203 203 line = readline()
204 204 while line:
205 205 l = line[0]
206 206 if l <= 'Z' and l >= 'A':
207 207 l = ord(l) - ord('A') + 1
208 208 else:
209 209 l = ord(l) - ord('a') + 27
210 210 dec.append(base85.b85decode(line[1:])[:l])
211 211 line = readline()
212 212 text = zlib.decompress(''.join(dec))
213 213 if len(text) != size:
214 214 raise util.Abort(_('binary patch is %d bytes, not %d') %
215 215 (len(text), size))
216 216 return text, i[0]
217 217
218 218 pf = file(patchname)
219 219 pfline = 1
220 220
221 221 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
222 222 tmpfp = os.fdopen(fd, 'w')
223 223
224 224 try:
225 225 for i in xrange(len(gitpatches)):
226 226 p = gitpatches[i]
227 227 if not p.copymod and not p.binary:
228 228 continue
229 229
230 230 # rewrite patch hunk
231 231 while pfline < p.lineno:
232 232 tmpfp.write(pf.readline())
233 233 pfline += 1
234 234
235 235 if p.binary:
236 236 text, delta = extractbin(pf)
237 237 if not text:
238 238 raise util.Abort(_('binary patch extraction failed'))
239 239 pfline += delta
240 240 if not cwd:
241 241 cwd = os.getcwd()
242 242 absdst = os.path.join(cwd, p.path)
243 243 basedir = os.path.dirname(absdst)
244 244 if not os.path.isdir(basedir):
245 245 os.makedirs(basedir)
246 246 out = file(absdst, 'wb')
247 247 out.write(text)
248 248 out.close()
249 249 elif p.copymod:
250 250 copyfile(p.oldpath, p.path, basedir=cwd)
251 251 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
252 252 line = pf.readline()
253 253 pfline += 1
254 254 while not line.startswith('--- a/'):
255 255 tmpfp.write(line)
256 256 line = pf.readline()
257 257 pfline += 1
258 258 tmpfp.write('--- a/%s\n' % p.path)
259 259
260 260 line = pf.readline()
261 261 while line:
262 262 tmpfp.write(line)
263 263 line = pf.readline()
264 264 except:
265 265 tmpfp.close()
266 266 os.unlink(patchname)
267 267 raise
268 268
269 269 tmpfp.close()
270 270 return patchname
271 271
272 272 def patch(patchname, ui, strip=1, cwd=None, files={}):
273 273 """apply the patch <patchname> to the working directory.
274 274 a list of patched files is returned"""
275 275
276 276 # helper function
277 277 def __patch(patchname):
278 278 """patch and updates the files and fuzz variables"""
279 279 fuzz = False
280 280
281 281 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''),
282 282 'patch')
283 283 args = []
284 284 if cwd:
285 285 args.append('-d %s' % util.shellquote(cwd))
286 286 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
287 287 util.shellquote(patchname)))
288 288
289 289 for line in fp:
290 290 line = line.rstrip()
291 291 ui.note(line + '\n')
292 292 if line.startswith('patching file '):
293 293 pf = util.parse_patch_output(line)
294 294 printed_file = False
295 295 files.setdefault(pf, (None, None))
296 296 elif line.find('with fuzz') >= 0:
297 297 fuzz = True
298 298 if not printed_file:
299 299 ui.warn(pf + '\n')
300 300 printed_file = True
301 301 ui.warn(line + '\n')
302 302 elif line.find('saving rejects to file') >= 0:
303 303 ui.warn(line + '\n')
304 304 elif line.find('FAILED') >= 0:
305 305 if not printed_file:
306 306 ui.warn(pf + '\n')
307 307 printed_file = True
308 308 ui.warn(line + '\n')
309 309 code = fp.close()
310 310 if code:
311 311 raise util.Abort(_("patch command failed: %s") %
312 312 util.explain_exit(code)[0])
313 313 return fuzz
314 314
315 315 (dopatch, gitpatches) = readgitpatch(patchname)
316 316 for gp in gitpatches:
317 317 files[gp.path] = (gp.op, gp)
318 318
319 319 fuzz = False
320 320 if dopatch:
321 321 filterpatch = dopatch & (GP_FILTER | GP_BINARY)
322 322 if filterpatch:
323 323 patchname = dogitpatch(patchname, gitpatches, cwd=cwd)
324 324 try:
325 325 if dopatch & GP_PATCH:
326 326 fuzz = __patch(patchname)
327 327 finally:
328 328 if filterpatch:
329 329 os.unlink(patchname)
330 330
331 331 return fuzz
332 332
333 333 def diffopts(ui, opts={}, untrusted=False):
334 334 def get(key, name=None):
335 335 return (opts.get(key) or
336 336 ui.configbool('diff', name or key, None, untrusted=untrusted))
337 337 return mdiff.diffopts(
338 338 text=opts.get('text'),
339 339 git=get('git'),
340 340 nodates=get('nodates'),
341 341 showfunc=get('show_function', 'showfunc'),
342 342 ignorews=get('ignore_all_space', 'ignorews'),
343 343 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
344 344 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'))
345 345
346 346 def updatedir(ui, repo, patches, wlock=None):
347 347 '''Update dirstate after patch application according to metadata'''
348 348 if not patches:
349 349 return
350 350 copies = []
351 351 removes = {}
352 352 cfiles = patches.keys()
353 353 cwd = repo.getcwd()
354 354 if cwd:
355 355 cfiles = [util.pathto(cwd, f) for f in patches.keys()]
356 356 for f in patches:
357 357 ctype, gp = patches[f]
358 358 if ctype == 'RENAME':
359 359 copies.append((gp.oldpath, gp.path, gp.copymod))
360 360 removes[gp.oldpath] = 1
361 361 elif ctype == 'COPY':
362 362 copies.append((gp.oldpath, gp.path, gp.copymod))
363 363 elif ctype == 'DELETE':
364 364 removes[gp.path] = 1
365 365 for src, dst, after in copies:
366 366 if not after:
367 367 copyfile(src, dst, repo.root)
368 368 repo.copy(src, dst, wlock=wlock)
369 369 removes = removes.keys()
370 370 if removes:
371 371 removes.sort()
372 372 repo.remove(removes, True, wlock=wlock)
373 373 for f in patches:
374 374 ctype, gp = patches[f]
375 375 if gp and gp.mode:
376 376 x = gp.mode & 0100 != 0
377 377 dst = os.path.join(repo.root, gp.path)
378 378 # patch won't create empty files
379 379 if ctype == 'ADD' and not os.path.exists(dst):
380 380 repo.wwrite(gp.path, '')
381 381 util.set_exec(dst, x)
382 382 cmdutil.addremove(repo, cfiles, wlock=wlock)
383 383 files = patches.keys()
384 384 files.extend([r for r in removes if r not in files])
385 385 files.sort()
386 386
387 387 return files
388 388
389 389 def b85diff(fp, to, tn):
390 390 '''print base85-encoded binary diff'''
391 391 def gitindex(text):
392 392 if not text:
393 393 return '0' * 40
394 394 l = len(text)
395 395 s = sha.new('blob %d\0' % l)
396 396 s.update(text)
397 397 return s.hexdigest()
398 398
399 399 def fmtline(line):
400 400 l = len(line)
401 401 if l <= 26:
402 402 l = chr(ord('A') + l - 1)
403 403 else:
404 404 l = chr(l - 26 + ord('a') - 1)
405 405 return '%c%s\n' % (l, base85.b85encode(line, True))
406 406
407 407 def chunk(text, csize=52):
408 408 l = len(text)
409 409 i = 0
410 410 while i < l:
411 411 yield text[i:i+csize]
412 412 i += csize
413 413
414 414 # TODO: deltas
415 415 l = len(tn)
416 416 fp.write('index %s..%s\nGIT binary patch\nliteral %s\n' %
417 417 (gitindex(to), gitindex(tn), len(tn)))
418 418
419 419 tn = ''.join([fmtline(l) for l in chunk(zlib.compress(tn))])
420 420 fp.write(tn)
421 421 fp.write('\n')
422 422
423 423 def diff(repo, node1=None, node2=None, files=None, match=util.always,
424 424 fp=None, changes=None, opts=None):
425 425 '''print diff of changes to files between two nodes, or node and
426 426 working directory.
427 427
428 428 if node1 is None, use first dirstate parent instead.
429 429 if node2 is None, compare node1 with working directory.'''
430 430
431 431 if opts is None:
432 432 opts = mdiff.defaultopts
433 433 if fp is None:
434 434 fp = repo.ui
435 435
436 436 if not node1:
437 437 node1 = repo.dirstate.parents()[0]
438 438
439 439 clcache = {}
440 440 def getchangelog(n):
441 441 if n not in clcache:
442 442 clcache[n] = repo.changelog.read(n)
443 443 return clcache[n]
444 444 mcache = {}
445 445 def getmanifest(n):
446 446 if n not in mcache:
447 447 mcache[n] = repo.manifest.read(n)
448 448 return mcache[n]
449 449 fcache = {}
450 450 def getfile(f):
451 451 if f not in fcache:
452 452 fcache[f] = repo.file(f)
453 453 return fcache[f]
454 454
455 455 # reading the data for node1 early allows it to play nicely
456 456 # with repo.status and the revlog cache.
457 457 change = getchangelog(node1)
458 458 mmap = getmanifest(change[0])
459 459 date1 = util.datestr(change[2])
460 460
461 461 if not changes:
462 462 changes = repo.status(node1, node2, files, match=match)[:5]
463 463 modified, added, removed, deleted, unknown = changes
464 464 if files:
465 465 def filterfiles(filters):
466 466 l = [x for x in filters if x in files]
467 467
468 468 for t in files:
469 469 if not t.endswith("/"):
470 470 t += "/"
471 471 l += [x for x in filters if x.startswith(t)]
472 472 return l
473 473
474 474 modified, added, removed = map(filterfiles, (modified, added, removed))
475 475
476 476 if not modified and not added and not removed:
477 477 return
478 478
479 479 # returns False if there was no rename between n1 and n2
480 480 # returns None if the file was created between n1 and n2
481 481 # returns the (file, node) present in n1 that was renamed to f in n2
482 482 def renamedbetween(f, n1, n2):
483 483 r1, r2 = map(repo.changelog.rev, (n1, n2))
484 484 orig = f
485 485 src = None
486 486 while r2 > r1:
487 487 cl = getchangelog(n2)
488 488 if f in cl[3]:
489 489 m = getmanifest(cl[0])
490 490 try:
491 491 src = getfile(f).renamed(m[f])
492 492 except KeyError:
493 493 return None
494 494 if src:
495 495 f = src[0]
496 496 n2 = repo.changelog.parents(n2)[0]
497 497 r2 = repo.changelog.rev(n2)
498 498 cl = getchangelog(n1)
499 499 m = getmanifest(cl[0])
500 500 if f not in m:
501 501 return None
502 502 if f == orig:
503 503 return False
504 504 return f, m[f]
505 505
506 506 if node2:
507 507 change = getchangelog(node2)
508 508 mmap2 = getmanifest(change[0])
509 509 _date2 = util.datestr(change[2])
510 510 def date2(f):
511 511 return _date2
512 512 def read(f):
513 513 return getfile(f).read(mmap2[f])
514 514 def renamed(f):
515 515 return renamedbetween(f, node1, node2)
516 516 else:
517 517 tz = util.makedate()[1]
518 518 _date2 = util.datestr()
519 519 def date2(f):
520 520 try:
521 521 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
522 522 except OSError, err:
523 523 if err.errno != errno.ENOENT: raise
524 524 return _date2
525 525 def read(f):
526 526 return repo.wread(f)
527 527 def renamed(f):
528 528 src = repo.dirstate.copied(f)
529 529 parent = repo.dirstate.parents()[0]
530 530 if src:
531 531 f = src
532 532 of = renamedbetween(f, node1, parent)
533 533 if of or of is None:
534 534 return of
535 535 elif src:
536 536 cl = getchangelog(parent)[0]
537 537 return (src, getmanifest(cl)[src])
538 538 else:
539 539 return None
540 540
541 541 if repo.ui.quiet:
542 542 r = None
543 543 else:
544 544 hexfunc = repo.ui.debugflag and hex or short
545 545 r = [hexfunc(node) for node in [node1, node2] if node]
546 546
547 547 if opts.git:
548 548 copied = {}
549 549 for f in added:
550 550 src = renamed(f)
551 551 if src:
552 552 copied[f] = src
553 553 srcs = [x[1][0] for x in copied.items()]
554 554
555 555 all = modified + added + removed
556 556 all.sort()
557 557 gone = {}
558 558 for f in all:
559 559 to = None
560 560 tn = None
561 561 dodiff = True
562 562 header = []
563 563 if f in mmap:
564 564 to = getfile(f).read(mmap[f])
565 565 if f not in removed:
566 566 tn = read(f)
567 567 if opts.git:
568 568 def gitmode(x):
569 569 return x and '100755' or '100644'
570 570 def addmodehdr(header, omode, nmode):
571 571 if omode != nmode:
572 572 header.append('old mode %s\n' % omode)
573 573 header.append('new mode %s\n' % nmode)
574 574
575 575 a, b = f, f
576 576 if f in added:
577 577 if node2:
578 578 mode = gitmode(mmap2.execf(f))
579 579 else:
580 580 mode = gitmode(util.is_exec(repo.wjoin(f), None))
581 581 if f in copied:
582 582 a, arev = copied[f]
583 583 omode = gitmode(mmap.execf(a))
584 584 addmodehdr(header, omode, mode)
585 585 if a in removed and a not in gone:
586 586 op = 'rename'
587 587 gone[a] = 1
588 588 else:
589 589 op = 'copy'
590 590 header.append('%s from %s\n' % (op, a))
591 591 header.append('%s to %s\n' % (op, f))
592 592 to = getfile(a).read(arev)
593 593 else:
594 594 header.append('new file mode %s\n' % mode)
595 595 if util.binary(tn):
596 596 dodiff = 'binary'
597 597 elif f in removed:
598 598 if f in srcs:
599 599 dodiff = False
600 600 else:
601 601 mode = gitmode(mmap.execf(f))
602 602 header.append('deleted file mode %s\n' % mode)
603 603 else:
604 604 omode = gitmode(mmap.execf(f))
605 605 if node2:
606 606 nmode = gitmode(mmap2.execf(f))
607 607 else:
608 608 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
609 609 addmodehdr(header, omode, nmode)
610 610 if util.binary(to) or util.binary(tn):
611 611 dodiff = 'binary'
612 612 r = None
613 613 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
614 614 if dodiff == 'binary':
615 615 fp.write(''.join(header))
616 616 b85diff(fp, to, tn)
617 617 elif dodiff:
618 618 text = mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts)
619 619 if text or len(header) > 1:
620 620 fp.write(''.join(header))
621 621 fp.write(text)
622 622
623 623 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
624 624 opts=None):
625 625 '''export changesets as hg patches.'''
626 626
627 627 total = len(revs)
628 628 revwidth = max([len(str(rev)) for rev in revs])
629 629
630 630 def single(node, seqno, fp):
631 631 parents = [p for p in repo.changelog.parents(node) if p != nullid]
632 632 if switch_parent:
633 633 parents.reverse()
634 634 prev = (parents and parents[0]) or nullid
635 635 change = repo.changelog.read(node)
636 636
637 637 if not fp:
638 638 fp = cmdutil.make_file(repo, template, node, total=total,
639 639 seqno=seqno, revwidth=revwidth)
640 640 if fp not in (sys.stdout, repo.ui):
641 641 repo.ui.note("%s\n" % fp.name)
642 642
643 643 fp.write("# HG changeset patch\n")
644 644 fp.write("# User %s\n" % change[1])
645 645 fp.write("# Date %d %d\n" % change[2])
646 646 fp.write("# Node ID %s\n" % hex(node))
647 647 fp.write("# Parent %s\n" % hex(prev))
648 648 if len(parents) > 1:
649 649 fp.write("# Parent %s\n" % hex(parents[1]))
650 650 fp.write(change[4].rstrip())
651 651 fp.write("\n\n")
652 652
653 653 diff(repo, prev, node, fp=fp, opts=opts)
654 654 if fp not in (sys.stdout, repo.ui):
655 655 fp.close()
656 656
657 657 for seqno, rev in enumerate(revs):
658 658 single(repo.lookup(rev), seqno+1, fp)
659 659
660 660 def diffstat(patchlines):
661 661 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
662 662 try:
663 663 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
664 664 try:
665 665 for line in patchlines: print >> p.tochild, line
666 666 p.tochild.close()
667 667 if p.wait(): return
668 668 fp = os.fdopen(fd, 'r')
669 669 stat = []
670 670 for line in fp: stat.append(line.lstrip())
671 671 last = stat.pop()
672 672 stat.insert(0, last)
673 673 stat = ''.join(stat)
674 674 if stat.startswith('0 files'): raise ValueError
675 675 return stat
676 676 except: raise
677 677 finally:
678 678 try: os.unlink(name)
679 679 except: pass
General Comments 0
You need to be logged in to leave comments. Login now