##// END OF EJS Templates
merge with stable
Matt Mackall -
r20773:efbf1597 merge default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,924 +1,925 b''
1 # histedit.py - interactive history editing for mercurial
1 # histedit.py - interactive history editing for mercurial
2 #
2 #
3 # Copyright 2009 Augie Fackler <raf@durin42.com>
3 # Copyright 2009 Augie Fackler <raf@durin42.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 or any later version.
6 # GNU General Public License version 2 or any later version.
7 """interactive history editing
7 """interactive history editing
8
8
9 With this extension installed, Mercurial gains one new command: histedit. Usage
9 With this extension installed, Mercurial gains one new command: histedit. Usage
10 is as follows, assuming the following history::
10 is as follows, assuming the following history::
11
11
12 @ 3[tip] 7c2fd3b9020c 2009-04-27 18:04 -0500 durin42
12 @ 3[tip] 7c2fd3b9020c 2009-04-27 18:04 -0500 durin42
13 | Add delta
13 | Add delta
14 |
14 |
15 o 2 030b686bedc4 2009-04-27 18:04 -0500 durin42
15 o 2 030b686bedc4 2009-04-27 18:04 -0500 durin42
16 | Add gamma
16 | Add gamma
17 |
17 |
18 o 1 c561b4e977df 2009-04-27 18:04 -0500 durin42
18 o 1 c561b4e977df 2009-04-27 18:04 -0500 durin42
19 | Add beta
19 | Add beta
20 |
20 |
21 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
21 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
22 Add alpha
22 Add alpha
23
23
24 If you were to run ``hg histedit c561b4e977df``, you would see the following
24 If you were to run ``hg histedit c561b4e977df``, you would see the following
25 file open in your editor::
25 file open in your editor::
26
26
27 pick c561b4e977df Add beta
27 pick c561b4e977df Add beta
28 pick 030b686bedc4 Add gamma
28 pick 030b686bedc4 Add gamma
29 pick 7c2fd3b9020c Add delta
29 pick 7c2fd3b9020c Add delta
30
30
31 # Edit history between c561b4e977df and 7c2fd3b9020c
31 # Edit history between c561b4e977df and 7c2fd3b9020c
32 #
32 #
33 # Commits are listed from least to most recent
33 # Commits are listed from least to most recent
34 #
34 #
35 # Commands:
35 # Commands:
36 # p, pick = use commit
36 # p, pick = use commit
37 # e, edit = use commit, but stop for amending
37 # e, edit = use commit, but stop for amending
38 # f, fold = use commit, but combine it with the one above
38 # f, fold = use commit, but combine it with the one above
39 # d, drop = remove commit from history
39 # d, drop = remove commit from history
40 # m, mess = edit message without changing commit content
40 # m, mess = edit message without changing commit content
41 #
41 #
42
42
43 In this file, lines beginning with ``#`` are ignored. You must specify a rule
43 In this file, lines beginning with ``#`` are ignored. You must specify a rule
44 for each revision in your history. For example, if you had meant to add gamma
44 for each revision in your history. For example, if you had meant to add gamma
45 before beta, and then wanted to add delta in the same revision as beta, you
45 before beta, and then wanted to add delta in the same revision as beta, you
46 would reorganize the file to look like this::
46 would reorganize the file to look like this::
47
47
48 pick 030b686bedc4 Add gamma
48 pick 030b686bedc4 Add gamma
49 pick c561b4e977df Add beta
49 pick c561b4e977df Add beta
50 fold 7c2fd3b9020c Add delta
50 fold 7c2fd3b9020c Add delta
51
51
52 # Edit history between c561b4e977df and 7c2fd3b9020c
52 # Edit history between c561b4e977df and 7c2fd3b9020c
53 #
53 #
54 # Commits are listed from least to most recent
54 # Commits are listed from least to most recent
55 #
55 #
56 # Commands:
56 # Commands:
57 # p, pick = use commit
57 # p, pick = use commit
58 # e, edit = use commit, but stop for amending
58 # e, edit = use commit, but stop for amending
59 # f, fold = use commit, but combine it with the one above
59 # f, fold = use commit, but combine it with the one above
60 # d, drop = remove commit from history
60 # d, drop = remove commit from history
61 # m, mess = edit message without changing commit content
61 # m, mess = edit message without changing commit content
62 #
62 #
63
63
64 At which point you close the editor and ``histedit`` starts working. When you
64 At which point you close the editor and ``histedit`` starts working. When you
65 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
65 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
66 those revisions together, offering you a chance to clean up the commit message::
66 those revisions together, offering you a chance to clean up the commit message::
67
67
68 Add beta
68 Add beta
69 ***
69 ***
70 Add delta
70 Add delta
71
71
72 Edit the commit message to your liking, then close the editor. For
72 Edit the commit message to your liking, then close the editor. For
73 this example, let's assume that the commit message was changed to
73 this example, let's assume that the commit message was changed to
74 ``Add beta and delta.`` After histedit has run and had a chance to
74 ``Add beta and delta.`` After histedit has run and had a chance to
75 remove any old or temporary revisions it needed, the history looks
75 remove any old or temporary revisions it needed, the history looks
76 like this::
76 like this::
77
77
78 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
78 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
79 | Add beta and delta.
79 | Add beta and delta.
80 |
80 |
81 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
81 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
82 | Add gamma
82 | Add gamma
83 |
83 |
84 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
84 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
85 Add alpha
85 Add alpha
86
86
87 Note that ``histedit`` does *not* remove any revisions (even its own temporary
87 Note that ``histedit`` does *not* remove any revisions (even its own temporary
88 ones) until after it has completed all the editing operations, so it will
88 ones) until after it has completed all the editing operations, so it will
89 probably perform several strip operations when it's done. For the above example,
89 probably perform several strip operations when it's done. For the above example,
90 it had to run strip twice. Strip can be slow depending on a variety of factors,
90 it had to run strip twice. Strip can be slow depending on a variety of factors,
91 so you might need to be a little patient. You can choose to keep the original
91 so you might need to be a little patient. You can choose to keep the original
92 revisions by passing the ``--keep`` flag.
92 revisions by passing the ``--keep`` flag.
93
93
94 The ``edit`` operation will drop you back to a command prompt,
94 The ``edit`` operation will drop you back to a command prompt,
95 allowing you to edit files freely, or even use ``hg record`` to commit
95 allowing you to edit files freely, or even use ``hg record`` to commit
96 some changes as a separate commit. When you're done, any remaining
96 some changes as a separate commit. When you're done, any remaining
97 uncommitted changes will be committed as well. When done, run ``hg
97 uncommitted changes will be committed as well. When done, run ``hg
98 histedit --continue`` to finish this step. You'll be prompted for a
98 histedit --continue`` to finish this step. You'll be prompted for a
99 new commit message, but the default commit message will be the
99 new commit message, but the default commit message will be the
100 original message for the ``edit`` ed revision.
100 original message for the ``edit`` ed revision.
101
101
102 The ``message`` operation will give you a chance to revise a commit
102 The ``message`` operation will give you a chance to revise a commit
103 message without changing the contents. It's a shortcut for doing
103 message without changing the contents. It's a shortcut for doing
104 ``edit`` immediately followed by `hg histedit --continue``.
104 ``edit`` immediately followed by `hg histedit --continue``.
105
105
106 If ``histedit`` encounters a conflict when moving a revision (while
106 If ``histedit`` encounters a conflict when moving a revision (while
107 handling ``pick`` or ``fold``), it'll stop in a similar manner to
107 handling ``pick`` or ``fold``), it'll stop in a similar manner to
108 ``edit`` with the difference that it won't prompt you for a commit
108 ``edit`` with the difference that it won't prompt you for a commit
109 message when done. If you decide at this point that you don't like how
109 message when done. If you decide at this point that you don't like how
110 much work it will be to rearrange history, or that you made a mistake,
110 much work it will be to rearrange history, or that you made a mistake,
111 you can use ``hg histedit --abort`` to abandon the new changes you
111 you can use ``hg histedit --abort`` to abandon the new changes you
112 have made and return to the state before you attempted to edit your
112 have made and return to the state before you attempted to edit your
113 history.
113 history.
114
114
115 If we clone the histedit-ed example repository above and add four more
115 If we clone the histedit-ed example repository above and add four more
116 changes, such that we have the following history::
116 changes, such that we have the following history::
117
117
118 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
118 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
119 | Add theta
119 | Add theta
120 |
120 |
121 o 5 140988835471 2009-04-27 18:04 -0500 stefan
121 o 5 140988835471 2009-04-27 18:04 -0500 stefan
122 | Add eta
122 | Add eta
123 |
123 |
124 o 4 122930637314 2009-04-27 18:04 -0500 stefan
124 o 4 122930637314 2009-04-27 18:04 -0500 stefan
125 | Add zeta
125 | Add zeta
126 |
126 |
127 o 3 836302820282 2009-04-27 18:04 -0500 stefan
127 o 3 836302820282 2009-04-27 18:04 -0500 stefan
128 | Add epsilon
128 | Add epsilon
129 |
129 |
130 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
130 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
131 | Add beta and delta.
131 | Add beta and delta.
132 |
132 |
133 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
133 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
134 | Add gamma
134 | Add gamma
135 |
135 |
136 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
136 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
137 Add alpha
137 Add alpha
138
138
139 If you run ``hg histedit --outgoing`` on the clone then it is the same
139 If you run ``hg histedit --outgoing`` on the clone then it is the same
140 as running ``hg histedit 836302820282``. If you need plan to push to a
140 as running ``hg histedit 836302820282``. If you need plan to push to a
141 repository that Mercurial does not detect to be related to the source
141 repository that Mercurial does not detect to be related to the source
142 repo, you can add a ``--force`` option.
142 repo, you can add a ``--force`` option.
143 """
143 """
144
144
145 try:
145 try:
146 import cPickle as pickle
146 import cPickle as pickle
147 pickle.dump # import now
147 pickle.dump # import now
148 except ImportError:
148 except ImportError:
149 import pickle
149 import pickle
150 import os
150 import os
151 import sys
151 import sys
152
152
153 from mercurial import cmdutil
153 from mercurial import cmdutil
154 from mercurial import discovery
154 from mercurial import discovery
155 from mercurial import error
155 from mercurial import error
156 from mercurial import copies
156 from mercurial import copies
157 from mercurial import context
157 from mercurial import context
158 from mercurial import hg
158 from mercurial import hg
159 from mercurial import node
159 from mercurial import node
160 from mercurial import repair
160 from mercurial import repair
161 from mercurial import scmutil
161 from mercurial import scmutil
162 from mercurial import util
162 from mercurial import util
163 from mercurial import obsolete
163 from mercurial import obsolete
164 from mercurial import merge as mergemod
164 from mercurial import merge as mergemod
165 from mercurial.lock import release
165 from mercurial.lock import release
166 from mercurial.i18n import _
166 from mercurial.i18n import _
167
167
168 cmdtable = {}
168 cmdtable = {}
169 command = cmdutil.command(cmdtable)
169 command = cmdutil.command(cmdtable)
170
170
171 testedwith = 'internal'
171 testedwith = 'internal'
172
172
173 # i18n: command names and abbreviations must remain untranslated
173 # i18n: command names and abbreviations must remain untranslated
174 editcomment = _("""# Edit history between %s and %s
174 editcomment = _("""# Edit history between %s and %s
175 #
175 #
176 # Commits are listed from least to most recent
176 # Commits are listed from least to most recent
177 #
177 #
178 # Commands:
178 # Commands:
179 # p, pick = use commit
179 # p, pick = use commit
180 # e, edit = use commit, but stop for amending
180 # e, edit = use commit, but stop for amending
181 # f, fold = use commit, but combine it with the one above
181 # f, fold = use commit, but combine it with the one above
182 # d, drop = remove commit from history
182 # d, drop = remove commit from history
183 # m, mess = edit message without changing commit content
183 # m, mess = edit message without changing commit content
184 #
184 #
185 """)
185 """)
186
186
187 def commitfuncfor(repo, src):
187 def commitfuncfor(repo, src):
188 """Build a commit function for the replacement of <src>
188 """Build a commit function for the replacement of <src>
189
189
190 This function ensure we apply the same treatment to all changesets.
190 This function ensure we apply the same treatment to all changesets.
191
191
192 - Add a 'histedit_source' entry in extra.
192 - Add a 'histedit_source' entry in extra.
193
193
194 Note that fold have its own separated logic because its handling is a bit
194 Note that fold have its own separated logic because its handling is a bit
195 different and not easily factored out of the fold method.
195 different and not easily factored out of the fold method.
196 """
196 """
197 phasemin = src.phase()
197 phasemin = src.phase()
198 def commitfunc(**kwargs):
198 def commitfunc(**kwargs):
199 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
199 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
200 try:
200 try:
201 repo.ui.setconfig('phases', 'new-commit', phasemin)
201 repo.ui.setconfig('phases', 'new-commit', phasemin)
202 extra = kwargs.get('extra', {}).copy()
202 extra = kwargs.get('extra', {}).copy()
203 extra['histedit_source'] = src.hex()
203 extra['histedit_source'] = src.hex()
204 kwargs['extra'] = extra
204 kwargs['extra'] = extra
205 return repo.commit(**kwargs)
205 return repo.commit(**kwargs)
206 finally:
206 finally:
207 repo.ui.restoreconfig(phasebackup)
207 repo.ui.restoreconfig(phasebackup)
208 return commitfunc
208 return commitfunc
209
209
210
210
211
211
212 def applychanges(ui, repo, ctx, opts):
212 def applychanges(ui, repo, ctx, opts):
213 """Merge changeset from ctx (only) in the current working directory"""
213 """Merge changeset from ctx (only) in the current working directory"""
214 wcpar = repo.dirstate.parents()[0]
214 wcpar = repo.dirstate.parents()[0]
215 if ctx.p1().node() == wcpar:
215 if ctx.p1().node() == wcpar:
216 # edition ar "in place" we do not need to make any merge,
216 # edition ar "in place" we do not need to make any merge,
217 # just applies changes on parent for edition
217 # just applies changes on parent for edition
218 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
218 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
219 stats = None
219 stats = None
220 else:
220 else:
221 try:
221 try:
222 # ui.forcemerge is an internal variable, do not document
222 # ui.forcemerge is an internal variable, do not document
223 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
223 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
224 stats = mergemod.update(repo, ctx.node(), True, True, False,
224 stats = mergemod.update(repo, ctx.node(), True, True, False,
225 ctx.p1().node())
225 ctx.p1().node())
226 finally:
226 finally:
227 repo.ui.setconfig('ui', 'forcemerge', '')
227 repo.ui.setconfig('ui', 'forcemerge', '')
228 repo.setparents(wcpar, node.nullid)
228 repo.setparents(wcpar, node.nullid)
229 repo.dirstate.write()
229 repo.dirstate.write()
230 # fix up dirstate for copies and renames
230 # fix up dirstate for copies and renames
231 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
231 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
232 return stats
232 return stats
233
233
234 def collapse(repo, first, last, commitopts):
234 def collapse(repo, first, last, commitopts):
235 """collapse the set of revisions from first to last as new one.
235 """collapse the set of revisions from first to last as new one.
236
236
237 Expected commit options are:
237 Expected commit options are:
238 - message
238 - message
239 - date
239 - date
240 - username
240 - username
241 Commit message is edited in all cases.
241 Commit message is edited in all cases.
242
242
243 This function works in memory."""
243 This function works in memory."""
244 ctxs = list(repo.set('%d::%d', first, last))
244 ctxs = list(repo.set('%d::%d', first, last))
245 if not ctxs:
245 if not ctxs:
246 return None
246 return None
247 base = first.parents()[0]
247 base = first.parents()[0]
248
248
249 # commit a new version of the old changeset, including the update
249 # commit a new version of the old changeset, including the update
250 # collect all files which might be affected
250 # collect all files which might be affected
251 files = set()
251 files = set()
252 for ctx in ctxs:
252 for ctx in ctxs:
253 files.update(ctx.files())
253 files.update(ctx.files())
254
254
255 # Recompute copies (avoid recording a -> b -> a)
255 # Recompute copies (avoid recording a -> b -> a)
256 copied = copies.pathcopies(base, last)
256 copied = copies.pathcopies(base, last)
257
257
258 # prune files which were reverted by the updates
258 # prune files which were reverted by the updates
259 def samefile(f):
259 def samefile(f):
260 if f in last.manifest():
260 if f in last.manifest():
261 a = last.filectx(f)
261 a = last.filectx(f)
262 if f in base.manifest():
262 if f in base.manifest():
263 b = base.filectx(f)
263 b = base.filectx(f)
264 return (a.data() == b.data()
264 return (a.data() == b.data()
265 and a.flags() == b.flags())
265 and a.flags() == b.flags())
266 else:
266 else:
267 return False
267 return False
268 else:
268 else:
269 return f not in base.manifest()
269 return f not in base.manifest()
270 files = [f for f in files if not samefile(f)]
270 files = [f for f in files if not samefile(f)]
271 # commit version of these files as defined by head
271 # commit version of these files as defined by head
272 headmf = last.manifest()
272 headmf = last.manifest()
273 def filectxfn(repo, ctx, path):
273 def filectxfn(repo, ctx, path):
274 if path in headmf:
274 if path in headmf:
275 fctx = last[path]
275 fctx = last[path]
276 flags = fctx.flags()
276 flags = fctx.flags()
277 mctx = context.memfilectx(fctx.path(), fctx.data(),
277 mctx = context.memfilectx(fctx.path(), fctx.data(),
278 islink='l' in flags,
278 islink='l' in flags,
279 isexec='x' in flags,
279 isexec='x' in flags,
280 copied=copied.get(path))
280 copied=copied.get(path))
281 return mctx
281 return mctx
282 raise IOError()
282 raise IOError()
283
283
284 if commitopts.get('message'):
284 if commitopts.get('message'):
285 message = commitopts['message']
285 message = commitopts['message']
286 else:
286 else:
287 message = first.description()
287 message = first.description()
288 user = commitopts.get('user')
288 user = commitopts.get('user')
289 date = commitopts.get('date')
289 date = commitopts.get('date')
290 extra = commitopts.get('extra')
290 extra = commitopts.get('extra')
291
291
292 parents = (first.p1().node(), first.p2().node())
292 parents = (first.p1().node(), first.p2().node())
293 new = context.memctx(repo,
293 new = context.memctx(repo,
294 parents=parents,
294 parents=parents,
295 text=message,
295 text=message,
296 files=files,
296 files=files,
297 filectxfn=filectxfn,
297 filectxfn=filectxfn,
298 user=user,
298 user=user,
299 date=date,
299 date=date,
300 extra=extra)
300 extra=extra)
301 new._text = cmdutil.commitforceeditor(repo, new, [])
301 new._text = cmdutil.commitforceeditor(repo, new, [])
302 repo.savecommitmessage(new.description())
302 return repo.commitctx(new)
303 return repo.commitctx(new)
303
304
304 def pick(ui, repo, ctx, ha, opts):
305 def pick(ui, repo, ctx, ha, opts):
305 oldctx = repo[ha]
306 oldctx = repo[ha]
306 if oldctx.parents()[0] == ctx:
307 if oldctx.parents()[0] == ctx:
307 ui.debug('node %s unchanged\n' % ha)
308 ui.debug('node %s unchanged\n' % ha)
308 return oldctx, []
309 return oldctx, []
309 hg.update(repo, ctx.node())
310 hg.update(repo, ctx.node())
310 stats = applychanges(ui, repo, oldctx, opts)
311 stats = applychanges(ui, repo, oldctx, opts)
311 if stats and stats[3] > 0:
312 if stats and stats[3] > 0:
312 raise error.InterventionRequired(_('Fix up the change and run '
313 raise error.InterventionRequired(_('Fix up the change and run '
313 'hg histedit --continue'))
314 'hg histedit --continue'))
314 # drop the second merge parent
315 # drop the second merge parent
315 commit = commitfuncfor(repo, oldctx)
316 commit = commitfuncfor(repo, oldctx)
316 n = commit(text=oldctx.description(), user=oldctx.user(),
317 n = commit(text=oldctx.description(), user=oldctx.user(),
317 date=oldctx.date(), extra=oldctx.extra())
318 date=oldctx.date(), extra=oldctx.extra())
318 if n is None:
319 if n is None:
319 ui.warn(_('%s: empty changeset\n')
320 ui.warn(_('%s: empty changeset\n')
320 % node.hex(ha))
321 % node.hex(ha))
321 return ctx, []
322 return ctx, []
322 new = repo[n]
323 new = repo[n]
323 return new, [(oldctx.node(), (n,))]
324 return new, [(oldctx.node(), (n,))]
324
325
325
326
326 def edit(ui, repo, ctx, ha, opts):
327 def edit(ui, repo, ctx, ha, opts):
327 oldctx = repo[ha]
328 oldctx = repo[ha]
328 hg.update(repo, ctx.node())
329 hg.update(repo, ctx.node())
329 applychanges(ui, repo, oldctx, opts)
330 applychanges(ui, repo, oldctx, opts)
330 raise error.InterventionRequired(
331 raise error.InterventionRequired(
331 _('Make changes as needed, you may commit or record as needed now.\n'
332 _('Make changes as needed, you may commit or record as needed now.\n'
332 'When you are finished, run hg histedit --continue to resume.'))
333 'When you are finished, run hg histedit --continue to resume.'))
333
334
334 def fold(ui, repo, ctx, ha, opts):
335 def fold(ui, repo, ctx, ha, opts):
335 oldctx = repo[ha]
336 oldctx = repo[ha]
336 hg.update(repo, ctx.node())
337 hg.update(repo, ctx.node())
337 stats = applychanges(ui, repo, oldctx, opts)
338 stats = applychanges(ui, repo, oldctx, opts)
338 if stats and stats[3] > 0:
339 if stats and stats[3] > 0:
339 raise error.InterventionRequired(
340 raise error.InterventionRequired(
340 _('Fix up the change and run hg histedit --continue'))
341 _('Fix up the change and run hg histedit --continue'))
341 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
342 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
342 date=oldctx.date(), extra=oldctx.extra())
343 date=oldctx.date(), extra=oldctx.extra())
343 if n is None:
344 if n is None:
344 ui.warn(_('%s: empty changeset')
345 ui.warn(_('%s: empty changeset')
345 % node.hex(ha))
346 % node.hex(ha))
346 return ctx, []
347 return ctx, []
347 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
348 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
348
349
349 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
350 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
350 parent = ctx.parents()[0].node()
351 parent = ctx.parents()[0].node()
351 hg.update(repo, parent)
352 hg.update(repo, parent)
352 ### prepare new commit data
353 ### prepare new commit data
353 commitopts = opts.copy()
354 commitopts = opts.copy()
354 # username
355 # username
355 if ctx.user() == oldctx.user():
356 if ctx.user() == oldctx.user():
356 username = ctx.user()
357 username = ctx.user()
357 else:
358 else:
358 username = ui.username()
359 username = ui.username()
359 commitopts['user'] = username
360 commitopts['user'] = username
360 # commit message
361 # commit message
361 newmessage = '\n***\n'.join(
362 newmessage = '\n***\n'.join(
362 [ctx.description()] +
363 [ctx.description()] +
363 [repo[r].description() for r in internalchanges] +
364 [repo[r].description() for r in internalchanges] +
364 [oldctx.description()]) + '\n'
365 [oldctx.description()]) + '\n'
365 commitopts['message'] = newmessage
366 commitopts['message'] = newmessage
366 # date
367 # date
367 commitopts['date'] = max(ctx.date(), oldctx.date())
368 commitopts['date'] = max(ctx.date(), oldctx.date())
368 extra = ctx.extra().copy()
369 extra = ctx.extra().copy()
369 # histedit_source
370 # histedit_source
370 # note: ctx is likely a temporary commit but that the best we can do here
371 # note: ctx is likely a temporary commit but that the best we can do here
371 # This is sufficient to solve issue3681 anyway
372 # This is sufficient to solve issue3681 anyway
372 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
373 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
373 commitopts['extra'] = extra
374 commitopts['extra'] = extra
374 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
375 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
375 try:
376 try:
376 phasemin = max(ctx.phase(), oldctx.phase())
377 phasemin = max(ctx.phase(), oldctx.phase())
377 repo.ui.setconfig('phases', 'new-commit', phasemin)
378 repo.ui.setconfig('phases', 'new-commit', phasemin)
378 n = collapse(repo, ctx, repo[newnode], commitopts)
379 n = collapse(repo, ctx, repo[newnode], commitopts)
379 finally:
380 finally:
380 repo.ui.restoreconfig(phasebackup)
381 repo.ui.restoreconfig(phasebackup)
381 if n is None:
382 if n is None:
382 return ctx, []
383 return ctx, []
383 hg.update(repo, n)
384 hg.update(repo, n)
384 replacements = [(oldctx.node(), (newnode,)),
385 replacements = [(oldctx.node(), (newnode,)),
385 (ctx.node(), (n,)),
386 (ctx.node(), (n,)),
386 (newnode, (n,)),
387 (newnode, (n,)),
387 ]
388 ]
388 for ich in internalchanges:
389 for ich in internalchanges:
389 replacements.append((ich, (n,)))
390 replacements.append((ich, (n,)))
390 return repo[n], replacements
391 return repo[n], replacements
391
392
392 def drop(ui, repo, ctx, ha, opts):
393 def drop(ui, repo, ctx, ha, opts):
393 return ctx, [(repo[ha].node(), ())]
394 return ctx, [(repo[ha].node(), ())]
394
395
395
396
396 def message(ui, repo, ctx, ha, opts):
397 def message(ui, repo, ctx, ha, opts):
397 oldctx = repo[ha]
398 oldctx = repo[ha]
398 hg.update(repo, ctx.node())
399 hg.update(repo, ctx.node())
399 stats = applychanges(ui, repo, oldctx, opts)
400 stats = applychanges(ui, repo, oldctx, opts)
400 if stats and stats[3] > 0:
401 if stats and stats[3] > 0:
401 raise error.InterventionRequired(
402 raise error.InterventionRequired(
402 _('Fix up the change and run hg histedit --continue'))
403 _('Fix up the change and run hg histedit --continue'))
403 message = oldctx.description() + '\n'
404 message = oldctx.description() + '\n'
404 message = ui.edit(message, ui.username())
405 message = ui.edit(message, ui.username())
405 commit = commitfuncfor(repo, oldctx)
406 commit = commitfuncfor(repo, oldctx)
406 new = commit(text=message, user=oldctx.user(), date=oldctx.date(),
407 new = commit(text=message, user=oldctx.user(), date=oldctx.date(),
407 extra=oldctx.extra())
408 extra=oldctx.extra())
408 newctx = repo[new]
409 newctx = repo[new]
409 if oldctx.node() != newctx.node():
410 if oldctx.node() != newctx.node():
410 return newctx, [(oldctx.node(), (new,))]
411 return newctx, [(oldctx.node(), (new,))]
411 # We didn't make an edit, so just indicate no replaced nodes
412 # We didn't make an edit, so just indicate no replaced nodes
412 return newctx, []
413 return newctx, []
413
414
414 def findoutgoing(ui, repo, remote=None, force=False, opts={}):
415 def findoutgoing(ui, repo, remote=None, force=False, opts={}):
415 """utility function to find the first outgoing changeset
416 """utility function to find the first outgoing changeset
416
417
417 Used by initialisation code"""
418 Used by initialisation code"""
418 dest = ui.expandpath(remote or 'default-push', remote or 'default')
419 dest = ui.expandpath(remote or 'default-push', remote or 'default')
419 dest, revs = hg.parseurl(dest, None)[:2]
420 dest, revs = hg.parseurl(dest, None)[:2]
420 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
421 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
421
422
422 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
423 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
423 other = hg.peer(repo, opts, dest)
424 other = hg.peer(repo, opts, dest)
424
425
425 if revs:
426 if revs:
426 revs = [repo.lookup(rev) for rev in revs]
427 revs = [repo.lookup(rev) for rev in revs]
427
428
428 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
429 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
429 if not outgoing.missing:
430 if not outgoing.missing:
430 raise util.Abort(_('no outgoing ancestors'))
431 raise util.Abort(_('no outgoing ancestors'))
431 roots = list(repo.revs("roots(%ln)", outgoing.missing))
432 roots = list(repo.revs("roots(%ln)", outgoing.missing))
432 if 1 < len(roots):
433 if 1 < len(roots):
433 msg = _('there are ambiguous outgoing revisions')
434 msg = _('there are ambiguous outgoing revisions')
434 hint = _('see "hg help histedit" for more detail')
435 hint = _('see "hg help histedit" for more detail')
435 raise util.Abort(msg, hint=hint)
436 raise util.Abort(msg, hint=hint)
436 return repo.lookup(roots[0])
437 return repo.lookup(roots[0])
437
438
438 actiontable = {'p': pick,
439 actiontable = {'p': pick,
439 'pick': pick,
440 'pick': pick,
440 'e': edit,
441 'e': edit,
441 'edit': edit,
442 'edit': edit,
442 'f': fold,
443 'f': fold,
443 'fold': fold,
444 'fold': fold,
444 'd': drop,
445 'd': drop,
445 'drop': drop,
446 'drop': drop,
446 'm': message,
447 'm': message,
447 'mess': message,
448 'mess': message,
448 }
449 }
449
450
450 @command('histedit',
451 @command('histedit',
451 [('', 'commands', '',
452 [('', 'commands', '',
452 _('Read history edits from the specified file.')),
453 _('Read history edits from the specified file.')),
453 ('c', 'continue', False, _('continue an edit already in progress')),
454 ('c', 'continue', False, _('continue an edit already in progress')),
454 ('k', 'keep', False,
455 ('k', 'keep', False,
455 _("don't strip old nodes after edit is complete")),
456 _("don't strip old nodes after edit is complete")),
456 ('', 'abort', False, _('abort an edit in progress')),
457 ('', 'abort', False, _('abort an edit in progress')),
457 ('o', 'outgoing', False, _('changesets not found in destination')),
458 ('o', 'outgoing', False, _('changesets not found in destination')),
458 ('f', 'force', False,
459 ('f', 'force', False,
459 _('force outgoing even for unrelated repositories')),
460 _('force outgoing even for unrelated repositories')),
460 ('r', 'rev', [], _('first revision to be edited'))],
461 ('r', 'rev', [], _('first revision to be edited'))],
461 _("ANCESTOR | --outgoing [URL]"))
462 _("ANCESTOR | --outgoing [URL]"))
462 def histedit(ui, repo, *freeargs, **opts):
463 def histedit(ui, repo, *freeargs, **opts):
463 """interactively edit changeset history
464 """interactively edit changeset history
464
465
465 This command edits changesets between ANCESTOR and the parent of
466 This command edits changesets between ANCESTOR and the parent of
466 the working directory.
467 the working directory.
467
468
468 With --outgoing, this edits changesets not found in the
469 With --outgoing, this edits changesets not found in the
469 destination repository. If URL of the destination is omitted, the
470 destination repository. If URL of the destination is omitted, the
470 'default-push' (or 'default') path will be used.
471 'default-push' (or 'default') path will be used.
471
472
472 For safety, this command is aborted, also if there are ambiguous
473 For safety, this command is aborted, also if there are ambiguous
473 outgoing revisions which may confuse users: for example, there are
474 outgoing revisions which may confuse users: for example, there are
474 multiple branches containing outgoing revisions.
475 multiple branches containing outgoing revisions.
475
476
476 Use "min(outgoing() and ::.)" or similar revset specification
477 Use "min(outgoing() and ::.)" or similar revset specification
477 instead of --outgoing to specify edit target revision exactly in
478 instead of --outgoing to specify edit target revision exactly in
478 such ambiguous situation. See :hg:`help revsets` for detail about
479 such ambiguous situation. See :hg:`help revsets` for detail about
479 selecting revisions.
480 selecting revisions.
480
481
481 Returns 0 on success, 1 if user intervention is required (not only
482 Returns 0 on success, 1 if user intervention is required (not only
482 for intentional "edit" command, but also for resolving unexpected
483 for intentional "edit" command, but also for resolving unexpected
483 conflicts).
484 conflicts).
484 """
485 """
485 lock = wlock = None
486 lock = wlock = None
486 try:
487 try:
487 wlock = repo.wlock()
488 wlock = repo.wlock()
488 lock = repo.lock()
489 lock = repo.lock()
489 _histedit(ui, repo, *freeargs, **opts)
490 _histedit(ui, repo, *freeargs, **opts)
490 finally:
491 finally:
491 release(lock, wlock)
492 release(lock, wlock)
492
493
493 def _histedit(ui, repo, *freeargs, **opts):
494 def _histedit(ui, repo, *freeargs, **opts):
494 # TODO only abort if we try and histedit mq patches, not just
495 # TODO only abort if we try and histedit mq patches, not just
495 # blanket if mq patches are applied somewhere
496 # blanket if mq patches are applied somewhere
496 mq = getattr(repo, 'mq', None)
497 mq = getattr(repo, 'mq', None)
497 if mq and mq.applied:
498 if mq and mq.applied:
498 raise util.Abort(_('source has mq patches applied'))
499 raise util.Abort(_('source has mq patches applied'))
499
500
500 # basic argument incompatibility processing
501 # basic argument incompatibility processing
501 outg = opts.get('outgoing')
502 outg = opts.get('outgoing')
502 cont = opts.get('continue')
503 cont = opts.get('continue')
503 abort = opts.get('abort')
504 abort = opts.get('abort')
504 force = opts.get('force')
505 force = opts.get('force')
505 rules = opts.get('commands', '')
506 rules = opts.get('commands', '')
506 revs = opts.get('rev', [])
507 revs = opts.get('rev', [])
507 goal = 'new' # This invocation goal, in new, continue, abort
508 goal = 'new' # This invocation goal, in new, continue, abort
508 if force and not outg:
509 if force and not outg:
509 raise util.Abort(_('--force only allowed with --outgoing'))
510 raise util.Abort(_('--force only allowed with --outgoing'))
510 if cont:
511 if cont:
511 if util.any((outg, abort, revs, freeargs, rules)):
512 if util.any((outg, abort, revs, freeargs, rules)):
512 raise util.Abort(_('no arguments allowed with --continue'))
513 raise util.Abort(_('no arguments allowed with --continue'))
513 goal = 'continue'
514 goal = 'continue'
514 elif abort:
515 elif abort:
515 if util.any((outg, revs, freeargs, rules)):
516 if util.any((outg, revs, freeargs, rules)):
516 raise util.Abort(_('no arguments allowed with --abort'))
517 raise util.Abort(_('no arguments allowed with --abort'))
517 goal = 'abort'
518 goal = 'abort'
518 else:
519 else:
519 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
520 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
520 raise util.Abort(_('history edit already in progress, try '
521 raise util.Abort(_('history edit already in progress, try '
521 '--continue or --abort'))
522 '--continue or --abort'))
522 if outg:
523 if outg:
523 if revs:
524 if revs:
524 raise util.Abort(_('no revisions allowed with --outgoing'))
525 raise util.Abort(_('no revisions allowed with --outgoing'))
525 if len(freeargs) > 1:
526 if len(freeargs) > 1:
526 raise util.Abort(
527 raise util.Abort(
527 _('only one repo argument allowed with --outgoing'))
528 _('only one repo argument allowed with --outgoing'))
528 else:
529 else:
529 revs.extend(freeargs)
530 revs.extend(freeargs)
530 if len(revs) != 1:
531 if len(revs) != 1:
531 raise util.Abort(
532 raise util.Abort(
532 _('histedit requires exactly one ancestor revision'))
533 _('histedit requires exactly one ancestor revision'))
533
534
534
535
535 if goal == 'continue':
536 if goal == 'continue':
536 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
537 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
537 parentctx = repo[parentctxnode]
538 parentctx = repo[parentctxnode]
538 parentctx, repl = bootstrapcontinue(ui, repo, parentctx, rules, opts)
539 parentctx, repl = bootstrapcontinue(ui, repo, parentctx, rules, opts)
539 replacements.extend(repl)
540 replacements.extend(repl)
540 elif goal == 'abort':
541 elif goal == 'abort':
541 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
542 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
542 mapping, tmpnodes, leafs, _ntm = processreplacement(repo, replacements)
543 mapping, tmpnodes, leafs, _ntm = processreplacement(repo, replacements)
543 ui.debug('restore wc to old parent %s\n' % node.short(topmost))
544 ui.debug('restore wc to old parent %s\n' % node.short(topmost))
544 # check whether we should update away
545 # check whether we should update away
545 parentnodes = [c.node() for c in repo[None].parents()]
546 parentnodes = [c.node() for c in repo[None].parents()]
546 for n in leafs | set([parentctxnode]):
547 for n in leafs | set([parentctxnode]):
547 if n in parentnodes:
548 if n in parentnodes:
548 hg.clean(repo, topmost)
549 hg.clean(repo, topmost)
549 break
550 break
550 else:
551 else:
551 pass
552 pass
552 cleanupnode(ui, repo, 'created', tmpnodes)
553 cleanupnode(ui, repo, 'created', tmpnodes)
553 cleanupnode(ui, repo, 'temp', leafs)
554 cleanupnode(ui, repo, 'temp', leafs)
554 os.unlink(os.path.join(repo.path, 'histedit-state'))
555 os.unlink(os.path.join(repo.path, 'histedit-state'))
555 return
556 return
556 else:
557 else:
557 cmdutil.checkunfinished(repo)
558 cmdutil.checkunfinished(repo)
558 cmdutil.bailifchanged(repo)
559 cmdutil.bailifchanged(repo)
559
560
560 topmost, empty = repo.dirstate.parents()
561 topmost, empty = repo.dirstate.parents()
561 if outg:
562 if outg:
562 if freeargs:
563 if freeargs:
563 remote = freeargs[0]
564 remote = freeargs[0]
564 else:
565 else:
565 remote = None
566 remote = None
566 root = findoutgoing(ui, repo, remote, force, opts)
567 root = findoutgoing(ui, repo, remote, force, opts)
567 else:
568 else:
568 root = revs[0]
569 root = revs[0]
569 root = scmutil.revsingle(repo, root).node()
570 root = scmutil.revsingle(repo, root).node()
570
571
571 keep = opts.get('keep', False)
572 keep = opts.get('keep', False)
572 revs = between(repo, root, topmost, keep)
573 revs = between(repo, root, topmost, keep)
573 if not revs:
574 if not revs:
574 raise util.Abort(_('%s is not an ancestor of working directory') %
575 raise util.Abort(_('%s is not an ancestor of working directory') %
575 node.short(root))
576 node.short(root))
576
577
577 ctxs = [repo[r] for r in revs]
578 ctxs = [repo[r] for r in revs]
578 if not rules:
579 if not rules:
579 rules = '\n'.join([makedesc(c) for c in ctxs])
580 rules = '\n'.join([makedesc(c) for c in ctxs])
580 rules += '\n\n'
581 rules += '\n\n'
581 rules += editcomment % (node.short(root), node.short(topmost))
582 rules += editcomment % (node.short(root), node.short(topmost))
582 rules = ui.edit(rules, ui.username())
583 rules = ui.edit(rules, ui.username())
583 # Save edit rules in .hg/histedit-last-edit.txt in case
584 # Save edit rules in .hg/histedit-last-edit.txt in case
584 # the user needs to ask for help after something
585 # the user needs to ask for help after something
585 # surprising happens.
586 # surprising happens.
586 f = open(repo.join('histedit-last-edit.txt'), 'w')
587 f = open(repo.join('histedit-last-edit.txt'), 'w')
587 f.write(rules)
588 f.write(rules)
588 f.close()
589 f.close()
589 else:
590 else:
590 if rules == '-':
591 if rules == '-':
591 f = sys.stdin
592 f = sys.stdin
592 else:
593 else:
593 f = open(rules)
594 f = open(rules)
594 rules = f.read()
595 rules = f.read()
595 f.close()
596 f.close()
596 rules = [l for l in (r.strip() for r in rules.splitlines())
597 rules = [l for l in (r.strip() for r in rules.splitlines())
597 if l and not l[0] == '#']
598 if l and not l[0] == '#']
598 rules = verifyrules(rules, repo, ctxs)
599 rules = verifyrules(rules, repo, ctxs)
599
600
600 parentctx = repo[root].parents()[0]
601 parentctx = repo[root].parents()[0]
601 keep = opts.get('keep', False)
602 keep = opts.get('keep', False)
602 replacements = []
603 replacements = []
603
604
604
605
605 while rules:
606 while rules:
606 writestate(repo, parentctx.node(), rules, keep, topmost, replacements)
607 writestate(repo, parentctx.node(), rules, keep, topmost, replacements)
607 action, ha = rules.pop(0)
608 action, ha = rules.pop(0)
608 ui.debug('histedit: processing %s %s\n' % (action, ha))
609 ui.debug('histedit: processing %s %s\n' % (action, ha))
609 actfunc = actiontable[action]
610 actfunc = actiontable[action]
610 parentctx, replacement_ = actfunc(ui, repo, parentctx, ha, opts)
611 parentctx, replacement_ = actfunc(ui, repo, parentctx, ha, opts)
611 replacements.extend(replacement_)
612 replacements.extend(replacement_)
612
613
613 hg.update(repo, parentctx.node())
614 hg.update(repo, parentctx.node())
614
615
615 mapping, tmpnodes, created, ntm = processreplacement(repo, replacements)
616 mapping, tmpnodes, created, ntm = processreplacement(repo, replacements)
616 if mapping:
617 if mapping:
617 for prec, succs in mapping.iteritems():
618 for prec, succs in mapping.iteritems():
618 if not succs:
619 if not succs:
619 ui.debug('histedit: %s is dropped\n' % node.short(prec))
620 ui.debug('histedit: %s is dropped\n' % node.short(prec))
620 else:
621 else:
621 ui.debug('histedit: %s is replaced by %s\n' % (
622 ui.debug('histedit: %s is replaced by %s\n' % (
622 node.short(prec), node.short(succs[0])))
623 node.short(prec), node.short(succs[0])))
623 if len(succs) > 1:
624 if len(succs) > 1:
624 m = 'histedit: %s'
625 m = 'histedit: %s'
625 for n in succs[1:]:
626 for n in succs[1:]:
626 ui.debug(m % node.short(n))
627 ui.debug(m % node.short(n))
627
628
628 if not keep:
629 if not keep:
629 if mapping:
630 if mapping:
630 movebookmarks(ui, repo, mapping, topmost, ntm)
631 movebookmarks(ui, repo, mapping, topmost, ntm)
631 # TODO update mq state
632 # TODO update mq state
632 if obsolete._enabled:
633 if obsolete._enabled:
633 markers = []
634 markers = []
634 # sort by revision number because it sound "right"
635 # sort by revision number because it sound "right"
635 for prec in sorted(mapping, key=repo.changelog.rev):
636 for prec in sorted(mapping, key=repo.changelog.rev):
636 succs = mapping[prec]
637 succs = mapping[prec]
637 markers.append((repo[prec],
638 markers.append((repo[prec],
638 tuple(repo[s] for s in succs)))
639 tuple(repo[s] for s in succs)))
639 if markers:
640 if markers:
640 obsolete.createmarkers(repo, markers)
641 obsolete.createmarkers(repo, markers)
641 else:
642 else:
642 cleanupnode(ui, repo, 'replaced', mapping)
643 cleanupnode(ui, repo, 'replaced', mapping)
643
644
644 cleanupnode(ui, repo, 'temp', tmpnodes)
645 cleanupnode(ui, repo, 'temp', tmpnodes)
645 os.unlink(os.path.join(repo.path, 'histedit-state'))
646 os.unlink(os.path.join(repo.path, 'histedit-state'))
646 if os.path.exists(repo.sjoin('undo')):
647 if os.path.exists(repo.sjoin('undo')):
647 os.unlink(repo.sjoin('undo'))
648 os.unlink(repo.sjoin('undo'))
648
649
649 def gatherchildren(repo, ctx):
650 def gatherchildren(repo, ctx):
650 # is there any new commit between the expected parent and "."
651 # is there any new commit between the expected parent and "."
651 #
652 #
652 # note: does not take non linear new change in account (but previous
653 # note: does not take non linear new change in account (but previous
653 # implementation didn't used them anyway (issue3655)
654 # implementation didn't used them anyway (issue3655)
654 newchildren = [c.node() for c in repo.set('(%d::.)', ctx)]
655 newchildren = [c.node() for c in repo.set('(%d::.)', ctx)]
655 if ctx.node() != node.nullid:
656 if ctx.node() != node.nullid:
656 if not newchildren:
657 if not newchildren:
657 # `ctx` should match but no result. This means that
658 # `ctx` should match but no result. This means that
658 # currentnode is not a descendant from ctx.
659 # currentnode is not a descendant from ctx.
659 msg = _('%s is not an ancestor of working directory')
660 msg = _('%s is not an ancestor of working directory')
660 hint = _('use "histedit --abort" to clear broken state')
661 hint = _('use "histedit --abort" to clear broken state')
661 raise util.Abort(msg % ctx, hint=hint)
662 raise util.Abort(msg % ctx, hint=hint)
662 newchildren.pop(0) # remove ctx
663 newchildren.pop(0) # remove ctx
663 return newchildren
664 return newchildren
664
665
665 def bootstrapcontinue(ui, repo, parentctx, rules, opts):
666 def bootstrapcontinue(ui, repo, parentctx, rules, opts):
666 action, currentnode = rules.pop(0)
667 action, currentnode = rules.pop(0)
667 ctx = repo[currentnode]
668 ctx = repo[currentnode]
668
669
669 newchildren = gatherchildren(repo, parentctx)
670 newchildren = gatherchildren(repo, parentctx)
670
671
671 # Commit dirty working directory if necessary
672 # Commit dirty working directory if necessary
672 new = None
673 new = None
673 m, a, r, d = repo.status()[:4]
674 m, a, r, d = repo.status()[:4]
674 if m or a or r or d:
675 if m or a or r or d:
675 # prepare the message for the commit to comes
676 # prepare the message for the commit to comes
676 if action in ('f', 'fold'):
677 if action in ('f', 'fold'):
677 message = 'fold-temp-revision %s' % currentnode
678 message = 'fold-temp-revision %s' % currentnode
678 else:
679 else:
679 message = ctx.description() + '\n'
680 message = ctx.description() + '\n'
680 if action in ('e', 'edit', 'm', 'mess'):
681 if action in ('e', 'edit', 'm', 'mess'):
681 editor = cmdutil.commitforceeditor
682 editor = cmdutil.commitforceeditor
682 else:
683 else:
683 editor = False
684 editor = False
684 commit = commitfuncfor(repo, ctx)
685 commit = commitfuncfor(repo, ctx)
685 new = commit(text=message, user=ctx.user(),
686 new = commit(text=message, user=ctx.user(),
686 date=ctx.date(), extra=ctx.extra(),
687 date=ctx.date(), extra=ctx.extra(),
687 editor=editor)
688 editor=editor)
688 if new is not None:
689 if new is not None:
689 newchildren.append(new)
690 newchildren.append(new)
690
691
691 replacements = []
692 replacements = []
692 # track replacements
693 # track replacements
693 if ctx.node() not in newchildren:
694 if ctx.node() not in newchildren:
694 # note: new children may be empty when the changeset is dropped.
695 # note: new children may be empty when the changeset is dropped.
695 # this happen e.g during conflicting pick where we revert content
696 # this happen e.g during conflicting pick where we revert content
696 # to parent.
697 # to parent.
697 replacements.append((ctx.node(), tuple(newchildren)))
698 replacements.append((ctx.node(), tuple(newchildren)))
698
699
699 if action in ('f', 'fold'):
700 if action in ('f', 'fold'):
700 if newchildren:
701 if newchildren:
701 # finalize fold operation if applicable
702 # finalize fold operation if applicable
702 if new is None:
703 if new is None:
703 new = newchildren[-1]
704 new = newchildren[-1]
704 else:
705 else:
705 newchildren.pop() # remove new from internal changes
706 newchildren.pop() # remove new from internal changes
706 parentctx, repl = finishfold(ui, repo, parentctx, ctx, new, opts,
707 parentctx, repl = finishfold(ui, repo, parentctx, ctx, new, opts,
707 newchildren)
708 newchildren)
708 replacements.extend(repl)
709 replacements.extend(repl)
709 else:
710 else:
710 # newchildren is empty if the fold did not result in any commit
711 # newchildren is empty if the fold did not result in any commit
711 # this happen when all folded change are discarded during the
712 # this happen when all folded change are discarded during the
712 # merge.
713 # merge.
713 replacements.append((ctx.node(), (parentctx.node(),)))
714 replacements.append((ctx.node(), (parentctx.node(),)))
714 elif newchildren:
715 elif newchildren:
715 # otherwise update "parentctx" before proceeding to further operation
716 # otherwise update "parentctx" before proceeding to further operation
716 parentctx = repo[newchildren[-1]]
717 parentctx = repo[newchildren[-1]]
717 return parentctx, replacements
718 return parentctx, replacements
718
719
719
720
720 def between(repo, old, new, keep):
721 def between(repo, old, new, keep):
721 """select and validate the set of revision to edit
722 """select and validate the set of revision to edit
722
723
723 When keep is false, the specified set can't have children."""
724 When keep is false, the specified set can't have children."""
724 ctxs = list(repo.set('%n::%n', old, new))
725 ctxs = list(repo.set('%n::%n', old, new))
725 if ctxs and not keep:
726 if ctxs and not keep:
726 if (not obsolete._enabled and
727 if (not obsolete._enabled and
727 repo.revs('(%ld::) - (%ld)', ctxs, ctxs)):
728 repo.revs('(%ld::) - (%ld)', ctxs, ctxs)):
728 raise util.Abort(_('cannot edit history that would orphan nodes'))
729 raise util.Abort(_('cannot edit history that would orphan nodes'))
729 if repo.revs('(%ld) and merge()', ctxs):
730 if repo.revs('(%ld) and merge()', ctxs):
730 raise util.Abort(_('cannot edit history that contains merges'))
731 raise util.Abort(_('cannot edit history that contains merges'))
731 root = ctxs[0] # list is already sorted by repo.set
732 root = ctxs[0] # list is already sorted by repo.set
732 if not root.phase():
733 if not root.phase():
733 raise util.Abort(_('cannot edit immutable changeset: %s') % root)
734 raise util.Abort(_('cannot edit immutable changeset: %s') % root)
734 return [c.node() for c in ctxs]
735 return [c.node() for c in ctxs]
735
736
736
737
737 def writestate(repo, parentnode, rules, keep, topmost, replacements):
738 def writestate(repo, parentnode, rules, keep, topmost, replacements):
738 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
739 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
739 pickle.dump((parentnode, rules, keep, topmost, replacements), fp)
740 pickle.dump((parentnode, rules, keep, topmost, replacements), fp)
740 fp.close()
741 fp.close()
741
742
742 def readstate(repo):
743 def readstate(repo):
743 """Returns a tuple of (parentnode, rules, keep, topmost, replacements).
744 """Returns a tuple of (parentnode, rules, keep, topmost, replacements).
744 """
745 """
745 fp = open(os.path.join(repo.path, 'histedit-state'))
746 fp = open(os.path.join(repo.path, 'histedit-state'))
746 return pickle.load(fp)
747 return pickle.load(fp)
747
748
748
749
749 def makedesc(c):
750 def makedesc(c):
750 """build a initial action line for a ctx `c`
751 """build a initial action line for a ctx `c`
751
752
752 line are in the form:
753 line are in the form:
753
754
754 pick <hash> <rev> <summary>
755 pick <hash> <rev> <summary>
755 """
756 """
756 summary = ''
757 summary = ''
757 if c.description():
758 if c.description():
758 summary = c.description().splitlines()[0]
759 summary = c.description().splitlines()[0]
759 line = 'pick %s %d %s' % (c, c.rev(), summary)
760 line = 'pick %s %d %s' % (c, c.rev(), summary)
760 return line[:80] # trim to 80 chars so it's not stupidly wide in my editor
761 return line[:80] # trim to 80 chars so it's not stupidly wide in my editor
761
762
762 def verifyrules(rules, repo, ctxs):
763 def verifyrules(rules, repo, ctxs):
763 """Verify that there exists exactly one edit rule per given changeset.
764 """Verify that there exists exactly one edit rule per given changeset.
764
765
765 Will abort if there are to many or too few rules, a malformed rule,
766 Will abort if there are to many or too few rules, a malformed rule,
766 or a rule on a changeset outside of the user-given range.
767 or a rule on a changeset outside of the user-given range.
767 """
768 """
768 parsed = []
769 parsed = []
769 expected = set(str(c) for c in ctxs)
770 expected = set(str(c) for c in ctxs)
770 seen = set()
771 seen = set()
771 for r in rules:
772 for r in rules:
772 if ' ' not in r:
773 if ' ' not in r:
773 raise util.Abort(_('malformed line "%s"') % r)
774 raise util.Abort(_('malformed line "%s"') % r)
774 action, rest = r.split(' ', 1)
775 action, rest = r.split(' ', 1)
775 ha = rest.strip().split(' ', 1)[0]
776 ha = rest.strip().split(' ', 1)[0]
776 try:
777 try:
777 ha = str(repo[ha]) # ensure its a short hash
778 ha = str(repo[ha]) # ensure its a short hash
778 except error.RepoError:
779 except error.RepoError:
779 raise util.Abort(_('unknown changeset %s listed') % ha)
780 raise util.Abort(_('unknown changeset %s listed') % ha)
780 if ha not in expected:
781 if ha not in expected:
781 raise util.Abort(
782 raise util.Abort(
782 _('may not use changesets other than the ones listed'))
783 _('may not use changesets other than the ones listed'))
783 if ha in seen:
784 if ha in seen:
784 raise util.Abort(_('duplicated command for changeset %s') % ha)
785 raise util.Abort(_('duplicated command for changeset %s') % ha)
785 seen.add(ha)
786 seen.add(ha)
786 if action not in actiontable:
787 if action not in actiontable:
787 raise util.Abort(_('unknown action "%s"') % action)
788 raise util.Abort(_('unknown action "%s"') % action)
788 parsed.append([action, ha])
789 parsed.append([action, ha])
789 missing = sorted(expected - seen) # sort to stabilize output
790 missing = sorted(expected - seen) # sort to stabilize output
790 if missing:
791 if missing:
791 raise util.Abort(_('missing rules for changeset %s') % missing[0],
792 raise util.Abort(_('missing rules for changeset %s') % missing[0],
792 hint=_('do you want to use the drop action?'))
793 hint=_('do you want to use the drop action?'))
793 return parsed
794 return parsed
794
795
795 def processreplacement(repo, replacements):
796 def processreplacement(repo, replacements):
796 """process the list of replacements to return
797 """process the list of replacements to return
797
798
798 1) the final mapping between original and created nodes
799 1) the final mapping between original and created nodes
799 2) the list of temporary node created by histedit
800 2) the list of temporary node created by histedit
800 3) the list of new commit created by histedit"""
801 3) the list of new commit created by histedit"""
801 allsuccs = set()
802 allsuccs = set()
802 replaced = set()
803 replaced = set()
803 fullmapping = {}
804 fullmapping = {}
804 # initialise basic set
805 # initialise basic set
805 # fullmapping record all operation recorded in replacement
806 # fullmapping record all operation recorded in replacement
806 for rep in replacements:
807 for rep in replacements:
807 allsuccs.update(rep[1])
808 allsuccs.update(rep[1])
808 replaced.add(rep[0])
809 replaced.add(rep[0])
809 fullmapping.setdefault(rep[0], set()).update(rep[1])
810 fullmapping.setdefault(rep[0], set()).update(rep[1])
810 new = allsuccs - replaced
811 new = allsuccs - replaced
811 tmpnodes = allsuccs & replaced
812 tmpnodes = allsuccs & replaced
812 # Reduce content fullmapping into direct relation between original nodes
813 # Reduce content fullmapping into direct relation between original nodes
813 # and final node created during history edition
814 # and final node created during history edition
814 # Dropped changeset are replaced by an empty list
815 # Dropped changeset are replaced by an empty list
815 toproceed = set(fullmapping)
816 toproceed = set(fullmapping)
816 final = {}
817 final = {}
817 while toproceed:
818 while toproceed:
818 for x in list(toproceed):
819 for x in list(toproceed):
819 succs = fullmapping[x]
820 succs = fullmapping[x]
820 for s in list(succs):
821 for s in list(succs):
821 if s in toproceed:
822 if s in toproceed:
822 # non final node with unknown closure
823 # non final node with unknown closure
823 # We can't process this now
824 # We can't process this now
824 break
825 break
825 elif s in final:
826 elif s in final:
826 # non final node, replace with closure
827 # non final node, replace with closure
827 succs.remove(s)
828 succs.remove(s)
828 succs.update(final[s])
829 succs.update(final[s])
829 else:
830 else:
830 final[x] = succs
831 final[x] = succs
831 toproceed.remove(x)
832 toproceed.remove(x)
832 # remove tmpnodes from final mapping
833 # remove tmpnodes from final mapping
833 for n in tmpnodes:
834 for n in tmpnodes:
834 del final[n]
835 del final[n]
835 # we expect all changes involved in final to exist in the repo
836 # we expect all changes involved in final to exist in the repo
836 # turn `final` into list (topologically sorted)
837 # turn `final` into list (topologically sorted)
837 nm = repo.changelog.nodemap
838 nm = repo.changelog.nodemap
838 for prec, succs in final.items():
839 for prec, succs in final.items():
839 final[prec] = sorted(succs, key=nm.get)
840 final[prec] = sorted(succs, key=nm.get)
840
841
841 # computed topmost element (necessary for bookmark)
842 # computed topmost element (necessary for bookmark)
842 if new:
843 if new:
843 newtopmost = sorted(new, key=repo.changelog.rev)[-1]
844 newtopmost = sorted(new, key=repo.changelog.rev)[-1]
844 elif not final:
845 elif not final:
845 # Nothing rewritten at all. we won't need `newtopmost`
846 # Nothing rewritten at all. we won't need `newtopmost`
846 # It is the same as `oldtopmost` and `processreplacement` know it
847 # It is the same as `oldtopmost` and `processreplacement` know it
847 newtopmost = None
848 newtopmost = None
848 else:
849 else:
849 # every body died. The newtopmost is the parent of the root.
850 # every body died. The newtopmost is the parent of the root.
850 newtopmost = repo[sorted(final, key=repo.changelog.rev)[0]].p1().node()
851 newtopmost = repo[sorted(final, key=repo.changelog.rev)[0]].p1().node()
851
852
852 return final, tmpnodes, new, newtopmost
853 return final, tmpnodes, new, newtopmost
853
854
854 def movebookmarks(ui, repo, mapping, oldtopmost, newtopmost):
855 def movebookmarks(ui, repo, mapping, oldtopmost, newtopmost):
855 """Move bookmark from old to newly created node"""
856 """Move bookmark from old to newly created node"""
856 if not mapping:
857 if not mapping:
857 # if nothing got rewritten there is not purpose for this function
858 # if nothing got rewritten there is not purpose for this function
858 return
859 return
859 moves = []
860 moves = []
860 for bk, old in sorted(repo._bookmarks.iteritems()):
861 for bk, old in sorted(repo._bookmarks.iteritems()):
861 if old == oldtopmost:
862 if old == oldtopmost:
862 # special case ensure bookmark stay on tip.
863 # special case ensure bookmark stay on tip.
863 #
864 #
864 # This is arguably a feature and we may only want that for the
865 # This is arguably a feature and we may only want that for the
865 # active bookmark. But the behavior is kept compatible with the old
866 # active bookmark. But the behavior is kept compatible with the old
866 # version for now.
867 # version for now.
867 moves.append((bk, newtopmost))
868 moves.append((bk, newtopmost))
868 continue
869 continue
869 base = old
870 base = old
870 new = mapping.get(base, None)
871 new = mapping.get(base, None)
871 if new is None:
872 if new is None:
872 continue
873 continue
873 while not new:
874 while not new:
874 # base is killed, trying with parent
875 # base is killed, trying with parent
875 base = repo[base].p1().node()
876 base = repo[base].p1().node()
876 new = mapping.get(base, (base,))
877 new = mapping.get(base, (base,))
877 # nothing to move
878 # nothing to move
878 moves.append((bk, new[-1]))
879 moves.append((bk, new[-1]))
879 if moves:
880 if moves:
880 marks = repo._bookmarks
881 marks = repo._bookmarks
881 for mark, new in moves:
882 for mark, new in moves:
882 old = marks[mark]
883 old = marks[mark]
883 ui.note(_('histedit: moving bookmarks %s from %s to %s\n')
884 ui.note(_('histedit: moving bookmarks %s from %s to %s\n')
884 % (mark, node.short(old), node.short(new)))
885 % (mark, node.short(old), node.short(new)))
885 marks[mark] = new
886 marks[mark] = new
886 marks.write()
887 marks.write()
887
888
888 def cleanupnode(ui, repo, name, nodes):
889 def cleanupnode(ui, repo, name, nodes):
889 """strip a group of nodes from the repository
890 """strip a group of nodes from the repository
890
891
891 The set of node to strip may contains unknown nodes."""
892 The set of node to strip may contains unknown nodes."""
892 ui.debug('should strip %s nodes %s\n' %
893 ui.debug('should strip %s nodes %s\n' %
893 (name, ', '.join([node.short(n) for n in nodes])))
894 (name, ', '.join([node.short(n) for n in nodes])))
894 lock = None
895 lock = None
895 try:
896 try:
896 lock = repo.lock()
897 lock = repo.lock()
897 # Find all node that need to be stripped
898 # Find all node that need to be stripped
898 # (we hg %lr instead of %ln to silently ignore unknown item
899 # (we hg %lr instead of %ln to silently ignore unknown item
899 nm = repo.changelog.nodemap
900 nm = repo.changelog.nodemap
900 nodes = [n for n in nodes if n in nm]
901 nodes = [n for n in nodes if n in nm]
901 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
902 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
902 for c in roots:
903 for c in roots:
903 # We should process node in reverse order to strip tip most first.
904 # We should process node in reverse order to strip tip most first.
904 # but this trigger a bug in changegroup hook.
905 # but this trigger a bug in changegroup hook.
905 # This would reduce bundle overhead
906 # This would reduce bundle overhead
906 repair.strip(ui, repo, c)
907 repair.strip(ui, repo, c)
907 finally:
908 finally:
908 release(lock)
909 release(lock)
909
910
910 def summaryhook(ui, repo):
911 def summaryhook(ui, repo):
911 if not os.path.exists(repo.join('histedit-state')):
912 if not os.path.exists(repo.join('histedit-state')):
912 return
913 return
913 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
914 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
914 if rules:
915 if rules:
915 # i18n: column positioning for "hg summary"
916 # i18n: column positioning for "hg summary"
916 ui.write(_('hist: %s (histedit --continue)\n') %
917 ui.write(_('hist: %s (histedit --continue)\n') %
917 (ui.label(_('%d remaining'), 'histedit.remaining') %
918 (ui.label(_('%d remaining'), 'histedit.remaining') %
918 len(rules)))
919 len(rules)))
919
920
920 def extsetup(ui):
921 def extsetup(ui):
921 cmdutil.summaryhooks.add('histedit', summaryhook)
922 cmdutil.summaryhooks.add('histedit', summaryhook)
922 cmdutil.unfinishedstates.append(
923 cmdutil.unfinishedstates.append(
923 ['histedit-state', False, True, _('histedit in progress'),
924 ['histedit-state', False, True, _('histedit in progress'),
924 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
925 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
@@ -1,3460 +1,3462 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 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''manage a stack of patches
8 '''manage a stack of patches
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 create new patch qnew
19 create new patch qnew
20 import existing patch qimport
20 import existing patch qimport
21
21
22 print patch series qseries
22 print patch series qseries
23 print applied patches qapplied
23 print applied patches qapplied
24
24
25 add known patch to applied stack qpush
25 add known patch to applied stack qpush
26 remove patch from applied stack qpop
26 remove patch from applied stack qpop
27 refresh contents of top applied patch qrefresh
27 refresh contents of top applied patch qrefresh
28
28
29 By default, mq will automatically use git patches when required to
29 By default, mq will automatically use git patches when required to
30 avoid losing file mode changes, copy records, binary files or empty
30 avoid losing file mode changes, copy records, binary files or empty
31 files creations or deletions. This behaviour can be configured with::
31 files creations or deletions. This behaviour can be configured with::
32
32
33 [mq]
33 [mq]
34 git = auto/keep/yes/no
34 git = auto/keep/yes/no
35
35
36 If set to 'keep', mq will obey the [diff] section configuration while
36 If set to 'keep', mq will obey the [diff] section configuration while
37 preserving existing git patches upon qrefresh. If set to 'yes' or
37 preserving existing git patches upon qrefresh. If set to 'yes' or
38 'no', mq will override the [diff] section and always generate git or
38 'no', mq will override the [diff] section and always generate git or
39 regular patches, possibly losing data in the second case.
39 regular patches, possibly losing data in the second case.
40
40
41 It may be desirable for mq changesets to be kept in the secret phase (see
41 It may be desirable for mq changesets to be kept in the secret phase (see
42 :hg:`help phases`), which can be enabled with the following setting::
42 :hg:`help phases`), which can be enabled with the following setting::
43
43
44 [mq]
44 [mq]
45 secret = True
45 secret = True
46
46
47 You will by default be managing a patch queue named "patches". You can
47 You will by default be managing a patch queue named "patches". You can
48 create other, independent patch queues with the :hg:`qqueue` command.
48 create other, independent patch queues with the :hg:`qqueue` command.
49
49
50 If the working directory contains uncommitted files, qpush, qpop and
50 If the working directory contains uncommitted files, qpush, qpop and
51 qgoto abort immediately. If -f/--force is used, the changes are
51 qgoto abort immediately. If -f/--force is used, the changes are
52 discarded. Setting::
52 discarded. Setting::
53
53
54 [mq]
54 [mq]
55 keepchanges = True
55 keepchanges = True
56
56
57 make them behave as if --keep-changes were passed, and non-conflicting
57 make them behave as if --keep-changes were passed, and non-conflicting
58 local changes will be tolerated and preserved. If incompatible options
58 local changes will be tolerated and preserved. If incompatible options
59 such as -f/--force or --exact are passed, this setting is ignored.
59 such as -f/--force or --exact are passed, this setting is ignored.
60
60
61 This extension used to provide a strip command. This command now lives
61 This extension used to provide a strip command. This command now lives
62 in the strip extension.
62 in the strip extension.
63 '''
63 '''
64
64
65 from mercurial.i18n import _
65 from mercurial.i18n import _
66 from mercurial.node import bin, hex, short, nullid, nullrev
66 from mercurial.node import bin, hex, short, nullid, nullrev
67 from mercurial.lock import release
67 from mercurial.lock import release
68 from mercurial import commands, cmdutil, hg, scmutil, util, revset
68 from mercurial import commands, cmdutil, hg, scmutil, util, revset
69 from mercurial import extensions, error, phases
69 from mercurial import extensions, error, phases
70 from mercurial import patch as patchmod
70 from mercurial import patch as patchmod
71 from mercurial import localrepo
71 from mercurial import localrepo
72 from mercurial import subrepo
72 from mercurial import subrepo
73 import os, re, errno, shutil
73 import os, re, errno, shutil
74
74
75 commands.norepo += " qclone"
75 commands.norepo += " qclone"
76
76
77 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
77 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
78
78
79 cmdtable = {}
79 cmdtable = {}
80 command = cmdutil.command(cmdtable)
80 command = cmdutil.command(cmdtable)
81 testedwith = 'internal'
81 testedwith = 'internal'
82
82
83 # force load strip extension formerly included in mq and import some utility
83 # force load strip extension formerly included in mq and import some utility
84 try:
84 try:
85 stripext = extensions.find('strip')
85 stripext = extensions.find('strip')
86 except KeyError:
86 except KeyError:
87 # note: load is lazy so we could avoid the try-except,
87 # note: load is lazy so we could avoid the try-except,
88 # but I (marmoute) prefer this explicit code.
88 # but I (marmoute) prefer this explicit code.
89 class dummyui(object):
89 class dummyui(object):
90 def debug(self, msg):
90 def debug(self, msg):
91 pass
91 pass
92 stripext = extensions.load(dummyui(), 'strip', '')
92 stripext = extensions.load(dummyui(), 'strip', '')
93
93
94 strip = stripext.strip
94 strip = stripext.strip
95 checksubstate = stripext.checksubstate
95 checksubstate = stripext.checksubstate
96 checklocalchanges = stripext.checklocalchanges
96 checklocalchanges = stripext.checklocalchanges
97
97
98
98
99 # Patch names looks like unix-file names.
99 # Patch names looks like unix-file names.
100 # They must be joinable with queue directory and result in the patch path.
100 # They must be joinable with queue directory and result in the patch path.
101 normname = util.normpath
101 normname = util.normpath
102
102
103 class statusentry(object):
103 class statusentry(object):
104 def __init__(self, node, name):
104 def __init__(self, node, name):
105 self.node, self.name = node, name
105 self.node, self.name = node, name
106 def __repr__(self):
106 def __repr__(self):
107 return hex(self.node) + ':' + self.name
107 return hex(self.node) + ':' + self.name
108
108
109 class patchheader(object):
109 class patchheader(object):
110 def __init__(self, pf, plainmode=False):
110 def __init__(self, pf, plainmode=False):
111 def eatdiff(lines):
111 def eatdiff(lines):
112 while lines:
112 while lines:
113 l = lines[-1]
113 l = lines[-1]
114 if (l.startswith("diff -") or
114 if (l.startswith("diff -") or
115 l.startswith("Index:") or
115 l.startswith("Index:") or
116 l.startswith("===========")):
116 l.startswith("===========")):
117 del lines[-1]
117 del lines[-1]
118 else:
118 else:
119 break
119 break
120 def eatempty(lines):
120 def eatempty(lines):
121 while lines:
121 while lines:
122 if not lines[-1].strip():
122 if not lines[-1].strip():
123 del lines[-1]
123 del lines[-1]
124 else:
124 else:
125 break
125 break
126
126
127 message = []
127 message = []
128 comments = []
128 comments = []
129 user = None
129 user = None
130 date = None
130 date = None
131 parent = None
131 parent = None
132 format = None
132 format = None
133 subject = None
133 subject = None
134 branch = None
134 branch = None
135 nodeid = None
135 nodeid = None
136 diffstart = 0
136 diffstart = 0
137
137
138 for line in file(pf):
138 for line in file(pf):
139 line = line.rstrip()
139 line = line.rstrip()
140 if (line.startswith('diff --git')
140 if (line.startswith('diff --git')
141 or (diffstart and line.startswith('+++ '))):
141 or (diffstart and line.startswith('+++ '))):
142 diffstart = 2
142 diffstart = 2
143 break
143 break
144 diffstart = 0 # reset
144 diffstart = 0 # reset
145 if line.startswith("--- "):
145 if line.startswith("--- "):
146 diffstart = 1
146 diffstart = 1
147 continue
147 continue
148 elif format == "hgpatch":
148 elif format == "hgpatch":
149 # parse values when importing the result of an hg export
149 # parse values when importing the result of an hg export
150 if line.startswith("# User "):
150 if line.startswith("# User "):
151 user = line[7:]
151 user = line[7:]
152 elif line.startswith("# Date "):
152 elif line.startswith("# Date "):
153 date = line[7:]
153 date = line[7:]
154 elif line.startswith("# Parent "):
154 elif line.startswith("# Parent "):
155 parent = line[9:].lstrip()
155 parent = line[9:].lstrip()
156 elif line.startswith("# Branch "):
156 elif line.startswith("# Branch "):
157 branch = line[9:]
157 branch = line[9:]
158 elif line.startswith("# Node ID "):
158 elif line.startswith("# Node ID "):
159 nodeid = line[10:]
159 nodeid = line[10:]
160 elif not line.startswith("# ") and line:
160 elif not line.startswith("# ") and line:
161 message.append(line)
161 message.append(line)
162 format = None
162 format = None
163 elif line == '# HG changeset patch':
163 elif line == '# HG changeset patch':
164 message = []
164 message = []
165 format = "hgpatch"
165 format = "hgpatch"
166 elif (format != "tagdone" and (line.startswith("Subject: ") or
166 elif (format != "tagdone" and (line.startswith("Subject: ") or
167 line.startswith("subject: "))):
167 line.startswith("subject: "))):
168 subject = line[9:]
168 subject = line[9:]
169 format = "tag"
169 format = "tag"
170 elif (format != "tagdone" and (line.startswith("From: ") or
170 elif (format != "tagdone" and (line.startswith("From: ") or
171 line.startswith("from: "))):
171 line.startswith("from: "))):
172 user = line[6:]
172 user = line[6:]
173 format = "tag"
173 format = "tag"
174 elif (format != "tagdone" and (line.startswith("Date: ") or
174 elif (format != "tagdone" and (line.startswith("Date: ") or
175 line.startswith("date: "))):
175 line.startswith("date: "))):
176 date = line[6:]
176 date = line[6:]
177 format = "tag"
177 format = "tag"
178 elif format == "tag" and line == "":
178 elif format == "tag" and line == "":
179 # when looking for tags (subject: from: etc) they
179 # when looking for tags (subject: from: etc) they
180 # end once you find a blank line in the source
180 # end once you find a blank line in the source
181 format = "tagdone"
181 format = "tagdone"
182 elif message or line:
182 elif message or line:
183 message.append(line)
183 message.append(line)
184 comments.append(line)
184 comments.append(line)
185
185
186 eatdiff(message)
186 eatdiff(message)
187 eatdiff(comments)
187 eatdiff(comments)
188 # Remember the exact starting line of the patch diffs before consuming
188 # Remember the exact starting line of the patch diffs before consuming
189 # empty lines, for external use by TortoiseHg and others
189 # empty lines, for external use by TortoiseHg and others
190 self.diffstartline = len(comments)
190 self.diffstartline = len(comments)
191 eatempty(message)
191 eatempty(message)
192 eatempty(comments)
192 eatempty(comments)
193
193
194 # make sure message isn't empty
194 # make sure message isn't empty
195 if format and format.startswith("tag") and subject:
195 if format and format.startswith("tag") and subject:
196 message.insert(0, "")
196 message.insert(0, "")
197 message.insert(0, subject)
197 message.insert(0, subject)
198
198
199 self.message = message
199 self.message = message
200 self.comments = comments
200 self.comments = comments
201 self.user = user
201 self.user = user
202 self.date = date
202 self.date = date
203 self.parent = parent
203 self.parent = parent
204 # nodeid and branch are for external use by TortoiseHg and others
204 # nodeid and branch are for external use by TortoiseHg and others
205 self.nodeid = nodeid
205 self.nodeid = nodeid
206 self.branch = branch
206 self.branch = branch
207 self.haspatch = diffstart > 1
207 self.haspatch = diffstart > 1
208 self.plainmode = plainmode
208 self.plainmode = plainmode
209
209
210 def setuser(self, user):
210 def setuser(self, user):
211 if not self.updateheader(['From: ', '# User '], user):
211 if not self.updateheader(['From: ', '# User '], user):
212 try:
212 try:
213 patchheaderat = self.comments.index('# HG changeset patch')
213 patchheaderat = self.comments.index('# HG changeset patch')
214 self.comments.insert(patchheaderat + 1, '# User ' + user)
214 self.comments.insert(patchheaderat + 1, '# User ' + user)
215 except ValueError:
215 except ValueError:
216 if self.plainmode or self._hasheader(['Date: ']):
216 if self.plainmode or self._hasheader(['Date: ']):
217 self.comments = ['From: ' + user] + self.comments
217 self.comments = ['From: ' + user] + self.comments
218 else:
218 else:
219 tmp = ['# HG changeset patch', '# User ' + user, '']
219 tmp = ['# HG changeset patch', '# User ' + user, '']
220 self.comments = tmp + self.comments
220 self.comments = tmp + self.comments
221 self.user = user
221 self.user = user
222
222
223 def setdate(self, date):
223 def setdate(self, date):
224 if not self.updateheader(['Date: ', '# Date '], date):
224 if not self.updateheader(['Date: ', '# Date '], date):
225 try:
225 try:
226 patchheaderat = self.comments.index('# HG changeset patch')
226 patchheaderat = self.comments.index('# HG changeset patch')
227 self.comments.insert(patchheaderat + 1, '# Date ' + date)
227 self.comments.insert(patchheaderat + 1, '# Date ' + date)
228 except ValueError:
228 except ValueError:
229 if self.plainmode or self._hasheader(['From: ']):
229 if self.plainmode or self._hasheader(['From: ']):
230 self.comments = ['Date: ' + date] + self.comments
230 self.comments = ['Date: ' + date] + self.comments
231 else:
231 else:
232 tmp = ['# HG changeset patch', '# Date ' + date, '']
232 tmp = ['# HG changeset patch', '# Date ' + date, '']
233 self.comments = tmp + self.comments
233 self.comments = tmp + self.comments
234 self.date = date
234 self.date = date
235
235
236 def setparent(self, parent):
236 def setparent(self, parent):
237 if not self.updateheader(['# Parent '], parent):
237 if not self.updateheader(['# Parent '], parent):
238 try:
238 try:
239 patchheaderat = self.comments.index('# HG changeset patch')
239 patchheaderat = self.comments.index('# HG changeset patch')
240 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
240 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
241 except ValueError:
241 except ValueError:
242 pass
242 pass
243 self.parent = parent
243 self.parent = parent
244
244
245 def setmessage(self, message):
245 def setmessage(self, message):
246 if self.comments:
246 if self.comments:
247 self._delmsg()
247 self._delmsg()
248 self.message = [message]
248 self.message = [message]
249 self.comments += self.message
249 self.comments += self.message
250
250
251 def updateheader(self, prefixes, new):
251 def updateheader(self, prefixes, new):
252 '''Update all references to a field in the patch header.
252 '''Update all references to a field in the patch header.
253 Return whether the field is present.'''
253 Return whether the field is present.'''
254 res = False
254 res = False
255 for prefix in prefixes:
255 for prefix in prefixes:
256 for i in xrange(len(self.comments)):
256 for i in xrange(len(self.comments)):
257 if self.comments[i].startswith(prefix):
257 if self.comments[i].startswith(prefix):
258 self.comments[i] = prefix + new
258 self.comments[i] = prefix + new
259 res = True
259 res = True
260 break
260 break
261 return res
261 return res
262
262
263 def _hasheader(self, prefixes):
263 def _hasheader(self, prefixes):
264 '''Check if a header starts with any of the given prefixes.'''
264 '''Check if a header starts with any of the given prefixes.'''
265 for prefix in prefixes:
265 for prefix in prefixes:
266 for comment in self.comments:
266 for comment in self.comments:
267 if comment.startswith(prefix):
267 if comment.startswith(prefix):
268 return True
268 return True
269 return False
269 return False
270
270
271 def __str__(self):
271 def __str__(self):
272 if not self.comments:
272 if not self.comments:
273 return ''
273 return ''
274 return '\n'.join(self.comments) + '\n\n'
274 return '\n'.join(self.comments) + '\n\n'
275
275
276 def _delmsg(self):
276 def _delmsg(self):
277 '''Remove existing message, keeping the rest of the comments fields.
277 '''Remove existing message, keeping the rest of the comments fields.
278 If comments contains 'subject: ', message will prepend
278 If comments contains 'subject: ', message will prepend
279 the field and a blank line.'''
279 the field and a blank line.'''
280 if self.message:
280 if self.message:
281 subj = 'subject: ' + self.message[0].lower()
281 subj = 'subject: ' + self.message[0].lower()
282 for i in xrange(len(self.comments)):
282 for i in xrange(len(self.comments)):
283 if subj == self.comments[i].lower():
283 if subj == self.comments[i].lower():
284 del self.comments[i]
284 del self.comments[i]
285 self.message = self.message[2:]
285 self.message = self.message[2:]
286 break
286 break
287 ci = 0
287 ci = 0
288 for mi in self.message:
288 for mi in self.message:
289 while mi != self.comments[ci]:
289 while mi != self.comments[ci]:
290 ci += 1
290 ci += 1
291 del self.comments[ci]
291 del self.comments[ci]
292
292
293 def newcommit(repo, phase, *args, **kwargs):
293 def newcommit(repo, phase, *args, **kwargs):
294 """helper dedicated to ensure a commit respect mq.secret setting
294 """helper dedicated to ensure a commit respect mq.secret setting
295
295
296 It should be used instead of repo.commit inside the mq source for operation
296 It should be used instead of repo.commit inside the mq source for operation
297 creating new changeset.
297 creating new changeset.
298 """
298 """
299 repo = repo.unfiltered()
299 repo = repo.unfiltered()
300 if phase is None:
300 if phase is None:
301 if repo.ui.configbool('mq', 'secret', False):
301 if repo.ui.configbool('mq', 'secret', False):
302 phase = phases.secret
302 phase = phases.secret
303 if phase is not None:
303 if phase is not None:
304 backup = repo.ui.backupconfig('phases', 'new-commit')
304 backup = repo.ui.backupconfig('phases', 'new-commit')
305 try:
305 try:
306 if phase is not None:
306 if phase is not None:
307 repo.ui.setconfig('phases', 'new-commit', phase)
307 repo.ui.setconfig('phases', 'new-commit', phase)
308 return repo.commit(*args, **kwargs)
308 return repo.commit(*args, **kwargs)
309 finally:
309 finally:
310 if phase is not None:
310 if phase is not None:
311 repo.ui.restoreconfig(backup)
311 repo.ui.restoreconfig(backup)
312
312
313 class AbortNoCleanup(error.Abort):
313 class AbortNoCleanup(error.Abort):
314 pass
314 pass
315
315
316 class queue(object):
316 class queue(object):
317 def __init__(self, ui, baseui, path, patchdir=None):
317 def __init__(self, ui, baseui, path, patchdir=None):
318 self.basepath = path
318 self.basepath = path
319 try:
319 try:
320 fh = open(os.path.join(path, 'patches.queue'))
320 fh = open(os.path.join(path, 'patches.queue'))
321 cur = fh.read().rstrip()
321 cur = fh.read().rstrip()
322 fh.close()
322 fh.close()
323 if not cur:
323 if not cur:
324 curpath = os.path.join(path, 'patches')
324 curpath = os.path.join(path, 'patches')
325 else:
325 else:
326 curpath = os.path.join(path, 'patches-' + cur)
326 curpath = os.path.join(path, 'patches-' + cur)
327 except IOError:
327 except IOError:
328 curpath = os.path.join(path, 'patches')
328 curpath = os.path.join(path, 'patches')
329 self.path = patchdir or curpath
329 self.path = patchdir or curpath
330 self.opener = scmutil.opener(self.path)
330 self.opener = scmutil.opener(self.path)
331 self.ui = ui
331 self.ui = ui
332 self.baseui = baseui
332 self.baseui = baseui
333 self.applieddirty = False
333 self.applieddirty = False
334 self.seriesdirty = False
334 self.seriesdirty = False
335 self.added = []
335 self.added = []
336 self.seriespath = "series"
336 self.seriespath = "series"
337 self.statuspath = "status"
337 self.statuspath = "status"
338 self.guardspath = "guards"
338 self.guardspath = "guards"
339 self.activeguards = None
339 self.activeguards = None
340 self.guardsdirty = False
340 self.guardsdirty = False
341 # Handle mq.git as a bool with extended values
341 # Handle mq.git as a bool with extended values
342 try:
342 try:
343 gitmode = ui.configbool('mq', 'git', None)
343 gitmode = ui.configbool('mq', 'git', None)
344 if gitmode is None:
344 if gitmode is None:
345 raise error.ConfigError
345 raise error.ConfigError
346 self.gitmode = gitmode and 'yes' or 'no'
346 self.gitmode = gitmode and 'yes' or 'no'
347 except error.ConfigError:
347 except error.ConfigError:
348 self.gitmode = ui.config('mq', 'git', 'auto').lower()
348 self.gitmode = ui.config('mq', 'git', 'auto').lower()
349 self.plainmode = ui.configbool('mq', 'plain', False)
349 self.plainmode = ui.configbool('mq', 'plain', False)
350 self.checkapplied = True
350 self.checkapplied = True
351
351
352 @util.propertycache
352 @util.propertycache
353 def applied(self):
353 def applied(self):
354 def parselines(lines):
354 def parselines(lines):
355 for l in lines:
355 for l in lines:
356 entry = l.split(':', 1)
356 entry = l.split(':', 1)
357 if len(entry) > 1:
357 if len(entry) > 1:
358 n, name = entry
358 n, name = entry
359 yield statusentry(bin(n), name)
359 yield statusentry(bin(n), name)
360 elif l.strip():
360 elif l.strip():
361 self.ui.warn(_('malformated mq status line: %s\n') % entry)
361 self.ui.warn(_('malformated mq status line: %s\n') % entry)
362 # else we ignore empty lines
362 # else we ignore empty lines
363 try:
363 try:
364 lines = self.opener.read(self.statuspath).splitlines()
364 lines = self.opener.read(self.statuspath).splitlines()
365 return list(parselines(lines))
365 return list(parselines(lines))
366 except IOError, e:
366 except IOError, e:
367 if e.errno == errno.ENOENT:
367 if e.errno == errno.ENOENT:
368 return []
368 return []
369 raise
369 raise
370
370
371 @util.propertycache
371 @util.propertycache
372 def fullseries(self):
372 def fullseries(self):
373 try:
373 try:
374 return self.opener.read(self.seriespath).splitlines()
374 return self.opener.read(self.seriespath).splitlines()
375 except IOError, e:
375 except IOError, e:
376 if e.errno == errno.ENOENT:
376 if e.errno == errno.ENOENT:
377 return []
377 return []
378 raise
378 raise
379
379
380 @util.propertycache
380 @util.propertycache
381 def series(self):
381 def series(self):
382 self.parseseries()
382 self.parseseries()
383 return self.series
383 return self.series
384
384
385 @util.propertycache
385 @util.propertycache
386 def seriesguards(self):
386 def seriesguards(self):
387 self.parseseries()
387 self.parseseries()
388 return self.seriesguards
388 return self.seriesguards
389
389
390 def invalidate(self):
390 def invalidate(self):
391 for a in 'applied fullseries series seriesguards'.split():
391 for a in 'applied fullseries series seriesguards'.split():
392 if a in self.__dict__:
392 if a in self.__dict__:
393 delattr(self, a)
393 delattr(self, a)
394 self.applieddirty = False
394 self.applieddirty = False
395 self.seriesdirty = False
395 self.seriesdirty = False
396 self.guardsdirty = False
396 self.guardsdirty = False
397 self.activeguards = None
397 self.activeguards = None
398
398
399 def diffopts(self, opts={}, patchfn=None):
399 def diffopts(self, opts={}, patchfn=None):
400 diffopts = patchmod.diffopts(self.ui, opts)
400 diffopts = patchmod.diffopts(self.ui, opts)
401 if self.gitmode == 'auto':
401 if self.gitmode == 'auto':
402 diffopts.upgrade = True
402 diffopts.upgrade = True
403 elif self.gitmode == 'keep':
403 elif self.gitmode == 'keep':
404 pass
404 pass
405 elif self.gitmode in ('yes', 'no'):
405 elif self.gitmode in ('yes', 'no'):
406 diffopts.git = self.gitmode == 'yes'
406 diffopts.git = self.gitmode == 'yes'
407 else:
407 else:
408 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
408 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
409 ' got %s') % self.gitmode)
409 ' got %s') % self.gitmode)
410 if patchfn:
410 if patchfn:
411 diffopts = self.patchopts(diffopts, patchfn)
411 diffopts = self.patchopts(diffopts, patchfn)
412 return diffopts
412 return diffopts
413
413
414 def patchopts(self, diffopts, *patches):
414 def patchopts(self, diffopts, *patches):
415 """Return a copy of input diff options with git set to true if
415 """Return a copy of input diff options with git set to true if
416 referenced patch is a git patch and should be preserved as such.
416 referenced patch is a git patch and should be preserved as such.
417 """
417 """
418 diffopts = diffopts.copy()
418 diffopts = diffopts.copy()
419 if not diffopts.git and self.gitmode == 'keep':
419 if not diffopts.git and self.gitmode == 'keep':
420 for patchfn in patches:
420 for patchfn in patches:
421 patchf = self.opener(patchfn, 'r')
421 patchf = self.opener(patchfn, 'r')
422 # if the patch was a git patch, refresh it as a git patch
422 # if the patch was a git patch, refresh it as a git patch
423 for line in patchf:
423 for line in patchf:
424 if line.startswith('diff --git'):
424 if line.startswith('diff --git'):
425 diffopts.git = True
425 diffopts.git = True
426 break
426 break
427 patchf.close()
427 patchf.close()
428 return diffopts
428 return diffopts
429
429
430 def join(self, *p):
430 def join(self, *p):
431 return os.path.join(self.path, *p)
431 return os.path.join(self.path, *p)
432
432
433 def findseries(self, patch):
433 def findseries(self, patch):
434 def matchpatch(l):
434 def matchpatch(l):
435 l = l.split('#', 1)[0]
435 l = l.split('#', 1)[0]
436 return l.strip() == patch
436 return l.strip() == patch
437 for index, l in enumerate(self.fullseries):
437 for index, l in enumerate(self.fullseries):
438 if matchpatch(l):
438 if matchpatch(l):
439 return index
439 return index
440 return None
440 return None
441
441
442 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
442 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
443
443
444 def parseseries(self):
444 def parseseries(self):
445 self.series = []
445 self.series = []
446 self.seriesguards = []
446 self.seriesguards = []
447 for l in self.fullseries:
447 for l in self.fullseries:
448 h = l.find('#')
448 h = l.find('#')
449 if h == -1:
449 if h == -1:
450 patch = l
450 patch = l
451 comment = ''
451 comment = ''
452 elif h == 0:
452 elif h == 0:
453 continue
453 continue
454 else:
454 else:
455 patch = l[:h]
455 patch = l[:h]
456 comment = l[h:]
456 comment = l[h:]
457 patch = patch.strip()
457 patch = patch.strip()
458 if patch:
458 if patch:
459 if patch in self.series:
459 if patch in self.series:
460 raise util.Abort(_('%s appears more than once in %s') %
460 raise util.Abort(_('%s appears more than once in %s') %
461 (patch, self.join(self.seriespath)))
461 (patch, self.join(self.seriespath)))
462 self.series.append(patch)
462 self.series.append(patch)
463 self.seriesguards.append(self.guard_re.findall(comment))
463 self.seriesguards.append(self.guard_re.findall(comment))
464
464
465 def checkguard(self, guard):
465 def checkguard(self, guard):
466 if not guard:
466 if not guard:
467 return _('guard cannot be an empty string')
467 return _('guard cannot be an empty string')
468 bad_chars = '# \t\r\n\f'
468 bad_chars = '# \t\r\n\f'
469 first = guard[0]
469 first = guard[0]
470 if first in '-+':
470 if first in '-+':
471 return (_('guard %r starts with invalid character: %r') %
471 return (_('guard %r starts with invalid character: %r') %
472 (guard, first))
472 (guard, first))
473 for c in bad_chars:
473 for c in bad_chars:
474 if c in guard:
474 if c in guard:
475 return _('invalid character in guard %r: %r') % (guard, c)
475 return _('invalid character in guard %r: %r') % (guard, c)
476
476
477 def setactive(self, guards):
477 def setactive(self, guards):
478 for guard in guards:
478 for guard in guards:
479 bad = self.checkguard(guard)
479 bad = self.checkguard(guard)
480 if bad:
480 if bad:
481 raise util.Abort(bad)
481 raise util.Abort(bad)
482 guards = sorted(set(guards))
482 guards = sorted(set(guards))
483 self.ui.debug('active guards: %s\n' % ' '.join(guards))
483 self.ui.debug('active guards: %s\n' % ' '.join(guards))
484 self.activeguards = guards
484 self.activeguards = guards
485 self.guardsdirty = True
485 self.guardsdirty = True
486
486
487 def active(self):
487 def active(self):
488 if self.activeguards is None:
488 if self.activeguards is None:
489 self.activeguards = []
489 self.activeguards = []
490 try:
490 try:
491 guards = self.opener.read(self.guardspath).split()
491 guards = self.opener.read(self.guardspath).split()
492 except IOError, err:
492 except IOError, err:
493 if err.errno != errno.ENOENT:
493 if err.errno != errno.ENOENT:
494 raise
494 raise
495 guards = []
495 guards = []
496 for i, guard in enumerate(guards):
496 for i, guard in enumerate(guards):
497 bad = self.checkguard(guard)
497 bad = self.checkguard(guard)
498 if bad:
498 if bad:
499 self.ui.warn('%s:%d: %s\n' %
499 self.ui.warn('%s:%d: %s\n' %
500 (self.join(self.guardspath), i + 1, bad))
500 (self.join(self.guardspath), i + 1, bad))
501 else:
501 else:
502 self.activeguards.append(guard)
502 self.activeguards.append(guard)
503 return self.activeguards
503 return self.activeguards
504
504
505 def setguards(self, idx, guards):
505 def setguards(self, idx, guards):
506 for g in guards:
506 for g in guards:
507 if len(g) < 2:
507 if len(g) < 2:
508 raise util.Abort(_('guard %r too short') % g)
508 raise util.Abort(_('guard %r too short') % g)
509 if g[0] not in '-+':
509 if g[0] not in '-+':
510 raise util.Abort(_('guard %r starts with invalid char') % g)
510 raise util.Abort(_('guard %r starts with invalid char') % g)
511 bad = self.checkguard(g[1:])
511 bad = self.checkguard(g[1:])
512 if bad:
512 if bad:
513 raise util.Abort(bad)
513 raise util.Abort(bad)
514 drop = self.guard_re.sub('', self.fullseries[idx])
514 drop = self.guard_re.sub('', self.fullseries[idx])
515 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
515 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
516 self.parseseries()
516 self.parseseries()
517 self.seriesdirty = True
517 self.seriesdirty = True
518
518
519 def pushable(self, idx):
519 def pushable(self, idx):
520 if isinstance(idx, str):
520 if isinstance(idx, str):
521 idx = self.series.index(idx)
521 idx = self.series.index(idx)
522 patchguards = self.seriesguards[idx]
522 patchguards = self.seriesguards[idx]
523 if not patchguards:
523 if not patchguards:
524 return True, None
524 return True, None
525 guards = self.active()
525 guards = self.active()
526 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
526 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
527 if exactneg:
527 if exactneg:
528 return False, repr(exactneg[0])
528 return False, repr(exactneg[0])
529 pos = [g for g in patchguards if g[0] == '+']
529 pos = [g for g in patchguards if g[0] == '+']
530 exactpos = [g for g in pos if g[1:] in guards]
530 exactpos = [g for g in pos if g[1:] in guards]
531 if pos:
531 if pos:
532 if exactpos:
532 if exactpos:
533 return True, repr(exactpos[0])
533 return True, repr(exactpos[0])
534 return False, ' '.join(map(repr, pos))
534 return False, ' '.join(map(repr, pos))
535 return True, ''
535 return True, ''
536
536
537 def explainpushable(self, idx, all_patches=False):
537 def explainpushable(self, idx, all_patches=False):
538 write = all_patches and self.ui.write or self.ui.warn
538 write = all_patches and self.ui.write or self.ui.warn
539 if all_patches or self.ui.verbose:
539 if all_patches or self.ui.verbose:
540 if isinstance(idx, str):
540 if isinstance(idx, str):
541 idx = self.series.index(idx)
541 idx = self.series.index(idx)
542 pushable, why = self.pushable(idx)
542 pushable, why = self.pushable(idx)
543 if all_patches and pushable:
543 if all_patches and pushable:
544 if why is None:
544 if why is None:
545 write(_('allowing %s - no guards in effect\n') %
545 write(_('allowing %s - no guards in effect\n') %
546 self.series[idx])
546 self.series[idx])
547 else:
547 else:
548 if not why:
548 if not why:
549 write(_('allowing %s - no matching negative guards\n') %
549 write(_('allowing %s - no matching negative guards\n') %
550 self.series[idx])
550 self.series[idx])
551 else:
551 else:
552 write(_('allowing %s - guarded by %s\n') %
552 write(_('allowing %s - guarded by %s\n') %
553 (self.series[idx], why))
553 (self.series[idx], why))
554 if not pushable:
554 if not pushable:
555 if why:
555 if why:
556 write(_('skipping %s - guarded by %s\n') %
556 write(_('skipping %s - guarded by %s\n') %
557 (self.series[idx], why))
557 (self.series[idx], why))
558 else:
558 else:
559 write(_('skipping %s - no matching guards\n') %
559 write(_('skipping %s - no matching guards\n') %
560 self.series[idx])
560 self.series[idx])
561
561
562 def savedirty(self):
562 def savedirty(self):
563 def writelist(items, path):
563 def writelist(items, path):
564 fp = self.opener(path, 'w')
564 fp = self.opener(path, 'w')
565 for i in items:
565 for i in items:
566 fp.write("%s\n" % i)
566 fp.write("%s\n" % i)
567 fp.close()
567 fp.close()
568 if self.applieddirty:
568 if self.applieddirty:
569 writelist(map(str, self.applied), self.statuspath)
569 writelist(map(str, self.applied), self.statuspath)
570 self.applieddirty = False
570 self.applieddirty = False
571 if self.seriesdirty:
571 if self.seriesdirty:
572 writelist(self.fullseries, self.seriespath)
572 writelist(self.fullseries, self.seriespath)
573 self.seriesdirty = False
573 self.seriesdirty = False
574 if self.guardsdirty:
574 if self.guardsdirty:
575 writelist(self.activeguards, self.guardspath)
575 writelist(self.activeguards, self.guardspath)
576 self.guardsdirty = False
576 self.guardsdirty = False
577 if self.added:
577 if self.added:
578 qrepo = self.qrepo()
578 qrepo = self.qrepo()
579 if qrepo:
579 if qrepo:
580 qrepo[None].add(f for f in self.added if f not in qrepo[None])
580 qrepo[None].add(f for f in self.added if f not in qrepo[None])
581 self.added = []
581 self.added = []
582
582
583 def removeundo(self, repo):
583 def removeundo(self, repo):
584 undo = repo.sjoin('undo')
584 undo = repo.sjoin('undo')
585 if not os.path.exists(undo):
585 if not os.path.exists(undo):
586 return
586 return
587 try:
587 try:
588 os.unlink(undo)
588 os.unlink(undo)
589 except OSError, inst:
589 except OSError, inst:
590 self.ui.warn(_('error removing undo: %s\n') % str(inst))
590 self.ui.warn(_('error removing undo: %s\n') % str(inst))
591
591
592 def backup(self, repo, files, copy=False):
592 def backup(self, repo, files, copy=False):
593 # backup local changes in --force case
593 # backup local changes in --force case
594 for f in sorted(files):
594 for f in sorted(files):
595 absf = repo.wjoin(f)
595 absf = repo.wjoin(f)
596 if os.path.lexists(absf):
596 if os.path.lexists(absf):
597 self.ui.note(_('saving current version of %s as %s\n') %
597 self.ui.note(_('saving current version of %s as %s\n') %
598 (f, f + '.orig'))
598 (f, f + '.orig'))
599 if copy:
599 if copy:
600 util.copyfile(absf, absf + '.orig')
600 util.copyfile(absf, absf + '.orig')
601 else:
601 else:
602 util.rename(absf, absf + '.orig')
602 util.rename(absf, absf + '.orig')
603
603
604 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
604 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
605 fp=None, changes=None, opts={}):
605 fp=None, changes=None, opts={}):
606 stat = opts.get('stat')
606 stat = opts.get('stat')
607 m = scmutil.match(repo[node1], files, opts)
607 m = scmutil.match(repo[node1], files, opts)
608 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
608 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
609 changes, stat, fp)
609 changes, stat, fp)
610
610
611 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
611 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
612 # first try just applying the patch
612 # first try just applying the patch
613 (err, n) = self.apply(repo, [patch], update_status=False,
613 (err, n) = self.apply(repo, [patch], update_status=False,
614 strict=True, merge=rev)
614 strict=True, merge=rev)
615
615
616 if err == 0:
616 if err == 0:
617 return (err, n)
617 return (err, n)
618
618
619 if n is None:
619 if n is None:
620 raise util.Abort(_("apply failed for patch %s") % patch)
620 raise util.Abort(_("apply failed for patch %s") % patch)
621
621
622 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
622 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
623
623
624 # apply failed, strip away that rev and merge.
624 # apply failed, strip away that rev and merge.
625 hg.clean(repo, head)
625 hg.clean(repo, head)
626 strip(self.ui, repo, [n], update=False, backup='strip')
626 strip(self.ui, repo, [n], update=False, backup='strip')
627
627
628 ctx = repo[rev]
628 ctx = repo[rev]
629 ret = hg.merge(repo, rev)
629 ret = hg.merge(repo, rev)
630 if ret:
630 if ret:
631 raise util.Abort(_("update returned %d") % ret)
631 raise util.Abort(_("update returned %d") % ret)
632 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
632 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
633 if n is None:
633 if n is None:
634 raise util.Abort(_("repo commit failed"))
634 raise util.Abort(_("repo commit failed"))
635 try:
635 try:
636 ph = patchheader(mergeq.join(patch), self.plainmode)
636 ph = patchheader(mergeq.join(patch), self.plainmode)
637 except Exception:
637 except Exception:
638 raise util.Abort(_("unable to read %s") % patch)
638 raise util.Abort(_("unable to read %s") % patch)
639
639
640 diffopts = self.patchopts(diffopts, patch)
640 diffopts = self.patchopts(diffopts, patch)
641 patchf = self.opener(patch, "w")
641 patchf = self.opener(patch, "w")
642 comments = str(ph)
642 comments = str(ph)
643 if comments:
643 if comments:
644 patchf.write(comments)
644 patchf.write(comments)
645 self.printdiff(repo, diffopts, head, n, fp=patchf)
645 self.printdiff(repo, diffopts, head, n, fp=patchf)
646 patchf.close()
646 patchf.close()
647 self.removeundo(repo)
647 self.removeundo(repo)
648 return (0, n)
648 return (0, n)
649
649
650 def qparents(self, repo, rev=None):
650 def qparents(self, repo, rev=None):
651 """return the mq handled parent or p1
651 """return the mq handled parent or p1
652
652
653 In some case where mq get himself in being the parent of a merge the
653 In some case where mq get himself in being the parent of a merge the
654 appropriate parent may be p2.
654 appropriate parent may be p2.
655 (eg: an in progress merge started with mq disabled)
655 (eg: an in progress merge started with mq disabled)
656
656
657 If no parent are managed by mq, p1 is returned.
657 If no parent are managed by mq, p1 is returned.
658 """
658 """
659 if rev is None:
659 if rev is None:
660 (p1, p2) = repo.dirstate.parents()
660 (p1, p2) = repo.dirstate.parents()
661 if p2 == nullid:
661 if p2 == nullid:
662 return p1
662 return p1
663 if not self.applied:
663 if not self.applied:
664 return None
664 return None
665 return self.applied[-1].node
665 return self.applied[-1].node
666 p1, p2 = repo.changelog.parents(rev)
666 p1, p2 = repo.changelog.parents(rev)
667 if p2 != nullid and p2 in [x.node for x in self.applied]:
667 if p2 != nullid and p2 in [x.node for x in self.applied]:
668 return p2
668 return p2
669 return p1
669 return p1
670
670
671 def mergepatch(self, repo, mergeq, series, diffopts):
671 def mergepatch(self, repo, mergeq, series, diffopts):
672 if not self.applied:
672 if not self.applied:
673 # each of the patches merged in will have two parents. This
673 # each of the patches merged in will have two parents. This
674 # can confuse the qrefresh, qdiff, and strip code because it
674 # can confuse the qrefresh, qdiff, and strip code because it
675 # needs to know which parent is actually in the patch queue.
675 # needs to know which parent is actually in the patch queue.
676 # so, we insert a merge marker with only one parent. This way
676 # so, we insert a merge marker with only one parent. This way
677 # the first patch in the queue is never a merge patch
677 # the first patch in the queue is never a merge patch
678 #
678 #
679 pname = ".hg.patches.merge.marker"
679 pname = ".hg.patches.merge.marker"
680 n = newcommit(repo, None, '[mq]: merge marker', force=True)
680 n = newcommit(repo, None, '[mq]: merge marker', force=True)
681 self.removeundo(repo)
681 self.removeundo(repo)
682 self.applied.append(statusentry(n, pname))
682 self.applied.append(statusentry(n, pname))
683 self.applieddirty = True
683 self.applieddirty = True
684
684
685 head = self.qparents(repo)
685 head = self.qparents(repo)
686
686
687 for patch in series:
687 for patch in series:
688 patch = mergeq.lookup(patch, strict=True)
688 patch = mergeq.lookup(patch, strict=True)
689 if not patch:
689 if not patch:
690 self.ui.warn(_("patch %s does not exist\n") % patch)
690 self.ui.warn(_("patch %s does not exist\n") % patch)
691 return (1, None)
691 return (1, None)
692 pushable, reason = self.pushable(patch)
692 pushable, reason = self.pushable(patch)
693 if not pushable:
693 if not pushable:
694 self.explainpushable(patch, all_patches=True)
694 self.explainpushable(patch, all_patches=True)
695 continue
695 continue
696 info = mergeq.isapplied(patch)
696 info = mergeq.isapplied(patch)
697 if not info:
697 if not info:
698 self.ui.warn(_("patch %s is not applied\n") % patch)
698 self.ui.warn(_("patch %s is not applied\n") % patch)
699 return (1, None)
699 return (1, None)
700 rev = info[1]
700 rev = info[1]
701 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
701 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
702 if head:
702 if head:
703 self.applied.append(statusentry(head, patch))
703 self.applied.append(statusentry(head, patch))
704 self.applieddirty = True
704 self.applieddirty = True
705 if err:
705 if err:
706 return (err, head)
706 return (err, head)
707 self.savedirty()
707 self.savedirty()
708 return (0, head)
708 return (0, head)
709
709
710 def patch(self, repo, patchfile):
710 def patch(self, repo, patchfile):
711 '''Apply patchfile to the working directory.
711 '''Apply patchfile to the working directory.
712 patchfile: name of patch file'''
712 patchfile: name of patch file'''
713 files = set()
713 files = set()
714 try:
714 try:
715 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
715 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
716 files=files, eolmode=None)
716 files=files, eolmode=None)
717 return (True, list(files), fuzz)
717 return (True, list(files), fuzz)
718 except Exception, inst:
718 except Exception, inst:
719 self.ui.note(str(inst) + '\n')
719 self.ui.note(str(inst) + '\n')
720 if not self.ui.verbose:
720 if not self.ui.verbose:
721 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
721 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
722 self.ui.traceback()
722 self.ui.traceback()
723 return (False, list(files), False)
723 return (False, list(files), False)
724
724
725 def apply(self, repo, series, list=False, update_status=True,
725 def apply(self, repo, series, list=False, update_status=True,
726 strict=False, patchdir=None, merge=None, all_files=None,
726 strict=False, patchdir=None, merge=None, all_files=None,
727 tobackup=None, keepchanges=False):
727 tobackup=None, keepchanges=False):
728 wlock = lock = tr = None
728 wlock = lock = tr = None
729 try:
729 try:
730 wlock = repo.wlock()
730 wlock = repo.wlock()
731 lock = repo.lock()
731 lock = repo.lock()
732 tr = repo.transaction("qpush")
732 tr = repo.transaction("qpush")
733 try:
733 try:
734 ret = self._apply(repo, series, list, update_status,
734 ret = self._apply(repo, series, list, update_status,
735 strict, patchdir, merge, all_files=all_files,
735 strict, patchdir, merge, all_files=all_files,
736 tobackup=tobackup, keepchanges=keepchanges)
736 tobackup=tobackup, keepchanges=keepchanges)
737 tr.close()
737 tr.close()
738 self.savedirty()
738 self.savedirty()
739 return ret
739 return ret
740 except AbortNoCleanup:
740 except AbortNoCleanup:
741 tr.close()
741 tr.close()
742 self.savedirty()
742 self.savedirty()
743 return 2, repo.dirstate.p1()
743 return 2, repo.dirstate.p1()
744 except: # re-raises
744 except: # re-raises
745 try:
745 try:
746 tr.abort()
746 tr.abort()
747 finally:
747 finally:
748 repo.invalidate()
748 repo.invalidate()
749 repo.dirstate.invalidate()
749 repo.dirstate.invalidate()
750 self.invalidate()
750 self.invalidate()
751 raise
751 raise
752 finally:
752 finally:
753 release(tr, lock, wlock)
753 release(tr, lock, wlock)
754 self.removeundo(repo)
754 self.removeundo(repo)
755
755
756 def _apply(self, repo, series, list=False, update_status=True,
756 def _apply(self, repo, series, list=False, update_status=True,
757 strict=False, patchdir=None, merge=None, all_files=None,
757 strict=False, patchdir=None, merge=None, all_files=None,
758 tobackup=None, keepchanges=False):
758 tobackup=None, keepchanges=False):
759 """returns (error, hash)
759 """returns (error, hash)
760
760
761 error = 1 for unable to read, 2 for patch failed, 3 for patch
761 error = 1 for unable to read, 2 for patch failed, 3 for patch
762 fuzz. tobackup is None or a set of files to backup before they
762 fuzz. tobackup is None or a set of files to backup before they
763 are modified by a patch.
763 are modified by a patch.
764 """
764 """
765 # TODO unify with commands.py
765 # TODO unify with commands.py
766 if not patchdir:
766 if not patchdir:
767 patchdir = self.path
767 patchdir = self.path
768 err = 0
768 err = 0
769 n = None
769 n = None
770 for patchname in series:
770 for patchname in series:
771 pushable, reason = self.pushable(patchname)
771 pushable, reason = self.pushable(patchname)
772 if not pushable:
772 if not pushable:
773 self.explainpushable(patchname, all_patches=True)
773 self.explainpushable(patchname, all_patches=True)
774 continue
774 continue
775 self.ui.status(_("applying %s\n") % patchname)
775 self.ui.status(_("applying %s\n") % patchname)
776 pf = os.path.join(patchdir, patchname)
776 pf = os.path.join(patchdir, patchname)
777
777
778 try:
778 try:
779 ph = patchheader(self.join(patchname), self.plainmode)
779 ph = patchheader(self.join(patchname), self.plainmode)
780 except IOError:
780 except IOError:
781 self.ui.warn(_("unable to read %s\n") % patchname)
781 self.ui.warn(_("unable to read %s\n") % patchname)
782 err = 1
782 err = 1
783 break
783 break
784
784
785 message = ph.message
785 message = ph.message
786 if not message:
786 if not message:
787 # The commit message should not be translated
787 # The commit message should not be translated
788 message = "imported patch %s\n" % patchname
788 message = "imported patch %s\n" % patchname
789 else:
789 else:
790 if list:
790 if list:
791 # The commit message should not be translated
791 # The commit message should not be translated
792 message.append("\nimported patch %s" % patchname)
792 message.append("\nimported patch %s" % patchname)
793 message = '\n'.join(message)
793 message = '\n'.join(message)
794
794
795 if ph.haspatch:
795 if ph.haspatch:
796 if tobackup:
796 if tobackup:
797 touched = patchmod.changedfiles(self.ui, repo, pf)
797 touched = patchmod.changedfiles(self.ui, repo, pf)
798 touched = set(touched) & tobackup
798 touched = set(touched) & tobackup
799 if touched and keepchanges:
799 if touched and keepchanges:
800 raise AbortNoCleanup(
800 raise AbortNoCleanup(
801 _("local changes found, refresh first"))
801 _("local changes found, refresh first"))
802 self.backup(repo, touched, copy=True)
802 self.backup(repo, touched, copy=True)
803 tobackup = tobackup - touched
803 tobackup = tobackup - touched
804 (patcherr, files, fuzz) = self.patch(repo, pf)
804 (patcherr, files, fuzz) = self.patch(repo, pf)
805 if all_files is not None:
805 if all_files is not None:
806 all_files.update(files)
806 all_files.update(files)
807 patcherr = not patcherr
807 patcherr = not patcherr
808 else:
808 else:
809 self.ui.warn(_("patch %s is empty\n") % patchname)
809 self.ui.warn(_("patch %s is empty\n") % patchname)
810 patcherr, files, fuzz = 0, [], 0
810 patcherr, files, fuzz = 0, [], 0
811
811
812 if merge and files:
812 if merge and files:
813 # Mark as removed/merged and update dirstate parent info
813 # Mark as removed/merged and update dirstate parent info
814 removed = []
814 removed = []
815 merged = []
815 merged = []
816 for f in files:
816 for f in files:
817 if os.path.lexists(repo.wjoin(f)):
817 if os.path.lexists(repo.wjoin(f)):
818 merged.append(f)
818 merged.append(f)
819 else:
819 else:
820 removed.append(f)
820 removed.append(f)
821 for f in removed:
821 for f in removed:
822 repo.dirstate.remove(f)
822 repo.dirstate.remove(f)
823 for f in merged:
823 for f in merged:
824 repo.dirstate.merge(f)
824 repo.dirstate.merge(f)
825 p1, p2 = repo.dirstate.parents()
825 p1, p2 = repo.dirstate.parents()
826 repo.setparents(p1, merge)
826 repo.setparents(p1, merge)
827
827
828 if all_files and '.hgsubstate' in all_files:
828 if all_files and '.hgsubstate' in all_files:
829 wctx = repo['.']
829 wctx = repo['.']
830 mctx = actx = repo[None]
830 mctx = actx = repo[None]
831 overwrite = False
831 overwrite = False
832 mergedsubstate = subrepo.submerge(repo, wctx, mctx, actx,
832 mergedsubstate = subrepo.submerge(repo, wctx, mctx, actx,
833 overwrite)
833 overwrite)
834 files += mergedsubstate.keys()
834 files += mergedsubstate.keys()
835
835
836 match = scmutil.matchfiles(repo, files or [])
836 match = scmutil.matchfiles(repo, files or [])
837 oldtip = repo['tip']
837 oldtip = repo['tip']
838 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
838 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
839 force=True)
839 force=True)
840 if repo['tip'] == oldtip:
840 if repo['tip'] == oldtip:
841 raise util.Abort(_("qpush exactly duplicates child changeset"))
841 raise util.Abort(_("qpush exactly duplicates child changeset"))
842 if n is None:
842 if n is None:
843 raise util.Abort(_("repository commit failed"))
843 raise util.Abort(_("repository commit failed"))
844
844
845 if update_status:
845 if update_status:
846 self.applied.append(statusentry(n, patchname))
846 self.applied.append(statusentry(n, patchname))
847
847
848 if patcherr:
848 if patcherr:
849 self.ui.warn(_("patch failed, rejects left in working dir\n"))
849 self.ui.warn(_("patch failed, rejects left in working dir\n"))
850 err = 2
850 err = 2
851 break
851 break
852
852
853 if fuzz and strict:
853 if fuzz and strict:
854 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
854 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
855 err = 3
855 err = 3
856 break
856 break
857 return (err, n)
857 return (err, n)
858
858
859 def _cleanup(self, patches, numrevs, keep=False):
859 def _cleanup(self, patches, numrevs, keep=False):
860 if not keep:
860 if not keep:
861 r = self.qrepo()
861 r = self.qrepo()
862 if r:
862 if r:
863 r[None].forget(patches)
863 r[None].forget(patches)
864 for p in patches:
864 for p in patches:
865 try:
865 try:
866 os.unlink(self.join(p))
866 os.unlink(self.join(p))
867 except OSError, inst:
867 except OSError, inst:
868 if inst.errno != errno.ENOENT:
868 if inst.errno != errno.ENOENT:
869 raise
869 raise
870
870
871 qfinished = []
871 qfinished = []
872 if numrevs:
872 if numrevs:
873 qfinished = self.applied[:numrevs]
873 qfinished = self.applied[:numrevs]
874 del self.applied[:numrevs]
874 del self.applied[:numrevs]
875 self.applieddirty = True
875 self.applieddirty = True
876
876
877 unknown = []
877 unknown = []
878
878
879 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
879 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
880 reverse=True):
880 reverse=True):
881 if i is not None:
881 if i is not None:
882 del self.fullseries[i]
882 del self.fullseries[i]
883 else:
883 else:
884 unknown.append(p)
884 unknown.append(p)
885
885
886 if unknown:
886 if unknown:
887 if numrevs:
887 if numrevs:
888 rev = dict((entry.name, entry.node) for entry in qfinished)
888 rev = dict((entry.name, entry.node) for entry in qfinished)
889 for p in unknown:
889 for p in unknown:
890 msg = _('revision %s refers to unknown patches: %s\n')
890 msg = _('revision %s refers to unknown patches: %s\n')
891 self.ui.warn(msg % (short(rev[p]), p))
891 self.ui.warn(msg % (short(rev[p]), p))
892 else:
892 else:
893 msg = _('unknown patches: %s\n')
893 msg = _('unknown patches: %s\n')
894 raise util.Abort(''.join(msg % p for p in unknown))
894 raise util.Abort(''.join(msg % p for p in unknown))
895
895
896 self.parseseries()
896 self.parseseries()
897 self.seriesdirty = True
897 self.seriesdirty = True
898 return [entry.node for entry in qfinished]
898 return [entry.node for entry in qfinished]
899
899
900 def _revpatches(self, repo, revs):
900 def _revpatches(self, repo, revs):
901 firstrev = repo[self.applied[0].node].rev()
901 firstrev = repo[self.applied[0].node].rev()
902 patches = []
902 patches = []
903 for i, rev in enumerate(revs):
903 for i, rev in enumerate(revs):
904
904
905 if rev < firstrev:
905 if rev < firstrev:
906 raise util.Abort(_('revision %d is not managed') % rev)
906 raise util.Abort(_('revision %d is not managed') % rev)
907
907
908 ctx = repo[rev]
908 ctx = repo[rev]
909 base = self.applied[i].node
909 base = self.applied[i].node
910 if ctx.node() != base:
910 if ctx.node() != base:
911 msg = _('cannot delete revision %d above applied patches')
911 msg = _('cannot delete revision %d above applied patches')
912 raise util.Abort(msg % rev)
912 raise util.Abort(msg % rev)
913
913
914 patch = self.applied[i].name
914 patch = self.applied[i].name
915 for fmt in ('[mq]: %s', 'imported patch %s'):
915 for fmt in ('[mq]: %s', 'imported patch %s'):
916 if ctx.description() == fmt % patch:
916 if ctx.description() == fmt % patch:
917 msg = _('patch %s finalized without changeset message\n')
917 msg = _('patch %s finalized without changeset message\n')
918 repo.ui.status(msg % patch)
918 repo.ui.status(msg % patch)
919 break
919 break
920
920
921 patches.append(patch)
921 patches.append(patch)
922 return patches
922 return patches
923
923
924 def finish(self, repo, revs):
924 def finish(self, repo, revs):
925 # Manually trigger phase computation to ensure phasedefaults is
925 # Manually trigger phase computation to ensure phasedefaults is
926 # executed before we remove the patches.
926 # executed before we remove the patches.
927 repo._phasecache
927 repo._phasecache
928 patches = self._revpatches(repo, sorted(revs))
928 patches = self._revpatches(repo, sorted(revs))
929 qfinished = self._cleanup(patches, len(patches))
929 qfinished = self._cleanup(patches, len(patches))
930 if qfinished and repo.ui.configbool('mq', 'secret', False):
930 if qfinished and repo.ui.configbool('mq', 'secret', False):
931 # only use this logic when the secret option is added
931 # only use this logic when the secret option is added
932 oldqbase = repo[qfinished[0]]
932 oldqbase = repo[qfinished[0]]
933 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
933 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
934 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
934 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
935 phases.advanceboundary(repo, tphase, qfinished)
935 phases.advanceboundary(repo, tphase, qfinished)
936
936
937 def delete(self, repo, patches, opts):
937 def delete(self, repo, patches, opts):
938 if not patches and not opts.get('rev'):
938 if not patches and not opts.get('rev'):
939 raise util.Abort(_('qdelete requires at least one revision or '
939 raise util.Abort(_('qdelete requires at least one revision or '
940 'patch name'))
940 'patch name'))
941
941
942 realpatches = []
942 realpatches = []
943 for patch in patches:
943 for patch in patches:
944 patch = self.lookup(patch, strict=True)
944 patch = self.lookup(patch, strict=True)
945 info = self.isapplied(patch)
945 info = self.isapplied(patch)
946 if info:
946 if info:
947 raise util.Abort(_("cannot delete applied patch %s") % patch)
947 raise util.Abort(_("cannot delete applied patch %s") % patch)
948 if patch not in self.series:
948 if patch not in self.series:
949 raise util.Abort(_("patch %s not in series file") % patch)
949 raise util.Abort(_("patch %s not in series file") % patch)
950 if patch not in realpatches:
950 if patch not in realpatches:
951 realpatches.append(patch)
951 realpatches.append(patch)
952
952
953 numrevs = 0
953 numrevs = 0
954 if opts.get('rev'):
954 if opts.get('rev'):
955 if not self.applied:
955 if not self.applied:
956 raise util.Abort(_('no patches applied'))
956 raise util.Abort(_('no patches applied'))
957 revs = scmutil.revrange(repo, opts.get('rev'))
957 revs = scmutil.revrange(repo, opts.get('rev'))
958 if len(revs) > 1 and revs[0] > revs[1]:
958 if len(revs) > 1 and revs[0] > revs[1]:
959 revs.reverse()
959 revs.reverse()
960 revpatches = self._revpatches(repo, revs)
960 revpatches = self._revpatches(repo, revs)
961 realpatches += revpatches
961 realpatches += revpatches
962 numrevs = len(revpatches)
962 numrevs = len(revpatches)
963
963
964 self._cleanup(realpatches, numrevs, opts.get('keep'))
964 self._cleanup(realpatches, numrevs, opts.get('keep'))
965
965
966 def checktoppatch(self, repo):
966 def checktoppatch(self, repo):
967 '''check that working directory is at qtip'''
967 '''check that working directory is at qtip'''
968 if self.applied:
968 if self.applied:
969 top = self.applied[-1].node
969 top = self.applied[-1].node
970 patch = self.applied[-1].name
970 patch = self.applied[-1].name
971 if repo.dirstate.p1() != top:
971 if repo.dirstate.p1() != top:
972 raise util.Abort(_("working directory revision is not qtip"))
972 raise util.Abort(_("working directory revision is not qtip"))
973 return top, patch
973 return top, patch
974 return None, None
974 return None, None
975
975
976 def putsubstate2changes(self, substatestate, changes):
976 def putsubstate2changes(self, substatestate, changes):
977 for files in changes[:3]:
977 for files in changes[:3]:
978 if '.hgsubstate' in files:
978 if '.hgsubstate' in files:
979 return # already listed up
979 return # already listed up
980 # not yet listed up
980 # not yet listed up
981 if substatestate in 'a?':
981 if substatestate in 'a?':
982 changes[1].append('.hgsubstate')
982 changes[1].append('.hgsubstate')
983 elif substatestate in 'r':
983 elif substatestate in 'r':
984 changes[2].append('.hgsubstate')
984 changes[2].append('.hgsubstate')
985 else: # modified
985 else: # modified
986 changes[0].append('.hgsubstate')
986 changes[0].append('.hgsubstate')
987
987
988 def checklocalchanges(self, repo, force=False, refresh=True):
988 def checklocalchanges(self, repo, force=False, refresh=True):
989 excsuffix = ''
989 excsuffix = ''
990 if refresh:
990 if refresh:
991 excsuffix = ', refresh first'
991 excsuffix = ', refresh first'
992 # plain versions for i18n tool to detect them
992 # plain versions for i18n tool to detect them
993 _("local changes found, refresh first")
993 _("local changes found, refresh first")
994 _("local changed subrepos found, refresh first")
994 _("local changed subrepos found, refresh first")
995 return checklocalchanges(repo, force, excsuffix)
995 return checklocalchanges(repo, force, excsuffix)
996
996
997 _reserved = ('series', 'status', 'guards', '.', '..')
997 _reserved = ('series', 'status', 'guards', '.', '..')
998 def checkreservedname(self, name):
998 def checkreservedname(self, name):
999 if name in self._reserved:
999 if name in self._reserved:
1000 raise util.Abort(_('"%s" cannot be used as the name of a patch')
1000 raise util.Abort(_('"%s" cannot be used as the name of a patch')
1001 % name)
1001 % name)
1002 for prefix in ('.hg', '.mq'):
1002 for prefix in ('.hg', '.mq'):
1003 if name.startswith(prefix):
1003 if name.startswith(prefix):
1004 raise util.Abort(_('patch name cannot begin with "%s"')
1004 raise util.Abort(_('patch name cannot begin with "%s"')
1005 % prefix)
1005 % prefix)
1006 for c in ('#', ':'):
1006 for c in ('#', ':'):
1007 if c in name:
1007 if c in name:
1008 raise util.Abort(_('"%s" cannot be used in the name of a patch')
1008 raise util.Abort(_('"%s" cannot be used in the name of a patch')
1009 % c)
1009 % c)
1010
1010
1011 def checkpatchname(self, name, force=False):
1011 def checkpatchname(self, name, force=False):
1012 self.checkreservedname(name)
1012 self.checkreservedname(name)
1013 if not force and os.path.exists(self.join(name)):
1013 if not force and os.path.exists(self.join(name)):
1014 if os.path.isdir(self.join(name)):
1014 if os.path.isdir(self.join(name)):
1015 raise util.Abort(_('"%s" already exists as a directory')
1015 raise util.Abort(_('"%s" already exists as a directory')
1016 % name)
1016 % name)
1017 else:
1017 else:
1018 raise util.Abort(_('patch "%s" already exists') % name)
1018 raise util.Abort(_('patch "%s" already exists') % name)
1019
1019
1020 def checkkeepchanges(self, keepchanges, force):
1020 def checkkeepchanges(self, keepchanges, force):
1021 if force and keepchanges:
1021 if force and keepchanges:
1022 raise util.Abort(_('cannot use both --force and --keep-changes'))
1022 raise util.Abort(_('cannot use both --force and --keep-changes'))
1023
1023
1024 def new(self, repo, patchfn, *pats, **opts):
1024 def new(self, repo, patchfn, *pats, **opts):
1025 """options:
1025 """options:
1026 msg: a string or a no-argument function returning a string
1026 msg: a string or a no-argument function returning a string
1027 """
1027 """
1028 msg = opts.get('msg')
1028 msg = opts.get('msg')
1029 user = opts.get('user')
1029 user = opts.get('user')
1030 date = opts.get('date')
1030 date = opts.get('date')
1031 if date:
1031 if date:
1032 date = util.parsedate(date)
1032 date = util.parsedate(date)
1033 diffopts = self.diffopts({'git': opts.get('git')})
1033 diffopts = self.diffopts({'git': opts.get('git')})
1034 if opts.get('checkname', True):
1034 if opts.get('checkname', True):
1035 self.checkpatchname(patchfn)
1035 self.checkpatchname(patchfn)
1036 inclsubs = checksubstate(repo)
1036 inclsubs = checksubstate(repo)
1037 if inclsubs:
1037 if inclsubs:
1038 inclsubs.append('.hgsubstate')
1038 inclsubs.append('.hgsubstate')
1039 substatestate = repo.dirstate['.hgsubstate']
1039 substatestate = repo.dirstate['.hgsubstate']
1040 if opts.get('include') or opts.get('exclude') or pats:
1040 if opts.get('include') or opts.get('exclude') or pats:
1041 if inclsubs:
1041 if inclsubs:
1042 pats = list(pats or []) + inclsubs
1042 pats = list(pats or []) + inclsubs
1043 match = scmutil.match(repo[None], pats, opts)
1043 match = scmutil.match(repo[None], pats, opts)
1044 # detect missing files in pats
1044 # detect missing files in pats
1045 def badfn(f, msg):
1045 def badfn(f, msg):
1046 if f != '.hgsubstate': # .hgsubstate is auto-created
1046 if f != '.hgsubstate': # .hgsubstate is auto-created
1047 raise util.Abort('%s: %s' % (f, msg))
1047 raise util.Abort('%s: %s' % (f, msg))
1048 match.bad = badfn
1048 match.bad = badfn
1049 changes = repo.status(match=match)
1049 changes = repo.status(match=match)
1050 m, a, r, d = changes[:4]
1050 m, a, r, d = changes[:4]
1051 else:
1051 else:
1052 changes = self.checklocalchanges(repo, force=True)
1052 changes = self.checklocalchanges(repo, force=True)
1053 m, a, r, d = changes
1053 m, a, r, d = changes
1054 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
1054 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
1055 if len(repo[None].parents()) > 1:
1055 if len(repo[None].parents()) > 1:
1056 raise util.Abort(_('cannot manage merge changesets'))
1056 raise util.Abort(_('cannot manage merge changesets'))
1057 commitfiles = m + a + r
1057 commitfiles = m + a + r
1058 self.checktoppatch(repo)
1058 self.checktoppatch(repo)
1059 insert = self.fullseriesend()
1059 insert = self.fullseriesend()
1060 wlock = repo.wlock()
1060 wlock = repo.wlock()
1061 try:
1061 try:
1062 try:
1062 try:
1063 # if patch file write fails, abort early
1063 # if patch file write fails, abort early
1064 p = self.opener(patchfn, "w")
1064 p = self.opener(patchfn, "w")
1065 except IOError, e:
1065 except IOError, e:
1066 raise util.Abort(_('cannot write patch "%s": %s')
1066 raise util.Abort(_('cannot write patch "%s": %s')
1067 % (patchfn, e.strerror))
1067 % (patchfn, e.strerror))
1068 try:
1068 try:
1069 if self.plainmode:
1069 if self.plainmode:
1070 if user:
1070 if user:
1071 p.write("From: " + user + "\n")
1071 p.write("From: " + user + "\n")
1072 if not date:
1072 if not date:
1073 p.write("\n")
1073 p.write("\n")
1074 if date:
1074 if date:
1075 p.write("Date: %d %d\n\n" % date)
1075 p.write("Date: %d %d\n\n" % date)
1076 else:
1076 else:
1077 p.write("# HG changeset patch\n")
1077 p.write("# HG changeset patch\n")
1078 p.write("# Parent "
1078 p.write("# Parent "
1079 + hex(repo[None].p1().node()) + "\n")
1079 + hex(repo[None].p1().node()) + "\n")
1080 if user:
1080 if user:
1081 p.write("# User " + user + "\n")
1081 p.write("# User " + user + "\n")
1082 if date:
1082 if date:
1083 p.write("# Date %s %s\n\n" % date)
1083 p.write("# Date %s %s\n\n" % date)
1084 if util.safehasattr(msg, '__call__'):
1084 if util.safehasattr(msg, '__call__'):
1085 msg = msg()
1085 msg = msg()
1086 repo.savecommitmessage(msg)
1086 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
1087 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
1087 n = newcommit(repo, None, commitmsg, user, date, match=match,
1088 n = newcommit(repo, None, commitmsg, user, date, match=match,
1088 force=True)
1089 force=True)
1089 if n is None:
1090 if n is None:
1090 raise util.Abort(_("repo commit failed"))
1091 raise util.Abort(_("repo commit failed"))
1091 try:
1092 try:
1092 self.fullseries[insert:insert] = [patchfn]
1093 self.fullseries[insert:insert] = [patchfn]
1093 self.applied.append(statusentry(n, patchfn))
1094 self.applied.append(statusentry(n, patchfn))
1094 self.parseseries()
1095 self.parseseries()
1095 self.seriesdirty = True
1096 self.seriesdirty = True
1096 self.applieddirty = True
1097 self.applieddirty = True
1097 if msg:
1098 if msg:
1098 msg = msg + "\n\n"
1099 msg = msg + "\n\n"
1099 p.write(msg)
1100 p.write(msg)
1100 if commitfiles:
1101 if commitfiles:
1101 parent = self.qparents(repo, n)
1102 parent = self.qparents(repo, n)
1102 if inclsubs:
1103 if inclsubs:
1103 self.putsubstate2changes(substatestate, changes)
1104 self.putsubstate2changes(substatestate, changes)
1104 chunks = patchmod.diff(repo, node1=parent, node2=n,
1105 chunks = patchmod.diff(repo, node1=parent, node2=n,
1105 changes=changes, opts=diffopts)
1106 changes=changes, opts=diffopts)
1106 for chunk in chunks:
1107 for chunk in chunks:
1107 p.write(chunk)
1108 p.write(chunk)
1108 p.close()
1109 p.close()
1109 r = self.qrepo()
1110 r = self.qrepo()
1110 if r:
1111 if r:
1111 r[None].add([patchfn])
1112 r[None].add([patchfn])
1112 except: # re-raises
1113 except: # re-raises
1113 repo.rollback()
1114 repo.rollback()
1114 raise
1115 raise
1115 except Exception:
1116 except Exception:
1116 patchpath = self.join(patchfn)
1117 patchpath = self.join(patchfn)
1117 try:
1118 try:
1118 os.unlink(patchpath)
1119 os.unlink(patchpath)
1119 except OSError:
1120 except OSError:
1120 self.ui.warn(_('error unlinking %s\n') % patchpath)
1121 self.ui.warn(_('error unlinking %s\n') % patchpath)
1121 raise
1122 raise
1122 self.removeundo(repo)
1123 self.removeundo(repo)
1123 finally:
1124 finally:
1124 release(wlock)
1125 release(wlock)
1125
1126
1126 def isapplied(self, patch):
1127 def isapplied(self, patch):
1127 """returns (index, rev, patch)"""
1128 """returns (index, rev, patch)"""
1128 for i, a in enumerate(self.applied):
1129 for i, a in enumerate(self.applied):
1129 if a.name == patch:
1130 if a.name == patch:
1130 return (i, a.node, a.name)
1131 return (i, a.node, a.name)
1131 return None
1132 return None
1132
1133
1133 # if the exact patch name does not exist, we try a few
1134 # if the exact patch name does not exist, we try a few
1134 # variations. If strict is passed, we try only #1
1135 # variations. If strict is passed, we try only #1
1135 #
1136 #
1136 # 1) a number (as string) to indicate an offset in the series file
1137 # 1) a number (as string) to indicate an offset in the series file
1137 # 2) a unique substring of the patch name was given
1138 # 2) a unique substring of the patch name was given
1138 # 3) patchname[-+]num to indicate an offset in the series file
1139 # 3) patchname[-+]num to indicate an offset in the series file
1139 def lookup(self, patch, strict=False):
1140 def lookup(self, patch, strict=False):
1140 def partialname(s):
1141 def partialname(s):
1141 if s in self.series:
1142 if s in self.series:
1142 return s
1143 return s
1143 matches = [x for x in self.series if s in x]
1144 matches = [x for x in self.series if s in x]
1144 if len(matches) > 1:
1145 if len(matches) > 1:
1145 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1146 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1146 for m in matches:
1147 for m in matches:
1147 self.ui.warn(' %s\n' % m)
1148 self.ui.warn(' %s\n' % m)
1148 return None
1149 return None
1149 if matches:
1150 if matches:
1150 return matches[0]
1151 return matches[0]
1151 if self.series and self.applied:
1152 if self.series and self.applied:
1152 if s == 'qtip':
1153 if s == 'qtip':
1153 return self.series[self.seriesend(True) - 1]
1154 return self.series[self.seriesend(True) - 1]
1154 if s == 'qbase':
1155 if s == 'qbase':
1155 return self.series[0]
1156 return self.series[0]
1156 return None
1157 return None
1157
1158
1158 if patch in self.series:
1159 if patch in self.series:
1159 return patch
1160 return patch
1160
1161
1161 if not os.path.isfile(self.join(patch)):
1162 if not os.path.isfile(self.join(patch)):
1162 try:
1163 try:
1163 sno = int(patch)
1164 sno = int(patch)
1164 except (ValueError, OverflowError):
1165 except (ValueError, OverflowError):
1165 pass
1166 pass
1166 else:
1167 else:
1167 if -len(self.series) <= sno < len(self.series):
1168 if -len(self.series) <= sno < len(self.series):
1168 return self.series[sno]
1169 return self.series[sno]
1169
1170
1170 if not strict:
1171 if not strict:
1171 res = partialname(patch)
1172 res = partialname(patch)
1172 if res:
1173 if res:
1173 return res
1174 return res
1174 minus = patch.rfind('-')
1175 minus = patch.rfind('-')
1175 if minus >= 0:
1176 if minus >= 0:
1176 res = partialname(patch[:minus])
1177 res = partialname(patch[:minus])
1177 if res:
1178 if res:
1178 i = self.series.index(res)
1179 i = self.series.index(res)
1179 try:
1180 try:
1180 off = int(patch[minus + 1:] or 1)
1181 off = int(patch[minus + 1:] or 1)
1181 except (ValueError, OverflowError):
1182 except (ValueError, OverflowError):
1182 pass
1183 pass
1183 else:
1184 else:
1184 if i - off >= 0:
1185 if i - off >= 0:
1185 return self.series[i - off]
1186 return self.series[i - off]
1186 plus = patch.rfind('+')
1187 plus = patch.rfind('+')
1187 if plus >= 0:
1188 if plus >= 0:
1188 res = partialname(patch[:plus])
1189 res = partialname(patch[:plus])
1189 if res:
1190 if res:
1190 i = self.series.index(res)
1191 i = self.series.index(res)
1191 try:
1192 try:
1192 off = int(patch[plus + 1:] or 1)
1193 off = int(patch[plus + 1:] or 1)
1193 except (ValueError, OverflowError):
1194 except (ValueError, OverflowError):
1194 pass
1195 pass
1195 else:
1196 else:
1196 if i + off < len(self.series):
1197 if i + off < len(self.series):
1197 return self.series[i + off]
1198 return self.series[i + off]
1198 raise util.Abort(_("patch %s not in series") % patch)
1199 raise util.Abort(_("patch %s not in series") % patch)
1199
1200
1200 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1201 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1201 all=False, move=False, exact=False, nobackup=False,
1202 all=False, move=False, exact=False, nobackup=False,
1202 keepchanges=False):
1203 keepchanges=False):
1203 self.checkkeepchanges(keepchanges, force)
1204 self.checkkeepchanges(keepchanges, force)
1204 diffopts = self.diffopts()
1205 diffopts = self.diffopts()
1205 wlock = repo.wlock()
1206 wlock = repo.wlock()
1206 try:
1207 try:
1207 heads = []
1208 heads = []
1208 for hs in repo.branchmap().itervalues():
1209 for hs in repo.branchmap().itervalues():
1209 heads.extend(hs)
1210 heads.extend(hs)
1210 if not heads:
1211 if not heads:
1211 heads = [nullid]
1212 heads = [nullid]
1212 if repo.dirstate.p1() not in heads and not exact:
1213 if repo.dirstate.p1() not in heads and not exact:
1213 self.ui.status(_("(working directory not at a head)\n"))
1214 self.ui.status(_("(working directory not at a head)\n"))
1214
1215
1215 if not self.series:
1216 if not self.series:
1216 self.ui.warn(_('no patches in series\n'))
1217 self.ui.warn(_('no patches in series\n'))
1217 return 0
1218 return 0
1218
1219
1219 # Suppose our series file is: A B C and the current 'top'
1220 # Suppose our series file is: A B C and the current 'top'
1220 # patch is B. qpush C should be performed (moving forward)
1221 # patch is B. qpush C should be performed (moving forward)
1221 # qpush B is a NOP (no change) qpush A is an error (can't
1222 # qpush B is a NOP (no change) qpush A is an error (can't
1222 # go backwards with qpush)
1223 # go backwards with qpush)
1223 if patch:
1224 if patch:
1224 patch = self.lookup(patch)
1225 patch = self.lookup(patch)
1225 info = self.isapplied(patch)
1226 info = self.isapplied(patch)
1226 if info and info[0] >= len(self.applied) - 1:
1227 if info and info[0] >= len(self.applied) - 1:
1227 self.ui.warn(
1228 self.ui.warn(
1228 _('qpush: %s is already at the top\n') % patch)
1229 _('qpush: %s is already at the top\n') % patch)
1229 return 0
1230 return 0
1230
1231
1231 pushable, reason = self.pushable(patch)
1232 pushable, reason = self.pushable(patch)
1232 if pushable:
1233 if pushable:
1233 if self.series.index(patch) < self.seriesend():
1234 if self.series.index(patch) < self.seriesend():
1234 raise util.Abort(
1235 raise util.Abort(
1235 _("cannot push to a previous patch: %s") % patch)
1236 _("cannot push to a previous patch: %s") % patch)
1236 else:
1237 else:
1237 if reason:
1238 if reason:
1238 reason = _('guarded by %s') % reason
1239 reason = _('guarded by %s') % reason
1239 else:
1240 else:
1240 reason = _('no matching guards')
1241 reason = _('no matching guards')
1241 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1242 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1242 return 1
1243 return 1
1243 elif all:
1244 elif all:
1244 patch = self.series[-1]
1245 patch = self.series[-1]
1245 if self.isapplied(patch):
1246 if self.isapplied(patch):
1246 self.ui.warn(_('all patches are currently applied\n'))
1247 self.ui.warn(_('all patches are currently applied\n'))
1247 return 0
1248 return 0
1248
1249
1249 # Following the above example, starting at 'top' of B:
1250 # Following the above example, starting at 'top' of B:
1250 # qpush should be performed (pushes C), but a subsequent
1251 # qpush should be performed (pushes C), but a subsequent
1251 # qpush without an argument is an error (nothing to
1252 # qpush without an argument is an error (nothing to
1252 # apply). This allows a loop of "...while hg qpush..." to
1253 # apply). This allows a loop of "...while hg qpush..." to
1253 # work as it detects an error when done
1254 # work as it detects an error when done
1254 start = self.seriesend()
1255 start = self.seriesend()
1255 if start == len(self.series):
1256 if start == len(self.series):
1256 self.ui.warn(_('patch series already fully applied\n'))
1257 self.ui.warn(_('patch series already fully applied\n'))
1257 return 1
1258 return 1
1258 if not force and not keepchanges:
1259 if not force and not keepchanges:
1259 self.checklocalchanges(repo, refresh=self.applied)
1260 self.checklocalchanges(repo, refresh=self.applied)
1260
1261
1261 if exact:
1262 if exact:
1262 if keepchanges:
1263 if keepchanges:
1263 raise util.Abort(
1264 raise util.Abort(
1264 _("cannot use --exact and --keep-changes together"))
1265 _("cannot use --exact and --keep-changes together"))
1265 if move:
1266 if move:
1266 raise util.Abort(_('cannot use --exact and --move '
1267 raise util.Abort(_('cannot use --exact and --move '
1267 'together'))
1268 'together'))
1268 if self.applied:
1269 if self.applied:
1269 raise util.Abort(_('cannot push --exact with applied '
1270 raise util.Abort(_('cannot push --exact with applied '
1270 'patches'))
1271 'patches'))
1271 root = self.series[start]
1272 root = self.series[start]
1272 target = patchheader(self.join(root), self.plainmode).parent
1273 target = patchheader(self.join(root), self.plainmode).parent
1273 if not target:
1274 if not target:
1274 raise util.Abort(
1275 raise util.Abort(
1275 _("%s does not have a parent recorded") % root)
1276 _("%s does not have a parent recorded") % root)
1276 if not repo[target] == repo['.']:
1277 if not repo[target] == repo['.']:
1277 hg.update(repo, target)
1278 hg.update(repo, target)
1278
1279
1279 if move:
1280 if move:
1280 if not patch:
1281 if not patch:
1281 raise util.Abort(_("please specify the patch to move"))
1282 raise util.Abort(_("please specify the patch to move"))
1282 for fullstart, rpn in enumerate(self.fullseries):
1283 for fullstart, rpn in enumerate(self.fullseries):
1283 # strip markers for patch guards
1284 # strip markers for patch guards
1284 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1285 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1285 break
1286 break
1286 for i, rpn in enumerate(self.fullseries[fullstart:]):
1287 for i, rpn in enumerate(self.fullseries[fullstart:]):
1287 # strip markers for patch guards
1288 # strip markers for patch guards
1288 if self.guard_re.split(rpn, 1)[0] == patch:
1289 if self.guard_re.split(rpn, 1)[0] == patch:
1289 break
1290 break
1290 index = fullstart + i
1291 index = fullstart + i
1291 assert index < len(self.fullseries)
1292 assert index < len(self.fullseries)
1292 fullpatch = self.fullseries[index]
1293 fullpatch = self.fullseries[index]
1293 del self.fullseries[index]
1294 del self.fullseries[index]
1294 self.fullseries.insert(fullstart, fullpatch)
1295 self.fullseries.insert(fullstart, fullpatch)
1295 self.parseseries()
1296 self.parseseries()
1296 self.seriesdirty = True
1297 self.seriesdirty = True
1297
1298
1298 self.applieddirty = True
1299 self.applieddirty = True
1299 if start > 0:
1300 if start > 0:
1300 self.checktoppatch(repo)
1301 self.checktoppatch(repo)
1301 if not patch:
1302 if not patch:
1302 patch = self.series[start]
1303 patch = self.series[start]
1303 end = start + 1
1304 end = start + 1
1304 else:
1305 else:
1305 end = self.series.index(patch, start) + 1
1306 end = self.series.index(patch, start) + 1
1306
1307
1307 tobackup = set()
1308 tobackup = set()
1308 if (not nobackup and force) or keepchanges:
1309 if (not nobackup and force) or keepchanges:
1309 m, a, r, d = self.checklocalchanges(repo, force=True)
1310 m, a, r, d = self.checklocalchanges(repo, force=True)
1310 if keepchanges:
1311 if keepchanges:
1311 tobackup.update(m + a + r + d)
1312 tobackup.update(m + a + r + d)
1312 else:
1313 else:
1313 tobackup.update(m + a)
1314 tobackup.update(m + a)
1314
1315
1315 s = self.series[start:end]
1316 s = self.series[start:end]
1316 all_files = set()
1317 all_files = set()
1317 try:
1318 try:
1318 if mergeq:
1319 if mergeq:
1319 ret = self.mergepatch(repo, mergeq, s, diffopts)
1320 ret = self.mergepatch(repo, mergeq, s, diffopts)
1320 else:
1321 else:
1321 ret = self.apply(repo, s, list, all_files=all_files,
1322 ret = self.apply(repo, s, list, all_files=all_files,
1322 tobackup=tobackup, keepchanges=keepchanges)
1323 tobackup=tobackup, keepchanges=keepchanges)
1323 except: # re-raises
1324 except: # re-raises
1324 self.ui.warn(_('cleaning up working directory...'))
1325 self.ui.warn(_('cleaning up working directory...'))
1325 node = repo.dirstate.p1()
1326 node = repo.dirstate.p1()
1326 hg.revert(repo, node, None)
1327 hg.revert(repo, node, None)
1327 # only remove unknown files that we know we touched or
1328 # only remove unknown files that we know we touched or
1328 # created while patching
1329 # created while patching
1329 for f in all_files:
1330 for f in all_files:
1330 if f not in repo.dirstate:
1331 if f not in repo.dirstate:
1331 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1332 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1332 self.ui.warn(_('done\n'))
1333 self.ui.warn(_('done\n'))
1333 raise
1334 raise
1334
1335
1335 if not self.applied:
1336 if not self.applied:
1336 return ret[0]
1337 return ret[0]
1337 top = self.applied[-1].name
1338 top = self.applied[-1].name
1338 if ret[0] and ret[0] > 1:
1339 if ret[0] and ret[0] > 1:
1339 msg = _("errors during apply, please fix and refresh %s\n")
1340 msg = _("errors during apply, please fix and refresh %s\n")
1340 self.ui.write(msg % top)
1341 self.ui.write(msg % top)
1341 else:
1342 else:
1342 self.ui.write(_("now at: %s\n") % top)
1343 self.ui.write(_("now at: %s\n") % top)
1343 return ret[0]
1344 return ret[0]
1344
1345
1345 finally:
1346 finally:
1346 wlock.release()
1347 wlock.release()
1347
1348
1348 def pop(self, repo, patch=None, force=False, update=True, all=False,
1349 def pop(self, repo, patch=None, force=False, update=True, all=False,
1349 nobackup=False, keepchanges=False):
1350 nobackup=False, keepchanges=False):
1350 self.checkkeepchanges(keepchanges, force)
1351 self.checkkeepchanges(keepchanges, force)
1351 wlock = repo.wlock()
1352 wlock = repo.wlock()
1352 try:
1353 try:
1353 if patch:
1354 if patch:
1354 # index, rev, patch
1355 # index, rev, patch
1355 info = self.isapplied(patch)
1356 info = self.isapplied(patch)
1356 if not info:
1357 if not info:
1357 patch = self.lookup(patch)
1358 patch = self.lookup(patch)
1358 info = self.isapplied(patch)
1359 info = self.isapplied(patch)
1359 if not info:
1360 if not info:
1360 raise util.Abort(_("patch %s is not applied") % patch)
1361 raise util.Abort(_("patch %s is not applied") % patch)
1361
1362
1362 if not self.applied:
1363 if not self.applied:
1363 # Allow qpop -a to work repeatedly,
1364 # Allow qpop -a to work repeatedly,
1364 # but not qpop without an argument
1365 # but not qpop without an argument
1365 self.ui.warn(_("no patches applied\n"))
1366 self.ui.warn(_("no patches applied\n"))
1366 return not all
1367 return not all
1367
1368
1368 if all:
1369 if all:
1369 start = 0
1370 start = 0
1370 elif patch:
1371 elif patch:
1371 start = info[0] + 1
1372 start = info[0] + 1
1372 else:
1373 else:
1373 start = len(self.applied) - 1
1374 start = len(self.applied) - 1
1374
1375
1375 if start >= len(self.applied):
1376 if start >= len(self.applied):
1376 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1377 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1377 return
1378 return
1378
1379
1379 if not update:
1380 if not update:
1380 parents = repo.dirstate.parents()
1381 parents = repo.dirstate.parents()
1381 rr = [x.node for x in self.applied]
1382 rr = [x.node for x in self.applied]
1382 for p in parents:
1383 for p in parents:
1383 if p in rr:
1384 if p in rr:
1384 self.ui.warn(_("qpop: forcing dirstate update\n"))
1385 self.ui.warn(_("qpop: forcing dirstate update\n"))
1385 update = True
1386 update = True
1386 else:
1387 else:
1387 parents = [p.node() for p in repo[None].parents()]
1388 parents = [p.node() for p in repo[None].parents()]
1388 needupdate = False
1389 needupdate = False
1389 for entry in self.applied[start:]:
1390 for entry in self.applied[start:]:
1390 if entry.node in parents:
1391 if entry.node in parents:
1391 needupdate = True
1392 needupdate = True
1392 break
1393 break
1393 update = needupdate
1394 update = needupdate
1394
1395
1395 tobackup = set()
1396 tobackup = set()
1396 if update:
1397 if update:
1397 m, a, r, d = self.checklocalchanges(
1398 m, a, r, d = self.checklocalchanges(
1398 repo, force=force or keepchanges)
1399 repo, force=force or keepchanges)
1399 if force:
1400 if force:
1400 if not nobackup:
1401 if not nobackup:
1401 tobackup.update(m + a)
1402 tobackup.update(m + a)
1402 elif keepchanges:
1403 elif keepchanges:
1403 tobackup.update(m + a + r + d)
1404 tobackup.update(m + a + r + d)
1404
1405
1405 self.applieddirty = True
1406 self.applieddirty = True
1406 end = len(self.applied)
1407 end = len(self.applied)
1407 rev = self.applied[start].node
1408 rev = self.applied[start].node
1408
1409
1409 try:
1410 try:
1410 heads = repo.changelog.heads(rev)
1411 heads = repo.changelog.heads(rev)
1411 except error.LookupError:
1412 except error.LookupError:
1412 node = short(rev)
1413 node = short(rev)
1413 raise util.Abort(_('trying to pop unknown node %s') % node)
1414 raise util.Abort(_('trying to pop unknown node %s') % node)
1414
1415
1415 if heads != [self.applied[-1].node]:
1416 if heads != [self.applied[-1].node]:
1416 raise util.Abort(_("popping would remove a revision not "
1417 raise util.Abort(_("popping would remove a revision not "
1417 "managed by this patch queue"))
1418 "managed by this patch queue"))
1418 if not repo[self.applied[-1].node].mutable():
1419 if not repo[self.applied[-1].node].mutable():
1419 raise util.Abort(
1420 raise util.Abort(
1420 _("popping would remove an immutable revision"),
1421 _("popping would remove an immutable revision"),
1421 hint=_('see "hg help phases" for details'))
1422 hint=_('see "hg help phases" for details'))
1422
1423
1423 # we know there are no local changes, so we can make a simplified
1424 # we know there are no local changes, so we can make a simplified
1424 # form of hg.update.
1425 # form of hg.update.
1425 if update:
1426 if update:
1426 qp = self.qparents(repo, rev)
1427 qp = self.qparents(repo, rev)
1427 ctx = repo[qp]
1428 ctx = repo[qp]
1428 m, a, r, d = repo.status(qp, '.')[:4]
1429 m, a, r, d = repo.status(qp, '.')[:4]
1429 if d:
1430 if d:
1430 raise util.Abort(_("deletions found between repo revs"))
1431 raise util.Abort(_("deletions found between repo revs"))
1431
1432
1432 tobackup = set(a + m + r) & tobackup
1433 tobackup = set(a + m + r) & tobackup
1433 if keepchanges and tobackup:
1434 if keepchanges and tobackup:
1434 raise util.Abort(_("local changes found, refresh first"))
1435 raise util.Abort(_("local changes found, refresh first"))
1435 self.backup(repo, tobackup)
1436 self.backup(repo, tobackup)
1436
1437
1437 for f in a:
1438 for f in a:
1438 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1439 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1439 repo.dirstate.drop(f)
1440 repo.dirstate.drop(f)
1440 for f in m + r:
1441 for f in m + r:
1441 fctx = ctx[f]
1442 fctx = ctx[f]
1442 repo.wwrite(f, fctx.data(), fctx.flags())
1443 repo.wwrite(f, fctx.data(), fctx.flags())
1443 repo.dirstate.normal(f)
1444 repo.dirstate.normal(f)
1444 repo.setparents(qp, nullid)
1445 repo.setparents(qp, nullid)
1445 for patch in reversed(self.applied[start:end]):
1446 for patch in reversed(self.applied[start:end]):
1446 self.ui.status(_("popping %s\n") % patch.name)
1447 self.ui.status(_("popping %s\n") % patch.name)
1447 del self.applied[start:end]
1448 del self.applied[start:end]
1448 strip(self.ui, repo, [rev], update=False, backup='strip')
1449 strip(self.ui, repo, [rev], update=False, backup='strip')
1449 for s, state in repo['.'].substate.items():
1450 for s, state in repo['.'].substate.items():
1450 repo['.'].sub(s).get(state)
1451 repo['.'].sub(s).get(state)
1451 if self.applied:
1452 if self.applied:
1452 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1453 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1453 else:
1454 else:
1454 self.ui.write(_("patch queue now empty\n"))
1455 self.ui.write(_("patch queue now empty\n"))
1455 finally:
1456 finally:
1456 wlock.release()
1457 wlock.release()
1457
1458
1458 def diff(self, repo, pats, opts):
1459 def diff(self, repo, pats, opts):
1459 top, patch = self.checktoppatch(repo)
1460 top, patch = self.checktoppatch(repo)
1460 if not top:
1461 if not top:
1461 self.ui.write(_("no patches applied\n"))
1462 self.ui.write(_("no patches applied\n"))
1462 return
1463 return
1463 qp = self.qparents(repo, top)
1464 qp = self.qparents(repo, top)
1464 if opts.get('reverse'):
1465 if opts.get('reverse'):
1465 node1, node2 = None, qp
1466 node1, node2 = None, qp
1466 else:
1467 else:
1467 node1, node2 = qp, None
1468 node1, node2 = qp, None
1468 diffopts = self.diffopts(opts, patch)
1469 diffopts = self.diffopts(opts, patch)
1469 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1470 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1470
1471
1471 def refresh(self, repo, pats=None, **opts):
1472 def refresh(self, repo, pats=None, **opts):
1472 if not self.applied:
1473 if not self.applied:
1473 self.ui.write(_("no patches applied\n"))
1474 self.ui.write(_("no patches applied\n"))
1474 return 1
1475 return 1
1475 msg = opts.get('msg', '').rstrip()
1476 msg = opts.get('msg', '').rstrip()
1476 newuser = opts.get('user')
1477 newuser = opts.get('user')
1477 newdate = opts.get('date')
1478 newdate = opts.get('date')
1478 if newdate:
1479 if newdate:
1479 newdate = '%d %d' % util.parsedate(newdate)
1480 newdate = '%d %d' % util.parsedate(newdate)
1480 wlock = repo.wlock()
1481 wlock = repo.wlock()
1481
1482
1482 try:
1483 try:
1483 self.checktoppatch(repo)
1484 self.checktoppatch(repo)
1484 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1485 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1485 if repo.changelog.heads(top) != [top]:
1486 if repo.changelog.heads(top) != [top]:
1486 raise util.Abort(_("cannot refresh a revision with children"))
1487 raise util.Abort(_("cannot refresh a revision with children"))
1487 if not repo[top].mutable():
1488 if not repo[top].mutable():
1488 raise util.Abort(_("cannot refresh immutable revision"),
1489 raise util.Abort(_("cannot refresh immutable revision"),
1489 hint=_('see "hg help phases" for details'))
1490 hint=_('see "hg help phases" for details'))
1490
1491
1491 cparents = repo.changelog.parents(top)
1492 cparents = repo.changelog.parents(top)
1492 patchparent = self.qparents(repo, top)
1493 patchparent = self.qparents(repo, top)
1493
1494
1494 inclsubs = checksubstate(repo, hex(patchparent))
1495 inclsubs = checksubstate(repo, hex(patchparent))
1495 if inclsubs:
1496 if inclsubs:
1496 inclsubs.append('.hgsubstate')
1497 inclsubs.append('.hgsubstate')
1497 substatestate = repo.dirstate['.hgsubstate']
1498 substatestate = repo.dirstate['.hgsubstate']
1498
1499
1499 ph = patchheader(self.join(patchfn), self.plainmode)
1500 ph = patchheader(self.join(patchfn), self.plainmode)
1500 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1501 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1501 if msg:
1502 if msg:
1502 ph.setmessage(msg)
1503 ph.setmessage(msg)
1503 if newuser:
1504 if newuser:
1504 ph.setuser(newuser)
1505 ph.setuser(newuser)
1505 if newdate:
1506 if newdate:
1506 ph.setdate(newdate)
1507 ph.setdate(newdate)
1507 ph.setparent(hex(patchparent))
1508 ph.setparent(hex(patchparent))
1508
1509
1509 # only commit new patch when write is complete
1510 # only commit new patch when write is complete
1510 patchf = self.opener(patchfn, 'w', atomictemp=True)
1511 patchf = self.opener(patchfn, 'w', atomictemp=True)
1511
1512
1512 comments = str(ph)
1513 comments = str(ph)
1513 if comments:
1514 if comments:
1514 patchf.write(comments)
1515 patchf.write(comments)
1515
1516
1516 # update the dirstate in place, strip off the qtip commit
1517 # update the dirstate in place, strip off the qtip commit
1517 # and then commit.
1518 # and then commit.
1518 #
1519 #
1519 # this should really read:
1520 # this should really read:
1520 # mm, dd, aa = repo.status(top, patchparent)[:3]
1521 # mm, dd, aa = repo.status(top, patchparent)[:3]
1521 # but we do it backwards to take advantage of manifest/changelog
1522 # but we do it backwards to take advantage of manifest/changelog
1522 # caching against the next repo.status call
1523 # caching against the next repo.status call
1523 mm, aa, dd = repo.status(patchparent, top)[:3]
1524 mm, aa, dd = repo.status(patchparent, top)[:3]
1524 changes = repo.changelog.read(top)
1525 changes = repo.changelog.read(top)
1525 man = repo.manifest.read(changes[0])
1526 man = repo.manifest.read(changes[0])
1526 aaa = aa[:]
1527 aaa = aa[:]
1527 matchfn = scmutil.match(repo[None], pats, opts)
1528 matchfn = scmutil.match(repo[None], pats, opts)
1528 # in short mode, we only diff the files included in the
1529 # in short mode, we only diff the files included in the
1529 # patch already plus specified files
1530 # patch already plus specified files
1530 if opts.get('short'):
1531 if opts.get('short'):
1531 # if amending a patch, we start with existing
1532 # if amending a patch, we start with existing
1532 # files plus specified files - unfiltered
1533 # files plus specified files - unfiltered
1533 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1534 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1534 # filter with include/exclude options
1535 # filter with include/exclude options
1535 matchfn = scmutil.match(repo[None], opts=opts)
1536 matchfn = scmutil.match(repo[None], opts=opts)
1536 else:
1537 else:
1537 match = scmutil.matchall(repo)
1538 match = scmutil.matchall(repo)
1538 m, a, r, d = repo.status(match=match)[:4]
1539 m, a, r, d = repo.status(match=match)[:4]
1539 mm = set(mm)
1540 mm = set(mm)
1540 aa = set(aa)
1541 aa = set(aa)
1541 dd = set(dd)
1542 dd = set(dd)
1542
1543
1543 # we might end up with files that were added between
1544 # we might end up with files that were added between
1544 # qtip and the dirstate parent, but then changed in the
1545 # qtip and the dirstate parent, but then changed in the
1545 # local dirstate. in this case, we want them to only
1546 # local dirstate. in this case, we want them to only
1546 # show up in the added section
1547 # show up in the added section
1547 for x in m:
1548 for x in m:
1548 if x not in aa:
1549 if x not in aa:
1549 mm.add(x)
1550 mm.add(x)
1550 # we might end up with files added by the local dirstate that
1551 # we might end up with files added by the local dirstate that
1551 # were deleted by the patch. In this case, they should only
1552 # were deleted by the patch. In this case, they should only
1552 # show up in the changed section.
1553 # show up in the changed section.
1553 for x in a:
1554 for x in a:
1554 if x in dd:
1555 if x in dd:
1555 dd.remove(x)
1556 dd.remove(x)
1556 mm.add(x)
1557 mm.add(x)
1557 else:
1558 else:
1558 aa.add(x)
1559 aa.add(x)
1559 # make sure any files deleted in the local dirstate
1560 # make sure any files deleted in the local dirstate
1560 # are not in the add or change column of the patch
1561 # are not in the add or change column of the patch
1561 forget = []
1562 forget = []
1562 for x in d + r:
1563 for x in d + r:
1563 if x in aa:
1564 if x in aa:
1564 aa.remove(x)
1565 aa.remove(x)
1565 forget.append(x)
1566 forget.append(x)
1566 continue
1567 continue
1567 else:
1568 else:
1568 mm.discard(x)
1569 mm.discard(x)
1569 dd.add(x)
1570 dd.add(x)
1570
1571
1571 m = list(mm)
1572 m = list(mm)
1572 r = list(dd)
1573 r = list(dd)
1573 a = list(aa)
1574 a = list(aa)
1574
1575
1575 # create 'match' that includes the files to be recommitted.
1576 # create 'match' that includes the files to be recommitted.
1576 # apply matchfn via repo.status to ensure correct case handling.
1577 # apply matchfn via repo.status to ensure correct case handling.
1577 cm, ca, cr, cd = repo.status(patchparent, match=matchfn)[:4]
1578 cm, ca, cr, cd = repo.status(patchparent, match=matchfn)[:4]
1578 allmatches = set(cm + ca + cr + cd)
1579 allmatches = set(cm + ca + cr + cd)
1579 refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
1580 refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
1580
1581
1581 files = set(inclsubs)
1582 files = set(inclsubs)
1582 for x in refreshchanges:
1583 for x in refreshchanges:
1583 files.update(x)
1584 files.update(x)
1584 match = scmutil.matchfiles(repo, files)
1585 match = scmutil.matchfiles(repo, files)
1585
1586
1586 bmlist = repo[top].bookmarks()
1587 bmlist = repo[top].bookmarks()
1587
1588
1588 try:
1589 try:
1589 if diffopts.git or diffopts.upgrade:
1590 if diffopts.git or diffopts.upgrade:
1590 copies = {}
1591 copies = {}
1591 for dst in a:
1592 for dst in a:
1592 src = repo.dirstate.copied(dst)
1593 src = repo.dirstate.copied(dst)
1593 # during qfold, the source file for copies may
1594 # during qfold, the source file for copies may
1594 # be removed. Treat this as a simple add.
1595 # be removed. Treat this as a simple add.
1595 if src is not None and src in repo.dirstate:
1596 if src is not None and src in repo.dirstate:
1596 copies.setdefault(src, []).append(dst)
1597 copies.setdefault(src, []).append(dst)
1597 repo.dirstate.add(dst)
1598 repo.dirstate.add(dst)
1598 # remember the copies between patchparent and qtip
1599 # remember the copies between patchparent and qtip
1599 for dst in aaa:
1600 for dst in aaa:
1600 f = repo.file(dst)
1601 f = repo.file(dst)
1601 src = f.renamed(man[dst])
1602 src = f.renamed(man[dst])
1602 if src:
1603 if src:
1603 copies.setdefault(src[0], []).extend(
1604 copies.setdefault(src[0], []).extend(
1604 copies.get(dst, []))
1605 copies.get(dst, []))
1605 if dst in a:
1606 if dst in a:
1606 copies[src[0]].append(dst)
1607 copies[src[0]].append(dst)
1607 # we can't copy a file created by the patch itself
1608 # we can't copy a file created by the patch itself
1608 if dst in copies:
1609 if dst in copies:
1609 del copies[dst]
1610 del copies[dst]
1610 for src, dsts in copies.iteritems():
1611 for src, dsts in copies.iteritems():
1611 for dst in dsts:
1612 for dst in dsts:
1612 repo.dirstate.copy(src, dst)
1613 repo.dirstate.copy(src, dst)
1613 else:
1614 else:
1614 for dst in a:
1615 for dst in a:
1615 repo.dirstate.add(dst)
1616 repo.dirstate.add(dst)
1616 # Drop useless copy information
1617 # Drop useless copy information
1617 for f in list(repo.dirstate.copies()):
1618 for f in list(repo.dirstate.copies()):
1618 repo.dirstate.copy(None, f)
1619 repo.dirstate.copy(None, f)
1619 for f in r:
1620 for f in r:
1620 repo.dirstate.remove(f)
1621 repo.dirstate.remove(f)
1621 # if the patch excludes a modified file, mark that
1622 # if the patch excludes a modified file, mark that
1622 # file with mtime=0 so status can see it.
1623 # file with mtime=0 so status can see it.
1623 mm = []
1624 mm = []
1624 for i in xrange(len(m) - 1, -1, -1):
1625 for i in xrange(len(m) - 1, -1, -1):
1625 if not matchfn(m[i]):
1626 if not matchfn(m[i]):
1626 mm.append(m[i])
1627 mm.append(m[i])
1627 del m[i]
1628 del m[i]
1628 for f in m:
1629 for f in m:
1629 repo.dirstate.normal(f)
1630 repo.dirstate.normal(f)
1630 for f in mm:
1631 for f in mm:
1631 repo.dirstate.normallookup(f)
1632 repo.dirstate.normallookup(f)
1632 for f in forget:
1633 for f in forget:
1633 repo.dirstate.drop(f)
1634 repo.dirstate.drop(f)
1634
1635
1635 if not msg:
1636 if not msg:
1636 if not ph.message:
1637 if not ph.message:
1637 message = "[mq]: %s\n" % patchfn
1638 message = "[mq]: %s\n" % patchfn
1638 else:
1639 else:
1639 message = "\n".join(ph.message)
1640 message = "\n".join(ph.message)
1640 else:
1641 else:
1641 message = msg
1642 message = msg
1642
1643
1643 user = ph.user or changes[1]
1644 user = ph.user or changes[1]
1644
1645
1645 oldphase = repo[top].phase()
1646 oldphase = repo[top].phase()
1646
1647
1647 # assumes strip can roll itself back if interrupted
1648 # assumes strip can roll itself back if interrupted
1648 repo.setparents(*cparents)
1649 repo.setparents(*cparents)
1649 self.applied.pop()
1650 self.applied.pop()
1650 self.applieddirty = True
1651 self.applieddirty = True
1651 strip(self.ui, repo, [top], update=False, backup='strip')
1652 strip(self.ui, repo, [top], update=False, backup='strip')
1652 except: # re-raises
1653 except: # re-raises
1653 repo.dirstate.invalidate()
1654 repo.dirstate.invalidate()
1654 raise
1655 raise
1655
1656
1656 try:
1657 try:
1657 # might be nice to attempt to roll back strip after this
1658 # might be nice to attempt to roll back strip after this
1658
1659
1659 # Ensure we create a new changeset in the same phase than
1660 # Ensure we create a new changeset in the same phase than
1660 # the old one.
1661 # the old one.
1661 n = newcommit(repo, oldphase, message, user, ph.date,
1662 n = newcommit(repo, oldphase, message, user, ph.date,
1662 match=match, force=True)
1663 match=match, force=True)
1663 # only write patch after a successful commit
1664 # only write patch after a successful commit
1664 c = [list(x) for x in refreshchanges]
1665 c = [list(x) for x in refreshchanges]
1665 if inclsubs:
1666 if inclsubs:
1666 self.putsubstate2changes(substatestate, c)
1667 self.putsubstate2changes(substatestate, c)
1667 chunks = patchmod.diff(repo, patchparent,
1668 chunks = patchmod.diff(repo, patchparent,
1668 changes=c, opts=diffopts)
1669 changes=c, opts=diffopts)
1669 for chunk in chunks:
1670 for chunk in chunks:
1670 patchf.write(chunk)
1671 patchf.write(chunk)
1671 patchf.close()
1672 patchf.close()
1672
1673
1673 marks = repo._bookmarks
1674 marks = repo._bookmarks
1674 for bm in bmlist:
1675 for bm in bmlist:
1675 marks[bm] = n
1676 marks[bm] = n
1676 marks.write()
1677 marks.write()
1677
1678
1678 self.applied.append(statusentry(n, patchfn))
1679 self.applied.append(statusentry(n, patchfn))
1679 except: # re-raises
1680 except: # re-raises
1680 ctx = repo[cparents[0]]
1681 ctx = repo[cparents[0]]
1681 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1682 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1682 self.savedirty()
1683 self.savedirty()
1683 self.ui.warn(_('refresh interrupted while patch was popped! '
1684 self.ui.warn(_('refresh interrupted while patch was popped! '
1684 '(revert --all, qpush to recover)\n'))
1685 '(revert --all, qpush to recover)\n'))
1685 raise
1686 raise
1686 finally:
1687 finally:
1687 wlock.release()
1688 wlock.release()
1688 self.removeundo(repo)
1689 self.removeundo(repo)
1689
1690
1690 def init(self, repo, create=False):
1691 def init(self, repo, create=False):
1691 if not create and os.path.isdir(self.path):
1692 if not create and os.path.isdir(self.path):
1692 raise util.Abort(_("patch queue directory already exists"))
1693 raise util.Abort(_("patch queue directory already exists"))
1693 try:
1694 try:
1694 os.mkdir(self.path)
1695 os.mkdir(self.path)
1695 except OSError, inst:
1696 except OSError, inst:
1696 if inst.errno != errno.EEXIST or not create:
1697 if inst.errno != errno.EEXIST or not create:
1697 raise
1698 raise
1698 if create:
1699 if create:
1699 return self.qrepo(create=True)
1700 return self.qrepo(create=True)
1700
1701
1701 def unapplied(self, repo, patch=None):
1702 def unapplied(self, repo, patch=None):
1702 if patch and patch not in self.series:
1703 if patch and patch not in self.series:
1703 raise util.Abort(_("patch %s is not in series file") % patch)
1704 raise util.Abort(_("patch %s is not in series file") % patch)
1704 if not patch:
1705 if not patch:
1705 start = self.seriesend()
1706 start = self.seriesend()
1706 else:
1707 else:
1707 start = self.series.index(patch) + 1
1708 start = self.series.index(patch) + 1
1708 unapplied = []
1709 unapplied = []
1709 for i in xrange(start, len(self.series)):
1710 for i in xrange(start, len(self.series)):
1710 pushable, reason = self.pushable(i)
1711 pushable, reason = self.pushable(i)
1711 if pushable:
1712 if pushable:
1712 unapplied.append((i, self.series[i]))
1713 unapplied.append((i, self.series[i]))
1713 self.explainpushable(i)
1714 self.explainpushable(i)
1714 return unapplied
1715 return unapplied
1715
1716
1716 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1717 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1717 summary=False):
1718 summary=False):
1718 def displayname(pfx, patchname, state):
1719 def displayname(pfx, patchname, state):
1719 if pfx:
1720 if pfx:
1720 self.ui.write(pfx)
1721 self.ui.write(pfx)
1721 if summary:
1722 if summary:
1722 ph = patchheader(self.join(patchname), self.plainmode)
1723 ph = patchheader(self.join(patchname), self.plainmode)
1723 msg = ph.message and ph.message[0] or ''
1724 msg = ph.message and ph.message[0] or ''
1724 if self.ui.formatted():
1725 if self.ui.formatted():
1725 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1726 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1726 if width > 0:
1727 if width > 0:
1727 msg = util.ellipsis(msg, width)
1728 msg = util.ellipsis(msg, width)
1728 else:
1729 else:
1729 msg = ''
1730 msg = ''
1730 self.ui.write(patchname, label='qseries.' + state)
1731 self.ui.write(patchname, label='qseries.' + state)
1731 self.ui.write(': ')
1732 self.ui.write(': ')
1732 self.ui.write(msg, label='qseries.message.' + state)
1733 self.ui.write(msg, label='qseries.message.' + state)
1733 else:
1734 else:
1734 self.ui.write(patchname, label='qseries.' + state)
1735 self.ui.write(patchname, label='qseries.' + state)
1735 self.ui.write('\n')
1736 self.ui.write('\n')
1736
1737
1737 applied = set([p.name for p in self.applied])
1738 applied = set([p.name for p in self.applied])
1738 if length is None:
1739 if length is None:
1739 length = len(self.series) - start
1740 length = len(self.series) - start
1740 if not missing:
1741 if not missing:
1741 if self.ui.verbose:
1742 if self.ui.verbose:
1742 idxwidth = len(str(start + length - 1))
1743 idxwidth = len(str(start + length - 1))
1743 for i in xrange(start, start + length):
1744 for i in xrange(start, start + length):
1744 patch = self.series[i]
1745 patch = self.series[i]
1745 if patch in applied:
1746 if patch in applied:
1746 char, state = 'A', 'applied'
1747 char, state = 'A', 'applied'
1747 elif self.pushable(i)[0]:
1748 elif self.pushable(i)[0]:
1748 char, state = 'U', 'unapplied'
1749 char, state = 'U', 'unapplied'
1749 else:
1750 else:
1750 char, state = 'G', 'guarded'
1751 char, state = 'G', 'guarded'
1751 pfx = ''
1752 pfx = ''
1752 if self.ui.verbose:
1753 if self.ui.verbose:
1753 pfx = '%*d %s ' % (idxwidth, i, char)
1754 pfx = '%*d %s ' % (idxwidth, i, char)
1754 elif status and status != char:
1755 elif status and status != char:
1755 continue
1756 continue
1756 displayname(pfx, patch, state)
1757 displayname(pfx, patch, state)
1757 else:
1758 else:
1758 msng_list = []
1759 msng_list = []
1759 for root, dirs, files in os.walk(self.path):
1760 for root, dirs, files in os.walk(self.path):
1760 d = root[len(self.path) + 1:]
1761 d = root[len(self.path) + 1:]
1761 for f in files:
1762 for f in files:
1762 fl = os.path.join(d, f)
1763 fl = os.path.join(d, f)
1763 if (fl not in self.series and
1764 if (fl not in self.series and
1764 fl not in (self.statuspath, self.seriespath,
1765 fl not in (self.statuspath, self.seriespath,
1765 self.guardspath)
1766 self.guardspath)
1766 and not fl.startswith('.')):
1767 and not fl.startswith('.')):
1767 msng_list.append(fl)
1768 msng_list.append(fl)
1768 for x in sorted(msng_list):
1769 for x in sorted(msng_list):
1769 pfx = self.ui.verbose and ('D ') or ''
1770 pfx = self.ui.verbose and ('D ') or ''
1770 displayname(pfx, x, 'missing')
1771 displayname(pfx, x, 'missing')
1771
1772
1772 def issaveline(self, l):
1773 def issaveline(self, l):
1773 if l.name == '.hg.patches.save.line':
1774 if l.name == '.hg.patches.save.line':
1774 return True
1775 return True
1775
1776
1776 def qrepo(self, create=False):
1777 def qrepo(self, create=False):
1777 ui = self.baseui.copy()
1778 ui = self.baseui.copy()
1778 if create or os.path.isdir(self.join(".hg")):
1779 if create or os.path.isdir(self.join(".hg")):
1779 return hg.repository(ui, path=self.path, create=create)
1780 return hg.repository(ui, path=self.path, create=create)
1780
1781
1781 def restore(self, repo, rev, delete=None, qupdate=None):
1782 def restore(self, repo, rev, delete=None, qupdate=None):
1782 desc = repo[rev].description().strip()
1783 desc = repo[rev].description().strip()
1783 lines = desc.splitlines()
1784 lines = desc.splitlines()
1784 i = 0
1785 i = 0
1785 datastart = None
1786 datastart = None
1786 series = []
1787 series = []
1787 applied = []
1788 applied = []
1788 qpp = None
1789 qpp = None
1789 for i, line in enumerate(lines):
1790 for i, line in enumerate(lines):
1790 if line == 'Patch Data:':
1791 if line == 'Patch Data:':
1791 datastart = i + 1
1792 datastart = i + 1
1792 elif line.startswith('Dirstate:'):
1793 elif line.startswith('Dirstate:'):
1793 l = line.rstrip()
1794 l = line.rstrip()
1794 l = l[10:].split(' ')
1795 l = l[10:].split(' ')
1795 qpp = [bin(x) for x in l]
1796 qpp = [bin(x) for x in l]
1796 elif datastart is not None:
1797 elif datastart is not None:
1797 l = line.rstrip()
1798 l = line.rstrip()
1798 n, name = l.split(':', 1)
1799 n, name = l.split(':', 1)
1799 if n:
1800 if n:
1800 applied.append(statusentry(bin(n), name))
1801 applied.append(statusentry(bin(n), name))
1801 else:
1802 else:
1802 series.append(l)
1803 series.append(l)
1803 if datastart is None:
1804 if datastart is None:
1804 self.ui.warn(_("no saved patch data found\n"))
1805 self.ui.warn(_("no saved patch data found\n"))
1805 return 1
1806 return 1
1806 self.ui.warn(_("restoring status: %s\n") % lines[0])
1807 self.ui.warn(_("restoring status: %s\n") % lines[0])
1807 self.fullseries = series
1808 self.fullseries = series
1808 self.applied = applied
1809 self.applied = applied
1809 self.parseseries()
1810 self.parseseries()
1810 self.seriesdirty = True
1811 self.seriesdirty = True
1811 self.applieddirty = True
1812 self.applieddirty = True
1812 heads = repo.changelog.heads()
1813 heads = repo.changelog.heads()
1813 if delete:
1814 if delete:
1814 if rev not in heads:
1815 if rev not in heads:
1815 self.ui.warn(_("save entry has children, leaving it alone\n"))
1816 self.ui.warn(_("save entry has children, leaving it alone\n"))
1816 else:
1817 else:
1817 self.ui.warn(_("removing save entry %s\n") % short(rev))
1818 self.ui.warn(_("removing save entry %s\n") % short(rev))
1818 pp = repo.dirstate.parents()
1819 pp = repo.dirstate.parents()
1819 if rev in pp:
1820 if rev in pp:
1820 update = True
1821 update = True
1821 else:
1822 else:
1822 update = False
1823 update = False
1823 strip(self.ui, repo, [rev], update=update, backup='strip')
1824 strip(self.ui, repo, [rev], update=update, backup='strip')
1824 if qpp:
1825 if qpp:
1825 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1826 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1826 (short(qpp[0]), short(qpp[1])))
1827 (short(qpp[0]), short(qpp[1])))
1827 if qupdate:
1828 if qupdate:
1828 self.ui.status(_("updating queue directory\n"))
1829 self.ui.status(_("updating queue directory\n"))
1829 r = self.qrepo()
1830 r = self.qrepo()
1830 if not r:
1831 if not r:
1831 self.ui.warn(_("unable to load queue repository\n"))
1832 self.ui.warn(_("unable to load queue repository\n"))
1832 return 1
1833 return 1
1833 hg.clean(r, qpp[0])
1834 hg.clean(r, qpp[0])
1834
1835
1835 def save(self, repo, msg=None):
1836 def save(self, repo, msg=None):
1836 if not self.applied:
1837 if not self.applied:
1837 self.ui.warn(_("save: no patches applied, exiting\n"))
1838 self.ui.warn(_("save: no patches applied, exiting\n"))
1838 return 1
1839 return 1
1839 if self.issaveline(self.applied[-1]):
1840 if self.issaveline(self.applied[-1]):
1840 self.ui.warn(_("status is already saved\n"))
1841 self.ui.warn(_("status is already saved\n"))
1841 return 1
1842 return 1
1842
1843
1843 if not msg:
1844 if not msg:
1844 msg = _("hg patches saved state")
1845 msg = _("hg patches saved state")
1845 else:
1846 else:
1846 msg = "hg patches: " + msg.rstrip('\r\n')
1847 msg = "hg patches: " + msg.rstrip('\r\n')
1847 r = self.qrepo()
1848 r = self.qrepo()
1848 if r:
1849 if r:
1849 pp = r.dirstate.parents()
1850 pp = r.dirstate.parents()
1850 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1851 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1851 msg += "\n\nPatch Data:\n"
1852 msg += "\n\nPatch Data:\n"
1852 msg += ''.join('%s\n' % x for x in self.applied)
1853 msg += ''.join('%s\n' % x for x in self.applied)
1853 msg += ''.join(':%s\n' % x for x in self.fullseries)
1854 msg += ''.join(':%s\n' % x for x in self.fullseries)
1854 n = repo.commit(msg, force=True)
1855 n = repo.commit(msg, force=True)
1855 if not n:
1856 if not n:
1856 self.ui.warn(_("repo commit failed\n"))
1857 self.ui.warn(_("repo commit failed\n"))
1857 return 1
1858 return 1
1858 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1859 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1859 self.applieddirty = True
1860 self.applieddirty = True
1860 self.removeundo(repo)
1861 self.removeundo(repo)
1861
1862
1862 def fullseriesend(self):
1863 def fullseriesend(self):
1863 if self.applied:
1864 if self.applied:
1864 p = self.applied[-1].name
1865 p = self.applied[-1].name
1865 end = self.findseries(p)
1866 end = self.findseries(p)
1866 if end is None:
1867 if end is None:
1867 return len(self.fullseries)
1868 return len(self.fullseries)
1868 return end + 1
1869 return end + 1
1869 return 0
1870 return 0
1870
1871
1871 def seriesend(self, all_patches=False):
1872 def seriesend(self, all_patches=False):
1872 """If all_patches is False, return the index of the next pushable patch
1873 """If all_patches is False, return the index of the next pushable patch
1873 in the series, or the series length. If all_patches is True, return the
1874 in the series, or the series length. If all_patches is True, return the
1874 index of the first patch past the last applied one.
1875 index of the first patch past the last applied one.
1875 """
1876 """
1876 end = 0
1877 end = 0
1877 def nextpatch(start):
1878 def nextpatch(start):
1878 if all_patches or start >= len(self.series):
1879 if all_patches or start >= len(self.series):
1879 return start
1880 return start
1880 for i in xrange(start, len(self.series)):
1881 for i in xrange(start, len(self.series)):
1881 p, reason = self.pushable(i)
1882 p, reason = self.pushable(i)
1882 if p:
1883 if p:
1883 return i
1884 return i
1884 self.explainpushable(i)
1885 self.explainpushable(i)
1885 return len(self.series)
1886 return len(self.series)
1886 if self.applied:
1887 if self.applied:
1887 p = self.applied[-1].name
1888 p = self.applied[-1].name
1888 try:
1889 try:
1889 end = self.series.index(p)
1890 end = self.series.index(p)
1890 except ValueError:
1891 except ValueError:
1891 return 0
1892 return 0
1892 return nextpatch(end + 1)
1893 return nextpatch(end + 1)
1893 return nextpatch(end)
1894 return nextpatch(end)
1894
1895
1895 def appliedname(self, index):
1896 def appliedname(self, index):
1896 pname = self.applied[index].name
1897 pname = self.applied[index].name
1897 if not self.ui.verbose:
1898 if not self.ui.verbose:
1898 p = pname
1899 p = pname
1899 else:
1900 else:
1900 p = str(self.series.index(pname)) + " " + pname
1901 p = str(self.series.index(pname)) + " " + pname
1901 return p
1902 return p
1902
1903
1903 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1904 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1904 force=None, git=False):
1905 force=None, git=False):
1905 def checkseries(patchname):
1906 def checkseries(patchname):
1906 if patchname in self.series:
1907 if patchname in self.series:
1907 raise util.Abort(_('patch %s is already in the series file')
1908 raise util.Abort(_('patch %s is already in the series file')
1908 % patchname)
1909 % patchname)
1909
1910
1910 if rev:
1911 if rev:
1911 if files:
1912 if files:
1912 raise util.Abort(_('option "-r" not valid when importing '
1913 raise util.Abort(_('option "-r" not valid when importing '
1913 'files'))
1914 'files'))
1914 rev = scmutil.revrange(repo, rev)
1915 rev = scmutil.revrange(repo, rev)
1915 rev.sort(reverse=True)
1916 rev.sort(reverse=True)
1916 elif not files:
1917 elif not files:
1917 raise util.Abort(_('no files or revisions specified'))
1918 raise util.Abort(_('no files or revisions specified'))
1918 if (len(files) > 1 or len(rev) > 1) and patchname:
1919 if (len(files) > 1 or len(rev) > 1) and patchname:
1919 raise util.Abort(_('option "-n" not valid when importing multiple '
1920 raise util.Abort(_('option "-n" not valid when importing multiple '
1920 'patches'))
1921 'patches'))
1921 imported = []
1922 imported = []
1922 if rev:
1923 if rev:
1923 # If mq patches are applied, we can only import revisions
1924 # If mq patches are applied, we can only import revisions
1924 # that form a linear path to qbase.
1925 # that form a linear path to qbase.
1925 # Otherwise, they should form a linear path to a head.
1926 # Otherwise, they should form a linear path to a head.
1926 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1927 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1927 if len(heads) > 1:
1928 if len(heads) > 1:
1928 raise util.Abort(_('revision %d is the root of more than one '
1929 raise util.Abort(_('revision %d is the root of more than one '
1929 'branch') % rev[-1])
1930 'branch') % rev[-1])
1930 if self.applied:
1931 if self.applied:
1931 base = repo.changelog.node(rev[0])
1932 base = repo.changelog.node(rev[0])
1932 if base in [n.node for n in self.applied]:
1933 if base in [n.node for n in self.applied]:
1933 raise util.Abort(_('revision %d is already managed')
1934 raise util.Abort(_('revision %d is already managed')
1934 % rev[0])
1935 % rev[0])
1935 if heads != [self.applied[-1].node]:
1936 if heads != [self.applied[-1].node]:
1936 raise util.Abort(_('revision %d is not the parent of '
1937 raise util.Abort(_('revision %d is not the parent of '
1937 'the queue') % rev[0])
1938 'the queue') % rev[0])
1938 base = repo.changelog.rev(self.applied[0].node)
1939 base = repo.changelog.rev(self.applied[0].node)
1939 lastparent = repo.changelog.parentrevs(base)[0]
1940 lastparent = repo.changelog.parentrevs(base)[0]
1940 else:
1941 else:
1941 if heads != [repo.changelog.node(rev[0])]:
1942 if heads != [repo.changelog.node(rev[0])]:
1942 raise util.Abort(_('revision %d has unmanaged children')
1943 raise util.Abort(_('revision %d has unmanaged children')
1943 % rev[0])
1944 % rev[0])
1944 lastparent = None
1945 lastparent = None
1945
1946
1946 diffopts = self.diffopts({'git': git})
1947 diffopts = self.diffopts({'git': git})
1947 for r in rev:
1948 for r in rev:
1948 if not repo[r].mutable():
1949 if not repo[r].mutable():
1949 raise util.Abort(_('revision %d is not mutable') % r,
1950 raise util.Abort(_('revision %d is not mutable') % r,
1950 hint=_('see "hg help phases" for details'))
1951 hint=_('see "hg help phases" for details'))
1951 p1, p2 = repo.changelog.parentrevs(r)
1952 p1, p2 = repo.changelog.parentrevs(r)
1952 n = repo.changelog.node(r)
1953 n = repo.changelog.node(r)
1953 if p2 != nullrev:
1954 if p2 != nullrev:
1954 raise util.Abort(_('cannot import merge revision %d') % r)
1955 raise util.Abort(_('cannot import merge revision %d') % r)
1955 if lastparent and lastparent != r:
1956 if lastparent and lastparent != r:
1956 raise util.Abort(_('revision %d is not the parent of %d')
1957 raise util.Abort(_('revision %d is not the parent of %d')
1957 % (r, lastparent))
1958 % (r, lastparent))
1958 lastparent = p1
1959 lastparent = p1
1959
1960
1960 if not patchname:
1961 if not patchname:
1961 patchname = normname('%d.diff' % r)
1962 patchname = normname('%d.diff' % r)
1962 checkseries(patchname)
1963 checkseries(patchname)
1963 self.checkpatchname(patchname, force)
1964 self.checkpatchname(patchname, force)
1964 self.fullseries.insert(0, patchname)
1965 self.fullseries.insert(0, patchname)
1965
1966
1966 patchf = self.opener(patchname, "w")
1967 patchf = self.opener(patchname, "w")
1967 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1968 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1968 patchf.close()
1969 patchf.close()
1969
1970
1970 se = statusentry(n, patchname)
1971 se = statusentry(n, patchname)
1971 self.applied.insert(0, se)
1972 self.applied.insert(0, se)
1972
1973
1973 self.added.append(patchname)
1974 self.added.append(patchname)
1974 imported.append(patchname)
1975 imported.append(patchname)
1975 patchname = None
1976 patchname = None
1976 if rev and repo.ui.configbool('mq', 'secret', False):
1977 if rev and repo.ui.configbool('mq', 'secret', False):
1977 # if we added anything with --rev, we must move the secret root
1978 # if we added anything with --rev, we must move the secret root
1978 phases.retractboundary(repo, phases.secret, [n])
1979 phases.retractboundary(repo, phases.secret, [n])
1979 self.parseseries()
1980 self.parseseries()
1980 self.applieddirty = True
1981 self.applieddirty = True
1981 self.seriesdirty = True
1982 self.seriesdirty = True
1982
1983
1983 for i, filename in enumerate(files):
1984 for i, filename in enumerate(files):
1984 if existing:
1985 if existing:
1985 if filename == '-':
1986 if filename == '-':
1986 raise util.Abort(_('-e is incompatible with import from -'))
1987 raise util.Abort(_('-e is incompatible with import from -'))
1987 filename = normname(filename)
1988 filename = normname(filename)
1988 self.checkreservedname(filename)
1989 self.checkreservedname(filename)
1989 if util.url(filename).islocal():
1990 if util.url(filename).islocal():
1990 originpath = self.join(filename)
1991 originpath = self.join(filename)
1991 if not os.path.isfile(originpath):
1992 if not os.path.isfile(originpath):
1992 raise util.Abort(
1993 raise util.Abort(
1993 _("patch %s does not exist") % filename)
1994 _("patch %s does not exist") % filename)
1994
1995
1995 if patchname:
1996 if patchname:
1996 self.checkpatchname(patchname, force)
1997 self.checkpatchname(patchname, force)
1997
1998
1998 self.ui.write(_('renaming %s to %s\n')
1999 self.ui.write(_('renaming %s to %s\n')
1999 % (filename, patchname))
2000 % (filename, patchname))
2000 util.rename(originpath, self.join(patchname))
2001 util.rename(originpath, self.join(patchname))
2001 else:
2002 else:
2002 patchname = filename
2003 patchname = filename
2003
2004
2004 else:
2005 else:
2005 if filename == '-' and not patchname:
2006 if filename == '-' and not patchname:
2006 raise util.Abort(_('need --name to import a patch from -'))
2007 raise util.Abort(_('need --name to import a patch from -'))
2007 elif not patchname:
2008 elif not patchname:
2008 patchname = normname(os.path.basename(filename.rstrip('/')))
2009 patchname = normname(os.path.basename(filename.rstrip('/')))
2009 self.checkpatchname(patchname, force)
2010 self.checkpatchname(patchname, force)
2010 try:
2011 try:
2011 if filename == '-':
2012 if filename == '-':
2012 text = self.ui.fin.read()
2013 text = self.ui.fin.read()
2013 else:
2014 else:
2014 fp = hg.openpath(self.ui, filename)
2015 fp = hg.openpath(self.ui, filename)
2015 text = fp.read()
2016 text = fp.read()
2016 fp.close()
2017 fp.close()
2017 except (OSError, IOError):
2018 except (OSError, IOError):
2018 raise util.Abort(_("unable to read file %s") % filename)
2019 raise util.Abort(_("unable to read file %s") % filename)
2019 patchf = self.opener(patchname, "w")
2020 patchf = self.opener(patchname, "w")
2020 patchf.write(text)
2021 patchf.write(text)
2021 patchf.close()
2022 patchf.close()
2022 if not force:
2023 if not force:
2023 checkseries(patchname)
2024 checkseries(patchname)
2024 if patchname not in self.series:
2025 if patchname not in self.series:
2025 index = self.fullseriesend() + i
2026 index = self.fullseriesend() + i
2026 self.fullseries[index:index] = [patchname]
2027 self.fullseries[index:index] = [patchname]
2027 self.parseseries()
2028 self.parseseries()
2028 self.seriesdirty = True
2029 self.seriesdirty = True
2029 self.ui.warn(_("adding %s to series file\n") % patchname)
2030 self.ui.warn(_("adding %s to series file\n") % patchname)
2030 self.added.append(patchname)
2031 self.added.append(patchname)
2031 imported.append(patchname)
2032 imported.append(patchname)
2032 patchname = None
2033 patchname = None
2033
2034
2034 self.removeundo(repo)
2035 self.removeundo(repo)
2035 return imported
2036 return imported
2036
2037
2037 def fixkeepchangesopts(ui, opts):
2038 def fixkeepchangesopts(ui, opts):
2038 if (not ui.configbool('mq', 'keepchanges') or opts.get('force')
2039 if (not ui.configbool('mq', 'keepchanges') or opts.get('force')
2039 or opts.get('exact')):
2040 or opts.get('exact')):
2040 return opts
2041 return opts
2041 opts = dict(opts)
2042 opts = dict(opts)
2042 opts['keep_changes'] = True
2043 opts['keep_changes'] = True
2043 return opts
2044 return opts
2044
2045
2045 @command("qdelete|qremove|qrm",
2046 @command("qdelete|qremove|qrm",
2046 [('k', 'keep', None, _('keep patch file')),
2047 [('k', 'keep', None, _('keep patch file')),
2047 ('r', 'rev', [],
2048 ('r', 'rev', [],
2048 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2049 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2049 _('hg qdelete [-k] [PATCH]...'))
2050 _('hg qdelete [-k] [PATCH]...'))
2050 def delete(ui, repo, *patches, **opts):
2051 def delete(ui, repo, *patches, **opts):
2051 """remove patches from queue
2052 """remove patches from queue
2052
2053
2053 The patches must not be applied, and at least one patch is required. Exact
2054 The patches must not be applied, and at least one patch is required. Exact
2054 patch identifiers must be given. With -k/--keep, the patch files are
2055 patch identifiers must be given. With -k/--keep, the patch files are
2055 preserved in the patch directory.
2056 preserved in the patch directory.
2056
2057
2057 To stop managing a patch and move it into permanent history,
2058 To stop managing a patch and move it into permanent history,
2058 use the :hg:`qfinish` command."""
2059 use the :hg:`qfinish` command."""
2059 q = repo.mq
2060 q = repo.mq
2060 q.delete(repo, patches, opts)
2061 q.delete(repo, patches, opts)
2061 q.savedirty()
2062 q.savedirty()
2062 return 0
2063 return 0
2063
2064
2064 @command("qapplied",
2065 @command("qapplied",
2065 [('1', 'last', None, _('show only the preceding applied patch'))
2066 [('1', 'last', None, _('show only the preceding applied patch'))
2066 ] + seriesopts,
2067 ] + seriesopts,
2067 _('hg qapplied [-1] [-s] [PATCH]'))
2068 _('hg qapplied [-1] [-s] [PATCH]'))
2068 def applied(ui, repo, patch=None, **opts):
2069 def applied(ui, repo, patch=None, **opts):
2069 """print the patches already applied
2070 """print the patches already applied
2070
2071
2071 Returns 0 on success."""
2072 Returns 0 on success."""
2072
2073
2073 q = repo.mq
2074 q = repo.mq
2074
2075
2075 if patch:
2076 if patch:
2076 if patch not in q.series:
2077 if patch not in q.series:
2077 raise util.Abort(_("patch %s is not in series file") % patch)
2078 raise util.Abort(_("patch %s is not in series file") % patch)
2078 end = q.series.index(patch) + 1
2079 end = q.series.index(patch) + 1
2079 else:
2080 else:
2080 end = q.seriesend(True)
2081 end = q.seriesend(True)
2081
2082
2082 if opts.get('last') and not end:
2083 if opts.get('last') and not end:
2083 ui.write(_("no patches applied\n"))
2084 ui.write(_("no patches applied\n"))
2084 return 1
2085 return 1
2085 elif opts.get('last') and end == 1:
2086 elif opts.get('last') and end == 1:
2086 ui.write(_("only one patch applied\n"))
2087 ui.write(_("only one patch applied\n"))
2087 return 1
2088 return 1
2088 elif opts.get('last'):
2089 elif opts.get('last'):
2089 start = end - 2
2090 start = end - 2
2090 end = 1
2091 end = 1
2091 else:
2092 else:
2092 start = 0
2093 start = 0
2093
2094
2094 q.qseries(repo, length=end, start=start, status='A',
2095 q.qseries(repo, length=end, start=start, status='A',
2095 summary=opts.get('summary'))
2096 summary=opts.get('summary'))
2096
2097
2097
2098
2098 @command("qunapplied",
2099 @command("qunapplied",
2099 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2100 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2100 _('hg qunapplied [-1] [-s] [PATCH]'))
2101 _('hg qunapplied [-1] [-s] [PATCH]'))
2101 def unapplied(ui, repo, patch=None, **opts):
2102 def unapplied(ui, repo, patch=None, **opts):
2102 """print the patches not yet applied
2103 """print the patches not yet applied
2103
2104
2104 Returns 0 on success."""
2105 Returns 0 on success."""
2105
2106
2106 q = repo.mq
2107 q = repo.mq
2107 if patch:
2108 if patch:
2108 if patch not in q.series:
2109 if patch not in q.series:
2109 raise util.Abort(_("patch %s is not in series file") % patch)
2110 raise util.Abort(_("patch %s is not in series file") % patch)
2110 start = q.series.index(patch) + 1
2111 start = q.series.index(patch) + 1
2111 else:
2112 else:
2112 start = q.seriesend(True)
2113 start = q.seriesend(True)
2113
2114
2114 if start == len(q.series) and opts.get('first'):
2115 if start == len(q.series) and opts.get('first'):
2115 ui.write(_("all patches applied\n"))
2116 ui.write(_("all patches applied\n"))
2116 return 1
2117 return 1
2117
2118
2118 length = opts.get('first') and 1 or None
2119 length = opts.get('first') and 1 or None
2119 q.qseries(repo, start=start, length=length, status='U',
2120 q.qseries(repo, start=start, length=length, status='U',
2120 summary=opts.get('summary'))
2121 summary=opts.get('summary'))
2121
2122
2122 @command("qimport",
2123 @command("qimport",
2123 [('e', 'existing', None, _('import file in patch directory')),
2124 [('e', 'existing', None, _('import file in patch directory')),
2124 ('n', 'name', '',
2125 ('n', 'name', '',
2125 _('name of patch file'), _('NAME')),
2126 _('name of patch file'), _('NAME')),
2126 ('f', 'force', None, _('overwrite existing files')),
2127 ('f', 'force', None, _('overwrite existing files')),
2127 ('r', 'rev', [],
2128 ('r', 'rev', [],
2128 _('place existing revisions under mq control'), _('REV')),
2129 _('place existing revisions under mq control'), _('REV')),
2129 ('g', 'git', None, _('use git extended diff format')),
2130 ('g', 'git', None, _('use git extended diff format')),
2130 ('P', 'push', None, _('qpush after importing'))],
2131 ('P', 'push', None, _('qpush after importing'))],
2131 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'))
2132 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'))
2132 def qimport(ui, repo, *filename, **opts):
2133 def qimport(ui, repo, *filename, **opts):
2133 """import a patch or existing changeset
2134 """import a patch or existing changeset
2134
2135
2135 The patch is inserted into the series after the last applied
2136 The patch is inserted into the series after the last applied
2136 patch. If no patches have been applied, qimport prepends the patch
2137 patch. If no patches have been applied, qimport prepends the patch
2137 to the series.
2138 to the series.
2138
2139
2139 The patch will have the same name as its source file unless you
2140 The patch will have the same name as its source file unless you
2140 give it a new one with -n/--name.
2141 give it a new one with -n/--name.
2141
2142
2142 You can register an existing patch inside the patch directory with
2143 You can register an existing patch inside the patch directory with
2143 the -e/--existing flag.
2144 the -e/--existing flag.
2144
2145
2145 With -f/--force, an existing patch of the same name will be
2146 With -f/--force, an existing patch of the same name will be
2146 overwritten.
2147 overwritten.
2147
2148
2148 An existing changeset may be placed under mq control with -r/--rev
2149 An existing changeset may be placed under mq control with -r/--rev
2149 (e.g. qimport --rev . -n patch will place the current revision
2150 (e.g. qimport --rev . -n patch will place the current revision
2150 under mq control). With -g/--git, patches imported with --rev will
2151 under mq control). With -g/--git, patches imported with --rev will
2151 use the git diff format. See the diffs help topic for information
2152 use the git diff format. See the diffs help topic for information
2152 on why this is important for preserving rename/copy information
2153 on why this is important for preserving rename/copy information
2153 and permission changes. Use :hg:`qfinish` to remove changesets
2154 and permission changes. Use :hg:`qfinish` to remove changesets
2154 from mq control.
2155 from mq control.
2155
2156
2156 To import a patch from standard input, pass - as the patch file.
2157 To import a patch from standard input, pass - as the patch file.
2157 When importing from standard input, a patch name must be specified
2158 When importing from standard input, a patch name must be specified
2158 using the --name flag.
2159 using the --name flag.
2159
2160
2160 To import an existing patch while renaming it::
2161 To import an existing patch while renaming it::
2161
2162
2162 hg qimport -e existing-patch -n new-name
2163 hg qimport -e existing-patch -n new-name
2163
2164
2164 Returns 0 if import succeeded.
2165 Returns 0 if import succeeded.
2165 """
2166 """
2166 lock = repo.lock() # cause this may move phase
2167 lock = repo.lock() # cause this may move phase
2167 try:
2168 try:
2168 q = repo.mq
2169 q = repo.mq
2169 try:
2170 try:
2170 imported = q.qimport(
2171 imported = q.qimport(
2171 repo, filename, patchname=opts.get('name'),
2172 repo, filename, patchname=opts.get('name'),
2172 existing=opts.get('existing'), force=opts.get('force'),
2173 existing=opts.get('existing'), force=opts.get('force'),
2173 rev=opts.get('rev'), git=opts.get('git'))
2174 rev=opts.get('rev'), git=opts.get('git'))
2174 finally:
2175 finally:
2175 q.savedirty()
2176 q.savedirty()
2176 finally:
2177 finally:
2177 lock.release()
2178 lock.release()
2178
2179
2179 if imported and opts.get('push') and not opts.get('rev'):
2180 if imported and opts.get('push') and not opts.get('rev'):
2180 return q.push(repo, imported[-1])
2181 return q.push(repo, imported[-1])
2181 return 0
2182 return 0
2182
2183
2183 def qinit(ui, repo, create):
2184 def qinit(ui, repo, create):
2184 """initialize a new queue repository
2185 """initialize a new queue repository
2185
2186
2186 This command also creates a series file for ordering patches, and
2187 This command also creates a series file for ordering patches, and
2187 an mq-specific .hgignore file in the queue repository, to exclude
2188 an mq-specific .hgignore file in the queue repository, to exclude
2188 the status and guards files (these contain mostly transient state).
2189 the status and guards files (these contain mostly transient state).
2189
2190
2190 Returns 0 if initialization succeeded."""
2191 Returns 0 if initialization succeeded."""
2191 q = repo.mq
2192 q = repo.mq
2192 r = q.init(repo, create)
2193 r = q.init(repo, create)
2193 q.savedirty()
2194 q.savedirty()
2194 if r:
2195 if r:
2195 if not os.path.exists(r.wjoin('.hgignore')):
2196 if not os.path.exists(r.wjoin('.hgignore')):
2196 fp = r.wopener('.hgignore', 'w')
2197 fp = r.wopener('.hgignore', 'w')
2197 fp.write('^\\.hg\n')
2198 fp.write('^\\.hg\n')
2198 fp.write('^\\.mq\n')
2199 fp.write('^\\.mq\n')
2199 fp.write('syntax: glob\n')
2200 fp.write('syntax: glob\n')
2200 fp.write('status\n')
2201 fp.write('status\n')
2201 fp.write('guards\n')
2202 fp.write('guards\n')
2202 fp.close()
2203 fp.close()
2203 if not os.path.exists(r.wjoin('series')):
2204 if not os.path.exists(r.wjoin('series')):
2204 r.wopener('series', 'w').close()
2205 r.wopener('series', 'w').close()
2205 r[None].add(['.hgignore', 'series'])
2206 r[None].add(['.hgignore', 'series'])
2206 commands.add(ui, r)
2207 commands.add(ui, r)
2207 return 0
2208 return 0
2208
2209
2209 @command("^qinit",
2210 @command("^qinit",
2210 [('c', 'create-repo', None, _('create queue repository'))],
2211 [('c', 'create-repo', None, _('create queue repository'))],
2211 _('hg qinit [-c]'))
2212 _('hg qinit [-c]'))
2212 def init(ui, repo, **opts):
2213 def init(ui, repo, **opts):
2213 """init a new queue repository (DEPRECATED)
2214 """init a new queue repository (DEPRECATED)
2214
2215
2215 The queue repository is unversioned by default. If
2216 The queue repository is unversioned by default. If
2216 -c/--create-repo is specified, qinit will create a separate nested
2217 -c/--create-repo is specified, qinit will create a separate nested
2217 repository for patches (qinit -c may also be run later to convert
2218 repository for patches (qinit -c may also be run later to convert
2218 an unversioned patch repository into a versioned one). You can use
2219 an unversioned patch repository into a versioned one). You can use
2219 qcommit to commit changes to this queue repository.
2220 qcommit to commit changes to this queue repository.
2220
2221
2221 This command is deprecated. Without -c, it's implied by other relevant
2222 This command is deprecated. Without -c, it's implied by other relevant
2222 commands. With -c, use :hg:`init --mq` instead."""
2223 commands. With -c, use :hg:`init --mq` instead."""
2223 return qinit(ui, repo, create=opts.get('create_repo'))
2224 return qinit(ui, repo, create=opts.get('create_repo'))
2224
2225
2225 @command("qclone",
2226 @command("qclone",
2226 [('', 'pull', None, _('use pull protocol to copy metadata')),
2227 [('', 'pull', None, _('use pull protocol to copy metadata')),
2227 ('U', 'noupdate', None,
2228 ('U', 'noupdate', None,
2228 _('do not update the new working directories')),
2229 _('do not update the new working directories')),
2229 ('', 'uncompressed', None,
2230 ('', 'uncompressed', None,
2230 _('use uncompressed transfer (fast over LAN)')),
2231 _('use uncompressed transfer (fast over LAN)')),
2231 ('p', 'patches', '',
2232 ('p', 'patches', '',
2232 _('location of source patch repository'), _('REPO')),
2233 _('location of source patch repository'), _('REPO')),
2233 ] + commands.remoteopts,
2234 ] + commands.remoteopts,
2234 _('hg qclone [OPTION]... SOURCE [DEST]'))
2235 _('hg qclone [OPTION]... SOURCE [DEST]'))
2235 def clone(ui, source, dest=None, **opts):
2236 def clone(ui, source, dest=None, **opts):
2236 '''clone main and patch repository at same time
2237 '''clone main and patch repository at same time
2237
2238
2238 If source is local, destination will have no patches applied. If
2239 If source is local, destination will have no patches applied. If
2239 source is remote, this command can not check if patches are
2240 source is remote, this command can not check if patches are
2240 applied in source, so cannot guarantee that patches are not
2241 applied in source, so cannot guarantee that patches are not
2241 applied in destination. If you clone remote repository, be sure
2242 applied in destination. If you clone remote repository, be sure
2242 before that it has no patches applied.
2243 before that it has no patches applied.
2243
2244
2244 Source patch repository is looked for in <src>/.hg/patches by
2245 Source patch repository is looked for in <src>/.hg/patches by
2245 default. Use -p <url> to change.
2246 default. Use -p <url> to change.
2246
2247
2247 The patch directory must be a nested Mercurial repository, as
2248 The patch directory must be a nested Mercurial repository, as
2248 would be created by :hg:`init --mq`.
2249 would be created by :hg:`init --mq`.
2249
2250
2250 Return 0 on success.
2251 Return 0 on success.
2251 '''
2252 '''
2252 def patchdir(repo):
2253 def patchdir(repo):
2253 """compute a patch repo url from a repo object"""
2254 """compute a patch repo url from a repo object"""
2254 url = repo.url()
2255 url = repo.url()
2255 if url.endswith('/'):
2256 if url.endswith('/'):
2256 url = url[:-1]
2257 url = url[:-1]
2257 return url + '/.hg/patches'
2258 return url + '/.hg/patches'
2258
2259
2259 # main repo (destination and sources)
2260 # main repo (destination and sources)
2260 if dest is None:
2261 if dest is None:
2261 dest = hg.defaultdest(source)
2262 dest = hg.defaultdest(source)
2262 sr = hg.peer(ui, opts, ui.expandpath(source))
2263 sr = hg.peer(ui, opts, ui.expandpath(source))
2263
2264
2264 # patches repo (source only)
2265 # patches repo (source only)
2265 if opts.get('patches'):
2266 if opts.get('patches'):
2266 patchespath = ui.expandpath(opts.get('patches'))
2267 patchespath = ui.expandpath(opts.get('patches'))
2267 else:
2268 else:
2268 patchespath = patchdir(sr)
2269 patchespath = patchdir(sr)
2269 try:
2270 try:
2270 hg.peer(ui, opts, patchespath)
2271 hg.peer(ui, opts, patchespath)
2271 except error.RepoError:
2272 except error.RepoError:
2272 raise util.Abort(_('versioned patch repository not found'
2273 raise util.Abort(_('versioned patch repository not found'
2273 ' (see init --mq)'))
2274 ' (see init --mq)'))
2274 qbase, destrev = None, None
2275 qbase, destrev = None, None
2275 if sr.local():
2276 if sr.local():
2276 repo = sr.local()
2277 repo = sr.local()
2277 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2278 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2278 qbase = repo.mq.applied[0].node
2279 qbase = repo.mq.applied[0].node
2279 if not hg.islocal(dest):
2280 if not hg.islocal(dest):
2280 heads = set(repo.heads())
2281 heads = set(repo.heads())
2281 destrev = list(heads.difference(repo.heads(qbase)))
2282 destrev = list(heads.difference(repo.heads(qbase)))
2282 destrev.append(repo.changelog.parents(qbase)[0])
2283 destrev.append(repo.changelog.parents(qbase)[0])
2283 elif sr.capable('lookup'):
2284 elif sr.capable('lookup'):
2284 try:
2285 try:
2285 qbase = sr.lookup('qbase')
2286 qbase = sr.lookup('qbase')
2286 except error.RepoError:
2287 except error.RepoError:
2287 pass
2288 pass
2288
2289
2289 ui.note(_('cloning main repository\n'))
2290 ui.note(_('cloning main repository\n'))
2290 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2291 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2291 pull=opts.get('pull'),
2292 pull=opts.get('pull'),
2292 rev=destrev,
2293 rev=destrev,
2293 update=False,
2294 update=False,
2294 stream=opts.get('uncompressed'))
2295 stream=opts.get('uncompressed'))
2295
2296
2296 ui.note(_('cloning patch repository\n'))
2297 ui.note(_('cloning patch repository\n'))
2297 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2298 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2298 pull=opts.get('pull'), update=not opts.get('noupdate'),
2299 pull=opts.get('pull'), update=not opts.get('noupdate'),
2299 stream=opts.get('uncompressed'))
2300 stream=opts.get('uncompressed'))
2300
2301
2301 if dr.local():
2302 if dr.local():
2302 repo = dr.local()
2303 repo = dr.local()
2303 if qbase:
2304 if qbase:
2304 ui.note(_('stripping applied patches from destination '
2305 ui.note(_('stripping applied patches from destination '
2305 'repository\n'))
2306 'repository\n'))
2306 strip(ui, repo, [qbase], update=False, backup=None)
2307 strip(ui, repo, [qbase], update=False, backup=None)
2307 if not opts.get('noupdate'):
2308 if not opts.get('noupdate'):
2308 ui.note(_('updating destination repository\n'))
2309 ui.note(_('updating destination repository\n'))
2309 hg.update(repo, repo.changelog.tip())
2310 hg.update(repo, repo.changelog.tip())
2310
2311
2311 @command("qcommit|qci",
2312 @command("qcommit|qci",
2312 commands.table["^commit|ci"][1],
2313 commands.table["^commit|ci"][1],
2313 _('hg qcommit [OPTION]... [FILE]...'))
2314 _('hg qcommit [OPTION]... [FILE]...'))
2314 def commit(ui, repo, *pats, **opts):
2315 def commit(ui, repo, *pats, **opts):
2315 """commit changes in the queue repository (DEPRECATED)
2316 """commit changes in the queue repository (DEPRECATED)
2316
2317
2317 This command is deprecated; use :hg:`commit --mq` instead."""
2318 This command is deprecated; use :hg:`commit --mq` instead."""
2318 q = repo.mq
2319 q = repo.mq
2319 r = q.qrepo()
2320 r = q.qrepo()
2320 if not r:
2321 if not r:
2321 raise util.Abort('no queue repository')
2322 raise util.Abort('no queue repository')
2322 commands.commit(r.ui, r, *pats, **opts)
2323 commands.commit(r.ui, r, *pats, **opts)
2323
2324
2324 @command("qseries",
2325 @command("qseries",
2325 [('m', 'missing', None, _('print patches not in series')),
2326 [('m', 'missing', None, _('print patches not in series')),
2326 ] + seriesopts,
2327 ] + seriesopts,
2327 _('hg qseries [-ms]'))
2328 _('hg qseries [-ms]'))
2328 def series(ui, repo, **opts):
2329 def series(ui, repo, **opts):
2329 """print the entire series file
2330 """print the entire series file
2330
2331
2331 Returns 0 on success."""
2332 Returns 0 on success."""
2332 repo.mq.qseries(repo, missing=opts.get('missing'),
2333 repo.mq.qseries(repo, missing=opts.get('missing'),
2333 summary=opts.get('summary'))
2334 summary=opts.get('summary'))
2334 return 0
2335 return 0
2335
2336
2336 @command("qtop", seriesopts, _('hg qtop [-s]'))
2337 @command("qtop", seriesopts, _('hg qtop [-s]'))
2337 def top(ui, repo, **opts):
2338 def top(ui, repo, **opts):
2338 """print the name of the current patch
2339 """print the name of the current patch
2339
2340
2340 Returns 0 on success."""
2341 Returns 0 on success."""
2341 q = repo.mq
2342 q = repo.mq
2342 t = q.applied and q.seriesend(True) or 0
2343 t = q.applied and q.seriesend(True) or 0
2343 if t:
2344 if t:
2344 q.qseries(repo, start=t - 1, length=1, status='A',
2345 q.qseries(repo, start=t - 1, length=1, status='A',
2345 summary=opts.get('summary'))
2346 summary=opts.get('summary'))
2346 else:
2347 else:
2347 ui.write(_("no patches applied\n"))
2348 ui.write(_("no patches applied\n"))
2348 return 1
2349 return 1
2349
2350
2350 @command("qnext", seriesopts, _('hg qnext [-s]'))
2351 @command("qnext", seriesopts, _('hg qnext [-s]'))
2351 def next(ui, repo, **opts):
2352 def next(ui, repo, **opts):
2352 """print the name of the next pushable patch
2353 """print the name of the next pushable patch
2353
2354
2354 Returns 0 on success."""
2355 Returns 0 on success."""
2355 q = repo.mq
2356 q = repo.mq
2356 end = q.seriesend()
2357 end = q.seriesend()
2357 if end == len(q.series):
2358 if end == len(q.series):
2358 ui.write(_("all patches applied\n"))
2359 ui.write(_("all patches applied\n"))
2359 return 1
2360 return 1
2360 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2361 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2361
2362
2362 @command("qprev", seriesopts, _('hg qprev [-s]'))
2363 @command("qprev", seriesopts, _('hg qprev [-s]'))
2363 def prev(ui, repo, **opts):
2364 def prev(ui, repo, **opts):
2364 """print the name of the preceding applied patch
2365 """print the name of the preceding applied patch
2365
2366
2366 Returns 0 on success."""
2367 Returns 0 on success."""
2367 q = repo.mq
2368 q = repo.mq
2368 l = len(q.applied)
2369 l = len(q.applied)
2369 if l == 1:
2370 if l == 1:
2370 ui.write(_("only one patch applied\n"))
2371 ui.write(_("only one patch applied\n"))
2371 return 1
2372 return 1
2372 if not l:
2373 if not l:
2373 ui.write(_("no patches applied\n"))
2374 ui.write(_("no patches applied\n"))
2374 return 1
2375 return 1
2375 idx = q.series.index(q.applied[-2].name)
2376 idx = q.series.index(q.applied[-2].name)
2376 q.qseries(repo, start=idx, length=1, status='A',
2377 q.qseries(repo, start=idx, length=1, status='A',
2377 summary=opts.get('summary'))
2378 summary=opts.get('summary'))
2378
2379
2379 def setupheaderopts(ui, opts):
2380 def setupheaderopts(ui, opts):
2380 if not opts.get('user') and opts.get('currentuser'):
2381 if not opts.get('user') and opts.get('currentuser'):
2381 opts['user'] = ui.username()
2382 opts['user'] = ui.username()
2382 if not opts.get('date') and opts.get('currentdate'):
2383 if not opts.get('date') and opts.get('currentdate'):
2383 opts['date'] = "%d %d" % util.makedate()
2384 opts['date'] = "%d %d" % util.makedate()
2384
2385
2385 @command("^qnew",
2386 @command("^qnew",
2386 [('e', 'edit', None, _('edit commit message')),
2387 [('e', 'edit', None, _('edit commit message')),
2387 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2388 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2388 ('g', 'git', None, _('use git extended diff format')),
2389 ('g', 'git', None, _('use git extended diff format')),
2389 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2390 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2390 ('u', 'user', '',
2391 ('u', 'user', '',
2391 _('add "From: <USER>" to patch'), _('USER')),
2392 _('add "From: <USER>" to patch'), _('USER')),
2392 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2393 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2393 ('d', 'date', '',
2394 ('d', 'date', '',
2394 _('add "Date: <DATE>" to patch'), _('DATE'))
2395 _('add "Date: <DATE>" to patch'), _('DATE'))
2395 ] + commands.walkopts + commands.commitopts,
2396 ] + commands.walkopts + commands.commitopts,
2396 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2397 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2397 def new(ui, repo, patch, *args, **opts):
2398 def new(ui, repo, patch, *args, **opts):
2398 """create a new patch
2399 """create a new patch
2399
2400
2400 qnew creates a new patch on top of the currently-applied patch (if
2401 qnew creates a new patch on top of the currently-applied patch (if
2401 any). The patch will be initialized with any outstanding changes
2402 any). The patch will be initialized with any outstanding changes
2402 in the working directory. You may also use -I/--include,
2403 in the working directory. You may also use -I/--include,
2403 -X/--exclude, and/or a list of files after the patch name to add
2404 -X/--exclude, and/or a list of files after the patch name to add
2404 only changes to matching files to the new patch, leaving the rest
2405 only changes to matching files to the new patch, leaving the rest
2405 as uncommitted modifications.
2406 as uncommitted modifications.
2406
2407
2407 -u/--user and -d/--date can be used to set the (given) user and
2408 -u/--user and -d/--date can be used to set the (given) user and
2408 date, respectively. -U/--currentuser and -D/--currentdate set user
2409 date, respectively. -U/--currentuser and -D/--currentdate set user
2409 to current user and date to current date.
2410 to current user and date to current date.
2410
2411
2411 -e/--edit, -m/--message or -l/--logfile set the patch header as
2412 -e/--edit, -m/--message or -l/--logfile set the patch header as
2412 well as the commit message. If none is specified, the header is
2413 well as the commit message. If none is specified, the header is
2413 empty and the commit message is '[mq]: PATCH'.
2414 empty and the commit message is '[mq]: PATCH'.
2414
2415
2415 Use the -g/--git option to keep the patch in the git extended diff
2416 Use the -g/--git option to keep the patch in the git extended diff
2416 format. Read the diffs help topic for more information on why this
2417 format. Read the diffs help topic for more information on why this
2417 is important for preserving permission changes and copy/rename
2418 is important for preserving permission changes and copy/rename
2418 information.
2419 information.
2419
2420
2420 Returns 0 on successful creation of a new patch.
2421 Returns 0 on successful creation of a new patch.
2421 """
2422 """
2422 msg = cmdutil.logmessage(ui, opts)
2423 msg = cmdutil.logmessage(ui, opts)
2423 def getmsg():
2424 def getmsg():
2424 return ui.edit(msg, opts.get('user') or ui.username())
2425 return ui.edit(msg, opts.get('user') or ui.username())
2425 q = repo.mq
2426 q = repo.mq
2426 opts['msg'] = msg
2427 opts['msg'] = msg
2427 if opts.get('edit'):
2428 if opts.get('edit'):
2428 opts['msg'] = getmsg
2429 opts['msg'] = getmsg
2429 else:
2430 else:
2430 opts['msg'] = msg
2431 opts['msg'] = msg
2431 setupheaderopts(ui, opts)
2432 setupheaderopts(ui, opts)
2432 q.new(repo, patch, *args, **opts)
2433 q.new(repo, patch, *args, **opts)
2433 q.savedirty()
2434 q.savedirty()
2434 return 0
2435 return 0
2435
2436
2436 @command("^qrefresh",
2437 @command("^qrefresh",
2437 [('e', 'edit', None, _('edit commit message')),
2438 [('e', 'edit', None, _('edit commit message')),
2438 ('g', 'git', None, _('use git extended diff format')),
2439 ('g', 'git', None, _('use git extended diff format')),
2439 ('s', 'short', None,
2440 ('s', 'short', None,
2440 _('refresh only files already in the patch and specified files')),
2441 _('refresh only files already in the patch and specified files')),
2441 ('U', 'currentuser', None,
2442 ('U', 'currentuser', None,
2442 _('add/update author field in patch with current user')),
2443 _('add/update author field in patch with current user')),
2443 ('u', 'user', '',
2444 ('u', 'user', '',
2444 _('add/update author field in patch with given user'), _('USER')),
2445 _('add/update author field in patch with given user'), _('USER')),
2445 ('D', 'currentdate', None,
2446 ('D', 'currentdate', None,
2446 _('add/update date field in patch with current date')),
2447 _('add/update date field in patch with current date')),
2447 ('d', 'date', '',
2448 ('d', 'date', '',
2448 _('add/update date field in patch with given date'), _('DATE'))
2449 _('add/update date field in patch with given date'), _('DATE'))
2449 ] + commands.walkopts + commands.commitopts,
2450 ] + commands.walkopts + commands.commitopts,
2450 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2451 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2451 def refresh(ui, repo, *pats, **opts):
2452 def refresh(ui, repo, *pats, **opts):
2452 """update the current patch
2453 """update the current patch
2453
2454
2454 If any file patterns are provided, the refreshed patch will
2455 If any file patterns are provided, the refreshed patch will
2455 contain only the modifications that match those patterns; the
2456 contain only the modifications that match those patterns; the
2456 remaining modifications will remain in the working directory.
2457 remaining modifications will remain in the working directory.
2457
2458
2458 If -s/--short is specified, files currently included in the patch
2459 If -s/--short is specified, files currently included in the patch
2459 will be refreshed just like matched files and remain in the patch.
2460 will be refreshed just like matched files and remain in the patch.
2460
2461
2461 If -e/--edit is specified, Mercurial will start your configured editor for
2462 If -e/--edit is specified, Mercurial will start your configured editor for
2462 you to enter a message. In case qrefresh fails, you will find a backup of
2463 you to enter a message. In case qrefresh fails, you will find a backup of
2463 your message in ``.hg/last-message.txt``.
2464 your message in ``.hg/last-message.txt``.
2464
2465
2465 hg add/remove/copy/rename work as usual, though you might want to
2466 hg add/remove/copy/rename work as usual, though you might want to
2466 use git-style patches (-g/--git or [diff] git=1) to track copies
2467 use git-style patches (-g/--git or [diff] git=1) to track copies
2467 and renames. See the diffs help topic for more information on the
2468 and renames. See the diffs help topic for more information on the
2468 git diff format.
2469 git diff format.
2469
2470
2470 Returns 0 on success.
2471 Returns 0 on success.
2471 """
2472 """
2472 q = repo.mq
2473 q = repo.mq
2473 message = cmdutil.logmessage(ui, opts)
2474 message = cmdutil.logmessage(ui, opts)
2474 if opts.get('edit'):
2475 if opts.get('edit'):
2475 if not q.applied:
2476 if not q.applied:
2476 ui.write(_("no patches applied\n"))
2477 ui.write(_("no patches applied\n"))
2477 return 1
2478 return 1
2478 if message:
2479 if message:
2479 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2480 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2480 patch = q.applied[-1].name
2481 patch = q.applied[-1].name
2481 ph = patchheader(q.join(patch), q.plainmode)
2482 ph = patchheader(q.join(patch), q.plainmode)
2482 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2483 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2483 # We don't want to lose the patch message if qrefresh fails (issue2062)
2484 # We don't want to lose the patch message if qrefresh fails (issue2062)
2484 repo.savecommitmessage(message)
2485 repo.savecommitmessage(message)
2485 setupheaderopts(ui, opts)
2486 setupheaderopts(ui, opts)
2486 wlock = repo.wlock()
2487 wlock = repo.wlock()
2487 try:
2488 try:
2488 ret = q.refresh(repo, pats, msg=message, **opts)
2489 ret = q.refresh(repo, pats, msg=message, **opts)
2489 q.savedirty()
2490 q.savedirty()
2490 return ret
2491 return ret
2491 finally:
2492 finally:
2492 wlock.release()
2493 wlock.release()
2493
2494
2494 @command("^qdiff",
2495 @command("^qdiff",
2495 commands.diffopts + commands.diffopts2 + commands.walkopts,
2496 commands.diffopts + commands.diffopts2 + commands.walkopts,
2496 _('hg qdiff [OPTION]... [FILE]...'))
2497 _('hg qdiff [OPTION]... [FILE]...'))
2497 def diff(ui, repo, *pats, **opts):
2498 def diff(ui, repo, *pats, **opts):
2498 """diff of the current patch and subsequent modifications
2499 """diff of the current patch and subsequent modifications
2499
2500
2500 Shows a diff which includes the current patch as well as any
2501 Shows a diff which includes the current patch as well as any
2501 changes which have been made in the working directory since the
2502 changes which have been made in the working directory since the
2502 last refresh (thus showing what the current patch would become
2503 last refresh (thus showing what the current patch would become
2503 after a qrefresh).
2504 after a qrefresh).
2504
2505
2505 Use :hg:`diff` if you only want to see the changes made since the
2506 Use :hg:`diff` if you only want to see the changes made since the
2506 last qrefresh, or :hg:`export qtip` if you want to see changes
2507 last qrefresh, or :hg:`export qtip` if you want to see changes
2507 made by the current patch without including changes made since the
2508 made by the current patch without including changes made since the
2508 qrefresh.
2509 qrefresh.
2509
2510
2510 Returns 0 on success.
2511 Returns 0 on success.
2511 """
2512 """
2512 repo.mq.diff(repo, pats, opts)
2513 repo.mq.diff(repo, pats, opts)
2513 return 0
2514 return 0
2514
2515
2515 @command('qfold',
2516 @command('qfold',
2516 [('e', 'edit', None, _('edit patch header')),
2517 [('e', 'edit', None, _('edit patch header')),
2517 ('k', 'keep', None, _('keep folded patch files')),
2518 ('k', 'keep', None, _('keep folded patch files')),
2518 ] + commands.commitopts,
2519 ] + commands.commitopts,
2519 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2520 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2520 def fold(ui, repo, *files, **opts):
2521 def fold(ui, repo, *files, **opts):
2521 """fold the named patches into the current patch
2522 """fold the named patches into the current patch
2522
2523
2523 Patches must not yet be applied. Each patch will be successively
2524 Patches must not yet be applied. Each patch will be successively
2524 applied to the current patch in the order given. If all the
2525 applied to the current patch in the order given. If all the
2525 patches apply successfully, the current patch will be refreshed
2526 patches apply successfully, the current patch will be refreshed
2526 with the new cumulative patch, and the folded patches will be
2527 with the new cumulative patch, and the folded patches will be
2527 deleted. With -k/--keep, the folded patch files will not be
2528 deleted. With -k/--keep, the folded patch files will not be
2528 removed afterwards.
2529 removed afterwards.
2529
2530
2530 The header for each folded patch will be concatenated with the
2531 The header for each folded patch will be concatenated with the
2531 current patch header, separated by a line of ``* * *``.
2532 current patch header, separated by a line of ``* * *``.
2532
2533
2533 Returns 0 on success."""
2534 Returns 0 on success."""
2534 q = repo.mq
2535 q = repo.mq
2535 if not files:
2536 if not files:
2536 raise util.Abort(_('qfold requires at least one patch name'))
2537 raise util.Abort(_('qfold requires at least one patch name'))
2537 if not q.checktoppatch(repo)[0]:
2538 if not q.checktoppatch(repo)[0]:
2538 raise util.Abort(_('no patches applied'))
2539 raise util.Abort(_('no patches applied'))
2539 q.checklocalchanges(repo)
2540 q.checklocalchanges(repo)
2540
2541
2541 message = cmdutil.logmessage(ui, opts)
2542 message = cmdutil.logmessage(ui, opts)
2542 if opts.get('edit'):
2543 if opts.get('edit'):
2543 if message:
2544 if message:
2544 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2545 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2545
2546
2546 parent = q.lookup('qtip')
2547 parent = q.lookup('qtip')
2547 patches = []
2548 patches = []
2548 messages = []
2549 messages = []
2549 for f in files:
2550 for f in files:
2550 p = q.lookup(f)
2551 p = q.lookup(f)
2551 if p in patches or p == parent:
2552 if p in patches or p == parent:
2552 ui.warn(_('skipping already folded patch %s\n') % p)
2553 ui.warn(_('skipping already folded patch %s\n') % p)
2553 if q.isapplied(p):
2554 if q.isapplied(p):
2554 raise util.Abort(_('qfold cannot fold already applied patch %s')
2555 raise util.Abort(_('qfold cannot fold already applied patch %s')
2555 % p)
2556 % p)
2556 patches.append(p)
2557 patches.append(p)
2557
2558
2558 for p in patches:
2559 for p in patches:
2559 if not message:
2560 if not message:
2560 ph = patchheader(q.join(p), q.plainmode)
2561 ph = patchheader(q.join(p), q.plainmode)
2561 if ph.message:
2562 if ph.message:
2562 messages.append(ph.message)
2563 messages.append(ph.message)
2563 pf = q.join(p)
2564 pf = q.join(p)
2564 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2565 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2565 if not patchsuccess:
2566 if not patchsuccess:
2566 raise util.Abort(_('error folding patch %s') % p)
2567 raise util.Abort(_('error folding patch %s') % p)
2567
2568
2568 if not message:
2569 if not message:
2569 ph = patchheader(q.join(parent), q.plainmode)
2570 ph = patchheader(q.join(parent), q.plainmode)
2570 message, user = ph.message, ph.user
2571 message, user = ph.message, ph.user
2571 for msg in messages:
2572 for msg in messages:
2572 if msg:
2573 if msg:
2573 if message:
2574 if message:
2574 message.append('* * *')
2575 message.append('* * *')
2575 message.extend(msg)
2576 message.extend(msg)
2576 message = '\n'.join(message)
2577 message = '\n'.join(message)
2577
2578
2578 if opts.get('edit'):
2579 if opts.get('edit'):
2579 message = ui.edit(message, user or ui.username())
2580 message = ui.edit(message, user or ui.username())
2581 repo.savecommitmessage(message)
2580
2582
2581 diffopts = q.patchopts(q.diffopts(), *patches)
2583 diffopts = q.patchopts(q.diffopts(), *patches)
2582 wlock = repo.wlock()
2584 wlock = repo.wlock()
2583 try:
2585 try:
2584 q.refresh(repo, msg=message, git=diffopts.git)
2586 q.refresh(repo, msg=message, git=diffopts.git)
2585 q.delete(repo, patches, opts)
2587 q.delete(repo, patches, opts)
2586 q.savedirty()
2588 q.savedirty()
2587 finally:
2589 finally:
2588 wlock.release()
2590 wlock.release()
2589
2591
2590 @command("qgoto",
2592 @command("qgoto",
2591 [('', 'keep-changes', None,
2593 [('', 'keep-changes', None,
2592 _('tolerate non-conflicting local changes')),
2594 _('tolerate non-conflicting local changes')),
2593 ('f', 'force', None, _('overwrite any local changes')),
2595 ('f', 'force', None, _('overwrite any local changes')),
2594 ('', 'no-backup', None, _('do not save backup copies of files'))],
2596 ('', 'no-backup', None, _('do not save backup copies of files'))],
2595 _('hg qgoto [OPTION]... PATCH'))
2597 _('hg qgoto [OPTION]... PATCH'))
2596 def goto(ui, repo, patch, **opts):
2598 def goto(ui, repo, patch, **opts):
2597 '''push or pop patches until named patch is at top of stack
2599 '''push or pop patches until named patch is at top of stack
2598
2600
2599 Returns 0 on success.'''
2601 Returns 0 on success.'''
2600 opts = fixkeepchangesopts(ui, opts)
2602 opts = fixkeepchangesopts(ui, opts)
2601 q = repo.mq
2603 q = repo.mq
2602 patch = q.lookup(patch)
2604 patch = q.lookup(patch)
2603 nobackup = opts.get('no_backup')
2605 nobackup = opts.get('no_backup')
2604 keepchanges = opts.get('keep_changes')
2606 keepchanges = opts.get('keep_changes')
2605 if q.isapplied(patch):
2607 if q.isapplied(patch):
2606 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2608 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2607 keepchanges=keepchanges)
2609 keepchanges=keepchanges)
2608 else:
2610 else:
2609 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2611 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2610 keepchanges=keepchanges)
2612 keepchanges=keepchanges)
2611 q.savedirty()
2613 q.savedirty()
2612 return ret
2614 return ret
2613
2615
2614 @command("qguard",
2616 @command("qguard",
2615 [('l', 'list', None, _('list all patches and guards')),
2617 [('l', 'list', None, _('list all patches and guards')),
2616 ('n', 'none', None, _('drop all guards'))],
2618 ('n', 'none', None, _('drop all guards'))],
2617 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2619 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2618 def guard(ui, repo, *args, **opts):
2620 def guard(ui, repo, *args, **opts):
2619 '''set or print guards for a patch
2621 '''set or print guards for a patch
2620
2622
2621 Guards control whether a patch can be pushed. A patch with no
2623 Guards control whether a patch can be pushed. A patch with no
2622 guards is always pushed. A patch with a positive guard ("+foo") is
2624 guards is always pushed. A patch with a positive guard ("+foo") is
2623 pushed only if the :hg:`qselect` command has activated it. A patch with
2625 pushed only if the :hg:`qselect` command has activated it. A patch with
2624 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2626 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2625 has activated it.
2627 has activated it.
2626
2628
2627 With no arguments, print the currently active guards.
2629 With no arguments, print the currently active guards.
2628 With arguments, set guards for the named patch.
2630 With arguments, set guards for the named patch.
2629
2631
2630 .. note::
2632 .. note::
2631
2633
2632 Specifying negative guards now requires '--'.
2634 Specifying negative guards now requires '--'.
2633
2635
2634 To set guards on another patch::
2636 To set guards on another patch::
2635
2637
2636 hg qguard other.patch -- +2.6.17 -stable
2638 hg qguard other.patch -- +2.6.17 -stable
2637
2639
2638 Returns 0 on success.
2640 Returns 0 on success.
2639 '''
2641 '''
2640 def status(idx):
2642 def status(idx):
2641 guards = q.seriesguards[idx] or ['unguarded']
2643 guards = q.seriesguards[idx] or ['unguarded']
2642 if q.series[idx] in applied:
2644 if q.series[idx] in applied:
2643 state = 'applied'
2645 state = 'applied'
2644 elif q.pushable(idx)[0]:
2646 elif q.pushable(idx)[0]:
2645 state = 'unapplied'
2647 state = 'unapplied'
2646 else:
2648 else:
2647 state = 'guarded'
2649 state = 'guarded'
2648 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2650 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2649 ui.write('%s: ' % ui.label(q.series[idx], label))
2651 ui.write('%s: ' % ui.label(q.series[idx], label))
2650
2652
2651 for i, guard in enumerate(guards):
2653 for i, guard in enumerate(guards):
2652 if guard.startswith('+'):
2654 if guard.startswith('+'):
2653 ui.write(guard, label='qguard.positive')
2655 ui.write(guard, label='qguard.positive')
2654 elif guard.startswith('-'):
2656 elif guard.startswith('-'):
2655 ui.write(guard, label='qguard.negative')
2657 ui.write(guard, label='qguard.negative')
2656 else:
2658 else:
2657 ui.write(guard, label='qguard.unguarded')
2659 ui.write(guard, label='qguard.unguarded')
2658 if i != len(guards) - 1:
2660 if i != len(guards) - 1:
2659 ui.write(' ')
2661 ui.write(' ')
2660 ui.write('\n')
2662 ui.write('\n')
2661 q = repo.mq
2663 q = repo.mq
2662 applied = set(p.name for p in q.applied)
2664 applied = set(p.name for p in q.applied)
2663 patch = None
2665 patch = None
2664 args = list(args)
2666 args = list(args)
2665 if opts.get('list'):
2667 if opts.get('list'):
2666 if args or opts.get('none'):
2668 if args or opts.get('none'):
2667 raise util.Abort(_('cannot mix -l/--list with options or '
2669 raise util.Abort(_('cannot mix -l/--list with options or '
2668 'arguments'))
2670 'arguments'))
2669 for i in xrange(len(q.series)):
2671 for i in xrange(len(q.series)):
2670 status(i)
2672 status(i)
2671 return
2673 return
2672 if not args or args[0][0:1] in '-+':
2674 if not args or args[0][0:1] in '-+':
2673 if not q.applied:
2675 if not q.applied:
2674 raise util.Abort(_('no patches applied'))
2676 raise util.Abort(_('no patches applied'))
2675 patch = q.applied[-1].name
2677 patch = q.applied[-1].name
2676 if patch is None and args[0][0:1] not in '-+':
2678 if patch is None and args[0][0:1] not in '-+':
2677 patch = args.pop(0)
2679 patch = args.pop(0)
2678 if patch is None:
2680 if patch is None:
2679 raise util.Abort(_('no patch to work with'))
2681 raise util.Abort(_('no patch to work with'))
2680 if args or opts.get('none'):
2682 if args or opts.get('none'):
2681 idx = q.findseries(patch)
2683 idx = q.findseries(patch)
2682 if idx is None:
2684 if idx is None:
2683 raise util.Abort(_('no patch named %s') % patch)
2685 raise util.Abort(_('no patch named %s') % patch)
2684 q.setguards(idx, args)
2686 q.setguards(idx, args)
2685 q.savedirty()
2687 q.savedirty()
2686 else:
2688 else:
2687 status(q.series.index(q.lookup(patch)))
2689 status(q.series.index(q.lookup(patch)))
2688
2690
2689 @command("qheader", [], _('hg qheader [PATCH]'))
2691 @command("qheader", [], _('hg qheader [PATCH]'))
2690 def header(ui, repo, patch=None):
2692 def header(ui, repo, patch=None):
2691 """print the header of the topmost or specified patch
2693 """print the header of the topmost or specified patch
2692
2694
2693 Returns 0 on success."""
2695 Returns 0 on success."""
2694 q = repo.mq
2696 q = repo.mq
2695
2697
2696 if patch:
2698 if patch:
2697 patch = q.lookup(patch)
2699 patch = q.lookup(patch)
2698 else:
2700 else:
2699 if not q.applied:
2701 if not q.applied:
2700 ui.write(_('no patches applied\n'))
2702 ui.write(_('no patches applied\n'))
2701 return 1
2703 return 1
2702 patch = q.lookup('qtip')
2704 patch = q.lookup('qtip')
2703 ph = patchheader(q.join(patch), q.plainmode)
2705 ph = patchheader(q.join(patch), q.plainmode)
2704
2706
2705 ui.write('\n'.join(ph.message) + '\n')
2707 ui.write('\n'.join(ph.message) + '\n')
2706
2708
2707 def lastsavename(path):
2709 def lastsavename(path):
2708 (directory, base) = os.path.split(path)
2710 (directory, base) = os.path.split(path)
2709 names = os.listdir(directory)
2711 names = os.listdir(directory)
2710 namere = re.compile("%s.([0-9]+)" % base)
2712 namere = re.compile("%s.([0-9]+)" % base)
2711 maxindex = None
2713 maxindex = None
2712 maxname = None
2714 maxname = None
2713 for f in names:
2715 for f in names:
2714 m = namere.match(f)
2716 m = namere.match(f)
2715 if m:
2717 if m:
2716 index = int(m.group(1))
2718 index = int(m.group(1))
2717 if maxindex is None or index > maxindex:
2719 if maxindex is None or index > maxindex:
2718 maxindex = index
2720 maxindex = index
2719 maxname = f
2721 maxname = f
2720 if maxname:
2722 if maxname:
2721 return (os.path.join(directory, maxname), maxindex)
2723 return (os.path.join(directory, maxname), maxindex)
2722 return (None, None)
2724 return (None, None)
2723
2725
2724 def savename(path):
2726 def savename(path):
2725 (last, index) = lastsavename(path)
2727 (last, index) = lastsavename(path)
2726 if last is None:
2728 if last is None:
2727 index = 0
2729 index = 0
2728 newpath = path + ".%d" % (index + 1)
2730 newpath = path + ".%d" % (index + 1)
2729 return newpath
2731 return newpath
2730
2732
2731 @command("^qpush",
2733 @command("^qpush",
2732 [('', 'keep-changes', None,
2734 [('', 'keep-changes', None,
2733 _('tolerate non-conflicting local changes')),
2735 _('tolerate non-conflicting local changes')),
2734 ('f', 'force', None, _('apply on top of local changes')),
2736 ('f', 'force', None, _('apply on top of local changes')),
2735 ('e', 'exact', None,
2737 ('e', 'exact', None,
2736 _('apply the target patch to its recorded parent')),
2738 _('apply the target patch to its recorded parent')),
2737 ('l', 'list', None, _('list patch name in commit text')),
2739 ('l', 'list', None, _('list patch name in commit text')),
2738 ('a', 'all', None, _('apply all patches')),
2740 ('a', 'all', None, _('apply all patches')),
2739 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2741 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2740 ('n', 'name', '',
2742 ('n', 'name', '',
2741 _('merge queue name (DEPRECATED)'), _('NAME')),
2743 _('merge queue name (DEPRECATED)'), _('NAME')),
2742 ('', 'move', None,
2744 ('', 'move', None,
2743 _('reorder patch series and apply only the patch')),
2745 _('reorder patch series and apply only the patch')),
2744 ('', 'no-backup', None, _('do not save backup copies of files'))],
2746 ('', 'no-backup', None, _('do not save backup copies of files'))],
2745 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2747 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2746 def push(ui, repo, patch=None, **opts):
2748 def push(ui, repo, patch=None, **opts):
2747 """push the next patch onto the stack
2749 """push the next patch onto the stack
2748
2750
2749 By default, abort if the working directory contains uncommitted
2751 By default, abort if the working directory contains uncommitted
2750 changes. With --keep-changes, abort only if the uncommitted files
2752 changes. With --keep-changes, abort only if the uncommitted files
2751 overlap with patched files. With -f/--force, backup and patch over
2753 overlap with patched files. With -f/--force, backup and patch over
2752 uncommitted changes.
2754 uncommitted changes.
2753
2755
2754 Return 0 on success.
2756 Return 0 on success.
2755 """
2757 """
2756 q = repo.mq
2758 q = repo.mq
2757 mergeq = None
2759 mergeq = None
2758
2760
2759 opts = fixkeepchangesopts(ui, opts)
2761 opts = fixkeepchangesopts(ui, opts)
2760 if opts.get('merge'):
2762 if opts.get('merge'):
2761 if opts.get('name'):
2763 if opts.get('name'):
2762 newpath = repo.join(opts.get('name'))
2764 newpath = repo.join(opts.get('name'))
2763 else:
2765 else:
2764 newpath, i = lastsavename(q.path)
2766 newpath, i = lastsavename(q.path)
2765 if not newpath:
2767 if not newpath:
2766 ui.warn(_("no saved queues found, please use -n\n"))
2768 ui.warn(_("no saved queues found, please use -n\n"))
2767 return 1
2769 return 1
2768 mergeq = queue(ui, repo.baseui, repo.path, newpath)
2770 mergeq = queue(ui, repo.baseui, repo.path, newpath)
2769 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2771 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2770 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2772 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2771 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2773 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2772 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2774 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2773 keepchanges=opts.get('keep_changes'))
2775 keepchanges=opts.get('keep_changes'))
2774 return ret
2776 return ret
2775
2777
2776 @command("^qpop",
2778 @command("^qpop",
2777 [('a', 'all', None, _('pop all patches')),
2779 [('a', 'all', None, _('pop all patches')),
2778 ('n', 'name', '',
2780 ('n', 'name', '',
2779 _('queue name to pop (DEPRECATED)'), _('NAME')),
2781 _('queue name to pop (DEPRECATED)'), _('NAME')),
2780 ('', 'keep-changes', None,
2782 ('', 'keep-changes', None,
2781 _('tolerate non-conflicting local changes')),
2783 _('tolerate non-conflicting local changes')),
2782 ('f', 'force', None, _('forget any local changes to patched files')),
2784 ('f', 'force', None, _('forget any local changes to patched files')),
2783 ('', 'no-backup', None, _('do not save backup copies of files'))],
2785 ('', 'no-backup', None, _('do not save backup copies of files'))],
2784 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2786 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2785 def pop(ui, repo, patch=None, **opts):
2787 def pop(ui, repo, patch=None, **opts):
2786 """pop the current patch off the stack
2788 """pop the current patch off the stack
2787
2789
2788 Without argument, pops off the top of the patch stack. If given a
2790 Without argument, pops off the top of the patch stack. If given a
2789 patch name, keeps popping off patches until the named patch is at
2791 patch name, keeps popping off patches until the named patch is at
2790 the top of the stack.
2792 the top of the stack.
2791
2793
2792 By default, abort if the working directory contains uncommitted
2794 By default, abort if the working directory contains uncommitted
2793 changes. With --keep-changes, abort only if the uncommitted files
2795 changes. With --keep-changes, abort only if the uncommitted files
2794 overlap with patched files. With -f/--force, backup and discard
2796 overlap with patched files. With -f/--force, backup and discard
2795 changes made to such files.
2797 changes made to such files.
2796
2798
2797 Return 0 on success.
2799 Return 0 on success.
2798 """
2800 """
2799 opts = fixkeepchangesopts(ui, opts)
2801 opts = fixkeepchangesopts(ui, opts)
2800 localupdate = True
2802 localupdate = True
2801 if opts.get('name'):
2803 if opts.get('name'):
2802 q = queue(ui, repo.baseui, repo.path, repo.join(opts.get('name')))
2804 q = queue(ui, repo.baseui, repo.path, repo.join(opts.get('name')))
2803 ui.warn(_('using patch queue: %s\n') % q.path)
2805 ui.warn(_('using patch queue: %s\n') % q.path)
2804 localupdate = False
2806 localupdate = False
2805 else:
2807 else:
2806 q = repo.mq
2808 q = repo.mq
2807 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2809 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2808 all=opts.get('all'), nobackup=opts.get('no_backup'),
2810 all=opts.get('all'), nobackup=opts.get('no_backup'),
2809 keepchanges=opts.get('keep_changes'))
2811 keepchanges=opts.get('keep_changes'))
2810 q.savedirty()
2812 q.savedirty()
2811 return ret
2813 return ret
2812
2814
2813 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2815 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2814 def rename(ui, repo, patch, name=None, **opts):
2816 def rename(ui, repo, patch, name=None, **opts):
2815 """rename a patch
2817 """rename a patch
2816
2818
2817 With one argument, renames the current patch to PATCH1.
2819 With one argument, renames the current patch to PATCH1.
2818 With two arguments, renames PATCH1 to PATCH2.
2820 With two arguments, renames PATCH1 to PATCH2.
2819
2821
2820 Returns 0 on success."""
2822 Returns 0 on success."""
2821 q = repo.mq
2823 q = repo.mq
2822 if not name:
2824 if not name:
2823 name = patch
2825 name = patch
2824 patch = None
2826 patch = None
2825
2827
2826 if patch:
2828 if patch:
2827 patch = q.lookup(patch)
2829 patch = q.lookup(patch)
2828 else:
2830 else:
2829 if not q.applied:
2831 if not q.applied:
2830 ui.write(_('no patches applied\n'))
2832 ui.write(_('no patches applied\n'))
2831 return
2833 return
2832 patch = q.lookup('qtip')
2834 patch = q.lookup('qtip')
2833 absdest = q.join(name)
2835 absdest = q.join(name)
2834 if os.path.isdir(absdest):
2836 if os.path.isdir(absdest):
2835 name = normname(os.path.join(name, os.path.basename(patch)))
2837 name = normname(os.path.join(name, os.path.basename(patch)))
2836 absdest = q.join(name)
2838 absdest = q.join(name)
2837 q.checkpatchname(name)
2839 q.checkpatchname(name)
2838
2840
2839 ui.note(_('renaming %s to %s\n') % (patch, name))
2841 ui.note(_('renaming %s to %s\n') % (patch, name))
2840 i = q.findseries(patch)
2842 i = q.findseries(patch)
2841 guards = q.guard_re.findall(q.fullseries[i])
2843 guards = q.guard_re.findall(q.fullseries[i])
2842 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2844 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2843 q.parseseries()
2845 q.parseseries()
2844 q.seriesdirty = True
2846 q.seriesdirty = True
2845
2847
2846 info = q.isapplied(patch)
2848 info = q.isapplied(patch)
2847 if info:
2849 if info:
2848 q.applied[info[0]] = statusentry(info[1], name)
2850 q.applied[info[0]] = statusentry(info[1], name)
2849 q.applieddirty = True
2851 q.applieddirty = True
2850
2852
2851 destdir = os.path.dirname(absdest)
2853 destdir = os.path.dirname(absdest)
2852 if not os.path.isdir(destdir):
2854 if not os.path.isdir(destdir):
2853 os.makedirs(destdir)
2855 os.makedirs(destdir)
2854 util.rename(q.join(patch), absdest)
2856 util.rename(q.join(patch), absdest)
2855 r = q.qrepo()
2857 r = q.qrepo()
2856 if r and patch in r.dirstate:
2858 if r and patch in r.dirstate:
2857 wctx = r[None]
2859 wctx = r[None]
2858 wlock = r.wlock()
2860 wlock = r.wlock()
2859 try:
2861 try:
2860 if r.dirstate[patch] == 'a':
2862 if r.dirstate[patch] == 'a':
2861 r.dirstate.drop(patch)
2863 r.dirstate.drop(patch)
2862 r.dirstate.add(name)
2864 r.dirstate.add(name)
2863 else:
2865 else:
2864 wctx.copy(patch, name)
2866 wctx.copy(patch, name)
2865 wctx.forget([patch])
2867 wctx.forget([patch])
2866 finally:
2868 finally:
2867 wlock.release()
2869 wlock.release()
2868
2870
2869 q.savedirty()
2871 q.savedirty()
2870
2872
2871 @command("qrestore",
2873 @command("qrestore",
2872 [('d', 'delete', None, _('delete save entry')),
2874 [('d', 'delete', None, _('delete save entry')),
2873 ('u', 'update', None, _('update queue working directory'))],
2875 ('u', 'update', None, _('update queue working directory'))],
2874 _('hg qrestore [-d] [-u] REV'))
2876 _('hg qrestore [-d] [-u] REV'))
2875 def restore(ui, repo, rev, **opts):
2877 def restore(ui, repo, rev, **opts):
2876 """restore the queue state saved by a revision (DEPRECATED)
2878 """restore the queue state saved by a revision (DEPRECATED)
2877
2879
2878 This command is deprecated, use :hg:`rebase` instead."""
2880 This command is deprecated, use :hg:`rebase` instead."""
2879 rev = repo.lookup(rev)
2881 rev = repo.lookup(rev)
2880 q = repo.mq
2882 q = repo.mq
2881 q.restore(repo, rev, delete=opts.get('delete'),
2883 q.restore(repo, rev, delete=opts.get('delete'),
2882 qupdate=opts.get('update'))
2884 qupdate=opts.get('update'))
2883 q.savedirty()
2885 q.savedirty()
2884 return 0
2886 return 0
2885
2887
2886 @command("qsave",
2888 @command("qsave",
2887 [('c', 'copy', None, _('copy patch directory')),
2889 [('c', 'copy', None, _('copy patch directory')),
2888 ('n', 'name', '',
2890 ('n', 'name', '',
2889 _('copy directory name'), _('NAME')),
2891 _('copy directory name'), _('NAME')),
2890 ('e', 'empty', None, _('clear queue status file')),
2892 ('e', 'empty', None, _('clear queue status file')),
2891 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2893 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2892 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2894 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2893 def save(ui, repo, **opts):
2895 def save(ui, repo, **opts):
2894 """save current queue state (DEPRECATED)
2896 """save current queue state (DEPRECATED)
2895
2897
2896 This command is deprecated, use :hg:`rebase` instead."""
2898 This command is deprecated, use :hg:`rebase` instead."""
2897 q = repo.mq
2899 q = repo.mq
2898 message = cmdutil.logmessage(ui, opts)
2900 message = cmdutil.logmessage(ui, opts)
2899 ret = q.save(repo, msg=message)
2901 ret = q.save(repo, msg=message)
2900 if ret:
2902 if ret:
2901 return ret
2903 return ret
2902 q.savedirty() # save to .hg/patches before copying
2904 q.savedirty() # save to .hg/patches before copying
2903 if opts.get('copy'):
2905 if opts.get('copy'):
2904 path = q.path
2906 path = q.path
2905 if opts.get('name'):
2907 if opts.get('name'):
2906 newpath = os.path.join(q.basepath, opts.get('name'))
2908 newpath = os.path.join(q.basepath, opts.get('name'))
2907 if os.path.exists(newpath):
2909 if os.path.exists(newpath):
2908 if not os.path.isdir(newpath):
2910 if not os.path.isdir(newpath):
2909 raise util.Abort(_('destination %s exists and is not '
2911 raise util.Abort(_('destination %s exists and is not '
2910 'a directory') % newpath)
2912 'a directory') % newpath)
2911 if not opts.get('force'):
2913 if not opts.get('force'):
2912 raise util.Abort(_('destination %s exists, '
2914 raise util.Abort(_('destination %s exists, '
2913 'use -f to force') % newpath)
2915 'use -f to force') % newpath)
2914 else:
2916 else:
2915 newpath = savename(path)
2917 newpath = savename(path)
2916 ui.warn(_("copy %s to %s\n") % (path, newpath))
2918 ui.warn(_("copy %s to %s\n") % (path, newpath))
2917 util.copyfiles(path, newpath)
2919 util.copyfiles(path, newpath)
2918 if opts.get('empty'):
2920 if opts.get('empty'):
2919 del q.applied[:]
2921 del q.applied[:]
2920 q.applieddirty = True
2922 q.applieddirty = True
2921 q.savedirty()
2923 q.savedirty()
2922 return 0
2924 return 0
2923
2925
2924
2926
2925 @command("qselect",
2927 @command("qselect",
2926 [('n', 'none', None, _('disable all guards')),
2928 [('n', 'none', None, _('disable all guards')),
2927 ('s', 'series', None, _('list all guards in series file')),
2929 ('s', 'series', None, _('list all guards in series file')),
2928 ('', 'pop', None, _('pop to before first guarded applied patch')),
2930 ('', 'pop', None, _('pop to before first guarded applied patch')),
2929 ('', 'reapply', None, _('pop, then reapply patches'))],
2931 ('', 'reapply', None, _('pop, then reapply patches'))],
2930 _('hg qselect [OPTION]... [GUARD]...'))
2932 _('hg qselect [OPTION]... [GUARD]...'))
2931 def select(ui, repo, *args, **opts):
2933 def select(ui, repo, *args, **opts):
2932 '''set or print guarded patches to push
2934 '''set or print guarded patches to push
2933
2935
2934 Use the :hg:`qguard` command to set or print guards on patch, then use
2936 Use the :hg:`qguard` command to set or print guards on patch, then use
2935 qselect to tell mq which guards to use. A patch will be pushed if
2937 qselect to tell mq which guards to use. A patch will be pushed if
2936 it has no guards or any positive guards match the currently
2938 it has no guards or any positive guards match the currently
2937 selected guard, but will not be pushed if any negative guards
2939 selected guard, but will not be pushed if any negative guards
2938 match the current guard. For example::
2940 match the current guard. For example::
2939
2941
2940 qguard foo.patch -- -stable (negative guard)
2942 qguard foo.patch -- -stable (negative guard)
2941 qguard bar.patch +stable (positive guard)
2943 qguard bar.patch +stable (positive guard)
2942 qselect stable
2944 qselect stable
2943
2945
2944 This activates the "stable" guard. mq will skip foo.patch (because
2946 This activates the "stable" guard. mq will skip foo.patch (because
2945 it has a negative match) but push bar.patch (because it has a
2947 it has a negative match) but push bar.patch (because it has a
2946 positive match).
2948 positive match).
2947
2949
2948 With no arguments, prints the currently active guards.
2950 With no arguments, prints the currently active guards.
2949 With one argument, sets the active guard.
2951 With one argument, sets the active guard.
2950
2952
2951 Use -n/--none to deactivate guards (no other arguments needed).
2953 Use -n/--none to deactivate guards (no other arguments needed).
2952 When no guards are active, patches with positive guards are
2954 When no guards are active, patches with positive guards are
2953 skipped and patches with negative guards are pushed.
2955 skipped and patches with negative guards are pushed.
2954
2956
2955 qselect can change the guards on applied patches. It does not pop
2957 qselect can change the guards on applied patches. It does not pop
2956 guarded patches by default. Use --pop to pop back to the last
2958 guarded patches by default. Use --pop to pop back to the last
2957 applied patch that is not guarded. Use --reapply (which implies
2959 applied patch that is not guarded. Use --reapply (which implies
2958 --pop) to push back to the current patch afterwards, but skip
2960 --pop) to push back to the current patch afterwards, but skip
2959 guarded patches.
2961 guarded patches.
2960
2962
2961 Use -s/--series to print a list of all guards in the series file
2963 Use -s/--series to print a list of all guards in the series file
2962 (no other arguments needed). Use -v for more information.
2964 (no other arguments needed). Use -v for more information.
2963
2965
2964 Returns 0 on success.'''
2966 Returns 0 on success.'''
2965
2967
2966 q = repo.mq
2968 q = repo.mq
2967 guards = q.active()
2969 guards = q.active()
2968 if args or opts.get('none'):
2970 if args or opts.get('none'):
2969 old_unapplied = q.unapplied(repo)
2971 old_unapplied = q.unapplied(repo)
2970 old_guarded = [i for i in xrange(len(q.applied)) if
2972 old_guarded = [i for i in xrange(len(q.applied)) if
2971 not q.pushable(i)[0]]
2973 not q.pushable(i)[0]]
2972 q.setactive(args)
2974 q.setactive(args)
2973 q.savedirty()
2975 q.savedirty()
2974 if not args:
2976 if not args:
2975 ui.status(_('guards deactivated\n'))
2977 ui.status(_('guards deactivated\n'))
2976 if not opts.get('pop') and not opts.get('reapply'):
2978 if not opts.get('pop') and not opts.get('reapply'):
2977 unapplied = q.unapplied(repo)
2979 unapplied = q.unapplied(repo)
2978 guarded = [i for i in xrange(len(q.applied))
2980 guarded = [i for i in xrange(len(q.applied))
2979 if not q.pushable(i)[0]]
2981 if not q.pushable(i)[0]]
2980 if len(unapplied) != len(old_unapplied):
2982 if len(unapplied) != len(old_unapplied):
2981 ui.status(_('number of unguarded, unapplied patches has '
2983 ui.status(_('number of unguarded, unapplied patches has '
2982 'changed from %d to %d\n') %
2984 'changed from %d to %d\n') %
2983 (len(old_unapplied), len(unapplied)))
2985 (len(old_unapplied), len(unapplied)))
2984 if len(guarded) != len(old_guarded):
2986 if len(guarded) != len(old_guarded):
2985 ui.status(_('number of guarded, applied patches has changed '
2987 ui.status(_('number of guarded, applied patches has changed '
2986 'from %d to %d\n') %
2988 'from %d to %d\n') %
2987 (len(old_guarded), len(guarded)))
2989 (len(old_guarded), len(guarded)))
2988 elif opts.get('series'):
2990 elif opts.get('series'):
2989 guards = {}
2991 guards = {}
2990 noguards = 0
2992 noguards = 0
2991 for gs in q.seriesguards:
2993 for gs in q.seriesguards:
2992 if not gs:
2994 if not gs:
2993 noguards += 1
2995 noguards += 1
2994 for g in gs:
2996 for g in gs:
2995 guards.setdefault(g, 0)
2997 guards.setdefault(g, 0)
2996 guards[g] += 1
2998 guards[g] += 1
2997 if ui.verbose:
2999 if ui.verbose:
2998 guards['NONE'] = noguards
3000 guards['NONE'] = noguards
2999 guards = guards.items()
3001 guards = guards.items()
3000 guards.sort(key=lambda x: x[0][1:])
3002 guards.sort(key=lambda x: x[0][1:])
3001 if guards:
3003 if guards:
3002 ui.note(_('guards in series file:\n'))
3004 ui.note(_('guards in series file:\n'))
3003 for guard, count in guards:
3005 for guard, count in guards:
3004 ui.note('%2d ' % count)
3006 ui.note('%2d ' % count)
3005 ui.write(guard, '\n')
3007 ui.write(guard, '\n')
3006 else:
3008 else:
3007 ui.note(_('no guards in series file\n'))
3009 ui.note(_('no guards in series file\n'))
3008 else:
3010 else:
3009 if guards:
3011 if guards:
3010 ui.note(_('active guards:\n'))
3012 ui.note(_('active guards:\n'))
3011 for g in guards:
3013 for g in guards:
3012 ui.write(g, '\n')
3014 ui.write(g, '\n')
3013 else:
3015 else:
3014 ui.write(_('no active guards\n'))
3016 ui.write(_('no active guards\n'))
3015 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
3017 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
3016 popped = False
3018 popped = False
3017 if opts.get('pop') or opts.get('reapply'):
3019 if opts.get('pop') or opts.get('reapply'):
3018 for i in xrange(len(q.applied)):
3020 for i in xrange(len(q.applied)):
3019 pushable, reason = q.pushable(i)
3021 pushable, reason = q.pushable(i)
3020 if not pushable:
3022 if not pushable:
3021 ui.status(_('popping guarded patches\n'))
3023 ui.status(_('popping guarded patches\n'))
3022 popped = True
3024 popped = True
3023 if i == 0:
3025 if i == 0:
3024 q.pop(repo, all=True)
3026 q.pop(repo, all=True)
3025 else:
3027 else:
3026 q.pop(repo, str(i - 1))
3028 q.pop(repo, str(i - 1))
3027 break
3029 break
3028 if popped:
3030 if popped:
3029 try:
3031 try:
3030 if reapply:
3032 if reapply:
3031 ui.status(_('reapplying unguarded patches\n'))
3033 ui.status(_('reapplying unguarded patches\n'))
3032 q.push(repo, reapply)
3034 q.push(repo, reapply)
3033 finally:
3035 finally:
3034 q.savedirty()
3036 q.savedirty()
3035
3037
3036 @command("qfinish",
3038 @command("qfinish",
3037 [('a', 'applied', None, _('finish all applied changesets'))],
3039 [('a', 'applied', None, _('finish all applied changesets'))],
3038 _('hg qfinish [-a] [REV]...'))
3040 _('hg qfinish [-a] [REV]...'))
3039 def finish(ui, repo, *revrange, **opts):
3041 def finish(ui, repo, *revrange, **opts):
3040 """move applied patches into repository history
3042 """move applied patches into repository history
3041
3043
3042 Finishes the specified revisions (corresponding to applied
3044 Finishes the specified revisions (corresponding to applied
3043 patches) by moving them out of mq control into regular repository
3045 patches) by moving them out of mq control into regular repository
3044 history.
3046 history.
3045
3047
3046 Accepts a revision range or the -a/--applied option. If --applied
3048 Accepts a revision range or the -a/--applied option. If --applied
3047 is specified, all applied mq revisions are removed from mq
3049 is specified, all applied mq revisions are removed from mq
3048 control. Otherwise, the given revisions must be at the base of the
3050 control. Otherwise, the given revisions must be at the base of the
3049 stack of applied patches.
3051 stack of applied patches.
3050
3052
3051 This can be especially useful if your changes have been applied to
3053 This can be especially useful if your changes have been applied to
3052 an upstream repository, or if you are about to push your changes
3054 an upstream repository, or if you are about to push your changes
3053 to upstream.
3055 to upstream.
3054
3056
3055 Returns 0 on success.
3057 Returns 0 on success.
3056 """
3058 """
3057 if not opts.get('applied') and not revrange:
3059 if not opts.get('applied') and not revrange:
3058 raise util.Abort(_('no revisions specified'))
3060 raise util.Abort(_('no revisions specified'))
3059 elif opts.get('applied'):
3061 elif opts.get('applied'):
3060 revrange = ('qbase::qtip',) + revrange
3062 revrange = ('qbase::qtip',) + revrange
3061
3063
3062 q = repo.mq
3064 q = repo.mq
3063 if not q.applied:
3065 if not q.applied:
3064 ui.status(_('no patches applied\n'))
3066 ui.status(_('no patches applied\n'))
3065 return 0
3067 return 0
3066
3068
3067 revs = scmutil.revrange(repo, revrange)
3069 revs = scmutil.revrange(repo, revrange)
3068 if repo['.'].rev() in revs and repo[None].files():
3070 if repo['.'].rev() in revs and repo[None].files():
3069 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3071 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3070 # queue.finish may changes phases but leave the responsibility to lock the
3072 # queue.finish may changes phases but leave the responsibility to lock the
3071 # repo to the caller to avoid deadlock with wlock. This command code is
3073 # repo to the caller to avoid deadlock with wlock. This command code is
3072 # responsibility for this locking.
3074 # responsibility for this locking.
3073 lock = repo.lock()
3075 lock = repo.lock()
3074 try:
3076 try:
3075 q.finish(repo, revs)
3077 q.finish(repo, revs)
3076 q.savedirty()
3078 q.savedirty()
3077 finally:
3079 finally:
3078 lock.release()
3080 lock.release()
3079 return 0
3081 return 0
3080
3082
3081 @command("qqueue",
3083 @command("qqueue",
3082 [('l', 'list', False, _('list all available queues')),
3084 [('l', 'list', False, _('list all available queues')),
3083 ('', 'active', False, _('print name of active queue')),
3085 ('', 'active', False, _('print name of active queue')),
3084 ('c', 'create', False, _('create new queue')),
3086 ('c', 'create', False, _('create new queue')),
3085 ('', 'rename', False, _('rename active queue')),
3087 ('', 'rename', False, _('rename active queue')),
3086 ('', 'delete', False, _('delete reference to queue')),
3088 ('', 'delete', False, _('delete reference to queue')),
3087 ('', 'purge', False, _('delete queue, and remove patch dir')),
3089 ('', 'purge', False, _('delete queue, and remove patch dir')),
3088 ],
3090 ],
3089 _('[OPTION] [QUEUE]'))
3091 _('[OPTION] [QUEUE]'))
3090 def qqueue(ui, repo, name=None, **opts):
3092 def qqueue(ui, repo, name=None, **opts):
3091 '''manage multiple patch queues
3093 '''manage multiple patch queues
3092
3094
3093 Supports switching between different patch queues, as well as creating
3095 Supports switching between different patch queues, as well as creating
3094 new patch queues and deleting existing ones.
3096 new patch queues and deleting existing ones.
3095
3097
3096 Omitting a queue name or specifying -l/--list will show you the registered
3098 Omitting a queue name or specifying -l/--list will show you the registered
3097 queues - by default the "normal" patches queue is registered. The currently
3099 queues - by default the "normal" patches queue is registered. The currently
3098 active queue will be marked with "(active)". Specifying --active will print
3100 active queue will be marked with "(active)". Specifying --active will print
3099 only the name of the active queue.
3101 only the name of the active queue.
3100
3102
3101 To create a new queue, use -c/--create. The queue is automatically made
3103 To create a new queue, use -c/--create. The queue is automatically made
3102 active, except in the case where there are applied patches from the
3104 active, except in the case where there are applied patches from the
3103 currently active queue in the repository. Then the queue will only be
3105 currently active queue in the repository. Then the queue will only be
3104 created and switching will fail.
3106 created and switching will fail.
3105
3107
3106 To delete an existing queue, use --delete. You cannot delete the currently
3108 To delete an existing queue, use --delete. You cannot delete the currently
3107 active queue.
3109 active queue.
3108
3110
3109 Returns 0 on success.
3111 Returns 0 on success.
3110 '''
3112 '''
3111 q = repo.mq
3113 q = repo.mq
3112 _defaultqueue = 'patches'
3114 _defaultqueue = 'patches'
3113 _allqueues = 'patches.queues'
3115 _allqueues = 'patches.queues'
3114 _activequeue = 'patches.queue'
3116 _activequeue = 'patches.queue'
3115
3117
3116 def _getcurrent():
3118 def _getcurrent():
3117 cur = os.path.basename(q.path)
3119 cur = os.path.basename(q.path)
3118 if cur.startswith('patches-'):
3120 if cur.startswith('patches-'):
3119 cur = cur[8:]
3121 cur = cur[8:]
3120 return cur
3122 return cur
3121
3123
3122 def _noqueues():
3124 def _noqueues():
3123 try:
3125 try:
3124 fh = repo.opener(_allqueues, 'r')
3126 fh = repo.opener(_allqueues, 'r')
3125 fh.close()
3127 fh.close()
3126 except IOError:
3128 except IOError:
3127 return True
3129 return True
3128
3130
3129 return False
3131 return False
3130
3132
3131 def _getqueues():
3133 def _getqueues():
3132 current = _getcurrent()
3134 current = _getcurrent()
3133
3135
3134 try:
3136 try:
3135 fh = repo.opener(_allqueues, 'r')
3137 fh = repo.opener(_allqueues, 'r')
3136 queues = [queue.strip() for queue in fh if queue.strip()]
3138 queues = [queue.strip() for queue in fh if queue.strip()]
3137 fh.close()
3139 fh.close()
3138 if current not in queues:
3140 if current not in queues:
3139 queues.append(current)
3141 queues.append(current)
3140 except IOError:
3142 except IOError:
3141 queues = [_defaultqueue]
3143 queues = [_defaultqueue]
3142
3144
3143 return sorted(queues)
3145 return sorted(queues)
3144
3146
3145 def _setactive(name):
3147 def _setactive(name):
3146 if q.applied:
3148 if q.applied:
3147 raise util.Abort(_('new queue created, but cannot make active '
3149 raise util.Abort(_('new queue created, but cannot make active '
3148 'as patches are applied'))
3150 'as patches are applied'))
3149 _setactivenocheck(name)
3151 _setactivenocheck(name)
3150
3152
3151 def _setactivenocheck(name):
3153 def _setactivenocheck(name):
3152 fh = repo.opener(_activequeue, 'w')
3154 fh = repo.opener(_activequeue, 'w')
3153 if name != 'patches':
3155 if name != 'patches':
3154 fh.write(name)
3156 fh.write(name)
3155 fh.close()
3157 fh.close()
3156
3158
3157 def _addqueue(name):
3159 def _addqueue(name):
3158 fh = repo.opener(_allqueues, 'a')
3160 fh = repo.opener(_allqueues, 'a')
3159 fh.write('%s\n' % (name,))
3161 fh.write('%s\n' % (name,))
3160 fh.close()
3162 fh.close()
3161
3163
3162 def _queuedir(name):
3164 def _queuedir(name):
3163 if name == 'patches':
3165 if name == 'patches':
3164 return repo.join('patches')
3166 return repo.join('patches')
3165 else:
3167 else:
3166 return repo.join('patches-' + name)
3168 return repo.join('patches-' + name)
3167
3169
3168 def _validname(name):
3170 def _validname(name):
3169 for n in name:
3171 for n in name:
3170 if n in ':\\/.':
3172 if n in ':\\/.':
3171 return False
3173 return False
3172 return True
3174 return True
3173
3175
3174 def _delete(name):
3176 def _delete(name):
3175 if name not in existing:
3177 if name not in existing:
3176 raise util.Abort(_('cannot delete queue that does not exist'))
3178 raise util.Abort(_('cannot delete queue that does not exist'))
3177
3179
3178 current = _getcurrent()
3180 current = _getcurrent()
3179
3181
3180 if name == current:
3182 if name == current:
3181 raise util.Abort(_('cannot delete currently active queue'))
3183 raise util.Abort(_('cannot delete currently active queue'))
3182
3184
3183 fh = repo.opener('patches.queues.new', 'w')
3185 fh = repo.opener('patches.queues.new', 'w')
3184 for queue in existing:
3186 for queue in existing:
3185 if queue == name:
3187 if queue == name:
3186 continue
3188 continue
3187 fh.write('%s\n' % (queue,))
3189 fh.write('%s\n' % (queue,))
3188 fh.close()
3190 fh.close()
3189 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3191 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3190
3192
3191 if not name or opts.get('list') or opts.get('active'):
3193 if not name or opts.get('list') or opts.get('active'):
3192 current = _getcurrent()
3194 current = _getcurrent()
3193 if opts.get('active'):
3195 if opts.get('active'):
3194 ui.write('%s\n' % (current,))
3196 ui.write('%s\n' % (current,))
3195 return
3197 return
3196 for queue in _getqueues():
3198 for queue in _getqueues():
3197 ui.write('%s' % (queue,))
3199 ui.write('%s' % (queue,))
3198 if queue == current and not ui.quiet:
3200 if queue == current and not ui.quiet:
3199 ui.write(_(' (active)\n'))
3201 ui.write(_(' (active)\n'))
3200 else:
3202 else:
3201 ui.write('\n')
3203 ui.write('\n')
3202 return
3204 return
3203
3205
3204 if not _validname(name):
3206 if not _validname(name):
3205 raise util.Abort(
3207 raise util.Abort(
3206 _('invalid queue name, may not contain the characters ":\\/."'))
3208 _('invalid queue name, may not contain the characters ":\\/."'))
3207
3209
3208 existing = _getqueues()
3210 existing = _getqueues()
3209
3211
3210 if opts.get('create'):
3212 if opts.get('create'):
3211 if name in existing:
3213 if name in existing:
3212 raise util.Abort(_('queue "%s" already exists') % name)
3214 raise util.Abort(_('queue "%s" already exists') % name)
3213 if _noqueues():
3215 if _noqueues():
3214 _addqueue(_defaultqueue)
3216 _addqueue(_defaultqueue)
3215 _addqueue(name)
3217 _addqueue(name)
3216 _setactive(name)
3218 _setactive(name)
3217 elif opts.get('rename'):
3219 elif opts.get('rename'):
3218 current = _getcurrent()
3220 current = _getcurrent()
3219 if name == current:
3221 if name == current:
3220 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3222 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3221 if name in existing:
3223 if name in existing:
3222 raise util.Abort(_('queue "%s" already exists') % name)
3224 raise util.Abort(_('queue "%s" already exists') % name)
3223
3225
3224 olddir = _queuedir(current)
3226 olddir = _queuedir(current)
3225 newdir = _queuedir(name)
3227 newdir = _queuedir(name)
3226
3228
3227 if os.path.exists(newdir):
3229 if os.path.exists(newdir):
3228 raise util.Abort(_('non-queue directory "%s" already exists') %
3230 raise util.Abort(_('non-queue directory "%s" already exists') %
3229 newdir)
3231 newdir)
3230
3232
3231 fh = repo.opener('patches.queues.new', 'w')
3233 fh = repo.opener('patches.queues.new', 'w')
3232 for queue in existing:
3234 for queue in existing:
3233 if queue == current:
3235 if queue == current:
3234 fh.write('%s\n' % (name,))
3236 fh.write('%s\n' % (name,))
3235 if os.path.exists(olddir):
3237 if os.path.exists(olddir):
3236 util.rename(olddir, newdir)
3238 util.rename(olddir, newdir)
3237 else:
3239 else:
3238 fh.write('%s\n' % (queue,))
3240 fh.write('%s\n' % (queue,))
3239 fh.close()
3241 fh.close()
3240 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3242 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3241 _setactivenocheck(name)
3243 _setactivenocheck(name)
3242 elif opts.get('delete'):
3244 elif opts.get('delete'):
3243 _delete(name)
3245 _delete(name)
3244 elif opts.get('purge'):
3246 elif opts.get('purge'):
3245 if name in existing:
3247 if name in existing:
3246 _delete(name)
3248 _delete(name)
3247 qdir = _queuedir(name)
3249 qdir = _queuedir(name)
3248 if os.path.exists(qdir):
3250 if os.path.exists(qdir):
3249 shutil.rmtree(qdir)
3251 shutil.rmtree(qdir)
3250 else:
3252 else:
3251 if name not in existing:
3253 if name not in existing:
3252 raise util.Abort(_('use --create to create a new queue'))
3254 raise util.Abort(_('use --create to create a new queue'))
3253 _setactive(name)
3255 _setactive(name)
3254
3256
3255 def mqphasedefaults(repo, roots):
3257 def mqphasedefaults(repo, roots):
3256 """callback used to set mq changeset as secret when no phase data exists"""
3258 """callback used to set mq changeset as secret when no phase data exists"""
3257 if repo.mq.applied:
3259 if repo.mq.applied:
3258 if repo.ui.configbool('mq', 'secret', False):
3260 if repo.ui.configbool('mq', 'secret', False):
3259 mqphase = phases.secret
3261 mqphase = phases.secret
3260 else:
3262 else:
3261 mqphase = phases.draft
3263 mqphase = phases.draft
3262 qbase = repo[repo.mq.applied[0].node]
3264 qbase = repo[repo.mq.applied[0].node]
3263 roots[mqphase].add(qbase.node())
3265 roots[mqphase].add(qbase.node())
3264 return roots
3266 return roots
3265
3267
3266 def reposetup(ui, repo):
3268 def reposetup(ui, repo):
3267 class mqrepo(repo.__class__):
3269 class mqrepo(repo.__class__):
3268 @localrepo.unfilteredpropertycache
3270 @localrepo.unfilteredpropertycache
3269 def mq(self):
3271 def mq(self):
3270 return queue(self.ui, self.baseui, self.path)
3272 return queue(self.ui, self.baseui, self.path)
3271
3273
3272 def invalidateall(self):
3274 def invalidateall(self):
3273 super(mqrepo, self).invalidateall()
3275 super(mqrepo, self).invalidateall()
3274 if localrepo.hasunfilteredcache(self, 'mq'):
3276 if localrepo.hasunfilteredcache(self, 'mq'):
3275 # recreate mq in case queue path was changed
3277 # recreate mq in case queue path was changed
3276 delattr(self.unfiltered(), 'mq')
3278 delattr(self.unfiltered(), 'mq')
3277
3279
3278 def abortifwdirpatched(self, errmsg, force=False):
3280 def abortifwdirpatched(self, errmsg, force=False):
3279 if self.mq.applied and self.mq.checkapplied and not force:
3281 if self.mq.applied and self.mq.checkapplied and not force:
3280 parents = self.dirstate.parents()
3282 parents = self.dirstate.parents()
3281 patches = [s.node for s in self.mq.applied]
3283 patches = [s.node for s in self.mq.applied]
3282 if parents[0] in patches or parents[1] in patches:
3284 if parents[0] in patches or parents[1] in patches:
3283 raise util.Abort(errmsg)
3285 raise util.Abort(errmsg)
3284
3286
3285 def commit(self, text="", user=None, date=None, match=None,
3287 def commit(self, text="", user=None, date=None, match=None,
3286 force=False, editor=False, extra={}):
3288 force=False, editor=False, extra={}):
3287 self.abortifwdirpatched(
3289 self.abortifwdirpatched(
3288 _('cannot commit over an applied mq patch'),
3290 _('cannot commit over an applied mq patch'),
3289 force)
3291 force)
3290
3292
3291 return super(mqrepo, self).commit(text, user, date, match, force,
3293 return super(mqrepo, self).commit(text, user, date, match, force,
3292 editor, extra)
3294 editor, extra)
3293
3295
3294 def checkpush(self, force, revs):
3296 def checkpush(self, force, revs):
3295 if self.mq.applied and self.mq.checkapplied and not force:
3297 if self.mq.applied and self.mq.checkapplied and not force:
3296 outapplied = [e.node for e in self.mq.applied]
3298 outapplied = [e.node for e in self.mq.applied]
3297 if revs:
3299 if revs:
3298 # Assume applied patches have no non-patch descendants and
3300 # Assume applied patches have no non-patch descendants and
3299 # are not on remote already. Filtering any changeset not
3301 # are not on remote already. Filtering any changeset not
3300 # pushed.
3302 # pushed.
3301 heads = set(revs)
3303 heads = set(revs)
3302 for node in reversed(outapplied):
3304 for node in reversed(outapplied):
3303 if node in heads:
3305 if node in heads:
3304 break
3306 break
3305 else:
3307 else:
3306 outapplied.pop()
3308 outapplied.pop()
3307 # looking for pushed and shared changeset
3309 # looking for pushed and shared changeset
3308 for node in outapplied:
3310 for node in outapplied:
3309 if self[node].phase() < phases.secret:
3311 if self[node].phase() < phases.secret:
3310 raise util.Abort(_('source has mq patches applied'))
3312 raise util.Abort(_('source has mq patches applied'))
3311 # no non-secret patches pushed
3313 # no non-secret patches pushed
3312 super(mqrepo, self).checkpush(force, revs)
3314 super(mqrepo, self).checkpush(force, revs)
3313
3315
3314 def _findtags(self):
3316 def _findtags(self):
3315 '''augment tags from base class with patch tags'''
3317 '''augment tags from base class with patch tags'''
3316 result = super(mqrepo, self)._findtags()
3318 result = super(mqrepo, self)._findtags()
3317
3319
3318 q = self.mq
3320 q = self.mq
3319 if not q.applied:
3321 if not q.applied:
3320 return result
3322 return result
3321
3323
3322 mqtags = [(patch.node, patch.name) for patch in q.applied]
3324 mqtags = [(patch.node, patch.name) for patch in q.applied]
3323
3325
3324 try:
3326 try:
3325 # for now ignore filtering business
3327 # for now ignore filtering business
3326 self.unfiltered().changelog.rev(mqtags[-1][0])
3328 self.unfiltered().changelog.rev(mqtags[-1][0])
3327 except error.LookupError:
3329 except error.LookupError:
3328 self.ui.warn(_('mq status file refers to unknown node %s\n')
3330 self.ui.warn(_('mq status file refers to unknown node %s\n')
3329 % short(mqtags[-1][0]))
3331 % short(mqtags[-1][0]))
3330 return result
3332 return result
3331
3333
3332 # do not add fake tags for filtered revisions
3334 # do not add fake tags for filtered revisions
3333 included = self.changelog.hasnode
3335 included = self.changelog.hasnode
3334 mqtags = [mqt for mqt in mqtags if included(mqt[0])]
3336 mqtags = [mqt for mqt in mqtags if included(mqt[0])]
3335 if not mqtags:
3337 if not mqtags:
3336 return result
3338 return result
3337
3339
3338 mqtags.append((mqtags[-1][0], 'qtip'))
3340 mqtags.append((mqtags[-1][0], 'qtip'))
3339 mqtags.append((mqtags[0][0], 'qbase'))
3341 mqtags.append((mqtags[0][0], 'qbase'))
3340 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3342 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3341 tags = result[0]
3343 tags = result[0]
3342 for patch in mqtags:
3344 for patch in mqtags:
3343 if patch[1] in tags:
3345 if patch[1] in tags:
3344 self.ui.warn(_('tag %s overrides mq patch of the same '
3346 self.ui.warn(_('tag %s overrides mq patch of the same '
3345 'name\n') % patch[1])
3347 'name\n') % patch[1])
3346 else:
3348 else:
3347 tags[patch[1]] = patch[0]
3349 tags[patch[1]] = patch[0]
3348
3350
3349 return result
3351 return result
3350
3352
3351 if repo.local():
3353 if repo.local():
3352 repo.__class__ = mqrepo
3354 repo.__class__ = mqrepo
3353
3355
3354 repo._phasedefaults.append(mqphasedefaults)
3356 repo._phasedefaults.append(mqphasedefaults)
3355
3357
3356 def mqimport(orig, ui, repo, *args, **kwargs):
3358 def mqimport(orig, ui, repo, *args, **kwargs):
3357 if (util.safehasattr(repo, 'abortifwdirpatched')
3359 if (util.safehasattr(repo, 'abortifwdirpatched')
3358 and not kwargs.get('no_commit', False)):
3360 and not kwargs.get('no_commit', False)):
3359 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3361 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3360 kwargs.get('force'))
3362 kwargs.get('force'))
3361 return orig(ui, repo, *args, **kwargs)
3363 return orig(ui, repo, *args, **kwargs)
3362
3364
3363 def mqinit(orig, ui, *args, **kwargs):
3365 def mqinit(orig, ui, *args, **kwargs):
3364 mq = kwargs.pop('mq', None)
3366 mq = kwargs.pop('mq', None)
3365
3367
3366 if not mq:
3368 if not mq:
3367 return orig(ui, *args, **kwargs)
3369 return orig(ui, *args, **kwargs)
3368
3370
3369 if args:
3371 if args:
3370 repopath = args[0]
3372 repopath = args[0]
3371 if not hg.islocal(repopath):
3373 if not hg.islocal(repopath):
3372 raise util.Abort(_('only a local queue repository '
3374 raise util.Abort(_('only a local queue repository '
3373 'may be initialized'))
3375 'may be initialized'))
3374 else:
3376 else:
3375 repopath = cmdutil.findrepo(os.getcwd())
3377 repopath = cmdutil.findrepo(os.getcwd())
3376 if not repopath:
3378 if not repopath:
3377 raise util.Abort(_('there is no Mercurial repository here '
3379 raise util.Abort(_('there is no Mercurial repository here '
3378 '(.hg not found)'))
3380 '(.hg not found)'))
3379 repo = hg.repository(ui, repopath)
3381 repo = hg.repository(ui, repopath)
3380 return qinit(ui, repo, True)
3382 return qinit(ui, repo, True)
3381
3383
3382 def mqcommand(orig, ui, repo, *args, **kwargs):
3384 def mqcommand(orig, ui, repo, *args, **kwargs):
3383 """Add --mq option to operate on patch repository instead of main"""
3385 """Add --mq option to operate on patch repository instead of main"""
3384
3386
3385 # some commands do not like getting unknown options
3387 # some commands do not like getting unknown options
3386 mq = kwargs.pop('mq', None)
3388 mq = kwargs.pop('mq', None)
3387
3389
3388 if not mq:
3390 if not mq:
3389 return orig(ui, repo, *args, **kwargs)
3391 return orig(ui, repo, *args, **kwargs)
3390
3392
3391 q = repo.mq
3393 q = repo.mq
3392 r = q.qrepo()
3394 r = q.qrepo()
3393 if not r:
3395 if not r:
3394 raise util.Abort(_('no queue repository'))
3396 raise util.Abort(_('no queue repository'))
3395 return orig(r.ui, r, *args, **kwargs)
3397 return orig(r.ui, r, *args, **kwargs)
3396
3398
3397 def summaryhook(ui, repo):
3399 def summaryhook(ui, repo):
3398 q = repo.mq
3400 q = repo.mq
3399 m = []
3401 m = []
3400 a, u = len(q.applied), len(q.unapplied(repo))
3402 a, u = len(q.applied), len(q.unapplied(repo))
3401 if a:
3403 if a:
3402 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3404 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3403 if u:
3405 if u:
3404 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3406 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3405 if m:
3407 if m:
3406 # i18n: column positioning for "hg summary"
3408 # i18n: column positioning for "hg summary"
3407 ui.write(_("mq: %s\n") % ', '.join(m))
3409 ui.write(_("mq: %s\n") % ', '.join(m))
3408 else:
3410 else:
3409 # i18n: column positioning for "hg summary"
3411 # i18n: column positioning for "hg summary"
3410 ui.note(_("mq: (empty queue)\n"))
3412 ui.note(_("mq: (empty queue)\n"))
3411
3413
3412 def revsetmq(repo, subset, x):
3414 def revsetmq(repo, subset, x):
3413 """``mq()``
3415 """``mq()``
3414 Changesets managed by MQ.
3416 Changesets managed by MQ.
3415 """
3417 """
3416 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3418 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3417 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3419 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3418 return revset.baseset([r for r in subset if r in applied])
3420 return revset.baseset([r for r in subset if r in applied])
3419
3421
3420 # tell hggettext to extract docstrings from these functions:
3422 # tell hggettext to extract docstrings from these functions:
3421 i18nfunctions = [revsetmq]
3423 i18nfunctions = [revsetmq]
3422
3424
3423 def extsetup(ui):
3425 def extsetup(ui):
3424 # Ensure mq wrappers are called first, regardless of extension load order by
3426 # Ensure mq wrappers are called first, regardless of extension load order by
3425 # NOT wrapping in uisetup() and instead deferring to init stage two here.
3427 # NOT wrapping in uisetup() and instead deferring to init stage two here.
3426 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3428 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3427
3429
3428 extensions.wrapcommand(commands.table, 'import', mqimport)
3430 extensions.wrapcommand(commands.table, 'import', mqimport)
3429 cmdutil.summaryhooks.add('mq', summaryhook)
3431 cmdutil.summaryhooks.add('mq', summaryhook)
3430
3432
3431 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3433 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3432 entry[1].extend(mqopt)
3434 entry[1].extend(mqopt)
3433
3435
3434 nowrap = set(commands.norepo.split(" "))
3436 nowrap = set(commands.norepo.split(" "))
3435
3437
3436 def dotable(cmdtable):
3438 def dotable(cmdtable):
3437 for cmd in cmdtable.keys():
3439 for cmd in cmdtable.keys():
3438 cmd = cmdutil.parsealiases(cmd)[0]
3440 cmd = cmdutil.parsealiases(cmd)[0]
3439 if cmd in nowrap:
3441 if cmd in nowrap:
3440 continue
3442 continue
3441 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3443 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3442 entry[1].extend(mqopt)
3444 entry[1].extend(mqopt)
3443
3445
3444 dotable(commands.table)
3446 dotable(commands.table)
3445
3447
3446 for extname, extmodule in extensions.extensions():
3448 for extname, extmodule in extensions.extensions():
3447 if extmodule.__file__ != __file__:
3449 if extmodule.__file__ != __file__:
3448 dotable(getattr(extmodule, 'cmdtable', {}))
3450 dotable(getattr(extmodule, 'cmdtable', {}))
3449
3451
3450 revset.symbols['mq'] = revsetmq
3452 revset.symbols['mq'] = revsetmq
3451
3453
3452 colortable = {'qguard.negative': 'red',
3454 colortable = {'qguard.negative': 'red',
3453 'qguard.positive': 'yellow',
3455 'qguard.positive': 'yellow',
3454 'qguard.unguarded': 'green',
3456 'qguard.unguarded': 'green',
3455 'qseries.applied': 'blue bold underline',
3457 'qseries.applied': 'blue bold underline',
3456 'qseries.guarded': 'black bold',
3458 'qseries.guarded': 'black bold',
3457 'qseries.missing': 'red bold',
3459 'qseries.missing': 'red bold',
3458 'qseries.unapplied': 'black bold'}
3460 'qseries.unapplied': 'black bold'}
3459
3461
3460 commands.inferrepo += " qnew qrefresh qdiff qcommit"
3462 commands.inferrepo += " qnew qrefresh qdiff qcommit"
@@ -1,955 +1,955 b''
1 # rebase.py - rebasing feature for mercurial
1 # rebase.py - rebasing feature for mercurial
2 #
2 #
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot 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 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to move sets of revisions to a different ancestor
8 '''command to move sets of revisions to a different ancestor
9
9
10 This extension lets you rebase changesets in an existing Mercurial
10 This extension lets you rebase changesets in an existing Mercurial
11 repository.
11 repository.
12
12
13 For more information:
13 For more information:
14 http://mercurial.selenic.com/wiki/RebaseExtension
14 http://mercurial.selenic.com/wiki/RebaseExtension
15 '''
15 '''
16
16
17 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
17 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
18 from mercurial import extensions, patch, scmutil, phases, obsolete, error
18 from mercurial import extensions, patch, scmutil, phases, obsolete, error
19 from mercurial.commands import templateopts
19 from mercurial.commands import templateopts
20 from mercurial.node import nullrev
20 from mercurial.node import nullrev
21 from mercurial.lock import release
21 from mercurial.lock import release
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23 import os, errno
23 import os, errno
24
24
25 nullmerge = -2
25 nullmerge = -2
26 revignored = -3
26 revignored = -3
27
27
28 cmdtable = {}
28 cmdtable = {}
29 command = cmdutil.command(cmdtable)
29 command = cmdutil.command(cmdtable)
30 testedwith = 'internal'
30 testedwith = 'internal'
31
31
32 def _savegraft(ctx, extra):
32 def _savegraft(ctx, extra):
33 s = ctx.extra().get('source', None)
33 s = ctx.extra().get('source', None)
34 if s is not None:
34 if s is not None:
35 extra['source'] = s
35 extra['source'] = s
36
36
37 def _savebranch(ctx, extra):
37 def _savebranch(ctx, extra):
38 extra['branch'] = ctx.branch()
38 extra['branch'] = ctx.branch()
39
39
40 def _makeextrafn(copiers):
40 def _makeextrafn(copiers):
41 """make an extrafn out of the given copy-functions.
41 """make an extrafn out of the given copy-functions.
42
42
43 A copy function takes a context and an extra dict, and mutates the
43 A copy function takes a context and an extra dict, and mutates the
44 extra dict as needed based on the given context.
44 extra dict as needed based on the given context.
45 """
45 """
46 def extrafn(ctx, extra):
46 def extrafn(ctx, extra):
47 for c in copiers:
47 for c in copiers:
48 c(ctx, extra)
48 c(ctx, extra)
49 return extrafn
49 return extrafn
50
50
51 @command('rebase',
51 @command('rebase',
52 [('s', 'source', '',
52 [('s', 'source', '',
53 _('rebase from the specified changeset'), _('REV')),
53 _('rebase from the specified changeset'), _('REV')),
54 ('b', 'base', '',
54 ('b', 'base', '',
55 _('rebase from the base of the specified changeset '
55 _('rebase from the base of the specified changeset '
56 '(up to greatest common ancestor of base and dest)'),
56 '(up to greatest common ancestor of base and dest)'),
57 _('REV')),
57 _('REV')),
58 ('r', 'rev', [],
58 ('r', 'rev', [],
59 _('rebase these revisions'),
59 _('rebase these revisions'),
60 _('REV')),
60 _('REV')),
61 ('d', 'dest', '',
61 ('d', 'dest', '',
62 _('rebase onto the specified changeset'), _('REV')),
62 _('rebase onto the specified changeset'), _('REV')),
63 ('', 'collapse', False, _('collapse the rebased changesets')),
63 ('', 'collapse', False, _('collapse the rebased changesets')),
64 ('m', 'message', '',
64 ('m', 'message', '',
65 _('use text as collapse commit message'), _('TEXT')),
65 _('use text as collapse commit message'), _('TEXT')),
66 ('e', 'edit', False, _('invoke editor on commit messages')),
66 ('e', 'edit', False, _('invoke editor on commit messages')),
67 ('l', 'logfile', '',
67 ('l', 'logfile', '',
68 _('read collapse commit message from file'), _('FILE')),
68 _('read collapse commit message from file'), _('FILE')),
69 ('', 'keep', False, _('keep original changesets')),
69 ('', 'keep', False, _('keep original changesets')),
70 ('', 'keepbranches', False, _('keep original branch names')),
70 ('', 'keepbranches', False, _('keep original branch names')),
71 ('D', 'detach', False, _('(DEPRECATED)')),
71 ('D', 'detach', False, _('(DEPRECATED)')),
72 ('t', 'tool', '', _('specify merge tool')),
72 ('t', 'tool', '', _('specify merge tool')),
73 ('c', 'continue', False, _('continue an interrupted rebase')),
73 ('c', 'continue', False, _('continue an interrupted rebase')),
74 ('a', 'abort', False, _('abort an interrupted rebase'))] +
74 ('a', 'abort', False, _('abort an interrupted rebase'))] +
75 templateopts,
75 templateopts,
76 _('[-s REV | -b REV] [-d REV] [OPTION]'))
76 _('[-s REV | -b REV] [-d REV] [OPTION]'))
77 def rebase(ui, repo, **opts):
77 def rebase(ui, repo, **opts):
78 """move changeset (and descendants) to a different branch
78 """move changeset (and descendants) to a different branch
79
79
80 Rebase uses repeated merging to graft changesets from one part of
80 Rebase uses repeated merging to graft changesets from one part of
81 history (the source) onto another (the destination). This can be
81 history (the source) onto another (the destination). This can be
82 useful for linearizing *local* changes relative to a master
82 useful for linearizing *local* changes relative to a master
83 development tree.
83 development tree.
84
84
85 You should not rebase changesets that have already been shared
85 You should not rebase changesets that have already been shared
86 with others. Doing so will force everybody else to perform the
86 with others. Doing so will force everybody else to perform the
87 same rebase or they will end up with duplicated changesets after
87 same rebase or they will end up with duplicated changesets after
88 pulling in your rebased changesets.
88 pulling in your rebased changesets.
89
89
90 In its default configuration, Mercurial will prevent you from
90 In its default configuration, Mercurial will prevent you from
91 rebasing published changes. See :hg:`help phases` for details.
91 rebasing published changes. See :hg:`help phases` for details.
92
92
93 If you don't specify a destination changeset (``-d/--dest``),
93 If you don't specify a destination changeset (``-d/--dest``),
94 rebase uses the current branch tip as the destination. (The
94 rebase uses the current branch tip as the destination. (The
95 destination changeset is not modified by rebasing, but new
95 destination changeset is not modified by rebasing, but new
96 changesets are added as its descendants.)
96 changesets are added as its descendants.)
97
97
98 You can specify which changesets to rebase in two ways: as a
98 You can specify which changesets to rebase in two ways: as a
99 "source" changeset or as a "base" changeset. Both are shorthand
99 "source" changeset or as a "base" changeset. Both are shorthand
100 for a topologically related set of changesets (the "source
100 for a topologically related set of changesets (the "source
101 branch"). If you specify source (``-s/--source``), rebase will
101 branch"). If you specify source (``-s/--source``), rebase will
102 rebase that changeset and all of its descendants onto dest. If you
102 rebase that changeset and all of its descendants onto dest. If you
103 specify base (``-b/--base``), rebase will select ancestors of base
103 specify base (``-b/--base``), rebase will select ancestors of base
104 back to but not including the common ancestor with dest. Thus,
104 back to but not including the common ancestor with dest. Thus,
105 ``-b`` is less precise but more convenient than ``-s``: you can
105 ``-b`` is less precise but more convenient than ``-s``: you can
106 specify any changeset in the source branch, and rebase will select
106 specify any changeset in the source branch, and rebase will select
107 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
107 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
108 uses the parent of the working directory as the base.
108 uses the parent of the working directory as the base.
109
109
110 For advanced usage, a third way is available through the ``--rev``
110 For advanced usage, a third way is available through the ``--rev``
111 option. It allows you to specify an arbitrary set of changesets to
111 option. It allows you to specify an arbitrary set of changesets to
112 rebase. Descendants of revs you specify with this option are not
112 rebase. Descendants of revs you specify with this option are not
113 automatically included in the rebase.
113 automatically included in the rebase.
114
114
115 By default, rebase recreates the changesets in the source branch
115 By default, rebase recreates the changesets in the source branch
116 as descendants of dest and then destroys the originals. Use
116 as descendants of dest and then destroys the originals. Use
117 ``--keep`` to preserve the original source changesets. Some
117 ``--keep`` to preserve the original source changesets. Some
118 changesets in the source branch (e.g. merges from the destination
118 changesets in the source branch (e.g. merges from the destination
119 branch) may be dropped if they no longer contribute any change.
119 branch) may be dropped if they no longer contribute any change.
120
120
121 One result of the rules for selecting the destination changeset
121 One result of the rules for selecting the destination changeset
122 and source branch is that, unlike ``merge``, rebase will do
122 and source branch is that, unlike ``merge``, rebase will do
123 nothing if you are at the branch tip of a named branch
123 nothing if you are at the branch tip of a named branch
124 with two heads. You need to explicitly specify source and/or
124 with two heads. You need to explicitly specify source and/or
125 destination (or ``update`` to the other head, if it's the head of
125 destination (or ``update`` to the other head, if it's the head of
126 the intended source branch).
126 the intended source branch).
127
127
128 If a rebase is interrupted to manually resolve a merge, it can be
128 If a rebase is interrupted to manually resolve a merge, it can be
129 continued with --continue/-c or aborted with --abort/-a.
129 continued with --continue/-c or aborted with --abort/-a.
130
130
131 Returns 0 on success, 1 if nothing to rebase or there are
131 Returns 0 on success, 1 if nothing to rebase or there are
132 unresolved conflicts.
132 unresolved conflicts.
133 """
133 """
134 originalwd = target = None
134 originalwd = target = None
135 activebookmark = None
135 activebookmark = None
136 external = nullrev
136 external = nullrev
137 state = {}
137 state = {}
138 skipped = set()
138 skipped = set()
139 targetancestors = set()
139 targetancestors = set()
140
140
141 editor = None
141 editor = None
142 if opts.get('edit'):
142 if opts.get('edit'):
143 editor = cmdutil.commitforceeditor
143 editor = cmdutil.commitforceeditor
144
144
145 lock = wlock = None
145 lock = wlock = None
146 try:
146 try:
147 wlock = repo.wlock()
147 wlock = repo.wlock()
148 lock = repo.lock()
148 lock = repo.lock()
149
149
150 # Validate input and define rebasing points
150 # Validate input and define rebasing points
151 destf = opts.get('dest', None)
151 destf = opts.get('dest', None)
152 srcf = opts.get('source', None)
152 srcf = opts.get('source', None)
153 basef = opts.get('base', None)
153 basef = opts.get('base', None)
154 revf = opts.get('rev', [])
154 revf = opts.get('rev', [])
155 contf = opts.get('continue')
155 contf = opts.get('continue')
156 abortf = opts.get('abort')
156 abortf = opts.get('abort')
157 collapsef = opts.get('collapse', False)
157 collapsef = opts.get('collapse', False)
158 collapsemsg = cmdutil.logmessage(ui, opts)
158 collapsemsg = cmdutil.logmessage(ui, opts)
159 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
159 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
160 extrafns = [_savegraft]
160 extrafns = [_savegraft]
161 if e:
161 if e:
162 extrafns = [e]
162 extrafns = [e]
163 keepf = opts.get('keep', False)
163 keepf = opts.get('keep', False)
164 keepbranchesf = opts.get('keepbranches', False)
164 keepbranchesf = opts.get('keepbranches', False)
165 # keepopen is not meant for use on the command line, but by
165 # keepopen is not meant for use on the command line, but by
166 # other extensions
166 # other extensions
167 keepopen = opts.get('keepopen', False)
167 keepopen = opts.get('keepopen', False)
168
168
169 if collapsemsg and not collapsef:
169 if collapsemsg and not collapsef:
170 raise util.Abort(
170 raise util.Abort(
171 _('message can only be specified with collapse'))
171 _('message can only be specified with collapse'))
172
172
173 if contf or abortf:
173 if contf or abortf:
174 if contf and abortf:
174 if contf and abortf:
175 raise util.Abort(_('cannot use both abort and continue'))
175 raise util.Abort(_('cannot use both abort and continue'))
176 if collapsef:
176 if collapsef:
177 raise util.Abort(
177 raise util.Abort(
178 _('cannot use collapse with continue or abort'))
178 _('cannot use collapse with continue or abort'))
179 if srcf or basef or destf:
179 if srcf or basef or destf:
180 raise util.Abort(
180 raise util.Abort(
181 _('abort and continue do not allow specifying revisions'))
181 _('abort and continue do not allow specifying revisions'))
182 if opts.get('tool', False):
182 if opts.get('tool', False):
183 ui.warn(_('tool option will be ignored\n'))
183 ui.warn(_('tool option will be ignored\n'))
184
184
185 try:
185 try:
186 (originalwd, target, state, skipped, collapsef, keepf,
186 (originalwd, target, state, skipped, collapsef, keepf,
187 keepbranchesf, external, activebookmark) = restorestatus(repo)
187 keepbranchesf, external, activebookmark) = restorestatus(repo)
188 except error.RepoLookupError:
188 except error.RepoLookupError:
189 if abortf:
189 if abortf:
190 clearstatus(repo)
190 clearstatus(repo)
191 repo.ui.warn(_('rebase aborted (no revision is removed,'
191 repo.ui.warn(_('rebase aborted (no revision is removed,'
192 ' only broken state is cleared)\n'))
192 ' only broken state is cleared)\n'))
193 return 0
193 return 0
194 else:
194 else:
195 msg = _('cannot continue inconsistent rebase')
195 msg = _('cannot continue inconsistent rebase')
196 hint = _('use "hg rebase --abort" to clear broken state')
196 hint = _('use "hg rebase --abort" to clear broken state')
197 raise util.Abort(msg, hint=hint)
197 raise util.Abort(msg, hint=hint)
198 if abortf:
198 if abortf:
199 return abort(repo, originalwd, target, state)
199 return abort(repo, originalwd, target, state)
200 else:
200 else:
201 if srcf and basef:
201 if srcf and basef:
202 raise util.Abort(_('cannot specify both a '
202 raise util.Abort(_('cannot specify both a '
203 'source and a base'))
203 'source and a base'))
204 if revf and basef:
204 if revf and basef:
205 raise util.Abort(_('cannot specify both a '
205 raise util.Abort(_('cannot specify both a '
206 'revision and a base'))
206 'revision and a base'))
207 if revf and srcf:
207 if revf and srcf:
208 raise util.Abort(_('cannot specify both a '
208 raise util.Abort(_('cannot specify both a '
209 'revision and a source'))
209 'revision and a source'))
210
210
211 cmdutil.checkunfinished(repo)
211 cmdutil.checkunfinished(repo)
212 cmdutil.bailifchanged(repo)
212 cmdutil.bailifchanged(repo)
213
213
214 if not destf:
214 if not destf:
215 # Destination defaults to the latest revision in the
215 # Destination defaults to the latest revision in the
216 # current branch
216 # current branch
217 branch = repo[None].branch()
217 branch = repo[None].branch()
218 dest = repo[branch]
218 dest = repo[branch]
219 else:
219 else:
220 dest = scmutil.revsingle(repo, destf)
220 dest = scmutil.revsingle(repo, destf)
221
221
222 if revf:
222 if revf:
223 rebaseset = scmutil.revrange(repo, revf)
223 rebaseset = scmutil.revrange(repo, revf)
224 if not rebaseset:
224 if not rebaseset:
225 raise util.Abort(_('empty "rev" revision set - '
225 raise util.Abort(_('empty "rev" revision set - '
226 'nothing to rebase'))
226 'nothing to rebase'))
227 elif srcf:
227 elif srcf:
228 src = scmutil.revrange(repo, [srcf])
228 src = scmutil.revrange(repo, [srcf])
229 if not src:
229 if not src:
230 raise util.Abort(_('empty "source" revision set - '
230 raise util.Abort(_('empty "source" revision set - '
231 'nothing to rebase'))
231 'nothing to rebase'))
232 rebaseset = repo.revs('(%ld)::', src)
232 rebaseset = repo.revs('(%ld)::', src)
233 assert rebaseset
233 assert rebaseset
234 else:
234 else:
235 base = scmutil.revrange(repo, [basef or '.'])
235 base = scmutil.revrange(repo, [basef or '.'])
236 if not base:
236 if not base:
237 raise util.Abort(_('empty "base" revision set - '
237 raise util.Abort(_('empty "base" revision set - '
238 "can't compute rebase set"))
238 "can't compute rebase set"))
239 rebaseset = repo.revs(
239 rebaseset = repo.revs(
240 '(children(ancestor(%ld, %d)) and ::(%ld))::',
240 '(children(ancestor(%ld, %d)) and ::(%ld))::',
241 base, dest, base)
241 base, dest, base)
242 if not rebaseset:
242 if not rebaseset:
243 if base == [dest.rev()]:
243 if base == [dest.rev()]:
244 if basef:
244 if basef:
245 ui.status(_('nothing to rebase - %s is both "base"'
245 ui.status(_('nothing to rebase - %s is both "base"'
246 ' and destination\n') % dest)
246 ' and destination\n') % dest)
247 else:
247 else:
248 ui.status(_('nothing to rebase - working directory '
248 ui.status(_('nothing to rebase - working directory '
249 'parent is also destination\n'))
249 'parent is also destination\n'))
250 elif not repo.revs('%ld - ::%d', base, dest):
250 elif not repo.revs('%ld - ::%d', base, dest):
251 if basef:
251 if basef:
252 ui.status(_('nothing to rebase - "base" %s is '
252 ui.status(_('nothing to rebase - "base" %s is '
253 'already an ancestor of destination '
253 'already an ancestor of destination '
254 '%s\n') %
254 '%s\n') %
255 ('+'.join(str(repo[r]) for r in base),
255 ('+'.join(str(repo[r]) for r in base),
256 dest))
256 dest))
257 else:
257 else:
258 ui.status(_('nothing to rebase - working '
258 ui.status(_('nothing to rebase - working '
259 'directory parent is already an '
259 'directory parent is already an '
260 'ancestor of destination %s\n') % dest)
260 'ancestor of destination %s\n') % dest)
261 else: # can it happen?
261 else: # can it happen?
262 ui.status(_('nothing to rebase from %s to %s\n') %
262 ui.status(_('nothing to rebase from %s to %s\n') %
263 ('+'.join(str(repo[r]) for r in base), dest))
263 ('+'.join(str(repo[r]) for r in base), dest))
264 return 1
264 return 1
265
265
266 if (not (keepf or obsolete._enabled)
266 if (not (keepf or obsolete._enabled)
267 and repo.revs('first(children(%ld) - %ld)',
267 and repo.revs('first(children(%ld) - %ld)',
268 rebaseset, rebaseset)):
268 rebaseset, rebaseset)):
269 raise util.Abort(
269 raise util.Abort(
270 _("can't remove original changesets with"
270 _("can't remove original changesets with"
271 " unrebased descendants"),
271 " unrebased descendants"),
272 hint=_('use --keep to keep original changesets'))
272 hint=_('use --keep to keep original changesets'))
273
273
274 result = buildstate(repo, dest, rebaseset, collapsef)
274 result = buildstate(repo, dest, rebaseset, collapsef)
275 if not result:
275 if not result:
276 # Empty state built, nothing to rebase
276 # Empty state built, nothing to rebase
277 ui.status(_('nothing to rebase\n'))
277 ui.status(_('nothing to rebase\n'))
278 return 1
278 return 1
279
279
280 root = min(rebaseset)
280 root = min(rebaseset)
281 if not keepf and not repo[root].mutable():
281 if not keepf and not repo[root].mutable():
282 raise util.Abort(_("can't rebase immutable changeset %s")
282 raise util.Abort(_("can't rebase immutable changeset %s")
283 % repo[root],
283 % repo[root],
284 hint=_('see hg help phases for details'))
284 hint=_('see hg help phases for details'))
285
285
286 originalwd, target, state = result
286 originalwd, target, state = result
287 if collapsef:
287 if collapsef:
288 targetancestors = repo.changelog.ancestors([target],
288 targetancestors = repo.changelog.ancestors([target],
289 inclusive=True)
289 inclusive=True)
290 external = externalparent(repo, state, targetancestors)
290 external = externalparent(repo, state, targetancestors)
291
291
292 if keepbranchesf:
292 if keepbranchesf:
293 # insert _savebranch at the start of extrafns so if
293 # insert _savebranch at the start of extrafns so if
294 # there's a user-provided extrafn it can clobber branch if
294 # there's a user-provided extrafn it can clobber branch if
295 # desired
295 # desired
296 extrafns.insert(0, _savebranch)
296 extrafns.insert(0, _savebranch)
297 if collapsef:
297 if collapsef:
298 branches = set()
298 branches = set()
299 for rev in state:
299 for rev in state:
300 branches.add(repo[rev].branch())
300 branches.add(repo[rev].branch())
301 if len(branches) > 1:
301 if len(branches) > 1:
302 raise util.Abort(_('cannot collapse multiple named '
302 raise util.Abort(_('cannot collapse multiple named '
303 'branches'))
303 'branches'))
304
304
305 # Rebase
305 # Rebase
306 if not targetancestors:
306 if not targetancestors:
307 targetancestors = repo.changelog.ancestors([target], inclusive=True)
307 targetancestors = repo.changelog.ancestors([target], inclusive=True)
308
308
309 # Keep track of the current bookmarks in order to reset them later
309 # Keep track of the current bookmarks in order to reset them later
310 currentbookmarks = repo._bookmarks.copy()
310 currentbookmarks = repo._bookmarks.copy()
311 activebookmark = activebookmark or repo._bookmarkcurrent
311 activebookmark = activebookmark or repo._bookmarkcurrent
312 if activebookmark:
312 if activebookmark:
313 bookmarks.unsetcurrent(repo)
313 bookmarks.unsetcurrent(repo)
314
314
315 extrafn = _makeextrafn(extrafns)
315 extrafn = _makeextrafn(extrafns)
316
316
317 sortedstate = sorted(state)
317 sortedstate = sorted(state)
318 total = len(sortedstate)
318 total = len(sortedstate)
319 pos = 0
319 pos = 0
320 for rev in sortedstate:
320 for rev in sortedstate:
321 pos += 1
321 pos += 1
322 if state[rev] == -1:
322 if state[rev] == -1:
323 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
323 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
324 _('changesets'), total)
324 _('changesets'), total)
325 p1, p2 = defineparents(repo, rev, target, state,
325 p1, p2 = defineparents(repo, rev, target, state,
326 targetancestors)
326 targetancestors)
327 storestatus(repo, originalwd, target, state, collapsef, keepf,
327 storestatus(repo, originalwd, target, state, collapsef, keepf,
328 keepbranchesf, external, activebookmark)
328 keepbranchesf, external, activebookmark)
329 if len(repo.parents()) == 2:
329 if len(repo.parents()) == 2:
330 repo.ui.debug('resuming interrupted rebase\n')
330 repo.ui.debug('resuming interrupted rebase\n')
331 else:
331 else:
332 try:
332 try:
333 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
333 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
334 stats = rebasenode(repo, rev, p1, state, collapsef)
334 stats = rebasenode(repo, rev, p1, state, collapsef)
335 if stats and stats[3] > 0:
335 if stats and stats[3] > 0:
336 raise error.InterventionRequired(
336 raise error.InterventionRequired(
337 _('unresolved conflicts (see hg '
337 _('unresolved conflicts (see hg '
338 'resolve, then hg rebase --continue)'))
338 'resolve, then hg rebase --continue)'))
339 finally:
339 finally:
340 ui.setconfig('ui', 'forcemerge', '')
340 ui.setconfig('ui', 'forcemerge', '')
341 cmdutil.duplicatecopies(repo, rev, target)
341 cmdutil.duplicatecopies(repo, rev, target)
342 if not collapsef:
342 if not collapsef:
343 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
343 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
344 editor=editor)
344 editor=editor)
345 else:
345 else:
346 # Skip commit if we are collapsing
346 # Skip commit if we are collapsing
347 repo.setparents(repo[p1].node())
347 repo.setparents(repo[p1].node())
348 newrev = None
348 newrev = None
349 # Update the state
349 # Update the state
350 if newrev is not None:
350 if newrev is not None:
351 state[rev] = repo[newrev].rev()
351 state[rev] = repo[newrev].rev()
352 else:
352 else:
353 if not collapsef:
353 if not collapsef:
354 ui.note(_('no changes, revision %d skipped\n') % rev)
354 ui.note(_('no changes, revision %d skipped\n') % rev)
355 ui.debug('next revision set to %s\n' % p1)
355 ui.debug('next revision set to %s\n' % p1)
356 skipped.add(rev)
356 skipped.add(rev)
357 state[rev] = p1
357 state[rev] = p1
358
358
359 ui.progress(_('rebasing'), None)
359 ui.progress(_('rebasing'), None)
360 ui.note(_('rebase merging completed\n'))
360 ui.note(_('rebase merging completed\n'))
361
361
362 if collapsef and not keepopen:
362 if collapsef and not keepopen:
363 p1, p2 = defineparents(repo, min(state), target,
363 p1, p2 = defineparents(repo, min(state), target,
364 state, targetancestors)
364 state, targetancestors)
365 if collapsemsg:
365 if collapsemsg:
366 commitmsg = collapsemsg
366 commitmsg = collapsemsg
367 else:
367 else:
368 commitmsg = 'Collapsed revision'
368 commitmsg = 'Collapsed revision'
369 for rebased in state:
369 for rebased in state:
370 if rebased not in skipped and state[rebased] > nullmerge:
370 if rebased not in skipped and state[rebased] > nullmerge:
371 commitmsg += '\n* %s' % repo[rebased].description()
371 commitmsg += '\n* %s' % repo[rebased].description()
372 commitmsg = ui.edit(commitmsg, repo.ui.username())
372 editor = cmdutil.commitforceeditor
373 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
373 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
374 extrafn=extrafn, editor=editor)
374 extrafn=extrafn, editor=editor)
375 for oldrev in state.iterkeys():
375 for oldrev in state.iterkeys():
376 if state[oldrev] > nullmerge:
376 if state[oldrev] > nullmerge:
377 state[oldrev] = newrev
377 state[oldrev] = newrev
378
378
379 if 'qtip' in repo.tags():
379 if 'qtip' in repo.tags():
380 updatemq(repo, state, skipped, **opts)
380 updatemq(repo, state, skipped, **opts)
381
381
382 if currentbookmarks:
382 if currentbookmarks:
383 # Nodeids are needed to reset bookmarks
383 # Nodeids are needed to reset bookmarks
384 nstate = {}
384 nstate = {}
385 for k, v in state.iteritems():
385 for k, v in state.iteritems():
386 if v > nullmerge:
386 if v > nullmerge:
387 nstate[repo[k].node()] = repo[v].node()
387 nstate[repo[k].node()] = repo[v].node()
388 # XXX this is the same as dest.node() for the non-continue path --
388 # XXX this is the same as dest.node() for the non-continue path --
389 # this should probably be cleaned up
389 # this should probably be cleaned up
390 targetnode = repo[target].node()
390 targetnode = repo[target].node()
391
391
392 # restore original working directory
392 # restore original working directory
393 # (we do this before stripping)
393 # (we do this before stripping)
394 newwd = state.get(originalwd, originalwd)
394 newwd = state.get(originalwd, originalwd)
395 if newwd not in [c.rev() for c in repo[None].parents()]:
395 if newwd not in [c.rev() for c in repo[None].parents()]:
396 ui.note(_("update back to initial working directory parent\n"))
396 ui.note(_("update back to initial working directory parent\n"))
397 hg.updaterepo(repo, newwd, False)
397 hg.updaterepo(repo, newwd, False)
398
398
399 if not keepf:
399 if not keepf:
400 collapsedas = None
400 collapsedas = None
401 if collapsef:
401 if collapsef:
402 collapsedas = newrev
402 collapsedas = newrev
403 clearrebased(ui, repo, state, skipped, collapsedas)
403 clearrebased(ui, repo, state, skipped, collapsedas)
404
404
405 if currentbookmarks:
405 if currentbookmarks:
406 updatebookmarks(repo, targetnode, nstate, currentbookmarks)
406 updatebookmarks(repo, targetnode, nstate, currentbookmarks)
407 if activebookmark not in repo._bookmarks:
407 if activebookmark not in repo._bookmarks:
408 # active bookmark was divergent one and has been deleted
408 # active bookmark was divergent one and has been deleted
409 activebookmark = None
409 activebookmark = None
410
410
411 clearstatus(repo)
411 clearstatus(repo)
412 ui.note(_("rebase completed\n"))
412 ui.note(_("rebase completed\n"))
413 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
413 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
414 if skipped:
414 if skipped:
415 ui.note(_("%d revisions have been skipped\n") % len(skipped))
415 ui.note(_("%d revisions have been skipped\n") % len(skipped))
416
416
417 if (activebookmark and
417 if (activebookmark and
418 repo['.'].node() == repo._bookmarks[activebookmark]):
418 repo['.'].node() == repo._bookmarks[activebookmark]):
419 bookmarks.setcurrent(repo, activebookmark)
419 bookmarks.setcurrent(repo, activebookmark)
420
420
421 finally:
421 finally:
422 release(lock, wlock)
422 release(lock, wlock)
423
423
424 def externalparent(repo, state, targetancestors):
424 def externalparent(repo, state, targetancestors):
425 """Return the revision that should be used as the second parent
425 """Return the revision that should be used as the second parent
426 when the revisions in state is collapsed on top of targetancestors.
426 when the revisions in state is collapsed on top of targetancestors.
427 Abort if there is more than one parent.
427 Abort if there is more than one parent.
428 """
428 """
429 parents = set()
429 parents = set()
430 source = min(state)
430 source = min(state)
431 for rev in state:
431 for rev in state:
432 if rev == source:
432 if rev == source:
433 continue
433 continue
434 for p in repo[rev].parents():
434 for p in repo[rev].parents():
435 if (p.rev() not in state
435 if (p.rev() not in state
436 and p.rev() not in targetancestors):
436 and p.rev() not in targetancestors):
437 parents.add(p.rev())
437 parents.add(p.rev())
438 if not parents:
438 if not parents:
439 return nullrev
439 return nullrev
440 if len(parents) == 1:
440 if len(parents) == 1:
441 return parents.pop()
441 return parents.pop()
442 raise util.Abort(_('unable to collapse on top of %s, there is more '
442 raise util.Abort(_('unable to collapse on top of %s, there is more '
443 'than one external parent: %s') %
443 'than one external parent: %s') %
444 (max(targetancestors),
444 (max(targetancestors),
445 ', '.join(str(p) for p in sorted(parents))))
445 ', '.join(str(p) for p in sorted(parents))))
446
446
447 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
447 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
448 'Commit the changes and store useful information in extra'
448 'Commit the changes and store useful information in extra'
449 try:
449 try:
450 repo.setparents(repo[p1].node(), repo[p2].node())
450 repo.setparents(repo[p1].node(), repo[p2].node())
451 ctx = repo[rev]
451 ctx = repo[rev]
452 if commitmsg is None:
452 if commitmsg is None:
453 commitmsg = ctx.description()
453 commitmsg = ctx.description()
454 extra = {'rebase_source': ctx.hex()}
454 extra = {'rebase_source': ctx.hex()}
455 if extrafn:
455 if extrafn:
456 extrafn(ctx, extra)
456 extrafn(ctx, extra)
457 # Commit might fail if unresolved files exist
457 # Commit might fail if unresolved files exist
458 newrev = repo.commit(text=commitmsg, user=ctx.user(),
458 newrev = repo.commit(text=commitmsg, user=ctx.user(),
459 date=ctx.date(), extra=extra, editor=editor)
459 date=ctx.date(), extra=extra, editor=editor)
460 repo.dirstate.setbranch(repo[newrev].branch())
460 repo.dirstate.setbranch(repo[newrev].branch())
461 targetphase = max(ctx.phase(), phases.draft)
461 targetphase = max(ctx.phase(), phases.draft)
462 # retractboundary doesn't overwrite upper phase inherited from parent
462 # retractboundary doesn't overwrite upper phase inherited from parent
463 newnode = repo[newrev].node()
463 newnode = repo[newrev].node()
464 if newnode:
464 if newnode:
465 phases.retractboundary(repo, targetphase, [newnode])
465 phases.retractboundary(repo, targetphase, [newnode])
466 return newrev
466 return newrev
467 except util.Abort:
467 except util.Abort:
468 # Invalidate the previous setparents
468 # Invalidate the previous setparents
469 repo.dirstate.invalidate()
469 repo.dirstate.invalidate()
470 raise
470 raise
471
471
472 def rebasenode(repo, rev, p1, state, collapse):
472 def rebasenode(repo, rev, p1, state, collapse):
473 'Rebase a single revision'
473 'Rebase a single revision'
474 # Merge phase
474 # Merge phase
475 # Update to target and merge it with local
475 # Update to target and merge it with local
476 if repo['.'].rev() != repo[p1].rev():
476 if repo['.'].rev() != repo[p1].rev():
477 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
477 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
478 merge.update(repo, p1, False, True, False)
478 merge.update(repo, p1, False, True, False)
479 else:
479 else:
480 repo.ui.debug(" already in target\n")
480 repo.ui.debug(" already in target\n")
481 repo.dirstate.write()
481 repo.dirstate.write()
482 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
482 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
483 if repo[rev].rev() == repo[min(state)].rev():
483 if repo[rev].rev() == repo[min(state)].rev():
484 # Case (1) initial changeset of a non-detaching rebase.
484 # Case (1) initial changeset of a non-detaching rebase.
485 # Let the merge mechanism find the base itself.
485 # Let the merge mechanism find the base itself.
486 base = None
486 base = None
487 elif not repo[rev].p2():
487 elif not repo[rev].p2():
488 # Case (2) detaching the node with a single parent, use this parent
488 # Case (2) detaching the node with a single parent, use this parent
489 base = repo[rev].p1().node()
489 base = repo[rev].p1().node()
490 else:
490 else:
491 # In case of merge, we need to pick the right parent as merge base.
491 # In case of merge, we need to pick the right parent as merge base.
492 #
492 #
493 # Imagine we have:
493 # Imagine we have:
494 # - M: currently rebase revision in this step
494 # - M: currently rebase revision in this step
495 # - A: one parent of M
495 # - A: one parent of M
496 # - B: second parent of M
496 # - B: second parent of M
497 # - D: destination of this merge step (p1 var)
497 # - D: destination of this merge step (p1 var)
498 #
498 #
499 # If we are rebasing on D, D is the successors of A or B. The right
499 # If we are rebasing on D, D is the successors of A or B. The right
500 # merge base is the one D succeed to. We pretend it is B for the rest
500 # merge base is the one D succeed to. We pretend it is B for the rest
501 # of this comment
501 # of this comment
502 #
502 #
503 # If we pick B as the base, the merge involves:
503 # If we pick B as the base, the merge involves:
504 # - changes from B to M (actual changeset payload)
504 # - changes from B to M (actual changeset payload)
505 # - changes from B to D (induced by rebase) as D is a rebased
505 # - changes from B to D (induced by rebase) as D is a rebased
506 # version of B)
506 # version of B)
507 # Which exactly represent the rebase operation.
507 # Which exactly represent the rebase operation.
508 #
508 #
509 # If we pick the A as the base, the merge involves
509 # If we pick the A as the base, the merge involves
510 # - changes from A to M (actual changeset payload)
510 # - changes from A to M (actual changeset payload)
511 # - changes from A to D (with include changes between unrelated A and B
511 # - changes from A to D (with include changes between unrelated A and B
512 # plus changes induced by rebase)
512 # plus changes induced by rebase)
513 # Which does not represent anything sensible and creates a lot of
513 # Which does not represent anything sensible and creates a lot of
514 # conflicts.
514 # conflicts.
515 for p in repo[rev].parents():
515 for p in repo[rev].parents():
516 if state.get(p.rev()) == repo[p1].rev():
516 if state.get(p.rev()) == repo[p1].rev():
517 base = p.node()
517 base = p.node()
518 break
518 break
519 else: # fallback when base not found
519 else: # fallback when base not found
520 base = None
520 base = None
521
521
522 # Raise because this function is called wrong (see issue 4106)
522 # Raise because this function is called wrong (see issue 4106)
523 raise AssertionError('no base found to rebase on '
523 raise AssertionError('no base found to rebase on '
524 '(rebasenode called wrong)')
524 '(rebasenode called wrong)')
525 if base is not None:
525 if base is not None:
526 repo.ui.debug(" detach base %d:%s\n" % (repo[base].rev(), repo[base]))
526 repo.ui.debug(" detach base %d:%s\n" % (repo[base].rev(), repo[base]))
527 # When collapsing in-place, the parent is the common ancestor, we
527 # When collapsing in-place, the parent is the common ancestor, we
528 # have to allow merging with it.
528 # have to allow merging with it.
529 return merge.update(repo, rev, True, True, False, base, collapse)
529 return merge.update(repo, rev, True, True, False, base, collapse)
530
530
531 def nearestrebased(repo, rev, state):
531 def nearestrebased(repo, rev, state):
532 """return the nearest ancestors of rev in the rebase result"""
532 """return the nearest ancestors of rev in the rebase result"""
533 rebased = [r for r in state if state[r] > nullmerge]
533 rebased = [r for r in state if state[r] > nullmerge]
534 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
534 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
535 if candidates:
535 if candidates:
536 return state[candidates[0]]
536 return state[candidates[0]]
537 else:
537 else:
538 return None
538 return None
539
539
540 def defineparents(repo, rev, target, state, targetancestors):
540 def defineparents(repo, rev, target, state, targetancestors):
541 'Return the new parent relationship of the revision that will be rebased'
541 'Return the new parent relationship of the revision that will be rebased'
542 parents = repo[rev].parents()
542 parents = repo[rev].parents()
543 p1 = p2 = nullrev
543 p1 = p2 = nullrev
544
544
545 P1n = parents[0].rev()
545 P1n = parents[0].rev()
546 if P1n in targetancestors:
546 if P1n in targetancestors:
547 p1 = target
547 p1 = target
548 elif P1n in state:
548 elif P1n in state:
549 if state[P1n] == nullmerge:
549 if state[P1n] == nullmerge:
550 p1 = target
550 p1 = target
551 elif state[P1n] == revignored:
551 elif state[P1n] == revignored:
552 p1 = nearestrebased(repo, P1n, state)
552 p1 = nearestrebased(repo, P1n, state)
553 if p1 is None:
553 if p1 is None:
554 p1 = target
554 p1 = target
555 else:
555 else:
556 p1 = state[P1n]
556 p1 = state[P1n]
557 else: # P1n external
557 else: # P1n external
558 p1 = target
558 p1 = target
559 p2 = P1n
559 p2 = P1n
560
560
561 if len(parents) == 2 and parents[1].rev() not in targetancestors:
561 if len(parents) == 2 and parents[1].rev() not in targetancestors:
562 P2n = parents[1].rev()
562 P2n = parents[1].rev()
563 # interesting second parent
563 # interesting second parent
564 if P2n in state:
564 if P2n in state:
565 if p1 == target: # P1n in targetancestors or external
565 if p1 == target: # P1n in targetancestors or external
566 p1 = state[P2n]
566 p1 = state[P2n]
567 elif state[P2n] == revignored:
567 elif state[P2n] == revignored:
568 p2 = nearestrebased(repo, P2n, state)
568 p2 = nearestrebased(repo, P2n, state)
569 if p2 is None:
569 if p2 is None:
570 # no ancestors rebased yet, detach
570 # no ancestors rebased yet, detach
571 p2 = target
571 p2 = target
572 else:
572 else:
573 p2 = state[P2n]
573 p2 = state[P2n]
574 else: # P2n external
574 else: # P2n external
575 if p2 != nullrev: # P1n external too => rev is a merged revision
575 if p2 != nullrev: # P1n external too => rev is a merged revision
576 raise util.Abort(_('cannot use revision %d as base, result '
576 raise util.Abort(_('cannot use revision %d as base, result '
577 'would have 3 parents') % rev)
577 'would have 3 parents') % rev)
578 p2 = P2n
578 p2 = P2n
579 repo.ui.debug(" future parents are %d and %d\n" %
579 repo.ui.debug(" future parents are %d and %d\n" %
580 (repo[p1].rev(), repo[p2].rev()))
580 (repo[p1].rev(), repo[p2].rev()))
581 return p1, p2
581 return p1, p2
582
582
583 def isagitpatch(repo, patchname):
583 def isagitpatch(repo, patchname):
584 'Return true if the given patch is in git format'
584 'Return true if the given patch is in git format'
585 mqpatch = os.path.join(repo.mq.path, patchname)
585 mqpatch = os.path.join(repo.mq.path, patchname)
586 for line in patch.linereader(file(mqpatch, 'rb')):
586 for line in patch.linereader(file(mqpatch, 'rb')):
587 if line.startswith('diff --git'):
587 if line.startswith('diff --git'):
588 return True
588 return True
589 return False
589 return False
590
590
591 def updatemq(repo, state, skipped, **opts):
591 def updatemq(repo, state, skipped, **opts):
592 'Update rebased mq patches - finalize and then import them'
592 'Update rebased mq patches - finalize and then import them'
593 mqrebase = {}
593 mqrebase = {}
594 mq = repo.mq
594 mq = repo.mq
595 original_series = mq.fullseries[:]
595 original_series = mq.fullseries[:]
596 skippedpatches = set()
596 skippedpatches = set()
597
597
598 for p in mq.applied:
598 for p in mq.applied:
599 rev = repo[p.node].rev()
599 rev = repo[p.node].rev()
600 if rev in state:
600 if rev in state:
601 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
601 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
602 (rev, p.name))
602 (rev, p.name))
603 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
603 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
604 else:
604 else:
605 # Applied but not rebased, not sure this should happen
605 # Applied but not rebased, not sure this should happen
606 skippedpatches.add(p.name)
606 skippedpatches.add(p.name)
607
607
608 if mqrebase:
608 if mqrebase:
609 mq.finish(repo, mqrebase.keys())
609 mq.finish(repo, mqrebase.keys())
610
610
611 # We must start import from the newest revision
611 # We must start import from the newest revision
612 for rev in sorted(mqrebase, reverse=True):
612 for rev in sorted(mqrebase, reverse=True):
613 if rev not in skipped:
613 if rev not in skipped:
614 name, isgit = mqrebase[rev]
614 name, isgit = mqrebase[rev]
615 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
615 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
616 mq.qimport(repo, (), patchname=name, git=isgit,
616 mq.qimport(repo, (), patchname=name, git=isgit,
617 rev=[str(state[rev])])
617 rev=[str(state[rev])])
618 else:
618 else:
619 # Rebased and skipped
619 # Rebased and skipped
620 skippedpatches.add(mqrebase[rev][0])
620 skippedpatches.add(mqrebase[rev][0])
621
621
622 # Patches were either applied and rebased and imported in
622 # Patches were either applied and rebased and imported in
623 # order, applied and removed or unapplied. Discard the removed
623 # order, applied and removed or unapplied. Discard the removed
624 # ones while preserving the original series order and guards.
624 # ones while preserving the original series order and guards.
625 newseries = [s for s in original_series
625 newseries = [s for s in original_series
626 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
626 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
627 mq.fullseries[:] = newseries
627 mq.fullseries[:] = newseries
628 mq.seriesdirty = True
628 mq.seriesdirty = True
629 mq.savedirty()
629 mq.savedirty()
630
630
631 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
631 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
632 'Move bookmarks to their correct changesets, and delete divergent ones'
632 'Move bookmarks to their correct changesets, and delete divergent ones'
633 marks = repo._bookmarks
633 marks = repo._bookmarks
634 for k, v in originalbookmarks.iteritems():
634 for k, v in originalbookmarks.iteritems():
635 if v in nstate:
635 if v in nstate:
636 # update the bookmarks for revs that have moved
636 # update the bookmarks for revs that have moved
637 marks[k] = nstate[v]
637 marks[k] = nstate[v]
638 bookmarks.deletedivergent(repo, [targetnode], k)
638 bookmarks.deletedivergent(repo, [targetnode], k)
639
639
640 marks.write()
640 marks.write()
641
641
642 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
642 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
643 external, activebookmark):
643 external, activebookmark):
644 'Store the current status to allow recovery'
644 'Store the current status to allow recovery'
645 f = repo.opener("rebasestate", "w")
645 f = repo.opener("rebasestate", "w")
646 f.write(repo[originalwd].hex() + '\n')
646 f.write(repo[originalwd].hex() + '\n')
647 f.write(repo[target].hex() + '\n')
647 f.write(repo[target].hex() + '\n')
648 f.write(repo[external].hex() + '\n')
648 f.write(repo[external].hex() + '\n')
649 f.write('%d\n' % int(collapse))
649 f.write('%d\n' % int(collapse))
650 f.write('%d\n' % int(keep))
650 f.write('%d\n' % int(keep))
651 f.write('%d\n' % int(keepbranches))
651 f.write('%d\n' % int(keepbranches))
652 f.write('%s\n' % (activebookmark or ''))
652 f.write('%s\n' % (activebookmark or ''))
653 for d, v in state.iteritems():
653 for d, v in state.iteritems():
654 oldrev = repo[d].hex()
654 oldrev = repo[d].hex()
655 if v > nullmerge:
655 if v > nullmerge:
656 newrev = repo[v].hex()
656 newrev = repo[v].hex()
657 else:
657 else:
658 newrev = v
658 newrev = v
659 f.write("%s:%s\n" % (oldrev, newrev))
659 f.write("%s:%s\n" % (oldrev, newrev))
660 f.close()
660 f.close()
661 repo.ui.debug('rebase status stored\n')
661 repo.ui.debug('rebase status stored\n')
662
662
663 def clearstatus(repo):
663 def clearstatus(repo):
664 'Remove the status files'
664 'Remove the status files'
665 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
665 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
666
666
667 def restorestatus(repo):
667 def restorestatus(repo):
668 'Restore a previously stored status'
668 'Restore a previously stored status'
669 try:
669 try:
670 keepbranches = None
670 keepbranches = None
671 target = None
671 target = None
672 collapse = False
672 collapse = False
673 external = nullrev
673 external = nullrev
674 activebookmark = None
674 activebookmark = None
675 state = {}
675 state = {}
676 f = repo.opener("rebasestate")
676 f = repo.opener("rebasestate")
677 for i, l in enumerate(f.read().splitlines()):
677 for i, l in enumerate(f.read().splitlines()):
678 if i == 0:
678 if i == 0:
679 originalwd = repo[l].rev()
679 originalwd = repo[l].rev()
680 elif i == 1:
680 elif i == 1:
681 target = repo[l].rev()
681 target = repo[l].rev()
682 elif i == 2:
682 elif i == 2:
683 external = repo[l].rev()
683 external = repo[l].rev()
684 elif i == 3:
684 elif i == 3:
685 collapse = bool(int(l))
685 collapse = bool(int(l))
686 elif i == 4:
686 elif i == 4:
687 keep = bool(int(l))
687 keep = bool(int(l))
688 elif i == 5:
688 elif i == 5:
689 keepbranches = bool(int(l))
689 keepbranches = bool(int(l))
690 elif i == 6 and not (len(l) == 81 and ':' in l):
690 elif i == 6 and not (len(l) == 81 and ':' in l):
691 # line 6 is a recent addition, so for backwards compatibility
691 # line 6 is a recent addition, so for backwards compatibility
692 # check that the line doesn't look like the oldrev:newrev lines
692 # check that the line doesn't look like the oldrev:newrev lines
693 activebookmark = l
693 activebookmark = l
694 else:
694 else:
695 oldrev, newrev = l.split(':')
695 oldrev, newrev = l.split(':')
696 if newrev in (str(nullmerge), str(revignored)):
696 if newrev in (str(nullmerge), str(revignored)):
697 state[repo[oldrev].rev()] = int(newrev)
697 state[repo[oldrev].rev()] = int(newrev)
698 else:
698 else:
699 state[repo[oldrev].rev()] = repo[newrev].rev()
699 state[repo[oldrev].rev()] = repo[newrev].rev()
700
700
701 if keepbranches is None:
701 if keepbranches is None:
702 raise util.Abort(_('.hg/rebasestate is incomplete'))
702 raise util.Abort(_('.hg/rebasestate is incomplete'))
703
703
704 skipped = set()
704 skipped = set()
705 # recompute the set of skipped revs
705 # recompute the set of skipped revs
706 if not collapse:
706 if not collapse:
707 seen = set([target])
707 seen = set([target])
708 for old, new in sorted(state.items()):
708 for old, new in sorted(state.items()):
709 if new != nullrev and new in seen:
709 if new != nullrev and new in seen:
710 skipped.add(old)
710 skipped.add(old)
711 seen.add(new)
711 seen.add(new)
712 repo.ui.debug('computed skipped revs: %s\n' %
712 repo.ui.debug('computed skipped revs: %s\n' %
713 (' '.join(str(r) for r in sorted(skipped)) or None))
713 (' '.join(str(r) for r in sorted(skipped)) or None))
714 repo.ui.debug('rebase status resumed\n')
714 repo.ui.debug('rebase status resumed\n')
715 return (originalwd, target, state, skipped,
715 return (originalwd, target, state, skipped,
716 collapse, keep, keepbranches, external, activebookmark)
716 collapse, keep, keepbranches, external, activebookmark)
717 except IOError, err:
717 except IOError, err:
718 if err.errno != errno.ENOENT:
718 if err.errno != errno.ENOENT:
719 raise
719 raise
720 raise util.Abort(_('no rebase in progress'))
720 raise util.Abort(_('no rebase in progress'))
721
721
722 def inrebase(repo, originalwd, state):
722 def inrebase(repo, originalwd, state):
723 '''check whether the working dir is in an interrupted rebase'''
723 '''check whether the working dir is in an interrupted rebase'''
724 parents = [p.rev() for p in repo.parents()]
724 parents = [p.rev() for p in repo.parents()]
725 if originalwd in parents:
725 if originalwd in parents:
726 return True
726 return True
727
727
728 for newrev in state.itervalues():
728 for newrev in state.itervalues():
729 if newrev in parents:
729 if newrev in parents:
730 return True
730 return True
731
731
732 return False
732 return False
733
733
734 def abort(repo, originalwd, target, state):
734 def abort(repo, originalwd, target, state):
735 'Restore the repository to its original state'
735 'Restore the repository to its original state'
736 dstates = [s for s in state.values() if s > nullrev]
736 dstates = [s for s in state.values() if s > nullrev]
737 immutable = [d for d in dstates if not repo[d].mutable()]
737 immutable = [d for d in dstates if not repo[d].mutable()]
738 cleanup = True
738 cleanup = True
739 if immutable:
739 if immutable:
740 repo.ui.warn(_("warning: can't clean up immutable changesets %s\n")
740 repo.ui.warn(_("warning: can't clean up immutable changesets %s\n")
741 % ', '.join(str(repo[r]) for r in immutable),
741 % ', '.join(str(repo[r]) for r in immutable),
742 hint=_('see hg help phases for details'))
742 hint=_('see hg help phases for details'))
743 cleanup = False
743 cleanup = False
744
744
745 descendants = set()
745 descendants = set()
746 if dstates:
746 if dstates:
747 descendants = set(repo.changelog.descendants(dstates))
747 descendants = set(repo.changelog.descendants(dstates))
748 if descendants - set(dstates):
748 if descendants - set(dstates):
749 repo.ui.warn(_("warning: new changesets detected on target branch, "
749 repo.ui.warn(_("warning: new changesets detected on target branch, "
750 "can't strip\n"))
750 "can't strip\n"))
751 cleanup = False
751 cleanup = False
752
752
753 if cleanup:
753 if cleanup:
754 # Update away from the rebase if necessary
754 # Update away from the rebase if necessary
755 if inrebase(repo, originalwd, state):
755 if inrebase(repo, originalwd, state):
756 merge.update(repo, repo[originalwd].rev(), False, True, False)
756 merge.update(repo, repo[originalwd].rev(), False, True, False)
757
757
758 # Strip from the first rebased revision
758 # Strip from the first rebased revision
759 rebased = filter(lambda x: x > -1 and x != target, state.values())
759 rebased = filter(lambda x: x > -1 and x != target, state.values())
760 if rebased:
760 if rebased:
761 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
761 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
762 # no backup of rebased cset versions needed
762 # no backup of rebased cset versions needed
763 repair.strip(repo.ui, repo, strippoints)
763 repair.strip(repo.ui, repo, strippoints)
764
764
765 clearstatus(repo)
765 clearstatus(repo)
766 repo.ui.warn(_('rebase aborted\n'))
766 repo.ui.warn(_('rebase aborted\n'))
767 return 0
767 return 0
768
768
769 def buildstate(repo, dest, rebaseset, collapse):
769 def buildstate(repo, dest, rebaseset, collapse):
770 '''Define which revisions are going to be rebased and where
770 '''Define which revisions are going to be rebased and where
771
771
772 repo: repo
772 repo: repo
773 dest: context
773 dest: context
774 rebaseset: set of rev
774 rebaseset: set of rev
775 '''
775 '''
776
776
777 # This check isn't strictly necessary, since mq detects commits over an
777 # This check isn't strictly necessary, since mq detects commits over an
778 # applied patch. But it prevents messing up the working directory when
778 # applied patch. But it prevents messing up the working directory when
779 # a partially completed rebase is blocked by mq.
779 # a partially completed rebase is blocked by mq.
780 if 'qtip' in repo.tags() and (dest.node() in
780 if 'qtip' in repo.tags() and (dest.node() in
781 [s.node for s in repo.mq.applied]):
781 [s.node for s in repo.mq.applied]):
782 raise util.Abort(_('cannot rebase onto an applied mq patch'))
782 raise util.Abort(_('cannot rebase onto an applied mq patch'))
783
783
784 roots = list(repo.set('roots(%ld)', rebaseset))
784 roots = list(repo.set('roots(%ld)', rebaseset))
785 if not roots:
785 if not roots:
786 raise util.Abort(_('no matching revisions'))
786 raise util.Abort(_('no matching revisions'))
787 roots.sort()
787 roots.sort()
788 state = {}
788 state = {}
789 detachset = set()
789 detachset = set()
790 for root in roots:
790 for root in roots:
791 commonbase = root.ancestor(dest)
791 commonbase = root.ancestor(dest)
792 if commonbase == root:
792 if commonbase == root:
793 raise util.Abort(_('source is ancestor of destination'))
793 raise util.Abort(_('source is ancestor of destination'))
794 if commonbase == dest:
794 if commonbase == dest:
795 samebranch = root.branch() == dest.branch()
795 samebranch = root.branch() == dest.branch()
796 if not collapse and samebranch and root in dest.children():
796 if not collapse and samebranch and root in dest.children():
797 repo.ui.debug('source is a child of destination\n')
797 repo.ui.debug('source is a child of destination\n')
798 return None
798 return None
799
799
800 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, root))
800 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, root))
801 state.update(dict.fromkeys(rebaseset, nullrev))
801 state.update(dict.fromkeys(rebaseset, nullrev))
802 # Rebase tries to turn <dest> into a parent of <root> while
802 # Rebase tries to turn <dest> into a parent of <root> while
803 # preserving the number of parents of rebased changesets:
803 # preserving the number of parents of rebased changesets:
804 #
804 #
805 # - A changeset with a single parent will always be rebased as a
805 # - A changeset with a single parent will always be rebased as a
806 # changeset with a single parent.
806 # changeset with a single parent.
807 #
807 #
808 # - A merge will be rebased as merge unless its parents are both
808 # - A merge will be rebased as merge unless its parents are both
809 # ancestors of <dest> or are themselves in the rebased set and
809 # ancestors of <dest> or are themselves in the rebased set and
810 # pruned while rebased.
810 # pruned while rebased.
811 #
811 #
812 # If one parent of <root> is an ancestor of <dest>, the rebased
812 # If one parent of <root> is an ancestor of <dest>, the rebased
813 # version of this parent will be <dest>. This is always true with
813 # version of this parent will be <dest>. This is always true with
814 # --base option.
814 # --base option.
815 #
815 #
816 # Otherwise, we need to *replace* the original parents with
816 # Otherwise, we need to *replace* the original parents with
817 # <dest>. This "detaches" the rebased set from its former location
817 # <dest>. This "detaches" the rebased set from its former location
818 # and rebases it onto <dest>. Changes introduced by ancestors of
818 # and rebases it onto <dest>. Changes introduced by ancestors of
819 # <root> not common with <dest> (the detachset, marked as
819 # <root> not common with <dest> (the detachset, marked as
820 # nullmerge) are "removed" from the rebased changesets.
820 # nullmerge) are "removed" from the rebased changesets.
821 #
821 #
822 # - If <root> has a single parent, set it to <dest>.
822 # - If <root> has a single parent, set it to <dest>.
823 #
823 #
824 # - If <root> is a merge, we cannot decide which parent to
824 # - If <root> is a merge, we cannot decide which parent to
825 # replace, the rebase operation is not clearly defined.
825 # replace, the rebase operation is not clearly defined.
826 #
826 #
827 # The table below sums up this behavior:
827 # The table below sums up this behavior:
828 #
828 #
829 # +------------------+----------------------+-------------------------+
829 # +------------------+----------------------+-------------------------+
830 # | | one parent | merge |
830 # | | one parent | merge |
831 # +------------------+----------------------+-------------------------+
831 # +------------------+----------------------+-------------------------+
832 # | parent in | new parent is <dest> | parents in ::<dest> are |
832 # | parent in | new parent is <dest> | parents in ::<dest> are |
833 # | ::<dest> | | remapped to <dest> |
833 # | ::<dest> | | remapped to <dest> |
834 # +------------------+----------------------+-------------------------+
834 # +------------------+----------------------+-------------------------+
835 # | unrelated source | new parent is <dest> | ambiguous, abort |
835 # | unrelated source | new parent is <dest> | ambiguous, abort |
836 # +------------------+----------------------+-------------------------+
836 # +------------------+----------------------+-------------------------+
837 #
837 #
838 # The actual abort is handled by `defineparents`
838 # The actual abort is handled by `defineparents`
839 if len(root.parents()) <= 1:
839 if len(root.parents()) <= 1:
840 # ancestors of <root> not ancestors of <dest>
840 # ancestors of <root> not ancestors of <dest>
841 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
841 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
842 [root.rev()]))
842 [root.rev()]))
843 for r in detachset:
843 for r in detachset:
844 if r not in state:
844 if r not in state:
845 state[r] = nullmerge
845 state[r] = nullmerge
846 if len(roots) > 1:
846 if len(roots) > 1:
847 # If we have multiple roots, we may have "hole" in the rebase set.
847 # If we have multiple roots, we may have "hole" in the rebase set.
848 # Rebase roots that descend from those "hole" should not be detached as
848 # Rebase roots that descend from those "hole" should not be detached as
849 # other root are. We use the special `revignored` to inform rebase that
849 # other root are. We use the special `revignored` to inform rebase that
850 # the revision should be ignored but that `defineparents` should search
850 # the revision should be ignored but that `defineparents` should search
851 # a rebase destination that make sense regarding rebased topology.
851 # a rebase destination that make sense regarding rebased topology.
852 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
852 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
853 for ignored in set(rebasedomain) - set(rebaseset):
853 for ignored in set(rebasedomain) - set(rebaseset):
854 state[ignored] = revignored
854 state[ignored] = revignored
855 return repo['.'].rev(), dest.rev(), state
855 return repo['.'].rev(), dest.rev(), state
856
856
857 def clearrebased(ui, repo, state, skipped, collapsedas=None):
857 def clearrebased(ui, repo, state, skipped, collapsedas=None):
858 """dispose of rebased revision at the end of the rebase
858 """dispose of rebased revision at the end of the rebase
859
859
860 If `collapsedas` is not None, the rebase was a collapse whose result if the
860 If `collapsedas` is not None, the rebase was a collapse whose result if the
861 `collapsedas` node."""
861 `collapsedas` node."""
862 if obsolete._enabled:
862 if obsolete._enabled:
863 markers = []
863 markers = []
864 for rev, newrev in sorted(state.items()):
864 for rev, newrev in sorted(state.items()):
865 if newrev >= 0:
865 if newrev >= 0:
866 if rev in skipped:
866 if rev in skipped:
867 succs = ()
867 succs = ()
868 elif collapsedas is not None:
868 elif collapsedas is not None:
869 succs = (repo[collapsedas],)
869 succs = (repo[collapsedas],)
870 else:
870 else:
871 succs = (repo[newrev],)
871 succs = (repo[newrev],)
872 markers.append((repo[rev], succs))
872 markers.append((repo[rev], succs))
873 if markers:
873 if markers:
874 obsolete.createmarkers(repo, markers)
874 obsolete.createmarkers(repo, markers)
875 else:
875 else:
876 rebased = [rev for rev in state if state[rev] > nullmerge]
876 rebased = [rev for rev in state if state[rev] > nullmerge]
877 if rebased:
877 if rebased:
878 stripped = []
878 stripped = []
879 for root in repo.set('roots(%ld)', rebased):
879 for root in repo.set('roots(%ld)', rebased):
880 if set(repo.changelog.descendants([root.rev()])) - set(state):
880 if set(repo.changelog.descendants([root.rev()])) - set(state):
881 ui.warn(_("warning: new changesets detected "
881 ui.warn(_("warning: new changesets detected "
882 "on source branch, not stripping\n"))
882 "on source branch, not stripping\n"))
883 else:
883 else:
884 stripped.append(root.node())
884 stripped.append(root.node())
885 if stripped:
885 if stripped:
886 # backup the old csets by default
886 # backup the old csets by default
887 repair.strip(ui, repo, stripped, "all")
887 repair.strip(ui, repo, stripped, "all")
888
888
889
889
890 def pullrebase(orig, ui, repo, *args, **opts):
890 def pullrebase(orig, ui, repo, *args, **opts):
891 'Call rebase after pull if the latter has been invoked with --rebase'
891 'Call rebase after pull if the latter has been invoked with --rebase'
892 if opts.get('rebase'):
892 if opts.get('rebase'):
893 if opts.get('update'):
893 if opts.get('update'):
894 del opts['update']
894 del opts['update']
895 ui.debug('--update and --rebase are not compatible, ignoring '
895 ui.debug('--update and --rebase are not compatible, ignoring '
896 'the update flag\n')
896 'the update flag\n')
897
897
898 movemarkfrom = repo['.'].node()
898 movemarkfrom = repo['.'].node()
899 revsprepull = len(repo)
899 revsprepull = len(repo)
900 origpostincoming = commands.postincoming
900 origpostincoming = commands.postincoming
901 def _dummy(*args, **kwargs):
901 def _dummy(*args, **kwargs):
902 pass
902 pass
903 commands.postincoming = _dummy
903 commands.postincoming = _dummy
904 try:
904 try:
905 orig(ui, repo, *args, **opts)
905 orig(ui, repo, *args, **opts)
906 finally:
906 finally:
907 commands.postincoming = origpostincoming
907 commands.postincoming = origpostincoming
908 revspostpull = len(repo)
908 revspostpull = len(repo)
909 if revspostpull > revsprepull:
909 if revspostpull > revsprepull:
910 # --rev option from pull conflict with rebase own --rev
910 # --rev option from pull conflict with rebase own --rev
911 # dropping it
911 # dropping it
912 if 'rev' in opts:
912 if 'rev' in opts:
913 del opts['rev']
913 del opts['rev']
914 rebase(ui, repo, **opts)
914 rebase(ui, repo, **opts)
915 branch = repo[None].branch()
915 branch = repo[None].branch()
916 dest = repo[branch].rev()
916 dest = repo[branch].rev()
917 if dest != repo['.'].rev():
917 if dest != repo['.'].rev():
918 # there was nothing to rebase we force an update
918 # there was nothing to rebase we force an update
919 hg.update(repo, dest)
919 hg.update(repo, dest)
920 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
920 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
921 ui.status(_("updating bookmark %s\n")
921 ui.status(_("updating bookmark %s\n")
922 % repo._bookmarkcurrent)
922 % repo._bookmarkcurrent)
923 else:
923 else:
924 if opts.get('tool'):
924 if opts.get('tool'):
925 raise util.Abort(_('--tool can only be used with --rebase'))
925 raise util.Abort(_('--tool can only be used with --rebase'))
926 orig(ui, repo, *args, **opts)
926 orig(ui, repo, *args, **opts)
927
927
928 def summaryhook(ui, repo):
928 def summaryhook(ui, repo):
929 if not os.path.exists(repo.join('rebasestate')):
929 if not os.path.exists(repo.join('rebasestate')):
930 return
930 return
931 try:
931 try:
932 state = restorestatus(repo)[2]
932 state = restorestatus(repo)[2]
933 except error.RepoLookupError:
933 except error.RepoLookupError:
934 # i18n: column positioning for "hg summary"
934 # i18n: column positioning for "hg summary"
935 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
935 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
936 ui.write(msg)
936 ui.write(msg)
937 return
937 return
938 numrebased = len([i for i in state.itervalues() if i != -1])
938 numrebased = len([i for i in state.itervalues() if i != -1])
939 # i18n: column positioning for "hg summary"
939 # i18n: column positioning for "hg summary"
940 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
940 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
941 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
941 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
942 ui.label(_('%d remaining'), 'rebase.remaining') %
942 ui.label(_('%d remaining'), 'rebase.remaining') %
943 (len(state) - numrebased)))
943 (len(state) - numrebased)))
944
944
945 def uisetup(ui):
945 def uisetup(ui):
946 'Replace pull with a decorator to provide --rebase option'
946 'Replace pull with a decorator to provide --rebase option'
947 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
947 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
948 entry[1].append(('', 'rebase', None,
948 entry[1].append(('', 'rebase', None,
949 _("rebase working directory to branch head")))
949 _("rebase working directory to branch head")))
950 entry[1].append(('t', 'tool', '',
950 entry[1].append(('t', 'tool', '',
951 _("specify merge tool for rebase")))
951 _("specify merge tool for rebase")))
952 cmdutil.summaryhooks.add('rebase', summaryhook)
952 cmdutil.summaryhooks.add('rebase', summaryhook)
953 cmdutil.unfinishedstates.append(
953 cmdutil.unfinishedstates.append(
954 ['rebasestate', False, False, _('rebase in progress'),
954 ['rebasestate', False, False, _('rebase in progress'),
955 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
955 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
@@ -1,2360 +1,2361 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.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 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, tempfile
10 import os, sys, errno, re, tempfile
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 import match as matchmod
12 import match as matchmod
13 import context, repair, graphmod, revset, phases, obsolete, pathutil
13 import context, repair, graphmod, revset, phases, obsolete, pathutil
14 import changelog
14 import changelog
15 import bookmarks
15 import bookmarks
16 import lock as lockmod
16 import lock as lockmod
17
17
18 def parsealiases(cmd):
18 def parsealiases(cmd):
19 return cmd.lstrip("^").split("|")
19 return cmd.lstrip("^").split("|")
20
20
21 def findpossible(cmd, table, strict=False):
21 def findpossible(cmd, table, strict=False):
22 """
22 """
23 Return cmd -> (aliases, command table entry)
23 Return cmd -> (aliases, command table entry)
24 for each matching command.
24 for each matching command.
25 Return debug commands (or their aliases) only if no normal command matches.
25 Return debug commands (or their aliases) only if no normal command matches.
26 """
26 """
27 choice = {}
27 choice = {}
28 debugchoice = {}
28 debugchoice = {}
29
29
30 if cmd in table:
30 if cmd in table:
31 # short-circuit exact matches, "log" alias beats "^log|history"
31 # short-circuit exact matches, "log" alias beats "^log|history"
32 keys = [cmd]
32 keys = [cmd]
33 else:
33 else:
34 keys = table.keys()
34 keys = table.keys()
35
35
36 for e in keys:
36 for e in keys:
37 aliases = parsealiases(e)
37 aliases = parsealiases(e)
38 found = None
38 found = None
39 if cmd in aliases:
39 if cmd in aliases:
40 found = cmd
40 found = cmd
41 elif not strict:
41 elif not strict:
42 for a in aliases:
42 for a in aliases:
43 if a.startswith(cmd):
43 if a.startswith(cmd):
44 found = a
44 found = a
45 break
45 break
46 if found is not None:
46 if found is not None:
47 if aliases[0].startswith("debug") or found.startswith("debug"):
47 if aliases[0].startswith("debug") or found.startswith("debug"):
48 debugchoice[found] = (aliases, table[e])
48 debugchoice[found] = (aliases, table[e])
49 else:
49 else:
50 choice[found] = (aliases, table[e])
50 choice[found] = (aliases, table[e])
51
51
52 if not choice and debugchoice:
52 if not choice and debugchoice:
53 choice = debugchoice
53 choice = debugchoice
54
54
55 return choice
55 return choice
56
56
57 def findcmd(cmd, table, strict=True):
57 def findcmd(cmd, table, strict=True):
58 """Return (aliases, command table entry) for command string."""
58 """Return (aliases, command table entry) for command string."""
59 choice = findpossible(cmd, table, strict)
59 choice = findpossible(cmd, table, strict)
60
60
61 if cmd in choice:
61 if cmd in choice:
62 return choice[cmd]
62 return choice[cmd]
63
63
64 if len(choice) > 1:
64 if len(choice) > 1:
65 clist = choice.keys()
65 clist = choice.keys()
66 clist.sort()
66 clist.sort()
67 raise error.AmbiguousCommand(cmd, clist)
67 raise error.AmbiguousCommand(cmd, clist)
68
68
69 if choice:
69 if choice:
70 return choice.values()[0]
70 return choice.values()[0]
71
71
72 raise error.UnknownCommand(cmd)
72 raise error.UnknownCommand(cmd)
73
73
74 def findrepo(p):
74 def findrepo(p):
75 while not os.path.isdir(os.path.join(p, ".hg")):
75 while not os.path.isdir(os.path.join(p, ".hg")):
76 oldp, p = p, os.path.dirname(p)
76 oldp, p = p, os.path.dirname(p)
77 if p == oldp:
77 if p == oldp:
78 return None
78 return None
79
79
80 return p
80 return p
81
81
82 def bailifchanged(repo):
82 def bailifchanged(repo):
83 if repo.dirstate.p2() != nullid:
83 if repo.dirstate.p2() != nullid:
84 raise util.Abort(_('outstanding uncommitted merge'))
84 raise util.Abort(_('outstanding uncommitted merge'))
85 modified, added, removed, deleted = repo.status()[:4]
85 modified, added, removed, deleted = repo.status()[:4]
86 if modified or added or removed or deleted:
86 if modified or added or removed or deleted:
87 raise util.Abort(_('uncommitted changes'))
87 raise util.Abort(_('uncommitted changes'))
88 ctx = repo[None]
88 ctx = repo[None]
89 for s in sorted(ctx.substate):
89 for s in sorted(ctx.substate):
90 if ctx.sub(s).dirty():
90 if ctx.sub(s).dirty():
91 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
91 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
92
92
93 def logmessage(ui, opts):
93 def logmessage(ui, opts):
94 """ get the log message according to -m and -l option """
94 """ get the log message according to -m and -l option """
95 message = opts.get('message')
95 message = opts.get('message')
96 logfile = opts.get('logfile')
96 logfile = opts.get('logfile')
97
97
98 if message and logfile:
98 if message and logfile:
99 raise util.Abort(_('options --message and --logfile are mutually '
99 raise util.Abort(_('options --message and --logfile are mutually '
100 'exclusive'))
100 'exclusive'))
101 if not message and logfile:
101 if not message and logfile:
102 try:
102 try:
103 if logfile == '-':
103 if logfile == '-':
104 message = ui.fin.read()
104 message = ui.fin.read()
105 else:
105 else:
106 message = '\n'.join(util.readfile(logfile).splitlines())
106 message = '\n'.join(util.readfile(logfile).splitlines())
107 except IOError, inst:
107 except IOError, inst:
108 raise util.Abort(_("can't read commit message '%s': %s") %
108 raise util.Abort(_("can't read commit message '%s': %s") %
109 (logfile, inst.strerror))
109 (logfile, inst.strerror))
110 return message
110 return message
111
111
112 def loglimit(opts):
112 def loglimit(opts):
113 """get the log limit according to option -l/--limit"""
113 """get the log limit according to option -l/--limit"""
114 limit = opts.get('limit')
114 limit = opts.get('limit')
115 if limit:
115 if limit:
116 try:
116 try:
117 limit = int(limit)
117 limit = int(limit)
118 except ValueError:
118 except ValueError:
119 raise util.Abort(_('limit must be a positive integer'))
119 raise util.Abort(_('limit must be a positive integer'))
120 if limit <= 0:
120 if limit <= 0:
121 raise util.Abort(_('limit must be positive'))
121 raise util.Abort(_('limit must be positive'))
122 else:
122 else:
123 limit = None
123 limit = None
124 return limit
124 return limit
125
125
126 def makefilename(repo, pat, node, desc=None,
126 def makefilename(repo, pat, node, desc=None,
127 total=None, seqno=None, revwidth=None, pathname=None):
127 total=None, seqno=None, revwidth=None, pathname=None):
128 node_expander = {
128 node_expander = {
129 'H': lambda: hex(node),
129 'H': lambda: hex(node),
130 'R': lambda: str(repo.changelog.rev(node)),
130 'R': lambda: str(repo.changelog.rev(node)),
131 'h': lambda: short(node),
131 'h': lambda: short(node),
132 'm': lambda: re.sub('[^\w]', '_', str(desc))
132 'm': lambda: re.sub('[^\w]', '_', str(desc))
133 }
133 }
134 expander = {
134 expander = {
135 '%': lambda: '%',
135 '%': lambda: '%',
136 'b': lambda: os.path.basename(repo.root),
136 'b': lambda: os.path.basename(repo.root),
137 }
137 }
138
138
139 try:
139 try:
140 if node:
140 if node:
141 expander.update(node_expander)
141 expander.update(node_expander)
142 if node:
142 if node:
143 expander['r'] = (lambda:
143 expander['r'] = (lambda:
144 str(repo.changelog.rev(node)).zfill(revwidth or 0))
144 str(repo.changelog.rev(node)).zfill(revwidth or 0))
145 if total is not None:
145 if total is not None:
146 expander['N'] = lambda: str(total)
146 expander['N'] = lambda: str(total)
147 if seqno is not None:
147 if seqno is not None:
148 expander['n'] = lambda: str(seqno)
148 expander['n'] = lambda: str(seqno)
149 if total is not None and seqno is not None:
149 if total is not None and seqno is not None:
150 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
150 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
151 if pathname is not None:
151 if pathname is not None:
152 expander['s'] = lambda: os.path.basename(pathname)
152 expander['s'] = lambda: os.path.basename(pathname)
153 expander['d'] = lambda: os.path.dirname(pathname) or '.'
153 expander['d'] = lambda: os.path.dirname(pathname) or '.'
154 expander['p'] = lambda: pathname
154 expander['p'] = lambda: pathname
155
155
156 newname = []
156 newname = []
157 patlen = len(pat)
157 patlen = len(pat)
158 i = 0
158 i = 0
159 while i < patlen:
159 while i < patlen:
160 c = pat[i]
160 c = pat[i]
161 if c == '%':
161 if c == '%':
162 i += 1
162 i += 1
163 c = pat[i]
163 c = pat[i]
164 c = expander[c]()
164 c = expander[c]()
165 newname.append(c)
165 newname.append(c)
166 i += 1
166 i += 1
167 return ''.join(newname)
167 return ''.join(newname)
168 except KeyError, inst:
168 except KeyError, inst:
169 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
169 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
170 inst.args[0])
170 inst.args[0])
171
171
172 def makefileobj(repo, pat, node=None, desc=None, total=None,
172 def makefileobj(repo, pat, node=None, desc=None, total=None,
173 seqno=None, revwidth=None, mode='wb', modemap=None,
173 seqno=None, revwidth=None, mode='wb', modemap=None,
174 pathname=None):
174 pathname=None):
175
175
176 writable = mode not in ('r', 'rb')
176 writable = mode not in ('r', 'rb')
177
177
178 if not pat or pat == '-':
178 if not pat or pat == '-':
179 fp = writable and repo.ui.fout or repo.ui.fin
179 fp = writable and repo.ui.fout or repo.ui.fin
180 if util.safehasattr(fp, 'fileno'):
180 if util.safehasattr(fp, 'fileno'):
181 return os.fdopen(os.dup(fp.fileno()), mode)
181 return os.fdopen(os.dup(fp.fileno()), mode)
182 else:
182 else:
183 # if this fp can't be duped properly, return
183 # if this fp can't be duped properly, return
184 # a dummy object that can be closed
184 # a dummy object that can be closed
185 class wrappedfileobj(object):
185 class wrappedfileobj(object):
186 noop = lambda x: None
186 noop = lambda x: None
187 def __init__(self, f):
187 def __init__(self, f):
188 self.f = f
188 self.f = f
189 def __getattr__(self, attr):
189 def __getattr__(self, attr):
190 if attr == 'close':
190 if attr == 'close':
191 return self.noop
191 return self.noop
192 else:
192 else:
193 return getattr(self.f, attr)
193 return getattr(self.f, attr)
194
194
195 return wrappedfileobj(fp)
195 return wrappedfileobj(fp)
196 if util.safehasattr(pat, 'write') and writable:
196 if util.safehasattr(pat, 'write') and writable:
197 return pat
197 return pat
198 if util.safehasattr(pat, 'read') and 'r' in mode:
198 if util.safehasattr(pat, 'read') and 'r' in mode:
199 return pat
199 return pat
200 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
200 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
201 if modemap is not None:
201 if modemap is not None:
202 mode = modemap.get(fn, mode)
202 mode = modemap.get(fn, mode)
203 if mode == 'wb':
203 if mode == 'wb':
204 modemap[fn] = 'ab'
204 modemap[fn] = 'ab'
205 return open(fn, mode)
205 return open(fn, mode)
206
206
207 def openrevlog(repo, cmd, file_, opts):
207 def openrevlog(repo, cmd, file_, opts):
208 """opens the changelog, manifest, a filelog or a given revlog"""
208 """opens the changelog, manifest, a filelog or a given revlog"""
209 cl = opts['changelog']
209 cl = opts['changelog']
210 mf = opts['manifest']
210 mf = opts['manifest']
211 msg = None
211 msg = None
212 if cl and mf:
212 if cl and mf:
213 msg = _('cannot specify --changelog and --manifest at the same time')
213 msg = _('cannot specify --changelog and --manifest at the same time')
214 elif cl or mf:
214 elif cl or mf:
215 if file_:
215 if file_:
216 msg = _('cannot specify filename with --changelog or --manifest')
216 msg = _('cannot specify filename with --changelog or --manifest')
217 elif not repo:
217 elif not repo:
218 msg = _('cannot specify --changelog or --manifest '
218 msg = _('cannot specify --changelog or --manifest '
219 'without a repository')
219 'without a repository')
220 if msg:
220 if msg:
221 raise util.Abort(msg)
221 raise util.Abort(msg)
222
222
223 r = None
223 r = None
224 if repo:
224 if repo:
225 if cl:
225 if cl:
226 r = repo.changelog
226 r = repo.changelog
227 elif mf:
227 elif mf:
228 r = repo.manifest
228 r = repo.manifest
229 elif file_:
229 elif file_:
230 filelog = repo.file(file_)
230 filelog = repo.file(file_)
231 if len(filelog):
231 if len(filelog):
232 r = filelog
232 r = filelog
233 if not r:
233 if not r:
234 if not file_:
234 if not file_:
235 raise error.CommandError(cmd, _('invalid arguments'))
235 raise error.CommandError(cmd, _('invalid arguments'))
236 if not os.path.isfile(file_):
236 if not os.path.isfile(file_):
237 raise util.Abort(_("revlog '%s' not found") % file_)
237 raise util.Abort(_("revlog '%s' not found") % file_)
238 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
238 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
239 file_[:-2] + ".i")
239 file_[:-2] + ".i")
240 return r
240 return r
241
241
242 def copy(ui, repo, pats, opts, rename=False):
242 def copy(ui, repo, pats, opts, rename=False):
243 # called with the repo lock held
243 # called with the repo lock held
244 #
244 #
245 # hgsep => pathname that uses "/" to separate directories
245 # hgsep => pathname that uses "/" to separate directories
246 # ossep => pathname that uses os.sep to separate directories
246 # ossep => pathname that uses os.sep to separate directories
247 cwd = repo.getcwd()
247 cwd = repo.getcwd()
248 targets = {}
248 targets = {}
249 after = opts.get("after")
249 after = opts.get("after")
250 dryrun = opts.get("dry_run")
250 dryrun = opts.get("dry_run")
251 wctx = repo[None]
251 wctx = repo[None]
252
252
253 def walkpat(pat):
253 def walkpat(pat):
254 srcs = []
254 srcs = []
255 badstates = after and '?' or '?r'
255 badstates = after and '?' or '?r'
256 m = scmutil.match(repo[None], [pat], opts, globbed=True)
256 m = scmutil.match(repo[None], [pat], opts, globbed=True)
257 for abs in repo.walk(m):
257 for abs in repo.walk(m):
258 state = repo.dirstate[abs]
258 state = repo.dirstate[abs]
259 rel = m.rel(abs)
259 rel = m.rel(abs)
260 exact = m.exact(abs)
260 exact = m.exact(abs)
261 if state in badstates:
261 if state in badstates:
262 if exact and state == '?':
262 if exact and state == '?':
263 ui.warn(_('%s: not copying - file is not managed\n') % rel)
263 ui.warn(_('%s: not copying - file is not managed\n') % rel)
264 if exact and state == 'r':
264 if exact and state == 'r':
265 ui.warn(_('%s: not copying - file has been marked for'
265 ui.warn(_('%s: not copying - file has been marked for'
266 ' remove\n') % rel)
266 ' remove\n') % rel)
267 continue
267 continue
268 # abs: hgsep
268 # abs: hgsep
269 # rel: ossep
269 # rel: ossep
270 srcs.append((abs, rel, exact))
270 srcs.append((abs, rel, exact))
271 return srcs
271 return srcs
272
272
273 # abssrc: hgsep
273 # abssrc: hgsep
274 # relsrc: ossep
274 # relsrc: ossep
275 # otarget: ossep
275 # otarget: ossep
276 def copyfile(abssrc, relsrc, otarget, exact):
276 def copyfile(abssrc, relsrc, otarget, exact):
277 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
277 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
278 if '/' in abstarget:
278 if '/' in abstarget:
279 # We cannot normalize abstarget itself, this would prevent
279 # We cannot normalize abstarget itself, this would prevent
280 # case only renames, like a => A.
280 # case only renames, like a => A.
281 abspath, absname = abstarget.rsplit('/', 1)
281 abspath, absname = abstarget.rsplit('/', 1)
282 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
282 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
283 reltarget = repo.pathto(abstarget, cwd)
283 reltarget = repo.pathto(abstarget, cwd)
284 target = repo.wjoin(abstarget)
284 target = repo.wjoin(abstarget)
285 src = repo.wjoin(abssrc)
285 src = repo.wjoin(abssrc)
286 state = repo.dirstate[abstarget]
286 state = repo.dirstate[abstarget]
287
287
288 scmutil.checkportable(ui, abstarget)
288 scmutil.checkportable(ui, abstarget)
289
289
290 # check for collisions
290 # check for collisions
291 prevsrc = targets.get(abstarget)
291 prevsrc = targets.get(abstarget)
292 if prevsrc is not None:
292 if prevsrc is not None:
293 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
293 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
294 (reltarget, repo.pathto(abssrc, cwd),
294 (reltarget, repo.pathto(abssrc, cwd),
295 repo.pathto(prevsrc, cwd)))
295 repo.pathto(prevsrc, cwd)))
296 return
296 return
297
297
298 # check for overwrites
298 # check for overwrites
299 exists = os.path.lexists(target)
299 exists = os.path.lexists(target)
300 samefile = False
300 samefile = False
301 if exists and abssrc != abstarget:
301 if exists and abssrc != abstarget:
302 if (repo.dirstate.normalize(abssrc) ==
302 if (repo.dirstate.normalize(abssrc) ==
303 repo.dirstate.normalize(abstarget)):
303 repo.dirstate.normalize(abstarget)):
304 if not rename:
304 if not rename:
305 ui.warn(_("%s: can't copy - same file\n") % reltarget)
305 ui.warn(_("%s: can't copy - same file\n") % reltarget)
306 return
306 return
307 exists = False
307 exists = False
308 samefile = True
308 samefile = True
309
309
310 if not after and exists or after and state in 'mn':
310 if not after and exists or after and state in 'mn':
311 if not opts['force']:
311 if not opts['force']:
312 ui.warn(_('%s: not overwriting - file exists\n') %
312 ui.warn(_('%s: not overwriting - file exists\n') %
313 reltarget)
313 reltarget)
314 return
314 return
315
315
316 if after:
316 if after:
317 if not exists:
317 if not exists:
318 if rename:
318 if rename:
319 ui.warn(_('%s: not recording move - %s does not exist\n') %
319 ui.warn(_('%s: not recording move - %s does not exist\n') %
320 (relsrc, reltarget))
320 (relsrc, reltarget))
321 else:
321 else:
322 ui.warn(_('%s: not recording copy - %s does not exist\n') %
322 ui.warn(_('%s: not recording copy - %s does not exist\n') %
323 (relsrc, reltarget))
323 (relsrc, reltarget))
324 return
324 return
325 elif not dryrun:
325 elif not dryrun:
326 try:
326 try:
327 if exists:
327 if exists:
328 os.unlink(target)
328 os.unlink(target)
329 targetdir = os.path.dirname(target) or '.'
329 targetdir = os.path.dirname(target) or '.'
330 if not os.path.isdir(targetdir):
330 if not os.path.isdir(targetdir):
331 os.makedirs(targetdir)
331 os.makedirs(targetdir)
332 if samefile:
332 if samefile:
333 tmp = target + "~hgrename"
333 tmp = target + "~hgrename"
334 os.rename(src, tmp)
334 os.rename(src, tmp)
335 os.rename(tmp, target)
335 os.rename(tmp, target)
336 else:
336 else:
337 util.copyfile(src, target)
337 util.copyfile(src, target)
338 srcexists = True
338 srcexists = True
339 except IOError, inst:
339 except IOError, inst:
340 if inst.errno == errno.ENOENT:
340 if inst.errno == errno.ENOENT:
341 ui.warn(_('%s: deleted in working copy\n') % relsrc)
341 ui.warn(_('%s: deleted in working copy\n') % relsrc)
342 srcexists = False
342 srcexists = False
343 else:
343 else:
344 ui.warn(_('%s: cannot copy - %s\n') %
344 ui.warn(_('%s: cannot copy - %s\n') %
345 (relsrc, inst.strerror))
345 (relsrc, inst.strerror))
346 return True # report a failure
346 return True # report a failure
347
347
348 if ui.verbose or not exact:
348 if ui.verbose or not exact:
349 if rename:
349 if rename:
350 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
350 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
351 else:
351 else:
352 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
352 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
353
353
354 targets[abstarget] = abssrc
354 targets[abstarget] = abssrc
355
355
356 # fix up dirstate
356 # fix up dirstate
357 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
357 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
358 dryrun=dryrun, cwd=cwd)
358 dryrun=dryrun, cwd=cwd)
359 if rename and not dryrun:
359 if rename and not dryrun:
360 if not after and srcexists and not samefile:
360 if not after and srcexists and not samefile:
361 util.unlinkpath(repo.wjoin(abssrc))
361 util.unlinkpath(repo.wjoin(abssrc))
362 wctx.forget([abssrc])
362 wctx.forget([abssrc])
363
363
364 # pat: ossep
364 # pat: ossep
365 # dest ossep
365 # dest ossep
366 # srcs: list of (hgsep, hgsep, ossep, bool)
366 # srcs: list of (hgsep, hgsep, ossep, bool)
367 # return: function that takes hgsep and returns ossep
367 # return: function that takes hgsep and returns ossep
368 def targetpathfn(pat, dest, srcs):
368 def targetpathfn(pat, dest, srcs):
369 if os.path.isdir(pat):
369 if os.path.isdir(pat):
370 abspfx = pathutil.canonpath(repo.root, cwd, pat)
370 abspfx = pathutil.canonpath(repo.root, cwd, pat)
371 abspfx = util.localpath(abspfx)
371 abspfx = util.localpath(abspfx)
372 if destdirexists:
372 if destdirexists:
373 striplen = len(os.path.split(abspfx)[0])
373 striplen = len(os.path.split(abspfx)[0])
374 else:
374 else:
375 striplen = len(abspfx)
375 striplen = len(abspfx)
376 if striplen:
376 if striplen:
377 striplen += len(os.sep)
377 striplen += len(os.sep)
378 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
378 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
379 elif destdirexists:
379 elif destdirexists:
380 res = lambda p: os.path.join(dest,
380 res = lambda p: os.path.join(dest,
381 os.path.basename(util.localpath(p)))
381 os.path.basename(util.localpath(p)))
382 else:
382 else:
383 res = lambda p: dest
383 res = lambda p: dest
384 return res
384 return res
385
385
386 # pat: ossep
386 # pat: ossep
387 # dest ossep
387 # dest ossep
388 # srcs: list of (hgsep, hgsep, ossep, bool)
388 # srcs: list of (hgsep, hgsep, ossep, bool)
389 # return: function that takes hgsep and returns ossep
389 # return: function that takes hgsep and returns ossep
390 def targetpathafterfn(pat, dest, srcs):
390 def targetpathafterfn(pat, dest, srcs):
391 if matchmod.patkind(pat):
391 if matchmod.patkind(pat):
392 # a mercurial pattern
392 # a mercurial pattern
393 res = lambda p: os.path.join(dest,
393 res = lambda p: os.path.join(dest,
394 os.path.basename(util.localpath(p)))
394 os.path.basename(util.localpath(p)))
395 else:
395 else:
396 abspfx = pathutil.canonpath(repo.root, cwd, pat)
396 abspfx = pathutil.canonpath(repo.root, cwd, pat)
397 if len(abspfx) < len(srcs[0][0]):
397 if len(abspfx) < len(srcs[0][0]):
398 # A directory. Either the target path contains the last
398 # A directory. Either the target path contains the last
399 # component of the source path or it does not.
399 # component of the source path or it does not.
400 def evalpath(striplen):
400 def evalpath(striplen):
401 score = 0
401 score = 0
402 for s in srcs:
402 for s in srcs:
403 t = os.path.join(dest, util.localpath(s[0])[striplen:])
403 t = os.path.join(dest, util.localpath(s[0])[striplen:])
404 if os.path.lexists(t):
404 if os.path.lexists(t):
405 score += 1
405 score += 1
406 return score
406 return score
407
407
408 abspfx = util.localpath(abspfx)
408 abspfx = util.localpath(abspfx)
409 striplen = len(abspfx)
409 striplen = len(abspfx)
410 if striplen:
410 if striplen:
411 striplen += len(os.sep)
411 striplen += len(os.sep)
412 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
412 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
413 score = evalpath(striplen)
413 score = evalpath(striplen)
414 striplen1 = len(os.path.split(abspfx)[0])
414 striplen1 = len(os.path.split(abspfx)[0])
415 if striplen1:
415 if striplen1:
416 striplen1 += len(os.sep)
416 striplen1 += len(os.sep)
417 if evalpath(striplen1) > score:
417 if evalpath(striplen1) > score:
418 striplen = striplen1
418 striplen = striplen1
419 res = lambda p: os.path.join(dest,
419 res = lambda p: os.path.join(dest,
420 util.localpath(p)[striplen:])
420 util.localpath(p)[striplen:])
421 else:
421 else:
422 # a file
422 # a file
423 if destdirexists:
423 if destdirexists:
424 res = lambda p: os.path.join(dest,
424 res = lambda p: os.path.join(dest,
425 os.path.basename(util.localpath(p)))
425 os.path.basename(util.localpath(p)))
426 else:
426 else:
427 res = lambda p: dest
427 res = lambda p: dest
428 return res
428 return res
429
429
430
430
431 pats = scmutil.expandpats(pats)
431 pats = scmutil.expandpats(pats)
432 if not pats:
432 if not pats:
433 raise util.Abort(_('no source or destination specified'))
433 raise util.Abort(_('no source or destination specified'))
434 if len(pats) == 1:
434 if len(pats) == 1:
435 raise util.Abort(_('no destination specified'))
435 raise util.Abort(_('no destination specified'))
436 dest = pats.pop()
436 dest = pats.pop()
437 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
437 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
438 if not destdirexists:
438 if not destdirexists:
439 if len(pats) > 1 or matchmod.patkind(pats[0]):
439 if len(pats) > 1 or matchmod.patkind(pats[0]):
440 raise util.Abort(_('with multiple sources, destination must be an '
440 raise util.Abort(_('with multiple sources, destination must be an '
441 'existing directory'))
441 'existing directory'))
442 if util.endswithsep(dest):
442 if util.endswithsep(dest):
443 raise util.Abort(_('destination %s is not a directory') % dest)
443 raise util.Abort(_('destination %s is not a directory') % dest)
444
444
445 tfn = targetpathfn
445 tfn = targetpathfn
446 if after:
446 if after:
447 tfn = targetpathafterfn
447 tfn = targetpathafterfn
448 copylist = []
448 copylist = []
449 for pat in pats:
449 for pat in pats:
450 srcs = walkpat(pat)
450 srcs = walkpat(pat)
451 if not srcs:
451 if not srcs:
452 continue
452 continue
453 copylist.append((tfn(pat, dest, srcs), srcs))
453 copylist.append((tfn(pat, dest, srcs), srcs))
454 if not copylist:
454 if not copylist:
455 raise util.Abort(_('no files to copy'))
455 raise util.Abort(_('no files to copy'))
456
456
457 errors = 0
457 errors = 0
458 for targetpath, srcs in copylist:
458 for targetpath, srcs in copylist:
459 for abssrc, relsrc, exact in srcs:
459 for abssrc, relsrc, exact in srcs:
460 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
460 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
461 errors += 1
461 errors += 1
462
462
463 if errors:
463 if errors:
464 ui.warn(_('(consider using --after)\n'))
464 ui.warn(_('(consider using --after)\n'))
465
465
466 return errors != 0
466 return errors != 0
467
467
468 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
468 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
469 runargs=None, appendpid=False):
469 runargs=None, appendpid=False):
470 '''Run a command as a service.'''
470 '''Run a command as a service.'''
471
471
472 def writepid(pid):
472 def writepid(pid):
473 if opts['pid_file']:
473 if opts['pid_file']:
474 mode = appendpid and 'a' or 'w'
474 mode = appendpid and 'a' or 'w'
475 fp = open(opts['pid_file'], mode)
475 fp = open(opts['pid_file'], mode)
476 fp.write(str(pid) + '\n')
476 fp.write(str(pid) + '\n')
477 fp.close()
477 fp.close()
478
478
479 if opts['daemon'] and not opts['daemon_pipefds']:
479 if opts['daemon'] and not opts['daemon_pipefds']:
480 # Signal child process startup with file removal
480 # Signal child process startup with file removal
481 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
481 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
482 os.close(lockfd)
482 os.close(lockfd)
483 try:
483 try:
484 if not runargs:
484 if not runargs:
485 runargs = util.hgcmd() + sys.argv[1:]
485 runargs = util.hgcmd() + sys.argv[1:]
486 runargs.append('--daemon-pipefds=%s' % lockpath)
486 runargs.append('--daemon-pipefds=%s' % lockpath)
487 # Don't pass --cwd to the child process, because we've already
487 # Don't pass --cwd to the child process, because we've already
488 # changed directory.
488 # changed directory.
489 for i in xrange(1, len(runargs)):
489 for i in xrange(1, len(runargs)):
490 if runargs[i].startswith('--cwd='):
490 if runargs[i].startswith('--cwd='):
491 del runargs[i]
491 del runargs[i]
492 break
492 break
493 elif runargs[i].startswith('--cwd'):
493 elif runargs[i].startswith('--cwd'):
494 del runargs[i:i + 2]
494 del runargs[i:i + 2]
495 break
495 break
496 def condfn():
496 def condfn():
497 return not os.path.exists(lockpath)
497 return not os.path.exists(lockpath)
498 pid = util.rundetached(runargs, condfn)
498 pid = util.rundetached(runargs, condfn)
499 if pid < 0:
499 if pid < 0:
500 raise util.Abort(_('child process failed to start'))
500 raise util.Abort(_('child process failed to start'))
501 writepid(pid)
501 writepid(pid)
502 finally:
502 finally:
503 try:
503 try:
504 os.unlink(lockpath)
504 os.unlink(lockpath)
505 except OSError, e:
505 except OSError, e:
506 if e.errno != errno.ENOENT:
506 if e.errno != errno.ENOENT:
507 raise
507 raise
508 if parentfn:
508 if parentfn:
509 return parentfn(pid)
509 return parentfn(pid)
510 else:
510 else:
511 return
511 return
512
512
513 if initfn:
513 if initfn:
514 initfn()
514 initfn()
515
515
516 if not opts['daemon']:
516 if not opts['daemon']:
517 writepid(os.getpid())
517 writepid(os.getpid())
518
518
519 if opts['daemon_pipefds']:
519 if opts['daemon_pipefds']:
520 lockpath = opts['daemon_pipefds']
520 lockpath = opts['daemon_pipefds']
521 try:
521 try:
522 os.setsid()
522 os.setsid()
523 except AttributeError:
523 except AttributeError:
524 pass
524 pass
525 os.unlink(lockpath)
525 os.unlink(lockpath)
526 util.hidewindow()
526 util.hidewindow()
527 sys.stdout.flush()
527 sys.stdout.flush()
528 sys.stderr.flush()
528 sys.stderr.flush()
529
529
530 nullfd = os.open(os.devnull, os.O_RDWR)
530 nullfd = os.open(os.devnull, os.O_RDWR)
531 logfilefd = nullfd
531 logfilefd = nullfd
532 if logfile:
532 if logfile:
533 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
533 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
534 os.dup2(nullfd, 0)
534 os.dup2(nullfd, 0)
535 os.dup2(logfilefd, 1)
535 os.dup2(logfilefd, 1)
536 os.dup2(logfilefd, 2)
536 os.dup2(logfilefd, 2)
537 if nullfd not in (0, 1, 2):
537 if nullfd not in (0, 1, 2):
538 os.close(nullfd)
538 os.close(nullfd)
539 if logfile and logfilefd not in (0, 1, 2):
539 if logfile and logfilefd not in (0, 1, 2):
540 os.close(logfilefd)
540 os.close(logfilefd)
541
541
542 if runfn:
542 if runfn:
543 return runfn()
543 return runfn()
544
544
545 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
545 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
546 """Utility function used by commands.import to import a single patch
546 """Utility function used by commands.import to import a single patch
547
547
548 This function is explicitly defined here to help the evolve extension to
548 This function is explicitly defined here to help the evolve extension to
549 wrap this part of the import logic.
549 wrap this part of the import logic.
550
550
551 The API is currently a bit ugly because it a simple code translation from
551 The API is currently a bit ugly because it a simple code translation from
552 the import command. Feel free to make it better.
552 the import command. Feel free to make it better.
553
553
554 :hunk: a patch (as a binary string)
554 :hunk: a patch (as a binary string)
555 :parents: nodes that will be parent of the created commit
555 :parents: nodes that will be parent of the created commit
556 :opts: the full dict of option passed to the import command
556 :opts: the full dict of option passed to the import command
557 :msgs: list to save commit message to.
557 :msgs: list to save commit message to.
558 (used in case we need to save it when failing)
558 (used in case we need to save it when failing)
559 :updatefunc: a function that update a repo to a given node
559 :updatefunc: a function that update a repo to a given node
560 updatefunc(<repo>, <node>)
560 updatefunc(<repo>, <node>)
561 """
561 """
562 tmpname, message, user, date, branch, nodeid, p1, p2 = \
562 tmpname, message, user, date, branch, nodeid, p1, p2 = \
563 patch.extract(ui, hunk)
563 patch.extract(ui, hunk)
564
564
565 editor = commiteditor
565 editor = commiteditor
566 if opts.get('edit'):
566 if opts.get('edit'):
567 editor = commitforceeditor
567 editor = commitforceeditor
568 update = not opts.get('bypass')
568 update = not opts.get('bypass')
569 strip = opts["strip"]
569 strip = opts["strip"]
570 sim = float(opts.get('similarity') or 0)
570 sim = float(opts.get('similarity') or 0)
571 if not tmpname:
571 if not tmpname:
572 return (None, None)
572 return (None, None)
573 msg = _('applied to working directory')
573 msg = _('applied to working directory')
574
574
575 try:
575 try:
576 cmdline_message = logmessage(ui, opts)
576 cmdline_message = logmessage(ui, opts)
577 if cmdline_message:
577 if cmdline_message:
578 # pickup the cmdline msg
578 # pickup the cmdline msg
579 message = cmdline_message
579 message = cmdline_message
580 elif message:
580 elif message:
581 # pickup the patch msg
581 # pickup the patch msg
582 message = message.strip()
582 message = message.strip()
583 else:
583 else:
584 # launch the editor
584 # launch the editor
585 message = None
585 message = None
586 ui.debug('message:\n%s\n' % message)
586 ui.debug('message:\n%s\n' % message)
587
587
588 if len(parents) == 1:
588 if len(parents) == 1:
589 parents.append(repo[nullid])
589 parents.append(repo[nullid])
590 if opts.get('exact'):
590 if opts.get('exact'):
591 if not nodeid or not p1:
591 if not nodeid or not p1:
592 raise util.Abort(_('not a Mercurial patch'))
592 raise util.Abort(_('not a Mercurial patch'))
593 p1 = repo[p1]
593 p1 = repo[p1]
594 p2 = repo[p2 or nullid]
594 p2 = repo[p2 or nullid]
595 elif p2:
595 elif p2:
596 try:
596 try:
597 p1 = repo[p1]
597 p1 = repo[p1]
598 p2 = repo[p2]
598 p2 = repo[p2]
599 # Without any options, consider p2 only if the
599 # Without any options, consider p2 only if the
600 # patch is being applied on top of the recorded
600 # patch is being applied on top of the recorded
601 # first parent.
601 # first parent.
602 if p1 != parents[0]:
602 if p1 != parents[0]:
603 p1 = parents[0]
603 p1 = parents[0]
604 p2 = repo[nullid]
604 p2 = repo[nullid]
605 except error.RepoError:
605 except error.RepoError:
606 p1, p2 = parents
606 p1, p2 = parents
607 else:
607 else:
608 p1, p2 = parents
608 p1, p2 = parents
609
609
610 n = None
610 n = None
611 if update:
611 if update:
612 if p1 != parents[0]:
612 if p1 != parents[0]:
613 updatefunc(repo, p1.node())
613 updatefunc(repo, p1.node())
614 if p2 != parents[1]:
614 if p2 != parents[1]:
615 repo.setparents(p1.node(), p2.node())
615 repo.setparents(p1.node(), p2.node())
616
616
617 if opts.get('exact') or opts.get('import_branch'):
617 if opts.get('exact') or opts.get('import_branch'):
618 repo.dirstate.setbranch(branch or 'default')
618 repo.dirstate.setbranch(branch or 'default')
619
619
620 files = set()
620 files = set()
621 patch.patch(ui, repo, tmpname, strip=strip, files=files,
621 patch.patch(ui, repo, tmpname, strip=strip, files=files,
622 eolmode=None, similarity=sim / 100.0)
622 eolmode=None, similarity=sim / 100.0)
623 files = list(files)
623 files = list(files)
624 if opts.get('no_commit'):
624 if opts.get('no_commit'):
625 if message:
625 if message:
626 msgs.append(message)
626 msgs.append(message)
627 else:
627 else:
628 if opts.get('exact') or p2:
628 if opts.get('exact') or p2:
629 # If you got here, you either use --force and know what
629 # If you got here, you either use --force and know what
630 # you are doing or used --exact or a merge patch while
630 # you are doing or used --exact or a merge patch while
631 # being updated to its first parent.
631 # being updated to its first parent.
632 m = None
632 m = None
633 else:
633 else:
634 m = scmutil.matchfiles(repo, files or [])
634 m = scmutil.matchfiles(repo, files or [])
635 n = repo.commit(message, opts.get('user') or user,
635 n = repo.commit(message, opts.get('user') or user,
636 opts.get('date') or date, match=m,
636 opts.get('date') or date, match=m,
637 editor=editor)
637 editor=editor)
638 else:
638 else:
639 if opts.get('exact') or opts.get('import_branch'):
639 if opts.get('exact') or opts.get('import_branch'):
640 branch = branch or 'default'
640 branch = branch or 'default'
641 else:
641 else:
642 branch = p1.branch()
642 branch = p1.branch()
643 store = patch.filestore()
643 store = patch.filestore()
644 try:
644 try:
645 files = set()
645 files = set()
646 try:
646 try:
647 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
647 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
648 files, eolmode=None)
648 files, eolmode=None)
649 except patch.PatchError, e:
649 except patch.PatchError, e:
650 raise util.Abort(str(e))
650 raise util.Abort(str(e))
651 memctx = context.makememctx(repo, (p1.node(), p2.node()),
651 memctx = context.makememctx(repo, (p1.node(), p2.node()),
652 message,
652 message,
653 opts.get('user') or user,
653 opts.get('user') or user,
654 opts.get('date') or date,
654 opts.get('date') or date,
655 branch, files, store,
655 branch, files, store,
656 editor=commiteditor)
656 editor=commiteditor)
657 repo.savecommitmessage(memctx.description())
657 repo.savecommitmessage(memctx.description())
658 n = memctx.commit()
658 n = memctx.commit()
659 finally:
659 finally:
660 store.close()
660 store.close()
661 if opts.get('exact') and hex(n) != nodeid:
661 if opts.get('exact') and hex(n) != nodeid:
662 raise util.Abort(_('patch is damaged or loses information'))
662 raise util.Abort(_('patch is damaged or loses information'))
663 if n:
663 if n:
664 # i18n: refers to a short changeset id
664 # i18n: refers to a short changeset id
665 msg = _('created %s') % short(n)
665 msg = _('created %s') % short(n)
666 return (msg, n)
666 return (msg, n)
667 finally:
667 finally:
668 os.unlink(tmpname)
668 os.unlink(tmpname)
669
669
670 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
670 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
671 opts=None):
671 opts=None):
672 '''export changesets as hg patches.'''
672 '''export changesets as hg patches.'''
673
673
674 total = len(revs)
674 total = len(revs)
675 revwidth = max([len(str(rev)) for rev in revs])
675 revwidth = max([len(str(rev)) for rev in revs])
676 filemode = {}
676 filemode = {}
677
677
678 def single(rev, seqno, fp):
678 def single(rev, seqno, fp):
679 ctx = repo[rev]
679 ctx = repo[rev]
680 node = ctx.node()
680 node = ctx.node()
681 parents = [p.node() for p in ctx.parents() if p]
681 parents = [p.node() for p in ctx.parents() if p]
682 branch = ctx.branch()
682 branch = ctx.branch()
683 if switch_parent:
683 if switch_parent:
684 parents.reverse()
684 parents.reverse()
685 prev = (parents and parents[0]) or nullid
685 prev = (parents and parents[0]) or nullid
686
686
687 shouldclose = False
687 shouldclose = False
688 if not fp and len(template) > 0:
688 if not fp and len(template) > 0:
689 desc_lines = ctx.description().rstrip().split('\n')
689 desc_lines = ctx.description().rstrip().split('\n')
690 desc = desc_lines[0] #Commit always has a first line.
690 desc = desc_lines[0] #Commit always has a first line.
691 fp = makefileobj(repo, template, node, desc=desc, total=total,
691 fp = makefileobj(repo, template, node, desc=desc, total=total,
692 seqno=seqno, revwidth=revwidth, mode='wb',
692 seqno=seqno, revwidth=revwidth, mode='wb',
693 modemap=filemode)
693 modemap=filemode)
694 if fp != template:
694 if fp != template:
695 shouldclose = True
695 shouldclose = True
696 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
696 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
697 repo.ui.note("%s\n" % fp.name)
697 repo.ui.note("%s\n" % fp.name)
698
698
699 if not fp:
699 if not fp:
700 write = repo.ui.write
700 write = repo.ui.write
701 else:
701 else:
702 def write(s, **kw):
702 def write(s, **kw):
703 fp.write(s)
703 fp.write(s)
704
704
705
705
706 write("# HG changeset patch\n")
706 write("# HG changeset patch\n")
707 write("# User %s\n" % ctx.user())
707 write("# User %s\n" % ctx.user())
708 write("# Date %d %d\n" % ctx.date())
708 write("# Date %d %d\n" % ctx.date())
709 write("# %s\n" % util.datestr(ctx.date()))
709 write("# %s\n" % util.datestr(ctx.date()))
710 if branch and branch != 'default':
710 if branch and branch != 'default':
711 write("# Branch %s\n" % branch)
711 write("# Branch %s\n" % branch)
712 write("# Node ID %s\n" % hex(node))
712 write("# Node ID %s\n" % hex(node))
713 write("# Parent %s\n" % hex(prev))
713 write("# Parent %s\n" % hex(prev))
714 if len(parents) > 1:
714 if len(parents) > 1:
715 write("# Parent %s\n" % hex(parents[1]))
715 write("# Parent %s\n" % hex(parents[1]))
716 write(ctx.description().rstrip())
716 write(ctx.description().rstrip())
717 write("\n\n")
717 write("\n\n")
718
718
719 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
719 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
720 write(chunk, label=label)
720 write(chunk, label=label)
721
721
722 if shouldclose:
722 if shouldclose:
723 fp.close()
723 fp.close()
724
724
725 for seqno, rev in enumerate(revs):
725 for seqno, rev in enumerate(revs):
726 single(rev, seqno + 1, fp)
726 single(rev, seqno + 1, fp)
727
727
728 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
728 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
729 changes=None, stat=False, fp=None, prefix='',
729 changes=None, stat=False, fp=None, prefix='',
730 listsubrepos=False):
730 listsubrepos=False):
731 '''show diff or diffstat.'''
731 '''show diff or diffstat.'''
732 if fp is None:
732 if fp is None:
733 write = ui.write
733 write = ui.write
734 else:
734 else:
735 def write(s, **kw):
735 def write(s, **kw):
736 fp.write(s)
736 fp.write(s)
737
737
738 if stat:
738 if stat:
739 diffopts = diffopts.copy(context=0)
739 diffopts = diffopts.copy(context=0)
740 width = 80
740 width = 80
741 if not ui.plain():
741 if not ui.plain():
742 width = ui.termwidth()
742 width = ui.termwidth()
743 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
743 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
744 prefix=prefix)
744 prefix=prefix)
745 for chunk, label in patch.diffstatui(util.iterlines(chunks),
745 for chunk, label in patch.diffstatui(util.iterlines(chunks),
746 width=width,
746 width=width,
747 git=diffopts.git):
747 git=diffopts.git):
748 write(chunk, label=label)
748 write(chunk, label=label)
749 else:
749 else:
750 for chunk, label in patch.diffui(repo, node1, node2, match,
750 for chunk, label in patch.diffui(repo, node1, node2, match,
751 changes, diffopts, prefix=prefix):
751 changes, diffopts, prefix=prefix):
752 write(chunk, label=label)
752 write(chunk, label=label)
753
753
754 if listsubrepos:
754 if listsubrepos:
755 ctx1 = repo[node1]
755 ctx1 = repo[node1]
756 ctx2 = repo[node2]
756 ctx2 = repo[node2]
757 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
757 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
758 tempnode2 = node2
758 tempnode2 = node2
759 try:
759 try:
760 if node2 is not None:
760 if node2 is not None:
761 tempnode2 = ctx2.substate[subpath][1]
761 tempnode2 = ctx2.substate[subpath][1]
762 except KeyError:
762 except KeyError:
763 # A subrepo that existed in node1 was deleted between node1 and
763 # A subrepo that existed in node1 was deleted between node1 and
764 # node2 (inclusive). Thus, ctx2's substate won't contain that
764 # node2 (inclusive). Thus, ctx2's substate won't contain that
765 # subpath. The best we can do is to ignore it.
765 # subpath. The best we can do is to ignore it.
766 tempnode2 = None
766 tempnode2 = None
767 submatch = matchmod.narrowmatcher(subpath, match)
767 submatch = matchmod.narrowmatcher(subpath, match)
768 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
768 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
769 stat=stat, fp=fp, prefix=prefix)
769 stat=stat, fp=fp, prefix=prefix)
770
770
771 class changeset_printer(object):
771 class changeset_printer(object):
772 '''show changeset information when templating not requested.'''
772 '''show changeset information when templating not requested.'''
773
773
774 def __init__(self, ui, repo, patch, diffopts, buffered):
774 def __init__(self, ui, repo, patch, diffopts, buffered):
775 self.ui = ui
775 self.ui = ui
776 self.repo = repo
776 self.repo = repo
777 self.buffered = buffered
777 self.buffered = buffered
778 self.patch = patch
778 self.patch = patch
779 self.diffopts = diffopts
779 self.diffopts = diffopts
780 self.header = {}
780 self.header = {}
781 self.hunk = {}
781 self.hunk = {}
782 self.lastheader = None
782 self.lastheader = None
783 self.footer = None
783 self.footer = None
784
784
785 def flush(self, rev):
785 def flush(self, rev):
786 if rev in self.header:
786 if rev in self.header:
787 h = self.header[rev]
787 h = self.header[rev]
788 if h != self.lastheader:
788 if h != self.lastheader:
789 self.lastheader = h
789 self.lastheader = h
790 self.ui.write(h)
790 self.ui.write(h)
791 del self.header[rev]
791 del self.header[rev]
792 if rev in self.hunk:
792 if rev in self.hunk:
793 self.ui.write(self.hunk[rev])
793 self.ui.write(self.hunk[rev])
794 del self.hunk[rev]
794 del self.hunk[rev]
795 return 1
795 return 1
796 return 0
796 return 0
797
797
798 def close(self):
798 def close(self):
799 if self.footer:
799 if self.footer:
800 self.ui.write(self.footer)
800 self.ui.write(self.footer)
801
801
802 def show(self, ctx, copies=None, matchfn=None, **props):
802 def show(self, ctx, copies=None, matchfn=None, **props):
803 if self.buffered:
803 if self.buffered:
804 self.ui.pushbuffer()
804 self.ui.pushbuffer()
805 self._show(ctx, copies, matchfn, props)
805 self._show(ctx, copies, matchfn, props)
806 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
806 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
807 else:
807 else:
808 self._show(ctx, copies, matchfn, props)
808 self._show(ctx, copies, matchfn, props)
809
809
810 def _show(self, ctx, copies, matchfn, props):
810 def _show(self, ctx, copies, matchfn, props):
811 '''show a single changeset or file revision'''
811 '''show a single changeset or file revision'''
812 changenode = ctx.node()
812 changenode = ctx.node()
813 rev = ctx.rev()
813 rev = ctx.rev()
814
814
815 if self.ui.quiet:
815 if self.ui.quiet:
816 self.ui.write("%d:%s\n" % (rev, short(changenode)),
816 self.ui.write("%d:%s\n" % (rev, short(changenode)),
817 label='log.node')
817 label='log.node')
818 return
818 return
819
819
820 log = self.repo.changelog
820 log = self.repo.changelog
821 date = util.datestr(ctx.date())
821 date = util.datestr(ctx.date())
822
822
823 hexfunc = self.ui.debugflag and hex or short
823 hexfunc = self.ui.debugflag and hex or short
824
824
825 parents = [(p, hexfunc(log.node(p)))
825 parents = [(p, hexfunc(log.node(p)))
826 for p in self._meaningful_parentrevs(log, rev)]
826 for p in self._meaningful_parentrevs(log, rev)]
827
827
828 # i18n: column positioning for "hg log"
828 # i18n: column positioning for "hg log"
829 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
829 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
830 label='log.changeset changeset.%s' % ctx.phasestr())
830 label='log.changeset changeset.%s' % ctx.phasestr())
831
831
832 branch = ctx.branch()
832 branch = ctx.branch()
833 # don't show the default branch name
833 # don't show the default branch name
834 if branch != 'default':
834 if branch != 'default':
835 # i18n: column positioning for "hg log"
835 # i18n: column positioning for "hg log"
836 self.ui.write(_("branch: %s\n") % branch,
836 self.ui.write(_("branch: %s\n") % branch,
837 label='log.branch')
837 label='log.branch')
838 for bookmark in self.repo.nodebookmarks(changenode):
838 for bookmark in self.repo.nodebookmarks(changenode):
839 # i18n: column positioning for "hg log"
839 # i18n: column positioning for "hg log"
840 self.ui.write(_("bookmark: %s\n") % bookmark,
840 self.ui.write(_("bookmark: %s\n") % bookmark,
841 label='log.bookmark')
841 label='log.bookmark')
842 for tag in self.repo.nodetags(changenode):
842 for tag in self.repo.nodetags(changenode):
843 # i18n: column positioning for "hg log"
843 # i18n: column positioning for "hg log"
844 self.ui.write(_("tag: %s\n") % tag,
844 self.ui.write(_("tag: %s\n") % tag,
845 label='log.tag')
845 label='log.tag')
846 if self.ui.debugflag and ctx.phase():
846 if self.ui.debugflag and ctx.phase():
847 # i18n: column positioning for "hg log"
847 # i18n: column positioning for "hg log"
848 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
848 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
849 label='log.phase')
849 label='log.phase')
850 for parent in parents:
850 for parent in parents:
851 # i18n: column positioning for "hg log"
851 # i18n: column positioning for "hg log"
852 self.ui.write(_("parent: %d:%s\n") % parent,
852 self.ui.write(_("parent: %d:%s\n") % parent,
853 label='log.parent changeset.%s' % ctx.phasestr())
853 label='log.parent changeset.%s' % ctx.phasestr())
854
854
855 if self.ui.debugflag:
855 if self.ui.debugflag:
856 mnode = ctx.manifestnode()
856 mnode = ctx.manifestnode()
857 # i18n: column positioning for "hg log"
857 # i18n: column positioning for "hg log"
858 self.ui.write(_("manifest: %d:%s\n") %
858 self.ui.write(_("manifest: %d:%s\n") %
859 (self.repo.manifest.rev(mnode), hex(mnode)),
859 (self.repo.manifest.rev(mnode), hex(mnode)),
860 label='ui.debug log.manifest')
860 label='ui.debug log.manifest')
861 # i18n: column positioning for "hg log"
861 # i18n: column positioning for "hg log"
862 self.ui.write(_("user: %s\n") % ctx.user(),
862 self.ui.write(_("user: %s\n") % ctx.user(),
863 label='log.user')
863 label='log.user')
864 # i18n: column positioning for "hg log"
864 # i18n: column positioning for "hg log"
865 self.ui.write(_("date: %s\n") % date,
865 self.ui.write(_("date: %s\n") % date,
866 label='log.date')
866 label='log.date')
867
867
868 if self.ui.debugflag:
868 if self.ui.debugflag:
869 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
869 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
870 for key, value in zip([# i18n: column positioning for "hg log"
870 for key, value in zip([# i18n: column positioning for "hg log"
871 _("files:"),
871 _("files:"),
872 # i18n: column positioning for "hg log"
872 # i18n: column positioning for "hg log"
873 _("files+:"),
873 _("files+:"),
874 # i18n: column positioning for "hg log"
874 # i18n: column positioning for "hg log"
875 _("files-:")], files):
875 _("files-:")], files):
876 if value:
876 if value:
877 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
877 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
878 label='ui.debug log.files')
878 label='ui.debug log.files')
879 elif ctx.files() and self.ui.verbose:
879 elif ctx.files() and self.ui.verbose:
880 # i18n: column positioning for "hg log"
880 # i18n: column positioning for "hg log"
881 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
881 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
882 label='ui.note log.files')
882 label='ui.note log.files')
883 if copies and self.ui.verbose:
883 if copies and self.ui.verbose:
884 copies = ['%s (%s)' % c for c in copies]
884 copies = ['%s (%s)' % c for c in copies]
885 # i18n: column positioning for "hg log"
885 # i18n: column positioning for "hg log"
886 self.ui.write(_("copies: %s\n") % ' '.join(copies),
886 self.ui.write(_("copies: %s\n") % ' '.join(copies),
887 label='ui.note log.copies')
887 label='ui.note log.copies')
888
888
889 extra = ctx.extra()
889 extra = ctx.extra()
890 if extra and self.ui.debugflag:
890 if extra and self.ui.debugflag:
891 for key, value in sorted(extra.items()):
891 for key, value in sorted(extra.items()):
892 # i18n: column positioning for "hg log"
892 # i18n: column positioning for "hg log"
893 self.ui.write(_("extra: %s=%s\n")
893 self.ui.write(_("extra: %s=%s\n")
894 % (key, value.encode('string_escape')),
894 % (key, value.encode('string_escape')),
895 label='ui.debug log.extra')
895 label='ui.debug log.extra')
896
896
897 description = ctx.description().strip()
897 description = ctx.description().strip()
898 if description:
898 if description:
899 if self.ui.verbose:
899 if self.ui.verbose:
900 self.ui.write(_("description:\n"),
900 self.ui.write(_("description:\n"),
901 label='ui.note log.description')
901 label='ui.note log.description')
902 self.ui.write(description,
902 self.ui.write(description,
903 label='ui.note log.description')
903 label='ui.note log.description')
904 self.ui.write("\n\n")
904 self.ui.write("\n\n")
905 else:
905 else:
906 # i18n: column positioning for "hg log"
906 # i18n: column positioning for "hg log"
907 self.ui.write(_("summary: %s\n") %
907 self.ui.write(_("summary: %s\n") %
908 description.splitlines()[0],
908 description.splitlines()[0],
909 label='log.summary')
909 label='log.summary')
910 self.ui.write("\n")
910 self.ui.write("\n")
911
911
912 self.showpatch(changenode, matchfn)
912 self.showpatch(changenode, matchfn)
913
913
914 def showpatch(self, node, matchfn):
914 def showpatch(self, node, matchfn):
915 if not matchfn:
915 if not matchfn:
916 matchfn = self.patch
916 matchfn = self.patch
917 if matchfn:
917 if matchfn:
918 stat = self.diffopts.get('stat')
918 stat = self.diffopts.get('stat')
919 diff = self.diffopts.get('patch')
919 diff = self.diffopts.get('patch')
920 diffopts = patch.diffopts(self.ui, self.diffopts)
920 diffopts = patch.diffopts(self.ui, self.diffopts)
921 prev = self.repo.changelog.parents(node)[0]
921 prev = self.repo.changelog.parents(node)[0]
922 if stat:
922 if stat:
923 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
923 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
924 match=matchfn, stat=True)
924 match=matchfn, stat=True)
925 if diff:
925 if diff:
926 if stat:
926 if stat:
927 self.ui.write("\n")
927 self.ui.write("\n")
928 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
928 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
929 match=matchfn, stat=False)
929 match=matchfn, stat=False)
930 self.ui.write("\n")
930 self.ui.write("\n")
931
931
932 def _meaningful_parentrevs(self, log, rev):
932 def _meaningful_parentrevs(self, log, rev):
933 """Return list of meaningful (or all if debug) parentrevs for rev.
933 """Return list of meaningful (or all if debug) parentrevs for rev.
934
934
935 For merges (two non-nullrev revisions) both parents are meaningful.
935 For merges (two non-nullrev revisions) both parents are meaningful.
936 Otherwise the first parent revision is considered meaningful if it
936 Otherwise the first parent revision is considered meaningful if it
937 is not the preceding revision.
937 is not the preceding revision.
938 """
938 """
939 parents = log.parentrevs(rev)
939 parents = log.parentrevs(rev)
940 if not self.ui.debugflag and parents[1] == nullrev:
940 if not self.ui.debugflag and parents[1] == nullrev:
941 if parents[0] >= rev - 1:
941 if parents[0] >= rev - 1:
942 parents = []
942 parents = []
943 else:
943 else:
944 parents = [parents[0]]
944 parents = [parents[0]]
945 return parents
945 return parents
946
946
947
947
948 class changeset_templater(changeset_printer):
948 class changeset_templater(changeset_printer):
949 '''format changeset information.'''
949 '''format changeset information.'''
950
950
951 def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
951 def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
952 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
952 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
953 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
953 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
954 defaulttempl = {
954 defaulttempl = {
955 'parent': '{rev}:{node|formatnode} ',
955 'parent': '{rev}:{node|formatnode} ',
956 'manifest': '{rev}:{node|formatnode}',
956 'manifest': '{rev}:{node|formatnode}',
957 'file_copy': '{name} ({source})',
957 'file_copy': '{name} ({source})',
958 'extra': '{key}={value|stringescape}'
958 'extra': '{key}={value|stringescape}'
959 }
959 }
960 # filecopy is preserved for compatibility reasons
960 # filecopy is preserved for compatibility reasons
961 defaulttempl['filecopy'] = defaulttempl['file_copy']
961 defaulttempl['filecopy'] = defaulttempl['file_copy']
962 self.t = templater.templater(mapfile, {'formatnode': formatnode},
962 self.t = templater.templater(mapfile, {'formatnode': formatnode},
963 cache=defaulttempl)
963 cache=defaulttempl)
964 if tmpl:
964 if tmpl:
965 self.t.cache['changeset'] = tmpl
965 self.t.cache['changeset'] = tmpl
966
966
967 self.cache = {}
967 self.cache = {}
968
968
969 def _meaningful_parentrevs(self, ctx):
969 def _meaningful_parentrevs(self, ctx):
970 """Return list of meaningful (or all if debug) parentrevs for rev.
970 """Return list of meaningful (or all if debug) parentrevs for rev.
971 """
971 """
972 parents = ctx.parents()
972 parents = ctx.parents()
973 if len(parents) > 1:
973 if len(parents) > 1:
974 return parents
974 return parents
975 if self.ui.debugflag:
975 if self.ui.debugflag:
976 return [parents[0], self.repo['null']]
976 return [parents[0], self.repo['null']]
977 if parents[0].rev() >= ctx.rev() - 1:
977 if parents[0].rev() >= ctx.rev() - 1:
978 return []
978 return []
979 return parents
979 return parents
980
980
981 def _show(self, ctx, copies, matchfn, props):
981 def _show(self, ctx, copies, matchfn, props):
982 '''show a single changeset or file revision'''
982 '''show a single changeset or file revision'''
983
983
984 showlist = templatekw.showlist
984 showlist = templatekw.showlist
985
985
986 # showparents() behaviour depends on ui trace level which
986 # showparents() behaviour depends on ui trace level which
987 # causes unexpected behaviours at templating level and makes
987 # causes unexpected behaviours at templating level and makes
988 # it harder to extract it in a standalone function. Its
988 # it harder to extract it in a standalone function. Its
989 # behaviour cannot be changed so leave it here for now.
989 # behaviour cannot be changed so leave it here for now.
990 def showparents(**args):
990 def showparents(**args):
991 ctx = args['ctx']
991 ctx = args['ctx']
992 parents = [[('rev', p.rev()), ('node', p.hex())]
992 parents = [[('rev', p.rev()), ('node', p.hex())]
993 for p in self._meaningful_parentrevs(ctx)]
993 for p in self._meaningful_parentrevs(ctx)]
994 return showlist('parent', parents, **args)
994 return showlist('parent', parents, **args)
995
995
996 props = props.copy()
996 props = props.copy()
997 props.update(templatekw.keywords)
997 props.update(templatekw.keywords)
998 props['parents'] = showparents
998 props['parents'] = showparents
999 props['templ'] = self.t
999 props['templ'] = self.t
1000 props['ctx'] = ctx
1000 props['ctx'] = ctx
1001 props['repo'] = self.repo
1001 props['repo'] = self.repo
1002 props['revcache'] = {'copies': copies}
1002 props['revcache'] = {'copies': copies}
1003 props['cache'] = self.cache
1003 props['cache'] = self.cache
1004
1004
1005 # find correct templates for current mode
1005 # find correct templates for current mode
1006
1006
1007 tmplmodes = [
1007 tmplmodes = [
1008 (True, None),
1008 (True, None),
1009 (self.ui.verbose, 'verbose'),
1009 (self.ui.verbose, 'verbose'),
1010 (self.ui.quiet, 'quiet'),
1010 (self.ui.quiet, 'quiet'),
1011 (self.ui.debugflag, 'debug'),
1011 (self.ui.debugflag, 'debug'),
1012 ]
1012 ]
1013
1013
1014 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1014 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1015 for mode, postfix in tmplmodes:
1015 for mode, postfix in tmplmodes:
1016 for type in types:
1016 for type in types:
1017 cur = postfix and ('%s_%s' % (type, postfix)) or type
1017 cur = postfix and ('%s_%s' % (type, postfix)) or type
1018 if mode and cur in self.t:
1018 if mode and cur in self.t:
1019 types[type] = cur
1019 types[type] = cur
1020
1020
1021 try:
1021 try:
1022
1022
1023 # write header
1023 # write header
1024 if types['header']:
1024 if types['header']:
1025 h = templater.stringify(self.t(types['header'], **props))
1025 h = templater.stringify(self.t(types['header'], **props))
1026 if self.buffered:
1026 if self.buffered:
1027 self.header[ctx.rev()] = h
1027 self.header[ctx.rev()] = h
1028 else:
1028 else:
1029 if self.lastheader != h:
1029 if self.lastheader != h:
1030 self.lastheader = h
1030 self.lastheader = h
1031 self.ui.write(h)
1031 self.ui.write(h)
1032
1032
1033 # write changeset metadata, then patch if requested
1033 # write changeset metadata, then patch if requested
1034 key = types['changeset']
1034 key = types['changeset']
1035 self.ui.write(templater.stringify(self.t(key, **props)))
1035 self.ui.write(templater.stringify(self.t(key, **props)))
1036 self.showpatch(ctx.node(), matchfn)
1036 self.showpatch(ctx.node(), matchfn)
1037
1037
1038 if types['footer']:
1038 if types['footer']:
1039 if not self.footer:
1039 if not self.footer:
1040 self.footer = templater.stringify(self.t(types['footer'],
1040 self.footer = templater.stringify(self.t(types['footer'],
1041 **props))
1041 **props))
1042
1042
1043 except KeyError, inst:
1043 except KeyError, inst:
1044 msg = _("%s: no key named '%s'")
1044 msg = _("%s: no key named '%s'")
1045 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1045 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1046 except SyntaxError, inst:
1046 except SyntaxError, inst:
1047 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1047 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1048
1048
1049 def gettemplate(ui, tmpl, style):
1049 def gettemplate(ui, tmpl, style):
1050 """
1050 """
1051 Find the template matching the given template spec or style.
1051 Find the template matching the given template spec or style.
1052 """
1052 """
1053
1053
1054 # ui settings
1054 # ui settings
1055 if not tmpl and not style:
1055 if not tmpl and not style:
1056 tmpl = ui.config('ui', 'logtemplate')
1056 tmpl = ui.config('ui', 'logtemplate')
1057 if tmpl:
1057 if tmpl:
1058 try:
1058 try:
1059 tmpl = templater.parsestring(tmpl)
1059 tmpl = templater.parsestring(tmpl)
1060 except SyntaxError:
1060 except SyntaxError:
1061 tmpl = templater.parsestring(tmpl, quoted=False)
1061 tmpl = templater.parsestring(tmpl, quoted=False)
1062 return tmpl, None
1062 return tmpl, None
1063 else:
1063 else:
1064 style = util.expandpath(ui.config('ui', 'style', ''))
1064 style = util.expandpath(ui.config('ui', 'style', ''))
1065
1065
1066 if style:
1066 if style:
1067 mapfile = style
1067 mapfile = style
1068 if not os.path.split(mapfile)[0]:
1068 if not os.path.split(mapfile)[0]:
1069 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1069 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1070 or templater.templatepath(mapfile))
1070 or templater.templatepath(mapfile))
1071 if mapname:
1071 if mapname:
1072 mapfile = mapname
1072 mapfile = mapname
1073 return None, mapfile
1073 return None, mapfile
1074
1074
1075 if not tmpl:
1075 if not tmpl:
1076 return None, None
1076 return None, None
1077
1077
1078 # looks like a literal template?
1078 # looks like a literal template?
1079 if '{' in tmpl:
1079 if '{' in tmpl:
1080 return tmpl, None
1080 return tmpl, None
1081
1081
1082 # perhaps a stock style?
1082 # perhaps a stock style?
1083 if not os.path.split(tmpl)[0]:
1083 if not os.path.split(tmpl)[0]:
1084 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1084 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1085 or templater.templatepath(tmpl))
1085 or templater.templatepath(tmpl))
1086 if mapname and os.path.isfile(mapname):
1086 if mapname and os.path.isfile(mapname):
1087 return None, mapname
1087 return None, mapname
1088
1088
1089 # perhaps it's a reference to [templates]
1089 # perhaps it's a reference to [templates]
1090 t = ui.config('templates', tmpl)
1090 t = ui.config('templates', tmpl)
1091 if t:
1091 if t:
1092 try:
1092 try:
1093 tmpl = templater.parsestring(t)
1093 tmpl = templater.parsestring(t)
1094 except SyntaxError:
1094 except SyntaxError:
1095 tmpl = templater.parsestring(t, quoted=False)
1095 tmpl = templater.parsestring(t, quoted=False)
1096 return tmpl, None
1096 return tmpl, None
1097
1097
1098 # perhaps it's a path to a map or a template
1098 # perhaps it's a path to a map or a template
1099 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1099 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1100 # is it a mapfile for a style?
1100 # is it a mapfile for a style?
1101 if os.path.basename(tmpl).startswith("map-"):
1101 if os.path.basename(tmpl).startswith("map-"):
1102 return None, os.path.realpath(tmpl)
1102 return None, os.path.realpath(tmpl)
1103 tmpl = open(tmpl).read()
1103 tmpl = open(tmpl).read()
1104 return tmpl, None
1104 return tmpl, None
1105
1105
1106 # constant string?
1106 # constant string?
1107 return tmpl, None
1107 return tmpl, None
1108
1108
1109 def show_changeset(ui, repo, opts, buffered=False):
1109 def show_changeset(ui, repo, opts, buffered=False):
1110 """show one changeset using template or regular display.
1110 """show one changeset using template or regular display.
1111
1111
1112 Display format will be the first non-empty hit of:
1112 Display format will be the first non-empty hit of:
1113 1. option 'template'
1113 1. option 'template'
1114 2. option 'style'
1114 2. option 'style'
1115 3. [ui] setting 'logtemplate'
1115 3. [ui] setting 'logtemplate'
1116 4. [ui] setting 'style'
1116 4. [ui] setting 'style'
1117 If all of these values are either the unset or the empty string,
1117 If all of these values are either the unset or the empty string,
1118 regular display via changeset_printer() is done.
1118 regular display via changeset_printer() is done.
1119 """
1119 """
1120 # options
1120 # options
1121 patch = None
1121 patch = None
1122 if opts.get('patch') or opts.get('stat'):
1122 if opts.get('patch') or opts.get('stat'):
1123 patch = scmutil.matchall(repo)
1123 patch = scmutil.matchall(repo)
1124
1124
1125 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1125 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1126
1126
1127 if not tmpl and not mapfile:
1127 if not tmpl and not mapfile:
1128 return changeset_printer(ui, repo, patch, opts, buffered)
1128 return changeset_printer(ui, repo, patch, opts, buffered)
1129
1129
1130 try:
1130 try:
1131 t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
1131 t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
1132 except SyntaxError, inst:
1132 except SyntaxError, inst:
1133 raise util.Abort(inst.args[0])
1133 raise util.Abort(inst.args[0])
1134 return t
1134 return t
1135
1135
1136 def showmarker(ui, marker):
1136 def showmarker(ui, marker):
1137 """utility function to display obsolescence marker in a readable way
1137 """utility function to display obsolescence marker in a readable way
1138
1138
1139 To be used by debug function."""
1139 To be used by debug function."""
1140 ui.write(hex(marker.precnode()))
1140 ui.write(hex(marker.precnode()))
1141 for repl in marker.succnodes():
1141 for repl in marker.succnodes():
1142 ui.write(' ')
1142 ui.write(' ')
1143 ui.write(hex(repl))
1143 ui.write(hex(repl))
1144 ui.write(' %X ' % marker._data[2])
1144 ui.write(' %X ' % marker._data[2])
1145 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1145 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1146 sorted(marker.metadata().items()))))
1146 sorted(marker.metadata().items()))))
1147 ui.write('\n')
1147 ui.write('\n')
1148
1148
1149 def finddate(ui, repo, date):
1149 def finddate(ui, repo, date):
1150 """Find the tipmost changeset that matches the given date spec"""
1150 """Find the tipmost changeset that matches the given date spec"""
1151
1151
1152 df = util.matchdate(date)
1152 df = util.matchdate(date)
1153 m = scmutil.matchall(repo)
1153 m = scmutil.matchall(repo)
1154 results = {}
1154 results = {}
1155
1155
1156 def prep(ctx, fns):
1156 def prep(ctx, fns):
1157 d = ctx.date()
1157 d = ctx.date()
1158 if df(d[0]):
1158 if df(d[0]):
1159 results[ctx.rev()] = d
1159 results[ctx.rev()] = d
1160
1160
1161 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1161 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1162 rev = ctx.rev()
1162 rev = ctx.rev()
1163 if rev in results:
1163 if rev in results:
1164 ui.status(_("found revision %s from %s\n") %
1164 ui.status(_("found revision %s from %s\n") %
1165 (rev, util.datestr(results[rev])))
1165 (rev, util.datestr(results[rev])))
1166 return str(rev)
1166 return str(rev)
1167
1167
1168 raise util.Abort(_("revision matching date not found"))
1168 raise util.Abort(_("revision matching date not found"))
1169
1169
1170 def increasingwindows(windowsize=8, sizelimit=512):
1170 def increasingwindows(windowsize=8, sizelimit=512):
1171 while True:
1171 while True:
1172 yield windowsize
1172 yield windowsize
1173 if windowsize < sizelimit:
1173 if windowsize < sizelimit:
1174 windowsize *= 2
1174 windowsize *= 2
1175
1175
1176 class FileWalkError(Exception):
1176 class FileWalkError(Exception):
1177 pass
1177 pass
1178
1178
1179 def walkfilerevs(repo, match, follow, revs, fncache):
1179 def walkfilerevs(repo, match, follow, revs, fncache):
1180 '''Walks the file history for the matched files.
1180 '''Walks the file history for the matched files.
1181
1181
1182 Returns the changeset revs that are involved in the file history.
1182 Returns the changeset revs that are involved in the file history.
1183
1183
1184 Throws FileWalkError if the file history can't be walked using
1184 Throws FileWalkError if the file history can't be walked using
1185 filelogs alone.
1185 filelogs alone.
1186 '''
1186 '''
1187 wanted = set()
1187 wanted = set()
1188 copies = []
1188 copies = []
1189 minrev, maxrev = min(revs), max(revs)
1189 minrev, maxrev = min(revs), max(revs)
1190 def filerevgen(filelog, last):
1190 def filerevgen(filelog, last):
1191 """
1191 """
1192 Only files, no patterns. Check the history of each file.
1192 Only files, no patterns. Check the history of each file.
1193
1193
1194 Examines filelog entries within minrev, maxrev linkrev range
1194 Examines filelog entries within minrev, maxrev linkrev range
1195 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1195 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1196 tuples in backwards order
1196 tuples in backwards order
1197 """
1197 """
1198 cl_count = len(repo)
1198 cl_count = len(repo)
1199 revs = []
1199 revs = []
1200 for j in xrange(0, last + 1):
1200 for j in xrange(0, last + 1):
1201 linkrev = filelog.linkrev(j)
1201 linkrev = filelog.linkrev(j)
1202 if linkrev < minrev:
1202 if linkrev < minrev:
1203 continue
1203 continue
1204 # only yield rev for which we have the changelog, it can
1204 # only yield rev for which we have the changelog, it can
1205 # happen while doing "hg log" during a pull or commit
1205 # happen while doing "hg log" during a pull or commit
1206 if linkrev >= cl_count:
1206 if linkrev >= cl_count:
1207 break
1207 break
1208
1208
1209 parentlinkrevs = []
1209 parentlinkrevs = []
1210 for p in filelog.parentrevs(j):
1210 for p in filelog.parentrevs(j):
1211 if p != nullrev:
1211 if p != nullrev:
1212 parentlinkrevs.append(filelog.linkrev(p))
1212 parentlinkrevs.append(filelog.linkrev(p))
1213 n = filelog.node(j)
1213 n = filelog.node(j)
1214 revs.append((linkrev, parentlinkrevs,
1214 revs.append((linkrev, parentlinkrevs,
1215 follow and filelog.renamed(n)))
1215 follow and filelog.renamed(n)))
1216
1216
1217 return reversed(revs)
1217 return reversed(revs)
1218 def iterfiles():
1218 def iterfiles():
1219 pctx = repo['.']
1219 pctx = repo['.']
1220 for filename in match.files():
1220 for filename in match.files():
1221 if follow:
1221 if follow:
1222 if filename not in pctx:
1222 if filename not in pctx:
1223 raise util.Abort(_('cannot follow file not in parent '
1223 raise util.Abort(_('cannot follow file not in parent '
1224 'revision: "%s"') % filename)
1224 'revision: "%s"') % filename)
1225 yield filename, pctx[filename].filenode()
1225 yield filename, pctx[filename].filenode()
1226 else:
1226 else:
1227 yield filename, None
1227 yield filename, None
1228 for filename_node in copies:
1228 for filename_node in copies:
1229 yield filename_node
1229 yield filename_node
1230
1230
1231 for file_, node in iterfiles():
1231 for file_, node in iterfiles():
1232 filelog = repo.file(file_)
1232 filelog = repo.file(file_)
1233 if not len(filelog):
1233 if not len(filelog):
1234 if node is None:
1234 if node is None:
1235 # A zero count may be a directory or deleted file, so
1235 # A zero count may be a directory or deleted file, so
1236 # try to find matching entries on the slow path.
1236 # try to find matching entries on the slow path.
1237 if follow:
1237 if follow:
1238 raise util.Abort(
1238 raise util.Abort(
1239 _('cannot follow nonexistent file: "%s"') % file_)
1239 _('cannot follow nonexistent file: "%s"') % file_)
1240 raise FileWalkError("Cannot walk via filelog")
1240 raise FileWalkError("Cannot walk via filelog")
1241 else:
1241 else:
1242 continue
1242 continue
1243
1243
1244 if node is None:
1244 if node is None:
1245 last = len(filelog) - 1
1245 last = len(filelog) - 1
1246 else:
1246 else:
1247 last = filelog.rev(node)
1247 last = filelog.rev(node)
1248
1248
1249
1249
1250 # keep track of all ancestors of the file
1250 # keep track of all ancestors of the file
1251 ancestors = set([filelog.linkrev(last)])
1251 ancestors = set([filelog.linkrev(last)])
1252
1252
1253 # iterate from latest to oldest revision
1253 # iterate from latest to oldest revision
1254 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1254 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1255 if not follow:
1255 if not follow:
1256 if rev > maxrev:
1256 if rev > maxrev:
1257 continue
1257 continue
1258 else:
1258 else:
1259 # Note that last might not be the first interesting
1259 # Note that last might not be the first interesting
1260 # rev to us:
1260 # rev to us:
1261 # if the file has been changed after maxrev, we'll
1261 # if the file has been changed after maxrev, we'll
1262 # have linkrev(last) > maxrev, and we still need
1262 # have linkrev(last) > maxrev, and we still need
1263 # to explore the file graph
1263 # to explore the file graph
1264 if rev not in ancestors:
1264 if rev not in ancestors:
1265 continue
1265 continue
1266 # XXX insert 1327 fix here
1266 # XXX insert 1327 fix here
1267 if flparentlinkrevs:
1267 if flparentlinkrevs:
1268 ancestors.update(flparentlinkrevs)
1268 ancestors.update(flparentlinkrevs)
1269
1269
1270 fncache.setdefault(rev, []).append(file_)
1270 fncache.setdefault(rev, []).append(file_)
1271 wanted.add(rev)
1271 wanted.add(rev)
1272 if copied:
1272 if copied:
1273 copies.append(copied)
1273 copies.append(copied)
1274
1274
1275 return wanted
1275 return wanted
1276
1276
1277 def walkchangerevs(repo, match, opts, prepare):
1277 def walkchangerevs(repo, match, opts, prepare):
1278 '''Iterate over files and the revs in which they changed.
1278 '''Iterate over files and the revs in which they changed.
1279
1279
1280 Callers most commonly need to iterate backwards over the history
1280 Callers most commonly need to iterate backwards over the history
1281 in which they are interested. Doing so has awful (quadratic-looking)
1281 in which they are interested. Doing so has awful (quadratic-looking)
1282 performance, so we use iterators in a "windowed" way.
1282 performance, so we use iterators in a "windowed" way.
1283
1283
1284 We walk a window of revisions in the desired order. Within the
1284 We walk a window of revisions in the desired order. Within the
1285 window, we first walk forwards to gather data, then in the desired
1285 window, we first walk forwards to gather data, then in the desired
1286 order (usually backwards) to display it.
1286 order (usually backwards) to display it.
1287
1287
1288 This function returns an iterator yielding contexts. Before
1288 This function returns an iterator yielding contexts. Before
1289 yielding each context, the iterator will first call the prepare
1289 yielding each context, the iterator will first call the prepare
1290 function on each context in the window in forward order.'''
1290 function on each context in the window in forward order.'''
1291
1291
1292 follow = opts.get('follow') or opts.get('follow_first')
1292 follow = opts.get('follow') or opts.get('follow_first')
1293
1293
1294 if opts.get('rev'):
1294 if opts.get('rev'):
1295 revs = scmutil.revrange(repo, opts.get('rev'))
1295 revs = scmutil.revrange(repo, opts.get('rev'))
1296 elif follow:
1296 elif follow:
1297 revs = repo.revs('reverse(:.)')
1297 revs = repo.revs('reverse(:.)')
1298 else:
1298 else:
1299 revs = revset.spanset(repo)
1299 revs = revset.spanset(repo)
1300 revs.reverse()
1300 revs.reverse()
1301 if not revs:
1301 if not revs:
1302 return []
1302 return []
1303 wanted = set()
1303 wanted = set()
1304 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1304 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1305 fncache = {}
1305 fncache = {}
1306 change = repo.changectx
1306 change = repo.changectx
1307
1307
1308 # First step is to fill wanted, the set of revisions that we want to yield.
1308 # First step is to fill wanted, the set of revisions that we want to yield.
1309 # When it does not induce extra cost, we also fill fncache for revisions in
1309 # When it does not induce extra cost, we also fill fncache for revisions in
1310 # wanted: a cache of filenames that were changed (ctx.files()) and that
1310 # wanted: a cache of filenames that were changed (ctx.files()) and that
1311 # match the file filtering conditions.
1311 # match the file filtering conditions.
1312
1312
1313 if not slowpath and not match.files():
1313 if not slowpath and not match.files():
1314 # No files, no patterns. Display all revs.
1314 # No files, no patterns. Display all revs.
1315 wanted = revs
1315 wanted = revs
1316
1316
1317 if not slowpath and match.files():
1317 if not slowpath and match.files():
1318 # We only have to read through the filelog to find wanted revisions
1318 # We only have to read through the filelog to find wanted revisions
1319
1319
1320 try:
1320 try:
1321 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1321 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1322 except FileWalkError:
1322 except FileWalkError:
1323 slowpath = True
1323 slowpath = True
1324
1324
1325 # We decided to fall back to the slowpath because at least one
1325 # We decided to fall back to the slowpath because at least one
1326 # of the paths was not a file. Check to see if at least one of them
1326 # of the paths was not a file. Check to see if at least one of them
1327 # existed in history, otherwise simply return
1327 # existed in history, otherwise simply return
1328 for path in match.files():
1328 for path in match.files():
1329 if path == '.' or path in repo.store:
1329 if path == '.' or path in repo.store:
1330 break
1330 break
1331 else:
1331 else:
1332 return []
1332 return []
1333
1333
1334 if slowpath:
1334 if slowpath:
1335 # We have to read the changelog to match filenames against
1335 # We have to read the changelog to match filenames against
1336 # changed files
1336 # changed files
1337
1337
1338 if follow:
1338 if follow:
1339 raise util.Abort(_('can only follow copies/renames for explicit '
1339 raise util.Abort(_('can only follow copies/renames for explicit '
1340 'filenames'))
1340 'filenames'))
1341
1341
1342 # The slow path checks files modified in every changeset.
1342 # The slow path checks files modified in every changeset.
1343 # This is really slow on large repos, so compute the set lazily.
1343 # This is really slow on large repos, so compute the set lazily.
1344 class lazywantedset(object):
1344 class lazywantedset(object):
1345 def __init__(self):
1345 def __init__(self):
1346 self.set = set()
1346 self.set = set()
1347 self.revs = set(revs)
1347 self.revs = set(revs)
1348
1348
1349 # No need to worry about locality here because it will be accessed
1349 # No need to worry about locality here because it will be accessed
1350 # in the same order as the increasing window below.
1350 # in the same order as the increasing window below.
1351 def __contains__(self, value):
1351 def __contains__(self, value):
1352 if value in self.set:
1352 if value in self.set:
1353 return True
1353 return True
1354 elif not value in self.revs:
1354 elif not value in self.revs:
1355 return False
1355 return False
1356 else:
1356 else:
1357 self.revs.discard(value)
1357 self.revs.discard(value)
1358 ctx = change(value)
1358 ctx = change(value)
1359 matches = filter(match, ctx.files())
1359 matches = filter(match, ctx.files())
1360 if matches:
1360 if matches:
1361 fncache[value] = matches
1361 fncache[value] = matches
1362 self.set.add(value)
1362 self.set.add(value)
1363 return True
1363 return True
1364 return False
1364 return False
1365
1365
1366 def discard(self, value):
1366 def discard(self, value):
1367 self.revs.discard(value)
1367 self.revs.discard(value)
1368 self.set.discard(value)
1368 self.set.discard(value)
1369
1369
1370 wanted = lazywantedset()
1370 wanted = lazywantedset()
1371
1371
1372 class followfilter(object):
1372 class followfilter(object):
1373 def __init__(self, onlyfirst=False):
1373 def __init__(self, onlyfirst=False):
1374 self.startrev = nullrev
1374 self.startrev = nullrev
1375 self.roots = set()
1375 self.roots = set()
1376 self.onlyfirst = onlyfirst
1376 self.onlyfirst = onlyfirst
1377
1377
1378 def match(self, rev):
1378 def match(self, rev):
1379 def realparents(rev):
1379 def realparents(rev):
1380 if self.onlyfirst:
1380 if self.onlyfirst:
1381 return repo.changelog.parentrevs(rev)[0:1]
1381 return repo.changelog.parentrevs(rev)[0:1]
1382 else:
1382 else:
1383 return filter(lambda x: x != nullrev,
1383 return filter(lambda x: x != nullrev,
1384 repo.changelog.parentrevs(rev))
1384 repo.changelog.parentrevs(rev))
1385
1385
1386 if self.startrev == nullrev:
1386 if self.startrev == nullrev:
1387 self.startrev = rev
1387 self.startrev = rev
1388 return True
1388 return True
1389
1389
1390 if rev > self.startrev:
1390 if rev > self.startrev:
1391 # forward: all descendants
1391 # forward: all descendants
1392 if not self.roots:
1392 if not self.roots:
1393 self.roots.add(self.startrev)
1393 self.roots.add(self.startrev)
1394 for parent in realparents(rev):
1394 for parent in realparents(rev):
1395 if parent in self.roots:
1395 if parent in self.roots:
1396 self.roots.add(rev)
1396 self.roots.add(rev)
1397 return True
1397 return True
1398 else:
1398 else:
1399 # backwards: all parents
1399 # backwards: all parents
1400 if not self.roots:
1400 if not self.roots:
1401 self.roots.update(realparents(self.startrev))
1401 self.roots.update(realparents(self.startrev))
1402 if rev in self.roots:
1402 if rev in self.roots:
1403 self.roots.remove(rev)
1403 self.roots.remove(rev)
1404 self.roots.update(realparents(rev))
1404 self.roots.update(realparents(rev))
1405 return True
1405 return True
1406
1406
1407 return False
1407 return False
1408
1408
1409 # it might be worthwhile to do this in the iterator if the rev range
1409 # it might be worthwhile to do this in the iterator if the rev range
1410 # is descending and the prune args are all within that range
1410 # is descending and the prune args are all within that range
1411 for rev in opts.get('prune', ()):
1411 for rev in opts.get('prune', ()):
1412 rev = repo[rev].rev()
1412 rev = repo[rev].rev()
1413 ff = followfilter()
1413 ff = followfilter()
1414 stop = min(revs[0], revs[-1])
1414 stop = min(revs[0], revs[-1])
1415 for x in xrange(rev, stop - 1, -1):
1415 for x in xrange(rev, stop - 1, -1):
1416 if ff.match(x):
1416 if ff.match(x):
1417 wanted = wanted - [x]
1417 wanted = wanted - [x]
1418
1418
1419 # Now that wanted is correctly initialized, we can iterate over the
1419 # Now that wanted is correctly initialized, we can iterate over the
1420 # revision range, yielding only revisions in wanted.
1420 # revision range, yielding only revisions in wanted.
1421 def iterate():
1421 def iterate():
1422 if follow and not match.files():
1422 if follow and not match.files():
1423 ff = followfilter(onlyfirst=opts.get('follow_first'))
1423 ff = followfilter(onlyfirst=opts.get('follow_first'))
1424 def want(rev):
1424 def want(rev):
1425 return ff.match(rev) and rev in wanted
1425 return ff.match(rev) and rev in wanted
1426 else:
1426 else:
1427 def want(rev):
1427 def want(rev):
1428 return rev in wanted
1428 return rev in wanted
1429
1429
1430 it = iter(revs)
1430 it = iter(revs)
1431 stopiteration = False
1431 stopiteration = False
1432 for windowsize in increasingwindows():
1432 for windowsize in increasingwindows():
1433 nrevs = []
1433 nrevs = []
1434 for i in xrange(windowsize):
1434 for i in xrange(windowsize):
1435 try:
1435 try:
1436 rev = it.next()
1436 rev = it.next()
1437 if want(rev):
1437 if want(rev):
1438 nrevs.append(rev)
1438 nrevs.append(rev)
1439 except (StopIteration):
1439 except (StopIteration):
1440 stopiteration = True
1440 stopiteration = True
1441 break
1441 break
1442 for rev in sorted(nrevs):
1442 for rev in sorted(nrevs):
1443 fns = fncache.get(rev)
1443 fns = fncache.get(rev)
1444 ctx = change(rev)
1444 ctx = change(rev)
1445 if not fns:
1445 if not fns:
1446 def fns_generator():
1446 def fns_generator():
1447 for f in ctx.files():
1447 for f in ctx.files():
1448 if match(f):
1448 if match(f):
1449 yield f
1449 yield f
1450 fns = fns_generator()
1450 fns = fns_generator()
1451 prepare(ctx, fns)
1451 prepare(ctx, fns)
1452 for rev in nrevs:
1452 for rev in nrevs:
1453 yield change(rev)
1453 yield change(rev)
1454
1454
1455 if stopiteration:
1455 if stopiteration:
1456 break
1456 break
1457
1457
1458 return iterate()
1458 return iterate()
1459
1459
1460 def _makegraphfilematcher(repo, pats, followfirst):
1460 def _makegraphfilematcher(repo, pats, followfirst):
1461 # When displaying a revision with --patch --follow FILE, we have
1461 # When displaying a revision with --patch --follow FILE, we have
1462 # to know which file of the revision must be diffed. With
1462 # to know which file of the revision must be diffed. With
1463 # --follow, we want the names of the ancestors of FILE in the
1463 # --follow, we want the names of the ancestors of FILE in the
1464 # revision, stored in "fcache". "fcache" is populated by
1464 # revision, stored in "fcache". "fcache" is populated by
1465 # reproducing the graph traversal already done by --follow revset
1465 # reproducing the graph traversal already done by --follow revset
1466 # and relating linkrevs to file names (which is not "correct" but
1466 # and relating linkrevs to file names (which is not "correct" but
1467 # good enough).
1467 # good enough).
1468 fcache = {}
1468 fcache = {}
1469 fcacheready = [False]
1469 fcacheready = [False]
1470 pctx = repo['.']
1470 pctx = repo['.']
1471 wctx = repo[None]
1471 wctx = repo[None]
1472
1472
1473 def populate():
1473 def populate():
1474 for fn in pats:
1474 for fn in pats:
1475 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1475 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1476 for c in i:
1476 for c in i:
1477 fcache.setdefault(c.linkrev(), set()).add(c.path())
1477 fcache.setdefault(c.linkrev(), set()).add(c.path())
1478
1478
1479 def filematcher(rev):
1479 def filematcher(rev):
1480 if not fcacheready[0]:
1480 if not fcacheready[0]:
1481 # Lazy initialization
1481 # Lazy initialization
1482 fcacheready[0] = True
1482 fcacheready[0] = True
1483 populate()
1483 populate()
1484 return scmutil.match(wctx, fcache.get(rev, []), default='path')
1484 return scmutil.match(wctx, fcache.get(rev, []), default='path')
1485
1485
1486 return filematcher
1486 return filematcher
1487
1487
1488 def _makegraphlogrevset(repo, pats, opts, revs):
1488 def _makegraphlogrevset(repo, pats, opts, revs):
1489 """Return (expr, filematcher) where expr is a revset string built
1489 """Return (expr, filematcher) where expr is a revset string built
1490 from log options and file patterns or None. If --stat or --patch
1490 from log options and file patterns or None. If --stat or --patch
1491 are not passed filematcher is None. Otherwise it is a callable
1491 are not passed filematcher is None. Otherwise it is a callable
1492 taking a revision number and returning a match objects filtering
1492 taking a revision number and returning a match objects filtering
1493 the files to be detailed when displaying the revision.
1493 the files to be detailed when displaying the revision.
1494 """
1494 """
1495 opt2revset = {
1495 opt2revset = {
1496 'no_merges': ('not merge()', None),
1496 'no_merges': ('not merge()', None),
1497 'only_merges': ('merge()', None),
1497 'only_merges': ('merge()', None),
1498 '_ancestors': ('ancestors(%(val)s)', None),
1498 '_ancestors': ('ancestors(%(val)s)', None),
1499 '_fancestors': ('_firstancestors(%(val)s)', None),
1499 '_fancestors': ('_firstancestors(%(val)s)', None),
1500 '_descendants': ('descendants(%(val)s)', None),
1500 '_descendants': ('descendants(%(val)s)', None),
1501 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1501 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1502 '_matchfiles': ('_matchfiles(%(val)s)', None),
1502 '_matchfiles': ('_matchfiles(%(val)s)', None),
1503 'date': ('date(%(val)r)', None),
1503 'date': ('date(%(val)r)', None),
1504 'branch': ('branch(%(val)r)', ' or '),
1504 'branch': ('branch(%(val)r)', ' or '),
1505 '_patslog': ('filelog(%(val)r)', ' or '),
1505 '_patslog': ('filelog(%(val)r)', ' or '),
1506 '_patsfollow': ('follow(%(val)r)', ' or '),
1506 '_patsfollow': ('follow(%(val)r)', ' or '),
1507 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1507 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1508 'keyword': ('keyword(%(val)r)', ' or '),
1508 'keyword': ('keyword(%(val)r)', ' or '),
1509 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1509 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1510 'user': ('user(%(val)r)', ' or '),
1510 'user': ('user(%(val)r)', ' or '),
1511 }
1511 }
1512
1512
1513 opts = dict(opts)
1513 opts = dict(opts)
1514 # follow or not follow?
1514 # follow or not follow?
1515 follow = opts.get('follow') or opts.get('follow_first')
1515 follow = opts.get('follow') or opts.get('follow_first')
1516 followfirst = opts.get('follow_first') and 1 or 0
1516 followfirst = opts.get('follow_first') and 1 or 0
1517 # --follow with FILE behaviour depends on revs...
1517 # --follow with FILE behaviour depends on revs...
1518 it = iter(revs)
1518 it = iter(revs)
1519 startrev = it.next()
1519 startrev = it.next()
1520 try:
1520 try:
1521 followdescendants = startrev < it.next()
1521 followdescendants = startrev < it.next()
1522 except (StopIteration):
1522 except (StopIteration):
1523 followdescendants = False
1523 followdescendants = False
1524
1524
1525 # branch and only_branch are really aliases and must be handled at
1525 # branch and only_branch are really aliases and must be handled at
1526 # the same time
1526 # the same time
1527 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1527 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1528 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1528 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1529 # pats/include/exclude are passed to match.match() directly in
1529 # pats/include/exclude are passed to match.match() directly in
1530 # _matchfiles() revset but walkchangerevs() builds its matcher with
1530 # _matchfiles() revset but walkchangerevs() builds its matcher with
1531 # scmutil.match(). The difference is input pats are globbed on
1531 # scmutil.match(). The difference is input pats are globbed on
1532 # platforms without shell expansion (windows).
1532 # platforms without shell expansion (windows).
1533 pctx = repo[None]
1533 pctx = repo[None]
1534 match, pats = scmutil.matchandpats(pctx, pats, opts)
1534 match, pats = scmutil.matchandpats(pctx, pats, opts)
1535 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1535 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1536 if not slowpath:
1536 if not slowpath:
1537 for f in match.files():
1537 for f in match.files():
1538 if follow and f not in pctx:
1538 if follow and f not in pctx:
1539 raise util.Abort(_('cannot follow file not in parent '
1539 raise util.Abort(_('cannot follow file not in parent '
1540 'revision: "%s"') % f)
1540 'revision: "%s"') % f)
1541 filelog = repo.file(f)
1541 filelog = repo.file(f)
1542 if not filelog:
1542 if not filelog:
1543 # A zero count may be a directory or deleted file, so
1543 # A zero count may be a directory or deleted file, so
1544 # try to find matching entries on the slow path.
1544 # try to find matching entries on the slow path.
1545 if follow:
1545 if follow:
1546 raise util.Abort(
1546 raise util.Abort(
1547 _('cannot follow nonexistent file: "%s"') % f)
1547 _('cannot follow nonexistent file: "%s"') % f)
1548 slowpath = True
1548 slowpath = True
1549
1549
1550 # We decided to fall back to the slowpath because at least one
1550 # We decided to fall back to the slowpath because at least one
1551 # of the paths was not a file. Check to see if at least one of them
1551 # of the paths was not a file. Check to see if at least one of them
1552 # existed in history - in that case, we'll continue down the
1552 # existed in history - in that case, we'll continue down the
1553 # slowpath; otherwise, we can turn off the slowpath
1553 # slowpath; otherwise, we can turn off the slowpath
1554 if slowpath:
1554 if slowpath:
1555 for path in match.files():
1555 for path in match.files():
1556 if path == '.' or path in repo.store:
1556 if path == '.' or path in repo.store:
1557 break
1557 break
1558 else:
1558 else:
1559 slowpath = False
1559 slowpath = False
1560
1560
1561 if slowpath:
1561 if slowpath:
1562 # See walkchangerevs() slow path.
1562 # See walkchangerevs() slow path.
1563 #
1563 #
1564 if follow:
1564 if follow:
1565 raise util.Abort(_('can only follow copies/renames for explicit '
1565 raise util.Abort(_('can only follow copies/renames for explicit '
1566 'filenames'))
1566 'filenames'))
1567 # pats/include/exclude cannot be represented as separate
1567 # pats/include/exclude cannot be represented as separate
1568 # revset expressions as their filtering logic applies at file
1568 # revset expressions as their filtering logic applies at file
1569 # level. For instance "-I a -X a" matches a revision touching
1569 # level. For instance "-I a -X a" matches a revision touching
1570 # "a" and "b" while "file(a) and not file(b)" does
1570 # "a" and "b" while "file(a) and not file(b)" does
1571 # not. Besides, filesets are evaluated against the working
1571 # not. Besides, filesets are evaluated against the working
1572 # directory.
1572 # directory.
1573 matchargs = ['r:', 'd:relpath']
1573 matchargs = ['r:', 'd:relpath']
1574 for p in pats:
1574 for p in pats:
1575 matchargs.append('p:' + p)
1575 matchargs.append('p:' + p)
1576 for p in opts.get('include', []):
1576 for p in opts.get('include', []):
1577 matchargs.append('i:' + p)
1577 matchargs.append('i:' + p)
1578 for p in opts.get('exclude', []):
1578 for p in opts.get('exclude', []):
1579 matchargs.append('x:' + p)
1579 matchargs.append('x:' + p)
1580 matchargs = ','.join(('%r' % p) for p in matchargs)
1580 matchargs = ','.join(('%r' % p) for p in matchargs)
1581 opts['_matchfiles'] = matchargs
1581 opts['_matchfiles'] = matchargs
1582 else:
1582 else:
1583 if follow:
1583 if follow:
1584 fpats = ('_patsfollow', '_patsfollowfirst')
1584 fpats = ('_patsfollow', '_patsfollowfirst')
1585 fnopats = (('_ancestors', '_fancestors'),
1585 fnopats = (('_ancestors', '_fancestors'),
1586 ('_descendants', '_fdescendants'))
1586 ('_descendants', '_fdescendants'))
1587 if pats:
1587 if pats:
1588 # follow() revset interprets its file argument as a
1588 # follow() revset interprets its file argument as a
1589 # manifest entry, so use match.files(), not pats.
1589 # manifest entry, so use match.files(), not pats.
1590 opts[fpats[followfirst]] = list(match.files())
1590 opts[fpats[followfirst]] = list(match.files())
1591 else:
1591 else:
1592 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1592 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1593 else:
1593 else:
1594 opts['_patslog'] = list(pats)
1594 opts['_patslog'] = list(pats)
1595
1595
1596 filematcher = None
1596 filematcher = None
1597 if opts.get('patch') or opts.get('stat'):
1597 if opts.get('patch') or opts.get('stat'):
1598 if follow:
1598 if follow:
1599 filematcher = _makegraphfilematcher(repo, pats, followfirst)
1599 filematcher = _makegraphfilematcher(repo, pats, followfirst)
1600 else:
1600 else:
1601 filematcher = lambda rev: match
1601 filematcher = lambda rev: match
1602
1602
1603 expr = []
1603 expr = []
1604 for op, val in opts.iteritems():
1604 for op, val in opts.iteritems():
1605 if not val:
1605 if not val:
1606 continue
1606 continue
1607 if op not in opt2revset:
1607 if op not in opt2revset:
1608 continue
1608 continue
1609 revop, andor = opt2revset[op]
1609 revop, andor = opt2revset[op]
1610 if '%(val)' not in revop:
1610 if '%(val)' not in revop:
1611 expr.append(revop)
1611 expr.append(revop)
1612 else:
1612 else:
1613 if not isinstance(val, list):
1613 if not isinstance(val, list):
1614 e = revop % {'val': val}
1614 e = revop % {'val': val}
1615 else:
1615 else:
1616 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1616 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1617 expr.append(e)
1617 expr.append(e)
1618
1618
1619 if expr:
1619 if expr:
1620 expr = '(' + ' and '.join(expr) + ')'
1620 expr = '(' + ' and '.join(expr) + ')'
1621 else:
1621 else:
1622 expr = None
1622 expr = None
1623 return expr, filematcher
1623 return expr, filematcher
1624
1624
1625 def getgraphlogrevs(repo, pats, opts):
1625 def getgraphlogrevs(repo, pats, opts):
1626 """Return (revs, expr, filematcher) where revs is an iterable of
1626 """Return (revs, expr, filematcher) where revs is an iterable of
1627 revision numbers, expr is a revset string built from log options
1627 revision numbers, expr is a revset string built from log options
1628 and file patterns or None, and used to filter 'revs'. If --stat or
1628 and file patterns or None, and used to filter 'revs'. If --stat or
1629 --patch are not passed filematcher is None. Otherwise it is a
1629 --patch are not passed filematcher is None. Otherwise it is a
1630 callable taking a revision number and returning a match objects
1630 callable taking a revision number and returning a match objects
1631 filtering the files to be detailed when displaying the revision.
1631 filtering the files to be detailed when displaying the revision.
1632 """
1632 """
1633 if not len(repo):
1633 if not len(repo):
1634 return [], None, None
1634 return [], None, None
1635 limit = loglimit(opts)
1635 limit = loglimit(opts)
1636 # Default --rev value depends on --follow but --follow behaviour
1636 # Default --rev value depends on --follow but --follow behaviour
1637 # depends on revisions resolved from --rev...
1637 # depends on revisions resolved from --rev...
1638 follow = opts.get('follow') or opts.get('follow_first')
1638 follow = opts.get('follow') or opts.get('follow_first')
1639 possiblyunsorted = False # whether revs might need sorting
1639 possiblyunsorted = False # whether revs might need sorting
1640 if opts.get('rev'):
1640 if opts.get('rev'):
1641 revs = scmutil.revrange(repo, opts['rev'])
1641 revs = scmutil.revrange(repo, opts['rev'])
1642 # Don't sort here because _makegraphlogrevset might depend on the
1642 # Don't sort here because _makegraphlogrevset might depend on the
1643 # order of revs
1643 # order of revs
1644 possiblyunsorted = True
1644 possiblyunsorted = True
1645 else:
1645 else:
1646 if follow and len(repo) > 0:
1646 if follow and len(repo) > 0:
1647 revs = repo.revs('reverse(:.)')
1647 revs = repo.revs('reverse(:.)')
1648 else:
1648 else:
1649 revs = revset.spanset(repo)
1649 revs = revset.spanset(repo)
1650 revs.reverse()
1650 revs.reverse()
1651 if not revs:
1651 if not revs:
1652 return revset.baseset(), None, None
1652 return revset.baseset(), None, None
1653 expr, filematcher = _makegraphlogrevset(repo, pats, opts, revs)
1653 expr, filematcher = _makegraphlogrevset(repo, pats, opts, revs)
1654 if possiblyunsorted:
1654 if possiblyunsorted:
1655 revs.sort(reverse=True)
1655 revs.sort(reverse=True)
1656 if expr:
1656 if expr:
1657 # Revset matchers often operate faster on revisions in changelog
1657 # Revset matchers often operate faster on revisions in changelog
1658 # order, because most filters deal with the changelog.
1658 # order, because most filters deal with the changelog.
1659 revs.reverse()
1659 revs.reverse()
1660 matcher = revset.match(repo.ui, expr)
1660 matcher = revset.match(repo.ui, expr)
1661 # Revset matches can reorder revisions. "A or B" typically returns
1661 # Revset matches can reorder revisions. "A or B" typically returns
1662 # returns the revision matching A then the revision matching B. Sort
1662 # returns the revision matching A then the revision matching B. Sort
1663 # again to fix that.
1663 # again to fix that.
1664 revs = matcher(repo, revs)
1664 revs = matcher(repo, revs)
1665 revs.sort(reverse=True)
1665 revs.sort(reverse=True)
1666 if limit is not None:
1666 if limit is not None:
1667 limitedrevs = revset.baseset()
1667 limitedrevs = revset.baseset()
1668 for idx, rev in enumerate(revs):
1668 for idx, rev in enumerate(revs):
1669 if idx >= limit:
1669 if idx >= limit:
1670 break
1670 break
1671 limitedrevs.append(rev)
1671 limitedrevs.append(rev)
1672 revs = limitedrevs
1672 revs = limitedrevs
1673
1673
1674 return revs, expr, filematcher
1674 return revs, expr, filematcher
1675
1675
1676 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1676 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1677 filematcher=None):
1677 filematcher=None):
1678 seen, state = [], graphmod.asciistate()
1678 seen, state = [], graphmod.asciistate()
1679 for rev, type, ctx, parents in dag:
1679 for rev, type, ctx, parents in dag:
1680 char = 'o'
1680 char = 'o'
1681 if ctx.node() in showparents:
1681 if ctx.node() in showparents:
1682 char = '@'
1682 char = '@'
1683 elif ctx.obsolete():
1683 elif ctx.obsolete():
1684 char = 'x'
1684 char = 'x'
1685 copies = None
1685 copies = None
1686 if getrenamed and ctx.rev():
1686 if getrenamed and ctx.rev():
1687 copies = []
1687 copies = []
1688 for fn in ctx.files():
1688 for fn in ctx.files():
1689 rename = getrenamed(fn, ctx.rev())
1689 rename = getrenamed(fn, ctx.rev())
1690 if rename:
1690 if rename:
1691 copies.append((fn, rename[0]))
1691 copies.append((fn, rename[0]))
1692 revmatchfn = None
1692 revmatchfn = None
1693 if filematcher is not None:
1693 if filematcher is not None:
1694 revmatchfn = filematcher(ctx.rev())
1694 revmatchfn = filematcher(ctx.rev())
1695 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1695 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1696 lines = displayer.hunk.pop(rev).split('\n')
1696 lines = displayer.hunk.pop(rev).split('\n')
1697 if not lines[-1]:
1697 if not lines[-1]:
1698 del lines[-1]
1698 del lines[-1]
1699 displayer.flush(rev)
1699 displayer.flush(rev)
1700 edges = edgefn(type, char, lines, seen, rev, parents)
1700 edges = edgefn(type, char, lines, seen, rev, parents)
1701 for type, char, lines, coldata in edges:
1701 for type, char, lines, coldata in edges:
1702 graphmod.ascii(ui, state, type, char, lines, coldata)
1702 graphmod.ascii(ui, state, type, char, lines, coldata)
1703 displayer.close()
1703 displayer.close()
1704
1704
1705 def graphlog(ui, repo, *pats, **opts):
1705 def graphlog(ui, repo, *pats, **opts):
1706 # Parameters are identical to log command ones
1706 # Parameters are identical to log command ones
1707 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1707 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1708 revdag = graphmod.dagwalker(repo, revs)
1708 revdag = graphmod.dagwalker(repo, revs)
1709
1709
1710 getrenamed = None
1710 getrenamed = None
1711 if opts.get('copies'):
1711 if opts.get('copies'):
1712 endrev = None
1712 endrev = None
1713 if opts.get('rev'):
1713 if opts.get('rev'):
1714 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1714 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1715 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1715 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1716 displayer = show_changeset(ui, repo, opts, buffered=True)
1716 displayer = show_changeset(ui, repo, opts, buffered=True)
1717 showparents = [ctx.node() for ctx in repo[None].parents()]
1717 showparents = [ctx.node() for ctx in repo[None].parents()]
1718 displaygraph(ui, revdag, displayer, showparents,
1718 displaygraph(ui, revdag, displayer, showparents,
1719 graphmod.asciiedges, getrenamed, filematcher)
1719 graphmod.asciiedges, getrenamed, filematcher)
1720
1720
1721 def checkunsupportedgraphflags(pats, opts):
1721 def checkunsupportedgraphflags(pats, opts):
1722 for op in ["newest_first"]:
1722 for op in ["newest_first"]:
1723 if op in opts and opts[op]:
1723 if op in opts and opts[op]:
1724 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1724 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1725 % op.replace("_", "-"))
1725 % op.replace("_", "-"))
1726
1726
1727 def graphrevs(repo, nodes, opts):
1727 def graphrevs(repo, nodes, opts):
1728 limit = loglimit(opts)
1728 limit = loglimit(opts)
1729 nodes.reverse()
1729 nodes.reverse()
1730 if limit is not None:
1730 if limit is not None:
1731 nodes = nodes[:limit]
1731 nodes = nodes[:limit]
1732 return graphmod.nodes(repo, nodes)
1732 return graphmod.nodes(repo, nodes)
1733
1733
1734 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1734 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1735 join = lambda f: os.path.join(prefix, f)
1735 join = lambda f: os.path.join(prefix, f)
1736 bad = []
1736 bad = []
1737 oldbad = match.bad
1737 oldbad = match.bad
1738 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1738 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1739 names = []
1739 names = []
1740 wctx = repo[None]
1740 wctx = repo[None]
1741 cca = None
1741 cca = None
1742 abort, warn = scmutil.checkportabilityalert(ui)
1742 abort, warn = scmutil.checkportabilityalert(ui)
1743 if abort or warn:
1743 if abort or warn:
1744 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1744 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1745 for f in repo.walk(match):
1745 for f in repo.walk(match):
1746 exact = match.exact(f)
1746 exact = match.exact(f)
1747 if exact or not explicitonly and f not in repo.dirstate:
1747 if exact or not explicitonly and f not in repo.dirstate:
1748 if cca:
1748 if cca:
1749 cca(f)
1749 cca(f)
1750 names.append(f)
1750 names.append(f)
1751 if ui.verbose or not exact:
1751 if ui.verbose or not exact:
1752 ui.status(_('adding %s\n') % match.rel(join(f)))
1752 ui.status(_('adding %s\n') % match.rel(join(f)))
1753
1753
1754 for subpath in sorted(wctx.substate):
1754 for subpath in sorted(wctx.substate):
1755 sub = wctx.sub(subpath)
1755 sub = wctx.sub(subpath)
1756 try:
1756 try:
1757 submatch = matchmod.narrowmatcher(subpath, match)
1757 submatch = matchmod.narrowmatcher(subpath, match)
1758 if listsubrepos:
1758 if listsubrepos:
1759 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1759 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1760 False))
1760 False))
1761 else:
1761 else:
1762 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1762 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1763 True))
1763 True))
1764 except error.LookupError:
1764 except error.LookupError:
1765 ui.status(_("skipping missing subrepository: %s\n")
1765 ui.status(_("skipping missing subrepository: %s\n")
1766 % join(subpath))
1766 % join(subpath))
1767
1767
1768 if not dryrun:
1768 if not dryrun:
1769 rejected = wctx.add(names, prefix)
1769 rejected = wctx.add(names, prefix)
1770 bad.extend(f for f in rejected if f in match.files())
1770 bad.extend(f for f in rejected if f in match.files())
1771 return bad
1771 return bad
1772
1772
1773 def forget(ui, repo, match, prefix, explicitonly):
1773 def forget(ui, repo, match, prefix, explicitonly):
1774 join = lambda f: os.path.join(prefix, f)
1774 join = lambda f: os.path.join(prefix, f)
1775 bad = []
1775 bad = []
1776 oldbad = match.bad
1776 oldbad = match.bad
1777 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1777 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1778 wctx = repo[None]
1778 wctx = repo[None]
1779 forgot = []
1779 forgot = []
1780 s = repo.status(match=match, clean=True)
1780 s = repo.status(match=match, clean=True)
1781 forget = sorted(s[0] + s[1] + s[3] + s[6])
1781 forget = sorted(s[0] + s[1] + s[3] + s[6])
1782 if explicitonly:
1782 if explicitonly:
1783 forget = [f for f in forget if match.exact(f)]
1783 forget = [f for f in forget if match.exact(f)]
1784
1784
1785 for subpath in sorted(wctx.substate):
1785 for subpath in sorted(wctx.substate):
1786 sub = wctx.sub(subpath)
1786 sub = wctx.sub(subpath)
1787 try:
1787 try:
1788 submatch = matchmod.narrowmatcher(subpath, match)
1788 submatch = matchmod.narrowmatcher(subpath, match)
1789 subbad, subforgot = sub.forget(ui, submatch, prefix)
1789 subbad, subforgot = sub.forget(ui, submatch, prefix)
1790 bad.extend([subpath + '/' + f for f in subbad])
1790 bad.extend([subpath + '/' + f for f in subbad])
1791 forgot.extend([subpath + '/' + f for f in subforgot])
1791 forgot.extend([subpath + '/' + f for f in subforgot])
1792 except error.LookupError:
1792 except error.LookupError:
1793 ui.status(_("skipping missing subrepository: %s\n")
1793 ui.status(_("skipping missing subrepository: %s\n")
1794 % join(subpath))
1794 % join(subpath))
1795
1795
1796 if not explicitonly:
1796 if not explicitonly:
1797 for f in match.files():
1797 for f in match.files():
1798 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1798 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1799 if f not in forgot:
1799 if f not in forgot:
1800 if os.path.exists(match.rel(join(f))):
1800 if os.path.exists(match.rel(join(f))):
1801 ui.warn(_('not removing %s: '
1801 ui.warn(_('not removing %s: '
1802 'file is already untracked\n')
1802 'file is already untracked\n')
1803 % match.rel(join(f)))
1803 % match.rel(join(f)))
1804 bad.append(f)
1804 bad.append(f)
1805
1805
1806 for f in forget:
1806 for f in forget:
1807 if ui.verbose or not match.exact(f):
1807 if ui.verbose or not match.exact(f):
1808 ui.status(_('removing %s\n') % match.rel(join(f)))
1808 ui.status(_('removing %s\n') % match.rel(join(f)))
1809
1809
1810 rejected = wctx.forget(forget, prefix)
1810 rejected = wctx.forget(forget, prefix)
1811 bad.extend(f for f in rejected if f in match.files())
1811 bad.extend(f for f in rejected if f in match.files())
1812 forgot.extend(forget)
1812 forgot.extend(forget)
1813 return bad, forgot
1813 return bad, forgot
1814
1814
1815 def duplicatecopies(repo, rev, fromrev):
1815 def duplicatecopies(repo, rev, fromrev):
1816 '''reproduce copies from fromrev to rev in the dirstate'''
1816 '''reproduce copies from fromrev to rev in the dirstate'''
1817 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
1817 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
1818 # copies.pathcopies returns backward renames, so dst might not
1818 # copies.pathcopies returns backward renames, so dst might not
1819 # actually be in the dirstate
1819 # actually be in the dirstate
1820 if repo.dirstate[dst] in "nma":
1820 if repo.dirstate[dst] in "nma":
1821 repo.dirstate.copy(src, dst)
1821 repo.dirstate.copy(src, dst)
1822
1822
1823 def commit(ui, repo, commitfunc, pats, opts):
1823 def commit(ui, repo, commitfunc, pats, opts):
1824 '''commit the specified files or all outstanding changes'''
1824 '''commit the specified files or all outstanding changes'''
1825 date = opts.get('date')
1825 date = opts.get('date')
1826 if date:
1826 if date:
1827 opts['date'] = util.parsedate(date)
1827 opts['date'] = util.parsedate(date)
1828 message = logmessage(ui, opts)
1828 message = logmessage(ui, opts)
1829
1829
1830 # extract addremove carefully -- this function can be called from a command
1830 # extract addremove carefully -- this function can be called from a command
1831 # that doesn't support addremove
1831 # that doesn't support addremove
1832 if opts.get('addremove'):
1832 if opts.get('addremove'):
1833 scmutil.addremove(repo, pats, opts)
1833 scmutil.addremove(repo, pats, opts)
1834
1834
1835 return commitfunc(ui, repo, message,
1835 return commitfunc(ui, repo, message,
1836 scmutil.match(repo[None], pats, opts), opts)
1836 scmutil.match(repo[None], pats, opts), opts)
1837
1837
1838 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1838 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1839 ui.note(_('amending changeset %s\n') % old)
1839 ui.note(_('amending changeset %s\n') % old)
1840 base = old.p1()
1840 base = old.p1()
1841
1841
1842 wlock = lock = newid = None
1842 wlock = lock = newid = None
1843 try:
1843 try:
1844 wlock = repo.wlock()
1844 wlock = repo.wlock()
1845 lock = repo.lock()
1845 lock = repo.lock()
1846 tr = repo.transaction('amend')
1846 tr = repo.transaction('amend')
1847 try:
1847 try:
1848 # See if we got a message from -m or -l, if not, open the editor
1848 # See if we got a message from -m or -l, if not, open the editor
1849 # with the message of the changeset to amend
1849 # with the message of the changeset to amend
1850 message = logmessage(ui, opts)
1850 message = logmessage(ui, opts)
1851 # ensure logfile does not conflict with later enforcement of the
1851 # ensure logfile does not conflict with later enforcement of the
1852 # message. potential logfile content has been processed by
1852 # message. potential logfile content has been processed by
1853 # `logmessage` anyway.
1853 # `logmessage` anyway.
1854 opts.pop('logfile')
1854 opts.pop('logfile')
1855 # First, do a regular commit to record all changes in the working
1855 # First, do a regular commit to record all changes in the working
1856 # directory (if there are any)
1856 # directory (if there are any)
1857 ui.callhooks = False
1857 ui.callhooks = False
1858 currentbookmark = repo._bookmarkcurrent
1858 currentbookmark = repo._bookmarkcurrent
1859 try:
1859 try:
1860 repo._bookmarkcurrent = None
1860 repo._bookmarkcurrent = None
1861 opts['message'] = 'temporary amend commit for %s' % old
1861 opts['message'] = 'temporary amend commit for %s' % old
1862 node = commit(ui, repo, commitfunc, pats, opts)
1862 node = commit(ui, repo, commitfunc, pats, opts)
1863 finally:
1863 finally:
1864 repo._bookmarkcurrent = currentbookmark
1864 repo._bookmarkcurrent = currentbookmark
1865 ui.callhooks = True
1865 ui.callhooks = True
1866 ctx = repo[node]
1866 ctx = repo[node]
1867
1867
1868 # Participating changesets:
1868 # Participating changesets:
1869 #
1869 #
1870 # node/ctx o - new (intermediate) commit that contains changes
1870 # node/ctx o - new (intermediate) commit that contains changes
1871 # | from working dir to go into amending commit
1871 # | from working dir to go into amending commit
1872 # | (or a workingctx if there were no changes)
1872 # | (or a workingctx if there were no changes)
1873 # |
1873 # |
1874 # old o - changeset to amend
1874 # old o - changeset to amend
1875 # |
1875 # |
1876 # base o - parent of amending changeset
1876 # base o - parent of amending changeset
1877
1877
1878 # Update extra dict from amended commit (e.g. to preserve graft
1878 # Update extra dict from amended commit (e.g. to preserve graft
1879 # source)
1879 # source)
1880 extra.update(old.extra())
1880 extra.update(old.extra())
1881
1881
1882 # Also update it from the intermediate commit or from the wctx
1882 # Also update it from the intermediate commit or from the wctx
1883 extra.update(ctx.extra())
1883 extra.update(ctx.extra())
1884
1884
1885 if len(old.parents()) > 1:
1885 if len(old.parents()) > 1:
1886 # ctx.files() isn't reliable for merges, so fall back to the
1886 # ctx.files() isn't reliable for merges, so fall back to the
1887 # slower repo.status() method
1887 # slower repo.status() method
1888 files = set([fn for st in repo.status(base, old)[:3]
1888 files = set([fn for st in repo.status(base, old)[:3]
1889 for fn in st])
1889 for fn in st])
1890 else:
1890 else:
1891 files = set(old.files())
1891 files = set(old.files())
1892
1892
1893 # Second, we use either the commit we just did, or if there were no
1893 # Second, we use either the commit we just did, or if there were no
1894 # changes the parent of the working directory as the version of the
1894 # changes the parent of the working directory as the version of the
1895 # files in the final amend commit
1895 # files in the final amend commit
1896 if node:
1896 if node:
1897 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
1897 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
1898
1898
1899 user = ctx.user()
1899 user = ctx.user()
1900 date = ctx.date()
1900 date = ctx.date()
1901 # Recompute copies (avoid recording a -> b -> a)
1901 # Recompute copies (avoid recording a -> b -> a)
1902 copied = copies.pathcopies(base, ctx)
1902 copied = copies.pathcopies(base, ctx)
1903
1903
1904 # Prune files which were reverted by the updates: if old
1904 # Prune files which were reverted by the updates: if old
1905 # introduced file X and our intermediate commit, node,
1905 # introduced file X and our intermediate commit, node,
1906 # renamed that file, then those two files are the same and
1906 # renamed that file, then those two files are the same and
1907 # we can discard X from our list of files. Likewise if X
1907 # we can discard X from our list of files. Likewise if X
1908 # was deleted, it's no longer relevant
1908 # was deleted, it's no longer relevant
1909 files.update(ctx.files())
1909 files.update(ctx.files())
1910
1910
1911 def samefile(f):
1911 def samefile(f):
1912 if f in ctx.manifest():
1912 if f in ctx.manifest():
1913 a = ctx.filectx(f)
1913 a = ctx.filectx(f)
1914 if f in base.manifest():
1914 if f in base.manifest():
1915 b = base.filectx(f)
1915 b = base.filectx(f)
1916 return (not a.cmp(b)
1916 return (not a.cmp(b)
1917 and a.flags() == b.flags())
1917 and a.flags() == b.flags())
1918 else:
1918 else:
1919 return False
1919 return False
1920 else:
1920 else:
1921 return f not in base.manifest()
1921 return f not in base.manifest()
1922 files = [f for f in files if not samefile(f)]
1922 files = [f for f in files if not samefile(f)]
1923
1923
1924 def filectxfn(repo, ctx_, path):
1924 def filectxfn(repo, ctx_, path):
1925 try:
1925 try:
1926 fctx = ctx[path]
1926 fctx = ctx[path]
1927 flags = fctx.flags()
1927 flags = fctx.flags()
1928 mctx = context.memfilectx(fctx.path(), fctx.data(),
1928 mctx = context.memfilectx(fctx.path(), fctx.data(),
1929 islink='l' in flags,
1929 islink='l' in flags,
1930 isexec='x' in flags,
1930 isexec='x' in flags,
1931 copied=copied.get(path))
1931 copied=copied.get(path))
1932 return mctx
1932 return mctx
1933 except KeyError:
1933 except KeyError:
1934 raise IOError
1934 raise IOError
1935 else:
1935 else:
1936 ui.note(_('copying changeset %s to %s\n') % (old, base))
1936 ui.note(_('copying changeset %s to %s\n') % (old, base))
1937
1937
1938 # Use version of files as in the old cset
1938 # Use version of files as in the old cset
1939 def filectxfn(repo, ctx_, path):
1939 def filectxfn(repo, ctx_, path):
1940 try:
1940 try:
1941 return old.filectx(path)
1941 return old.filectx(path)
1942 except KeyError:
1942 except KeyError:
1943 raise IOError
1943 raise IOError
1944
1944
1945 user = opts.get('user') or old.user()
1945 user = opts.get('user') or old.user()
1946 date = opts.get('date') or old.date()
1946 date = opts.get('date') or old.date()
1947 editmsg = False
1947 editmsg = False
1948 if not message:
1948 if not message:
1949 editmsg = True
1949 editmsg = True
1950 message = old.description()
1950 message = old.description()
1951
1951
1952 pureextra = extra.copy()
1952 pureextra = extra.copy()
1953 extra['amend_source'] = old.hex()
1953 extra['amend_source'] = old.hex()
1954
1954
1955 new = context.memctx(repo,
1955 new = context.memctx(repo,
1956 parents=[base.node(), old.p2().node()],
1956 parents=[base.node(), old.p2().node()],
1957 text=message,
1957 text=message,
1958 files=files,
1958 files=files,
1959 filectxfn=filectxfn,
1959 filectxfn=filectxfn,
1960 user=user,
1960 user=user,
1961 date=date,
1961 date=date,
1962 extra=extra)
1962 extra=extra)
1963 if editmsg:
1963 if editmsg:
1964 new._text = commitforceeditor(repo, new, [])
1964 new._text = commitforceeditor(repo, new, [])
1965 repo.savecommitmessage(new.description())
1965
1966
1966 newdesc = changelog.stripdesc(new.description())
1967 newdesc = changelog.stripdesc(new.description())
1967 if ((not node)
1968 if ((not node)
1968 and newdesc == old.description()
1969 and newdesc == old.description()
1969 and user == old.user()
1970 and user == old.user()
1970 and date == old.date()
1971 and date == old.date()
1971 and pureextra == old.extra()):
1972 and pureextra == old.extra()):
1972 # nothing changed. continuing here would create a new node
1973 # nothing changed. continuing here would create a new node
1973 # anyway because of the amend_source noise.
1974 # anyway because of the amend_source noise.
1974 #
1975 #
1975 # This not what we expect from amend.
1976 # This not what we expect from amend.
1976 return old.node()
1977 return old.node()
1977
1978
1978 ph = repo.ui.config('phases', 'new-commit', phases.draft)
1979 ph = repo.ui.config('phases', 'new-commit', phases.draft)
1979 try:
1980 try:
1980 if opts.get('secret'):
1981 if opts.get('secret'):
1981 commitphase = 'secret'
1982 commitphase = 'secret'
1982 else:
1983 else:
1983 commitphase = old.phase()
1984 commitphase = old.phase()
1984 repo.ui.setconfig('phases', 'new-commit', commitphase)
1985 repo.ui.setconfig('phases', 'new-commit', commitphase)
1985 newid = repo.commitctx(new)
1986 newid = repo.commitctx(new)
1986 finally:
1987 finally:
1987 repo.ui.setconfig('phases', 'new-commit', ph)
1988 repo.ui.setconfig('phases', 'new-commit', ph)
1988 if newid != old.node():
1989 if newid != old.node():
1989 # Reroute the working copy parent to the new changeset
1990 # Reroute the working copy parent to the new changeset
1990 repo.setparents(newid, nullid)
1991 repo.setparents(newid, nullid)
1991
1992
1992 # Move bookmarks from old parent to amend commit
1993 # Move bookmarks from old parent to amend commit
1993 bms = repo.nodebookmarks(old.node())
1994 bms = repo.nodebookmarks(old.node())
1994 if bms:
1995 if bms:
1995 marks = repo._bookmarks
1996 marks = repo._bookmarks
1996 for bm in bms:
1997 for bm in bms:
1997 marks[bm] = newid
1998 marks[bm] = newid
1998 marks.write()
1999 marks.write()
1999 #commit the whole amend process
2000 #commit the whole amend process
2000 if obsolete._enabled and newid != old.node():
2001 if obsolete._enabled and newid != old.node():
2001 # mark the new changeset as successor of the rewritten one
2002 # mark the new changeset as successor of the rewritten one
2002 new = repo[newid]
2003 new = repo[newid]
2003 obs = [(old, (new,))]
2004 obs = [(old, (new,))]
2004 if node:
2005 if node:
2005 obs.append((ctx, ()))
2006 obs.append((ctx, ()))
2006
2007
2007 obsolete.createmarkers(repo, obs)
2008 obsolete.createmarkers(repo, obs)
2008 tr.close()
2009 tr.close()
2009 finally:
2010 finally:
2010 tr.release()
2011 tr.release()
2011 if (not obsolete._enabled) and newid != old.node():
2012 if (not obsolete._enabled) and newid != old.node():
2012 # Strip the intermediate commit (if there was one) and the amended
2013 # Strip the intermediate commit (if there was one) and the amended
2013 # commit
2014 # commit
2014 if node:
2015 if node:
2015 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2016 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2016 ui.note(_('stripping amended changeset %s\n') % old)
2017 ui.note(_('stripping amended changeset %s\n') % old)
2017 repair.strip(ui, repo, old.node(), topic='amend-backup')
2018 repair.strip(ui, repo, old.node(), topic='amend-backup')
2018 finally:
2019 finally:
2019 if newid is None:
2020 if newid is None:
2020 repo.dirstate.invalidate()
2021 repo.dirstate.invalidate()
2021 lockmod.release(lock, wlock)
2022 lockmod.release(lock, wlock)
2022 return newid
2023 return newid
2023
2024
2024 def commiteditor(repo, ctx, subs):
2025 def commiteditor(repo, ctx, subs):
2025 if ctx.description():
2026 if ctx.description():
2026 return ctx.description()
2027 return ctx.description()
2027 return commitforceeditor(repo, ctx, subs)
2028 return commitforceeditor(repo, ctx, subs)
2028
2029
2029 def commitforceeditor(repo, ctx, subs):
2030 def commitforceeditor(repo, ctx, subs):
2030 edittext = []
2031 edittext = []
2031 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2032 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2032 if ctx.description():
2033 if ctx.description():
2033 edittext.append(ctx.description())
2034 edittext.append(ctx.description())
2034 edittext.append("")
2035 edittext.append("")
2035 edittext.append("") # Empty line between message and comments.
2036 edittext.append("") # Empty line between message and comments.
2036 edittext.append(_("HG: Enter commit message."
2037 edittext.append(_("HG: Enter commit message."
2037 " Lines beginning with 'HG:' are removed."))
2038 " Lines beginning with 'HG:' are removed."))
2038 edittext.append(_("HG: Leave message empty to abort commit."))
2039 edittext.append(_("HG: Leave message empty to abort commit."))
2039 edittext.append("HG: --")
2040 edittext.append("HG: --")
2040 edittext.append(_("HG: user: %s") % ctx.user())
2041 edittext.append(_("HG: user: %s") % ctx.user())
2041 if ctx.p2():
2042 if ctx.p2():
2042 edittext.append(_("HG: branch merge"))
2043 edittext.append(_("HG: branch merge"))
2043 if ctx.branch():
2044 if ctx.branch():
2044 edittext.append(_("HG: branch '%s'") % ctx.branch())
2045 edittext.append(_("HG: branch '%s'") % ctx.branch())
2045 if bookmarks.iscurrent(repo):
2046 if bookmarks.iscurrent(repo):
2046 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2047 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2047 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2048 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2048 edittext.extend([_("HG: added %s") % f for f in added])
2049 edittext.extend([_("HG: added %s") % f for f in added])
2049 edittext.extend([_("HG: changed %s") % f for f in modified])
2050 edittext.extend([_("HG: changed %s") % f for f in modified])
2050 edittext.extend([_("HG: removed %s") % f for f in removed])
2051 edittext.extend([_("HG: removed %s") % f for f in removed])
2051 if not added and not modified and not removed:
2052 if not added and not modified and not removed:
2052 edittext.append(_("HG: no files changed"))
2053 edittext.append(_("HG: no files changed"))
2053 edittext.append("")
2054 edittext.append("")
2054 # run editor in the repository root
2055 # run editor in the repository root
2055 olddir = os.getcwd()
2056 olddir = os.getcwd()
2056 os.chdir(repo.root)
2057 os.chdir(repo.root)
2057 text = repo.ui.edit("\n".join(edittext), ctx.user(), ctx.extra())
2058 text = repo.ui.edit("\n".join(edittext), ctx.user(), ctx.extra())
2058 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2059 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2059 os.chdir(olddir)
2060 os.chdir(olddir)
2060
2061
2061 if not text.strip():
2062 if not text.strip():
2062 raise util.Abort(_("empty commit message"))
2063 raise util.Abort(_("empty commit message"))
2063
2064
2064 return text
2065 return text
2065
2066
2066 def commitstatus(repo, node, branch, bheads=None, opts={}):
2067 def commitstatus(repo, node, branch, bheads=None, opts={}):
2067 ctx = repo[node]
2068 ctx = repo[node]
2068 parents = ctx.parents()
2069 parents = ctx.parents()
2069
2070
2070 if (not opts.get('amend') and bheads and node not in bheads and not
2071 if (not opts.get('amend') and bheads and node not in bheads and not
2071 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2072 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2072 repo.ui.status(_('created new head\n'))
2073 repo.ui.status(_('created new head\n'))
2073 # The message is not printed for initial roots. For the other
2074 # The message is not printed for initial roots. For the other
2074 # changesets, it is printed in the following situations:
2075 # changesets, it is printed in the following situations:
2075 #
2076 #
2076 # Par column: for the 2 parents with ...
2077 # Par column: for the 2 parents with ...
2077 # N: null or no parent
2078 # N: null or no parent
2078 # B: parent is on another named branch
2079 # B: parent is on another named branch
2079 # C: parent is a regular non head changeset
2080 # C: parent is a regular non head changeset
2080 # H: parent was a branch head of the current branch
2081 # H: parent was a branch head of the current branch
2081 # Msg column: whether we print "created new head" message
2082 # Msg column: whether we print "created new head" message
2082 # In the following, it is assumed that there already exists some
2083 # In the following, it is assumed that there already exists some
2083 # initial branch heads of the current branch, otherwise nothing is
2084 # initial branch heads of the current branch, otherwise nothing is
2084 # printed anyway.
2085 # printed anyway.
2085 #
2086 #
2086 # Par Msg Comment
2087 # Par Msg Comment
2087 # N N y additional topo root
2088 # N N y additional topo root
2088 #
2089 #
2089 # B N y additional branch root
2090 # B N y additional branch root
2090 # C N y additional topo head
2091 # C N y additional topo head
2091 # H N n usual case
2092 # H N n usual case
2092 #
2093 #
2093 # B B y weird additional branch root
2094 # B B y weird additional branch root
2094 # C B y branch merge
2095 # C B y branch merge
2095 # H B n merge with named branch
2096 # H B n merge with named branch
2096 #
2097 #
2097 # C C y additional head from merge
2098 # C C y additional head from merge
2098 # C H n merge with a head
2099 # C H n merge with a head
2099 #
2100 #
2100 # H H n head merge: head count decreases
2101 # H H n head merge: head count decreases
2101
2102
2102 if not opts.get('close_branch'):
2103 if not opts.get('close_branch'):
2103 for r in parents:
2104 for r in parents:
2104 if r.closesbranch() and r.branch() == branch:
2105 if r.closesbranch() and r.branch() == branch:
2105 repo.ui.status(_('reopening closed branch head %d\n') % r)
2106 repo.ui.status(_('reopening closed branch head %d\n') % r)
2106
2107
2107 if repo.ui.debugflag:
2108 if repo.ui.debugflag:
2108 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2109 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2109 elif repo.ui.verbose:
2110 elif repo.ui.verbose:
2110 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2111 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2111
2112
2112 def revert(ui, repo, ctx, parents, *pats, **opts):
2113 def revert(ui, repo, ctx, parents, *pats, **opts):
2113 parent, p2 = parents
2114 parent, p2 = parents
2114 node = ctx.node()
2115 node = ctx.node()
2115
2116
2116 mf = ctx.manifest()
2117 mf = ctx.manifest()
2117 if node == parent:
2118 if node == parent:
2118 pmf = mf
2119 pmf = mf
2119 else:
2120 else:
2120 pmf = None
2121 pmf = None
2121
2122
2122 # need all matching names in dirstate and manifest of target rev,
2123 # need all matching names in dirstate and manifest of target rev,
2123 # so have to walk both. do not print errors if files exist in one
2124 # so have to walk both. do not print errors if files exist in one
2124 # but not other.
2125 # but not other.
2125
2126
2126 names = {}
2127 names = {}
2127
2128
2128 wlock = repo.wlock()
2129 wlock = repo.wlock()
2129 try:
2130 try:
2130 # walk dirstate.
2131 # walk dirstate.
2131
2132
2132 m = scmutil.match(repo[None], pats, opts)
2133 m = scmutil.match(repo[None], pats, opts)
2133 m.bad = lambda x, y: False
2134 m.bad = lambda x, y: False
2134 for abs in repo.walk(m):
2135 for abs in repo.walk(m):
2135 names[abs] = m.rel(abs), m.exact(abs)
2136 names[abs] = m.rel(abs), m.exact(abs)
2136
2137
2137 # walk target manifest.
2138 # walk target manifest.
2138
2139
2139 def badfn(path, msg):
2140 def badfn(path, msg):
2140 if path in names:
2141 if path in names:
2141 return
2142 return
2142 if path in ctx.substate:
2143 if path in ctx.substate:
2143 return
2144 return
2144 path_ = path + '/'
2145 path_ = path + '/'
2145 for f in names:
2146 for f in names:
2146 if f.startswith(path_):
2147 if f.startswith(path_):
2147 return
2148 return
2148 ui.warn("%s: %s\n" % (m.rel(path), msg))
2149 ui.warn("%s: %s\n" % (m.rel(path), msg))
2149
2150
2150 m = scmutil.match(ctx, pats, opts)
2151 m = scmutil.match(ctx, pats, opts)
2151 m.bad = badfn
2152 m.bad = badfn
2152 for abs in ctx.walk(m):
2153 for abs in ctx.walk(m):
2153 if abs not in names:
2154 if abs not in names:
2154 names[abs] = m.rel(abs), m.exact(abs)
2155 names[abs] = m.rel(abs), m.exact(abs)
2155
2156
2156 # get the list of subrepos that must be reverted
2157 # get the list of subrepos that must be reverted
2157 targetsubs = sorted(s for s in ctx.substate if m(s))
2158 targetsubs = sorted(s for s in ctx.substate if m(s))
2158 m = scmutil.matchfiles(repo, names)
2159 m = scmutil.matchfiles(repo, names)
2159 changes = repo.status(match=m)[:4]
2160 changes = repo.status(match=m)[:4]
2160 modified, added, removed, deleted = map(set, changes)
2161 modified, added, removed, deleted = map(set, changes)
2161
2162
2162 # if f is a rename, also revert the source
2163 # if f is a rename, also revert the source
2163 cwd = repo.getcwd()
2164 cwd = repo.getcwd()
2164 for f in added:
2165 for f in added:
2165 src = repo.dirstate.copied(f)
2166 src = repo.dirstate.copied(f)
2166 if src and src not in names and repo.dirstate[src] == 'r':
2167 if src and src not in names and repo.dirstate[src] == 'r':
2167 removed.add(src)
2168 removed.add(src)
2168 names[src] = (repo.pathto(src, cwd), True)
2169 names[src] = (repo.pathto(src, cwd), True)
2169
2170
2170 def removeforget(abs):
2171 def removeforget(abs):
2171 if repo.dirstate[abs] == 'a':
2172 if repo.dirstate[abs] == 'a':
2172 return _('forgetting %s\n')
2173 return _('forgetting %s\n')
2173 return _('removing %s\n')
2174 return _('removing %s\n')
2174
2175
2175 revert = ([], _('reverting %s\n'))
2176 revert = ([], _('reverting %s\n'))
2176 add = ([], _('adding %s\n'))
2177 add = ([], _('adding %s\n'))
2177 remove = ([], removeforget)
2178 remove = ([], removeforget)
2178 undelete = ([], _('undeleting %s\n'))
2179 undelete = ([], _('undeleting %s\n'))
2179
2180
2180 disptable = (
2181 disptable = (
2181 # dispatch table:
2182 # dispatch table:
2182 # file state
2183 # file state
2183 # action if in target manifest
2184 # action if in target manifest
2184 # action if not in target manifest
2185 # action if not in target manifest
2185 # make backup if in target manifest
2186 # make backup if in target manifest
2186 # make backup if not in target manifest
2187 # make backup if not in target manifest
2187 (modified, revert, remove, True, True),
2188 (modified, revert, remove, True, True),
2188 (added, revert, remove, True, False),
2189 (added, revert, remove, True, False),
2189 (removed, undelete, None, True, False),
2190 (removed, undelete, None, True, False),
2190 (deleted, revert, remove, False, False),
2191 (deleted, revert, remove, False, False),
2191 )
2192 )
2192
2193
2193 for abs, (rel, exact) in sorted(names.items()):
2194 for abs, (rel, exact) in sorted(names.items()):
2194 mfentry = mf.get(abs)
2195 mfentry = mf.get(abs)
2195 target = repo.wjoin(abs)
2196 target = repo.wjoin(abs)
2196 def handle(xlist, dobackup):
2197 def handle(xlist, dobackup):
2197 xlist[0].append(abs)
2198 xlist[0].append(abs)
2198 if (dobackup and not opts.get('no_backup') and
2199 if (dobackup and not opts.get('no_backup') and
2199 os.path.lexists(target) and
2200 os.path.lexists(target) and
2200 abs in ctx and repo[None][abs].cmp(ctx[abs])):
2201 abs in ctx and repo[None][abs].cmp(ctx[abs])):
2201 bakname = "%s.orig" % rel
2202 bakname = "%s.orig" % rel
2202 ui.note(_('saving current version of %s as %s\n') %
2203 ui.note(_('saving current version of %s as %s\n') %
2203 (rel, bakname))
2204 (rel, bakname))
2204 if not opts.get('dry_run'):
2205 if not opts.get('dry_run'):
2205 util.rename(target, bakname)
2206 util.rename(target, bakname)
2206 if ui.verbose or not exact:
2207 if ui.verbose or not exact:
2207 msg = xlist[1]
2208 msg = xlist[1]
2208 if not isinstance(msg, basestring):
2209 if not isinstance(msg, basestring):
2209 msg = msg(abs)
2210 msg = msg(abs)
2210 ui.status(msg % rel)
2211 ui.status(msg % rel)
2211 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2212 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2212 if abs not in table:
2213 if abs not in table:
2213 continue
2214 continue
2214 # file has changed in dirstate
2215 # file has changed in dirstate
2215 if mfentry:
2216 if mfentry:
2216 handle(hitlist, backuphit)
2217 handle(hitlist, backuphit)
2217 elif misslist is not None:
2218 elif misslist is not None:
2218 handle(misslist, backupmiss)
2219 handle(misslist, backupmiss)
2219 break
2220 break
2220 else:
2221 else:
2221 if abs not in repo.dirstate:
2222 if abs not in repo.dirstate:
2222 if mfentry:
2223 if mfentry:
2223 handle(add, True)
2224 handle(add, True)
2224 elif exact:
2225 elif exact:
2225 ui.warn(_('file not managed: %s\n') % rel)
2226 ui.warn(_('file not managed: %s\n') % rel)
2226 continue
2227 continue
2227 # file has not changed in dirstate
2228 # file has not changed in dirstate
2228 if node == parent:
2229 if node == parent:
2229 if exact:
2230 if exact:
2230 ui.warn(_('no changes needed to %s\n') % rel)
2231 ui.warn(_('no changes needed to %s\n') % rel)
2231 continue
2232 continue
2232 if pmf is None:
2233 if pmf is None:
2233 # only need parent manifest in this unlikely case,
2234 # only need parent manifest in this unlikely case,
2234 # so do not read by default
2235 # so do not read by default
2235 pmf = repo[parent].manifest()
2236 pmf = repo[parent].manifest()
2236 if abs in pmf and mfentry:
2237 if abs in pmf and mfentry:
2237 # if version of file is same in parent and target
2238 # if version of file is same in parent and target
2238 # manifests, do nothing
2239 # manifests, do nothing
2239 if (pmf[abs] != mfentry or
2240 if (pmf[abs] != mfentry or
2240 pmf.flags(abs) != mf.flags(abs)):
2241 pmf.flags(abs) != mf.flags(abs)):
2241 handle(revert, False)
2242 handle(revert, False)
2242 else:
2243 else:
2243 handle(remove, False)
2244 handle(remove, False)
2244 if not opts.get('dry_run'):
2245 if not opts.get('dry_run'):
2245 _performrevert(repo, parents, ctx, revert, add, remove, undelete)
2246 _performrevert(repo, parents, ctx, revert, add, remove, undelete)
2246
2247
2247 if targetsubs:
2248 if targetsubs:
2248 # Revert the subrepos on the revert list
2249 # Revert the subrepos on the revert list
2249 for sub in targetsubs:
2250 for sub in targetsubs:
2250 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2251 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2251 finally:
2252 finally:
2252 wlock.release()
2253 wlock.release()
2253
2254
2254 def _performrevert(repo, parents, ctx, revert, add, remove, undelete):
2255 def _performrevert(repo, parents, ctx, revert, add, remove, undelete):
2255 """function that actually perform all the action computed for revert
2256 """function that actually perform all the action computed for revert
2256
2257
2257 This is an independent function to let extension to plug in and react to
2258 This is an independent function to let extension to plug in and react to
2258 the imminent revert.
2259 the imminent revert.
2259
2260
2260 Make sure you have the working directory locked when caling this function.
2261 Make sure you have the working directory locked when caling this function.
2261 """
2262 """
2262 parent, p2 = parents
2263 parent, p2 = parents
2263 node = ctx.node()
2264 node = ctx.node()
2264 def checkout(f):
2265 def checkout(f):
2265 fc = ctx[f]
2266 fc = ctx[f]
2266 repo.wwrite(f, fc.data(), fc.flags())
2267 repo.wwrite(f, fc.data(), fc.flags())
2267
2268
2268 audit_path = pathutil.pathauditor(repo.root)
2269 audit_path = pathutil.pathauditor(repo.root)
2269 for f in remove[0]:
2270 for f in remove[0]:
2270 if repo.dirstate[f] == 'a':
2271 if repo.dirstate[f] == 'a':
2271 repo.dirstate.drop(f)
2272 repo.dirstate.drop(f)
2272 continue
2273 continue
2273 audit_path(f)
2274 audit_path(f)
2274 try:
2275 try:
2275 util.unlinkpath(repo.wjoin(f))
2276 util.unlinkpath(repo.wjoin(f))
2276 except OSError:
2277 except OSError:
2277 pass
2278 pass
2278 repo.dirstate.remove(f)
2279 repo.dirstate.remove(f)
2279
2280
2280 normal = None
2281 normal = None
2281 if node == parent:
2282 if node == parent:
2282 # We're reverting to our parent. If possible, we'd like status
2283 # We're reverting to our parent. If possible, we'd like status
2283 # to report the file as clean. We have to use normallookup for
2284 # to report the file as clean. We have to use normallookup for
2284 # merges to avoid losing information about merged/dirty files.
2285 # merges to avoid losing information about merged/dirty files.
2285 if p2 != nullid:
2286 if p2 != nullid:
2286 normal = repo.dirstate.normallookup
2287 normal = repo.dirstate.normallookup
2287 else:
2288 else:
2288 normal = repo.dirstate.normal
2289 normal = repo.dirstate.normal
2289 for f in revert[0]:
2290 for f in revert[0]:
2290 checkout(f)
2291 checkout(f)
2291 if normal:
2292 if normal:
2292 normal(f)
2293 normal(f)
2293
2294
2294 for f in add[0]:
2295 for f in add[0]:
2295 checkout(f)
2296 checkout(f)
2296 repo.dirstate.add(f)
2297 repo.dirstate.add(f)
2297
2298
2298 normal = repo.dirstate.normallookup
2299 normal = repo.dirstate.normallookup
2299 if node == parent and p2 == nullid:
2300 if node == parent and p2 == nullid:
2300 normal = repo.dirstate.normal
2301 normal = repo.dirstate.normal
2301 for f in undelete[0]:
2302 for f in undelete[0]:
2302 checkout(f)
2303 checkout(f)
2303 normal(f)
2304 normal(f)
2304
2305
2305 copied = copies.pathcopies(repo[parent], ctx)
2306 copied = copies.pathcopies(repo[parent], ctx)
2306
2307
2307 for f in add[0] + undelete[0] + revert[0]:
2308 for f in add[0] + undelete[0] + revert[0]:
2308 if f in copied:
2309 if f in copied:
2309 repo.dirstate.copy(copied[f], f)
2310 repo.dirstate.copy(copied[f], f)
2310
2311
2311 def command(table):
2312 def command(table):
2312 '''returns a function object bound to table which can be used as
2313 '''returns a function object bound to table which can be used as
2313 a decorator for populating table as a command table'''
2314 a decorator for populating table as a command table'''
2314
2315
2315 def cmd(name, options=(), synopsis=None):
2316 def cmd(name, options=(), synopsis=None):
2316 def decorator(func):
2317 def decorator(func):
2317 if synopsis:
2318 if synopsis:
2318 table[name] = func, list(options), synopsis
2319 table[name] = func, list(options), synopsis
2319 else:
2320 else:
2320 table[name] = func, list(options)
2321 table[name] = func, list(options)
2321 return func
2322 return func
2322 return decorator
2323 return decorator
2323
2324
2324 return cmd
2325 return cmd
2325
2326
2326 # a list of (ui, repo) functions called by commands.summary
2327 # a list of (ui, repo) functions called by commands.summary
2327 summaryhooks = util.hooks()
2328 summaryhooks = util.hooks()
2328
2329
2329 # A list of state files kept by multistep operations like graft.
2330 # A list of state files kept by multistep operations like graft.
2330 # Since graft cannot be aborted, it is considered 'clearable' by update.
2331 # Since graft cannot be aborted, it is considered 'clearable' by update.
2331 # note: bisect is intentionally excluded
2332 # note: bisect is intentionally excluded
2332 # (state file, clearable, allowcommit, error, hint)
2333 # (state file, clearable, allowcommit, error, hint)
2333 unfinishedstates = [
2334 unfinishedstates = [
2334 ('graftstate', True, False, _('graft in progress'),
2335 ('graftstate', True, False, _('graft in progress'),
2335 _("use 'hg graft --continue' or 'hg update' to abort")),
2336 _("use 'hg graft --continue' or 'hg update' to abort")),
2336 ('updatestate', True, False, _('last update was interrupted'),
2337 ('updatestate', True, False, _('last update was interrupted'),
2337 _("use 'hg update' to get a consistent checkout"))
2338 _("use 'hg update' to get a consistent checkout"))
2338 ]
2339 ]
2339
2340
2340 def checkunfinished(repo, commit=False):
2341 def checkunfinished(repo, commit=False):
2341 '''Look for an unfinished multistep operation, like graft, and abort
2342 '''Look for an unfinished multistep operation, like graft, and abort
2342 if found. It's probably good to check this right before
2343 if found. It's probably good to check this right before
2343 bailifchanged().
2344 bailifchanged().
2344 '''
2345 '''
2345 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2346 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2346 if commit and allowcommit:
2347 if commit and allowcommit:
2347 continue
2348 continue
2348 if repo.vfs.exists(f):
2349 if repo.vfs.exists(f):
2349 raise util.Abort(msg, hint=hint)
2350 raise util.Abort(msg, hint=hint)
2350
2351
2351 def clearunfinished(repo):
2352 def clearunfinished(repo):
2352 '''Check for unfinished operations (as above), and clear the ones
2353 '''Check for unfinished operations (as above), and clear the ones
2353 that are clearable.
2354 that are clearable.
2354 '''
2355 '''
2355 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2356 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2356 if not clearable and repo.vfs.exists(f):
2357 if not clearable and repo.vfs.exists(f):
2357 raise util.Abort(msg, hint=hint)
2358 raise util.Abort(msg, hint=hint)
2358 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2359 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2359 if clearable and repo.vfs.exists(f):
2360 if clearable and repo.vfs.exists(f):
2360 util.unlink(repo.join(f))
2361 util.unlink(repo.join(f))
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now