##// END OF EJS Templates
Make mq, record and transplant honor patch.eol
Patrick Mezard -
r8811:8b35b087 default
parent child Browse files
Show More
@@ -0,0 +1,64 b''
1 #!/bin/sh
2
3 # Test interactions between mq and patch.eol
4
5 echo "[extensions]" >> $HGRCPATH
6 echo "mq=" >> $HGRCPATH
7
8 cat > makepatch.py <<EOF
9 f = file('eol.diff', 'wb')
10 w = f.write
11 w('test message\n')
12 w('diff --git a/a b/a\n')
13 w('--- a/a\n')
14 w('+++ b/a\n')
15 w('@@ -1,5 +1,5 @@\n')
16 w(' a\n')
17 w('-b\r\n')
18 w('+y\r\n')
19 w(' c\r\n')
20 w(' d\n')
21 w('-e\n')
22 w('\ No newline at end of file\n')
23 w('+z\r\n')
24 w('\ No newline at end of file\r\n')
25 EOF
26
27 cat > cateol.py <<EOF
28 import sys
29 for line in file(sys.argv[1], 'rb'):
30 line = line.replace('\r', '<CR>')
31 line = line.replace('\n', '<LF>')
32 print line
33 EOF
34
35 hg init repo
36 cd repo
37 echo '\.diff' > .hgignore
38 echo '\.rej' >> .hgignore
39
40 # Test different --eol values
41 python -c 'file("a", "wb").write("a\nb\nc\nd\ne")'
42 hg ci -Am adda
43 python ../makepatch.py
44 hg qimport eol.diff
45 echo % should fail in strict mode
46 hg qpush
47 hg qpop
48 echo % invalid eol
49 hg --config patch.eol='LFCR' qpush
50 hg qpop
51 echo % force LF
52 hg --config patch.eol='CRLF' qpush
53 hg qrefresh
54 python ../cateol.py .hg/patches/eol.diff
55 python ../cateol.py a
56 hg qpop
57 echo % push again forcing LF and compare revisions
58 hg --config patch.eol='CRLF' qpush
59 python ../cateol.py a
60 hg qpop
61 echo % push again without LF and compare revisions
62 hg qpush
63 python ../cateol.py a
64 hg qpop
@@ -0,0 +1,63 b''
1 adding .hgignore
2 adding a
3 adding eol.diff to series file
4 % should fail in strict mode
5 applying eol.diff
6 patching file a
7 Hunk #1 FAILED at 0
8 1 out of 1 hunks FAILED -- saving rejects to file a.rej
9 patch failed, unable to continue (try -v)
10 patch failed, rejects left in working dir
11 errors during apply, please fix and refresh eol.diff
12 patch queue now empty
13 % invalid eol
14 applying eol.diff
15 patch failed, unable to continue (try -v)
16 patch failed, rejects left in working dir
17 errors during apply, please fix and refresh eol.diff
18 patch queue now empty
19 % force LF
20 applying eol.diff
21 now at: eol.diff
22 test message<LF>
23 <LF>
24 diff --git a/a b/a<LF>
25 --- a/a<LF>
26 +++ b/a<LF>
27 @@ -1,5 +1,5 @@<LF>
28 -a<LF>
29 -b<LF>
30 -c<LF>
31 -d<LF>
32 -e<LF>
33 \ No newline at end of file<LF>
34 +a<CR><LF>
35 +y<CR><LF>
36 +c<CR><LF>
37 +d<CR><LF>
38 +z<LF>
39 \ No newline at end of file<LF>
40 a<CR><LF>
41 y<CR><LF>
42 c<CR><LF>
43 d<CR><LF>
44 z
45 patch queue now empty
46 % push again forcing LF and compare revisions
47 applying eol.diff
48 now at: eol.diff
49 a<CR><LF>
50 y<CR><LF>
51 c<CR><LF>
52 d<CR><LF>
53 z
54 patch queue now empty
55 % push again without LF and compare revisions
56 applying eol.diff
57 now at: eol.diff
58 a<CR><LF>
59 y<CR><LF>
60 c<CR><LF>
61 d<CR><LF>
62 z
63 patch queue now empty
@@ -1,2630 +1,2630 b''
1 # mq.py - patch queues for mercurial
1 # mq.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 '''patch management and development
8 '''patch management and development
9
9
10 This extension lets you work with a stack of patches in a Mercurial
10 This extension lets you work with a stack of patches in a Mercurial
11 repository. It manages two stacks of patches - all known patches, and
11 repository. It manages two stacks of patches - all known patches, and
12 applied patches (subset of known patches).
12 applied patches (subset of known patches).
13
13
14 Known patches are represented as patch files in the .hg/patches
14 Known patches are represented as patch files in the .hg/patches
15 directory. Applied patches are both patch files and changesets.
15 directory. Applied patches are both patch files and changesets.
16
16
17 Common tasks (use "hg help command" for more details):
17 Common tasks (use "hg help command" for more details):
18
18
19 prepare repository to work with patches qinit
19 prepare repository to work with patches qinit
20 create new patch qnew
20 create new patch qnew
21 import existing patch qimport
21 import existing patch qimport
22
22
23 print patch series qseries
23 print patch series qseries
24 print applied patches qapplied
24 print applied patches qapplied
25 print name of top applied patch qtop
25 print name of top applied patch qtop
26
26
27 add known patch to applied stack qpush
27 add known patch to applied stack qpush
28 remove patch from applied stack qpop
28 remove patch from applied stack qpop
29 refresh contents of top applied patch qrefresh
29 refresh contents of top applied patch qrefresh
30 '''
30 '''
31
31
32 from mercurial.i18n import _
32 from mercurial.i18n import _
33 from mercurial.node import bin, hex, short, nullid, nullrev
33 from mercurial.node import bin, hex, short, nullid, nullrev
34 from mercurial.lock import release
34 from mercurial.lock import release
35 from mercurial import commands, cmdutil, hg, patch, util
35 from mercurial import commands, cmdutil, hg, patch, util
36 from mercurial import repair, extensions, url, error
36 from mercurial import repair, extensions, url, error
37 import os, sys, re, errno
37 import os, sys, re, errno
38
38
39 commands.norepo += " qclone"
39 commands.norepo += " qclone"
40
40
41 # Patch names looks like unix-file names.
41 # Patch names looks like unix-file names.
42 # They must be joinable with queue directory and result in the patch path.
42 # They must be joinable with queue directory and result in the patch path.
43 normname = util.normpath
43 normname = util.normpath
44
44
45 class statusentry(object):
45 class statusentry(object):
46 def __init__(self, rev, name=None):
46 def __init__(self, rev, name=None):
47 if not name:
47 if not name:
48 fields = rev.split(':', 1)
48 fields = rev.split(':', 1)
49 if len(fields) == 2:
49 if len(fields) == 2:
50 self.rev, self.name = fields
50 self.rev, self.name = fields
51 else:
51 else:
52 self.rev, self.name = None, None
52 self.rev, self.name = None, None
53 else:
53 else:
54 self.rev, self.name = rev, name
54 self.rev, self.name = rev, name
55
55
56 def __str__(self):
56 def __str__(self):
57 return self.rev + ':' + self.name
57 return self.rev + ':' + self.name
58
58
59 class patchheader(object):
59 class patchheader(object):
60 def __init__(self, pf):
60 def __init__(self, pf):
61 def eatdiff(lines):
61 def eatdiff(lines):
62 while lines:
62 while lines:
63 l = lines[-1]
63 l = lines[-1]
64 if (l.startswith("diff -") or
64 if (l.startswith("diff -") or
65 l.startswith("Index:") or
65 l.startswith("Index:") or
66 l.startswith("===========")):
66 l.startswith("===========")):
67 del lines[-1]
67 del lines[-1]
68 else:
68 else:
69 break
69 break
70 def eatempty(lines):
70 def eatempty(lines):
71 while lines:
71 while lines:
72 l = lines[-1]
72 l = lines[-1]
73 if re.match('\s*$', l):
73 if re.match('\s*$', l):
74 del lines[-1]
74 del lines[-1]
75 else:
75 else:
76 break
76 break
77
77
78 message = []
78 message = []
79 comments = []
79 comments = []
80 user = None
80 user = None
81 date = None
81 date = None
82 format = None
82 format = None
83 subject = None
83 subject = None
84 diffstart = 0
84 diffstart = 0
85
85
86 for line in file(pf):
86 for line in file(pf):
87 line = line.rstrip()
87 line = line.rstrip()
88 if line.startswith('diff --git'):
88 if line.startswith('diff --git'):
89 diffstart = 2
89 diffstart = 2
90 break
90 break
91 if diffstart:
91 if diffstart:
92 if line.startswith('+++ '):
92 if line.startswith('+++ '):
93 diffstart = 2
93 diffstart = 2
94 break
94 break
95 if line.startswith("--- "):
95 if line.startswith("--- "):
96 diffstart = 1
96 diffstart = 1
97 continue
97 continue
98 elif format == "hgpatch":
98 elif format == "hgpatch":
99 # parse values when importing the result of an hg export
99 # parse values when importing the result of an hg export
100 if line.startswith("# User "):
100 if line.startswith("# User "):
101 user = line[7:]
101 user = line[7:]
102 elif line.startswith("# Date "):
102 elif line.startswith("# Date "):
103 date = line[7:]
103 date = line[7:]
104 elif not line.startswith("# ") and line:
104 elif not line.startswith("# ") and line:
105 message.append(line)
105 message.append(line)
106 format = None
106 format = None
107 elif line == '# HG changeset patch':
107 elif line == '# HG changeset patch':
108 format = "hgpatch"
108 format = "hgpatch"
109 elif (format != "tagdone" and (line.startswith("Subject: ") or
109 elif (format != "tagdone" and (line.startswith("Subject: ") or
110 line.startswith("subject: "))):
110 line.startswith("subject: "))):
111 subject = line[9:]
111 subject = line[9:]
112 format = "tag"
112 format = "tag"
113 elif (format != "tagdone" and (line.startswith("From: ") or
113 elif (format != "tagdone" and (line.startswith("From: ") or
114 line.startswith("from: "))):
114 line.startswith("from: "))):
115 user = line[6:]
115 user = line[6:]
116 format = "tag"
116 format = "tag"
117 elif format == "tag" and line == "":
117 elif format == "tag" and line == "":
118 # when looking for tags (subject: from: etc) they
118 # when looking for tags (subject: from: etc) they
119 # end once you find a blank line in the source
119 # end once you find a blank line in the source
120 format = "tagdone"
120 format = "tagdone"
121 elif message or line:
121 elif message or line:
122 message.append(line)
122 message.append(line)
123 comments.append(line)
123 comments.append(line)
124
124
125 eatdiff(message)
125 eatdiff(message)
126 eatdiff(comments)
126 eatdiff(comments)
127 eatempty(message)
127 eatempty(message)
128 eatempty(comments)
128 eatempty(comments)
129
129
130 # make sure message isn't empty
130 # make sure message isn't empty
131 if format and format.startswith("tag") and subject:
131 if format and format.startswith("tag") and subject:
132 message.insert(0, "")
132 message.insert(0, "")
133 message.insert(0, subject)
133 message.insert(0, subject)
134
134
135 self.message = message
135 self.message = message
136 self.comments = comments
136 self.comments = comments
137 self.user = user
137 self.user = user
138 self.date = date
138 self.date = date
139 self.haspatch = diffstart > 1
139 self.haspatch = diffstart > 1
140
140
141 def setuser(self, user):
141 def setuser(self, user):
142 if not self.updateheader(['From: ', '# User '], user):
142 if not self.updateheader(['From: ', '# User '], user):
143 try:
143 try:
144 patchheaderat = self.comments.index('# HG changeset patch')
144 patchheaderat = self.comments.index('# HG changeset patch')
145 self.comments.insert(patchheaderat + 1,'# User ' + user)
145 self.comments.insert(patchheaderat + 1,'# User ' + user)
146 except ValueError:
146 except ValueError:
147 self.comments = ['From: ' + user, ''] + self.comments
147 self.comments = ['From: ' + user, ''] + self.comments
148 self.user = user
148 self.user = user
149
149
150 def setdate(self, date):
150 def setdate(self, date):
151 if self.updateheader(['# Date '], date):
151 if self.updateheader(['# Date '], date):
152 self.date = date
152 self.date = date
153
153
154 def setmessage(self, message):
154 def setmessage(self, message):
155 if self.comments:
155 if self.comments:
156 self._delmsg()
156 self._delmsg()
157 self.message = [message]
157 self.message = [message]
158 self.comments += self.message
158 self.comments += self.message
159
159
160 def updateheader(self, prefixes, new):
160 def updateheader(self, prefixes, new):
161 '''Update all references to a field in the patch header.
161 '''Update all references to a field in the patch header.
162 Return whether the field is present.'''
162 Return whether the field is present.'''
163 res = False
163 res = False
164 for prefix in prefixes:
164 for prefix in prefixes:
165 for i in xrange(len(self.comments)):
165 for i in xrange(len(self.comments)):
166 if self.comments[i].startswith(prefix):
166 if self.comments[i].startswith(prefix):
167 self.comments[i] = prefix + new
167 self.comments[i] = prefix + new
168 res = True
168 res = True
169 break
169 break
170 return res
170 return res
171
171
172 def __str__(self):
172 def __str__(self):
173 if not self.comments:
173 if not self.comments:
174 return ''
174 return ''
175 return '\n'.join(self.comments) + '\n\n'
175 return '\n'.join(self.comments) + '\n\n'
176
176
177 def _delmsg(self):
177 def _delmsg(self):
178 '''Remove existing message, keeping the rest of the comments fields.
178 '''Remove existing message, keeping the rest of the comments fields.
179 If comments contains 'subject: ', message will prepend
179 If comments contains 'subject: ', message will prepend
180 the field and a blank line.'''
180 the field and a blank line.'''
181 if self.message:
181 if self.message:
182 subj = 'subject: ' + self.message[0].lower()
182 subj = 'subject: ' + self.message[0].lower()
183 for i in xrange(len(self.comments)):
183 for i in xrange(len(self.comments)):
184 if subj == self.comments[i].lower():
184 if subj == self.comments[i].lower():
185 del self.comments[i]
185 del self.comments[i]
186 self.message = self.message[2:]
186 self.message = self.message[2:]
187 break
187 break
188 ci = 0
188 ci = 0
189 for mi in self.message:
189 for mi in self.message:
190 while mi != self.comments[ci]:
190 while mi != self.comments[ci]:
191 ci += 1
191 ci += 1
192 del self.comments[ci]
192 del self.comments[ci]
193
193
194 class queue(object):
194 class queue(object):
195 def __init__(self, ui, path, patchdir=None):
195 def __init__(self, ui, path, patchdir=None):
196 self.basepath = path
196 self.basepath = path
197 self.path = patchdir or os.path.join(path, "patches")
197 self.path = patchdir or os.path.join(path, "patches")
198 self.opener = util.opener(self.path)
198 self.opener = util.opener(self.path)
199 self.ui = ui
199 self.ui = ui
200 self.applied_dirty = 0
200 self.applied_dirty = 0
201 self.series_dirty = 0
201 self.series_dirty = 0
202 self.series_path = "series"
202 self.series_path = "series"
203 self.status_path = "status"
203 self.status_path = "status"
204 self.guards_path = "guards"
204 self.guards_path = "guards"
205 self.active_guards = None
205 self.active_guards = None
206 self.guards_dirty = False
206 self.guards_dirty = False
207 self._diffopts = None
207 self._diffopts = None
208
208
209 @util.propertycache
209 @util.propertycache
210 def applied(self):
210 def applied(self):
211 if os.path.exists(self.join(self.status_path)):
211 if os.path.exists(self.join(self.status_path)):
212 lines = self.opener(self.status_path).read().splitlines()
212 lines = self.opener(self.status_path).read().splitlines()
213 return [statusentry(l) for l in lines]
213 return [statusentry(l) for l in lines]
214 return []
214 return []
215
215
216 @util.propertycache
216 @util.propertycache
217 def full_series(self):
217 def full_series(self):
218 if os.path.exists(self.join(self.series_path)):
218 if os.path.exists(self.join(self.series_path)):
219 return self.opener(self.series_path).read().splitlines()
219 return self.opener(self.series_path).read().splitlines()
220 return []
220 return []
221
221
222 @util.propertycache
222 @util.propertycache
223 def series(self):
223 def series(self):
224 self.parse_series()
224 self.parse_series()
225 return self.series
225 return self.series
226
226
227 @util.propertycache
227 @util.propertycache
228 def series_guards(self):
228 def series_guards(self):
229 self.parse_series()
229 self.parse_series()
230 return self.series_guards
230 return self.series_guards
231
231
232 def invalidate(self):
232 def invalidate(self):
233 for a in 'applied full_series series series_guards'.split():
233 for a in 'applied full_series series series_guards'.split():
234 if a in self.__dict__:
234 if a in self.__dict__:
235 delattr(self, a)
235 delattr(self, a)
236 self.applied_dirty = 0
236 self.applied_dirty = 0
237 self.series_dirty = 0
237 self.series_dirty = 0
238 self.guards_dirty = False
238 self.guards_dirty = False
239 self.active_guards = None
239 self.active_guards = None
240
240
241 def diffopts(self):
241 def diffopts(self):
242 if self._diffopts is None:
242 if self._diffopts is None:
243 self._diffopts = patch.diffopts(self.ui)
243 self._diffopts = patch.diffopts(self.ui)
244 return self._diffopts
244 return self._diffopts
245
245
246 def join(self, *p):
246 def join(self, *p):
247 return os.path.join(self.path, *p)
247 return os.path.join(self.path, *p)
248
248
249 def find_series(self, patch):
249 def find_series(self, patch):
250 pre = re.compile("(\s*)([^#]+)")
250 pre = re.compile("(\s*)([^#]+)")
251 index = 0
251 index = 0
252 for l in self.full_series:
252 for l in self.full_series:
253 m = pre.match(l)
253 m = pre.match(l)
254 if m:
254 if m:
255 s = m.group(2)
255 s = m.group(2)
256 s = s.rstrip()
256 s = s.rstrip()
257 if s == patch:
257 if s == patch:
258 return index
258 return index
259 index += 1
259 index += 1
260 return None
260 return None
261
261
262 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
262 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
263
263
264 def parse_series(self):
264 def parse_series(self):
265 self.series = []
265 self.series = []
266 self.series_guards = []
266 self.series_guards = []
267 for l in self.full_series:
267 for l in self.full_series:
268 h = l.find('#')
268 h = l.find('#')
269 if h == -1:
269 if h == -1:
270 patch = l
270 patch = l
271 comment = ''
271 comment = ''
272 elif h == 0:
272 elif h == 0:
273 continue
273 continue
274 else:
274 else:
275 patch = l[:h]
275 patch = l[:h]
276 comment = l[h:]
276 comment = l[h:]
277 patch = patch.strip()
277 patch = patch.strip()
278 if patch:
278 if patch:
279 if patch in self.series:
279 if patch in self.series:
280 raise util.Abort(_('%s appears more than once in %s') %
280 raise util.Abort(_('%s appears more than once in %s') %
281 (patch, self.join(self.series_path)))
281 (patch, self.join(self.series_path)))
282 self.series.append(patch)
282 self.series.append(patch)
283 self.series_guards.append(self.guard_re.findall(comment))
283 self.series_guards.append(self.guard_re.findall(comment))
284
284
285 def check_guard(self, guard):
285 def check_guard(self, guard):
286 if not guard:
286 if not guard:
287 return _('guard cannot be an empty string')
287 return _('guard cannot be an empty string')
288 bad_chars = '# \t\r\n\f'
288 bad_chars = '# \t\r\n\f'
289 first = guard[0]
289 first = guard[0]
290 if first in '-+':
290 if first in '-+':
291 return (_('guard %r starts with invalid character: %r') %
291 return (_('guard %r starts with invalid character: %r') %
292 (guard, first))
292 (guard, first))
293 for c in bad_chars:
293 for c in bad_chars:
294 if c in guard:
294 if c in guard:
295 return _('invalid character in guard %r: %r') % (guard, c)
295 return _('invalid character in guard %r: %r') % (guard, c)
296
296
297 def set_active(self, guards):
297 def set_active(self, guards):
298 for guard in guards:
298 for guard in guards:
299 bad = self.check_guard(guard)
299 bad = self.check_guard(guard)
300 if bad:
300 if bad:
301 raise util.Abort(bad)
301 raise util.Abort(bad)
302 guards = sorted(set(guards))
302 guards = sorted(set(guards))
303 self.ui.debug(_('active guards: %s\n') % ' '.join(guards))
303 self.ui.debug(_('active guards: %s\n') % ' '.join(guards))
304 self.active_guards = guards
304 self.active_guards = guards
305 self.guards_dirty = True
305 self.guards_dirty = True
306
306
307 def active(self):
307 def active(self):
308 if self.active_guards is None:
308 if self.active_guards is None:
309 self.active_guards = []
309 self.active_guards = []
310 try:
310 try:
311 guards = self.opener(self.guards_path).read().split()
311 guards = self.opener(self.guards_path).read().split()
312 except IOError, err:
312 except IOError, err:
313 if err.errno != errno.ENOENT: raise
313 if err.errno != errno.ENOENT: raise
314 guards = []
314 guards = []
315 for i, guard in enumerate(guards):
315 for i, guard in enumerate(guards):
316 bad = self.check_guard(guard)
316 bad = self.check_guard(guard)
317 if bad:
317 if bad:
318 self.ui.warn('%s:%d: %s\n' %
318 self.ui.warn('%s:%d: %s\n' %
319 (self.join(self.guards_path), i + 1, bad))
319 (self.join(self.guards_path), i + 1, bad))
320 else:
320 else:
321 self.active_guards.append(guard)
321 self.active_guards.append(guard)
322 return self.active_guards
322 return self.active_guards
323
323
324 def set_guards(self, idx, guards):
324 def set_guards(self, idx, guards):
325 for g in guards:
325 for g in guards:
326 if len(g) < 2:
326 if len(g) < 2:
327 raise util.Abort(_('guard %r too short') % g)
327 raise util.Abort(_('guard %r too short') % g)
328 if g[0] not in '-+':
328 if g[0] not in '-+':
329 raise util.Abort(_('guard %r starts with invalid char') % g)
329 raise util.Abort(_('guard %r starts with invalid char') % g)
330 bad = self.check_guard(g[1:])
330 bad = self.check_guard(g[1:])
331 if bad:
331 if bad:
332 raise util.Abort(bad)
332 raise util.Abort(bad)
333 drop = self.guard_re.sub('', self.full_series[idx])
333 drop = self.guard_re.sub('', self.full_series[idx])
334 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
334 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
335 self.parse_series()
335 self.parse_series()
336 self.series_dirty = True
336 self.series_dirty = True
337
337
338 def pushable(self, idx):
338 def pushable(self, idx):
339 if isinstance(idx, str):
339 if isinstance(idx, str):
340 idx = self.series.index(idx)
340 idx = self.series.index(idx)
341 patchguards = self.series_guards[idx]
341 patchguards = self.series_guards[idx]
342 if not patchguards:
342 if not patchguards:
343 return True, None
343 return True, None
344 guards = self.active()
344 guards = self.active()
345 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
345 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
346 if exactneg:
346 if exactneg:
347 return False, exactneg[0]
347 return False, exactneg[0]
348 pos = [g for g in patchguards if g[0] == '+']
348 pos = [g for g in patchguards if g[0] == '+']
349 exactpos = [g for g in pos if g[1:] in guards]
349 exactpos = [g for g in pos if g[1:] in guards]
350 if pos:
350 if pos:
351 if exactpos:
351 if exactpos:
352 return True, exactpos[0]
352 return True, exactpos[0]
353 return False, pos
353 return False, pos
354 return True, ''
354 return True, ''
355
355
356 def explain_pushable(self, idx, all_patches=False):
356 def explain_pushable(self, idx, all_patches=False):
357 write = all_patches and self.ui.write or self.ui.warn
357 write = all_patches and self.ui.write or self.ui.warn
358 if all_patches or self.ui.verbose:
358 if all_patches or self.ui.verbose:
359 if isinstance(idx, str):
359 if isinstance(idx, str):
360 idx = self.series.index(idx)
360 idx = self.series.index(idx)
361 pushable, why = self.pushable(idx)
361 pushable, why = self.pushable(idx)
362 if all_patches and pushable:
362 if all_patches and pushable:
363 if why is None:
363 if why is None:
364 write(_('allowing %s - no guards in effect\n') %
364 write(_('allowing %s - no guards in effect\n') %
365 self.series[idx])
365 self.series[idx])
366 else:
366 else:
367 if not why:
367 if not why:
368 write(_('allowing %s - no matching negative guards\n') %
368 write(_('allowing %s - no matching negative guards\n') %
369 self.series[idx])
369 self.series[idx])
370 else:
370 else:
371 write(_('allowing %s - guarded by %r\n') %
371 write(_('allowing %s - guarded by %r\n') %
372 (self.series[idx], why))
372 (self.series[idx], why))
373 if not pushable:
373 if not pushable:
374 if why:
374 if why:
375 write(_('skipping %s - guarded by %r\n') %
375 write(_('skipping %s - guarded by %r\n') %
376 (self.series[idx], why))
376 (self.series[idx], why))
377 else:
377 else:
378 write(_('skipping %s - no matching guards\n') %
378 write(_('skipping %s - no matching guards\n') %
379 self.series[idx])
379 self.series[idx])
380
380
381 def save_dirty(self):
381 def save_dirty(self):
382 def write_list(items, path):
382 def write_list(items, path):
383 fp = self.opener(path, 'w')
383 fp = self.opener(path, 'w')
384 for i in items:
384 for i in items:
385 fp.write("%s\n" % i)
385 fp.write("%s\n" % i)
386 fp.close()
386 fp.close()
387 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
387 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
388 if self.series_dirty: write_list(self.full_series, self.series_path)
388 if self.series_dirty: write_list(self.full_series, self.series_path)
389 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
389 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
390
390
391 def removeundo(self, repo):
391 def removeundo(self, repo):
392 undo = repo.sjoin('undo')
392 undo = repo.sjoin('undo')
393 if not os.path.exists(undo):
393 if not os.path.exists(undo):
394 return
394 return
395 try:
395 try:
396 os.unlink(undo)
396 os.unlink(undo)
397 except OSError, inst:
397 except OSError, inst:
398 self.ui.warn(_('error removing undo: %s\n') % str(inst))
398 self.ui.warn(_('error removing undo: %s\n') % str(inst))
399
399
400 def printdiff(self, repo, node1, node2=None, files=None,
400 def printdiff(self, repo, node1, node2=None, files=None,
401 fp=None, changes=None, opts={}):
401 fp=None, changes=None, opts={}):
402 m = cmdutil.match(repo, files, opts)
402 m = cmdutil.match(repo, files, opts)
403 chunks = patch.diff(repo, node1, node2, m, changes, self.diffopts())
403 chunks = patch.diff(repo, node1, node2, m, changes, self.diffopts())
404 write = fp is None and repo.ui.write or fp.write
404 write = fp is None and repo.ui.write or fp.write
405 for chunk in chunks:
405 for chunk in chunks:
406 write(chunk)
406 write(chunk)
407
407
408 def mergeone(self, repo, mergeq, head, patch, rev):
408 def mergeone(self, repo, mergeq, head, patch, rev):
409 # first try just applying the patch
409 # first try just applying the patch
410 (err, n) = self.apply(repo, [ patch ], update_status=False,
410 (err, n) = self.apply(repo, [ patch ], update_status=False,
411 strict=True, merge=rev)
411 strict=True, merge=rev)
412
412
413 if err == 0:
413 if err == 0:
414 return (err, n)
414 return (err, n)
415
415
416 if n is None:
416 if n is None:
417 raise util.Abort(_("apply failed for patch %s") % patch)
417 raise util.Abort(_("apply failed for patch %s") % patch)
418
418
419 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
419 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
420
420
421 # apply failed, strip away that rev and merge.
421 # apply failed, strip away that rev and merge.
422 hg.clean(repo, head)
422 hg.clean(repo, head)
423 self.strip(repo, n, update=False, backup='strip')
423 self.strip(repo, n, update=False, backup='strip')
424
424
425 ctx = repo[rev]
425 ctx = repo[rev]
426 ret = hg.merge(repo, rev)
426 ret = hg.merge(repo, rev)
427 if ret:
427 if ret:
428 raise util.Abort(_("update returned %d") % ret)
428 raise util.Abort(_("update returned %d") % ret)
429 n = repo.commit(ctx.description(), ctx.user(), force=True)
429 n = repo.commit(ctx.description(), ctx.user(), force=True)
430 if n is None:
430 if n is None:
431 raise util.Abort(_("repo commit failed"))
431 raise util.Abort(_("repo commit failed"))
432 try:
432 try:
433 ph = patchheader(mergeq.join(patch))
433 ph = patchheader(mergeq.join(patch))
434 except:
434 except:
435 raise util.Abort(_("unable to read %s") % patch)
435 raise util.Abort(_("unable to read %s") % patch)
436
436
437 patchf = self.opener(patch, "w")
437 patchf = self.opener(patch, "w")
438 comments = str(ph)
438 comments = str(ph)
439 if comments:
439 if comments:
440 patchf.write(comments)
440 patchf.write(comments)
441 self.printdiff(repo, head, n, fp=patchf)
441 self.printdiff(repo, head, n, fp=patchf)
442 patchf.close()
442 patchf.close()
443 self.removeundo(repo)
443 self.removeundo(repo)
444 return (0, n)
444 return (0, n)
445
445
446 def qparents(self, repo, rev=None):
446 def qparents(self, repo, rev=None):
447 if rev is None:
447 if rev is None:
448 (p1, p2) = repo.dirstate.parents()
448 (p1, p2) = repo.dirstate.parents()
449 if p2 == nullid:
449 if p2 == nullid:
450 return p1
450 return p1
451 if len(self.applied) == 0:
451 if len(self.applied) == 0:
452 return None
452 return None
453 return bin(self.applied[-1].rev)
453 return bin(self.applied[-1].rev)
454 pp = repo.changelog.parents(rev)
454 pp = repo.changelog.parents(rev)
455 if pp[1] != nullid:
455 if pp[1] != nullid:
456 arevs = [ x.rev for x in self.applied ]
456 arevs = [ x.rev for x in self.applied ]
457 p0 = hex(pp[0])
457 p0 = hex(pp[0])
458 p1 = hex(pp[1])
458 p1 = hex(pp[1])
459 if p0 in arevs:
459 if p0 in arevs:
460 return pp[0]
460 return pp[0]
461 if p1 in arevs:
461 if p1 in arevs:
462 return pp[1]
462 return pp[1]
463 return pp[0]
463 return pp[0]
464
464
465 def mergepatch(self, repo, mergeq, series):
465 def mergepatch(self, repo, mergeq, series):
466 if len(self.applied) == 0:
466 if len(self.applied) == 0:
467 # each of the patches merged in will have two parents. This
467 # each of the patches merged in will have two parents. This
468 # can confuse the qrefresh, qdiff, and strip code because it
468 # can confuse the qrefresh, qdiff, and strip code because it
469 # needs to know which parent is actually in the patch queue.
469 # needs to know which parent is actually in the patch queue.
470 # so, we insert a merge marker with only one parent. This way
470 # so, we insert a merge marker with only one parent. This way
471 # the first patch in the queue is never a merge patch
471 # the first patch in the queue is never a merge patch
472 #
472 #
473 pname = ".hg.patches.merge.marker"
473 pname = ".hg.patches.merge.marker"
474 n = repo.commit('[mq]: merge marker', force=True)
474 n = repo.commit('[mq]: merge marker', force=True)
475 self.removeundo(repo)
475 self.removeundo(repo)
476 self.applied.append(statusentry(hex(n), pname))
476 self.applied.append(statusentry(hex(n), pname))
477 self.applied_dirty = 1
477 self.applied_dirty = 1
478
478
479 head = self.qparents(repo)
479 head = self.qparents(repo)
480
480
481 for patch in series:
481 for patch in series:
482 patch = mergeq.lookup(patch, strict=True)
482 patch = mergeq.lookup(patch, strict=True)
483 if not patch:
483 if not patch:
484 self.ui.warn(_("patch %s does not exist\n") % patch)
484 self.ui.warn(_("patch %s does not exist\n") % patch)
485 return (1, None)
485 return (1, None)
486 pushable, reason = self.pushable(patch)
486 pushable, reason = self.pushable(patch)
487 if not pushable:
487 if not pushable:
488 self.explain_pushable(patch, all_patches=True)
488 self.explain_pushable(patch, all_patches=True)
489 continue
489 continue
490 info = mergeq.isapplied(patch)
490 info = mergeq.isapplied(patch)
491 if not info:
491 if not info:
492 self.ui.warn(_("patch %s is not applied\n") % patch)
492 self.ui.warn(_("patch %s is not applied\n") % patch)
493 return (1, None)
493 return (1, None)
494 rev = bin(info[1])
494 rev = bin(info[1])
495 (err, head) = self.mergeone(repo, mergeq, head, patch, rev)
495 (err, head) = self.mergeone(repo, mergeq, head, patch, rev)
496 if head:
496 if head:
497 self.applied.append(statusentry(hex(head), patch))
497 self.applied.append(statusentry(hex(head), patch))
498 self.applied_dirty = 1
498 self.applied_dirty = 1
499 if err:
499 if err:
500 return (err, head)
500 return (err, head)
501 self.save_dirty()
501 self.save_dirty()
502 return (0, head)
502 return (0, head)
503
503
504 def patch(self, repo, patchfile):
504 def patch(self, repo, patchfile):
505 '''Apply patchfile to the working directory.
505 '''Apply patchfile to the working directory.
506 patchfile: name of patch file'''
506 patchfile: name of patch file'''
507 files = {}
507 files = {}
508 try:
508 try:
509 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
509 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
510 files=files)
510 files=files, eolmode=None)
511 except Exception, inst:
511 except Exception, inst:
512 self.ui.note(str(inst) + '\n')
512 self.ui.note(str(inst) + '\n')
513 if not self.ui.verbose:
513 if not self.ui.verbose:
514 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
514 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
515 return (False, files, False)
515 return (False, files, False)
516
516
517 return (True, files, fuzz)
517 return (True, files, fuzz)
518
518
519 def apply(self, repo, series, list=False, update_status=True,
519 def apply(self, repo, series, list=False, update_status=True,
520 strict=False, patchdir=None, merge=None, all_files={}):
520 strict=False, patchdir=None, merge=None, all_files={}):
521 wlock = lock = tr = None
521 wlock = lock = tr = None
522 try:
522 try:
523 wlock = repo.wlock()
523 wlock = repo.wlock()
524 lock = repo.lock()
524 lock = repo.lock()
525 tr = repo.transaction()
525 tr = repo.transaction()
526 try:
526 try:
527 ret = self._apply(repo, series, list, update_status,
527 ret = self._apply(repo, series, list, update_status,
528 strict, patchdir, merge, all_files=all_files)
528 strict, patchdir, merge, all_files=all_files)
529 tr.close()
529 tr.close()
530 self.save_dirty()
530 self.save_dirty()
531 return ret
531 return ret
532 except:
532 except:
533 try:
533 try:
534 tr.abort()
534 tr.abort()
535 finally:
535 finally:
536 repo.invalidate()
536 repo.invalidate()
537 repo.dirstate.invalidate()
537 repo.dirstate.invalidate()
538 raise
538 raise
539 finally:
539 finally:
540 del tr
540 del tr
541 release(lock, wlock)
541 release(lock, wlock)
542 self.removeundo(repo)
542 self.removeundo(repo)
543
543
544 def _apply(self, repo, series, list=False, update_status=True,
544 def _apply(self, repo, series, list=False, update_status=True,
545 strict=False, patchdir=None, merge=None, all_files={}):
545 strict=False, patchdir=None, merge=None, all_files={}):
546 # TODO unify with commands.py
546 # TODO unify with commands.py
547 if not patchdir:
547 if not patchdir:
548 patchdir = self.path
548 patchdir = self.path
549 err = 0
549 err = 0
550 n = None
550 n = None
551 for patchname in series:
551 for patchname in series:
552 pushable, reason = self.pushable(patchname)
552 pushable, reason = self.pushable(patchname)
553 if not pushable:
553 if not pushable:
554 self.explain_pushable(patchname, all_patches=True)
554 self.explain_pushable(patchname, all_patches=True)
555 continue
555 continue
556 self.ui.warn(_("applying %s\n") % patchname)
556 self.ui.warn(_("applying %s\n") % patchname)
557 pf = os.path.join(patchdir, patchname)
557 pf = os.path.join(patchdir, patchname)
558
558
559 try:
559 try:
560 ph = patchheader(self.join(patchname))
560 ph = patchheader(self.join(patchname))
561 except:
561 except:
562 self.ui.warn(_("Unable to read %s\n") % patchname)
562 self.ui.warn(_("Unable to read %s\n") % patchname)
563 err = 1
563 err = 1
564 break
564 break
565
565
566 message = ph.message
566 message = ph.message
567 if not message:
567 if not message:
568 message = _("imported patch %s\n") % patchname
568 message = _("imported patch %s\n") % patchname
569 else:
569 else:
570 if list:
570 if list:
571 message.append(_("\nimported patch %s") % patchname)
571 message.append(_("\nimported patch %s") % patchname)
572 message = '\n'.join(message)
572 message = '\n'.join(message)
573
573
574 if ph.haspatch:
574 if ph.haspatch:
575 (patcherr, files, fuzz) = self.patch(repo, pf)
575 (patcherr, files, fuzz) = self.patch(repo, pf)
576 all_files.update(files)
576 all_files.update(files)
577 patcherr = not patcherr
577 patcherr = not patcherr
578 else:
578 else:
579 self.ui.warn(_("patch %s is empty\n") % patchname)
579 self.ui.warn(_("patch %s is empty\n") % patchname)
580 patcherr, files, fuzz = 0, [], 0
580 patcherr, files, fuzz = 0, [], 0
581
581
582 if merge and files:
582 if merge and files:
583 # Mark as removed/merged and update dirstate parent info
583 # Mark as removed/merged and update dirstate parent info
584 removed = []
584 removed = []
585 merged = []
585 merged = []
586 for f in files:
586 for f in files:
587 if os.path.exists(repo.wjoin(f)):
587 if os.path.exists(repo.wjoin(f)):
588 merged.append(f)
588 merged.append(f)
589 else:
589 else:
590 removed.append(f)
590 removed.append(f)
591 for f in removed:
591 for f in removed:
592 repo.dirstate.remove(f)
592 repo.dirstate.remove(f)
593 for f in merged:
593 for f in merged:
594 repo.dirstate.merge(f)
594 repo.dirstate.merge(f)
595 p1, p2 = repo.dirstate.parents()
595 p1, p2 = repo.dirstate.parents()
596 repo.dirstate.setparents(p1, merge)
596 repo.dirstate.setparents(p1, merge)
597
597
598 files = patch.updatedir(self.ui, repo, files)
598 files = patch.updatedir(self.ui, repo, files)
599 match = cmdutil.matchfiles(repo, files or [])
599 match = cmdutil.matchfiles(repo, files or [])
600 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
600 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
601
601
602 if n is None:
602 if n is None:
603 raise util.Abort(_("repo commit failed"))
603 raise util.Abort(_("repo commit failed"))
604
604
605 if update_status:
605 if update_status:
606 self.applied.append(statusentry(hex(n), patchname))
606 self.applied.append(statusentry(hex(n), patchname))
607
607
608 if patcherr:
608 if patcherr:
609 self.ui.warn(_("patch failed, rejects left in working dir\n"))
609 self.ui.warn(_("patch failed, rejects left in working dir\n"))
610 err = 1
610 err = 1
611 break
611 break
612
612
613 if fuzz and strict:
613 if fuzz and strict:
614 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
614 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
615 err = 1
615 err = 1
616 break
616 break
617 return (err, n)
617 return (err, n)
618
618
619 def _clean_series(self, patches):
619 def _clean_series(self, patches):
620 for i in sorted([self.find_series(p) for p in patches], reverse=True):
620 for i in sorted([self.find_series(p) for p in patches], reverse=True):
621 del self.full_series[i]
621 del self.full_series[i]
622 self.parse_series()
622 self.parse_series()
623 self.series_dirty = 1
623 self.series_dirty = 1
624
624
625 def finish(self, repo, revs):
625 def finish(self, repo, revs):
626 firstrev = repo[self.applied[0].rev].rev()
626 firstrev = repo[self.applied[0].rev].rev()
627 appliedbase = 0
627 appliedbase = 0
628 patches = []
628 patches = []
629 for rev in sorted(revs):
629 for rev in sorted(revs):
630 if rev < firstrev:
630 if rev < firstrev:
631 raise util.Abort(_('revision %d is not managed') % rev)
631 raise util.Abort(_('revision %d is not managed') % rev)
632 base = bin(self.applied[appliedbase].rev)
632 base = bin(self.applied[appliedbase].rev)
633 node = repo.changelog.node(rev)
633 node = repo.changelog.node(rev)
634 if node != base:
634 if node != base:
635 raise util.Abort(_('cannot delete revision %d above '
635 raise util.Abort(_('cannot delete revision %d above '
636 'applied patches') % rev)
636 'applied patches') % rev)
637 patches.append(self.applied[appliedbase].name)
637 patches.append(self.applied[appliedbase].name)
638 appliedbase += 1
638 appliedbase += 1
639
639
640 r = self.qrepo()
640 r = self.qrepo()
641 if r:
641 if r:
642 r.remove(patches, True)
642 r.remove(patches, True)
643 else:
643 else:
644 for p in patches:
644 for p in patches:
645 os.unlink(self.join(p))
645 os.unlink(self.join(p))
646
646
647 del self.applied[:appliedbase]
647 del self.applied[:appliedbase]
648 self.applied_dirty = 1
648 self.applied_dirty = 1
649 self._clean_series(patches)
649 self._clean_series(patches)
650
650
651 def delete(self, repo, patches, opts):
651 def delete(self, repo, patches, opts):
652 if not patches and not opts.get('rev'):
652 if not patches and not opts.get('rev'):
653 raise util.Abort(_('qdelete requires at least one revision or '
653 raise util.Abort(_('qdelete requires at least one revision or '
654 'patch name'))
654 'patch name'))
655
655
656 realpatches = []
656 realpatches = []
657 for patch in patches:
657 for patch in patches:
658 patch = self.lookup(patch, strict=True)
658 patch = self.lookup(patch, strict=True)
659 info = self.isapplied(patch)
659 info = self.isapplied(patch)
660 if info:
660 if info:
661 raise util.Abort(_("cannot delete applied patch %s") % patch)
661 raise util.Abort(_("cannot delete applied patch %s") % patch)
662 if patch not in self.series:
662 if patch not in self.series:
663 raise util.Abort(_("patch %s not in series file") % patch)
663 raise util.Abort(_("patch %s not in series file") % patch)
664 realpatches.append(patch)
664 realpatches.append(patch)
665
665
666 appliedbase = 0
666 appliedbase = 0
667 if opts.get('rev'):
667 if opts.get('rev'):
668 if not self.applied:
668 if not self.applied:
669 raise util.Abort(_('no patches applied'))
669 raise util.Abort(_('no patches applied'))
670 revs = cmdutil.revrange(repo, opts['rev'])
670 revs = cmdutil.revrange(repo, opts['rev'])
671 if len(revs) > 1 and revs[0] > revs[1]:
671 if len(revs) > 1 and revs[0] > revs[1]:
672 revs.reverse()
672 revs.reverse()
673 for rev in revs:
673 for rev in revs:
674 if appliedbase >= len(self.applied):
674 if appliedbase >= len(self.applied):
675 raise util.Abort(_("revision %d is not managed") % rev)
675 raise util.Abort(_("revision %d is not managed") % rev)
676
676
677 base = bin(self.applied[appliedbase].rev)
677 base = bin(self.applied[appliedbase].rev)
678 node = repo.changelog.node(rev)
678 node = repo.changelog.node(rev)
679 if node != base:
679 if node != base:
680 raise util.Abort(_("cannot delete revision %d above "
680 raise util.Abort(_("cannot delete revision %d above "
681 "applied patches") % rev)
681 "applied patches") % rev)
682 realpatches.append(self.applied[appliedbase].name)
682 realpatches.append(self.applied[appliedbase].name)
683 appliedbase += 1
683 appliedbase += 1
684
684
685 if not opts.get('keep'):
685 if not opts.get('keep'):
686 r = self.qrepo()
686 r = self.qrepo()
687 if r:
687 if r:
688 r.remove(realpatches, True)
688 r.remove(realpatches, True)
689 else:
689 else:
690 for p in realpatches:
690 for p in realpatches:
691 os.unlink(self.join(p))
691 os.unlink(self.join(p))
692
692
693 if appliedbase:
693 if appliedbase:
694 del self.applied[:appliedbase]
694 del self.applied[:appliedbase]
695 self.applied_dirty = 1
695 self.applied_dirty = 1
696 self._clean_series(realpatches)
696 self._clean_series(realpatches)
697
697
698 def check_toppatch(self, repo):
698 def check_toppatch(self, repo):
699 if len(self.applied) > 0:
699 if len(self.applied) > 0:
700 top = bin(self.applied[-1].rev)
700 top = bin(self.applied[-1].rev)
701 pp = repo.dirstate.parents()
701 pp = repo.dirstate.parents()
702 if top not in pp:
702 if top not in pp:
703 raise util.Abort(_("working directory revision is not qtip"))
703 raise util.Abort(_("working directory revision is not qtip"))
704 return top
704 return top
705 return None
705 return None
706 def check_localchanges(self, repo, force=False, refresh=True):
706 def check_localchanges(self, repo, force=False, refresh=True):
707 m, a, r, d = repo.status()[:4]
707 m, a, r, d = repo.status()[:4]
708 if m or a or r or d:
708 if m or a or r or d:
709 if not force:
709 if not force:
710 if refresh:
710 if refresh:
711 raise util.Abort(_("local changes found, refresh first"))
711 raise util.Abort(_("local changes found, refresh first"))
712 else:
712 else:
713 raise util.Abort(_("local changes found"))
713 raise util.Abort(_("local changes found"))
714 return m, a, r, d
714 return m, a, r, d
715
715
716 _reserved = ('series', 'status', 'guards')
716 _reserved = ('series', 'status', 'guards')
717 def check_reserved_name(self, name):
717 def check_reserved_name(self, name):
718 if (name in self._reserved or name.startswith('.hg')
718 if (name in self._reserved or name.startswith('.hg')
719 or name.startswith('.mq')):
719 or name.startswith('.mq')):
720 raise util.Abort(_('"%s" cannot be used as the name of a patch')
720 raise util.Abort(_('"%s" cannot be used as the name of a patch')
721 % name)
721 % name)
722
722
723 def new(self, repo, patchfn, *pats, **opts):
723 def new(self, repo, patchfn, *pats, **opts):
724 """options:
724 """options:
725 msg: a string or a no-argument function returning a string
725 msg: a string or a no-argument function returning a string
726 """
726 """
727 msg = opts.get('msg')
727 msg = opts.get('msg')
728 force = opts.get('force')
728 force = opts.get('force')
729 user = opts.get('user')
729 user = opts.get('user')
730 date = opts.get('date')
730 date = opts.get('date')
731 if date:
731 if date:
732 date = util.parsedate(date)
732 date = util.parsedate(date)
733 self.check_reserved_name(patchfn)
733 self.check_reserved_name(patchfn)
734 if os.path.exists(self.join(patchfn)):
734 if os.path.exists(self.join(patchfn)):
735 raise util.Abort(_('patch "%s" already exists') % patchfn)
735 raise util.Abort(_('patch "%s" already exists') % patchfn)
736 if opts.get('include') or opts.get('exclude') or pats:
736 if opts.get('include') or opts.get('exclude') or pats:
737 match = cmdutil.match(repo, pats, opts)
737 match = cmdutil.match(repo, pats, opts)
738 # detect missing files in pats
738 # detect missing files in pats
739 def badfn(f, msg):
739 def badfn(f, msg):
740 raise util.Abort('%s: %s' % (f, msg))
740 raise util.Abort('%s: %s' % (f, msg))
741 match.bad = badfn
741 match.bad = badfn
742 m, a, r, d = repo.status(match=match)[:4]
742 m, a, r, d = repo.status(match=match)[:4]
743 else:
743 else:
744 m, a, r, d = self.check_localchanges(repo, force)
744 m, a, r, d = self.check_localchanges(repo, force)
745 match = cmdutil.matchfiles(repo, m + a + r)
745 match = cmdutil.matchfiles(repo, m + a + r)
746 commitfiles = m + a + r
746 commitfiles = m + a + r
747 self.check_toppatch(repo)
747 self.check_toppatch(repo)
748 insert = self.full_series_end()
748 insert = self.full_series_end()
749 wlock = repo.wlock()
749 wlock = repo.wlock()
750 try:
750 try:
751 # if patch file write fails, abort early
751 # if patch file write fails, abort early
752 p = self.opener(patchfn, "w")
752 p = self.opener(patchfn, "w")
753 try:
753 try:
754 if date:
754 if date:
755 p.write("# HG changeset patch\n")
755 p.write("# HG changeset patch\n")
756 if user:
756 if user:
757 p.write("# User " + user + "\n")
757 p.write("# User " + user + "\n")
758 p.write("# Date %d %d\n\n" % date)
758 p.write("# Date %d %d\n\n" % date)
759 elif user:
759 elif user:
760 p.write("From: " + user + "\n\n")
760 p.write("From: " + user + "\n\n")
761
761
762 if hasattr(msg, '__call__'):
762 if hasattr(msg, '__call__'):
763 msg = msg()
763 msg = msg()
764 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
764 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
765 n = repo.commit(commitmsg, user, date, match=match, force=True)
765 n = repo.commit(commitmsg, user, date, match=match, force=True)
766 if n is None:
766 if n is None:
767 raise util.Abort(_("repo commit failed"))
767 raise util.Abort(_("repo commit failed"))
768 try:
768 try:
769 self.full_series[insert:insert] = [patchfn]
769 self.full_series[insert:insert] = [patchfn]
770 self.applied.append(statusentry(hex(n), patchfn))
770 self.applied.append(statusentry(hex(n), patchfn))
771 self.parse_series()
771 self.parse_series()
772 self.series_dirty = 1
772 self.series_dirty = 1
773 self.applied_dirty = 1
773 self.applied_dirty = 1
774 if msg:
774 if msg:
775 msg = msg + "\n\n"
775 msg = msg + "\n\n"
776 p.write(msg)
776 p.write(msg)
777 if commitfiles:
777 if commitfiles:
778 diffopts = self.diffopts()
778 diffopts = self.diffopts()
779 if opts.get('git'): diffopts.git = True
779 if opts.get('git'): diffopts.git = True
780 parent = self.qparents(repo, n)
780 parent = self.qparents(repo, n)
781 chunks = patch.diff(repo, node1=parent, node2=n,
781 chunks = patch.diff(repo, node1=parent, node2=n,
782 match=match, opts=diffopts)
782 match=match, opts=diffopts)
783 for chunk in chunks:
783 for chunk in chunks:
784 p.write(chunk)
784 p.write(chunk)
785 p.close()
785 p.close()
786 wlock.release()
786 wlock.release()
787 wlock = None
787 wlock = None
788 r = self.qrepo()
788 r = self.qrepo()
789 if r: r.add([patchfn])
789 if r: r.add([patchfn])
790 except:
790 except:
791 repo.rollback()
791 repo.rollback()
792 raise
792 raise
793 except Exception:
793 except Exception:
794 patchpath = self.join(patchfn)
794 patchpath = self.join(patchfn)
795 try:
795 try:
796 os.unlink(patchpath)
796 os.unlink(patchpath)
797 except:
797 except:
798 self.ui.warn(_('error unlinking %s\n') % patchpath)
798 self.ui.warn(_('error unlinking %s\n') % patchpath)
799 raise
799 raise
800 self.removeundo(repo)
800 self.removeundo(repo)
801 finally:
801 finally:
802 release(wlock)
802 release(wlock)
803
803
804 def strip(self, repo, rev, update=True, backup="all", force=None):
804 def strip(self, repo, rev, update=True, backup="all", force=None):
805 wlock = lock = None
805 wlock = lock = None
806 try:
806 try:
807 wlock = repo.wlock()
807 wlock = repo.wlock()
808 lock = repo.lock()
808 lock = repo.lock()
809
809
810 if update:
810 if update:
811 self.check_localchanges(repo, force=force, refresh=False)
811 self.check_localchanges(repo, force=force, refresh=False)
812 urev = self.qparents(repo, rev)
812 urev = self.qparents(repo, rev)
813 hg.clean(repo, urev)
813 hg.clean(repo, urev)
814 repo.dirstate.write()
814 repo.dirstate.write()
815
815
816 self.removeundo(repo)
816 self.removeundo(repo)
817 repair.strip(self.ui, repo, rev, backup)
817 repair.strip(self.ui, repo, rev, backup)
818 # strip may have unbundled a set of backed up revisions after
818 # strip may have unbundled a set of backed up revisions after
819 # the actual strip
819 # the actual strip
820 self.removeundo(repo)
820 self.removeundo(repo)
821 finally:
821 finally:
822 release(lock, wlock)
822 release(lock, wlock)
823
823
824 def isapplied(self, patch):
824 def isapplied(self, patch):
825 """returns (index, rev, patch)"""
825 """returns (index, rev, patch)"""
826 for i, a in enumerate(self.applied):
826 for i, a in enumerate(self.applied):
827 if a.name == patch:
827 if a.name == patch:
828 return (i, a.rev, a.name)
828 return (i, a.rev, a.name)
829 return None
829 return None
830
830
831 # if the exact patch name does not exist, we try a few
831 # if the exact patch name does not exist, we try a few
832 # variations. If strict is passed, we try only #1
832 # variations. If strict is passed, we try only #1
833 #
833 #
834 # 1) a number to indicate an offset in the series file
834 # 1) a number to indicate an offset in the series file
835 # 2) a unique substring of the patch name was given
835 # 2) a unique substring of the patch name was given
836 # 3) patchname[-+]num to indicate an offset in the series file
836 # 3) patchname[-+]num to indicate an offset in the series file
837 def lookup(self, patch, strict=False):
837 def lookup(self, patch, strict=False):
838 patch = patch and str(patch)
838 patch = patch and str(patch)
839
839
840 def partial_name(s):
840 def partial_name(s):
841 if s in self.series:
841 if s in self.series:
842 return s
842 return s
843 matches = [x for x in self.series if s in x]
843 matches = [x for x in self.series if s in x]
844 if len(matches) > 1:
844 if len(matches) > 1:
845 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
845 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
846 for m in matches:
846 for m in matches:
847 self.ui.warn(' %s\n' % m)
847 self.ui.warn(' %s\n' % m)
848 return None
848 return None
849 if matches:
849 if matches:
850 return matches[0]
850 return matches[0]
851 if len(self.series) > 0 and len(self.applied) > 0:
851 if len(self.series) > 0 and len(self.applied) > 0:
852 if s == 'qtip':
852 if s == 'qtip':
853 return self.series[self.series_end(True)-1]
853 return self.series[self.series_end(True)-1]
854 if s == 'qbase':
854 if s == 'qbase':
855 return self.series[0]
855 return self.series[0]
856 return None
856 return None
857
857
858 if patch is None:
858 if patch is None:
859 return None
859 return None
860 if patch in self.series:
860 if patch in self.series:
861 return patch
861 return patch
862
862
863 if not os.path.isfile(self.join(patch)):
863 if not os.path.isfile(self.join(patch)):
864 try:
864 try:
865 sno = int(patch)
865 sno = int(patch)
866 except(ValueError, OverflowError):
866 except(ValueError, OverflowError):
867 pass
867 pass
868 else:
868 else:
869 if -len(self.series) <= sno < len(self.series):
869 if -len(self.series) <= sno < len(self.series):
870 return self.series[sno]
870 return self.series[sno]
871
871
872 if not strict:
872 if not strict:
873 res = partial_name(patch)
873 res = partial_name(patch)
874 if res:
874 if res:
875 return res
875 return res
876 minus = patch.rfind('-')
876 minus = patch.rfind('-')
877 if minus >= 0:
877 if minus >= 0:
878 res = partial_name(patch[:minus])
878 res = partial_name(patch[:minus])
879 if res:
879 if res:
880 i = self.series.index(res)
880 i = self.series.index(res)
881 try:
881 try:
882 off = int(patch[minus+1:] or 1)
882 off = int(patch[minus+1:] or 1)
883 except(ValueError, OverflowError):
883 except(ValueError, OverflowError):
884 pass
884 pass
885 else:
885 else:
886 if i - off >= 0:
886 if i - off >= 0:
887 return self.series[i - off]
887 return self.series[i - off]
888 plus = patch.rfind('+')
888 plus = patch.rfind('+')
889 if plus >= 0:
889 if plus >= 0:
890 res = partial_name(patch[:plus])
890 res = partial_name(patch[:plus])
891 if res:
891 if res:
892 i = self.series.index(res)
892 i = self.series.index(res)
893 try:
893 try:
894 off = int(patch[plus+1:] or 1)
894 off = int(patch[plus+1:] or 1)
895 except(ValueError, OverflowError):
895 except(ValueError, OverflowError):
896 pass
896 pass
897 else:
897 else:
898 if i + off < len(self.series):
898 if i + off < len(self.series):
899 return self.series[i + off]
899 return self.series[i + off]
900 raise util.Abort(_("patch %s not in series") % patch)
900 raise util.Abort(_("patch %s not in series") % patch)
901
901
902 def push(self, repo, patch=None, force=False, list=False,
902 def push(self, repo, patch=None, force=False, list=False,
903 mergeq=None, all=False):
903 mergeq=None, all=False):
904 wlock = repo.wlock()
904 wlock = repo.wlock()
905 try:
905 try:
906 if repo.dirstate.parents()[0] not in repo.heads():
906 if repo.dirstate.parents()[0] not in repo.heads():
907 self.ui.status(_("(working directory not at a head)\n"))
907 self.ui.status(_("(working directory not at a head)\n"))
908
908
909 if not self.series:
909 if not self.series:
910 self.ui.warn(_('no patches in series\n'))
910 self.ui.warn(_('no patches in series\n'))
911 return 0
911 return 0
912
912
913 patch = self.lookup(patch)
913 patch = self.lookup(patch)
914 # Suppose our series file is: A B C and the current 'top'
914 # Suppose our series file is: A B C and the current 'top'
915 # patch is B. qpush C should be performed (moving forward)
915 # patch is B. qpush C should be performed (moving forward)
916 # qpush B is a NOP (no change) qpush A is an error (can't
916 # qpush B is a NOP (no change) qpush A is an error (can't
917 # go backwards with qpush)
917 # go backwards with qpush)
918 if patch:
918 if patch:
919 info = self.isapplied(patch)
919 info = self.isapplied(patch)
920 if info:
920 if info:
921 if info[0] < len(self.applied) - 1:
921 if info[0] < len(self.applied) - 1:
922 raise util.Abort(
922 raise util.Abort(
923 _("cannot push to a previous patch: %s") % patch)
923 _("cannot push to a previous patch: %s") % patch)
924 self.ui.warn(
924 self.ui.warn(
925 _('qpush: %s is already at the top\n') % patch)
925 _('qpush: %s is already at the top\n') % patch)
926 return
926 return
927 pushable, reason = self.pushable(patch)
927 pushable, reason = self.pushable(patch)
928 if not pushable:
928 if not pushable:
929 if reason:
929 if reason:
930 reason = _('guarded by %r') % reason
930 reason = _('guarded by %r') % reason
931 else:
931 else:
932 reason = _('no matching guards')
932 reason = _('no matching guards')
933 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
933 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
934 return 1
934 return 1
935 elif all:
935 elif all:
936 patch = self.series[-1]
936 patch = self.series[-1]
937 if self.isapplied(patch):
937 if self.isapplied(patch):
938 self.ui.warn(_('all patches are currently applied\n'))
938 self.ui.warn(_('all patches are currently applied\n'))
939 return 0
939 return 0
940
940
941 # Following the above example, starting at 'top' of B:
941 # Following the above example, starting at 'top' of B:
942 # qpush should be performed (pushes C), but a subsequent
942 # qpush should be performed (pushes C), but a subsequent
943 # qpush without an argument is an error (nothing to
943 # qpush without an argument is an error (nothing to
944 # apply). This allows a loop of "...while hg qpush..." to
944 # apply). This allows a loop of "...while hg qpush..." to
945 # work as it detects an error when done
945 # work as it detects an error when done
946 start = self.series_end()
946 start = self.series_end()
947 if start == len(self.series):
947 if start == len(self.series):
948 self.ui.warn(_('patch series already fully applied\n'))
948 self.ui.warn(_('patch series already fully applied\n'))
949 return 1
949 return 1
950 if not force:
950 if not force:
951 self.check_localchanges(repo)
951 self.check_localchanges(repo)
952
952
953 self.applied_dirty = 1
953 self.applied_dirty = 1
954 if start > 0:
954 if start > 0:
955 self.check_toppatch(repo)
955 self.check_toppatch(repo)
956 if not patch:
956 if not patch:
957 patch = self.series[start]
957 patch = self.series[start]
958 end = start + 1
958 end = start + 1
959 else:
959 else:
960 end = self.series.index(patch, start) + 1
960 end = self.series.index(patch, start) + 1
961 s = self.series[start:end]
961 s = self.series[start:end]
962 all_files = {}
962 all_files = {}
963 try:
963 try:
964 if mergeq:
964 if mergeq:
965 ret = self.mergepatch(repo, mergeq, s)
965 ret = self.mergepatch(repo, mergeq, s)
966 else:
966 else:
967 ret = self.apply(repo, s, list, all_files=all_files)
967 ret = self.apply(repo, s, list, all_files=all_files)
968 except:
968 except:
969 self.ui.warn(_('cleaning up working directory...'))
969 self.ui.warn(_('cleaning up working directory...'))
970 node = repo.dirstate.parents()[0]
970 node = repo.dirstate.parents()[0]
971 hg.revert(repo, node, None)
971 hg.revert(repo, node, None)
972 unknown = repo.status(unknown=True)[4]
972 unknown = repo.status(unknown=True)[4]
973 # only remove unknown files that we know we touched or
973 # only remove unknown files that we know we touched or
974 # created while patching
974 # created while patching
975 for f in unknown:
975 for f in unknown:
976 if f in all_files:
976 if f in all_files:
977 util.unlink(repo.wjoin(f))
977 util.unlink(repo.wjoin(f))
978 self.ui.warn(_('done\n'))
978 self.ui.warn(_('done\n'))
979 raise
979 raise
980 top = self.applied[-1].name
980 top = self.applied[-1].name
981 if ret[0]:
981 if ret[0]:
982 self.ui.write(_("errors during apply, please fix and "
982 self.ui.write(_("errors during apply, please fix and "
983 "refresh %s\n") % top)
983 "refresh %s\n") % top)
984 else:
984 else:
985 self.ui.write(_("now at: %s\n") % top)
985 self.ui.write(_("now at: %s\n") % top)
986 return ret[0]
986 return ret[0]
987 finally:
987 finally:
988 wlock.release()
988 wlock.release()
989
989
990 def pop(self, repo, patch=None, force=False, update=True, all=False):
990 def pop(self, repo, patch=None, force=False, update=True, all=False):
991 def getfile(f, rev, flags):
991 def getfile(f, rev, flags):
992 t = repo.file(f).read(rev)
992 t = repo.file(f).read(rev)
993 repo.wwrite(f, t, flags)
993 repo.wwrite(f, t, flags)
994
994
995 wlock = repo.wlock()
995 wlock = repo.wlock()
996 try:
996 try:
997 if patch:
997 if patch:
998 # index, rev, patch
998 # index, rev, patch
999 info = self.isapplied(patch)
999 info = self.isapplied(patch)
1000 if not info:
1000 if not info:
1001 patch = self.lookup(patch)
1001 patch = self.lookup(patch)
1002 info = self.isapplied(patch)
1002 info = self.isapplied(patch)
1003 if not info:
1003 if not info:
1004 raise util.Abort(_("patch %s is not applied") % patch)
1004 raise util.Abort(_("patch %s is not applied") % patch)
1005
1005
1006 if len(self.applied) == 0:
1006 if len(self.applied) == 0:
1007 # Allow qpop -a to work repeatedly,
1007 # Allow qpop -a to work repeatedly,
1008 # but not qpop without an argument
1008 # but not qpop without an argument
1009 self.ui.warn(_("no patches applied\n"))
1009 self.ui.warn(_("no patches applied\n"))
1010 return not all
1010 return not all
1011
1011
1012 if all:
1012 if all:
1013 start = 0
1013 start = 0
1014 elif patch:
1014 elif patch:
1015 start = info[0] + 1
1015 start = info[0] + 1
1016 else:
1016 else:
1017 start = len(self.applied) - 1
1017 start = len(self.applied) - 1
1018
1018
1019 if start >= len(self.applied):
1019 if start >= len(self.applied):
1020 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1020 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1021 return
1021 return
1022
1022
1023 if not update:
1023 if not update:
1024 parents = repo.dirstate.parents()
1024 parents = repo.dirstate.parents()
1025 rr = [ bin(x.rev) for x in self.applied ]
1025 rr = [ bin(x.rev) for x in self.applied ]
1026 for p in parents:
1026 for p in parents:
1027 if p in rr:
1027 if p in rr:
1028 self.ui.warn(_("qpop: forcing dirstate update\n"))
1028 self.ui.warn(_("qpop: forcing dirstate update\n"))
1029 update = True
1029 update = True
1030 else:
1030 else:
1031 parents = [p.hex() for p in repo[None].parents()]
1031 parents = [p.hex() for p in repo[None].parents()]
1032 needupdate = False
1032 needupdate = False
1033 for entry in self.applied[start:]:
1033 for entry in self.applied[start:]:
1034 if entry.rev in parents:
1034 if entry.rev in parents:
1035 needupdate = True
1035 needupdate = True
1036 break
1036 break
1037 update = needupdate
1037 update = needupdate
1038
1038
1039 if not force and update:
1039 if not force and update:
1040 self.check_localchanges(repo)
1040 self.check_localchanges(repo)
1041
1041
1042 self.applied_dirty = 1
1042 self.applied_dirty = 1
1043 end = len(self.applied)
1043 end = len(self.applied)
1044 rev = bin(self.applied[start].rev)
1044 rev = bin(self.applied[start].rev)
1045 if update:
1045 if update:
1046 top = self.check_toppatch(repo)
1046 top = self.check_toppatch(repo)
1047
1047
1048 try:
1048 try:
1049 heads = repo.changelog.heads(rev)
1049 heads = repo.changelog.heads(rev)
1050 except error.LookupError:
1050 except error.LookupError:
1051 node = short(rev)
1051 node = short(rev)
1052 raise util.Abort(_('trying to pop unknown node %s') % node)
1052 raise util.Abort(_('trying to pop unknown node %s') % node)
1053
1053
1054 if heads != [bin(self.applied[-1].rev)]:
1054 if heads != [bin(self.applied[-1].rev)]:
1055 raise util.Abort(_("popping would remove a revision not "
1055 raise util.Abort(_("popping would remove a revision not "
1056 "managed by this patch queue"))
1056 "managed by this patch queue"))
1057
1057
1058 # we know there are no local changes, so we can make a simplified
1058 # we know there are no local changes, so we can make a simplified
1059 # form of hg.update.
1059 # form of hg.update.
1060 if update:
1060 if update:
1061 qp = self.qparents(repo, rev)
1061 qp = self.qparents(repo, rev)
1062 changes = repo.changelog.read(qp)
1062 changes = repo.changelog.read(qp)
1063 mmap = repo.manifest.read(changes[0])
1063 mmap = repo.manifest.read(changes[0])
1064 m, a, r, d = repo.status(qp, top)[:4]
1064 m, a, r, d = repo.status(qp, top)[:4]
1065 if d:
1065 if d:
1066 raise util.Abort(_("deletions found between repo revs"))
1066 raise util.Abort(_("deletions found between repo revs"))
1067 for f in m:
1067 for f in m:
1068 getfile(f, mmap[f], mmap.flags(f))
1068 getfile(f, mmap[f], mmap.flags(f))
1069 for f in r:
1069 for f in r:
1070 getfile(f, mmap[f], mmap.flags(f))
1070 getfile(f, mmap[f], mmap.flags(f))
1071 for f in m + r:
1071 for f in m + r:
1072 repo.dirstate.normal(f)
1072 repo.dirstate.normal(f)
1073 for f in a:
1073 for f in a:
1074 try:
1074 try:
1075 os.unlink(repo.wjoin(f))
1075 os.unlink(repo.wjoin(f))
1076 except OSError, e:
1076 except OSError, e:
1077 if e.errno != errno.ENOENT:
1077 if e.errno != errno.ENOENT:
1078 raise
1078 raise
1079 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
1079 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
1080 except: pass
1080 except: pass
1081 repo.dirstate.forget(f)
1081 repo.dirstate.forget(f)
1082 repo.dirstate.setparents(qp, nullid)
1082 repo.dirstate.setparents(qp, nullid)
1083 del self.applied[start:end]
1083 del self.applied[start:end]
1084 self.strip(repo, rev, update=False, backup='strip')
1084 self.strip(repo, rev, update=False, backup='strip')
1085 if len(self.applied):
1085 if len(self.applied):
1086 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1086 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1087 else:
1087 else:
1088 self.ui.write(_("patch queue now empty\n"))
1088 self.ui.write(_("patch queue now empty\n"))
1089 finally:
1089 finally:
1090 wlock.release()
1090 wlock.release()
1091
1091
1092 def diff(self, repo, pats, opts):
1092 def diff(self, repo, pats, opts):
1093 top = self.check_toppatch(repo)
1093 top = self.check_toppatch(repo)
1094 if not top:
1094 if not top:
1095 self.ui.write(_("no patches applied\n"))
1095 self.ui.write(_("no patches applied\n"))
1096 return
1096 return
1097 qp = self.qparents(repo, top)
1097 qp = self.qparents(repo, top)
1098 self._diffopts = patch.diffopts(self.ui, opts)
1098 self._diffopts = patch.diffopts(self.ui, opts)
1099 self.printdiff(repo, qp, files=pats, opts=opts)
1099 self.printdiff(repo, qp, files=pats, opts=opts)
1100
1100
1101 def refresh(self, repo, pats=None, **opts):
1101 def refresh(self, repo, pats=None, **opts):
1102 if len(self.applied) == 0:
1102 if len(self.applied) == 0:
1103 self.ui.write(_("no patches applied\n"))
1103 self.ui.write(_("no patches applied\n"))
1104 return 1
1104 return 1
1105 msg = opts.get('msg', '').rstrip()
1105 msg = opts.get('msg', '').rstrip()
1106 newuser = opts.get('user')
1106 newuser = opts.get('user')
1107 newdate = opts.get('date')
1107 newdate = opts.get('date')
1108 if newdate:
1108 if newdate:
1109 newdate = '%d %d' % util.parsedate(newdate)
1109 newdate = '%d %d' % util.parsedate(newdate)
1110 wlock = repo.wlock()
1110 wlock = repo.wlock()
1111 try:
1111 try:
1112 self.check_toppatch(repo)
1112 self.check_toppatch(repo)
1113 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
1113 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
1114 top = bin(top)
1114 top = bin(top)
1115 if repo.changelog.heads(top) != [top]:
1115 if repo.changelog.heads(top) != [top]:
1116 raise util.Abort(_("cannot refresh a revision with children"))
1116 raise util.Abort(_("cannot refresh a revision with children"))
1117 cparents = repo.changelog.parents(top)
1117 cparents = repo.changelog.parents(top)
1118 patchparent = self.qparents(repo, top)
1118 patchparent = self.qparents(repo, top)
1119 ph = patchheader(self.join(patchfn))
1119 ph = patchheader(self.join(patchfn))
1120
1120
1121 patchf = self.opener(patchfn, 'r')
1121 patchf = self.opener(patchfn, 'r')
1122
1122
1123 # if the patch was a git patch, refresh it as a git patch
1123 # if the patch was a git patch, refresh it as a git patch
1124 for line in patchf:
1124 for line in patchf:
1125 if line.startswith('diff --git'):
1125 if line.startswith('diff --git'):
1126 self.diffopts().git = True
1126 self.diffopts().git = True
1127 break
1127 break
1128
1128
1129 if msg:
1129 if msg:
1130 ph.setmessage(msg)
1130 ph.setmessage(msg)
1131 if newuser:
1131 if newuser:
1132 ph.setuser(newuser)
1132 ph.setuser(newuser)
1133 if newdate:
1133 if newdate:
1134 ph.setdate(newdate)
1134 ph.setdate(newdate)
1135
1135
1136 # only commit new patch when write is complete
1136 # only commit new patch when write is complete
1137 patchf = self.opener(patchfn, 'w', atomictemp=True)
1137 patchf = self.opener(patchfn, 'w', atomictemp=True)
1138
1138
1139 patchf.seek(0)
1139 patchf.seek(0)
1140 patchf.truncate()
1140 patchf.truncate()
1141
1141
1142 comments = str(ph)
1142 comments = str(ph)
1143 if comments:
1143 if comments:
1144 patchf.write(comments)
1144 patchf.write(comments)
1145
1145
1146 if opts.get('git'):
1146 if opts.get('git'):
1147 self.diffopts().git = True
1147 self.diffopts().git = True
1148 tip = repo.changelog.tip()
1148 tip = repo.changelog.tip()
1149 if top == tip:
1149 if top == tip:
1150 # if the top of our patch queue is also the tip, there is an
1150 # if the top of our patch queue is also the tip, there is an
1151 # optimization here. We update the dirstate in place and strip
1151 # optimization here. We update the dirstate in place and strip
1152 # off the tip commit. Then just commit the current directory
1152 # off the tip commit. Then just commit the current directory
1153 # tree. We can also send repo.commit the list of files
1153 # tree. We can also send repo.commit the list of files
1154 # changed to speed up the diff
1154 # changed to speed up the diff
1155 #
1155 #
1156 # in short mode, we only diff the files included in the
1156 # in short mode, we only diff the files included in the
1157 # patch already plus specified files
1157 # patch already plus specified files
1158 #
1158 #
1159 # this should really read:
1159 # this should really read:
1160 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1160 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1161 # but we do it backwards to take advantage of manifest/chlog
1161 # but we do it backwards to take advantage of manifest/chlog
1162 # caching against the next repo.status call
1162 # caching against the next repo.status call
1163 #
1163 #
1164 mm, aa, dd, aa2 = repo.status(patchparent, tip)[:4]
1164 mm, aa, dd, aa2 = repo.status(patchparent, tip)[:4]
1165 changes = repo.changelog.read(tip)
1165 changes = repo.changelog.read(tip)
1166 man = repo.manifest.read(changes[0])
1166 man = repo.manifest.read(changes[0])
1167 aaa = aa[:]
1167 aaa = aa[:]
1168 matchfn = cmdutil.match(repo, pats, opts)
1168 matchfn = cmdutil.match(repo, pats, opts)
1169 if opts.get('short'):
1169 if opts.get('short'):
1170 # if amending a patch, we start with existing
1170 # if amending a patch, we start with existing
1171 # files plus specified files - unfiltered
1171 # files plus specified files - unfiltered
1172 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1172 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1173 # filter with inc/exl options
1173 # filter with inc/exl options
1174 matchfn = cmdutil.match(repo, opts=opts)
1174 matchfn = cmdutil.match(repo, opts=opts)
1175 else:
1175 else:
1176 match = cmdutil.matchall(repo)
1176 match = cmdutil.matchall(repo)
1177 m, a, r, d = repo.status(match=match)[:4]
1177 m, a, r, d = repo.status(match=match)[:4]
1178
1178
1179 # we might end up with files that were added between
1179 # we might end up with files that were added between
1180 # tip and the dirstate parent, but then changed in the
1180 # tip and the dirstate parent, but then changed in the
1181 # local dirstate. in this case, we want them to only
1181 # local dirstate. in this case, we want them to only
1182 # show up in the added section
1182 # show up in the added section
1183 for x in m:
1183 for x in m:
1184 if x not in aa:
1184 if x not in aa:
1185 mm.append(x)
1185 mm.append(x)
1186 # we might end up with files added by the local dirstate that
1186 # we might end up with files added by the local dirstate that
1187 # were deleted by the patch. In this case, they should only
1187 # were deleted by the patch. In this case, they should only
1188 # show up in the changed section.
1188 # show up in the changed section.
1189 for x in a:
1189 for x in a:
1190 if x in dd:
1190 if x in dd:
1191 del dd[dd.index(x)]
1191 del dd[dd.index(x)]
1192 mm.append(x)
1192 mm.append(x)
1193 else:
1193 else:
1194 aa.append(x)
1194 aa.append(x)
1195 # make sure any files deleted in the local dirstate
1195 # make sure any files deleted in the local dirstate
1196 # are not in the add or change column of the patch
1196 # are not in the add or change column of the patch
1197 forget = []
1197 forget = []
1198 for x in d + r:
1198 for x in d + r:
1199 if x in aa:
1199 if x in aa:
1200 del aa[aa.index(x)]
1200 del aa[aa.index(x)]
1201 forget.append(x)
1201 forget.append(x)
1202 continue
1202 continue
1203 elif x in mm:
1203 elif x in mm:
1204 del mm[mm.index(x)]
1204 del mm[mm.index(x)]
1205 dd.append(x)
1205 dd.append(x)
1206
1206
1207 m = list(set(mm))
1207 m = list(set(mm))
1208 r = list(set(dd))
1208 r = list(set(dd))
1209 a = list(set(aa))
1209 a = list(set(aa))
1210 c = [filter(matchfn, l) for l in (m, a, r)]
1210 c = [filter(matchfn, l) for l in (m, a, r)]
1211 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1211 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1212 chunks = patch.diff(repo, patchparent, match=match,
1212 chunks = patch.diff(repo, patchparent, match=match,
1213 changes=c, opts=self.diffopts())
1213 changes=c, opts=self.diffopts())
1214 for chunk in chunks:
1214 for chunk in chunks:
1215 patchf.write(chunk)
1215 patchf.write(chunk)
1216
1216
1217 try:
1217 try:
1218 if self.diffopts().git:
1218 if self.diffopts().git:
1219 copies = {}
1219 copies = {}
1220 for dst in a:
1220 for dst in a:
1221 src = repo.dirstate.copied(dst)
1221 src = repo.dirstate.copied(dst)
1222 # during qfold, the source file for copies may
1222 # during qfold, the source file for copies may
1223 # be removed. Treat this as a simple add.
1223 # be removed. Treat this as a simple add.
1224 if src is not None and src in repo.dirstate:
1224 if src is not None and src in repo.dirstate:
1225 copies.setdefault(src, []).append(dst)
1225 copies.setdefault(src, []).append(dst)
1226 repo.dirstate.add(dst)
1226 repo.dirstate.add(dst)
1227 # remember the copies between patchparent and tip
1227 # remember the copies between patchparent and tip
1228 for dst in aaa:
1228 for dst in aaa:
1229 f = repo.file(dst)
1229 f = repo.file(dst)
1230 src = f.renamed(man[dst])
1230 src = f.renamed(man[dst])
1231 if src:
1231 if src:
1232 copies.setdefault(src[0], []).extend(copies.get(dst, []))
1232 copies.setdefault(src[0], []).extend(copies.get(dst, []))
1233 if dst in a:
1233 if dst in a:
1234 copies[src[0]].append(dst)
1234 copies[src[0]].append(dst)
1235 # we can't copy a file created by the patch itself
1235 # we can't copy a file created by the patch itself
1236 if dst in copies:
1236 if dst in copies:
1237 del copies[dst]
1237 del copies[dst]
1238 for src, dsts in copies.iteritems():
1238 for src, dsts in copies.iteritems():
1239 for dst in dsts:
1239 for dst in dsts:
1240 repo.dirstate.copy(src, dst)
1240 repo.dirstate.copy(src, dst)
1241 else:
1241 else:
1242 for dst in a:
1242 for dst in a:
1243 repo.dirstate.add(dst)
1243 repo.dirstate.add(dst)
1244 # Drop useless copy information
1244 # Drop useless copy information
1245 for f in list(repo.dirstate.copies()):
1245 for f in list(repo.dirstate.copies()):
1246 repo.dirstate.copy(None, f)
1246 repo.dirstate.copy(None, f)
1247 for f in r:
1247 for f in r:
1248 repo.dirstate.remove(f)
1248 repo.dirstate.remove(f)
1249 # if the patch excludes a modified file, mark that
1249 # if the patch excludes a modified file, mark that
1250 # file with mtime=0 so status can see it.
1250 # file with mtime=0 so status can see it.
1251 mm = []
1251 mm = []
1252 for i in xrange(len(m)-1, -1, -1):
1252 for i in xrange(len(m)-1, -1, -1):
1253 if not matchfn(m[i]):
1253 if not matchfn(m[i]):
1254 mm.append(m[i])
1254 mm.append(m[i])
1255 del m[i]
1255 del m[i]
1256 for f in m:
1256 for f in m:
1257 repo.dirstate.normal(f)
1257 repo.dirstate.normal(f)
1258 for f in mm:
1258 for f in mm:
1259 repo.dirstate.normallookup(f)
1259 repo.dirstate.normallookup(f)
1260 for f in forget:
1260 for f in forget:
1261 repo.dirstate.forget(f)
1261 repo.dirstate.forget(f)
1262
1262
1263 if not msg:
1263 if not msg:
1264 if not ph.message:
1264 if not ph.message:
1265 message = "[mq]: %s\n" % patchfn
1265 message = "[mq]: %s\n" % patchfn
1266 else:
1266 else:
1267 message = "\n".join(ph.message)
1267 message = "\n".join(ph.message)
1268 else:
1268 else:
1269 message = msg
1269 message = msg
1270
1270
1271 user = ph.user or changes[1]
1271 user = ph.user or changes[1]
1272
1272
1273 # assumes strip can roll itself back if interrupted
1273 # assumes strip can roll itself back if interrupted
1274 repo.dirstate.setparents(*cparents)
1274 repo.dirstate.setparents(*cparents)
1275 self.applied.pop()
1275 self.applied.pop()
1276 self.applied_dirty = 1
1276 self.applied_dirty = 1
1277 self.strip(repo, top, update=False,
1277 self.strip(repo, top, update=False,
1278 backup='strip')
1278 backup='strip')
1279 except:
1279 except:
1280 repo.dirstate.invalidate()
1280 repo.dirstate.invalidate()
1281 raise
1281 raise
1282
1282
1283 try:
1283 try:
1284 # might be nice to attempt to roll back strip after this
1284 # might be nice to attempt to roll back strip after this
1285 patchf.rename()
1285 patchf.rename()
1286 n = repo.commit(message, user, ph.date, match=match,
1286 n = repo.commit(message, user, ph.date, match=match,
1287 force=True)
1287 force=True)
1288 self.applied.append(statusentry(hex(n), patchfn))
1288 self.applied.append(statusentry(hex(n), patchfn))
1289 except:
1289 except:
1290 ctx = repo[cparents[0]]
1290 ctx = repo[cparents[0]]
1291 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1291 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1292 self.save_dirty()
1292 self.save_dirty()
1293 self.ui.warn(_('refresh interrupted while patch was popped! '
1293 self.ui.warn(_('refresh interrupted while patch was popped! '
1294 '(revert --all, qpush to recover)\n'))
1294 '(revert --all, qpush to recover)\n'))
1295 raise
1295 raise
1296 else:
1296 else:
1297 self.printdiff(repo, patchparent, fp=patchf)
1297 self.printdiff(repo, patchparent, fp=patchf)
1298 patchf.rename()
1298 patchf.rename()
1299 added = repo.status()[1]
1299 added = repo.status()[1]
1300 for a in added:
1300 for a in added:
1301 f = repo.wjoin(a)
1301 f = repo.wjoin(a)
1302 try:
1302 try:
1303 os.unlink(f)
1303 os.unlink(f)
1304 except OSError, e:
1304 except OSError, e:
1305 if e.errno != errno.ENOENT:
1305 if e.errno != errno.ENOENT:
1306 raise
1306 raise
1307 try: os.removedirs(os.path.dirname(f))
1307 try: os.removedirs(os.path.dirname(f))
1308 except: pass
1308 except: pass
1309 # forget the file copies in the dirstate
1309 # forget the file copies in the dirstate
1310 # push should readd the files later on
1310 # push should readd the files later on
1311 repo.dirstate.forget(a)
1311 repo.dirstate.forget(a)
1312 self.pop(repo, force=True)
1312 self.pop(repo, force=True)
1313 self.push(repo, force=True)
1313 self.push(repo, force=True)
1314 finally:
1314 finally:
1315 wlock.release()
1315 wlock.release()
1316 self.removeundo(repo)
1316 self.removeundo(repo)
1317
1317
1318 def init(self, repo, create=False):
1318 def init(self, repo, create=False):
1319 if not create and os.path.isdir(self.path):
1319 if not create and os.path.isdir(self.path):
1320 raise util.Abort(_("patch queue directory already exists"))
1320 raise util.Abort(_("patch queue directory already exists"))
1321 try:
1321 try:
1322 os.mkdir(self.path)
1322 os.mkdir(self.path)
1323 except OSError, inst:
1323 except OSError, inst:
1324 if inst.errno != errno.EEXIST or not create:
1324 if inst.errno != errno.EEXIST or not create:
1325 raise
1325 raise
1326 if create:
1326 if create:
1327 return self.qrepo(create=True)
1327 return self.qrepo(create=True)
1328
1328
1329 def unapplied(self, repo, patch=None):
1329 def unapplied(self, repo, patch=None):
1330 if patch and patch not in self.series:
1330 if patch and patch not in self.series:
1331 raise util.Abort(_("patch %s is not in series file") % patch)
1331 raise util.Abort(_("patch %s is not in series file") % patch)
1332 if not patch:
1332 if not patch:
1333 start = self.series_end()
1333 start = self.series_end()
1334 else:
1334 else:
1335 start = self.series.index(patch) + 1
1335 start = self.series.index(patch) + 1
1336 unapplied = []
1336 unapplied = []
1337 for i in xrange(start, len(self.series)):
1337 for i in xrange(start, len(self.series)):
1338 pushable, reason = self.pushable(i)
1338 pushable, reason = self.pushable(i)
1339 if pushable:
1339 if pushable:
1340 unapplied.append((i, self.series[i]))
1340 unapplied.append((i, self.series[i]))
1341 self.explain_pushable(i)
1341 self.explain_pushable(i)
1342 return unapplied
1342 return unapplied
1343
1343
1344 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1344 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1345 summary=False):
1345 summary=False):
1346 def displayname(patchname):
1346 def displayname(patchname):
1347 if summary:
1347 if summary:
1348 ph = patchheader(self.join(patchname))
1348 ph = patchheader(self.join(patchname))
1349 msg = ph.message
1349 msg = ph.message
1350 msg = msg and ': ' + msg[0] or ': '
1350 msg = msg and ': ' + msg[0] or ': '
1351 else:
1351 else:
1352 msg = ''
1352 msg = ''
1353 return '%s%s' % (patchname, msg)
1353 return '%s%s' % (patchname, msg)
1354
1354
1355 applied = set([p.name for p in self.applied])
1355 applied = set([p.name for p in self.applied])
1356 if length is None:
1356 if length is None:
1357 length = len(self.series) - start
1357 length = len(self.series) - start
1358 if not missing:
1358 if not missing:
1359 for i in xrange(start, start+length):
1359 for i in xrange(start, start+length):
1360 patch = self.series[i]
1360 patch = self.series[i]
1361 if patch in applied:
1361 if patch in applied:
1362 stat = 'A'
1362 stat = 'A'
1363 elif self.pushable(i)[0]:
1363 elif self.pushable(i)[0]:
1364 stat = 'U'
1364 stat = 'U'
1365 else:
1365 else:
1366 stat = 'G'
1366 stat = 'G'
1367 pfx = ''
1367 pfx = ''
1368 if self.ui.verbose:
1368 if self.ui.verbose:
1369 pfx = '%d %s ' % (i, stat)
1369 pfx = '%d %s ' % (i, stat)
1370 elif status and status != stat:
1370 elif status and status != stat:
1371 continue
1371 continue
1372 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1372 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1373 else:
1373 else:
1374 msng_list = []
1374 msng_list = []
1375 for root, dirs, files in os.walk(self.path):
1375 for root, dirs, files in os.walk(self.path):
1376 d = root[len(self.path) + 1:]
1376 d = root[len(self.path) + 1:]
1377 for f in files:
1377 for f in files:
1378 fl = os.path.join(d, f)
1378 fl = os.path.join(d, f)
1379 if (fl not in self.series and
1379 if (fl not in self.series and
1380 fl not in (self.status_path, self.series_path,
1380 fl not in (self.status_path, self.series_path,
1381 self.guards_path)
1381 self.guards_path)
1382 and not fl.startswith('.')):
1382 and not fl.startswith('.')):
1383 msng_list.append(fl)
1383 msng_list.append(fl)
1384 for x in sorted(msng_list):
1384 for x in sorted(msng_list):
1385 pfx = self.ui.verbose and ('D ') or ''
1385 pfx = self.ui.verbose and ('D ') or ''
1386 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1386 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1387
1387
1388 def issaveline(self, l):
1388 def issaveline(self, l):
1389 if l.name == '.hg.patches.save.line':
1389 if l.name == '.hg.patches.save.line':
1390 return True
1390 return True
1391
1391
1392 def qrepo(self, create=False):
1392 def qrepo(self, create=False):
1393 if create or os.path.isdir(self.join(".hg")):
1393 if create or os.path.isdir(self.join(".hg")):
1394 return hg.repository(self.ui, path=self.path, create=create)
1394 return hg.repository(self.ui, path=self.path, create=create)
1395
1395
1396 def restore(self, repo, rev, delete=None, qupdate=None):
1396 def restore(self, repo, rev, delete=None, qupdate=None):
1397 c = repo.changelog.read(rev)
1397 c = repo.changelog.read(rev)
1398 desc = c[4].strip()
1398 desc = c[4].strip()
1399 lines = desc.splitlines()
1399 lines = desc.splitlines()
1400 i = 0
1400 i = 0
1401 datastart = None
1401 datastart = None
1402 series = []
1402 series = []
1403 applied = []
1403 applied = []
1404 qpp = None
1404 qpp = None
1405 for i, line in enumerate(lines):
1405 for i, line in enumerate(lines):
1406 if line == 'Patch Data:':
1406 if line == 'Patch Data:':
1407 datastart = i + 1
1407 datastart = i + 1
1408 elif line.startswith('Dirstate:'):
1408 elif line.startswith('Dirstate:'):
1409 l = line.rstrip()
1409 l = line.rstrip()
1410 l = l[10:].split(' ')
1410 l = l[10:].split(' ')
1411 qpp = [ bin(x) for x in l ]
1411 qpp = [ bin(x) for x in l ]
1412 elif datastart != None:
1412 elif datastart != None:
1413 l = line.rstrip()
1413 l = line.rstrip()
1414 se = statusentry(l)
1414 se = statusentry(l)
1415 file_ = se.name
1415 file_ = se.name
1416 if se.rev:
1416 if se.rev:
1417 applied.append(se)
1417 applied.append(se)
1418 else:
1418 else:
1419 series.append(file_)
1419 series.append(file_)
1420 if datastart is None:
1420 if datastart is None:
1421 self.ui.warn(_("No saved patch data found\n"))
1421 self.ui.warn(_("No saved patch data found\n"))
1422 return 1
1422 return 1
1423 self.ui.warn(_("restoring status: %s\n") % lines[0])
1423 self.ui.warn(_("restoring status: %s\n") % lines[0])
1424 self.full_series = series
1424 self.full_series = series
1425 self.applied = applied
1425 self.applied = applied
1426 self.parse_series()
1426 self.parse_series()
1427 self.series_dirty = 1
1427 self.series_dirty = 1
1428 self.applied_dirty = 1
1428 self.applied_dirty = 1
1429 heads = repo.changelog.heads()
1429 heads = repo.changelog.heads()
1430 if delete:
1430 if delete:
1431 if rev not in heads:
1431 if rev not in heads:
1432 self.ui.warn(_("save entry has children, leaving it alone\n"))
1432 self.ui.warn(_("save entry has children, leaving it alone\n"))
1433 else:
1433 else:
1434 self.ui.warn(_("removing save entry %s\n") % short(rev))
1434 self.ui.warn(_("removing save entry %s\n") % short(rev))
1435 pp = repo.dirstate.parents()
1435 pp = repo.dirstate.parents()
1436 if rev in pp:
1436 if rev in pp:
1437 update = True
1437 update = True
1438 else:
1438 else:
1439 update = False
1439 update = False
1440 self.strip(repo, rev, update=update, backup='strip')
1440 self.strip(repo, rev, update=update, backup='strip')
1441 if qpp:
1441 if qpp:
1442 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1442 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1443 (short(qpp[0]), short(qpp[1])))
1443 (short(qpp[0]), short(qpp[1])))
1444 if qupdate:
1444 if qupdate:
1445 self.ui.status(_("queue directory updating\n"))
1445 self.ui.status(_("queue directory updating\n"))
1446 r = self.qrepo()
1446 r = self.qrepo()
1447 if not r:
1447 if not r:
1448 self.ui.warn(_("Unable to load queue repository\n"))
1448 self.ui.warn(_("Unable to load queue repository\n"))
1449 return 1
1449 return 1
1450 hg.clean(r, qpp[0])
1450 hg.clean(r, qpp[0])
1451
1451
1452 def save(self, repo, msg=None):
1452 def save(self, repo, msg=None):
1453 if len(self.applied) == 0:
1453 if len(self.applied) == 0:
1454 self.ui.warn(_("save: no patches applied, exiting\n"))
1454 self.ui.warn(_("save: no patches applied, exiting\n"))
1455 return 1
1455 return 1
1456 if self.issaveline(self.applied[-1]):
1456 if self.issaveline(self.applied[-1]):
1457 self.ui.warn(_("status is already saved\n"))
1457 self.ui.warn(_("status is already saved\n"))
1458 return 1
1458 return 1
1459
1459
1460 ar = [ ':' + x for x in self.full_series ]
1460 ar = [ ':' + x for x in self.full_series ]
1461 if not msg:
1461 if not msg:
1462 msg = _("hg patches saved state")
1462 msg = _("hg patches saved state")
1463 else:
1463 else:
1464 msg = "hg patches: " + msg.rstrip('\r\n')
1464 msg = "hg patches: " + msg.rstrip('\r\n')
1465 r = self.qrepo()
1465 r = self.qrepo()
1466 if r:
1466 if r:
1467 pp = r.dirstate.parents()
1467 pp = r.dirstate.parents()
1468 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1468 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1469 msg += "\n\nPatch Data:\n"
1469 msg += "\n\nPatch Data:\n"
1470 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1470 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1471 "\n".join(ar) + '\n' or "")
1471 "\n".join(ar) + '\n' or "")
1472 n = repo.commit(text, force=True)
1472 n = repo.commit(text, force=True)
1473 if not n:
1473 if not n:
1474 self.ui.warn(_("repo commit failed\n"))
1474 self.ui.warn(_("repo commit failed\n"))
1475 return 1
1475 return 1
1476 self.applied.append(statusentry(hex(n),'.hg.patches.save.line'))
1476 self.applied.append(statusentry(hex(n),'.hg.patches.save.line'))
1477 self.applied_dirty = 1
1477 self.applied_dirty = 1
1478 self.removeundo(repo)
1478 self.removeundo(repo)
1479
1479
1480 def full_series_end(self):
1480 def full_series_end(self):
1481 if len(self.applied) > 0:
1481 if len(self.applied) > 0:
1482 p = self.applied[-1].name
1482 p = self.applied[-1].name
1483 end = self.find_series(p)
1483 end = self.find_series(p)
1484 if end is None:
1484 if end is None:
1485 return len(self.full_series)
1485 return len(self.full_series)
1486 return end + 1
1486 return end + 1
1487 return 0
1487 return 0
1488
1488
1489 def series_end(self, all_patches=False):
1489 def series_end(self, all_patches=False):
1490 """If all_patches is False, return the index of the next pushable patch
1490 """If all_patches is False, return the index of the next pushable patch
1491 in the series, or the series length. If all_patches is True, return the
1491 in the series, or the series length. If all_patches is True, return the
1492 index of the first patch past the last applied one.
1492 index of the first patch past the last applied one.
1493 """
1493 """
1494 end = 0
1494 end = 0
1495 def next(start):
1495 def next(start):
1496 if all_patches:
1496 if all_patches:
1497 return start
1497 return start
1498 i = start
1498 i = start
1499 while i < len(self.series):
1499 while i < len(self.series):
1500 p, reason = self.pushable(i)
1500 p, reason = self.pushable(i)
1501 if p:
1501 if p:
1502 break
1502 break
1503 self.explain_pushable(i)
1503 self.explain_pushable(i)
1504 i += 1
1504 i += 1
1505 return i
1505 return i
1506 if len(self.applied) > 0:
1506 if len(self.applied) > 0:
1507 p = self.applied[-1].name
1507 p = self.applied[-1].name
1508 try:
1508 try:
1509 end = self.series.index(p)
1509 end = self.series.index(p)
1510 except ValueError:
1510 except ValueError:
1511 return 0
1511 return 0
1512 return next(end + 1)
1512 return next(end + 1)
1513 return next(end)
1513 return next(end)
1514
1514
1515 def appliedname(self, index):
1515 def appliedname(self, index):
1516 pname = self.applied[index].name
1516 pname = self.applied[index].name
1517 if not self.ui.verbose:
1517 if not self.ui.verbose:
1518 p = pname
1518 p = pname
1519 else:
1519 else:
1520 p = str(self.series.index(pname)) + " " + pname
1520 p = str(self.series.index(pname)) + " " + pname
1521 return p
1521 return p
1522
1522
1523 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1523 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1524 force=None, git=False):
1524 force=None, git=False):
1525 def checkseries(patchname):
1525 def checkseries(patchname):
1526 if patchname in self.series:
1526 if patchname in self.series:
1527 raise util.Abort(_('patch %s is already in the series file')
1527 raise util.Abort(_('patch %s is already in the series file')
1528 % patchname)
1528 % patchname)
1529 def checkfile(patchname):
1529 def checkfile(patchname):
1530 if not force and os.path.exists(self.join(patchname)):
1530 if not force and os.path.exists(self.join(patchname)):
1531 raise util.Abort(_('patch "%s" already exists')
1531 raise util.Abort(_('patch "%s" already exists')
1532 % patchname)
1532 % patchname)
1533
1533
1534 if rev:
1534 if rev:
1535 if files:
1535 if files:
1536 raise util.Abort(_('option "-r" not valid when importing '
1536 raise util.Abort(_('option "-r" not valid when importing '
1537 'files'))
1537 'files'))
1538 rev = cmdutil.revrange(repo, rev)
1538 rev = cmdutil.revrange(repo, rev)
1539 rev.sort(lambda x, y: cmp(y, x))
1539 rev.sort(lambda x, y: cmp(y, x))
1540 if (len(files) > 1 or len(rev) > 1) and patchname:
1540 if (len(files) > 1 or len(rev) > 1) and patchname:
1541 raise util.Abort(_('option "-n" not valid when importing multiple '
1541 raise util.Abort(_('option "-n" not valid when importing multiple '
1542 'patches'))
1542 'patches'))
1543 i = 0
1543 i = 0
1544 added = []
1544 added = []
1545 if rev:
1545 if rev:
1546 # If mq patches are applied, we can only import revisions
1546 # If mq patches are applied, we can only import revisions
1547 # that form a linear path to qbase.
1547 # that form a linear path to qbase.
1548 # Otherwise, they should form a linear path to a head.
1548 # Otherwise, they should form a linear path to a head.
1549 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1549 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1550 if len(heads) > 1:
1550 if len(heads) > 1:
1551 raise util.Abort(_('revision %d is the root of more than one '
1551 raise util.Abort(_('revision %d is the root of more than one '
1552 'branch') % rev[-1])
1552 'branch') % rev[-1])
1553 if self.applied:
1553 if self.applied:
1554 base = hex(repo.changelog.node(rev[0]))
1554 base = hex(repo.changelog.node(rev[0]))
1555 if base in [n.rev for n in self.applied]:
1555 if base in [n.rev for n in self.applied]:
1556 raise util.Abort(_('revision %d is already managed')
1556 raise util.Abort(_('revision %d is already managed')
1557 % rev[0])
1557 % rev[0])
1558 if heads != [bin(self.applied[-1].rev)]:
1558 if heads != [bin(self.applied[-1].rev)]:
1559 raise util.Abort(_('revision %d is not the parent of '
1559 raise util.Abort(_('revision %d is not the parent of '
1560 'the queue') % rev[0])
1560 'the queue') % rev[0])
1561 base = repo.changelog.rev(bin(self.applied[0].rev))
1561 base = repo.changelog.rev(bin(self.applied[0].rev))
1562 lastparent = repo.changelog.parentrevs(base)[0]
1562 lastparent = repo.changelog.parentrevs(base)[0]
1563 else:
1563 else:
1564 if heads != [repo.changelog.node(rev[0])]:
1564 if heads != [repo.changelog.node(rev[0])]:
1565 raise util.Abort(_('revision %d has unmanaged children')
1565 raise util.Abort(_('revision %d has unmanaged children')
1566 % rev[0])
1566 % rev[0])
1567 lastparent = None
1567 lastparent = None
1568
1568
1569 if git:
1569 if git:
1570 self.diffopts().git = True
1570 self.diffopts().git = True
1571
1571
1572 for r in rev:
1572 for r in rev:
1573 p1, p2 = repo.changelog.parentrevs(r)
1573 p1, p2 = repo.changelog.parentrevs(r)
1574 n = repo.changelog.node(r)
1574 n = repo.changelog.node(r)
1575 if p2 != nullrev:
1575 if p2 != nullrev:
1576 raise util.Abort(_('cannot import merge revision %d') % r)
1576 raise util.Abort(_('cannot import merge revision %d') % r)
1577 if lastparent and lastparent != r:
1577 if lastparent and lastparent != r:
1578 raise util.Abort(_('revision %d is not the parent of %d')
1578 raise util.Abort(_('revision %d is not the parent of %d')
1579 % (r, lastparent))
1579 % (r, lastparent))
1580 lastparent = p1
1580 lastparent = p1
1581
1581
1582 if not patchname:
1582 if not patchname:
1583 patchname = normname('%d.diff' % r)
1583 patchname = normname('%d.diff' % r)
1584 self.check_reserved_name(patchname)
1584 self.check_reserved_name(patchname)
1585 checkseries(patchname)
1585 checkseries(patchname)
1586 checkfile(patchname)
1586 checkfile(patchname)
1587 self.full_series.insert(0, patchname)
1587 self.full_series.insert(0, patchname)
1588
1588
1589 patchf = self.opener(patchname, "w")
1589 patchf = self.opener(patchname, "w")
1590 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1590 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1591 patchf.close()
1591 patchf.close()
1592
1592
1593 se = statusentry(hex(n), patchname)
1593 se = statusentry(hex(n), patchname)
1594 self.applied.insert(0, se)
1594 self.applied.insert(0, se)
1595
1595
1596 added.append(patchname)
1596 added.append(patchname)
1597 patchname = None
1597 patchname = None
1598 self.parse_series()
1598 self.parse_series()
1599 self.applied_dirty = 1
1599 self.applied_dirty = 1
1600
1600
1601 for filename in files:
1601 for filename in files:
1602 if existing:
1602 if existing:
1603 if filename == '-':
1603 if filename == '-':
1604 raise util.Abort(_('-e is incompatible with import from -'))
1604 raise util.Abort(_('-e is incompatible with import from -'))
1605 if not patchname:
1605 if not patchname:
1606 patchname = normname(filename)
1606 patchname = normname(filename)
1607 self.check_reserved_name(patchname)
1607 self.check_reserved_name(patchname)
1608 if not os.path.isfile(self.join(patchname)):
1608 if not os.path.isfile(self.join(patchname)):
1609 raise util.Abort(_("patch %s does not exist") % patchname)
1609 raise util.Abort(_("patch %s does not exist") % patchname)
1610 else:
1610 else:
1611 try:
1611 try:
1612 if filename == '-':
1612 if filename == '-':
1613 if not patchname:
1613 if not patchname:
1614 raise util.Abort(_('need --name to import a patch from -'))
1614 raise util.Abort(_('need --name to import a patch from -'))
1615 text = sys.stdin.read()
1615 text = sys.stdin.read()
1616 else:
1616 else:
1617 text = url.open(self.ui, filename).read()
1617 text = url.open(self.ui, filename).read()
1618 except (OSError, IOError):
1618 except (OSError, IOError):
1619 raise util.Abort(_("unable to read %s") % filename)
1619 raise util.Abort(_("unable to read %s") % filename)
1620 if not patchname:
1620 if not patchname:
1621 patchname = normname(os.path.basename(filename))
1621 patchname = normname(os.path.basename(filename))
1622 self.check_reserved_name(patchname)
1622 self.check_reserved_name(patchname)
1623 checkfile(patchname)
1623 checkfile(patchname)
1624 patchf = self.opener(patchname, "w")
1624 patchf = self.opener(patchname, "w")
1625 patchf.write(text)
1625 patchf.write(text)
1626 if not force:
1626 if not force:
1627 checkseries(patchname)
1627 checkseries(patchname)
1628 if patchname not in self.series:
1628 if patchname not in self.series:
1629 index = self.full_series_end() + i
1629 index = self.full_series_end() + i
1630 self.full_series[index:index] = [patchname]
1630 self.full_series[index:index] = [patchname]
1631 self.parse_series()
1631 self.parse_series()
1632 self.ui.warn(_("adding %s to series file\n") % patchname)
1632 self.ui.warn(_("adding %s to series file\n") % patchname)
1633 i += 1
1633 i += 1
1634 added.append(patchname)
1634 added.append(patchname)
1635 patchname = None
1635 patchname = None
1636 self.series_dirty = 1
1636 self.series_dirty = 1
1637 qrepo = self.qrepo()
1637 qrepo = self.qrepo()
1638 if qrepo:
1638 if qrepo:
1639 qrepo.add(added)
1639 qrepo.add(added)
1640
1640
1641 def delete(ui, repo, *patches, **opts):
1641 def delete(ui, repo, *patches, **opts):
1642 """remove patches from queue
1642 """remove patches from queue
1643
1643
1644 The patches must not be applied, unless they are arguments to the
1644 The patches must not be applied, unless they are arguments to the
1645 -r/--rev parameter. At least one patch or revision is required.
1645 -r/--rev parameter. At least one patch or revision is required.
1646
1646
1647 With --rev, mq will stop managing the named revisions (converting
1647 With --rev, mq will stop managing the named revisions (converting
1648 them to regular Mercurial changesets). The qfinish command should
1648 them to regular Mercurial changesets). The qfinish command should
1649 be used as an alternative for qdelete -r, as the latter option is
1649 be used as an alternative for qdelete -r, as the latter option is
1650 deprecated.
1650 deprecated.
1651
1651
1652 With -k/--keep, the patch files are preserved in the patch
1652 With -k/--keep, the patch files are preserved in the patch
1653 directory."""
1653 directory."""
1654 q = repo.mq
1654 q = repo.mq
1655 q.delete(repo, patches, opts)
1655 q.delete(repo, patches, opts)
1656 q.save_dirty()
1656 q.save_dirty()
1657 return 0
1657 return 0
1658
1658
1659 def applied(ui, repo, patch=None, **opts):
1659 def applied(ui, repo, patch=None, **opts):
1660 """print the patches already applied"""
1660 """print the patches already applied"""
1661 q = repo.mq
1661 q = repo.mq
1662 if patch:
1662 if patch:
1663 if patch not in q.series:
1663 if patch not in q.series:
1664 raise util.Abort(_("patch %s is not in series file") % patch)
1664 raise util.Abort(_("patch %s is not in series file") % patch)
1665 end = q.series.index(patch) + 1
1665 end = q.series.index(patch) + 1
1666 else:
1666 else:
1667 end = q.series_end(True)
1667 end = q.series_end(True)
1668 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1668 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1669
1669
1670 def unapplied(ui, repo, patch=None, **opts):
1670 def unapplied(ui, repo, patch=None, **opts):
1671 """print the patches not yet applied"""
1671 """print the patches not yet applied"""
1672 q = repo.mq
1672 q = repo.mq
1673 if patch:
1673 if patch:
1674 if patch not in q.series:
1674 if patch not in q.series:
1675 raise util.Abort(_("patch %s is not in series file") % patch)
1675 raise util.Abort(_("patch %s is not in series file") % patch)
1676 start = q.series.index(patch) + 1
1676 start = q.series.index(patch) + 1
1677 else:
1677 else:
1678 start = q.series_end(True)
1678 start = q.series_end(True)
1679 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1679 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1680
1680
1681 def qimport(ui, repo, *filename, **opts):
1681 def qimport(ui, repo, *filename, **opts):
1682 """import a patch
1682 """import a patch
1683
1683
1684 The patch is inserted into the series after the last applied
1684 The patch is inserted into the series after the last applied
1685 patch. If no patches have been applied, qimport prepends the patch
1685 patch. If no patches have been applied, qimport prepends the patch
1686 to the series.
1686 to the series.
1687
1687
1688 The patch will have the same name as its source file unless you
1688 The patch will have the same name as its source file unless you
1689 give it a new one with -n/--name.
1689 give it a new one with -n/--name.
1690
1690
1691 You can register an existing patch inside the patch directory with
1691 You can register an existing patch inside the patch directory with
1692 the -e/--existing flag.
1692 the -e/--existing flag.
1693
1693
1694 With -f/--force, an existing patch of the same name will be
1694 With -f/--force, an existing patch of the same name will be
1695 overwritten.
1695 overwritten.
1696
1696
1697 An existing changeset may be placed under mq control with -r/--rev
1697 An existing changeset may be placed under mq control with -r/--rev
1698 (e.g. qimport --rev tip -n patch will place tip under mq control).
1698 (e.g. qimport --rev tip -n patch will place tip under mq control).
1699 With -g/--git, patches imported with --rev will use the git diff
1699 With -g/--git, patches imported with --rev will use the git diff
1700 format. See the diffs help topic for information on why this is
1700 format. See the diffs help topic for information on why this is
1701 important for preserving rename/copy information and permission
1701 important for preserving rename/copy information and permission
1702 changes.
1702 changes.
1703
1703
1704 To import a patch from standard input, pass - as the patch file.
1704 To import a patch from standard input, pass - as the patch file.
1705 When importing from standard input, a patch name must be specified
1705 When importing from standard input, a patch name must be specified
1706 using the --name flag.
1706 using the --name flag.
1707 """
1707 """
1708 q = repo.mq
1708 q = repo.mq
1709 q.qimport(repo, filename, patchname=opts['name'],
1709 q.qimport(repo, filename, patchname=opts['name'],
1710 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1710 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1711 git=opts['git'])
1711 git=opts['git'])
1712 q.save_dirty()
1712 q.save_dirty()
1713
1713
1714 if opts.get('push') and not opts.get('rev'):
1714 if opts.get('push') and not opts.get('rev'):
1715 return q.push(repo, None)
1715 return q.push(repo, None)
1716 return 0
1716 return 0
1717
1717
1718 def init(ui, repo, **opts):
1718 def init(ui, repo, **opts):
1719 """init a new queue repository
1719 """init a new queue repository
1720
1720
1721 The queue repository is unversioned by default. If
1721 The queue repository is unversioned by default. If
1722 -c/--create-repo is specified, qinit will create a separate nested
1722 -c/--create-repo is specified, qinit will create a separate nested
1723 repository for patches (qinit -c may also be run later to convert
1723 repository for patches (qinit -c may also be run later to convert
1724 an unversioned patch repository into a versioned one). You can use
1724 an unversioned patch repository into a versioned one). You can use
1725 qcommit to commit changes to this queue repository."""
1725 qcommit to commit changes to this queue repository."""
1726 q = repo.mq
1726 q = repo.mq
1727 r = q.init(repo, create=opts['create_repo'])
1727 r = q.init(repo, create=opts['create_repo'])
1728 q.save_dirty()
1728 q.save_dirty()
1729 if r:
1729 if r:
1730 if not os.path.exists(r.wjoin('.hgignore')):
1730 if not os.path.exists(r.wjoin('.hgignore')):
1731 fp = r.wopener('.hgignore', 'w')
1731 fp = r.wopener('.hgignore', 'w')
1732 fp.write('^\\.hg\n')
1732 fp.write('^\\.hg\n')
1733 fp.write('^\\.mq\n')
1733 fp.write('^\\.mq\n')
1734 fp.write('syntax: glob\n')
1734 fp.write('syntax: glob\n')
1735 fp.write('status\n')
1735 fp.write('status\n')
1736 fp.write('guards\n')
1736 fp.write('guards\n')
1737 fp.close()
1737 fp.close()
1738 if not os.path.exists(r.wjoin('series')):
1738 if not os.path.exists(r.wjoin('series')):
1739 r.wopener('series', 'w').close()
1739 r.wopener('series', 'w').close()
1740 r.add(['.hgignore', 'series'])
1740 r.add(['.hgignore', 'series'])
1741 commands.add(ui, r)
1741 commands.add(ui, r)
1742 return 0
1742 return 0
1743
1743
1744 def clone(ui, source, dest=None, **opts):
1744 def clone(ui, source, dest=None, **opts):
1745 '''clone main and patch repository at same time
1745 '''clone main and patch repository at same time
1746
1746
1747 If source is local, destination will have no patches applied. If
1747 If source is local, destination will have no patches applied. If
1748 source is remote, this command can not check if patches are
1748 source is remote, this command can not check if patches are
1749 applied in source, so cannot guarantee that patches are not
1749 applied in source, so cannot guarantee that patches are not
1750 applied in destination. If you clone remote repository, be sure
1750 applied in destination. If you clone remote repository, be sure
1751 before that it has no patches applied.
1751 before that it has no patches applied.
1752
1752
1753 Source patch repository is looked for in <src>/.hg/patches by
1753 Source patch repository is looked for in <src>/.hg/patches by
1754 default. Use -p <url> to change.
1754 default. Use -p <url> to change.
1755
1755
1756 The patch directory must be a nested Mercurial repository, as
1756 The patch directory must be a nested Mercurial repository, as
1757 would be created by qinit -c.
1757 would be created by qinit -c.
1758 '''
1758 '''
1759 def patchdir(repo):
1759 def patchdir(repo):
1760 url = repo.url()
1760 url = repo.url()
1761 if url.endswith('/'):
1761 if url.endswith('/'):
1762 url = url[:-1]
1762 url = url[:-1]
1763 return url + '/.hg/patches'
1763 return url + '/.hg/patches'
1764 if dest is None:
1764 if dest is None:
1765 dest = hg.defaultdest(source)
1765 dest = hg.defaultdest(source)
1766 sr = hg.repository(cmdutil.remoteui(ui, opts), ui.expandpath(source))
1766 sr = hg.repository(cmdutil.remoteui(ui, opts), ui.expandpath(source))
1767 if opts['patches']:
1767 if opts['patches']:
1768 patchespath = ui.expandpath(opts['patches'])
1768 patchespath = ui.expandpath(opts['patches'])
1769 else:
1769 else:
1770 patchespath = patchdir(sr)
1770 patchespath = patchdir(sr)
1771 try:
1771 try:
1772 hg.repository(ui, patchespath)
1772 hg.repository(ui, patchespath)
1773 except error.RepoError:
1773 except error.RepoError:
1774 raise util.Abort(_('versioned patch repository not found'
1774 raise util.Abort(_('versioned patch repository not found'
1775 ' (see qinit -c)'))
1775 ' (see qinit -c)'))
1776 qbase, destrev = None, None
1776 qbase, destrev = None, None
1777 if sr.local():
1777 if sr.local():
1778 if sr.mq.applied:
1778 if sr.mq.applied:
1779 qbase = bin(sr.mq.applied[0].rev)
1779 qbase = bin(sr.mq.applied[0].rev)
1780 if not hg.islocal(dest):
1780 if not hg.islocal(dest):
1781 heads = set(sr.heads())
1781 heads = set(sr.heads())
1782 destrev = list(heads.difference(sr.heads(qbase)))
1782 destrev = list(heads.difference(sr.heads(qbase)))
1783 destrev.append(sr.changelog.parents(qbase)[0])
1783 destrev.append(sr.changelog.parents(qbase)[0])
1784 elif sr.capable('lookup'):
1784 elif sr.capable('lookup'):
1785 try:
1785 try:
1786 qbase = sr.lookup('qbase')
1786 qbase = sr.lookup('qbase')
1787 except error.RepoError:
1787 except error.RepoError:
1788 pass
1788 pass
1789 ui.note(_('cloning main repository\n'))
1789 ui.note(_('cloning main repository\n'))
1790 sr, dr = hg.clone(ui, sr.url(), dest,
1790 sr, dr = hg.clone(ui, sr.url(), dest,
1791 pull=opts['pull'],
1791 pull=opts['pull'],
1792 rev=destrev,
1792 rev=destrev,
1793 update=False,
1793 update=False,
1794 stream=opts['uncompressed'])
1794 stream=opts['uncompressed'])
1795 ui.note(_('cloning patch repository\n'))
1795 ui.note(_('cloning patch repository\n'))
1796 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1796 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1797 pull=opts['pull'], update=not opts['noupdate'],
1797 pull=opts['pull'], update=not opts['noupdate'],
1798 stream=opts['uncompressed'])
1798 stream=opts['uncompressed'])
1799 if dr.local():
1799 if dr.local():
1800 if qbase:
1800 if qbase:
1801 ui.note(_('stripping applied patches from destination '
1801 ui.note(_('stripping applied patches from destination '
1802 'repository\n'))
1802 'repository\n'))
1803 dr.mq.strip(dr, qbase, update=False, backup=None)
1803 dr.mq.strip(dr, qbase, update=False, backup=None)
1804 if not opts['noupdate']:
1804 if not opts['noupdate']:
1805 ui.note(_('updating destination repository\n'))
1805 ui.note(_('updating destination repository\n'))
1806 hg.update(dr, dr.changelog.tip())
1806 hg.update(dr, dr.changelog.tip())
1807
1807
1808 def commit(ui, repo, *pats, **opts):
1808 def commit(ui, repo, *pats, **opts):
1809 """commit changes in the queue repository"""
1809 """commit changes in the queue repository"""
1810 q = repo.mq
1810 q = repo.mq
1811 r = q.qrepo()
1811 r = q.qrepo()
1812 if not r: raise util.Abort('no queue repository')
1812 if not r: raise util.Abort('no queue repository')
1813 commands.commit(r.ui, r, *pats, **opts)
1813 commands.commit(r.ui, r, *pats, **opts)
1814
1814
1815 def series(ui, repo, **opts):
1815 def series(ui, repo, **opts):
1816 """print the entire series file"""
1816 """print the entire series file"""
1817 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1817 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1818 return 0
1818 return 0
1819
1819
1820 def top(ui, repo, **opts):
1820 def top(ui, repo, **opts):
1821 """print the name of the current patch"""
1821 """print the name of the current patch"""
1822 q = repo.mq
1822 q = repo.mq
1823 t = q.applied and q.series_end(True) or 0
1823 t = q.applied and q.series_end(True) or 0
1824 if t:
1824 if t:
1825 return q.qseries(repo, start=t-1, length=1, status='A',
1825 return q.qseries(repo, start=t-1, length=1, status='A',
1826 summary=opts.get('summary'))
1826 summary=opts.get('summary'))
1827 else:
1827 else:
1828 ui.write(_("no patches applied\n"))
1828 ui.write(_("no patches applied\n"))
1829 return 1
1829 return 1
1830
1830
1831 def next(ui, repo, **opts):
1831 def next(ui, repo, **opts):
1832 """print the name of the next patch"""
1832 """print the name of the next patch"""
1833 q = repo.mq
1833 q = repo.mq
1834 end = q.series_end()
1834 end = q.series_end()
1835 if end == len(q.series):
1835 if end == len(q.series):
1836 ui.write(_("all patches applied\n"))
1836 ui.write(_("all patches applied\n"))
1837 return 1
1837 return 1
1838 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1838 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1839
1839
1840 def prev(ui, repo, **opts):
1840 def prev(ui, repo, **opts):
1841 """print the name of the previous patch"""
1841 """print the name of the previous patch"""
1842 q = repo.mq
1842 q = repo.mq
1843 l = len(q.applied)
1843 l = len(q.applied)
1844 if l == 1:
1844 if l == 1:
1845 ui.write(_("only one patch applied\n"))
1845 ui.write(_("only one patch applied\n"))
1846 return 1
1846 return 1
1847 if not l:
1847 if not l:
1848 ui.write(_("no patches applied\n"))
1848 ui.write(_("no patches applied\n"))
1849 return 1
1849 return 1
1850 return q.qseries(repo, start=l-2, length=1, status='A',
1850 return q.qseries(repo, start=l-2, length=1, status='A',
1851 summary=opts.get('summary'))
1851 summary=opts.get('summary'))
1852
1852
1853 def setupheaderopts(ui, opts):
1853 def setupheaderopts(ui, opts):
1854 def do(opt,val):
1854 def do(opt,val):
1855 if not opts[opt] and opts['current' + opt]:
1855 if not opts[opt] and opts['current' + opt]:
1856 opts[opt] = val
1856 opts[opt] = val
1857 do('user', ui.username())
1857 do('user', ui.username())
1858 do('date', "%d %d" % util.makedate())
1858 do('date', "%d %d" % util.makedate())
1859
1859
1860 def new(ui, repo, patch, *args, **opts):
1860 def new(ui, repo, patch, *args, **opts):
1861 """create a new patch
1861 """create a new patch
1862
1862
1863 qnew creates a new patch on top of the currently-applied patch (if
1863 qnew creates a new patch on top of the currently-applied patch (if
1864 any). It will refuse to run if there are any outstanding changes
1864 any). It will refuse to run if there are any outstanding changes
1865 unless -f/--force is specified, in which case the patch will be
1865 unless -f/--force is specified, in which case the patch will be
1866 initialized with them. You may also use -I/--include,
1866 initialized with them. You may also use -I/--include,
1867 -X/--exclude, and/or a list of files after the patch name to add
1867 -X/--exclude, and/or a list of files after the patch name to add
1868 only changes to matching files to the new patch, leaving the rest
1868 only changes to matching files to the new patch, leaving the rest
1869 as uncommitted modifications.
1869 as uncommitted modifications.
1870
1870
1871 -u/--user and -d/--date can be used to set the (given) user and
1871 -u/--user and -d/--date can be used to set the (given) user and
1872 date, respectively. -U/--currentuser and -D/--currentdate set user
1872 date, respectively. -U/--currentuser and -D/--currentdate set user
1873 to current user and date to current date.
1873 to current user and date to current date.
1874
1874
1875 -e/--edit, -m/--message or -l/--logfile set the patch header as
1875 -e/--edit, -m/--message or -l/--logfile set the patch header as
1876 well as the commit message. If none is specified, the header is
1876 well as the commit message. If none is specified, the header is
1877 empty and the commit message is '[mq]: PATCH'.
1877 empty and the commit message is '[mq]: PATCH'.
1878
1878
1879 Use the -g/--git option to keep the patch in the git extended diff
1879 Use the -g/--git option to keep the patch in the git extended diff
1880 format. Read the diffs help topic for more information on why this
1880 format. Read the diffs help topic for more information on why this
1881 is important for preserving permission changes and copy/rename
1881 is important for preserving permission changes and copy/rename
1882 information.
1882 information.
1883 """
1883 """
1884 msg = cmdutil.logmessage(opts)
1884 msg = cmdutil.logmessage(opts)
1885 def getmsg(): return ui.edit(msg, ui.username())
1885 def getmsg(): return ui.edit(msg, ui.username())
1886 q = repo.mq
1886 q = repo.mq
1887 opts['msg'] = msg
1887 opts['msg'] = msg
1888 if opts.get('edit'):
1888 if opts.get('edit'):
1889 opts['msg'] = getmsg
1889 opts['msg'] = getmsg
1890 else:
1890 else:
1891 opts['msg'] = msg
1891 opts['msg'] = msg
1892 setupheaderopts(ui, opts)
1892 setupheaderopts(ui, opts)
1893 q.new(repo, patch, *args, **opts)
1893 q.new(repo, patch, *args, **opts)
1894 q.save_dirty()
1894 q.save_dirty()
1895 return 0
1895 return 0
1896
1896
1897 def refresh(ui, repo, *pats, **opts):
1897 def refresh(ui, repo, *pats, **opts):
1898 """update the current patch
1898 """update the current patch
1899
1899
1900 If any file patterns are provided, the refreshed patch will
1900 If any file patterns are provided, the refreshed patch will
1901 contain only the modifications that match those patterns; the
1901 contain only the modifications that match those patterns; the
1902 remaining modifications will remain in the working directory.
1902 remaining modifications will remain in the working directory.
1903
1903
1904 If -s/--short is specified, files currently included in the patch
1904 If -s/--short is specified, files currently included in the patch
1905 will be refreshed just like matched files and remain in the patch.
1905 will be refreshed just like matched files and remain in the patch.
1906
1906
1907 hg add/remove/copy/rename work as usual, though you might want to
1907 hg add/remove/copy/rename work as usual, though you might want to
1908 use git-style patches (-g/--git or [diff] git=1) to track copies
1908 use git-style patches (-g/--git or [diff] git=1) to track copies
1909 and renames. See the diffs help topic for more information on the
1909 and renames. See the diffs help topic for more information on the
1910 git diff format.
1910 git diff format.
1911 """
1911 """
1912 q = repo.mq
1912 q = repo.mq
1913 message = cmdutil.logmessage(opts)
1913 message = cmdutil.logmessage(opts)
1914 if opts['edit']:
1914 if opts['edit']:
1915 if not q.applied:
1915 if not q.applied:
1916 ui.write(_("no patches applied\n"))
1916 ui.write(_("no patches applied\n"))
1917 return 1
1917 return 1
1918 if message:
1918 if message:
1919 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1919 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1920 patch = q.applied[-1].name
1920 patch = q.applied[-1].name
1921 ph = patchheader(q.join(patch))
1921 ph = patchheader(q.join(patch))
1922 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
1922 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
1923 setupheaderopts(ui, opts)
1923 setupheaderopts(ui, opts)
1924 ret = q.refresh(repo, pats, msg=message, **opts)
1924 ret = q.refresh(repo, pats, msg=message, **opts)
1925 q.save_dirty()
1925 q.save_dirty()
1926 return ret
1926 return ret
1927
1927
1928 def diff(ui, repo, *pats, **opts):
1928 def diff(ui, repo, *pats, **opts):
1929 """diff of the current patch and subsequent modifications
1929 """diff of the current patch and subsequent modifications
1930
1930
1931 Shows a diff which includes the current patch as well as any
1931 Shows a diff which includes the current patch as well as any
1932 changes which have been made in the working directory since the
1932 changes which have been made in the working directory since the
1933 last refresh (thus showing what the current patch would become
1933 last refresh (thus showing what the current patch would become
1934 after a qrefresh).
1934 after a qrefresh).
1935
1935
1936 Use 'hg diff' if you only want to see the changes made since the
1936 Use 'hg diff' if you only want to see the changes made since the
1937 last qrefresh, or 'hg export qtip' if you want to see changes made
1937 last qrefresh, or 'hg export qtip' if you want to see changes made
1938 by the current patch without including changes made since the
1938 by the current patch without including changes made since the
1939 qrefresh.
1939 qrefresh.
1940 """
1940 """
1941 repo.mq.diff(repo, pats, opts)
1941 repo.mq.diff(repo, pats, opts)
1942 return 0
1942 return 0
1943
1943
1944 def fold(ui, repo, *files, **opts):
1944 def fold(ui, repo, *files, **opts):
1945 """fold the named patches into the current patch
1945 """fold the named patches into the current patch
1946
1946
1947 Patches must not yet be applied. Each patch will be successively
1947 Patches must not yet be applied. Each patch will be successively
1948 applied to the current patch in the order given. If all the
1948 applied to the current patch in the order given. If all the
1949 patches apply successfully, the current patch will be refreshed
1949 patches apply successfully, the current patch will be refreshed
1950 with the new cumulative patch, and the folded patches will be
1950 with the new cumulative patch, and the folded patches will be
1951 deleted. With -k/--keep, the folded patch files will not be
1951 deleted. With -k/--keep, the folded patch files will not be
1952 removed afterwards.
1952 removed afterwards.
1953
1953
1954 The header for each folded patch will be concatenated with the
1954 The header for each folded patch will be concatenated with the
1955 current patch header, separated by a line of '* * *'."""
1955 current patch header, separated by a line of '* * *'."""
1956
1956
1957 q = repo.mq
1957 q = repo.mq
1958
1958
1959 if not files:
1959 if not files:
1960 raise util.Abort(_('qfold requires at least one patch name'))
1960 raise util.Abort(_('qfold requires at least one patch name'))
1961 if not q.check_toppatch(repo):
1961 if not q.check_toppatch(repo):
1962 raise util.Abort(_('No patches applied'))
1962 raise util.Abort(_('No patches applied'))
1963 q.check_localchanges(repo)
1963 q.check_localchanges(repo)
1964
1964
1965 message = cmdutil.logmessage(opts)
1965 message = cmdutil.logmessage(opts)
1966 if opts['edit']:
1966 if opts['edit']:
1967 if message:
1967 if message:
1968 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1968 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1969
1969
1970 parent = q.lookup('qtip')
1970 parent = q.lookup('qtip')
1971 patches = []
1971 patches = []
1972 messages = []
1972 messages = []
1973 for f in files:
1973 for f in files:
1974 p = q.lookup(f)
1974 p = q.lookup(f)
1975 if p in patches or p == parent:
1975 if p in patches or p == parent:
1976 ui.warn(_('Skipping already folded patch %s') % p)
1976 ui.warn(_('Skipping already folded patch %s') % p)
1977 if q.isapplied(p):
1977 if q.isapplied(p):
1978 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1978 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1979 patches.append(p)
1979 patches.append(p)
1980
1980
1981 for p in patches:
1981 for p in patches:
1982 if not message:
1982 if not message:
1983 ph = patchheader(q.join(p))
1983 ph = patchheader(q.join(p))
1984 if ph.message:
1984 if ph.message:
1985 messages.append(ph.message)
1985 messages.append(ph.message)
1986 pf = q.join(p)
1986 pf = q.join(p)
1987 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1987 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1988 if not patchsuccess:
1988 if not patchsuccess:
1989 raise util.Abort(_('Error folding patch %s') % p)
1989 raise util.Abort(_('Error folding patch %s') % p)
1990 patch.updatedir(ui, repo, files)
1990 patch.updatedir(ui, repo, files)
1991
1991
1992 if not message:
1992 if not message:
1993 ph = patchheader(q.join(parent))
1993 ph = patchheader(q.join(parent))
1994 message, user = ph.message, ph.user
1994 message, user = ph.message, ph.user
1995 for msg in messages:
1995 for msg in messages:
1996 message.append('* * *')
1996 message.append('* * *')
1997 message.extend(msg)
1997 message.extend(msg)
1998 message = '\n'.join(message)
1998 message = '\n'.join(message)
1999
1999
2000 if opts['edit']:
2000 if opts['edit']:
2001 message = ui.edit(message, user or ui.username())
2001 message = ui.edit(message, user or ui.username())
2002
2002
2003 q.refresh(repo, msg=message)
2003 q.refresh(repo, msg=message)
2004 q.delete(repo, patches, opts)
2004 q.delete(repo, patches, opts)
2005 q.save_dirty()
2005 q.save_dirty()
2006
2006
2007 def goto(ui, repo, patch, **opts):
2007 def goto(ui, repo, patch, **opts):
2008 '''push or pop patches until named patch is at top of stack'''
2008 '''push or pop patches until named patch is at top of stack'''
2009 q = repo.mq
2009 q = repo.mq
2010 patch = q.lookup(patch)
2010 patch = q.lookup(patch)
2011 if q.isapplied(patch):
2011 if q.isapplied(patch):
2012 ret = q.pop(repo, patch, force=opts['force'])
2012 ret = q.pop(repo, patch, force=opts['force'])
2013 else:
2013 else:
2014 ret = q.push(repo, patch, force=opts['force'])
2014 ret = q.push(repo, patch, force=opts['force'])
2015 q.save_dirty()
2015 q.save_dirty()
2016 return ret
2016 return ret
2017
2017
2018 def guard(ui, repo, *args, **opts):
2018 def guard(ui, repo, *args, **opts):
2019 '''set or print guards for a patch
2019 '''set or print guards for a patch
2020
2020
2021 Guards control whether a patch can be pushed. A patch with no
2021 Guards control whether a patch can be pushed. A patch with no
2022 guards is always pushed. A patch with a positive guard ("+foo") is
2022 guards is always pushed. A patch with a positive guard ("+foo") is
2023 pushed only if the qselect command has activated it. A patch with
2023 pushed only if the qselect command has activated it. A patch with
2024 a negative guard ("-foo") is never pushed if the qselect command
2024 a negative guard ("-foo") is never pushed if the qselect command
2025 has activated it.
2025 has activated it.
2026
2026
2027 With no arguments, print the currently active guards.
2027 With no arguments, print the currently active guards.
2028 With arguments, set guards for the named patch.
2028 With arguments, set guards for the named patch.
2029 NOTE: Specifying negative guards now requires '--'.
2029 NOTE: Specifying negative guards now requires '--'.
2030
2030
2031 To set guards on another patch:
2031 To set guards on another patch:
2032 hg qguard -- other.patch +2.6.17 -stable
2032 hg qguard -- other.patch +2.6.17 -stable
2033 '''
2033 '''
2034 def status(idx):
2034 def status(idx):
2035 guards = q.series_guards[idx] or ['unguarded']
2035 guards = q.series_guards[idx] or ['unguarded']
2036 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
2036 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
2037 q = repo.mq
2037 q = repo.mq
2038 patch = None
2038 patch = None
2039 args = list(args)
2039 args = list(args)
2040 if opts['list']:
2040 if opts['list']:
2041 if args or opts['none']:
2041 if args or opts['none']:
2042 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2042 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2043 for i in xrange(len(q.series)):
2043 for i in xrange(len(q.series)):
2044 status(i)
2044 status(i)
2045 return
2045 return
2046 if not args or args[0][0:1] in '-+':
2046 if not args or args[0][0:1] in '-+':
2047 if not q.applied:
2047 if not q.applied:
2048 raise util.Abort(_('no patches applied'))
2048 raise util.Abort(_('no patches applied'))
2049 patch = q.applied[-1].name
2049 patch = q.applied[-1].name
2050 if patch is None and args[0][0:1] not in '-+':
2050 if patch is None and args[0][0:1] not in '-+':
2051 patch = args.pop(0)
2051 patch = args.pop(0)
2052 if patch is None:
2052 if patch is None:
2053 raise util.Abort(_('no patch to work with'))
2053 raise util.Abort(_('no patch to work with'))
2054 if args or opts['none']:
2054 if args or opts['none']:
2055 idx = q.find_series(patch)
2055 idx = q.find_series(patch)
2056 if idx is None:
2056 if idx is None:
2057 raise util.Abort(_('no patch named %s') % patch)
2057 raise util.Abort(_('no patch named %s') % patch)
2058 q.set_guards(idx, args)
2058 q.set_guards(idx, args)
2059 q.save_dirty()
2059 q.save_dirty()
2060 else:
2060 else:
2061 status(q.series.index(q.lookup(patch)))
2061 status(q.series.index(q.lookup(patch)))
2062
2062
2063 def header(ui, repo, patch=None):
2063 def header(ui, repo, patch=None):
2064 """print the header of the topmost or specified patch"""
2064 """print the header of the topmost or specified patch"""
2065 q = repo.mq
2065 q = repo.mq
2066
2066
2067 if patch:
2067 if patch:
2068 patch = q.lookup(patch)
2068 patch = q.lookup(patch)
2069 else:
2069 else:
2070 if not q.applied:
2070 if not q.applied:
2071 ui.write('no patches applied\n')
2071 ui.write('no patches applied\n')
2072 return 1
2072 return 1
2073 patch = q.lookup('qtip')
2073 patch = q.lookup('qtip')
2074 ph = patchheader(repo.mq.join(patch))
2074 ph = patchheader(repo.mq.join(patch))
2075
2075
2076 ui.write('\n'.join(ph.message) + '\n')
2076 ui.write('\n'.join(ph.message) + '\n')
2077
2077
2078 def lastsavename(path):
2078 def lastsavename(path):
2079 (directory, base) = os.path.split(path)
2079 (directory, base) = os.path.split(path)
2080 names = os.listdir(directory)
2080 names = os.listdir(directory)
2081 namere = re.compile("%s.([0-9]+)" % base)
2081 namere = re.compile("%s.([0-9]+)" % base)
2082 maxindex = None
2082 maxindex = None
2083 maxname = None
2083 maxname = None
2084 for f in names:
2084 for f in names:
2085 m = namere.match(f)
2085 m = namere.match(f)
2086 if m:
2086 if m:
2087 index = int(m.group(1))
2087 index = int(m.group(1))
2088 if maxindex is None or index > maxindex:
2088 if maxindex is None or index > maxindex:
2089 maxindex = index
2089 maxindex = index
2090 maxname = f
2090 maxname = f
2091 if maxname:
2091 if maxname:
2092 return (os.path.join(directory, maxname), maxindex)
2092 return (os.path.join(directory, maxname), maxindex)
2093 return (None, None)
2093 return (None, None)
2094
2094
2095 def savename(path):
2095 def savename(path):
2096 (last, index) = lastsavename(path)
2096 (last, index) = lastsavename(path)
2097 if last is None:
2097 if last is None:
2098 index = 0
2098 index = 0
2099 newpath = path + ".%d" % (index + 1)
2099 newpath = path + ".%d" % (index + 1)
2100 return newpath
2100 return newpath
2101
2101
2102 def push(ui, repo, patch=None, **opts):
2102 def push(ui, repo, patch=None, **opts):
2103 """push the next patch onto the stack
2103 """push the next patch onto the stack
2104
2104
2105 When -f/--force is applied, all local changes in patched files
2105 When -f/--force is applied, all local changes in patched files
2106 will be lost.
2106 will be lost.
2107 """
2107 """
2108 q = repo.mq
2108 q = repo.mq
2109 mergeq = None
2109 mergeq = None
2110
2110
2111 if opts['merge']:
2111 if opts['merge']:
2112 if opts['name']:
2112 if opts['name']:
2113 newpath = repo.join(opts['name'])
2113 newpath = repo.join(opts['name'])
2114 else:
2114 else:
2115 newpath, i = lastsavename(q.path)
2115 newpath, i = lastsavename(q.path)
2116 if not newpath:
2116 if not newpath:
2117 ui.warn(_("no saved queues found, please use -n\n"))
2117 ui.warn(_("no saved queues found, please use -n\n"))
2118 return 1
2118 return 1
2119 mergeq = queue(ui, repo.join(""), newpath)
2119 mergeq = queue(ui, repo.join(""), newpath)
2120 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2120 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2121 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2121 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2122 mergeq=mergeq, all=opts.get('all'))
2122 mergeq=mergeq, all=opts.get('all'))
2123 return ret
2123 return ret
2124
2124
2125 def pop(ui, repo, patch=None, **opts):
2125 def pop(ui, repo, patch=None, **opts):
2126 """pop the current patch off the stack
2126 """pop the current patch off the stack
2127
2127
2128 By default, pops off the top of the patch stack. If given a patch
2128 By default, pops off the top of the patch stack. If given a patch
2129 name, keeps popping off patches until the named patch is at the
2129 name, keeps popping off patches until the named patch is at the
2130 top of the stack.
2130 top of the stack.
2131 """
2131 """
2132 localupdate = True
2132 localupdate = True
2133 if opts['name']:
2133 if opts['name']:
2134 q = queue(ui, repo.join(""), repo.join(opts['name']))
2134 q = queue(ui, repo.join(""), repo.join(opts['name']))
2135 ui.warn(_('using patch queue: %s\n') % q.path)
2135 ui.warn(_('using patch queue: %s\n') % q.path)
2136 localupdate = False
2136 localupdate = False
2137 else:
2137 else:
2138 q = repo.mq
2138 q = repo.mq
2139 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2139 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2140 all=opts['all'])
2140 all=opts['all'])
2141 q.save_dirty()
2141 q.save_dirty()
2142 return ret
2142 return ret
2143
2143
2144 def rename(ui, repo, patch, name=None, **opts):
2144 def rename(ui, repo, patch, name=None, **opts):
2145 """rename a patch
2145 """rename a patch
2146
2146
2147 With one argument, renames the current patch to PATCH1.
2147 With one argument, renames the current patch to PATCH1.
2148 With two arguments, renames PATCH1 to PATCH2."""
2148 With two arguments, renames PATCH1 to PATCH2."""
2149
2149
2150 q = repo.mq
2150 q = repo.mq
2151
2151
2152 if not name:
2152 if not name:
2153 name = patch
2153 name = patch
2154 patch = None
2154 patch = None
2155
2155
2156 if patch:
2156 if patch:
2157 patch = q.lookup(patch)
2157 patch = q.lookup(patch)
2158 else:
2158 else:
2159 if not q.applied:
2159 if not q.applied:
2160 ui.write(_('no patches applied\n'))
2160 ui.write(_('no patches applied\n'))
2161 return
2161 return
2162 patch = q.lookup('qtip')
2162 patch = q.lookup('qtip')
2163 absdest = q.join(name)
2163 absdest = q.join(name)
2164 if os.path.isdir(absdest):
2164 if os.path.isdir(absdest):
2165 name = normname(os.path.join(name, os.path.basename(patch)))
2165 name = normname(os.path.join(name, os.path.basename(patch)))
2166 absdest = q.join(name)
2166 absdest = q.join(name)
2167 if os.path.exists(absdest):
2167 if os.path.exists(absdest):
2168 raise util.Abort(_('%s already exists') % absdest)
2168 raise util.Abort(_('%s already exists') % absdest)
2169
2169
2170 if name in q.series:
2170 if name in q.series:
2171 raise util.Abort(_('A patch named %s already exists in the series file') % name)
2171 raise util.Abort(_('A patch named %s already exists in the series file') % name)
2172
2172
2173 if ui.verbose:
2173 if ui.verbose:
2174 ui.write('renaming %s to %s\n' % (patch, name))
2174 ui.write('renaming %s to %s\n' % (patch, name))
2175 i = q.find_series(patch)
2175 i = q.find_series(patch)
2176 guards = q.guard_re.findall(q.full_series[i])
2176 guards = q.guard_re.findall(q.full_series[i])
2177 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2177 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2178 q.parse_series()
2178 q.parse_series()
2179 q.series_dirty = 1
2179 q.series_dirty = 1
2180
2180
2181 info = q.isapplied(patch)
2181 info = q.isapplied(patch)
2182 if info:
2182 if info:
2183 q.applied[info[0]] = statusentry(info[1], name)
2183 q.applied[info[0]] = statusentry(info[1], name)
2184 q.applied_dirty = 1
2184 q.applied_dirty = 1
2185
2185
2186 util.rename(q.join(patch), absdest)
2186 util.rename(q.join(patch), absdest)
2187 r = q.qrepo()
2187 r = q.qrepo()
2188 if r:
2188 if r:
2189 wlock = r.wlock()
2189 wlock = r.wlock()
2190 try:
2190 try:
2191 if r.dirstate[patch] == 'a':
2191 if r.dirstate[patch] == 'a':
2192 r.dirstate.forget(patch)
2192 r.dirstate.forget(patch)
2193 r.dirstate.add(name)
2193 r.dirstate.add(name)
2194 else:
2194 else:
2195 if r.dirstate[name] == 'r':
2195 if r.dirstate[name] == 'r':
2196 r.undelete([name])
2196 r.undelete([name])
2197 r.copy(patch, name)
2197 r.copy(patch, name)
2198 r.remove([patch], False)
2198 r.remove([patch], False)
2199 finally:
2199 finally:
2200 wlock.release()
2200 wlock.release()
2201
2201
2202 q.save_dirty()
2202 q.save_dirty()
2203
2203
2204 def restore(ui, repo, rev, **opts):
2204 def restore(ui, repo, rev, **opts):
2205 """restore the queue state saved by a revision"""
2205 """restore the queue state saved by a revision"""
2206 rev = repo.lookup(rev)
2206 rev = repo.lookup(rev)
2207 q = repo.mq
2207 q = repo.mq
2208 q.restore(repo, rev, delete=opts['delete'],
2208 q.restore(repo, rev, delete=opts['delete'],
2209 qupdate=opts['update'])
2209 qupdate=opts['update'])
2210 q.save_dirty()
2210 q.save_dirty()
2211 return 0
2211 return 0
2212
2212
2213 def save(ui, repo, **opts):
2213 def save(ui, repo, **opts):
2214 """save current queue state"""
2214 """save current queue state"""
2215 q = repo.mq
2215 q = repo.mq
2216 message = cmdutil.logmessage(opts)
2216 message = cmdutil.logmessage(opts)
2217 ret = q.save(repo, msg=message)
2217 ret = q.save(repo, msg=message)
2218 if ret:
2218 if ret:
2219 return ret
2219 return ret
2220 q.save_dirty()
2220 q.save_dirty()
2221 if opts['copy']:
2221 if opts['copy']:
2222 path = q.path
2222 path = q.path
2223 if opts['name']:
2223 if opts['name']:
2224 newpath = os.path.join(q.basepath, opts['name'])
2224 newpath = os.path.join(q.basepath, opts['name'])
2225 if os.path.exists(newpath):
2225 if os.path.exists(newpath):
2226 if not os.path.isdir(newpath):
2226 if not os.path.isdir(newpath):
2227 raise util.Abort(_('destination %s exists and is not '
2227 raise util.Abort(_('destination %s exists and is not '
2228 'a directory') % newpath)
2228 'a directory') % newpath)
2229 if not opts['force']:
2229 if not opts['force']:
2230 raise util.Abort(_('destination %s exists, '
2230 raise util.Abort(_('destination %s exists, '
2231 'use -f to force') % newpath)
2231 'use -f to force') % newpath)
2232 else:
2232 else:
2233 newpath = savename(path)
2233 newpath = savename(path)
2234 ui.warn(_("copy %s to %s\n") % (path, newpath))
2234 ui.warn(_("copy %s to %s\n") % (path, newpath))
2235 util.copyfiles(path, newpath)
2235 util.copyfiles(path, newpath)
2236 if opts['empty']:
2236 if opts['empty']:
2237 try:
2237 try:
2238 os.unlink(q.join(q.status_path))
2238 os.unlink(q.join(q.status_path))
2239 except:
2239 except:
2240 pass
2240 pass
2241 return 0
2241 return 0
2242
2242
2243 def strip(ui, repo, rev, **opts):
2243 def strip(ui, repo, rev, **opts):
2244 """strip a revision and all its descendants from the repository
2244 """strip a revision and all its descendants from the repository
2245
2245
2246 If one of the working directory's parent revisions is stripped, the
2246 If one of the working directory's parent revisions is stripped, the
2247 working directory will be updated to the parent of the stripped
2247 working directory will be updated to the parent of the stripped
2248 revision.
2248 revision.
2249 """
2249 """
2250 backup = 'all'
2250 backup = 'all'
2251 if opts['backup']:
2251 if opts['backup']:
2252 backup = 'strip'
2252 backup = 'strip'
2253 elif opts['nobackup']:
2253 elif opts['nobackup']:
2254 backup = 'none'
2254 backup = 'none'
2255
2255
2256 rev = repo.lookup(rev)
2256 rev = repo.lookup(rev)
2257 p = repo.dirstate.parents()
2257 p = repo.dirstate.parents()
2258 cl = repo.changelog
2258 cl = repo.changelog
2259 update = True
2259 update = True
2260 if p[0] == nullid:
2260 if p[0] == nullid:
2261 update = False
2261 update = False
2262 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2262 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2263 update = False
2263 update = False
2264 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2264 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2265 update = False
2265 update = False
2266
2266
2267 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2267 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2268 return 0
2268 return 0
2269
2269
2270 def select(ui, repo, *args, **opts):
2270 def select(ui, repo, *args, **opts):
2271 '''set or print guarded patches to push
2271 '''set or print guarded patches to push
2272
2272
2273 Use the qguard command to set or print guards on patch, then use
2273 Use the qguard command to set or print guards on patch, then use
2274 qselect to tell mq which guards to use. A patch will be pushed if
2274 qselect to tell mq which guards to use. A patch will be pushed if
2275 it has no guards or any positive guards match the currently
2275 it has no guards or any positive guards match the currently
2276 selected guard, but will not be pushed if any negative guards
2276 selected guard, but will not be pushed if any negative guards
2277 match the current guard. For example:
2277 match the current guard. For example:
2278
2278
2279 qguard foo.patch -stable (negative guard)
2279 qguard foo.patch -stable (negative guard)
2280 qguard bar.patch +stable (positive guard)
2280 qguard bar.patch +stable (positive guard)
2281 qselect stable
2281 qselect stable
2282
2282
2283 This activates the "stable" guard. mq will skip foo.patch (because
2283 This activates the "stable" guard. mq will skip foo.patch (because
2284 it has a negative match) but push bar.patch (because it has a
2284 it has a negative match) but push bar.patch (because it has a
2285 positive match).
2285 positive match).
2286
2286
2287 With no arguments, prints the currently active guards.
2287 With no arguments, prints the currently active guards.
2288 With one argument, sets the active guard.
2288 With one argument, sets the active guard.
2289
2289
2290 Use -n/--none to deactivate guards (no other arguments needed).
2290 Use -n/--none to deactivate guards (no other arguments needed).
2291 When no guards are active, patches with positive guards are
2291 When no guards are active, patches with positive guards are
2292 skipped and patches with negative guards are pushed.
2292 skipped and patches with negative guards are pushed.
2293
2293
2294 qselect can change the guards on applied patches. It does not pop
2294 qselect can change the guards on applied patches. It does not pop
2295 guarded patches by default. Use --pop to pop back to the last
2295 guarded patches by default. Use --pop to pop back to the last
2296 applied patch that is not guarded. Use --reapply (which implies
2296 applied patch that is not guarded. Use --reapply (which implies
2297 --pop) to push back to the current patch afterwards, but skip
2297 --pop) to push back to the current patch afterwards, but skip
2298 guarded patches.
2298 guarded patches.
2299
2299
2300 Use -s/--series to print a list of all guards in the series file
2300 Use -s/--series to print a list of all guards in the series file
2301 (no other arguments needed). Use -v for more information.'''
2301 (no other arguments needed). Use -v for more information.'''
2302
2302
2303 q = repo.mq
2303 q = repo.mq
2304 guards = q.active()
2304 guards = q.active()
2305 if args or opts['none']:
2305 if args or opts['none']:
2306 old_unapplied = q.unapplied(repo)
2306 old_unapplied = q.unapplied(repo)
2307 old_guarded = [i for i in xrange(len(q.applied)) if
2307 old_guarded = [i for i in xrange(len(q.applied)) if
2308 not q.pushable(i)[0]]
2308 not q.pushable(i)[0]]
2309 q.set_active(args)
2309 q.set_active(args)
2310 q.save_dirty()
2310 q.save_dirty()
2311 if not args:
2311 if not args:
2312 ui.status(_('guards deactivated\n'))
2312 ui.status(_('guards deactivated\n'))
2313 if not opts['pop'] and not opts['reapply']:
2313 if not opts['pop'] and not opts['reapply']:
2314 unapplied = q.unapplied(repo)
2314 unapplied = q.unapplied(repo)
2315 guarded = [i for i in xrange(len(q.applied))
2315 guarded = [i for i in xrange(len(q.applied))
2316 if not q.pushable(i)[0]]
2316 if not q.pushable(i)[0]]
2317 if len(unapplied) != len(old_unapplied):
2317 if len(unapplied) != len(old_unapplied):
2318 ui.status(_('number of unguarded, unapplied patches has '
2318 ui.status(_('number of unguarded, unapplied patches has '
2319 'changed from %d to %d\n') %
2319 'changed from %d to %d\n') %
2320 (len(old_unapplied), len(unapplied)))
2320 (len(old_unapplied), len(unapplied)))
2321 if len(guarded) != len(old_guarded):
2321 if len(guarded) != len(old_guarded):
2322 ui.status(_('number of guarded, applied patches has changed '
2322 ui.status(_('number of guarded, applied patches has changed '
2323 'from %d to %d\n') %
2323 'from %d to %d\n') %
2324 (len(old_guarded), len(guarded)))
2324 (len(old_guarded), len(guarded)))
2325 elif opts['series']:
2325 elif opts['series']:
2326 guards = {}
2326 guards = {}
2327 noguards = 0
2327 noguards = 0
2328 for gs in q.series_guards:
2328 for gs in q.series_guards:
2329 if not gs:
2329 if not gs:
2330 noguards += 1
2330 noguards += 1
2331 for g in gs:
2331 for g in gs:
2332 guards.setdefault(g, 0)
2332 guards.setdefault(g, 0)
2333 guards[g] += 1
2333 guards[g] += 1
2334 if ui.verbose:
2334 if ui.verbose:
2335 guards['NONE'] = noguards
2335 guards['NONE'] = noguards
2336 guards = guards.items()
2336 guards = guards.items()
2337 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
2337 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
2338 if guards:
2338 if guards:
2339 ui.note(_('guards in series file:\n'))
2339 ui.note(_('guards in series file:\n'))
2340 for guard, count in guards:
2340 for guard, count in guards:
2341 ui.note('%2d ' % count)
2341 ui.note('%2d ' % count)
2342 ui.write(guard, '\n')
2342 ui.write(guard, '\n')
2343 else:
2343 else:
2344 ui.note(_('no guards in series file\n'))
2344 ui.note(_('no guards in series file\n'))
2345 else:
2345 else:
2346 if guards:
2346 if guards:
2347 ui.note(_('active guards:\n'))
2347 ui.note(_('active guards:\n'))
2348 for g in guards:
2348 for g in guards:
2349 ui.write(g, '\n')
2349 ui.write(g, '\n')
2350 else:
2350 else:
2351 ui.write(_('no active guards\n'))
2351 ui.write(_('no active guards\n'))
2352 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2352 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2353 popped = False
2353 popped = False
2354 if opts['pop'] or opts['reapply']:
2354 if opts['pop'] or opts['reapply']:
2355 for i in xrange(len(q.applied)):
2355 for i in xrange(len(q.applied)):
2356 pushable, reason = q.pushable(i)
2356 pushable, reason = q.pushable(i)
2357 if not pushable:
2357 if not pushable:
2358 ui.status(_('popping guarded patches\n'))
2358 ui.status(_('popping guarded patches\n'))
2359 popped = True
2359 popped = True
2360 if i == 0:
2360 if i == 0:
2361 q.pop(repo, all=True)
2361 q.pop(repo, all=True)
2362 else:
2362 else:
2363 q.pop(repo, i-1)
2363 q.pop(repo, i-1)
2364 break
2364 break
2365 if popped:
2365 if popped:
2366 try:
2366 try:
2367 if reapply:
2367 if reapply:
2368 ui.status(_('reapplying unguarded patches\n'))
2368 ui.status(_('reapplying unguarded patches\n'))
2369 q.push(repo, reapply)
2369 q.push(repo, reapply)
2370 finally:
2370 finally:
2371 q.save_dirty()
2371 q.save_dirty()
2372
2372
2373 def finish(ui, repo, *revrange, **opts):
2373 def finish(ui, repo, *revrange, **opts):
2374 """move applied patches into repository history
2374 """move applied patches into repository history
2375
2375
2376 Finishes the specified revisions (corresponding to applied
2376 Finishes the specified revisions (corresponding to applied
2377 patches) by moving them out of mq control into regular repository
2377 patches) by moving them out of mq control into regular repository
2378 history.
2378 history.
2379
2379
2380 Accepts a revision range or the -a/--applied option. If --applied
2380 Accepts a revision range or the -a/--applied option. If --applied
2381 is specified, all applied mq revisions are removed from mq
2381 is specified, all applied mq revisions are removed from mq
2382 control. Otherwise, the given revisions must be at the base of the
2382 control. Otherwise, the given revisions must be at the base of the
2383 stack of applied patches.
2383 stack of applied patches.
2384
2384
2385 This can be especially useful if your changes have been applied to
2385 This can be especially useful if your changes have been applied to
2386 an upstream repository, or if you are about to push your changes
2386 an upstream repository, or if you are about to push your changes
2387 to upstream.
2387 to upstream.
2388 """
2388 """
2389 if not opts['applied'] and not revrange:
2389 if not opts['applied'] and not revrange:
2390 raise util.Abort(_('no revisions specified'))
2390 raise util.Abort(_('no revisions specified'))
2391 elif opts['applied']:
2391 elif opts['applied']:
2392 revrange = ('qbase:qtip',) + revrange
2392 revrange = ('qbase:qtip',) + revrange
2393
2393
2394 q = repo.mq
2394 q = repo.mq
2395 if not q.applied:
2395 if not q.applied:
2396 ui.status(_('no patches applied\n'))
2396 ui.status(_('no patches applied\n'))
2397 return 0
2397 return 0
2398
2398
2399 revs = cmdutil.revrange(repo, revrange)
2399 revs = cmdutil.revrange(repo, revrange)
2400 q.finish(repo, revs)
2400 q.finish(repo, revs)
2401 q.save_dirty()
2401 q.save_dirty()
2402 return 0
2402 return 0
2403
2403
2404 def reposetup(ui, repo):
2404 def reposetup(ui, repo):
2405 class mqrepo(repo.__class__):
2405 class mqrepo(repo.__class__):
2406 @util.propertycache
2406 @util.propertycache
2407 def mq(self):
2407 def mq(self):
2408 return queue(self.ui, self.join(""))
2408 return queue(self.ui, self.join(""))
2409
2409
2410 def abort_if_wdir_patched(self, errmsg, force=False):
2410 def abort_if_wdir_patched(self, errmsg, force=False):
2411 if self.mq.applied and not force:
2411 if self.mq.applied and not force:
2412 parent = hex(self.dirstate.parents()[0])
2412 parent = hex(self.dirstate.parents()[0])
2413 if parent in [s.rev for s in self.mq.applied]:
2413 if parent in [s.rev for s in self.mq.applied]:
2414 raise util.Abort(errmsg)
2414 raise util.Abort(errmsg)
2415
2415
2416 def commit(self, text="", user=None, date=None, match=None,
2416 def commit(self, text="", user=None, date=None, match=None,
2417 force=False, editor=False, extra={}):
2417 force=False, editor=False, extra={}):
2418 self.abort_if_wdir_patched(
2418 self.abort_if_wdir_patched(
2419 _('cannot commit over an applied mq patch'),
2419 _('cannot commit over an applied mq patch'),
2420 force)
2420 force)
2421
2421
2422 return super(mqrepo, self).commit(text, user, date, match, force,
2422 return super(mqrepo, self).commit(text, user, date, match, force,
2423 editor, extra)
2423 editor, extra)
2424
2424
2425 def push(self, remote, force=False, revs=None):
2425 def push(self, remote, force=False, revs=None):
2426 if self.mq.applied and not force and not revs:
2426 if self.mq.applied and not force and not revs:
2427 raise util.Abort(_('source has mq patches applied'))
2427 raise util.Abort(_('source has mq patches applied'))
2428 return super(mqrepo, self).push(remote, force, revs)
2428 return super(mqrepo, self).push(remote, force, revs)
2429
2429
2430 def tags(self):
2430 def tags(self):
2431 if self.tagscache:
2431 if self.tagscache:
2432 return self.tagscache
2432 return self.tagscache
2433
2433
2434 tagscache = super(mqrepo, self).tags()
2434 tagscache = super(mqrepo, self).tags()
2435
2435
2436 q = self.mq
2436 q = self.mq
2437 if not q.applied:
2437 if not q.applied:
2438 return tagscache
2438 return tagscache
2439
2439
2440 mqtags = [(bin(patch.rev), patch.name) for patch in q.applied]
2440 mqtags = [(bin(patch.rev), patch.name) for patch in q.applied]
2441
2441
2442 if mqtags[-1][0] not in self.changelog.nodemap:
2442 if mqtags[-1][0] not in self.changelog.nodemap:
2443 self.ui.warn(_('mq status file refers to unknown node %s\n')
2443 self.ui.warn(_('mq status file refers to unknown node %s\n')
2444 % short(mqtags[-1][0]))
2444 % short(mqtags[-1][0]))
2445 return tagscache
2445 return tagscache
2446
2446
2447 mqtags.append((mqtags[-1][0], 'qtip'))
2447 mqtags.append((mqtags[-1][0], 'qtip'))
2448 mqtags.append((mqtags[0][0], 'qbase'))
2448 mqtags.append((mqtags[0][0], 'qbase'))
2449 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2449 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2450 for patch in mqtags:
2450 for patch in mqtags:
2451 if patch[1] in tagscache:
2451 if patch[1] in tagscache:
2452 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2452 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2453 % patch[1])
2453 % patch[1])
2454 else:
2454 else:
2455 tagscache[patch[1]] = patch[0]
2455 tagscache[patch[1]] = patch[0]
2456
2456
2457 return tagscache
2457 return tagscache
2458
2458
2459 def _branchtags(self, partial, lrev):
2459 def _branchtags(self, partial, lrev):
2460 q = self.mq
2460 q = self.mq
2461 if not q.applied:
2461 if not q.applied:
2462 return super(mqrepo, self)._branchtags(partial, lrev)
2462 return super(mqrepo, self)._branchtags(partial, lrev)
2463
2463
2464 cl = self.changelog
2464 cl = self.changelog
2465 qbasenode = bin(q.applied[0].rev)
2465 qbasenode = bin(q.applied[0].rev)
2466 if qbasenode not in cl.nodemap:
2466 if qbasenode not in cl.nodemap:
2467 self.ui.warn(_('mq status file refers to unknown node %s\n')
2467 self.ui.warn(_('mq status file refers to unknown node %s\n')
2468 % short(qbasenode))
2468 % short(qbasenode))
2469 return super(mqrepo, self)._branchtags(partial, lrev)
2469 return super(mqrepo, self)._branchtags(partial, lrev)
2470
2470
2471 qbase = cl.rev(qbasenode)
2471 qbase = cl.rev(qbasenode)
2472 start = lrev + 1
2472 start = lrev + 1
2473 if start < qbase:
2473 if start < qbase:
2474 # update the cache (excluding the patches) and save it
2474 # update the cache (excluding the patches) and save it
2475 self._updatebranchcache(partial, lrev+1, qbase)
2475 self._updatebranchcache(partial, lrev+1, qbase)
2476 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2476 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2477 start = qbase
2477 start = qbase
2478 # if start = qbase, the cache is as updated as it should be.
2478 # if start = qbase, the cache is as updated as it should be.
2479 # if start > qbase, the cache includes (part of) the patches.
2479 # if start > qbase, the cache includes (part of) the patches.
2480 # we might as well use it, but we won't save it.
2480 # we might as well use it, but we won't save it.
2481
2481
2482 # update the cache up to the tip
2482 # update the cache up to the tip
2483 self._updatebranchcache(partial, start, len(cl))
2483 self._updatebranchcache(partial, start, len(cl))
2484
2484
2485 return partial
2485 return partial
2486
2486
2487 if repo.local():
2487 if repo.local():
2488 repo.__class__ = mqrepo
2488 repo.__class__ = mqrepo
2489
2489
2490 def mqimport(orig, ui, repo, *args, **kwargs):
2490 def mqimport(orig, ui, repo, *args, **kwargs):
2491 if hasattr(repo, 'abort_if_wdir_patched'):
2491 if hasattr(repo, 'abort_if_wdir_patched'):
2492 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2492 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2493 kwargs.get('force'))
2493 kwargs.get('force'))
2494 return orig(ui, repo, *args, **kwargs)
2494 return orig(ui, repo, *args, **kwargs)
2495
2495
2496 def uisetup(ui):
2496 def uisetup(ui):
2497 extensions.wrapcommand(commands.table, 'import', mqimport)
2497 extensions.wrapcommand(commands.table, 'import', mqimport)
2498
2498
2499 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2499 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2500
2500
2501 cmdtable = {
2501 cmdtable = {
2502 "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')),
2502 "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')),
2503 "qclone":
2503 "qclone":
2504 (clone,
2504 (clone,
2505 [('', 'pull', None, _('use pull protocol to copy metadata')),
2505 [('', 'pull', None, _('use pull protocol to copy metadata')),
2506 ('U', 'noupdate', None, _('do not update the new working directories')),
2506 ('U', 'noupdate', None, _('do not update the new working directories')),
2507 ('', 'uncompressed', None,
2507 ('', 'uncompressed', None,
2508 _('use uncompressed transfer (fast over LAN)')),
2508 _('use uncompressed transfer (fast over LAN)')),
2509 ('p', 'patches', '', _('location of source patch repository')),
2509 ('p', 'patches', '', _('location of source patch repository')),
2510 ] + commands.remoteopts,
2510 ] + commands.remoteopts,
2511 _('hg qclone [OPTION]... SOURCE [DEST]')),
2511 _('hg qclone [OPTION]... SOURCE [DEST]')),
2512 "qcommit|qci":
2512 "qcommit|qci":
2513 (commit,
2513 (commit,
2514 commands.table["^commit|ci"][1],
2514 commands.table["^commit|ci"][1],
2515 _('hg qcommit [OPTION]... [FILE]...')),
2515 _('hg qcommit [OPTION]... [FILE]...')),
2516 "^qdiff":
2516 "^qdiff":
2517 (diff,
2517 (diff,
2518 commands.diffopts + commands.diffopts2 + commands.walkopts,
2518 commands.diffopts + commands.diffopts2 + commands.walkopts,
2519 _('hg qdiff [OPTION]... [FILE]...')),
2519 _('hg qdiff [OPTION]... [FILE]...')),
2520 "qdelete|qremove|qrm":
2520 "qdelete|qremove|qrm":
2521 (delete,
2521 (delete,
2522 [('k', 'keep', None, _('keep patch file')),
2522 [('k', 'keep', None, _('keep patch file')),
2523 ('r', 'rev', [], _('stop managing a revision'))],
2523 ('r', 'rev', [], _('stop managing a revision'))],
2524 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2524 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2525 'qfold':
2525 'qfold':
2526 (fold,
2526 (fold,
2527 [('e', 'edit', None, _('edit patch header')),
2527 [('e', 'edit', None, _('edit patch header')),
2528 ('k', 'keep', None, _('keep folded patch files')),
2528 ('k', 'keep', None, _('keep folded patch files')),
2529 ] + commands.commitopts,
2529 ] + commands.commitopts,
2530 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2530 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2531 'qgoto':
2531 'qgoto':
2532 (goto,
2532 (goto,
2533 [('f', 'force', None, _('overwrite any local changes'))],
2533 [('f', 'force', None, _('overwrite any local changes'))],
2534 _('hg qgoto [OPTION]... PATCH')),
2534 _('hg qgoto [OPTION]... PATCH')),
2535 'qguard':
2535 'qguard':
2536 (guard,
2536 (guard,
2537 [('l', 'list', None, _('list all patches and guards')),
2537 [('l', 'list', None, _('list all patches and guards')),
2538 ('n', 'none', None, _('drop all guards'))],
2538 ('n', 'none', None, _('drop all guards'))],
2539 _('hg qguard [-l] [-n] -- [PATCH] [+GUARD]... [-GUARD]...')),
2539 _('hg qguard [-l] [-n] -- [PATCH] [+GUARD]... [-GUARD]...')),
2540 'qheader': (header, [], _('hg qheader [PATCH]')),
2540 'qheader': (header, [], _('hg qheader [PATCH]')),
2541 "^qimport":
2541 "^qimport":
2542 (qimport,
2542 (qimport,
2543 [('e', 'existing', None, _('import file in patch directory')),
2543 [('e', 'existing', None, _('import file in patch directory')),
2544 ('n', 'name', '', _('name of patch file')),
2544 ('n', 'name', '', _('name of patch file')),
2545 ('f', 'force', None, _('overwrite existing files')),
2545 ('f', 'force', None, _('overwrite existing files')),
2546 ('r', 'rev', [], _('place existing revisions under mq control')),
2546 ('r', 'rev', [], _('place existing revisions under mq control')),
2547 ('g', 'git', None, _('use git extended diff format')),
2547 ('g', 'git', None, _('use git extended diff format')),
2548 ('P', 'push', None, _('qpush after importing'))],
2548 ('P', 'push', None, _('qpush after importing'))],
2549 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2549 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2550 "^qinit":
2550 "^qinit":
2551 (init,
2551 (init,
2552 [('c', 'create-repo', None, _('create queue repository'))],
2552 [('c', 'create-repo', None, _('create queue repository'))],
2553 _('hg qinit [-c]')),
2553 _('hg qinit [-c]')),
2554 "qnew":
2554 "qnew":
2555 (new,
2555 (new,
2556 [('e', 'edit', None, _('edit commit message')),
2556 [('e', 'edit', None, _('edit commit message')),
2557 ('f', 'force', None, _('import uncommitted changes into patch')),
2557 ('f', 'force', None, _('import uncommitted changes into patch')),
2558 ('g', 'git', None, _('use git extended diff format')),
2558 ('g', 'git', None, _('use git extended diff format')),
2559 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2559 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2560 ('u', 'user', '', _('add "From: <given user>" to patch')),
2560 ('u', 'user', '', _('add "From: <given user>" to patch')),
2561 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2561 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2562 ('d', 'date', '', _('add "Date: <given date>" to patch'))
2562 ('d', 'date', '', _('add "Date: <given date>" to patch'))
2563 ] + commands.walkopts + commands.commitopts,
2563 ] + commands.walkopts + commands.commitopts,
2564 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2564 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2565 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2565 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2566 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2566 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2567 "^qpop":
2567 "^qpop":
2568 (pop,
2568 (pop,
2569 [('a', 'all', None, _('pop all patches')),
2569 [('a', 'all', None, _('pop all patches')),
2570 ('n', 'name', '', _('queue name to pop')),
2570 ('n', 'name', '', _('queue name to pop')),
2571 ('f', 'force', None, _('forget any local changes'))],
2571 ('f', 'force', None, _('forget any local changes'))],
2572 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2572 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2573 "^qpush":
2573 "^qpush":
2574 (push,
2574 (push,
2575 [('f', 'force', None, _('apply if the patch has rejects')),
2575 [('f', 'force', None, _('apply if the patch has rejects')),
2576 ('l', 'list', None, _('list patch name in commit text')),
2576 ('l', 'list', None, _('list patch name in commit text')),
2577 ('a', 'all', None, _('apply all patches')),
2577 ('a', 'all', None, _('apply all patches')),
2578 ('m', 'merge', None, _('merge from another queue')),
2578 ('m', 'merge', None, _('merge from another queue')),
2579 ('n', 'name', '', _('merge queue name'))],
2579 ('n', 'name', '', _('merge queue name'))],
2580 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2580 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2581 "^qrefresh":
2581 "^qrefresh":
2582 (refresh,
2582 (refresh,
2583 [('e', 'edit', None, _('edit commit message')),
2583 [('e', 'edit', None, _('edit commit message')),
2584 ('g', 'git', None, _('use git extended diff format')),
2584 ('g', 'git', None, _('use git extended diff format')),
2585 ('s', 'short', None, _('refresh only files already in the patch and specified files')),
2585 ('s', 'short', None, _('refresh only files already in the patch and specified files')),
2586 ('U', 'currentuser', None, _('add/update "From: <current user>" in patch')),
2586 ('U', 'currentuser', None, _('add/update "From: <current user>" in patch')),
2587 ('u', 'user', '', _('add/update "From: <given user>" in patch')),
2587 ('u', 'user', '', _('add/update "From: <given user>" in patch')),
2588 ('D', 'currentdate', None, _('update "Date: <current date>" in patch (if present)')),
2588 ('D', 'currentdate', None, _('update "Date: <current date>" in patch (if present)')),
2589 ('d', 'date', '', _('update "Date: <given date>" in patch (if present)'))
2589 ('d', 'date', '', _('update "Date: <given date>" in patch (if present)'))
2590 ] + commands.walkopts + commands.commitopts,
2590 ] + commands.walkopts + commands.commitopts,
2591 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2591 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2592 'qrename|qmv':
2592 'qrename|qmv':
2593 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2593 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2594 "qrestore":
2594 "qrestore":
2595 (restore,
2595 (restore,
2596 [('d', 'delete', None, _('delete save entry')),
2596 [('d', 'delete', None, _('delete save entry')),
2597 ('u', 'update', None, _('update queue working directory'))],
2597 ('u', 'update', None, _('update queue working directory'))],
2598 _('hg qrestore [-d] [-u] REV')),
2598 _('hg qrestore [-d] [-u] REV')),
2599 "qsave":
2599 "qsave":
2600 (save,
2600 (save,
2601 [('c', 'copy', None, _('copy patch directory')),
2601 [('c', 'copy', None, _('copy patch directory')),
2602 ('n', 'name', '', _('copy directory name')),
2602 ('n', 'name', '', _('copy directory name')),
2603 ('e', 'empty', None, _('clear queue status file')),
2603 ('e', 'empty', None, _('clear queue status file')),
2604 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2604 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2605 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2605 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2606 "qselect":
2606 "qselect":
2607 (select,
2607 (select,
2608 [('n', 'none', None, _('disable all guards')),
2608 [('n', 'none', None, _('disable all guards')),
2609 ('s', 'series', None, _('list all guards in series file')),
2609 ('s', 'series', None, _('list all guards in series file')),
2610 ('', 'pop', None, _('pop to before first guarded applied patch')),
2610 ('', 'pop', None, _('pop to before first guarded applied patch')),
2611 ('', 'reapply', None, _('pop, then reapply patches'))],
2611 ('', 'reapply', None, _('pop, then reapply patches'))],
2612 _('hg qselect [OPTION]... [GUARD]...')),
2612 _('hg qselect [OPTION]... [GUARD]...')),
2613 "qseries":
2613 "qseries":
2614 (series,
2614 (series,
2615 [('m', 'missing', None, _('print patches not in series')),
2615 [('m', 'missing', None, _('print patches not in series')),
2616 ] + seriesopts,
2616 ] + seriesopts,
2617 _('hg qseries [-ms]')),
2617 _('hg qseries [-ms]')),
2618 "^strip":
2618 "^strip":
2619 (strip,
2619 (strip,
2620 [('f', 'force', None, _('force removal with local changes')),
2620 [('f', 'force', None, _('force removal with local changes')),
2621 ('b', 'backup', None, _('bundle unrelated changesets')),
2621 ('b', 'backup', None, _('bundle unrelated changesets')),
2622 ('n', 'nobackup', None, _('no backups'))],
2622 ('n', 'nobackup', None, _('no backups'))],
2623 _('hg strip [-f] [-b] [-n] REV')),
2623 _('hg strip [-f] [-b] [-n] REV')),
2624 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2624 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2625 "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')),
2625 "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')),
2626 "qfinish":
2626 "qfinish":
2627 (finish,
2627 (finish,
2628 [('a', 'applied', None, _('finish all applied changesets'))],
2628 [('a', 'applied', None, _('finish all applied changesets'))],
2629 _('hg qfinish [-a] [REV...]')),
2629 _('hg qfinish [-a] [REV...]')),
2630 }
2630 }
@@ -1,547 +1,548 b''
1 # record.py
1 # record.py
2 #
2 #
3 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
3 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 '''interactive change selection during commit or qrefresh'''
8 '''interactive change selection during commit or qrefresh'''
9
9
10 from mercurial.i18n import gettext, _
10 from mercurial.i18n import gettext, _
11 from mercurial import cmdutil, commands, extensions, hg, mdiff, patch
11 from mercurial import cmdutil, commands, extensions, hg, mdiff, patch
12 from mercurial import util
12 from mercurial import util
13 import copy, cStringIO, errno, operator, os, re, tempfile
13 import copy, cStringIO, errno, operator, os, re, tempfile
14
14
15 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
15 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
16
16
17 def scanpatch(fp):
17 def scanpatch(fp):
18 """like patch.iterhunks, but yield different events
18 """like patch.iterhunks, but yield different events
19
19
20 - ('file', [header_lines + fromfile + tofile])
20 - ('file', [header_lines + fromfile + tofile])
21 - ('context', [context_lines])
21 - ('context', [context_lines])
22 - ('hunk', [hunk_lines])
22 - ('hunk', [hunk_lines])
23 - ('range', (-start,len, +start,len, diffp))
23 - ('range', (-start,len, +start,len, diffp))
24 """
24 """
25 lr = patch.linereader(fp)
25 lr = patch.linereader(fp)
26
26
27 def scanwhile(first, p):
27 def scanwhile(first, p):
28 """scan lr while predicate holds"""
28 """scan lr while predicate holds"""
29 lines = [first]
29 lines = [first]
30 while True:
30 while True:
31 line = lr.readline()
31 line = lr.readline()
32 if not line:
32 if not line:
33 break
33 break
34 if p(line):
34 if p(line):
35 lines.append(line)
35 lines.append(line)
36 else:
36 else:
37 lr.push(line)
37 lr.push(line)
38 break
38 break
39 return lines
39 return lines
40
40
41 while True:
41 while True:
42 line = lr.readline()
42 line = lr.readline()
43 if not line:
43 if not line:
44 break
44 break
45 if line.startswith('diff --git a/'):
45 if line.startswith('diff --git a/'):
46 def notheader(line):
46 def notheader(line):
47 s = line.split(None, 1)
47 s = line.split(None, 1)
48 return not s or s[0] not in ('---', 'diff')
48 return not s or s[0] not in ('---', 'diff')
49 header = scanwhile(line, notheader)
49 header = scanwhile(line, notheader)
50 fromfile = lr.readline()
50 fromfile = lr.readline()
51 if fromfile.startswith('---'):
51 if fromfile.startswith('---'):
52 tofile = lr.readline()
52 tofile = lr.readline()
53 header += [fromfile, tofile]
53 header += [fromfile, tofile]
54 else:
54 else:
55 lr.push(fromfile)
55 lr.push(fromfile)
56 yield 'file', header
56 yield 'file', header
57 elif line[0] == ' ':
57 elif line[0] == ' ':
58 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
58 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
59 elif line[0] in '-+':
59 elif line[0] in '-+':
60 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
60 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
61 else:
61 else:
62 m = lines_re.match(line)
62 m = lines_re.match(line)
63 if m:
63 if m:
64 yield 'range', m.groups()
64 yield 'range', m.groups()
65 else:
65 else:
66 raise patch.PatchError('unknown patch content: %r' % line)
66 raise patch.PatchError('unknown patch content: %r' % line)
67
67
68 class header(object):
68 class header(object):
69 """patch header
69 """patch header
70
70
71 XXX shoudn't we move this to mercurial/patch.py ?
71 XXX shoudn't we move this to mercurial/patch.py ?
72 """
72 """
73 diff_re = re.compile('diff --git a/(.*) b/(.*)$')
73 diff_re = re.compile('diff --git a/(.*) b/(.*)$')
74 allhunks_re = re.compile('(?:index|new file|deleted file) ')
74 allhunks_re = re.compile('(?:index|new file|deleted file) ')
75 pretty_re = re.compile('(?:new file|deleted file) ')
75 pretty_re = re.compile('(?:new file|deleted file) ')
76 special_re = re.compile('(?:index|new|deleted|copy|rename) ')
76 special_re = re.compile('(?:index|new|deleted|copy|rename) ')
77
77
78 def __init__(self, header):
78 def __init__(self, header):
79 self.header = header
79 self.header = header
80 self.hunks = []
80 self.hunks = []
81
81
82 def binary(self):
82 def binary(self):
83 for h in self.header:
83 for h in self.header:
84 if h.startswith('index '):
84 if h.startswith('index '):
85 return True
85 return True
86
86
87 def pretty(self, fp):
87 def pretty(self, fp):
88 for h in self.header:
88 for h in self.header:
89 if h.startswith('index '):
89 if h.startswith('index '):
90 fp.write(_('this modifies a binary file (all or nothing)\n'))
90 fp.write(_('this modifies a binary file (all or nothing)\n'))
91 break
91 break
92 if self.pretty_re.match(h):
92 if self.pretty_re.match(h):
93 fp.write(h)
93 fp.write(h)
94 if self.binary():
94 if self.binary():
95 fp.write(_('this is a binary file\n'))
95 fp.write(_('this is a binary file\n'))
96 break
96 break
97 if h.startswith('---'):
97 if h.startswith('---'):
98 fp.write(_('%d hunks, %d lines changed\n') %
98 fp.write(_('%d hunks, %d lines changed\n') %
99 (len(self.hunks),
99 (len(self.hunks),
100 sum([h.added + h.removed for h in self.hunks])))
100 sum([h.added + h.removed for h in self.hunks])))
101 break
101 break
102 fp.write(h)
102 fp.write(h)
103
103
104 def write(self, fp):
104 def write(self, fp):
105 fp.write(''.join(self.header))
105 fp.write(''.join(self.header))
106
106
107 def allhunks(self):
107 def allhunks(self):
108 for h in self.header:
108 for h in self.header:
109 if self.allhunks_re.match(h):
109 if self.allhunks_re.match(h):
110 return True
110 return True
111
111
112 def files(self):
112 def files(self):
113 fromfile, tofile = self.diff_re.match(self.header[0]).groups()
113 fromfile, tofile = self.diff_re.match(self.header[0]).groups()
114 if fromfile == tofile:
114 if fromfile == tofile:
115 return [fromfile]
115 return [fromfile]
116 return [fromfile, tofile]
116 return [fromfile, tofile]
117
117
118 def filename(self):
118 def filename(self):
119 return self.files()[-1]
119 return self.files()[-1]
120
120
121 def __repr__(self):
121 def __repr__(self):
122 return '<header %s>' % (' '.join(map(repr, self.files())))
122 return '<header %s>' % (' '.join(map(repr, self.files())))
123
123
124 def special(self):
124 def special(self):
125 for h in self.header:
125 for h in self.header:
126 if self.special_re.match(h):
126 if self.special_re.match(h):
127 return True
127 return True
128
128
129 def countchanges(hunk):
129 def countchanges(hunk):
130 """hunk -> (n+,n-)"""
130 """hunk -> (n+,n-)"""
131 add = len([h for h in hunk if h[0] == '+'])
131 add = len([h for h in hunk if h[0] == '+'])
132 rem = len([h for h in hunk if h[0] == '-'])
132 rem = len([h for h in hunk if h[0] == '-'])
133 return add, rem
133 return add, rem
134
134
135 class hunk(object):
135 class hunk(object):
136 """patch hunk
136 """patch hunk
137
137
138 XXX shouldn't we merge this with patch.hunk ?
138 XXX shouldn't we merge this with patch.hunk ?
139 """
139 """
140 maxcontext = 3
140 maxcontext = 3
141
141
142 def __init__(self, header, fromline, toline, proc, before, hunk, after):
142 def __init__(self, header, fromline, toline, proc, before, hunk, after):
143 def trimcontext(number, lines):
143 def trimcontext(number, lines):
144 delta = len(lines) - self.maxcontext
144 delta = len(lines) - self.maxcontext
145 if False and delta > 0:
145 if False and delta > 0:
146 return number + delta, lines[:self.maxcontext]
146 return number + delta, lines[:self.maxcontext]
147 return number, lines
147 return number, lines
148
148
149 self.header = header
149 self.header = header
150 self.fromline, self.before = trimcontext(fromline, before)
150 self.fromline, self.before = trimcontext(fromline, before)
151 self.toline, self.after = trimcontext(toline, after)
151 self.toline, self.after = trimcontext(toline, after)
152 self.proc = proc
152 self.proc = proc
153 self.hunk = hunk
153 self.hunk = hunk
154 self.added, self.removed = countchanges(self.hunk)
154 self.added, self.removed = countchanges(self.hunk)
155
155
156 def write(self, fp):
156 def write(self, fp):
157 delta = len(self.before) + len(self.after)
157 delta = len(self.before) + len(self.after)
158 if self.after and self.after[-1] == '\\ No newline at end of file\n':
158 if self.after and self.after[-1] == '\\ No newline at end of file\n':
159 delta -= 1
159 delta -= 1
160 fromlen = delta + self.removed
160 fromlen = delta + self.removed
161 tolen = delta + self.added
161 tolen = delta + self.added
162 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
162 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
163 (self.fromline, fromlen, self.toline, tolen,
163 (self.fromline, fromlen, self.toline, tolen,
164 self.proc and (' ' + self.proc)))
164 self.proc and (' ' + self.proc)))
165 fp.write(''.join(self.before + self.hunk + self.after))
165 fp.write(''.join(self.before + self.hunk + self.after))
166
166
167 pretty = write
167 pretty = write
168
168
169 def filename(self):
169 def filename(self):
170 return self.header.filename()
170 return self.header.filename()
171
171
172 def __repr__(self):
172 def __repr__(self):
173 return '<hunk %r@%d>' % (self.filename(), self.fromline)
173 return '<hunk %r@%d>' % (self.filename(), self.fromline)
174
174
175 def parsepatch(fp):
175 def parsepatch(fp):
176 """patch -> [] of hunks """
176 """patch -> [] of hunks """
177 class parser(object):
177 class parser(object):
178 """patch parsing state machine"""
178 """patch parsing state machine"""
179 def __init__(self):
179 def __init__(self):
180 self.fromline = 0
180 self.fromline = 0
181 self.toline = 0
181 self.toline = 0
182 self.proc = ''
182 self.proc = ''
183 self.header = None
183 self.header = None
184 self.context = []
184 self.context = []
185 self.before = []
185 self.before = []
186 self.hunk = []
186 self.hunk = []
187 self.stream = []
187 self.stream = []
188
188
189 def addrange(self, (fromstart, fromend, tostart, toend, proc)):
189 def addrange(self, (fromstart, fromend, tostart, toend, proc)):
190 self.fromline = int(fromstart)
190 self.fromline = int(fromstart)
191 self.toline = int(tostart)
191 self.toline = int(tostart)
192 self.proc = proc
192 self.proc = proc
193
193
194 def addcontext(self, context):
194 def addcontext(self, context):
195 if self.hunk:
195 if self.hunk:
196 h = hunk(self.header, self.fromline, self.toline, self.proc,
196 h = hunk(self.header, self.fromline, self.toline, self.proc,
197 self.before, self.hunk, context)
197 self.before, self.hunk, context)
198 self.header.hunks.append(h)
198 self.header.hunks.append(h)
199 self.stream.append(h)
199 self.stream.append(h)
200 self.fromline += len(self.before) + h.removed
200 self.fromline += len(self.before) + h.removed
201 self.toline += len(self.before) + h.added
201 self.toline += len(self.before) + h.added
202 self.before = []
202 self.before = []
203 self.hunk = []
203 self.hunk = []
204 self.proc = ''
204 self.proc = ''
205 self.context = context
205 self.context = context
206
206
207 def addhunk(self, hunk):
207 def addhunk(self, hunk):
208 if self.context:
208 if self.context:
209 self.before = self.context
209 self.before = self.context
210 self.context = []
210 self.context = []
211 self.hunk = hunk
211 self.hunk = hunk
212
212
213 def newfile(self, hdr):
213 def newfile(self, hdr):
214 self.addcontext([])
214 self.addcontext([])
215 h = header(hdr)
215 h = header(hdr)
216 self.stream.append(h)
216 self.stream.append(h)
217 self.header = h
217 self.header = h
218
218
219 def finished(self):
219 def finished(self):
220 self.addcontext([])
220 self.addcontext([])
221 return self.stream
221 return self.stream
222
222
223 transitions = {
223 transitions = {
224 'file': {'context': addcontext,
224 'file': {'context': addcontext,
225 'file': newfile,
225 'file': newfile,
226 'hunk': addhunk,
226 'hunk': addhunk,
227 'range': addrange},
227 'range': addrange},
228 'context': {'file': newfile,
228 'context': {'file': newfile,
229 'hunk': addhunk,
229 'hunk': addhunk,
230 'range': addrange},
230 'range': addrange},
231 'hunk': {'context': addcontext,
231 'hunk': {'context': addcontext,
232 'file': newfile,
232 'file': newfile,
233 'range': addrange},
233 'range': addrange},
234 'range': {'context': addcontext,
234 'range': {'context': addcontext,
235 'hunk': addhunk},
235 'hunk': addhunk},
236 }
236 }
237
237
238 p = parser()
238 p = parser()
239
239
240 state = 'context'
240 state = 'context'
241 for newstate, data in scanpatch(fp):
241 for newstate, data in scanpatch(fp):
242 try:
242 try:
243 p.transitions[state][newstate](p, data)
243 p.transitions[state][newstate](p, data)
244 except KeyError:
244 except KeyError:
245 raise patch.PatchError('unhandled transition: %s -> %s' %
245 raise patch.PatchError('unhandled transition: %s -> %s' %
246 (state, newstate))
246 (state, newstate))
247 state = newstate
247 state = newstate
248 return p.finished()
248 return p.finished()
249
249
250 def filterpatch(ui, chunks):
250 def filterpatch(ui, chunks):
251 """Interactively filter patch chunks into applied-only chunks"""
251 """Interactively filter patch chunks into applied-only chunks"""
252 chunks = list(chunks)
252 chunks = list(chunks)
253 chunks.reverse()
253 chunks.reverse()
254 seen = set()
254 seen = set()
255 def consumefile():
255 def consumefile():
256 """fetch next portion from chunks until a 'header' is seen
256 """fetch next portion from chunks until a 'header' is seen
257 NB: header == new-file mark
257 NB: header == new-file mark
258 """
258 """
259 consumed = []
259 consumed = []
260 while chunks:
260 while chunks:
261 if isinstance(chunks[-1], header):
261 if isinstance(chunks[-1], header):
262 break
262 break
263 else:
263 else:
264 consumed.append(chunks.pop())
264 consumed.append(chunks.pop())
265 return consumed
265 return consumed
266
266
267 resp_all = [None] # this two are changed from inside prompt,
267 resp_all = [None] # this two are changed from inside prompt,
268 resp_file = [None] # so can't be usual variables
268 resp_file = [None] # so can't be usual variables
269 applied = {} # 'filename' -> [] of chunks
269 applied = {} # 'filename' -> [] of chunks
270 def prompt(query):
270 def prompt(query):
271 """prompt query, and process base inputs
271 """prompt query, and process base inputs
272
272
273 - y/n for the rest of file
273 - y/n for the rest of file
274 - y/n for the rest
274 - y/n for the rest
275 - ? (help)
275 - ? (help)
276 - q (quit)
276 - q (quit)
277
277
278 else, input is returned to the caller.
278 else, input is returned to the caller.
279 """
279 """
280 if resp_all[0] is not None:
280 if resp_all[0] is not None:
281 return resp_all[0]
281 return resp_all[0]
282 if resp_file[0] is not None:
282 if resp_file[0] is not None:
283 return resp_file[0]
283 return resp_file[0]
284 while True:
284 while True:
285 resps = _('[Ynsfdaq?]')
285 resps = _('[Ynsfdaq?]')
286 choices = (_('&Yes, record this change'),
286 choices = (_('&Yes, record this change'),
287 _('&No, skip this change'),
287 _('&No, skip this change'),
288 _('&Skip remaining changes to this file'),
288 _('&Skip remaining changes to this file'),
289 _('Record remaining changes to this &file'),
289 _('Record remaining changes to this &file'),
290 _('&Done, skip remaining changes and files'),
290 _('&Done, skip remaining changes and files'),
291 _('Record &all changes to all remaining files'),
291 _('Record &all changes to all remaining files'),
292 _('&Quit, recording no changes'),
292 _('&Quit, recording no changes'),
293 _('&?'))
293 _('&?'))
294 r = (ui.prompt("%s %s " % (query, resps), choices)
294 r = (ui.prompt("%s %s " % (query, resps), choices)
295 or _('y')).lower()
295 or _('y')).lower()
296 if r == _('?'):
296 if r == _('?'):
297 doc = gettext(record.__doc__)
297 doc = gettext(record.__doc__)
298 c = doc.find(_('y - record this change'))
298 c = doc.find(_('y - record this change'))
299 for l in doc[c:].splitlines():
299 for l in doc[c:].splitlines():
300 if l: ui.write(l.strip(), '\n')
300 if l: ui.write(l.strip(), '\n')
301 continue
301 continue
302 elif r == _('s'):
302 elif r == _('s'):
303 r = resp_file[0] = 'n'
303 r = resp_file[0] = 'n'
304 elif r == _('f'):
304 elif r == _('f'):
305 r = resp_file[0] = 'y'
305 r = resp_file[0] = 'y'
306 elif r == _('d'):
306 elif r == _('d'):
307 r = resp_all[0] = 'n'
307 r = resp_all[0] = 'n'
308 elif r == _('a'):
308 elif r == _('a'):
309 r = resp_all[0] = 'y'
309 r = resp_all[0] = 'y'
310 elif r == _('q'):
310 elif r == _('q'):
311 raise util.Abort(_('user quit'))
311 raise util.Abort(_('user quit'))
312 return r
312 return r
313 pos, total = 0, len(chunks) - 1
313 pos, total = 0, len(chunks) - 1
314 while chunks:
314 while chunks:
315 chunk = chunks.pop()
315 chunk = chunks.pop()
316 if isinstance(chunk, header):
316 if isinstance(chunk, header):
317 # new-file mark
317 # new-file mark
318 resp_file = [None]
318 resp_file = [None]
319 fixoffset = 0
319 fixoffset = 0
320 hdr = ''.join(chunk.header)
320 hdr = ''.join(chunk.header)
321 if hdr in seen:
321 if hdr in seen:
322 consumefile()
322 consumefile()
323 continue
323 continue
324 seen.add(hdr)
324 seen.add(hdr)
325 if resp_all[0] is None:
325 if resp_all[0] is None:
326 chunk.pretty(ui)
326 chunk.pretty(ui)
327 r = prompt(_('examine changes to %s?') %
327 r = prompt(_('examine changes to %s?') %
328 _(' and ').join(map(repr, chunk.files())))
328 _(' and ').join(map(repr, chunk.files())))
329 if r == _('y'):
329 if r == _('y'):
330 applied[chunk.filename()] = [chunk]
330 applied[chunk.filename()] = [chunk]
331 if chunk.allhunks():
331 if chunk.allhunks():
332 applied[chunk.filename()] += consumefile()
332 applied[chunk.filename()] += consumefile()
333 else:
333 else:
334 consumefile()
334 consumefile()
335 else:
335 else:
336 # new hunk
336 # new hunk
337 if resp_file[0] is None and resp_all[0] is None:
337 if resp_file[0] is None and resp_all[0] is None:
338 chunk.pretty(ui)
338 chunk.pretty(ui)
339 r = total == 1 and prompt(_('record this change to %r?') %
339 r = total == 1 and prompt(_('record this change to %r?') %
340 chunk.filename()) \
340 chunk.filename()) \
341 or prompt(_('record change %d/%d to %r?') %
341 or prompt(_('record change %d/%d to %r?') %
342 (pos, total, chunk.filename()))
342 (pos, total, chunk.filename()))
343 if r == _('y'):
343 if r == _('y'):
344 if fixoffset:
344 if fixoffset:
345 chunk = copy.copy(chunk)
345 chunk = copy.copy(chunk)
346 chunk.toline += fixoffset
346 chunk.toline += fixoffset
347 applied[chunk.filename()].append(chunk)
347 applied[chunk.filename()].append(chunk)
348 else:
348 else:
349 fixoffset += chunk.removed - chunk.added
349 fixoffset += chunk.removed - chunk.added
350 pos = pos + 1
350 pos = pos + 1
351 return reduce(operator.add, [h for h in applied.itervalues()
351 return reduce(operator.add, [h for h in applied.itervalues()
352 if h[0].special() or len(h) > 1], [])
352 if h[0].special() or len(h) > 1], [])
353
353
354 def record(ui, repo, *pats, **opts):
354 def record(ui, repo, *pats, **opts):
355 '''interactively select changes to commit
355 '''interactively select changes to commit
356
356
357 If a list of files is omitted, all changes reported by "hg status"
357 If a list of files is omitted, all changes reported by "hg status"
358 will be candidates for recording.
358 will be candidates for recording.
359
359
360 See 'hg help dates' for a list of formats valid for -d/--date.
360 See 'hg help dates' for a list of formats valid for -d/--date.
361
361
362 You will be prompted for whether to record changes to each
362 You will be prompted for whether to record changes to each
363 modified file, and for files with multiple changes, for each
363 modified file, and for files with multiple changes, for each
364 change to use. For each query, the following responses are
364 change to use. For each query, the following responses are
365 possible:
365 possible:
366
366
367 y - record this change
367 y - record this change
368 n - skip this change
368 n - skip this change
369
369
370 s - skip remaining changes to this file
370 s - skip remaining changes to this file
371 f - record remaining changes to this file
371 f - record remaining changes to this file
372
372
373 d - done, skip remaining changes and files
373 d - done, skip remaining changes and files
374 a - record all changes to all remaining files
374 a - record all changes to all remaining files
375 q - quit, recording no changes
375 q - quit, recording no changes
376
376
377 ? - display help'''
377 ? - display help'''
378
378
379 def record_committer(ui, repo, pats, opts):
379 def record_committer(ui, repo, pats, opts):
380 commands.commit(ui, repo, *pats, **opts)
380 commands.commit(ui, repo, *pats, **opts)
381
381
382 dorecord(ui, repo, record_committer, *pats, **opts)
382 dorecord(ui, repo, record_committer, *pats, **opts)
383
383
384
384
385 def qrecord(ui, repo, patch, *pats, **opts):
385 def qrecord(ui, repo, patch, *pats, **opts):
386 '''interactively record a new patch
386 '''interactively record a new patch
387
387
388 See 'hg help qnew' & 'hg help record' for more information and
388 See 'hg help qnew' & 'hg help record' for more information and
389 usage.
389 usage.
390 '''
390 '''
391
391
392 try:
392 try:
393 mq = extensions.find('mq')
393 mq = extensions.find('mq')
394 except KeyError:
394 except KeyError:
395 raise util.Abort(_("'mq' extension not loaded"))
395 raise util.Abort(_("'mq' extension not loaded"))
396
396
397 def qrecord_committer(ui, repo, pats, opts):
397 def qrecord_committer(ui, repo, pats, opts):
398 mq.new(ui, repo, patch, *pats, **opts)
398 mq.new(ui, repo, patch, *pats, **opts)
399
399
400 opts = opts.copy()
400 opts = opts.copy()
401 opts['force'] = True # always 'qnew -f'
401 opts['force'] = True # always 'qnew -f'
402 dorecord(ui, repo, qrecord_committer, *pats, **opts)
402 dorecord(ui, repo, qrecord_committer, *pats, **opts)
403
403
404
404
405 def dorecord(ui, repo, committer, *pats, **opts):
405 def dorecord(ui, repo, committer, *pats, **opts):
406 if not ui.interactive():
406 if not ui.interactive():
407 raise util.Abort(_('running non-interactively, use commit instead'))
407 raise util.Abort(_('running non-interactively, use commit instead'))
408
408
409 def recordfunc(ui, repo, message, match, opts):
409 def recordfunc(ui, repo, message, match, opts):
410 """This is generic record driver.
410 """This is generic record driver.
411
411
412 It's job is to interactively filter local changes, and accordingly
412 It's job is to interactively filter local changes, and accordingly
413 prepare working dir into a state, where the job can be delegated to
413 prepare working dir into a state, where the job can be delegated to
414 non-interactive commit command such as 'commit' or 'qrefresh'.
414 non-interactive commit command such as 'commit' or 'qrefresh'.
415
415
416 After the actual job is done by non-interactive command, working dir
416 After the actual job is done by non-interactive command, working dir
417 state is restored to original.
417 state is restored to original.
418
418
419 In the end we'll record intresting changes, and everything else will be
419 In the end we'll record intresting changes, and everything else will be
420 left in place, so the user can continue his work.
420 left in place, so the user can continue his work.
421 """
421 """
422
422
423 changes = repo.status(match=match)[:3]
423 changes = repo.status(match=match)[:3]
424 diffopts = mdiff.diffopts(git=True, nodates=True)
424 diffopts = mdiff.diffopts(git=True, nodates=True)
425 chunks = patch.diff(repo, changes=changes, opts=diffopts)
425 chunks = patch.diff(repo, changes=changes, opts=diffopts)
426 fp = cStringIO.StringIO()
426 fp = cStringIO.StringIO()
427 fp.write(''.join(chunks))
427 fp.write(''.join(chunks))
428 fp.seek(0)
428 fp.seek(0)
429
429
430 # 1. filter patch, so we have intending-to apply subset of it
430 # 1. filter patch, so we have intending-to apply subset of it
431 chunks = filterpatch(ui, parsepatch(fp))
431 chunks = filterpatch(ui, parsepatch(fp))
432 del fp
432 del fp
433
433
434 contenders = set()
434 contenders = set()
435 for h in chunks:
435 for h in chunks:
436 try: contenders.update(set(h.files()))
436 try: contenders.update(set(h.files()))
437 except AttributeError: pass
437 except AttributeError: pass
438
438
439 changed = changes[0] + changes[1] + changes[2]
439 changed = changes[0] + changes[1] + changes[2]
440 newfiles = [f for f in changed if f in contenders]
440 newfiles = [f for f in changed if f in contenders]
441 if not newfiles:
441 if not newfiles:
442 ui.status(_('no changes to record\n'))
442 ui.status(_('no changes to record\n'))
443 return 0
443 return 0
444
444
445 modified = set(changes[0])
445 modified = set(changes[0])
446
446
447 # 2. backup changed files, so we can restore them in the end
447 # 2. backup changed files, so we can restore them in the end
448 backups = {}
448 backups = {}
449 backupdir = repo.join('record-backups')
449 backupdir = repo.join('record-backups')
450 try:
450 try:
451 os.mkdir(backupdir)
451 os.mkdir(backupdir)
452 except OSError, err:
452 except OSError, err:
453 if err.errno != errno.EEXIST:
453 if err.errno != errno.EEXIST:
454 raise
454 raise
455 try:
455 try:
456 # backup continues
456 # backup continues
457 for f in newfiles:
457 for f in newfiles:
458 if f not in modified:
458 if f not in modified:
459 continue
459 continue
460 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
460 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
461 dir=backupdir)
461 dir=backupdir)
462 os.close(fd)
462 os.close(fd)
463 ui.debug(_('backup %r as %r\n') % (f, tmpname))
463 ui.debug(_('backup %r as %r\n') % (f, tmpname))
464 util.copyfile(repo.wjoin(f), tmpname)
464 util.copyfile(repo.wjoin(f), tmpname)
465 backups[f] = tmpname
465 backups[f] = tmpname
466
466
467 fp = cStringIO.StringIO()
467 fp = cStringIO.StringIO()
468 for c in chunks:
468 for c in chunks:
469 if c.filename() in backups:
469 if c.filename() in backups:
470 c.write(fp)
470 c.write(fp)
471 dopatch = fp.tell()
471 dopatch = fp.tell()
472 fp.seek(0)
472 fp.seek(0)
473
473
474 # 3a. apply filtered patch to clean repo (clean)
474 # 3a. apply filtered patch to clean repo (clean)
475 if backups:
475 if backups:
476 hg.revert(repo, repo.dirstate.parents()[0], backups.has_key)
476 hg.revert(repo, repo.dirstate.parents()[0], backups.has_key)
477
477
478 # 3b. (apply)
478 # 3b. (apply)
479 if dopatch:
479 if dopatch:
480 try:
480 try:
481 ui.debug(_('applying patch\n'))
481 ui.debug(_('applying patch\n'))
482 ui.debug(fp.getvalue())
482 ui.debug(fp.getvalue())
483 pfiles = {}
483 pfiles = {}
484 patch.internalpatch(fp, ui, 1, repo.root, files=pfiles)
484 patch.internalpatch(fp, ui, 1, repo.root, files=pfiles,
485 eolmode=None)
485 patch.updatedir(ui, repo, pfiles)
486 patch.updatedir(ui, repo, pfiles)
486 except patch.PatchError, err:
487 except patch.PatchError, err:
487 s = str(err)
488 s = str(err)
488 if s:
489 if s:
489 raise util.Abort(s)
490 raise util.Abort(s)
490 else:
491 else:
491 raise util.Abort(_('patch failed to apply'))
492 raise util.Abort(_('patch failed to apply'))
492 del fp
493 del fp
493
494
494 # 4. We prepared working directory according to filtered patch.
495 # 4. We prepared working directory according to filtered patch.
495 # Now is the time to delegate the job to commit/qrefresh or the like!
496 # Now is the time to delegate the job to commit/qrefresh or the like!
496
497
497 # it is important to first chdir to repo root -- we'll call a
498 # it is important to first chdir to repo root -- we'll call a
498 # highlevel command with list of pathnames relative to repo root
499 # highlevel command with list of pathnames relative to repo root
499 cwd = os.getcwd()
500 cwd = os.getcwd()
500 os.chdir(repo.root)
501 os.chdir(repo.root)
501 try:
502 try:
502 committer(ui, repo, newfiles, opts)
503 committer(ui, repo, newfiles, opts)
503 finally:
504 finally:
504 os.chdir(cwd)
505 os.chdir(cwd)
505
506
506 return 0
507 return 0
507 finally:
508 finally:
508 # 5. finally restore backed-up files
509 # 5. finally restore backed-up files
509 try:
510 try:
510 for realname, tmpname in backups.iteritems():
511 for realname, tmpname in backups.iteritems():
511 ui.debug(_('restoring %r to %r\n') % (tmpname, realname))
512 ui.debug(_('restoring %r to %r\n') % (tmpname, realname))
512 util.copyfile(tmpname, repo.wjoin(realname))
513 util.copyfile(tmpname, repo.wjoin(realname))
513 os.unlink(tmpname)
514 os.unlink(tmpname)
514 os.rmdir(backupdir)
515 os.rmdir(backupdir)
515 except OSError:
516 except OSError:
516 pass
517 pass
517 return cmdutil.commit(ui, repo, recordfunc, pats, opts)
518 return cmdutil.commit(ui, repo, recordfunc, pats, opts)
518
519
519 cmdtable = {
520 cmdtable = {
520 "record":
521 "record":
521 (record,
522 (record,
522
523
523 # add commit options
524 # add commit options
524 commands.table['^commit|ci'][1],
525 commands.table['^commit|ci'][1],
525
526
526 _('hg record [OPTION]... [FILE]...')),
527 _('hg record [OPTION]... [FILE]...')),
527 }
528 }
528
529
529
530
530 def extsetup():
531 def extsetup():
531 try:
532 try:
532 mq = extensions.find('mq')
533 mq = extensions.find('mq')
533 except KeyError:
534 except KeyError:
534 return
535 return
535
536
536 qcmdtable = {
537 qcmdtable = {
537 "qrecord":
538 "qrecord":
538 (qrecord,
539 (qrecord,
539
540
540 # add qnew options, except '--force'
541 # add qnew options, except '--force'
541 [opt for opt in mq.cmdtable['qnew'][1] if opt[1] != 'force'],
542 [opt for opt in mq.cmdtable['qnew'][1] if opt[1] != 'force'],
542
543
543 _('hg qrecord [OPTION]... PATCH [FILE]...')),
544 _('hg qrecord [OPTION]... PATCH [FILE]...')),
544 }
545 }
545
546
546 cmdtable.update(qcmdtable)
547 cmdtable.update(qcmdtable)
547
548
@@ -1,605 +1,605 b''
1 # Patch transplanting extension for Mercurial
1 # Patch transplanting extension for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 '''patch transplanting tool
8 '''patch transplanting tool
9
9
10 This extension allows you to transplant patches from another branch.
10 This extension allows you to transplant patches from another branch.
11
11
12 Transplanted patches are recorded in .hg/transplant/transplants, as a
12 Transplanted patches are recorded in .hg/transplant/transplants, as a
13 map from a changeset hash to its hash in the source repository.
13 map from a changeset hash to its hash in the source repository.
14 '''
14 '''
15
15
16 from mercurial.i18n import _
16 from mercurial.i18n import _
17 import os, tempfile
17 import os, tempfile
18 from mercurial import bundlerepo, changegroup, cmdutil, hg, merge, match
18 from mercurial import bundlerepo, changegroup, cmdutil, hg, merge, match
19 from mercurial import patch, revlog, util, error
19 from mercurial import patch, revlog, util, error
20
20
21 class transplantentry(object):
21 class transplantentry(object):
22 def __init__(self, lnode, rnode):
22 def __init__(self, lnode, rnode):
23 self.lnode = lnode
23 self.lnode = lnode
24 self.rnode = rnode
24 self.rnode = rnode
25
25
26 class transplants(object):
26 class transplants(object):
27 def __init__(self, path=None, transplantfile=None, opener=None):
27 def __init__(self, path=None, transplantfile=None, opener=None):
28 self.path = path
28 self.path = path
29 self.transplantfile = transplantfile
29 self.transplantfile = transplantfile
30 self.opener = opener
30 self.opener = opener
31
31
32 if not opener:
32 if not opener:
33 self.opener = util.opener(self.path)
33 self.opener = util.opener(self.path)
34 self.transplants = []
34 self.transplants = []
35 self.dirty = False
35 self.dirty = False
36 self.read()
36 self.read()
37
37
38 def read(self):
38 def read(self):
39 abspath = os.path.join(self.path, self.transplantfile)
39 abspath = os.path.join(self.path, self.transplantfile)
40 if self.transplantfile and os.path.exists(abspath):
40 if self.transplantfile and os.path.exists(abspath):
41 for line in self.opener(self.transplantfile).read().splitlines():
41 for line in self.opener(self.transplantfile).read().splitlines():
42 lnode, rnode = map(revlog.bin, line.split(':'))
42 lnode, rnode = map(revlog.bin, line.split(':'))
43 self.transplants.append(transplantentry(lnode, rnode))
43 self.transplants.append(transplantentry(lnode, rnode))
44
44
45 def write(self):
45 def write(self):
46 if self.dirty and self.transplantfile:
46 if self.dirty and self.transplantfile:
47 if not os.path.isdir(self.path):
47 if not os.path.isdir(self.path):
48 os.mkdir(self.path)
48 os.mkdir(self.path)
49 fp = self.opener(self.transplantfile, 'w')
49 fp = self.opener(self.transplantfile, 'w')
50 for c in self.transplants:
50 for c in self.transplants:
51 l, r = map(revlog.hex, (c.lnode, c.rnode))
51 l, r = map(revlog.hex, (c.lnode, c.rnode))
52 fp.write(l + ':' + r + '\n')
52 fp.write(l + ':' + r + '\n')
53 fp.close()
53 fp.close()
54 self.dirty = False
54 self.dirty = False
55
55
56 def get(self, rnode):
56 def get(self, rnode):
57 return [t for t in self.transplants if t.rnode == rnode]
57 return [t for t in self.transplants if t.rnode == rnode]
58
58
59 def set(self, lnode, rnode):
59 def set(self, lnode, rnode):
60 self.transplants.append(transplantentry(lnode, rnode))
60 self.transplants.append(transplantentry(lnode, rnode))
61 self.dirty = True
61 self.dirty = True
62
62
63 def remove(self, transplant):
63 def remove(self, transplant):
64 del self.transplants[self.transplants.index(transplant)]
64 del self.transplants[self.transplants.index(transplant)]
65 self.dirty = True
65 self.dirty = True
66
66
67 class transplanter(object):
67 class transplanter(object):
68 def __init__(self, ui, repo):
68 def __init__(self, ui, repo):
69 self.ui = ui
69 self.ui = ui
70 self.path = repo.join('transplant')
70 self.path = repo.join('transplant')
71 self.opener = util.opener(self.path)
71 self.opener = util.opener(self.path)
72 self.transplants = transplants(self.path, 'transplants',
72 self.transplants = transplants(self.path, 'transplants',
73 opener=self.opener)
73 opener=self.opener)
74
74
75 def applied(self, repo, node, parent):
75 def applied(self, repo, node, parent):
76 '''returns True if a node is already an ancestor of parent
76 '''returns True if a node is already an ancestor of parent
77 or has already been transplanted'''
77 or has already been transplanted'''
78 if hasnode(repo, node):
78 if hasnode(repo, node):
79 if node in repo.changelog.reachable(parent, stop=node):
79 if node in repo.changelog.reachable(parent, stop=node):
80 return True
80 return True
81 for t in self.transplants.get(node):
81 for t in self.transplants.get(node):
82 # it might have been stripped
82 # it might have been stripped
83 if not hasnode(repo, t.lnode):
83 if not hasnode(repo, t.lnode):
84 self.transplants.remove(t)
84 self.transplants.remove(t)
85 return False
85 return False
86 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
86 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
87 return True
87 return True
88 return False
88 return False
89
89
90 def apply(self, repo, source, revmap, merges, opts={}):
90 def apply(self, repo, source, revmap, merges, opts={}):
91 '''apply the revisions in revmap one by one in revision order'''
91 '''apply the revisions in revmap one by one in revision order'''
92 revs = sorted(revmap)
92 revs = sorted(revmap)
93 p1, p2 = repo.dirstate.parents()
93 p1, p2 = repo.dirstate.parents()
94 pulls = []
94 pulls = []
95 diffopts = patch.diffopts(self.ui, opts)
95 diffopts = patch.diffopts(self.ui, opts)
96 diffopts.git = True
96 diffopts.git = True
97
97
98 lock = wlock = None
98 lock = wlock = None
99 try:
99 try:
100 wlock = repo.wlock()
100 wlock = repo.wlock()
101 lock = repo.lock()
101 lock = repo.lock()
102 for rev in revs:
102 for rev in revs:
103 node = revmap[rev]
103 node = revmap[rev]
104 revstr = '%s:%s' % (rev, revlog.short(node))
104 revstr = '%s:%s' % (rev, revlog.short(node))
105
105
106 if self.applied(repo, node, p1):
106 if self.applied(repo, node, p1):
107 self.ui.warn(_('skipping already applied revision %s\n') %
107 self.ui.warn(_('skipping already applied revision %s\n') %
108 revstr)
108 revstr)
109 continue
109 continue
110
110
111 parents = source.changelog.parents(node)
111 parents = source.changelog.parents(node)
112 if not opts.get('filter'):
112 if not opts.get('filter'):
113 # If the changeset parent is the same as the
113 # If the changeset parent is the same as the
114 # wdir's parent, just pull it.
114 # wdir's parent, just pull it.
115 if parents[0] == p1:
115 if parents[0] == p1:
116 pulls.append(node)
116 pulls.append(node)
117 p1 = node
117 p1 = node
118 continue
118 continue
119 if pulls:
119 if pulls:
120 if source != repo:
120 if source != repo:
121 repo.pull(source, heads=pulls)
121 repo.pull(source, heads=pulls)
122 merge.update(repo, pulls[-1], False, False, None)
122 merge.update(repo, pulls[-1], False, False, None)
123 p1, p2 = repo.dirstate.parents()
123 p1, p2 = repo.dirstate.parents()
124 pulls = []
124 pulls = []
125
125
126 domerge = False
126 domerge = False
127 if node in merges:
127 if node in merges:
128 # pulling all the merge revs at once would mean we
128 # pulling all the merge revs at once would mean we
129 # couldn't transplant after the latest even if
129 # couldn't transplant after the latest even if
130 # transplants before them fail.
130 # transplants before them fail.
131 domerge = True
131 domerge = True
132 if not hasnode(repo, node):
132 if not hasnode(repo, node):
133 repo.pull(source, heads=[node])
133 repo.pull(source, heads=[node])
134
134
135 if parents[1] != revlog.nullid:
135 if parents[1] != revlog.nullid:
136 self.ui.note(_('skipping merge changeset %s:%s\n')
136 self.ui.note(_('skipping merge changeset %s:%s\n')
137 % (rev, revlog.short(node)))
137 % (rev, revlog.short(node)))
138 patchfile = None
138 patchfile = None
139 else:
139 else:
140 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
140 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
141 fp = os.fdopen(fd, 'w')
141 fp = os.fdopen(fd, 'w')
142 gen = patch.diff(source, parents[0], node, opts=diffopts)
142 gen = patch.diff(source, parents[0], node, opts=diffopts)
143 for chunk in gen:
143 for chunk in gen:
144 fp.write(chunk)
144 fp.write(chunk)
145 fp.close()
145 fp.close()
146
146
147 del revmap[rev]
147 del revmap[rev]
148 if patchfile or domerge:
148 if patchfile or domerge:
149 try:
149 try:
150 n = self.applyone(repo, node,
150 n = self.applyone(repo, node,
151 source.changelog.read(node),
151 source.changelog.read(node),
152 patchfile, merge=domerge,
152 patchfile, merge=domerge,
153 log=opts.get('log'),
153 log=opts.get('log'),
154 filter=opts.get('filter'))
154 filter=opts.get('filter'))
155 if n and domerge:
155 if n and domerge:
156 self.ui.status(_('%s merged at %s\n') % (revstr,
156 self.ui.status(_('%s merged at %s\n') % (revstr,
157 revlog.short(n)))
157 revlog.short(n)))
158 elif n:
158 elif n:
159 self.ui.status(_('%s transplanted to %s\n')
159 self.ui.status(_('%s transplanted to %s\n')
160 % (revlog.short(node),
160 % (revlog.short(node),
161 revlog.short(n)))
161 revlog.short(n)))
162 finally:
162 finally:
163 if patchfile:
163 if patchfile:
164 os.unlink(patchfile)
164 os.unlink(patchfile)
165 if pulls:
165 if pulls:
166 repo.pull(source, heads=pulls)
166 repo.pull(source, heads=pulls)
167 merge.update(repo, pulls[-1], False, False, None)
167 merge.update(repo, pulls[-1], False, False, None)
168 finally:
168 finally:
169 self.saveseries(revmap, merges)
169 self.saveseries(revmap, merges)
170 self.transplants.write()
170 self.transplants.write()
171 lock.release()
171 lock.release()
172 wlock.release()
172 wlock.release()
173
173
174 def filter(self, filter, changelog, patchfile):
174 def filter(self, filter, changelog, patchfile):
175 '''arbitrarily rewrite changeset before applying it'''
175 '''arbitrarily rewrite changeset before applying it'''
176
176
177 self.ui.status(_('filtering %s\n') % patchfile)
177 self.ui.status(_('filtering %s\n') % patchfile)
178 user, date, msg = (changelog[1], changelog[2], changelog[4])
178 user, date, msg = (changelog[1], changelog[2], changelog[4])
179
179
180 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
180 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
181 fp = os.fdopen(fd, 'w')
181 fp = os.fdopen(fd, 'w')
182 fp.write("# HG changeset patch\n")
182 fp.write("# HG changeset patch\n")
183 fp.write("# User %s\n" % user)
183 fp.write("# User %s\n" % user)
184 fp.write("# Date %d %d\n" % date)
184 fp.write("# Date %d %d\n" % date)
185 fp.write(changelog[4])
185 fp.write(changelog[4])
186 fp.close()
186 fp.close()
187
187
188 try:
188 try:
189 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
189 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
190 util.shellquote(patchfile)),
190 util.shellquote(patchfile)),
191 environ={'HGUSER': changelog[1]},
191 environ={'HGUSER': changelog[1]},
192 onerr=util.Abort, errprefix=_('filter failed'))
192 onerr=util.Abort, errprefix=_('filter failed'))
193 user, date, msg = self.parselog(file(headerfile))[1:4]
193 user, date, msg = self.parselog(file(headerfile))[1:4]
194 finally:
194 finally:
195 os.unlink(headerfile)
195 os.unlink(headerfile)
196
196
197 return (user, date, msg)
197 return (user, date, msg)
198
198
199 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
199 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
200 filter=None):
200 filter=None):
201 '''apply the patch in patchfile to the repository as a transplant'''
201 '''apply the patch in patchfile to the repository as a transplant'''
202 (manifest, user, (time, timezone), files, message) = cl[:5]
202 (manifest, user, (time, timezone), files, message) = cl[:5]
203 date = "%d %d" % (time, timezone)
203 date = "%d %d" % (time, timezone)
204 extra = {'transplant_source': node}
204 extra = {'transplant_source': node}
205 if filter:
205 if filter:
206 (user, date, message) = self.filter(filter, cl, patchfile)
206 (user, date, message) = self.filter(filter, cl, patchfile)
207
207
208 if log:
208 if log:
209 message += '\n(transplanted from %s)' % revlog.hex(node)
209 message += '\n(transplanted from %s)' % revlog.hex(node)
210
210
211 self.ui.status(_('applying %s\n') % revlog.short(node))
211 self.ui.status(_('applying %s\n') % revlog.short(node))
212 self.ui.note('%s %s\n%s\n' % (user, date, message))
212 self.ui.note('%s %s\n%s\n' % (user, date, message))
213
213
214 if not patchfile and not merge:
214 if not patchfile and not merge:
215 raise util.Abort(_('can only omit patchfile if merging'))
215 raise util.Abort(_('can only omit patchfile if merging'))
216 if patchfile:
216 if patchfile:
217 try:
217 try:
218 files = {}
218 files = {}
219 try:
219 try:
220 patch.patch(patchfile, self.ui, cwd=repo.root,
220 patch.patch(patchfile, self.ui, cwd=repo.root,
221 files=files)
221 files=files, eolmode=None)
222 if not files:
222 if not files:
223 self.ui.warn(_('%s: empty changeset')
223 self.ui.warn(_('%s: empty changeset')
224 % revlog.hex(node))
224 % revlog.hex(node))
225 return None
225 return None
226 finally:
226 finally:
227 files = patch.updatedir(self.ui, repo, files)
227 files = patch.updatedir(self.ui, repo, files)
228 except Exception, inst:
228 except Exception, inst:
229 if filter:
229 if filter:
230 os.unlink(patchfile)
230 os.unlink(patchfile)
231 seriespath = os.path.join(self.path, 'series')
231 seriespath = os.path.join(self.path, 'series')
232 if os.path.exists(seriespath):
232 if os.path.exists(seriespath):
233 os.unlink(seriespath)
233 os.unlink(seriespath)
234 p1 = repo.dirstate.parents()[0]
234 p1 = repo.dirstate.parents()[0]
235 p2 = node
235 p2 = node
236 self.log(user, date, message, p1, p2, merge=merge)
236 self.log(user, date, message, p1, p2, merge=merge)
237 self.ui.write(str(inst) + '\n')
237 self.ui.write(str(inst) + '\n')
238 raise util.Abort(_('Fix up the merge and run '
238 raise util.Abort(_('Fix up the merge and run '
239 'hg transplant --continue'))
239 'hg transplant --continue'))
240 else:
240 else:
241 files = None
241 files = None
242 if merge:
242 if merge:
243 p1, p2 = repo.dirstate.parents()
243 p1, p2 = repo.dirstate.parents()
244 repo.dirstate.setparents(p1, node)
244 repo.dirstate.setparents(p1, node)
245 m = match.always(repo.root, '')
245 m = match.always(repo.root, '')
246 else:
246 else:
247 m = match.exact(repo.root, '', files)
247 m = match.exact(repo.root, '', files)
248
248
249 n = repo.commit(message, user, date, extra=extra, match=m)
249 n = repo.commit(message, user, date, extra=extra, match=m)
250 if not merge:
250 if not merge:
251 self.transplants.set(n, node)
251 self.transplants.set(n, node)
252
252
253 return n
253 return n
254
254
255 def resume(self, repo, source, opts=None):
255 def resume(self, repo, source, opts=None):
256 '''recover last transaction and apply remaining changesets'''
256 '''recover last transaction and apply remaining changesets'''
257 if os.path.exists(os.path.join(self.path, 'journal')):
257 if os.path.exists(os.path.join(self.path, 'journal')):
258 n, node = self.recover(repo)
258 n, node = self.recover(repo)
259 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
259 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
260 revlog.short(n)))
260 revlog.short(n)))
261 seriespath = os.path.join(self.path, 'series')
261 seriespath = os.path.join(self.path, 'series')
262 if not os.path.exists(seriespath):
262 if not os.path.exists(seriespath):
263 self.transplants.write()
263 self.transplants.write()
264 return
264 return
265 nodes, merges = self.readseries()
265 nodes, merges = self.readseries()
266 revmap = {}
266 revmap = {}
267 for n in nodes:
267 for n in nodes:
268 revmap[source.changelog.rev(n)] = n
268 revmap[source.changelog.rev(n)] = n
269 os.unlink(seriespath)
269 os.unlink(seriespath)
270
270
271 self.apply(repo, source, revmap, merges, opts)
271 self.apply(repo, source, revmap, merges, opts)
272
272
273 def recover(self, repo):
273 def recover(self, repo):
274 '''commit working directory using journal metadata'''
274 '''commit working directory using journal metadata'''
275 node, user, date, message, parents = self.readlog()
275 node, user, date, message, parents = self.readlog()
276 merge = len(parents) == 2
276 merge = len(parents) == 2
277
277
278 if not user or not date or not message or not parents[0]:
278 if not user or not date or not message or not parents[0]:
279 raise util.Abort(_('transplant log file is corrupt'))
279 raise util.Abort(_('transplant log file is corrupt'))
280
280
281 extra = {'transplant_source': node}
281 extra = {'transplant_source': node}
282 wlock = repo.wlock()
282 wlock = repo.wlock()
283 try:
283 try:
284 p1, p2 = repo.dirstate.parents()
284 p1, p2 = repo.dirstate.parents()
285 if p1 != parents[0]:
285 if p1 != parents[0]:
286 raise util.Abort(
286 raise util.Abort(
287 _('working dir not at transplant parent %s') %
287 _('working dir not at transplant parent %s') %
288 revlog.hex(parents[0]))
288 revlog.hex(parents[0]))
289 if merge:
289 if merge:
290 repo.dirstate.setparents(p1, parents[1])
290 repo.dirstate.setparents(p1, parents[1])
291 n = repo.commit(message, user, date, extra=extra)
291 n = repo.commit(message, user, date, extra=extra)
292 if not n:
292 if not n:
293 raise util.Abort(_('commit failed'))
293 raise util.Abort(_('commit failed'))
294 if not merge:
294 if not merge:
295 self.transplants.set(n, node)
295 self.transplants.set(n, node)
296 self.unlog()
296 self.unlog()
297
297
298 return n, node
298 return n, node
299 finally:
299 finally:
300 wlock.release()
300 wlock.release()
301
301
302 def readseries(self):
302 def readseries(self):
303 nodes = []
303 nodes = []
304 merges = []
304 merges = []
305 cur = nodes
305 cur = nodes
306 for line in self.opener('series').read().splitlines():
306 for line in self.opener('series').read().splitlines():
307 if line.startswith('# Merges'):
307 if line.startswith('# Merges'):
308 cur = merges
308 cur = merges
309 continue
309 continue
310 cur.append(revlog.bin(line))
310 cur.append(revlog.bin(line))
311
311
312 return (nodes, merges)
312 return (nodes, merges)
313
313
314 def saveseries(self, revmap, merges):
314 def saveseries(self, revmap, merges):
315 if not revmap:
315 if not revmap:
316 return
316 return
317
317
318 if not os.path.isdir(self.path):
318 if not os.path.isdir(self.path):
319 os.mkdir(self.path)
319 os.mkdir(self.path)
320 series = self.opener('series', 'w')
320 series = self.opener('series', 'w')
321 for rev in sorted(revmap):
321 for rev in sorted(revmap):
322 series.write(revlog.hex(revmap[rev]) + '\n')
322 series.write(revlog.hex(revmap[rev]) + '\n')
323 if merges:
323 if merges:
324 series.write('# Merges\n')
324 series.write('# Merges\n')
325 for m in merges:
325 for m in merges:
326 series.write(revlog.hex(m) + '\n')
326 series.write(revlog.hex(m) + '\n')
327 series.close()
327 series.close()
328
328
329 def parselog(self, fp):
329 def parselog(self, fp):
330 parents = []
330 parents = []
331 message = []
331 message = []
332 node = revlog.nullid
332 node = revlog.nullid
333 inmsg = False
333 inmsg = False
334 for line in fp.read().splitlines():
334 for line in fp.read().splitlines():
335 if inmsg:
335 if inmsg:
336 message.append(line)
336 message.append(line)
337 elif line.startswith('# User '):
337 elif line.startswith('# User '):
338 user = line[7:]
338 user = line[7:]
339 elif line.startswith('# Date '):
339 elif line.startswith('# Date '):
340 date = line[7:]
340 date = line[7:]
341 elif line.startswith('# Node ID '):
341 elif line.startswith('# Node ID '):
342 node = revlog.bin(line[10:])
342 node = revlog.bin(line[10:])
343 elif line.startswith('# Parent '):
343 elif line.startswith('# Parent '):
344 parents.append(revlog.bin(line[9:]))
344 parents.append(revlog.bin(line[9:]))
345 elif not line.startswith('#'):
345 elif not line.startswith('#'):
346 inmsg = True
346 inmsg = True
347 message.append(line)
347 message.append(line)
348 return (node, user, date, '\n'.join(message), parents)
348 return (node, user, date, '\n'.join(message), parents)
349
349
350 def log(self, user, date, message, p1, p2, merge=False):
350 def log(self, user, date, message, p1, p2, merge=False):
351 '''journal changelog metadata for later recover'''
351 '''journal changelog metadata for later recover'''
352
352
353 if not os.path.isdir(self.path):
353 if not os.path.isdir(self.path):
354 os.mkdir(self.path)
354 os.mkdir(self.path)
355 fp = self.opener('journal', 'w')
355 fp = self.opener('journal', 'w')
356 fp.write('# User %s\n' % user)
356 fp.write('# User %s\n' % user)
357 fp.write('# Date %s\n' % date)
357 fp.write('# Date %s\n' % date)
358 fp.write('# Node ID %s\n' % revlog.hex(p2))
358 fp.write('# Node ID %s\n' % revlog.hex(p2))
359 fp.write('# Parent ' + revlog.hex(p1) + '\n')
359 fp.write('# Parent ' + revlog.hex(p1) + '\n')
360 if merge:
360 if merge:
361 fp.write('# Parent ' + revlog.hex(p2) + '\n')
361 fp.write('# Parent ' + revlog.hex(p2) + '\n')
362 fp.write(message.rstrip() + '\n')
362 fp.write(message.rstrip() + '\n')
363 fp.close()
363 fp.close()
364
364
365 def readlog(self):
365 def readlog(self):
366 return self.parselog(self.opener('journal'))
366 return self.parselog(self.opener('journal'))
367
367
368 def unlog(self):
368 def unlog(self):
369 '''remove changelog journal'''
369 '''remove changelog journal'''
370 absdst = os.path.join(self.path, 'journal')
370 absdst = os.path.join(self.path, 'journal')
371 if os.path.exists(absdst):
371 if os.path.exists(absdst):
372 os.unlink(absdst)
372 os.unlink(absdst)
373
373
374 def transplantfilter(self, repo, source, root):
374 def transplantfilter(self, repo, source, root):
375 def matchfn(node):
375 def matchfn(node):
376 if self.applied(repo, node, root):
376 if self.applied(repo, node, root):
377 return False
377 return False
378 if source.changelog.parents(node)[1] != revlog.nullid:
378 if source.changelog.parents(node)[1] != revlog.nullid:
379 return False
379 return False
380 extra = source.changelog.read(node)[5]
380 extra = source.changelog.read(node)[5]
381 cnode = extra.get('transplant_source')
381 cnode = extra.get('transplant_source')
382 if cnode and self.applied(repo, cnode, root):
382 if cnode and self.applied(repo, cnode, root):
383 return False
383 return False
384 return True
384 return True
385
385
386 return matchfn
386 return matchfn
387
387
388 def hasnode(repo, node):
388 def hasnode(repo, node):
389 try:
389 try:
390 return repo.changelog.rev(node) != None
390 return repo.changelog.rev(node) != None
391 except error.RevlogError:
391 except error.RevlogError:
392 return False
392 return False
393
393
394 def browserevs(ui, repo, nodes, opts):
394 def browserevs(ui, repo, nodes, opts):
395 '''interactively transplant changesets'''
395 '''interactively transplant changesets'''
396 def browsehelp(ui):
396 def browsehelp(ui):
397 ui.write('y: transplant this changeset\n'
397 ui.write('y: transplant this changeset\n'
398 'n: skip this changeset\n'
398 'n: skip this changeset\n'
399 'm: merge at this changeset\n'
399 'm: merge at this changeset\n'
400 'p: show patch\n'
400 'p: show patch\n'
401 'c: commit selected changesets\n'
401 'c: commit selected changesets\n'
402 'q: cancel transplant\n'
402 'q: cancel transplant\n'
403 '?: show this help\n')
403 '?: show this help\n')
404
404
405 displayer = cmdutil.show_changeset(ui, repo, opts)
405 displayer = cmdutil.show_changeset(ui, repo, opts)
406 transplants = []
406 transplants = []
407 merges = []
407 merges = []
408 for node in nodes:
408 for node in nodes:
409 displayer.show(repo[node])
409 displayer.show(repo[node])
410 action = None
410 action = None
411 while not action:
411 while not action:
412 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
412 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
413 if action == '?':
413 if action == '?':
414 browsehelp(ui)
414 browsehelp(ui)
415 action = None
415 action = None
416 elif action == 'p':
416 elif action == 'p':
417 parent = repo.changelog.parents(node)[0]
417 parent = repo.changelog.parents(node)[0]
418 for chunk in patch.diff(repo, parent, node):
418 for chunk in patch.diff(repo, parent, node):
419 ui.write(chunk)
419 ui.write(chunk)
420 action = None
420 action = None
421 elif action not in ('y', 'n', 'm', 'c', 'q'):
421 elif action not in ('y', 'n', 'm', 'c', 'q'):
422 ui.write('no such option\n')
422 ui.write('no such option\n')
423 action = None
423 action = None
424 if action == 'y':
424 if action == 'y':
425 transplants.append(node)
425 transplants.append(node)
426 elif action == 'm':
426 elif action == 'm':
427 merges.append(node)
427 merges.append(node)
428 elif action == 'c':
428 elif action == 'c':
429 break
429 break
430 elif action == 'q':
430 elif action == 'q':
431 transplants = ()
431 transplants = ()
432 merges = ()
432 merges = ()
433 break
433 break
434 return (transplants, merges)
434 return (transplants, merges)
435
435
436 def transplant(ui, repo, *revs, **opts):
436 def transplant(ui, repo, *revs, **opts):
437 '''transplant changesets from another branch
437 '''transplant changesets from another branch
438
438
439 Selected changesets will be applied on top of the current working
439 Selected changesets will be applied on top of the current working
440 directory with the log of the original changeset. If --log is
440 directory with the log of the original changeset. If --log is
441 specified, log messages will have a comment appended of the form:
441 specified, log messages will have a comment appended of the form:
442
442
443 (transplanted from CHANGESETHASH)
443 (transplanted from CHANGESETHASH)
444
444
445 You can rewrite the changelog message with the --filter option.
445 You can rewrite the changelog message with the --filter option.
446 Its argument will be invoked with the current changelog message as
446 Its argument will be invoked with the current changelog message as
447 $1 and the patch as $2.
447 $1 and the patch as $2.
448
448
449 If --source/-s is specified, selects changesets from the named
449 If --source/-s is specified, selects changesets from the named
450 repository. If --branch/-b is specified, selects changesets from
450 repository. If --branch/-b is specified, selects changesets from
451 the branch holding the named revision, up to that revision. If
451 the branch holding the named revision, up to that revision. If
452 --all/-a is specified, all changesets on the branch will be
452 --all/-a is specified, all changesets on the branch will be
453 transplanted, otherwise you will be prompted to select the
453 transplanted, otherwise you will be prompted to select the
454 changesets you want.
454 changesets you want.
455
455
456 hg transplant --branch REVISION --all will rebase the selected
456 hg transplant --branch REVISION --all will rebase the selected
457 branch (up to the named revision) onto your current working
457 branch (up to the named revision) onto your current working
458 directory.
458 directory.
459
459
460 You can optionally mark selected transplanted changesets as merge
460 You can optionally mark selected transplanted changesets as merge
461 changesets. You will not be prompted to transplant any ancestors
461 changesets. You will not be prompted to transplant any ancestors
462 of a merged transplant, and you can merge descendants of them
462 of a merged transplant, and you can merge descendants of them
463 normally instead of transplanting them.
463 normally instead of transplanting them.
464
464
465 If no merges or revisions are provided, hg transplant will start
465 If no merges or revisions are provided, hg transplant will start
466 an interactive changeset browser.
466 an interactive changeset browser.
467
467
468 If a changeset application fails, you can fix the merge by hand
468 If a changeset application fails, you can fix the merge by hand
469 and then resume where you left off by calling hg transplant
469 and then resume where you left off by calling hg transplant
470 --continue/-c.
470 --continue/-c.
471 '''
471 '''
472 def getremotechanges(repo, url):
472 def getremotechanges(repo, url):
473 sourcerepo = ui.expandpath(url)
473 sourcerepo = ui.expandpath(url)
474 source = hg.repository(ui, sourcerepo)
474 source = hg.repository(ui, sourcerepo)
475 common, incoming, rheads = repo.findcommonincoming(source, force=True)
475 common, incoming, rheads = repo.findcommonincoming(source, force=True)
476 if not incoming:
476 if not incoming:
477 return (source, None, None)
477 return (source, None, None)
478
478
479 bundle = None
479 bundle = None
480 if not source.local():
480 if not source.local():
481 if source.capable('changegroupsubset'):
481 if source.capable('changegroupsubset'):
482 cg = source.changegroupsubset(incoming, rheads, 'incoming')
482 cg = source.changegroupsubset(incoming, rheads, 'incoming')
483 else:
483 else:
484 cg = source.changegroup(incoming, 'incoming')
484 cg = source.changegroup(incoming, 'incoming')
485 bundle = changegroup.writebundle(cg, None, 'HG10UN')
485 bundle = changegroup.writebundle(cg, None, 'HG10UN')
486 source = bundlerepo.bundlerepository(ui, repo.root, bundle)
486 source = bundlerepo.bundlerepository(ui, repo.root, bundle)
487
487
488 return (source, incoming, bundle)
488 return (source, incoming, bundle)
489
489
490 def incwalk(repo, incoming, branches, match=util.always):
490 def incwalk(repo, incoming, branches, match=util.always):
491 if not branches:
491 if not branches:
492 branches=None
492 branches=None
493 for node in repo.changelog.nodesbetween(incoming, branches)[0]:
493 for node in repo.changelog.nodesbetween(incoming, branches)[0]:
494 if match(node):
494 if match(node):
495 yield node
495 yield node
496
496
497 def transplantwalk(repo, root, branches, match=util.always):
497 def transplantwalk(repo, root, branches, match=util.always):
498 if not branches:
498 if not branches:
499 branches = repo.heads()
499 branches = repo.heads()
500 ancestors = []
500 ancestors = []
501 for branch in branches:
501 for branch in branches:
502 ancestors.append(repo.changelog.ancestor(root, branch))
502 ancestors.append(repo.changelog.ancestor(root, branch))
503 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
503 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
504 if match(node):
504 if match(node):
505 yield node
505 yield node
506
506
507 def checkopts(opts, revs):
507 def checkopts(opts, revs):
508 if opts.get('continue'):
508 if opts.get('continue'):
509 if filter(lambda opt: opts.get(opt), ('branch', 'all', 'merge')):
509 if filter(lambda opt: opts.get(opt), ('branch', 'all', 'merge')):
510 raise util.Abort(_('--continue is incompatible with '
510 raise util.Abort(_('--continue is incompatible with '
511 'branch, all or merge'))
511 'branch, all or merge'))
512 return
512 return
513 if not (opts.get('source') or revs or
513 if not (opts.get('source') or revs or
514 opts.get('merge') or opts.get('branch')):
514 opts.get('merge') or opts.get('branch')):
515 raise util.Abort(_('no source URL, branch tag or revision '
515 raise util.Abort(_('no source URL, branch tag or revision '
516 'list provided'))
516 'list provided'))
517 if opts.get('all'):
517 if opts.get('all'):
518 if not opts.get('branch'):
518 if not opts.get('branch'):
519 raise util.Abort(_('--all requires a branch revision'))
519 raise util.Abort(_('--all requires a branch revision'))
520 if revs:
520 if revs:
521 raise util.Abort(_('--all is incompatible with a '
521 raise util.Abort(_('--all is incompatible with a '
522 'revision list'))
522 'revision list'))
523
523
524 checkopts(opts, revs)
524 checkopts(opts, revs)
525
525
526 if not opts.get('log'):
526 if not opts.get('log'):
527 opts['log'] = ui.config('transplant', 'log')
527 opts['log'] = ui.config('transplant', 'log')
528 if not opts.get('filter'):
528 if not opts.get('filter'):
529 opts['filter'] = ui.config('transplant', 'filter')
529 opts['filter'] = ui.config('transplant', 'filter')
530
530
531 tp = transplanter(ui, repo)
531 tp = transplanter(ui, repo)
532
532
533 p1, p2 = repo.dirstate.parents()
533 p1, p2 = repo.dirstate.parents()
534 if len(repo) > 0 and p1 == revlog.nullid:
534 if len(repo) > 0 and p1 == revlog.nullid:
535 raise util.Abort(_('no revision checked out'))
535 raise util.Abort(_('no revision checked out'))
536 if not opts.get('continue'):
536 if not opts.get('continue'):
537 if p2 != revlog.nullid:
537 if p2 != revlog.nullid:
538 raise util.Abort(_('outstanding uncommitted merges'))
538 raise util.Abort(_('outstanding uncommitted merges'))
539 m, a, r, d = repo.status()[:4]
539 m, a, r, d = repo.status()[:4]
540 if m or a or r or d:
540 if m or a or r or d:
541 raise util.Abort(_('outstanding local changes'))
541 raise util.Abort(_('outstanding local changes'))
542
542
543 bundle = None
543 bundle = None
544 source = opts.get('source')
544 source = opts.get('source')
545 if source:
545 if source:
546 (source, incoming, bundle) = getremotechanges(repo, source)
546 (source, incoming, bundle) = getremotechanges(repo, source)
547 else:
547 else:
548 source = repo
548 source = repo
549
549
550 try:
550 try:
551 if opts.get('continue'):
551 if opts.get('continue'):
552 tp.resume(repo, source, opts)
552 tp.resume(repo, source, opts)
553 return
553 return
554
554
555 tf=tp.transplantfilter(repo, source, p1)
555 tf=tp.transplantfilter(repo, source, p1)
556 if opts.get('prune'):
556 if opts.get('prune'):
557 prune = [source.lookup(r)
557 prune = [source.lookup(r)
558 for r in cmdutil.revrange(source, opts.get('prune'))]
558 for r in cmdutil.revrange(source, opts.get('prune'))]
559 matchfn = lambda x: tf(x) and x not in prune
559 matchfn = lambda x: tf(x) and x not in prune
560 else:
560 else:
561 matchfn = tf
561 matchfn = tf
562 branches = map(source.lookup, opts.get('branch', ()))
562 branches = map(source.lookup, opts.get('branch', ()))
563 merges = map(source.lookup, opts.get('merge', ()))
563 merges = map(source.lookup, opts.get('merge', ()))
564 revmap = {}
564 revmap = {}
565 if revs:
565 if revs:
566 for r in cmdutil.revrange(source, revs):
566 for r in cmdutil.revrange(source, revs):
567 revmap[int(r)] = source.lookup(r)
567 revmap[int(r)] = source.lookup(r)
568 elif opts.get('all') or not merges:
568 elif opts.get('all') or not merges:
569 if source != repo:
569 if source != repo:
570 alltransplants = incwalk(source, incoming, branches,
570 alltransplants = incwalk(source, incoming, branches,
571 match=matchfn)
571 match=matchfn)
572 else:
572 else:
573 alltransplants = transplantwalk(source, p1, branches,
573 alltransplants = transplantwalk(source, p1, branches,
574 match=matchfn)
574 match=matchfn)
575 if opts.get('all'):
575 if opts.get('all'):
576 revs = alltransplants
576 revs = alltransplants
577 else:
577 else:
578 revs, newmerges = browserevs(ui, source, alltransplants, opts)
578 revs, newmerges = browserevs(ui, source, alltransplants, opts)
579 merges.extend(newmerges)
579 merges.extend(newmerges)
580 for r in revs:
580 for r in revs:
581 revmap[source.changelog.rev(r)] = r
581 revmap[source.changelog.rev(r)] = r
582 for r in merges:
582 for r in merges:
583 revmap[source.changelog.rev(r)] = r
583 revmap[source.changelog.rev(r)] = r
584
584
585 tp.apply(repo, source, revmap, merges, opts)
585 tp.apply(repo, source, revmap, merges, opts)
586 finally:
586 finally:
587 if bundle:
587 if bundle:
588 source.close()
588 source.close()
589 os.unlink(bundle)
589 os.unlink(bundle)
590
590
591 cmdtable = {
591 cmdtable = {
592 "transplant":
592 "transplant":
593 (transplant,
593 (transplant,
594 [('s', 'source', '', _('pull patches from REPOSITORY')),
594 [('s', 'source', '', _('pull patches from REPOSITORY')),
595 ('b', 'branch', [], _('pull patches from branch BRANCH')),
595 ('b', 'branch', [], _('pull patches from branch BRANCH')),
596 ('a', 'all', None, _('pull all changesets up to BRANCH')),
596 ('a', 'all', None, _('pull all changesets up to BRANCH')),
597 ('p', 'prune', [], _('skip over REV')),
597 ('p', 'prune', [], _('skip over REV')),
598 ('m', 'merge', [], _('merge at REV')),
598 ('m', 'merge', [], _('merge at REV')),
599 ('', 'log', None, _('append transplant info to log message')),
599 ('', 'log', None, _('append transplant info to log message')),
600 ('c', 'continue', None, _('continue last transplant session '
600 ('c', 'continue', None, _('continue last transplant session '
601 'after repair')),
601 'after repair')),
602 ('', 'filter', '', _('filter changesets through FILTER'))],
602 ('', 'filter', '', _('filter changesets through FILTER'))],
603 _('hg transplant [-s REPOSITORY] [-b BRANCH [-a]] [-p REV] '
603 _('hg transplant [-s REPOSITORY] [-b BRANCH [-a]] [-p REV] '
604 '[-m REV] [REV]...'))
604 '[-m REV] [REV]...'))
605 }
605 }
@@ -1,298 +1,318 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 echo "[ui]" >> $HGRCPATH
3 echo "[ui]" >> $HGRCPATH
4 echo "interactive=true" >> $HGRCPATH
4 echo "interactive=true" >> $HGRCPATH
5 echo "[extensions]" >> $HGRCPATH
5 echo "[extensions]" >> $HGRCPATH
6 echo "record=" >> $HGRCPATH
6 echo "record=" >> $HGRCPATH
7
7
8 echo % help
8 echo % help
9
9
10 hg help record
10 hg help record
11
11
12 hg init a
12 hg init a
13 cd a
13 cd a
14
14
15 echo % select no files
15 echo % select no files
16
16
17 touch empty-rw
17 touch empty-rw
18 hg add empty-rw
18 hg add empty-rw
19 hg record empty-rw<<EOF
19 hg record empty-rw<<EOF
20 n
20 n
21 EOF
21 EOF
22 echo; hg tip -p
22 echo; hg tip -p
23
23
24 echo % select files but no hunks
24 echo % select files but no hunks
25
25
26 hg record empty-rw<<EOF
26 hg record empty-rw<<EOF
27 y
27 y
28 n
28 n
29 EOF
29 EOF
30 echo; hg tip -p
30 echo; hg tip -p
31
31
32 echo % record empty file
32 echo % record empty file
33
33
34 hg record -d '0 0' -m empty empty-rw<<EOF
34 hg record -d '0 0' -m empty empty-rw<<EOF
35 y
35 y
36 y
36 y
37 EOF
37 EOF
38 echo; hg tip -p
38 echo; hg tip -p
39
39
40 echo % rename empty file
40 echo % rename empty file
41
41
42 hg mv empty-rw empty-rename
42 hg mv empty-rw empty-rename
43 hg record -d '1 0' -m rename<<EOF
43 hg record -d '1 0' -m rename<<EOF
44 y
44 y
45 EOF
45 EOF
46 echo; hg tip -p
46 echo; hg tip -p
47
47
48 echo % copy empty file
48 echo % copy empty file
49
49
50 hg cp empty-rename empty-copy
50 hg cp empty-rename empty-copy
51 hg record -d '2 0' -m copy<<EOF
51 hg record -d '2 0' -m copy<<EOF
52 y
52 y
53 EOF
53 EOF
54 echo; hg tip -p
54 echo; hg tip -p
55
55
56 echo % delete empty file
56 echo % delete empty file
57
57
58 hg rm empty-copy
58 hg rm empty-copy
59 hg record -d '3 0' -m delete<<EOF
59 hg record -d '3 0' -m delete<<EOF
60 y
60 y
61 EOF
61 EOF
62 echo; hg tip -p
62 echo; hg tip -p
63
63
64 echo % add binary file
64 echo % add binary file
65
65
66 hg bundle --base -2 tip.bundle
66 hg bundle --base -2 tip.bundle
67 hg add tip.bundle
67 hg add tip.bundle
68 hg record -d '4 0' -m binary<<EOF
68 hg record -d '4 0' -m binary<<EOF
69 y
69 y
70 EOF
70 EOF
71 echo; hg tip -p
71 echo; hg tip -p
72
72
73 echo % change binary file
73 echo % change binary file
74
74
75 hg bundle --base -2 tip.bundle
75 hg bundle --base -2 tip.bundle
76 hg record -d '5 0' -m binary-change<<EOF
76 hg record -d '5 0' -m binary-change<<EOF
77 y
77 y
78 EOF
78 EOF
79 echo; hg tip -p
79 echo; hg tip -p
80
80
81 echo % rename and change binary file
81 echo % rename and change binary file
82
82
83 hg mv tip.bundle top.bundle
83 hg mv tip.bundle top.bundle
84 hg bundle --base -2 top.bundle
84 hg bundle --base -2 top.bundle
85 hg record -d '6 0' -m binary-change-rename<<EOF
85 hg record -d '6 0' -m binary-change-rename<<EOF
86 y
86 y
87 EOF
87 EOF
88 echo; hg tip -p
88 echo; hg tip -p
89
89
90 echo % add plain file
90 echo % add plain file
91
91
92 for i in 1 2 3 4 5 6 7 8 9 10; do
92 for i in 1 2 3 4 5 6 7 8 9 10; do
93 echo $i >> plain
93 echo $i >> plain
94 done
94 done
95
95
96 hg add plain
96 hg add plain
97 hg record -d '7 0' -m plain plain<<EOF
97 hg record -d '7 0' -m plain plain<<EOF
98 y
98 y
99 y
99 y
100 EOF
100 EOF
101 echo; hg tip -p
101 echo; hg tip -p
102
102
103 echo % modify end of plain file
103 echo % modify end of plain file
104
104
105 echo 11 >> plain
105 echo 11 >> plain
106 hg record -d '8 0' -m end plain <<EOF
106 hg record -d '8 0' -m end plain <<EOF
107 y
107 y
108 y
108 y
109 EOF
109 EOF
110
110
111 echo % modify end of plain file, no EOL
111 echo % modify end of plain file, no EOL
112
112
113 hg tip --template '{node}' >> plain
113 hg tip --template '{node}' >> plain
114 hg record -d '9 0' -m noeol plain <<EOF
114 hg record -d '9 0' -m noeol plain <<EOF
115 y
115 y
116 y
116 y
117 EOF
117 EOF
118
118
119 echo % modify end of plain file, add EOL
119 echo % modify end of plain file, add EOL
120
120
121 echo >> plain
121 echo >> plain
122 hg record -d '10 0' -m eol plain <<EOF
122 hg record -d '10 0' -m eol plain <<EOF
123 y
123 y
124 y
124 y
125 y
125 y
126 EOF
126 EOF
127
127
128 echo % modify beginning, trim end, record both
128 echo % modify beginning, trim end, record both
129
129
130 rm plain
130 rm plain
131 for i in 2 2 3 4 5 6 7 8 9 10; do
131 for i in 2 2 3 4 5 6 7 8 9 10; do
132 echo $i >> plain
132 echo $i >> plain
133 done
133 done
134
134
135 hg record -d '10 0' -m begin-and-end plain <<EOF
135 hg record -d '10 0' -m begin-and-end plain <<EOF
136 y
136 y
137 y
137 y
138 y
138 y
139 EOF
139 EOF
140 echo; hg tip -p
140 echo; hg tip -p
141
141
142 echo % trim beginning, modify end
142 echo % trim beginning, modify end
143
143
144 rm plain
144 rm plain
145 for i in 4 5 6 7 8 9 10.new; do
145 for i in 4 5 6 7 8 9 10.new; do
146 echo $i >> plain
146 echo $i >> plain
147 done
147 done
148
148
149 echo % record end
149 echo % record end
150
150
151 hg record -d '11 0' -m end-only plain <<EOF
151 hg record -d '11 0' -m end-only plain <<EOF
152 y
152 y
153 n
153 n
154 y
154 y
155 EOF
155 EOF
156 echo; hg tip -p
156 echo; hg tip -p
157
157
158 echo % record beginning
158 echo % record beginning
159
159
160 hg record -d '12 0' -m begin-only plain <<EOF
160 hg record -d '12 0' -m begin-only plain <<EOF
161 y
161 y
162 y
162 y
163 EOF
163 EOF
164 echo; hg tip -p
164 echo; hg tip -p
165
165
166 echo % add to beginning, trim from end
166 echo % add to beginning, trim from end
167
167
168 rm plain
168 rm plain
169 for i in 1 2 3 4 5 6 7 8 9; do
169 for i in 1 2 3 4 5 6 7 8 9; do
170 echo $i >> plain
170 echo $i >> plain
171 done
171 done
172
172
173 echo % record end
173 echo % record end
174
174
175 hg record --traceback -d '13 0' -m end-again plain<<EOF
175 hg record --traceback -d '13 0' -m end-again plain<<EOF
176 y
176 y
177 n
177 n
178 y
178 y
179 EOF
179 EOF
180
180
181 echo % add to beginning, middle, end
181 echo % add to beginning, middle, end
182
182
183 rm plain
183 rm plain
184 for i in 1 2 3 4 5 5.new 5.reallynew 6 7 8 9 10 11; do
184 for i in 1 2 3 4 5 5.new 5.reallynew 6 7 8 9 10 11; do
185 echo $i >> plain
185 echo $i >> plain
186 done
186 done
187
187
188 echo % record beginning, middle
188 echo % record beginning, middle
189
189
190 hg record -d '14 0' -m middle-only plain <<EOF
190 hg record -d '14 0' -m middle-only plain <<EOF
191 y
191 y
192 y
192 y
193 y
193 y
194 n
194 n
195 EOF
195 EOF
196 echo; hg tip -p
196 echo; hg tip -p
197
197
198 echo % record end
198 echo % record end
199
199
200 hg record -d '15 0' -m end-only plain <<EOF
200 hg record -d '15 0' -m end-only plain <<EOF
201 y
201 y
202 y
202 y
203 EOF
203 EOF
204 echo; hg tip -p
204 echo; hg tip -p
205
205
206 mkdir subdir
206 mkdir subdir
207 cd subdir
207 cd subdir
208 echo a > a
208 echo a > a
209 hg ci -d '16 0' -Amsubdir
209 hg ci -d '16 0' -Amsubdir
210
210
211 echo a >> a
211 echo a >> a
212 hg record -d '16 0' -m subdir-change a <<EOF
212 hg record -d '16 0' -m subdir-change a <<EOF
213 y
213 y
214 y
214 y
215 EOF
215 EOF
216 echo; hg tip -p
216 echo; hg tip -p
217
217
218 echo a > f1
218 echo a > f1
219 echo b > f2
219 echo b > f2
220 hg add f1 f2
220 hg add f1 f2
221
221
222 hg ci -mz -d '17 0'
222 hg ci -mz -d '17 0'
223
223
224 echo a >> f1
224 echo a >> f1
225 echo b >> f2
225 echo b >> f2
226
226
227 echo % help, quit
227 echo % help, quit
228
228
229 hg record <<EOF
229 hg record <<EOF
230 ?
230 ?
231 q
231 q
232 EOF
232 EOF
233
233
234 echo % skip
234 echo % skip
235
235
236 hg record <<EOF
236 hg record <<EOF
237 s
237 s
238 EOF
238 EOF
239
239
240 echo % no
240 echo % no
241
241
242 hg record <<EOF
242 hg record <<EOF
243 n
243 n
244 EOF
244 EOF
245
245
246 echo % f, quit
246 echo % f, quit
247
247
248 hg record <<EOF
248 hg record <<EOF
249 f
249 f
250 q
250 q
251 EOF
251 EOF
252
252
253 echo % s, all
253 echo % s, all
254
254
255 hg record -d '18 0' -mx <<EOF
255 hg record -d '18 0' -mx <<EOF
256 s
256 s
257 a
257 a
258 EOF
258 EOF
259 echo; hg tip -p
259 echo; hg tip -p
260
260
261 echo % f
261 echo % f
262
262
263 hg record -d '19 0' -my <<EOF
263 hg record -d '19 0' -my <<EOF
264 f
264 f
265 EOF
265 EOF
266 echo; hg tip -p
266 echo; hg tip -p
267
267
268 echo % preserve chmod +x
268 echo % preserve chmod +x
269
269
270 chmod +x f1
270 chmod +x f1
271 echo a >> f1
271 echo a >> f1
272 hg record -d '20 0' -mz <<EOF
272 hg record -d '20 0' -mz <<EOF
273 y
273 y
274 y
274 y
275 y
275 y
276 EOF
276 EOF
277 echo; hg tip --config diff.git=True -p
277 echo; hg tip --config diff.git=True -p
278
278
279 echo % preserve execute permission on original
279 echo % preserve execute permission on original
280
280
281 echo b >> f1
281 echo b >> f1
282 hg record -d '21 0' -maa <<EOF
282 hg record -d '21 0' -maa <<EOF
283 y
283 y
284 y
284 y
285 y
285 y
286 EOF
286 EOF
287 echo; hg tip --config diff.git=True -p
287 echo; hg tip --config diff.git=True -p
288
288
289 echo % preserve chmod -x
289 echo % preserve chmod -x
290
290
291 chmod -x f1
291 chmod -x f1
292 echo c >> f1
292 echo c >> f1
293 hg record -d '22 0' -mab <<EOF
293 hg record -d '22 0' -mab <<EOF
294 y
294 y
295 y
295 y
296 y
296 y
297 EOF
297 EOF
298 echo; hg tip --config diff.git=True -p
298 echo; hg tip --config diff.git=True -p
299
300
301 echo % with win32ext
302 cd ..
303 echo '[extensions]' >> .hg/hgrc
304 echo 'win32text = ' >> .hg/hgrc
305 echo '[decode]' >> .hg/hgrc
306 echo '** = cleverdecode:' >> .hg/hgrc
307 echo '[encode]' >> .hg/hgrc
308 echo '** = cleverencode:' >> .hg/hgrc
309 echo '[patch]' >> .hg/hgrc
310 echo 'eol = crlf' >> .hg/hgrc
311
312 echo d >> subdir/f1
313 hg record -d '23 0' -mw1 <<EOF
314 y
315 y
316 EOF
317 echo; hg tip -p
318
@@ -1,574 +1,598 b''
1 % help
1 % help
2 hg record [OPTION]... [FILE]...
2 hg record [OPTION]... [FILE]...
3
3
4 interactively select changes to commit
4 interactively select changes to commit
5
5
6 If a list of files is omitted, all changes reported by "hg status"
6 If a list of files is omitted, all changes reported by "hg status"
7 will be candidates for recording.
7 will be candidates for recording.
8
8
9 See 'hg help dates' for a list of formats valid for -d/--date.
9 See 'hg help dates' for a list of formats valid for -d/--date.
10
10
11 You will be prompted for whether to record changes to each
11 You will be prompted for whether to record changes to each
12 modified file, and for files with multiple changes, for each
12 modified file, and for files with multiple changes, for each
13 change to use. For each query, the following responses are
13 change to use. For each query, the following responses are
14 possible:
14 possible:
15
15
16 y - record this change
16 y - record this change
17 n - skip this change
17 n - skip this change
18
18
19 s - skip remaining changes to this file
19 s - skip remaining changes to this file
20 f - record remaining changes to this file
20 f - record remaining changes to this file
21
21
22 d - done, skip remaining changes and files
22 d - done, skip remaining changes and files
23 a - record all changes to all remaining files
23 a - record all changes to all remaining files
24 q - quit, recording no changes
24 q - quit, recording no changes
25
25
26 ? - display help
26 ? - display help
27
27
28 options:
28 options:
29
29
30 -A --addremove mark new/missing files as added/removed before
30 -A --addremove mark new/missing files as added/removed before
31 committing
31 committing
32 --close-branch mark a branch as closed, hiding it from the branch
32 --close-branch mark a branch as closed, hiding it from the branch
33 list
33 list
34 -I --include include names matching the given patterns
34 -I --include include names matching the given patterns
35 -X --exclude exclude names matching the given patterns
35 -X --exclude exclude names matching the given patterns
36 -m --message use <text> as commit message
36 -m --message use <text> as commit message
37 -l --logfile read commit message from <file>
37 -l --logfile read commit message from <file>
38 -d --date record datecode as commit date
38 -d --date record datecode as commit date
39 -u --user record the specified user as committer
39 -u --user record the specified user as committer
40
40
41 use "hg -v help record" to show global options
41 use "hg -v help record" to show global options
42 % select no files
42 % select no files
43 diff --git a/empty-rw b/empty-rw
43 diff --git a/empty-rw b/empty-rw
44 new file mode 100644
44 new file mode 100644
45 examine changes to 'empty-rw'? [Ynsfdaq?] no changes to record
45 examine changes to 'empty-rw'? [Ynsfdaq?] no changes to record
46
46
47 changeset: -1:000000000000
47 changeset: -1:000000000000
48 tag: tip
48 tag: tip
49 user:
49 user:
50 date: Thu Jan 01 00:00:00 1970 +0000
50 date: Thu Jan 01 00:00:00 1970 +0000
51
51
52
52
53 % select files but no hunks
53 % select files but no hunks
54 diff --git a/empty-rw b/empty-rw
54 diff --git a/empty-rw b/empty-rw
55 new file mode 100644
55 new file mode 100644
56 examine changes to 'empty-rw'? [Ynsfdaq?] abort: empty commit message
56 examine changes to 'empty-rw'? [Ynsfdaq?] abort: empty commit message
57
57
58 changeset: -1:000000000000
58 changeset: -1:000000000000
59 tag: tip
59 tag: tip
60 user:
60 user:
61 date: Thu Jan 01 00:00:00 1970 +0000
61 date: Thu Jan 01 00:00:00 1970 +0000
62
62
63
63
64 % record empty file
64 % record empty file
65 diff --git a/empty-rw b/empty-rw
65 diff --git a/empty-rw b/empty-rw
66 new file mode 100644
66 new file mode 100644
67 examine changes to 'empty-rw'? [Ynsfdaq?]
67 examine changes to 'empty-rw'? [Ynsfdaq?]
68 changeset: 0:c0708cf4e46e
68 changeset: 0:c0708cf4e46e
69 tag: tip
69 tag: tip
70 user: test
70 user: test
71 date: Thu Jan 01 00:00:00 1970 +0000
71 date: Thu Jan 01 00:00:00 1970 +0000
72 summary: empty
72 summary: empty
73
73
74
74
75 % rename empty file
75 % rename empty file
76 diff --git a/empty-rw b/empty-rename
76 diff --git a/empty-rw b/empty-rename
77 rename from empty-rw
77 rename from empty-rw
78 rename to empty-rename
78 rename to empty-rename
79 examine changes to 'empty-rw' and 'empty-rename'? [Ynsfdaq?]
79 examine changes to 'empty-rw' and 'empty-rename'? [Ynsfdaq?]
80 changeset: 1:df251d174da3
80 changeset: 1:df251d174da3
81 tag: tip
81 tag: tip
82 user: test
82 user: test
83 date: Thu Jan 01 00:00:01 1970 +0000
83 date: Thu Jan 01 00:00:01 1970 +0000
84 summary: rename
84 summary: rename
85
85
86
86
87 % copy empty file
87 % copy empty file
88 diff --git a/empty-rename b/empty-copy
88 diff --git a/empty-rename b/empty-copy
89 copy from empty-rename
89 copy from empty-rename
90 copy to empty-copy
90 copy to empty-copy
91 examine changes to 'empty-rename' and 'empty-copy'? [Ynsfdaq?]
91 examine changes to 'empty-rename' and 'empty-copy'? [Ynsfdaq?]
92 changeset: 2:b63ea3939f8d
92 changeset: 2:b63ea3939f8d
93 tag: tip
93 tag: tip
94 user: test
94 user: test
95 date: Thu Jan 01 00:00:02 1970 +0000
95 date: Thu Jan 01 00:00:02 1970 +0000
96 summary: copy
96 summary: copy
97
97
98
98
99 % delete empty file
99 % delete empty file
100 diff --git a/empty-copy b/empty-copy
100 diff --git a/empty-copy b/empty-copy
101 deleted file mode 100644
101 deleted file mode 100644
102 examine changes to 'empty-copy'? [Ynsfdaq?]
102 examine changes to 'empty-copy'? [Ynsfdaq?]
103 changeset: 3:a2546574bce9
103 changeset: 3:a2546574bce9
104 tag: tip
104 tag: tip
105 user: test
105 user: test
106 date: Thu Jan 01 00:00:03 1970 +0000
106 date: Thu Jan 01 00:00:03 1970 +0000
107 summary: delete
107 summary: delete
108
108
109
109
110 % add binary file
110 % add binary file
111 1 changesets found
111 1 changesets found
112 diff --git a/tip.bundle b/tip.bundle
112 diff --git a/tip.bundle b/tip.bundle
113 new file mode 100644
113 new file mode 100644
114 this is a binary file
114 this is a binary file
115 examine changes to 'tip.bundle'? [Ynsfdaq?]
115 examine changes to 'tip.bundle'? [Ynsfdaq?]
116 changeset: 4:9e998a545a8b
116 changeset: 4:9e998a545a8b
117 tag: tip
117 tag: tip
118 user: test
118 user: test
119 date: Thu Jan 01 00:00:04 1970 +0000
119 date: Thu Jan 01 00:00:04 1970 +0000
120 summary: binary
120 summary: binary
121
121
122 diff -r a2546574bce9 -r 9e998a545a8b tip.bundle
122 diff -r a2546574bce9 -r 9e998a545a8b tip.bundle
123 Binary file tip.bundle has changed
123 Binary file tip.bundle has changed
124
124
125 % change binary file
125 % change binary file
126 1 changesets found
126 1 changesets found
127 diff --git a/tip.bundle b/tip.bundle
127 diff --git a/tip.bundle b/tip.bundle
128 this modifies a binary file (all or nothing)
128 this modifies a binary file (all or nothing)
129 examine changes to 'tip.bundle'? [Ynsfdaq?]
129 examine changes to 'tip.bundle'? [Ynsfdaq?]
130 changeset: 5:93d05561507d
130 changeset: 5:93d05561507d
131 tag: tip
131 tag: tip
132 user: test
132 user: test
133 date: Thu Jan 01 00:00:05 1970 +0000
133 date: Thu Jan 01 00:00:05 1970 +0000
134 summary: binary-change
134 summary: binary-change
135
135
136 diff -r 9e998a545a8b -r 93d05561507d tip.bundle
136 diff -r 9e998a545a8b -r 93d05561507d tip.bundle
137 Binary file tip.bundle has changed
137 Binary file tip.bundle has changed
138
138
139 % rename and change binary file
139 % rename and change binary file
140 1 changesets found
140 1 changesets found
141 diff --git a/tip.bundle b/top.bundle
141 diff --git a/tip.bundle b/top.bundle
142 rename from tip.bundle
142 rename from tip.bundle
143 rename to top.bundle
143 rename to top.bundle
144 this modifies a binary file (all or nothing)
144 this modifies a binary file (all or nothing)
145 examine changes to 'tip.bundle' and 'top.bundle'? [Ynsfdaq?]
145 examine changes to 'tip.bundle' and 'top.bundle'? [Ynsfdaq?]
146 changeset: 6:699cc1bea9aa
146 changeset: 6:699cc1bea9aa
147 tag: tip
147 tag: tip
148 user: test
148 user: test
149 date: Thu Jan 01 00:00:06 1970 +0000
149 date: Thu Jan 01 00:00:06 1970 +0000
150 summary: binary-change-rename
150 summary: binary-change-rename
151
151
152 diff -r 93d05561507d -r 699cc1bea9aa tip.bundle
152 diff -r 93d05561507d -r 699cc1bea9aa tip.bundle
153 Binary file tip.bundle has changed
153 Binary file tip.bundle has changed
154 diff -r 93d05561507d -r 699cc1bea9aa top.bundle
154 diff -r 93d05561507d -r 699cc1bea9aa top.bundle
155 Binary file top.bundle has changed
155 Binary file top.bundle has changed
156
156
157 % add plain file
157 % add plain file
158 diff --git a/plain b/plain
158 diff --git a/plain b/plain
159 new file mode 100644
159 new file mode 100644
160 examine changes to 'plain'? [Ynsfdaq?]
160 examine changes to 'plain'? [Ynsfdaq?]
161 changeset: 7:118ed744216b
161 changeset: 7:118ed744216b
162 tag: tip
162 tag: tip
163 user: test
163 user: test
164 date: Thu Jan 01 00:00:07 1970 +0000
164 date: Thu Jan 01 00:00:07 1970 +0000
165 summary: plain
165 summary: plain
166
166
167 diff -r 699cc1bea9aa -r 118ed744216b plain
167 diff -r 699cc1bea9aa -r 118ed744216b plain
168 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
168 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
169 +++ b/plain Thu Jan 01 00:00:07 1970 +0000
169 +++ b/plain Thu Jan 01 00:00:07 1970 +0000
170 @@ -0,0 +1,10 @@
170 @@ -0,0 +1,10 @@
171 +1
171 +1
172 +2
172 +2
173 +3
173 +3
174 +4
174 +4
175 +5
175 +5
176 +6
176 +6
177 +7
177 +7
178 +8
178 +8
179 +9
179 +9
180 +10
180 +10
181
181
182 % modify end of plain file
182 % modify end of plain file
183 diff --git a/plain b/plain
183 diff --git a/plain b/plain
184 1 hunks, 1 lines changed
184 1 hunks, 1 lines changed
185 examine changes to 'plain'? [Ynsfdaq?] @@ -8,3 +8,4 @@
185 examine changes to 'plain'? [Ynsfdaq?] @@ -8,3 +8,4 @@
186 8
186 8
187 9
187 9
188 10
188 10
189 +11
189 +11
190 record this change to 'plain'? [Ynsfdaq?] % modify end of plain file, no EOL
190 record this change to 'plain'? [Ynsfdaq?] % modify end of plain file, no EOL
191 diff --git a/plain b/plain
191 diff --git a/plain b/plain
192 1 hunks, 1 lines changed
192 1 hunks, 1 lines changed
193 examine changes to 'plain'? [Ynsfdaq?] @@ -9,3 +9,4 @@
193 examine changes to 'plain'? [Ynsfdaq?] @@ -9,3 +9,4 @@
194 9
194 9
195 10
195 10
196 11
196 11
197 +cf81a2760718a74d44c0c2eecb72f659e63a69c5
197 +cf81a2760718a74d44c0c2eecb72f659e63a69c5
198 \ No newline at end of file
198 \ No newline at end of file
199 record this change to 'plain'? [Ynsfdaq?] % modify end of plain file, add EOL
199 record this change to 'plain'? [Ynsfdaq?] % modify end of plain file, add EOL
200 diff --git a/plain b/plain
200 diff --git a/plain b/plain
201 1 hunks, 2 lines changed
201 1 hunks, 2 lines changed
202 examine changes to 'plain'? [Ynsfdaq?] @@ -9,4 +9,4 @@
202 examine changes to 'plain'? [Ynsfdaq?] @@ -9,4 +9,4 @@
203 9
203 9
204 10
204 10
205 11
205 11
206 -cf81a2760718a74d44c0c2eecb72f659e63a69c5
206 -cf81a2760718a74d44c0c2eecb72f659e63a69c5
207 \ No newline at end of file
207 \ No newline at end of file
208 +cf81a2760718a74d44c0c2eecb72f659e63a69c5
208 +cf81a2760718a74d44c0c2eecb72f659e63a69c5
209 record this change to 'plain'? [Ynsfdaq?] % modify beginning, trim end, record both
209 record this change to 'plain'? [Ynsfdaq?] % modify beginning, trim end, record both
210 diff --git a/plain b/plain
210 diff --git a/plain b/plain
211 2 hunks, 4 lines changed
211 2 hunks, 4 lines changed
212 examine changes to 'plain'? [Ynsfdaq?] @@ -1,4 +1,4 @@
212 examine changes to 'plain'? [Ynsfdaq?] @@ -1,4 +1,4 @@
213 -1
213 -1
214 +2
214 +2
215 2
215 2
216 3
216 3
217 4
217 4
218 record change 1/2 to 'plain'? [Ynsfdaq?] @@ -8,5 +8,3 @@
218 record change 1/2 to 'plain'? [Ynsfdaq?] @@ -8,5 +8,3 @@
219 8
219 8
220 9
220 9
221 10
221 10
222 -11
222 -11
223 -cf81a2760718a74d44c0c2eecb72f659e63a69c5
223 -cf81a2760718a74d44c0c2eecb72f659e63a69c5
224 record change 2/2 to 'plain'? [Ynsfdaq?]
224 record change 2/2 to 'plain'? [Ynsfdaq?]
225 changeset: 11:d09ab1967dab
225 changeset: 11:d09ab1967dab
226 tag: tip
226 tag: tip
227 user: test
227 user: test
228 date: Thu Jan 01 00:00:10 1970 +0000
228 date: Thu Jan 01 00:00:10 1970 +0000
229 summary: begin-and-end
229 summary: begin-and-end
230
230
231 diff -r e2ecd9b0b78d -r d09ab1967dab plain
231 diff -r e2ecd9b0b78d -r d09ab1967dab plain
232 --- a/plain Thu Jan 01 00:00:10 1970 +0000
232 --- a/plain Thu Jan 01 00:00:10 1970 +0000
233 +++ b/plain Thu Jan 01 00:00:10 1970 +0000
233 +++ b/plain Thu Jan 01 00:00:10 1970 +0000
234 @@ -1,4 +1,4 @@
234 @@ -1,4 +1,4 @@
235 -1
235 -1
236 +2
236 +2
237 2
237 2
238 3
238 3
239 4
239 4
240 @@ -8,5 +8,3 @@
240 @@ -8,5 +8,3 @@
241 8
241 8
242 9
242 9
243 10
243 10
244 -11
244 -11
245 -cf81a2760718a74d44c0c2eecb72f659e63a69c5
245 -cf81a2760718a74d44c0c2eecb72f659e63a69c5
246
246
247 % trim beginning, modify end
247 % trim beginning, modify end
248 % record end
248 % record end
249 diff --git a/plain b/plain
249 diff --git a/plain b/plain
250 2 hunks, 5 lines changed
250 2 hunks, 5 lines changed
251 examine changes to 'plain'? [Ynsfdaq?] @@ -1,9 +1,6 @@
251 examine changes to 'plain'? [Ynsfdaq?] @@ -1,9 +1,6 @@
252 -2
252 -2
253 -2
253 -2
254 -3
254 -3
255 4
255 4
256 5
256 5
257 6
257 6
258 7
258 7
259 8
259 8
260 9
260 9
261 record change 1/2 to 'plain'? [Ynsfdaq?] @@ -4,7 +1,7 @@
261 record change 1/2 to 'plain'? [Ynsfdaq?] @@ -4,7 +1,7 @@
262 4
262 4
263 5
263 5
264 6
264 6
265 7
265 7
266 8
266 8
267 9
267 9
268 -10
268 -10
269 +10.new
269 +10.new
270 record change 2/2 to 'plain'? [Ynsfdaq?]
270 record change 2/2 to 'plain'? [Ynsfdaq?]
271 changeset: 12:44516c9708ae
271 changeset: 12:44516c9708ae
272 tag: tip
272 tag: tip
273 user: test
273 user: test
274 date: Thu Jan 01 00:00:11 1970 +0000
274 date: Thu Jan 01 00:00:11 1970 +0000
275 summary: end-only
275 summary: end-only
276
276
277 diff -r d09ab1967dab -r 44516c9708ae plain
277 diff -r d09ab1967dab -r 44516c9708ae plain
278 --- a/plain Thu Jan 01 00:00:10 1970 +0000
278 --- a/plain Thu Jan 01 00:00:10 1970 +0000
279 +++ b/plain Thu Jan 01 00:00:11 1970 +0000
279 +++ b/plain Thu Jan 01 00:00:11 1970 +0000
280 @@ -7,4 +7,4 @@
280 @@ -7,4 +7,4 @@
281 7
281 7
282 8
282 8
283 9
283 9
284 -10
284 -10
285 +10.new
285 +10.new
286
286
287 % record beginning
287 % record beginning
288 diff --git a/plain b/plain
288 diff --git a/plain b/plain
289 1 hunks, 3 lines changed
289 1 hunks, 3 lines changed
290 examine changes to 'plain'? [Ynsfdaq?] @@ -1,6 +1,3 @@
290 examine changes to 'plain'? [Ynsfdaq?] @@ -1,6 +1,3 @@
291 -2
291 -2
292 -2
292 -2
293 -3
293 -3
294 4
294 4
295 5
295 5
296 6
296 6
297 record this change to 'plain'? [Ynsfdaq?]
297 record this change to 'plain'? [Ynsfdaq?]
298 changeset: 13:3ebbace64a8d
298 changeset: 13:3ebbace64a8d
299 tag: tip
299 tag: tip
300 user: test
300 user: test
301 date: Thu Jan 01 00:00:12 1970 +0000
301 date: Thu Jan 01 00:00:12 1970 +0000
302 summary: begin-only
302 summary: begin-only
303
303
304 diff -r 44516c9708ae -r 3ebbace64a8d plain
304 diff -r 44516c9708ae -r 3ebbace64a8d plain
305 --- a/plain Thu Jan 01 00:00:11 1970 +0000
305 --- a/plain Thu Jan 01 00:00:11 1970 +0000
306 +++ b/plain Thu Jan 01 00:00:12 1970 +0000
306 +++ b/plain Thu Jan 01 00:00:12 1970 +0000
307 @@ -1,6 +1,3 @@
307 @@ -1,6 +1,3 @@
308 -2
308 -2
309 -2
309 -2
310 -3
310 -3
311 4
311 4
312 5
312 5
313 6
313 6
314
314
315 % add to beginning, trim from end
315 % add to beginning, trim from end
316 % record end
316 % record end
317 diff --git a/plain b/plain
317 diff --git a/plain b/plain
318 2 hunks, 4 lines changed
318 2 hunks, 4 lines changed
319 examine changes to 'plain'? [Ynsfdaq?] @@ -1,6 +1,9 @@
319 examine changes to 'plain'? [Ynsfdaq?] @@ -1,6 +1,9 @@
320 +1
320 +1
321 +2
321 +2
322 +3
322 +3
323 4
323 4
324 5
324 5
325 6
325 6
326 7
326 7
327 8
327 8
328 9
328 9
329 record change 1/2 to 'plain'? [Ynsfdaq?] @@ -1,7 +4,6 @@
329 record change 1/2 to 'plain'? [Ynsfdaq?] @@ -1,7 +4,6 @@
330 4
330 4
331 5
331 5
332 6
332 6
333 7
333 7
334 8
334 8
335 9
335 9
336 -10.new
336 -10.new
337 record change 2/2 to 'plain'? [Ynsfdaq?] % add to beginning, middle, end
337 record change 2/2 to 'plain'? [Ynsfdaq?] % add to beginning, middle, end
338 % record beginning, middle
338 % record beginning, middle
339 diff --git a/plain b/plain
339 diff --git a/plain b/plain
340 3 hunks, 7 lines changed
340 3 hunks, 7 lines changed
341 examine changes to 'plain'? [Ynsfdaq?] @@ -1,2 +1,5 @@
341 examine changes to 'plain'? [Ynsfdaq?] @@ -1,2 +1,5 @@
342 +1
342 +1
343 +2
343 +2
344 +3
344 +3
345 4
345 4
346 5
346 5
347 record change 1/3 to 'plain'? [Ynsfdaq?] @@ -1,6 +4,8 @@
347 record change 1/3 to 'plain'? [Ynsfdaq?] @@ -1,6 +4,8 @@
348 4
348 4
349 5
349 5
350 +5.new
350 +5.new
351 +5.reallynew
351 +5.reallynew
352 6
352 6
353 7
353 7
354 8
354 8
355 9
355 9
356 record change 2/3 to 'plain'? [Ynsfdaq?] @@ -3,4 +8,6 @@
356 record change 2/3 to 'plain'? [Ynsfdaq?] @@ -3,4 +8,6 @@
357 6
357 6
358 7
358 7
359 8
359 8
360 9
360 9
361 +10
361 +10
362 +11
362 +11
363 record change 3/3 to 'plain'? [Ynsfdaq?]
363 record change 3/3 to 'plain'? [Ynsfdaq?]
364 changeset: 15:c1c639d8b268
364 changeset: 15:c1c639d8b268
365 tag: tip
365 tag: tip
366 user: test
366 user: test
367 date: Thu Jan 01 00:00:14 1970 +0000
367 date: Thu Jan 01 00:00:14 1970 +0000
368 summary: middle-only
368 summary: middle-only
369
369
370 diff -r efc0dad7bd9f -r c1c639d8b268 plain
370 diff -r efc0dad7bd9f -r c1c639d8b268 plain
371 --- a/plain Thu Jan 01 00:00:13 1970 +0000
371 --- a/plain Thu Jan 01 00:00:13 1970 +0000
372 +++ b/plain Thu Jan 01 00:00:14 1970 +0000
372 +++ b/plain Thu Jan 01 00:00:14 1970 +0000
373 @@ -1,5 +1,10 @@
373 @@ -1,5 +1,10 @@
374 +1
374 +1
375 +2
375 +2
376 +3
376 +3
377 4
377 4
378 5
378 5
379 +5.new
379 +5.new
380 +5.reallynew
380 +5.reallynew
381 6
381 6
382 7
382 7
383 8
383 8
384
384
385 % record end
385 % record end
386 diff --git a/plain b/plain
386 diff --git a/plain b/plain
387 1 hunks, 2 lines changed
387 1 hunks, 2 lines changed
388 examine changes to 'plain'? [Ynsfdaq?] @@ -9,3 +9,5 @@
388 examine changes to 'plain'? [Ynsfdaq?] @@ -9,3 +9,5 @@
389 7
389 7
390 8
390 8
391 9
391 9
392 +10
392 +10
393 +11
393 +11
394 record this change to 'plain'? [Ynsfdaq?]
394 record this change to 'plain'? [Ynsfdaq?]
395 changeset: 16:80b74bbc7808
395 changeset: 16:80b74bbc7808
396 tag: tip
396 tag: tip
397 user: test
397 user: test
398 date: Thu Jan 01 00:00:15 1970 +0000
398 date: Thu Jan 01 00:00:15 1970 +0000
399 summary: end-only
399 summary: end-only
400
400
401 diff -r c1c639d8b268 -r 80b74bbc7808 plain
401 diff -r c1c639d8b268 -r 80b74bbc7808 plain
402 --- a/plain Thu Jan 01 00:00:14 1970 +0000
402 --- a/plain Thu Jan 01 00:00:14 1970 +0000
403 +++ b/plain Thu Jan 01 00:00:15 1970 +0000
403 +++ b/plain Thu Jan 01 00:00:15 1970 +0000
404 @@ -9,3 +9,5 @@
404 @@ -9,3 +9,5 @@
405 7
405 7
406 8
406 8
407 9
407 9
408 +10
408 +10
409 +11
409 +11
410
410
411 adding subdir/a
411 adding subdir/a
412 diff --git a/subdir/a b/subdir/a
412 diff --git a/subdir/a b/subdir/a
413 1 hunks, 1 lines changed
413 1 hunks, 1 lines changed
414 examine changes to 'subdir/a'? [Ynsfdaq?] @@ -1,1 +1,2 @@
414 examine changes to 'subdir/a'? [Ynsfdaq?] @@ -1,1 +1,2 @@
415 a
415 a
416 +a
416 +a
417 record this change to 'subdir/a'? [Ynsfdaq?]
417 record this change to 'subdir/a'? [Ynsfdaq?]
418 changeset: 18:33ff5c4fb017
418 changeset: 18:33ff5c4fb017
419 tag: tip
419 tag: tip
420 user: test
420 user: test
421 date: Thu Jan 01 00:00:16 1970 +0000
421 date: Thu Jan 01 00:00:16 1970 +0000
422 summary: subdir-change
422 summary: subdir-change
423
423
424 diff -r aecf2b2ea83c -r 33ff5c4fb017 subdir/a
424 diff -r aecf2b2ea83c -r 33ff5c4fb017 subdir/a
425 --- a/subdir/a Thu Jan 01 00:00:16 1970 +0000
425 --- a/subdir/a Thu Jan 01 00:00:16 1970 +0000
426 +++ b/subdir/a Thu Jan 01 00:00:16 1970 +0000
426 +++ b/subdir/a Thu Jan 01 00:00:16 1970 +0000
427 @@ -1,1 +1,2 @@
427 @@ -1,1 +1,2 @@
428 a
428 a
429 +a
429 +a
430
430
431 % help, quit
431 % help, quit
432 diff --git a/subdir/f1 b/subdir/f1
432 diff --git a/subdir/f1 b/subdir/f1
433 1 hunks, 1 lines changed
433 1 hunks, 1 lines changed
434 examine changes to 'subdir/f1'? [Ynsfdaq?] y - record this change
434 examine changes to 'subdir/f1'? [Ynsfdaq?] y - record this change
435 n - skip this change
435 n - skip this change
436 s - skip remaining changes to this file
436 s - skip remaining changes to this file
437 f - record remaining changes to this file
437 f - record remaining changes to this file
438 d - done, skip remaining changes and files
438 d - done, skip remaining changes and files
439 a - record all changes to all remaining files
439 a - record all changes to all remaining files
440 q - quit, recording no changes
440 q - quit, recording no changes
441 ? - display help
441 ? - display help
442 examine changes to 'subdir/f1'? [Ynsfdaq?] abort: user quit
442 examine changes to 'subdir/f1'? [Ynsfdaq?] abort: user quit
443 % skip
443 % skip
444 diff --git a/subdir/f1 b/subdir/f1
444 diff --git a/subdir/f1 b/subdir/f1
445 1 hunks, 1 lines changed
445 1 hunks, 1 lines changed
446 examine changes to 'subdir/f1'? [Ynsfdaq?] diff --git a/subdir/f2 b/subdir/f2
446 examine changes to 'subdir/f1'? [Ynsfdaq?] diff --git a/subdir/f2 b/subdir/f2
447 1 hunks, 1 lines changed
447 1 hunks, 1 lines changed
448 examine changes to 'subdir/f2'? [Ynsfdaq?] abort: response expected
448 examine changes to 'subdir/f2'? [Ynsfdaq?] abort: response expected
449 % no
449 % no
450 diff --git a/subdir/f1 b/subdir/f1
450 diff --git a/subdir/f1 b/subdir/f1
451 1 hunks, 1 lines changed
451 1 hunks, 1 lines changed
452 examine changes to 'subdir/f1'? [Ynsfdaq?] diff --git a/subdir/f2 b/subdir/f2
452 examine changes to 'subdir/f1'? [Ynsfdaq?] diff --git a/subdir/f2 b/subdir/f2
453 1 hunks, 1 lines changed
453 1 hunks, 1 lines changed
454 examine changes to 'subdir/f2'? [Ynsfdaq?] abort: response expected
454 examine changes to 'subdir/f2'? [Ynsfdaq?] abort: response expected
455 % f, quit
455 % f, quit
456 diff --git a/subdir/f1 b/subdir/f1
456 diff --git a/subdir/f1 b/subdir/f1
457 1 hunks, 1 lines changed
457 1 hunks, 1 lines changed
458 examine changes to 'subdir/f1'? [Ynsfdaq?] diff --git a/subdir/f2 b/subdir/f2
458 examine changes to 'subdir/f1'? [Ynsfdaq?] diff --git a/subdir/f2 b/subdir/f2
459 1 hunks, 1 lines changed
459 1 hunks, 1 lines changed
460 examine changes to 'subdir/f2'? [Ynsfdaq?] abort: user quit
460 examine changes to 'subdir/f2'? [Ynsfdaq?] abort: user quit
461 % s, all
461 % s, all
462 diff --git a/subdir/f1 b/subdir/f1
462 diff --git a/subdir/f1 b/subdir/f1
463 1 hunks, 1 lines changed
463 1 hunks, 1 lines changed
464 examine changes to 'subdir/f1'? [Ynsfdaq?] diff --git a/subdir/f2 b/subdir/f2
464 examine changes to 'subdir/f1'? [Ynsfdaq?] diff --git a/subdir/f2 b/subdir/f2
465 1 hunks, 1 lines changed
465 1 hunks, 1 lines changed
466 examine changes to 'subdir/f2'? [Ynsfdaq?]
466 examine changes to 'subdir/f2'? [Ynsfdaq?]
467 changeset: 20:094183e04b7c
467 changeset: 20:094183e04b7c
468 tag: tip
468 tag: tip
469 user: test
469 user: test
470 date: Thu Jan 01 00:00:18 1970 +0000
470 date: Thu Jan 01 00:00:18 1970 +0000
471 summary: x
471 summary: x
472
472
473 diff -r f9e855cd9374 -r 094183e04b7c subdir/f2
473 diff -r f9e855cd9374 -r 094183e04b7c subdir/f2
474 --- a/subdir/f2 Thu Jan 01 00:00:17 1970 +0000
474 --- a/subdir/f2 Thu Jan 01 00:00:17 1970 +0000
475 +++ b/subdir/f2 Thu Jan 01 00:00:18 1970 +0000
475 +++ b/subdir/f2 Thu Jan 01 00:00:18 1970 +0000
476 @@ -1,1 +1,2 @@
476 @@ -1,1 +1,2 @@
477 b
477 b
478 +b
478 +b
479
479
480 % f
480 % f
481 diff --git a/subdir/f1 b/subdir/f1
481 diff --git a/subdir/f1 b/subdir/f1
482 1 hunks, 1 lines changed
482 1 hunks, 1 lines changed
483 examine changes to 'subdir/f1'? [Ynsfdaq?]
483 examine changes to 'subdir/f1'? [Ynsfdaq?]
484 changeset: 21:38164785b0ef
484 changeset: 21:38164785b0ef
485 tag: tip
485 tag: tip
486 user: test
486 user: test
487 date: Thu Jan 01 00:00:19 1970 +0000
487 date: Thu Jan 01 00:00:19 1970 +0000
488 summary: y
488 summary: y
489
489
490 diff -r 094183e04b7c -r 38164785b0ef subdir/f1
490 diff -r 094183e04b7c -r 38164785b0ef subdir/f1
491 --- a/subdir/f1 Thu Jan 01 00:00:18 1970 +0000
491 --- a/subdir/f1 Thu Jan 01 00:00:18 1970 +0000
492 +++ b/subdir/f1 Thu Jan 01 00:00:19 1970 +0000
492 +++ b/subdir/f1 Thu Jan 01 00:00:19 1970 +0000
493 @@ -1,1 +1,2 @@
493 @@ -1,1 +1,2 @@
494 a
494 a
495 +a
495 +a
496
496
497 % preserve chmod +x
497 % preserve chmod +x
498 diff --git a/subdir/f1 b/subdir/f1
498 diff --git a/subdir/f1 b/subdir/f1
499 old mode 100644
499 old mode 100644
500 new mode 100755
500 new mode 100755
501 1 hunks, 1 lines changed
501 1 hunks, 1 lines changed
502 examine changes to 'subdir/f1'? [Ynsfdaq?] @@ -1,2 +1,3 @@
502 examine changes to 'subdir/f1'? [Ynsfdaq?] @@ -1,2 +1,3 @@
503 a
503 a
504 a
504 a
505 +a
505 +a
506 record this change to 'subdir/f1'? [Ynsfdaq?]
506 record this change to 'subdir/f1'? [Ynsfdaq?]
507 changeset: 22:a891589cb933
507 changeset: 22:a891589cb933
508 tag: tip
508 tag: tip
509 user: test
509 user: test
510 date: Thu Jan 01 00:00:20 1970 +0000
510 date: Thu Jan 01 00:00:20 1970 +0000
511 summary: z
511 summary: z
512
512
513 diff --git a/subdir/f1 b/subdir/f1
513 diff --git a/subdir/f1 b/subdir/f1
514 old mode 100644
514 old mode 100644
515 new mode 100755
515 new mode 100755
516 --- a/subdir/f1
516 --- a/subdir/f1
517 +++ b/subdir/f1
517 +++ b/subdir/f1
518 @@ -1,2 +1,3 @@
518 @@ -1,2 +1,3 @@
519 a
519 a
520 a
520 a
521 +a
521 +a
522
522
523 % preserve execute permission on original
523 % preserve execute permission on original
524 diff --git a/subdir/f1 b/subdir/f1
524 diff --git a/subdir/f1 b/subdir/f1
525 1 hunks, 1 lines changed
525 1 hunks, 1 lines changed
526 examine changes to 'subdir/f1'? [Ynsfdaq?] @@ -1,3 +1,4 @@
526 examine changes to 'subdir/f1'? [Ynsfdaq?] @@ -1,3 +1,4 @@
527 a
527 a
528 a
528 a
529 a
529 a
530 +b
530 +b
531 record this change to 'subdir/f1'? [Ynsfdaq?]
531 record this change to 'subdir/f1'? [Ynsfdaq?]
532 changeset: 23:befa0dae6201
532 changeset: 23:befa0dae6201
533 tag: tip
533 tag: tip
534 user: test
534 user: test
535 date: Thu Jan 01 00:00:21 1970 +0000
535 date: Thu Jan 01 00:00:21 1970 +0000
536 summary: aa
536 summary: aa
537
537
538 diff --git a/subdir/f1 b/subdir/f1
538 diff --git a/subdir/f1 b/subdir/f1
539 --- a/subdir/f1
539 --- a/subdir/f1
540 +++ b/subdir/f1
540 +++ b/subdir/f1
541 @@ -1,3 +1,4 @@
541 @@ -1,3 +1,4 @@
542 a
542 a
543 a
543 a
544 a
544 a
545 +b
545 +b
546
546
547 % preserve chmod -x
547 % preserve chmod -x
548 diff --git a/subdir/f1 b/subdir/f1
548 diff --git a/subdir/f1 b/subdir/f1
549 old mode 100755
549 old mode 100755
550 new mode 100644
550 new mode 100644
551 1 hunks, 1 lines changed
551 1 hunks, 1 lines changed
552 examine changes to 'subdir/f1'? [Ynsfdaq?] @@ -2,3 +2,4 @@
552 examine changes to 'subdir/f1'? [Ynsfdaq?] @@ -2,3 +2,4 @@
553 a
553 a
554 a
554 a
555 b
555 b
556 +c
556 +c
557 record this change to 'subdir/f1'? [Ynsfdaq?]
557 record this change to 'subdir/f1'? [Ynsfdaq?]
558 changeset: 24:8fd83ff53ce6
558 changeset: 24:8fd83ff53ce6
559 tag: tip
559 tag: tip
560 user: test
560 user: test
561 date: Thu Jan 01 00:00:22 1970 +0000
561 date: Thu Jan 01 00:00:22 1970 +0000
562 summary: ab
562 summary: ab
563
563
564 diff --git a/subdir/f1 b/subdir/f1
564 diff --git a/subdir/f1 b/subdir/f1
565 old mode 100755
565 old mode 100755
566 new mode 100644
566 new mode 100644
567 --- a/subdir/f1
567 --- a/subdir/f1
568 +++ b/subdir/f1
568 +++ b/subdir/f1
569 @@ -2,3 +2,4 @@
569 @@ -2,3 +2,4 @@
570 a
570 a
571 a
571 a
572 b
572 b
573 +c
573 +c
574
574
575 % with win32ext
576 diff --git a/subdir/f1 b/subdir/f1
577 1 hunks, 1 lines changed
578 examine changes to 'subdir/f1'? [Ynsfdaq?] @@ -3,3 +3,4 @@
579 a
580 b
581 c
582 +d
583 record this change to 'subdir/f1'? [Ynsfdaq?]
584 changeset: 25:49b3838dc9e7
585 tag: tip
586 user: test
587 date: Thu Jan 01 00:00:23 1970 +0000
588 summary: w1
589
590 diff -r 8fd83ff53ce6 -r 49b3838dc9e7 subdir/f1
591 --- a/subdir/f1 Thu Jan 01 00:00:22 1970 +0000
592 +++ b/subdir/f1 Thu Jan 01 00:00:23 1970 +0000
593 @@ -3,3 +3,4 @@
594 a
595 b
596 c
597 +d
598
@@ -1,136 +1,159 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 cat <<EOF >> $HGRCPATH
3 cat <<EOF >> $HGRCPATH
4 [extensions]
4 [extensions]
5 transplant=
5 transplant=
6 EOF
6 EOF
7
7
8 hg init t
8 hg init t
9 cd t
9 cd t
10 echo r1 > r1
10 echo r1 > r1
11 hg ci -Amr1 -d'0 0'
11 hg ci -Amr1 -d'0 0'
12 echo r2 > r2
12 echo r2 > r2
13 hg ci -Amr2 -d'1 0'
13 hg ci -Amr2 -d'1 0'
14 hg up 0
14 hg up 0
15
15
16 echo b1 > b1
16 echo b1 > b1
17 hg ci -Amb1 -d '0 0'
17 hg ci -Amb1 -d '0 0'
18 echo b2 > b2
18 echo b2 > b2
19 hg ci -Amb2 -d '1 0'
19 hg ci -Amb2 -d '1 0'
20 echo b3 > b3
20 echo b3 > b3
21 hg ci -Amb3 -d '2 0'
21 hg ci -Amb3 -d '2 0'
22
22
23 hg log --template '{rev} {parents} {desc}\n'
23 hg log --template '{rev} {parents} {desc}\n'
24
24
25 hg clone . ../rebase
25 hg clone . ../rebase
26 cd ../rebase
26 cd ../rebase
27
27
28 hg up -C 1
28 hg up -C 1
29 echo '% rebase b onto r1'
29 echo '% rebase b onto r1'
30 hg transplant -a -b tip
30 hg transplant -a -b tip
31 hg log --template '{rev} {parents} {desc}\n'
31 hg log --template '{rev} {parents} {desc}\n'
32
32
33 hg clone ../t ../prune
33 hg clone ../t ../prune
34 cd ../prune
34 cd ../prune
35
35
36 hg up -C 1
36 hg up -C 1
37 echo '% rebase b onto r1, skipping b2'
37 echo '% rebase b onto r1, skipping b2'
38 hg transplant -a -b tip -p 3
38 hg transplant -a -b tip -p 3
39 hg log --template '{rev} {parents} {desc}\n'
39 hg log --template '{rev} {parents} {desc}\n'
40
40
41 echo '% remote transplant'
41 echo '% remote transplant'
42 hg clone -r 1 ../t ../remote
42 hg clone -r 1 ../t ../remote
43 cd ../remote
43 cd ../remote
44 hg transplant --log -s ../t 2 4
44 hg transplant --log -s ../t 2 4
45 hg log --template '{rev} {parents} {desc}\n'
45 hg log --template '{rev} {parents} {desc}\n'
46
46
47 echo '% skip previous transplants'
47 echo '% skip previous transplants'
48 hg transplant -s ../t -a -b 4
48 hg transplant -s ../t -a -b 4
49 hg log --template '{rev} {parents} {desc}\n'
49 hg log --template '{rev} {parents} {desc}\n'
50
50
51 echo '% skip local changes transplanted to the source'
51 echo '% skip local changes transplanted to the source'
52 echo b4 > b4
52 echo b4 > b4
53 hg ci -Amb4 -d '3 0'
53 hg ci -Amb4 -d '3 0'
54 hg clone ../t ../pullback
54 hg clone ../t ../pullback
55 cd ../pullback
55 cd ../pullback
56 hg transplant -s ../remote -a -b tip
56 hg transplant -s ../remote -a -b tip
57
57
58 echo '% remote transplant with pull'
58 echo '% remote transplant with pull'
59 hg -R ../t serve -p $HGPORT -d --pid-file=../t.pid
59 hg -R ../t serve -p $HGPORT -d --pid-file=../t.pid
60 cat ../t.pid >> $DAEMON_PIDS
60 cat ../t.pid >> $DAEMON_PIDS
61
61
62 hg clone -r 0 ../t ../rp
62 hg clone -r 0 ../t ../rp
63 cd ../rp
63 cd ../rp
64 hg transplant -s http://localhost:$HGPORT/ 2 4
64 hg transplant -s http://localhost:$HGPORT/ 2 4
65 hg log --template '{rev} {parents} {desc}\n'
65 hg log --template '{rev} {parents} {desc}\n'
66
66
67 echo '% transplant --continue'
67 echo '% transplant --continue'
68 hg init ../tc
68 hg init ../tc
69 cd ../tc
69 cd ../tc
70 cat <<EOF > foo
70 cat <<EOF > foo
71 foo
71 foo
72 bar
72 bar
73 baz
73 baz
74 EOF
74 EOF
75 echo toremove > toremove
75 echo toremove > toremove
76 hg ci -Amfoo
76 hg ci -Amfoo
77 cat <<EOF > foo
77 cat <<EOF > foo
78 foo2
78 foo2
79 bar2
79 bar2
80 baz2
80 baz2
81 EOF
81 EOF
82 rm toremove
82 rm toremove
83 echo added > added
83 echo added > added
84 hg ci -Amfoo2
84 hg ci -Amfoo2
85 echo bar > bar
85 echo bar > bar
86 hg ci -Ambar
86 hg ci -Ambar
87 echo bar2 >> bar
87 echo bar2 >> bar
88 hg ci -mbar2
88 hg ci -mbar2
89 hg up 0
89 hg up 0
90 echo foobar > foo
90 echo foobar > foo
91 hg ci -mfoobar
91 hg ci -mfoobar
92 hg transplant 1:3
92 hg transplant 1:3
93 # transplant -c shouldn't use an old changeset
93 # transplant -c shouldn't use an old changeset
94 hg up -C
94 hg up -C
95 rm added
95 rm added
96 hg transplant 1
96 hg transplant 1
97 hg transplant --continue
97 hg transplant --continue
98 hg transplant 1:3
98 hg transplant 1:3
99 hg locate
99 hg locate
100 cd ..
100 cd ..
101
101
102 # Test transplant --merge (issue 1111)
102 # Test transplant --merge (issue 1111)
103 echo % test transplant merge
103 echo % test transplant merge
104 hg init t1111
104 hg init t1111
105 cd t1111
105 cd t1111
106 echo a > a
106 echo a > a
107 hg ci -Am adda
107 hg ci -Am adda
108 echo b >> a
108 echo b >> a
109 hg ci -m appendb
109 hg ci -m appendb
110 echo c >> a
110 echo c >> a
111 hg ci -m appendc
111 hg ci -m appendc
112 hg up -C 0
112 hg up -C 0
113 echo d >> a
113 echo d >> a
114 hg ci -m appendd
114 hg ci -m appendd
115 echo % tranplant
115 echo % tranplant
116 hg transplant -m 1
116 hg transplant -m 1
117 cd ..
117 cd ..
118
118
119 echo '% test transplant into empty repository'
119 echo '% test transplant into empty repository'
120 hg init empty
120 hg init empty
121 cd empty
121 cd empty
122 hg transplant -s ../t -b tip -a
122 hg transplant -s ../t -b tip -a
123 cd ..
123 cd ..
124
124
125 echo '% test filter'
125 echo '% test filter'
126 hg init filter
126 hg init filter
127 cd filter
127 cd filter
128 cat <<'EOF' >test-filter
128 cat <<'EOF' >test-filter
129 #!/bin/sh
129 #!/bin/sh
130 sed 's/r1/r2/' $1 > $1.new
130 sed 's/r1/r2/' $1 > $1.new
131 mv $1.new $1
131 mv $1.new $1
132 EOF
132 EOF
133 chmod +x test-filter
133 chmod +x test-filter
134 hg transplant -s ../t -b tip -a --filter ./test-filter |\
134 hg transplant -s ../t -b tip -a --filter ./test-filter |\
135 sed 's/filtering.*/filtering/g'
135 sed 's/filtering.*/filtering/g'
136 hg log --template '{rev} {parents} {desc}\n'
136 hg log --template '{rev} {parents} {desc}\n'
137 cd ..
138
139 echo '% test with a win32ext like setup (differing EOLs)'
140 hg init twin1
141 cd twin1
142 echo a > a
143 echo b > b
144 echo b >> b
145 hg ci -Am t
146 echo a > b
147 echo b >> b
148 hg ci -m changeb
149 cd ..
150
151 hg init twin2
152 cd twin2
153 echo '[patch]' >> .hg/hgrc
154 echo 'eol = crlf' >> .hg/hgrc
155 python -c "file('b', 'wb').write('b\r\nb\r\n')"
156 hg ci -m addb
157 hg transplant -s ../twin1 tip
158 python -c "print repr(file('b', 'rb').read())"
159 cd .. No newline at end of file
@@ -1,161 +1,168 b''
1 adding r1
1 adding r1
2 adding r2
2 adding r2
3 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
3 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
4 adding b1
4 adding b1
5 created new head
5 created new head
6 adding b2
6 adding b2
7 adding b3
7 adding b3
8 4 b3
8 4 b3
9 3 b2
9 3 b2
10 2 0:17ab29e464c6 b1
10 2 0:17ab29e464c6 b1
11 1 r2
11 1 r2
12 0 r1
12 0 r1
13 updating working directory
13 updating working directory
14 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
14 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
15 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
15 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
16 % rebase b onto r1
16 % rebase b onto r1
17 applying 37a1297eb21b
17 applying 37a1297eb21b
18 37a1297eb21b transplanted to e234d668f844
18 37a1297eb21b transplanted to e234d668f844
19 applying 722f4667af76
19 applying 722f4667af76
20 722f4667af76 transplanted to 539f377d78df
20 722f4667af76 transplanted to 539f377d78df
21 applying a53251cdf717
21 applying a53251cdf717
22 a53251cdf717 transplanted to ffd6818a3975
22 a53251cdf717 transplanted to ffd6818a3975
23 7 b3
23 7 b3
24 6 b2
24 6 b2
25 5 1:d11e3596cc1a b1
25 5 1:d11e3596cc1a b1
26 4 b3
26 4 b3
27 3 b2
27 3 b2
28 2 0:17ab29e464c6 b1
28 2 0:17ab29e464c6 b1
29 1 r2
29 1 r2
30 0 r1
30 0 r1
31 updating working directory
31 updating working directory
32 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
32 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
33 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
33 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
34 % rebase b onto r1, skipping b2
34 % rebase b onto r1, skipping b2
35 applying 37a1297eb21b
35 applying 37a1297eb21b
36 37a1297eb21b transplanted to e234d668f844
36 37a1297eb21b transplanted to e234d668f844
37 applying a53251cdf717
37 applying a53251cdf717
38 a53251cdf717 transplanted to 7275fda4d04f
38 a53251cdf717 transplanted to 7275fda4d04f
39 6 b3
39 6 b3
40 5 1:d11e3596cc1a b1
40 5 1:d11e3596cc1a b1
41 4 b3
41 4 b3
42 3 b2
42 3 b2
43 2 0:17ab29e464c6 b1
43 2 0:17ab29e464c6 b1
44 1 r2
44 1 r2
45 0 r1
45 0 r1
46 % remote transplant
46 % remote transplant
47 requesting all changes
47 requesting all changes
48 adding changesets
48 adding changesets
49 adding manifests
49 adding manifests
50 adding file changes
50 adding file changes
51 added 2 changesets with 2 changes to 2 files
51 added 2 changesets with 2 changes to 2 files
52 updating working directory
52 updating working directory
53 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
53 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
54 searching for changes
54 searching for changes
55 applying 37a1297eb21b
55 applying 37a1297eb21b
56 37a1297eb21b transplanted to c19cf0ccb069
56 37a1297eb21b transplanted to c19cf0ccb069
57 applying a53251cdf717
57 applying a53251cdf717
58 a53251cdf717 transplanted to f7fe5bf98525
58 a53251cdf717 transplanted to f7fe5bf98525
59 3 b3
59 3 b3
60 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
60 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
61 2 b1
61 2 b1
62 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
62 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
63 1 r2
63 1 r2
64 0 r1
64 0 r1
65 % skip previous transplants
65 % skip previous transplants
66 searching for changes
66 searching for changes
67 applying 722f4667af76
67 applying 722f4667af76
68 722f4667af76 transplanted to 47156cd86c0b
68 722f4667af76 transplanted to 47156cd86c0b
69 4 b2
69 4 b2
70 3 b3
70 3 b3
71 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
71 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
72 2 b1
72 2 b1
73 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
73 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
74 1 r2
74 1 r2
75 0 r1
75 0 r1
76 % skip local changes transplanted to the source
76 % skip local changes transplanted to the source
77 adding b4
77 adding b4
78 updating working directory
78 updating working directory
79 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
79 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
80 searching for changes
80 searching for changes
81 applying 4333daefcb15
81 applying 4333daefcb15
82 4333daefcb15 transplanted to 5f42c04e07cc
82 4333daefcb15 transplanted to 5f42c04e07cc
83 % remote transplant with pull
83 % remote transplant with pull
84 requesting all changes
84 requesting all changes
85 adding changesets
85 adding changesets
86 adding manifests
86 adding manifests
87 adding file changes
87 adding file changes
88 added 1 changesets with 1 changes to 1 files
88 added 1 changesets with 1 changes to 1 files
89 updating working directory
89 updating working directory
90 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
90 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
91 searching for changes
91 searching for changes
92 searching for changes
92 searching for changes
93 adding changesets
93 adding changesets
94 adding manifests
94 adding manifests
95 adding file changes
95 adding file changes
96 added 1 changesets with 1 changes to 1 files
96 added 1 changesets with 1 changes to 1 files
97 applying a53251cdf717
97 applying a53251cdf717
98 a53251cdf717 transplanted to 8d9279348abb
98 a53251cdf717 transplanted to 8d9279348abb
99 2 b3
99 2 b3
100 1 b1
100 1 b1
101 0 r1
101 0 r1
102 % transplant --continue
102 % transplant --continue
103 adding foo
103 adding foo
104 adding toremove
104 adding toremove
105 adding added
105 adding added
106 removing toremove
106 removing toremove
107 adding bar
107 adding bar
108 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
108 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
109 created new head
109 created new head
110 applying a1e30dd1b8e7
110 applying a1e30dd1b8e7
111 patching file foo
111 patching file foo
112 Hunk #1 FAILED at 0
112 Hunk #1 FAILED at 0
113 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
113 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
114 patch failed to apply
114 patch failed to apply
115 abort: Fix up the merge and run hg transplant --continue
115 abort: Fix up the merge and run hg transplant --continue
116 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
116 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 applying a1e30dd1b8e7
117 applying a1e30dd1b8e7
118 patching file foo
118 patching file foo
119 Hunk #1 FAILED at 0
119 Hunk #1 FAILED at 0
120 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
120 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
121 patch failed to apply
121 patch failed to apply
122 abort: Fix up the merge and run hg transplant --continue
122 abort: Fix up the merge and run hg transplant --continue
123 a1e30dd1b8e7 transplanted as f1563cf27039
123 a1e30dd1b8e7 transplanted as f1563cf27039
124 skipping already applied revision 1:a1e30dd1b8e7
124 skipping already applied revision 1:a1e30dd1b8e7
125 applying 1739ac5f6139
125 applying 1739ac5f6139
126 1739ac5f6139 transplanted to d649c221319f
126 1739ac5f6139 transplanted to d649c221319f
127 applying 0282d5fbbe02
127 applying 0282d5fbbe02
128 0282d5fbbe02 transplanted to 77418277ccb3
128 0282d5fbbe02 transplanted to 77418277ccb3
129 added
129 added
130 bar
130 bar
131 foo
131 foo
132 % test transplant merge
132 % test transplant merge
133 adding a
133 adding a
134 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
134 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
135 created new head
135 created new head
136 % tranplant
136 % tranplant
137 applying 42dc4432fd35
137 applying 42dc4432fd35
138 1:42dc4432fd35 merged at a9f4acbac129
138 1:42dc4432fd35 merged at a9f4acbac129
139 % test transplant into empty repository
139 % test transplant into empty repository
140 requesting all changes
140 requesting all changes
141 adding changesets
141 adding changesets
142 adding manifests
142 adding manifests
143 adding file changes
143 adding file changes
144 added 4 changesets with 4 changes to 4 files
144 added 4 changesets with 4 changes to 4 files
145 % test filter
145 % test filter
146 filtering
146 filtering
147 applying 17ab29e464c6
147 applying 17ab29e464c6
148 17ab29e464c6 transplanted to e9ffc54ea104
148 17ab29e464c6 transplanted to e9ffc54ea104
149 filtering
149 filtering
150 applying 37a1297eb21b
150 applying 37a1297eb21b
151 37a1297eb21b transplanted to 348b36d0b6a5
151 37a1297eb21b transplanted to 348b36d0b6a5
152 filtering
152 filtering
153 applying 722f4667af76
153 applying 722f4667af76
154 722f4667af76 transplanted to 0aa6979afb95
154 722f4667af76 transplanted to 0aa6979afb95
155 filtering
155 filtering
156 applying a53251cdf717
156 applying a53251cdf717
157 a53251cdf717 transplanted to 14f8512272b5
157 a53251cdf717 transplanted to 14f8512272b5
158 3 b3
158 3 b3
159 2 b2
159 2 b2
160 1 b1
160 1 b1
161 0 r2
161 0 r2
162 % test with a win32ext like setup (differing EOLs)
163 adding a
164 adding b
165 nothing changed
166 applying 2e849d776c17
167 2e849d776c17 transplanted to 589cea8ba85b
168 'a\r\nb\r\n'
General Comments 0
You need to be logged in to leave comments. Login now