##// END OF EJS Templates
obsolete: add allowunstable option...
Durham Goode -
r22952:8792ac09 default
parent child Browse files
Show More
@@ -1,945 +1,945 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 # r, roll = like fold, but discard this commit's description
39 # r, roll = like fold, but discard this commit's description
40 # d, drop = remove commit from history
40 # d, drop = remove commit from history
41 # m, mess = edit message without changing commit content
41 # m, mess = edit message without changing commit content
42 #
42 #
43
43
44 In this file, lines beginning with ``#`` are ignored. You must specify a rule
44 In this file, lines beginning with ``#`` are ignored. You must specify a rule
45 for each revision in your history. For example, if you had meant to add gamma
45 for each revision in your history. For example, if you had meant to add gamma
46 before beta, and then wanted to add delta in the same revision as beta, you
46 before beta, and then wanted to add delta in the same revision as beta, you
47 would reorganize the file to look like this::
47 would reorganize the file to look like this::
48
48
49 pick 030b686bedc4 Add gamma
49 pick 030b686bedc4 Add gamma
50 pick c561b4e977df Add beta
50 pick c561b4e977df Add beta
51 fold 7c2fd3b9020c Add delta
51 fold 7c2fd3b9020c Add delta
52
52
53 # Edit history between c561b4e977df and 7c2fd3b9020c
53 # Edit history between c561b4e977df and 7c2fd3b9020c
54 #
54 #
55 # Commits are listed from least to most recent
55 # Commits are listed from least to most recent
56 #
56 #
57 # Commands:
57 # Commands:
58 # p, pick = use commit
58 # p, pick = use commit
59 # e, edit = use commit, but stop for amending
59 # e, edit = use commit, but stop for amending
60 # f, fold = use commit, but combine it with the one above
60 # f, fold = use commit, but combine it with the one above
61 # r, roll = like fold, but discard this commit's description
61 # r, roll = like fold, but discard this commit's description
62 # d, drop = remove commit from history
62 # d, drop = remove commit from history
63 # m, mess = edit message without changing commit content
63 # m, mess = edit message without changing commit content
64 #
64 #
65
65
66 At which point you close the editor and ``histedit`` starts working. When you
66 At which point you close the editor and ``histedit`` starts working. When you
67 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
67 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
68 those revisions together, offering you a chance to clean up the commit message::
68 those revisions together, offering you a chance to clean up the commit message::
69
69
70 Add beta
70 Add beta
71 ***
71 ***
72 Add delta
72 Add delta
73
73
74 Edit the commit message to your liking, then close the editor. For
74 Edit the commit message to your liking, then close the editor. For
75 this example, let's assume that the commit message was changed to
75 this example, let's assume that the commit message was changed to
76 ``Add beta and delta.`` After histedit has run and had a chance to
76 ``Add beta and delta.`` After histedit has run and had a chance to
77 remove any old or temporary revisions it needed, the history looks
77 remove any old or temporary revisions it needed, the history looks
78 like this::
78 like this::
79
79
80 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
80 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
81 | Add beta and delta.
81 | Add beta and delta.
82 |
82 |
83 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
83 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
84 | Add gamma
84 | Add gamma
85 |
85 |
86 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
86 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
87 Add alpha
87 Add alpha
88
88
89 Note that ``histedit`` does *not* remove any revisions (even its own temporary
89 Note that ``histedit`` does *not* remove any revisions (even its own temporary
90 ones) until after it has completed all the editing operations, so it will
90 ones) until after it has completed all the editing operations, so it will
91 probably perform several strip operations when it's done. For the above example,
91 probably perform several strip operations when it's done. For the above example,
92 it had to run strip twice. Strip can be slow depending on a variety of factors,
92 it had to run strip twice. Strip can be slow depending on a variety of factors,
93 so you might need to be a little patient. You can choose to keep the original
93 so you might need to be a little patient. You can choose to keep the original
94 revisions by passing the ``--keep`` flag.
94 revisions by passing the ``--keep`` flag.
95
95
96 The ``edit`` operation will drop you back to a command prompt,
96 The ``edit`` operation will drop you back to a command prompt,
97 allowing you to edit files freely, or even use ``hg record`` to commit
97 allowing you to edit files freely, or even use ``hg record`` to commit
98 some changes as a separate commit. When you're done, any remaining
98 some changes as a separate commit. When you're done, any remaining
99 uncommitted changes will be committed as well. When done, run ``hg
99 uncommitted changes will be committed as well. When done, run ``hg
100 histedit --continue`` to finish this step. You'll be prompted for a
100 histedit --continue`` to finish this step. You'll be prompted for a
101 new commit message, but the default commit message will be the
101 new commit message, but the default commit message will be the
102 original message for the ``edit`` ed revision.
102 original message for the ``edit`` ed revision.
103
103
104 The ``message`` operation will give you a chance to revise a commit
104 The ``message`` operation will give you a chance to revise a commit
105 message without changing the contents. It's a shortcut for doing
105 message without changing the contents. It's a shortcut for doing
106 ``edit`` immediately followed by `hg histedit --continue``.
106 ``edit`` immediately followed by `hg histedit --continue``.
107
107
108 If ``histedit`` encounters a conflict when moving a revision (while
108 If ``histedit`` encounters a conflict when moving a revision (while
109 handling ``pick`` or ``fold``), it'll stop in a similar manner to
109 handling ``pick`` or ``fold``), it'll stop in a similar manner to
110 ``edit`` with the difference that it won't prompt you for a commit
110 ``edit`` with the difference that it won't prompt you for a commit
111 message when done. If you decide at this point that you don't like how
111 message when done. If you decide at this point that you don't like how
112 much work it will be to rearrange history, or that you made a mistake,
112 much work it will be to rearrange history, or that you made a mistake,
113 you can use ``hg histedit --abort`` to abandon the new changes you
113 you can use ``hg histedit --abort`` to abandon the new changes you
114 have made and return to the state before you attempted to edit your
114 have made and return to the state before you attempted to edit your
115 history.
115 history.
116
116
117 If we clone the histedit-ed example repository above and add four more
117 If we clone the histedit-ed example repository above and add four more
118 changes, such that we have the following history::
118 changes, such that we have the following history::
119
119
120 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
120 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
121 | Add theta
121 | Add theta
122 |
122 |
123 o 5 140988835471 2009-04-27 18:04 -0500 stefan
123 o 5 140988835471 2009-04-27 18:04 -0500 stefan
124 | Add eta
124 | Add eta
125 |
125 |
126 o 4 122930637314 2009-04-27 18:04 -0500 stefan
126 o 4 122930637314 2009-04-27 18:04 -0500 stefan
127 | Add zeta
127 | Add zeta
128 |
128 |
129 o 3 836302820282 2009-04-27 18:04 -0500 stefan
129 o 3 836302820282 2009-04-27 18:04 -0500 stefan
130 | Add epsilon
130 | Add epsilon
131 |
131 |
132 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
132 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
133 | Add beta and delta.
133 | Add beta and delta.
134 |
134 |
135 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
135 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
136 | Add gamma
136 | Add gamma
137 |
137 |
138 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
138 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
139 Add alpha
139 Add alpha
140
140
141 If you run ``hg histedit --outgoing`` on the clone then it is the same
141 If you run ``hg histedit --outgoing`` on the clone then it is the same
142 as running ``hg histedit 836302820282``. If you need plan to push to a
142 as running ``hg histedit 836302820282``. If you need plan to push to a
143 repository that Mercurial does not detect to be related to the source
143 repository that Mercurial does not detect to be related to the source
144 repo, you can add a ``--force`` option.
144 repo, you can add a ``--force`` option.
145 """
145 """
146
146
147 try:
147 try:
148 import cPickle as pickle
148 import cPickle as pickle
149 pickle.dump # import now
149 pickle.dump # import now
150 except ImportError:
150 except ImportError:
151 import pickle
151 import pickle
152 import errno
152 import errno
153 import os
153 import os
154 import sys
154 import sys
155
155
156 from mercurial import cmdutil
156 from mercurial import cmdutil
157 from mercurial import discovery
157 from mercurial import discovery
158 from mercurial import error
158 from mercurial import error
159 from mercurial import copies
159 from mercurial import copies
160 from mercurial import context
160 from mercurial import context
161 from mercurial import hg
161 from mercurial import hg
162 from mercurial import node
162 from mercurial import node
163 from mercurial import repair
163 from mercurial import repair
164 from mercurial import scmutil
164 from mercurial import scmutil
165 from mercurial import util
165 from mercurial import util
166 from mercurial import obsolete
166 from mercurial import obsolete
167 from mercurial import merge as mergemod
167 from mercurial import merge as mergemod
168 from mercurial.lock import release
168 from mercurial.lock import release
169 from mercurial.i18n import _
169 from mercurial.i18n import _
170
170
171 cmdtable = {}
171 cmdtable = {}
172 command = cmdutil.command(cmdtable)
172 command = cmdutil.command(cmdtable)
173
173
174 testedwith = 'internal'
174 testedwith = 'internal'
175
175
176 # i18n: command names and abbreviations must remain untranslated
176 # i18n: command names and abbreviations must remain untranslated
177 editcomment = _("""# Edit history between %s and %s
177 editcomment = _("""# Edit history between %s and %s
178 #
178 #
179 # Commits are listed from least to most recent
179 # Commits are listed from least to most recent
180 #
180 #
181 # Commands:
181 # Commands:
182 # p, pick = use commit
182 # p, pick = use commit
183 # e, edit = use commit, but stop for amending
183 # e, edit = use commit, but stop for amending
184 # f, fold = use commit, but combine it with the one above
184 # f, fold = use commit, but combine it with the one above
185 # r, roll = like fold, but discard this commit's description
185 # r, roll = like fold, but discard this commit's description
186 # d, drop = remove commit from history
186 # d, drop = remove commit from history
187 # m, mess = edit message without changing commit content
187 # m, mess = edit message without changing commit content
188 #
188 #
189 """)
189 """)
190
190
191 def commitfuncfor(repo, src):
191 def commitfuncfor(repo, src):
192 """Build a commit function for the replacement of <src>
192 """Build a commit function for the replacement of <src>
193
193
194 This function ensure we apply the same treatment to all changesets.
194 This function ensure we apply the same treatment to all changesets.
195
195
196 - Add a 'histedit_source' entry in extra.
196 - Add a 'histedit_source' entry in extra.
197
197
198 Note that fold have its own separated logic because its handling is a bit
198 Note that fold have its own separated logic because its handling is a bit
199 different and not easily factored out of the fold method.
199 different and not easily factored out of the fold method.
200 """
200 """
201 phasemin = src.phase()
201 phasemin = src.phase()
202 def commitfunc(**kwargs):
202 def commitfunc(**kwargs):
203 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
203 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
204 try:
204 try:
205 repo.ui.setconfig('phases', 'new-commit', phasemin,
205 repo.ui.setconfig('phases', 'new-commit', phasemin,
206 'histedit')
206 'histedit')
207 extra = kwargs.get('extra', {}).copy()
207 extra = kwargs.get('extra', {}).copy()
208 extra['histedit_source'] = src.hex()
208 extra['histedit_source'] = src.hex()
209 kwargs['extra'] = extra
209 kwargs['extra'] = extra
210 return repo.commit(**kwargs)
210 return repo.commit(**kwargs)
211 finally:
211 finally:
212 repo.ui.restoreconfig(phasebackup)
212 repo.ui.restoreconfig(phasebackup)
213 return commitfunc
213 return commitfunc
214
214
215 def applychanges(ui, repo, ctx, opts):
215 def applychanges(ui, repo, ctx, opts):
216 """Merge changeset from ctx (only) in the current working directory"""
216 """Merge changeset from ctx (only) in the current working directory"""
217 wcpar = repo.dirstate.parents()[0]
217 wcpar = repo.dirstate.parents()[0]
218 if ctx.p1().node() == wcpar:
218 if ctx.p1().node() == wcpar:
219 # edition ar "in place" we do not need to make any merge,
219 # edition ar "in place" we do not need to make any merge,
220 # just applies changes on parent for edition
220 # just applies changes on parent for edition
221 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
221 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
222 stats = None
222 stats = None
223 else:
223 else:
224 try:
224 try:
225 # ui.forcemerge is an internal variable, do not document
225 # ui.forcemerge is an internal variable, do not document
226 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
226 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
227 'histedit')
227 'histedit')
228 stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'histedit'])
228 stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'histedit'])
229 finally:
229 finally:
230 repo.ui.setconfig('ui', 'forcemerge', '', 'histedit')
230 repo.ui.setconfig('ui', 'forcemerge', '', 'histedit')
231 return stats
231 return stats
232
232
233 def collapse(repo, first, last, commitopts):
233 def collapse(repo, first, last, commitopts):
234 """collapse the set of revisions from first to last as new one.
234 """collapse the set of revisions from first to last as new one.
235
235
236 Expected commit options are:
236 Expected commit options are:
237 - message
237 - message
238 - date
238 - date
239 - username
239 - username
240 Commit message is edited in all cases.
240 Commit message is edited in all cases.
241
241
242 This function works in memory."""
242 This function works in memory."""
243 ctxs = list(repo.set('%d::%d', first, last))
243 ctxs = list(repo.set('%d::%d', first, last))
244 if not ctxs:
244 if not ctxs:
245 return None
245 return None
246 base = first.parents()[0]
246 base = first.parents()[0]
247
247
248 # commit a new version of the old changeset, including the update
248 # commit a new version of the old changeset, including the update
249 # collect all files which might be affected
249 # collect all files which might be affected
250 files = set()
250 files = set()
251 for ctx in ctxs:
251 for ctx in ctxs:
252 files.update(ctx.files())
252 files.update(ctx.files())
253
253
254 # Recompute copies (avoid recording a -> b -> a)
254 # Recompute copies (avoid recording a -> b -> a)
255 copied = copies.pathcopies(base, last)
255 copied = copies.pathcopies(base, last)
256
256
257 # prune files which were reverted by the updates
257 # prune files which were reverted by the updates
258 def samefile(f):
258 def samefile(f):
259 if f in last.manifest():
259 if f in last.manifest():
260 a = last.filectx(f)
260 a = last.filectx(f)
261 if f in base.manifest():
261 if f in base.manifest():
262 b = base.filectx(f)
262 b = base.filectx(f)
263 return (a.data() == b.data()
263 return (a.data() == b.data()
264 and a.flags() == b.flags())
264 and a.flags() == b.flags())
265 else:
265 else:
266 return False
266 return False
267 else:
267 else:
268 return f not in base.manifest()
268 return f not in base.manifest()
269 files = [f for f in files if not samefile(f)]
269 files = [f for f in files if not samefile(f)]
270 # commit version of these files as defined by head
270 # commit version of these files as defined by head
271 headmf = last.manifest()
271 headmf = last.manifest()
272 def filectxfn(repo, ctx, path):
272 def filectxfn(repo, ctx, path):
273 if path in headmf:
273 if path in headmf:
274 fctx = last[path]
274 fctx = last[path]
275 flags = fctx.flags()
275 flags = fctx.flags()
276 mctx = context.memfilectx(repo,
276 mctx = context.memfilectx(repo,
277 fctx.path(), fctx.data(),
277 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 return None
282 return None
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 editor = None
293 editor = None
294 if not commitopts.get('rollup'):
294 if not commitopts.get('rollup'):
295 editor = cmdutil.getcommiteditor(edit=True, editform='histedit.fold')
295 editor = cmdutil.getcommiteditor(edit=True, editform='histedit.fold')
296 new = context.memctx(repo,
296 new = context.memctx(repo,
297 parents=parents,
297 parents=parents,
298 text=message,
298 text=message,
299 files=files,
299 files=files,
300 filectxfn=filectxfn,
300 filectxfn=filectxfn,
301 user=user,
301 user=user,
302 date=date,
302 date=date,
303 extra=extra,
303 extra=extra,
304 editor=editor)
304 editor=editor)
305 return repo.commitctx(new)
305 return repo.commitctx(new)
306
306
307 def pick(ui, repo, ctx, ha, opts):
307 def pick(ui, repo, ctx, ha, opts):
308 oldctx = repo[ha]
308 oldctx = repo[ha]
309 if oldctx.parents()[0] == ctx:
309 if oldctx.parents()[0] == ctx:
310 ui.debug('node %s unchanged\n' % ha)
310 ui.debug('node %s unchanged\n' % ha)
311 return oldctx, []
311 return oldctx, []
312 hg.update(repo, ctx.node())
312 hg.update(repo, ctx.node())
313 stats = applychanges(ui, repo, oldctx, opts)
313 stats = applychanges(ui, repo, oldctx, opts)
314 if stats and stats[3] > 0:
314 if stats and stats[3] > 0:
315 raise error.InterventionRequired(_('Fix up the change and run '
315 raise error.InterventionRequired(_('Fix up the change and run '
316 'hg histedit --continue'))
316 'hg histedit --continue'))
317 # drop the second merge parent
317 # drop the second merge parent
318 commit = commitfuncfor(repo, oldctx)
318 commit = commitfuncfor(repo, oldctx)
319 n = commit(text=oldctx.description(), user=oldctx.user(),
319 n = commit(text=oldctx.description(), user=oldctx.user(),
320 date=oldctx.date(), extra=oldctx.extra())
320 date=oldctx.date(), extra=oldctx.extra())
321 if n is None:
321 if n is None:
322 ui.warn(_('%s: empty changeset\n')
322 ui.warn(_('%s: empty changeset\n')
323 % node.hex(ha))
323 % node.hex(ha))
324 return ctx, []
324 return ctx, []
325 new = repo[n]
325 new = repo[n]
326 return new, [(oldctx.node(), (n,))]
326 return new, [(oldctx.node(), (n,))]
327
327
328
328
329 def edit(ui, repo, ctx, ha, opts):
329 def edit(ui, repo, ctx, ha, opts):
330 oldctx = repo[ha]
330 oldctx = repo[ha]
331 hg.update(repo, ctx.node())
331 hg.update(repo, ctx.node())
332 applychanges(ui, repo, oldctx, opts)
332 applychanges(ui, repo, oldctx, opts)
333 raise error.InterventionRequired(
333 raise error.InterventionRequired(
334 _('Make changes as needed, you may commit or record as needed now.\n'
334 _('Make changes as needed, you may commit or record as needed now.\n'
335 'When you are finished, run hg histedit --continue to resume.'))
335 'When you are finished, run hg histedit --continue to resume.'))
336
336
337 def rollup(ui, repo, ctx, ha, opts):
337 def rollup(ui, repo, ctx, ha, opts):
338 rollupopts = opts.copy()
338 rollupopts = opts.copy()
339 rollupopts['rollup'] = True
339 rollupopts['rollup'] = True
340 return fold(ui, repo, ctx, ha, rollupopts)
340 return fold(ui, repo, ctx, ha, rollupopts)
341
341
342 def fold(ui, repo, ctx, ha, opts):
342 def fold(ui, repo, ctx, ha, opts):
343 oldctx = repo[ha]
343 oldctx = repo[ha]
344 hg.update(repo, ctx.node())
344 hg.update(repo, ctx.node())
345 stats = applychanges(ui, repo, oldctx, opts)
345 stats = applychanges(ui, repo, oldctx, opts)
346 if stats and stats[3] > 0:
346 if stats and stats[3] > 0:
347 raise error.InterventionRequired(
347 raise error.InterventionRequired(
348 _('Fix up the change and run hg histedit --continue'))
348 _('Fix up the change and run hg histedit --continue'))
349 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
349 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
350 date=oldctx.date(), extra=oldctx.extra())
350 date=oldctx.date(), extra=oldctx.extra())
351 if n is None:
351 if n is None:
352 ui.warn(_('%s: empty changeset')
352 ui.warn(_('%s: empty changeset')
353 % node.hex(ha))
353 % node.hex(ha))
354 return ctx, []
354 return ctx, []
355 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
355 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
356
356
357 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
357 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
358 parent = ctx.parents()[0].node()
358 parent = ctx.parents()[0].node()
359 hg.update(repo, parent)
359 hg.update(repo, parent)
360 ### prepare new commit data
360 ### prepare new commit data
361 commitopts = opts.copy()
361 commitopts = opts.copy()
362 commitopts['user'] = ctx.user()
362 commitopts['user'] = ctx.user()
363 # commit message
363 # commit message
364 if opts.get('rollup'):
364 if opts.get('rollup'):
365 newmessage = ctx.description()
365 newmessage = ctx.description()
366 else:
366 else:
367 newmessage = '\n***\n'.join(
367 newmessage = '\n***\n'.join(
368 [ctx.description()] +
368 [ctx.description()] +
369 [repo[r].description() for r in internalchanges] +
369 [repo[r].description() for r in internalchanges] +
370 [oldctx.description()]) + '\n'
370 [oldctx.description()]) + '\n'
371 commitopts['message'] = newmessage
371 commitopts['message'] = newmessage
372 # date
372 # date
373 commitopts['date'] = max(ctx.date(), oldctx.date())
373 commitopts['date'] = max(ctx.date(), oldctx.date())
374 extra = ctx.extra().copy()
374 extra = ctx.extra().copy()
375 # histedit_source
375 # histedit_source
376 # note: ctx is likely a temporary commit but that the best we can do here
376 # note: ctx is likely a temporary commit but that the best we can do here
377 # This is sufficient to solve issue3681 anyway
377 # This is sufficient to solve issue3681 anyway
378 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
378 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
379 commitopts['extra'] = extra
379 commitopts['extra'] = extra
380 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
380 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
381 try:
381 try:
382 phasemin = max(ctx.phase(), oldctx.phase())
382 phasemin = max(ctx.phase(), oldctx.phase())
383 repo.ui.setconfig('phases', 'new-commit', phasemin, 'histedit')
383 repo.ui.setconfig('phases', 'new-commit', phasemin, 'histedit')
384 n = collapse(repo, ctx, repo[newnode], commitopts)
384 n = collapse(repo, ctx, repo[newnode], commitopts)
385 finally:
385 finally:
386 repo.ui.restoreconfig(phasebackup)
386 repo.ui.restoreconfig(phasebackup)
387 if n is None:
387 if n is None:
388 return ctx, []
388 return ctx, []
389 hg.update(repo, n)
389 hg.update(repo, n)
390 replacements = [(oldctx.node(), (newnode,)),
390 replacements = [(oldctx.node(), (newnode,)),
391 (ctx.node(), (n,)),
391 (ctx.node(), (n,)),
392 (newnode, (n,)),
392 (newnode, (n,)),
393 ]
393 ]
394 for ich in internalchanges:
394 for ich in internalchanges:
395 replacements.append((ich, (n,)))
395 replacements.append((ich, (n,)))
396 return repo[n], replacements
396 return repo[n], replacements
397
397
398 def drop(ui, repo, ctx, ha, opts):
398 def drop(ui, repo, ctx, ha, opts):
399 return ctx, [(repo[ha].node(), ())]
399 return ctx, [(repo[ha].node(), ())]
400
400
401
401
402 def message(ui, repo, ctx, ha, opts):
402 def message(ui, repo, ctx, ha, opts):
403 oldctx = repo[ha]
403 oldctx = repo[ha]
404 hg.update(repo, ctx.node())
404 hg.update(repo, ctx.node())
405 stats = applychanges(ui, repo, oldctx, opts)
405 stats = applychanges(ui, repo, oldctx, opts)
406 if stats and stats[3] > 0:
406 if stats and stats[3] > 0:
407 raise error.InterventionRequired(
407 raise error.InterventionRequired(
408 _('Fix up the change and run hg histedit --continue'))
408 _('Fix up the change and run hg histedit --continue'))
409 message = oldctx.description()
409 message = oldctx.description()
410 commit = commitfuncfor(repo, oldctx)
410 commit = commitfuncfor(repo, oldctx)
411 editor = cmdutil.getcommiteditor(edit=True, editform='histedit.mess')
411 editor = cmdutil.getcommiteditor(edit=True, editform='histedit.mess')
412 new = commit(text=message, user=oldctx.user(), date=oldctx.date(),
412 new = commit(text=message, user=oldctx.user(), date=oldctx.date(),
413 extra=oldctx.extra(),
413 extra=oldctx.extra(),
414 editor=editor)
414 editor=editor)
415 newctx = repo[new]
415 newctx = repo[new]
416 if oldctx.node() != newctx.node():
416 if oldctx.node() != newctx.node():
417 return newctx, [(oldctx.node(), (new,))]
417 return newctx, [(oldctx.node(), (new,))]
418 # We didn't make an edit, so just indicate no replaced nodes
418 # We didn't make an edit, so just indicate no replaced nodes
419 return newctx, []
419 return newctx, []
420
420
421 def findoutgoing(ui, repo, remote=None, force=False, opts={}):
421 def findoutgoing(ui, repo, remote=None, force=False, opts={}):
422 """utility function to find the first outgoing changeset
422 """utility function to find the first outgoing changeset
423
423
424 Used by initialisation code"""
424 Used by initialisation code"""
425 dest = ui.expandpath(remote or 'default-push', remote or 'default')
425 dest = ui.expandpath(remote or 'default-push', remote or 'default')
426 dest, revs = hg.parseurl(dest, None)[:2]
426 dest, revs = hg.parseurl(dest, None)[:2]
427 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
427 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
428
428
429 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
429 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
430 other = hg.peer(repo, opts, dest)
430 other = hg.peer(repo, opts, dest)
431
431
432 if revs:
432 if revs:
433 revs = [repo.lookup(rev) for rev in revs]
433 revs = [repo.lookup(rev) for rev in revs]
434
434
435 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
435 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
436 if not outgoing.missing:
436 if not outgoing.missing:
437 raise util.Abort(_('no outgoing ancestors'))
437 raise util.Abort(_('no outgoing ancestors'))
438 roots = list(repo.revs("roots(%ln)", outgoing.missing))
438 roots = list(repo.revs("roots(%ln)", outgoing.missing))
439 if 1 < len(roots):
439 if 1 < len(roots):
440 msg = _('there are ambiguous outgoing revisions')
440 msg = _('there are ambiguous outgoing revisions')
441 hint = _('see "hg help histedit" for more detail')
441 hint = _('see "hg help histedit" for more detail')
442 raise util.Abort(msg, hint=hint)
442 raise util.Abort(msg, hint=hint)
443 return repo.lookup(roots[0])
443 return repo.lookup(roots[0])
444
444
445 actiontable = {'p': pick,
445 actiontable = {'p': pick,
446 'pick': pick,
446 'pick': pick,
447 'e': edit,
447 'e': edit,
448 'edit': edit,
448 'edit': edit,
449 'f': fold,
449 'f': fold,
450 'fold': fold,
450 'fold': fold,
451 'r': rollup,
451 'r': rollup,
452 'roll': rollup,
452 'roll': rollup,
453 'd': drop,
453 'd': drop,
454 'drop': drop,
454 'drop': drop,
455 'm': message,
455 'm': message,
456 'mess': message,
456 'mess': message,
457 }
457 }
458
458
459 @command('histedit',
459 @command('histedit',
460 [('', 'commands', '',
460 [('', 'commands', '',
461 _('Read history edits from the specified file.')),
461 _('Read history edits from the specified file.')),
462 ('c', 'continue', False, _('continue an edit already in progress')),
462 ('c', 'continue', False, _('continue an edit already in progress')),
463 ('k', 'keep', False,
463 ('k', 'keep', False,
464 _("don't strip old nodes after edit is complete")),
464 _("don't strip old nodes after edit is complete")),
465 ('', 'abort', False, _('abort an edit in progress')),
465 ('', 'abort', False, _('abort an edit in progress')),
466 ('o', 'outgoing', False, _('changesets not found in destination')),
466 ('o', 'outgoing', False, _('changesets not found in destination')),
467 ('f', 'force', False,
467 ('f', 'force', False,
468 _('force outgoing even for unrelated repositories')),
468 _('force outgoing even for unrelated repositories')),
469 ('r', 'rev', [], _('first revision to be edited'))],
469 ('r', 'rev', [], _('first revision to be edited'))],
470 _("ANCESTOR | --outgoing [URL]"))
470 _("ANCESTOR | --outgoing [URL]"))
471 def histedit(ui, repo, *freeargs, **opts):
471 def histedit(ui, repo, *freeargs, **opts):
472 """interactively edit changeset history
472 """interactively edit changeset history
473
473
474 This command edits changesets between ANCESTOR and the parent of
474 This command edits changesets between ANCESTOR and the parent of
475 the working directory.
475 the working directory.
476
476
477 With --outgoing, this edits changesets not found in the
477 With --outgoing, this edits changesets not found in the
478 destination repository. If URL of the destination is omitted, the
478 destination repository. If URL of the destination is omitted, the
479 'default-push' (or 'default') path will be used.
479 'default-push' (or 'default') path will be used.
480
480
481 For safety, this command is aborted, also if there are ambiguous
481 For safety, this command is aborted, also if there are ambiguous
482 outgoing revisions which may confuse users: for example, there are
482 outgoing revisions which may confuse users: for example, there are
483 multiple branches containing outgoing revisions.
483 multiple branches containing outgoing revisions.
484
484
485 Use "min(outgoing() and ::.)" or similar revset specification
485 Use "min(outgoing() and ::.)" or similar revset specification
486 instead of --outgoing to specify edit target revision exactly in
486 instead of --outgoing to specify edit target revision exactly in
487 such ambiguous situation. See :hg:`help revsets` for detail about
487 such ambiguous situation. See :hg:`help revsets` for detail about
488 selecting revisions.
488 selecting revisions.
489
489
490 Returns 0 on success, 1 if user intervention is required (not only
490 Returns 0 on success, 1 if user intervention is required (not only
491 for intentional "edit" command, but also for resolving unexpected
491 for intentional "edit" command, but also for resolving unexpected
492 conflicts).
492 conflicts).
493 """
493 """
494 lock = wlock = None
494 lock = wlock = None
495 try:
495 try:
496 wlock = repo.wlock()
496 wlock = repo.wlock()
497 lock = repo.lock()
497 lock = repo.lock()
498 _histedit(ui, repo, *freeargs, **opts)
498 _histedit(ui, repo, *freeargs, **opts)
499 finally:
499 finally:
500 release(lock, wlock)
500 release(lock, wlock)
501
501
502 def _histedit(ui, repo, *freeargs, **opts):
502 def _histedit(ui, repo, *freeargs, **opts):
503 # TODO only abort if we try and histedit mq patches, not just
503 # TODO only abort if we try and histedit mq patches, not just
504 # blanket if mq patches are applied somewhere
504 # blanket if mq patches are applied somewhere
505 mq = getattr(repo, 'mq', None)
505 mq = getattr(repo, 'mq', None)
506 if mq and mq.applied:
506 if mq and mq.applied:
507 raise util.Abort(_('source has mq patches applied'))
507 raise util.Abort(_('source has mq patches applied'))
508
508
509 # basic argument incompatibility processing
509 # basic argument incompatibility processing
510 outg = opts.get('outgoing')
510 outg = opts.get('outgoing')
511 cont = opts.get('continue')
511 cont = opts.get('continue')
512 abort = opts.get('abort')
512 abort = opts.get('abort')
513 force = opts.get('force')
513 force = opts.get('force')
514 rules = opts.get('commands', '')
514 rules = opts.get('commands', '')
515 revs = opts.get('rev', [])
515 revs = opts.get('rev', [])
516 goal = 'new' # This invocation goal, in new, continue, abort
516 goal = 'new' # This invocation goal, in new, continue, abort
517 if force and not outg:
517 if force and not outg:
518 raise util.Abort(_('--force only allowed with --outgoing'))
518 raise util.Abort(_('--force only allowed with --outgoing'))
519 if cont:
519 if cont:
520 if util.any((outg, abort, revs, freeargs, rules)):
520 if util.any((outg, abort, revs, freeargs, rules)):
521 raise util.Abort(_('no arguments allowed with --continue'))
521 raise util.Abort(_('no arguments allowed with --continue'))
522 goal = 'continue'
522 goal = 'continue'
523 elif abort:
523 elif abort:
524 if util.any((outg, revs, freeargs, rules)):
524 if util.any((outg, revs, freeargs, rules)):
525 raise util.Abort(_('no arguments allowed with --abort'))
525 raise util.Abort(_('no arguments allowed with --abort'))
526 goal = 'abort'
526 goal = 'abort'
527 else:
527 else:
528 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
528 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
529 raise util.Abort(_('history edit already in progress, try '
529 raise util.Abort(_('history edit already in progress, try '
530 '--continue or --abort'))
530 '--continue or --abort'))
531 if outg:
531 if outg:
532 if revs:
532 if revs:
533 raise util.Abort(_('no revisions allowed with --outgoing'))
533 raise util.Abort(_('no revisions allowed with --outgoing'))
534 if len(freeargs) > 1:
534 if len(freeargs) > 1:
535 raise util.Abort(
535 raise util.Abort(
536 _('only one repo argument allowed with --outgoing'))
536 _('only one repo argument allowed with --outgoing'))
537 else:
537 else:
538 revs.extend(freeargs)
538 revs.extend(freeargs)
539 if len(revs) != 1:
539 if len(revs) != 1:
540 raise util.Abort(
540 raise util.Abort(
541 _('histedit requires exactly one ancestor revision'))
541 _('histedit requires exactly one ancestor revision'))
542
542
543
543
544 if goal == 'continue':
544 if goal == 'continue':
545 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
545 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
546 parentctx = repo[parentctxnode]
546 parentctx = repo[parentctxnode]
547 parentctx, repl = bootstrapcontinue(ui, repo, parentctx, rules, opts)
547 parentctx, repl = bootstrapcontinue(ui, repo, parentctx, rules, opts)
548 replacements.extend(repl)
548 replacements.extend(repl)
549 elif goal == 'abort':
549 elif goal == 'abort':
550 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
550 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
551 mapping, tmpnodes, leafs, _ntm = processreplacement(repo, replacements)
551 mapping, tmpnodes, leafs, _ntm = processreplacement(repo, replacements)
552 ui.debug('restore wc to old parent %s\n' % node.short(topmost))
552 ui.debug('restore wc to old parent %s\n' % node.short(topmost))
553 # check whether we should update away
553 # check whether we should update away
554 parentnodes = [c.node() for c in repo[None].parents()]
554 parentnodes = [c.node() for c in repo[None].parents()]
555 for n in leafs | set([parentctxnode]):
555 for n in leafs | set([parentctxnode]):
556 if n in parentnodes:
556 if n in parentnodes:
557 hg.clean(repo, topmost)
557 hg.clean(repo, topmost)
558 break
558 break
559 else:
559 else:
560 pass
560 pass
561 cleanupnode(ui, repo, 'created', tmpnodes)
561 cleanupnode(ui, repo, 'created', tmpnodes)
562 cleanupnode(ui, repo, 'temp', leafs)
562 cleanupnode(ui, repo, 'temp', leafs)
563 os.unlink(os.path.join(repo.path, 'histedit-state'))
563 os.unlink(os.path.join(repo.path, 'histedit-state'))
564 return
564 return
565 else:
565 else:
566 cmdutil.checkunfinished(repo)
566 cmdutil.checkunfinished(repo)
567 cmdutil.bailifchanged(repo)
567 cmdutil.bailifchanged(repo)
568
568
569 topmost, empty = repo.dirstate.parents()
569 topmost, empty = repo.dirstate.parents()
570 if outg:
570 if outg:
571 if freeargs:
571 if freeargs:
572 remote = freeargs[0]
572 remote = freeargs[0]
573 else:
573 else:
574 remote = None
574 remote = None
575 root = findoutgoing(ui, repo, remote, force, opts)
575 root = findoutgoing(ui, repo, remote, force, opts)
576 else:
576 else:
577 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs)))
577 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs)))
578 if len(rr) != 1:
578 if len(rr) != 1:
579 raise util.Abort(_('The specified revisions must have '
579 raise util.Abort(_('The specified revisions must have '
580 'exactly one common root'))
580 'exactly one common root'))
581 root = rr[0].node()
581 root = rr[0].node()
582
582
583 keep = opts.get('keep', False)
583 keep = opts.get('keep', False)
584 revs = between(repo, root, topmost, keep)
584 revs = between(repo, root, topmost, keep)
585 if not revs:
585 if not revs:
586 raise util.Abort(_('%s is not an ancestor of working directory') %
586 raise util.Abort(_('%s is not an ancestor of working directory') %
587 node.short(root))
587 node.short(root))
588
588
589 ctxs = [repo[r] for r in revs]
589 ctxs = [repo[r] for r in revs]
590 if not rules:
590 if not rules:
591 rules = '\n'.join([makedesc(c) for c in ctxs])
591 rules = '\n'.join([makedesc(c) for c in ctxs])
592 rules += '\n\n'
592 rules += '\n\n'
593 rules += editcomment % (node.short(root), node.short(topmost))
593 rules += editcomment % (node.short(root), node.short(topmost))
594 rules = ui.edit(rules, ui.username())
594 rules = ui.edit(rules, ui.username())
595 # Save edit rules in .hg/histedit-last-edit.txt in case
595 # Save edit rules in .hg/histedit-last-edit.txt in case
596 # the user needs to ask for help after something
596 # the user needs to ask for help after something
597 # surprising happens.
597 # surprising happens.
598 f = open(repo.join('histedit-last-edit.txt'), 'w')
598 f = open(repo.join('histedit-last-edit.txt'), 'w')
599 f.write(rules)
599 f.write(rules)
600 f.close()
600 f.close()
601 else:
601 else:
602 if rules == '-':
602 if rules == '-':
603 f = sys.stdin
603 f = sys.stdin
604 else:
604 else:
605 f = open(rules)
605 f = open(rules)
606 rules = f.read()
606 rules = f.read()
607 f.close()
607 f.close()
608 rules = [l for l in (r.strip() for r in rules.splitlines())
608 rules = [l for l in (r.strip() for r in rules.splitlines())
609 if l and not l.startswith('#')]
609 if l and not l.startswith('#')]
610 rules = verifyrules(rules, repo, ctxs)
610 rules = verifyrules(rules, repo, ctxs)
611
611
612 parentctx = repo[root].parents()[0]
612 parentctx = repo[root].parents()[0]
613 replacements = []
613 replacements = []
614
614
615
615
616 while rules:
616 while rules:
617 writestate(repo, parentctx.node(), rules, keep, topmost, replacements)
617 writestate(repo, parentctx.node(), rules, keep, topmost, replacements)
618 action, ha = rules.pop(0)
618 action, ha = rules.pop(0)
619 ui.debug('histedit: processing %s %s\n' % (action, ha))
619 ui.debug('histedit: processing %s %s\n' % (action, ha))
620 actfunc = actiontable[action]
620 actfunc = actiontable[action]
621 parentctx, replacement_ = actfunc(ui, repo, parentctx, ha, opts)
621 parentctx, replacement_ = actfunc(ui, repo, parentctx, ha, opts)
622 replacements.extend(replacement_)
622 replacements.extend(replacement_)
623
623
624 hg.update(repo, parentctx.node())
624 hg.update(repo, parentctx.node())
625
625
626 mapping, tmpnodes, created, ntm = processreplacement(repo, replacements)
626 mapping, tmpnodes, created, ntm = processreplacement(repo, replacements)
627 if mapping:
627 if mapping:
628 for prec, succs in mapping.iteritems():
628 for prec, succs in mapping.iteritems():
629 if not succs:
629 if not succs:
630 ui.debug('histedit: %s is dropped\n' % node.short(prec))
630 ui.debug('histedit: %s is dropped\n' % node.short(prec))
631 else:
631 else:
632 ui.debug('histedit: %s is replaced by %s\n' % (
632 ui.debug('histedit: %s is replaced by %s\n' % (
633 node.short(prec), node.short(succs[0])))
633 node.short(prec), node.short(succs[0])))
634 if len(succs) > 1:
634 if len(succs) > 1:
635 m = 'histedit: %s'
635 m = 'histedit: %s'
636 for n in succs[1:]:
636 for n in succs[1:]:
637 ui.debug(m % node.short(n))
637 ui.debug(m % node.short(n))
638
638
639 if not keep:
639 if not keep:
640 if mapping:
640 if mapping:
641 movebookmarks(ui, repo, mapping, topmost, ntm)
641 movebookmarks(ui, repo, mapping, topmost, ntm)
642 # TODO update mq state
642 # TODO update mq state
643 if obsolete.isenabled(repo, obsolete.createmarkersopt):
643 if obsolete.isenabled(repo, obsolete.createmarkersopt):
644 markers = []
644 markers = []
645 # sort by revision number because it sound "right"
645 # sort by revision number because it sound "right"
646 for prec in sorted(mapping, key=repo.changelog.rev):
646 for prec in sorted(mapping, key=repo.changelog.rev):
647 succs = mapping[prec]
647 succs = mapping[prec]
648 markers.append((repo[prec],
648 markers.append((repo[prec],
649 tuple(repo[s] for s in succs)))
649 tuple(repo[s] for s in succs)))
650 if markers:
650 if markers:
651 obsolete.createmarkers(repo, markers)
651 obsolete.createmarkers(repo, markers)
652 else:
652 else:
653 cleanupnode(ui, repo, 'replaced', mapping)
653 cleanupnode(ui, repo, 'replaced', mapping)
654
654
655 cleanupnode(ui, repo, 'temp', tmpnodes)
655 cleanupnode(ui, repo, 'temp', tmpnodes)
656 os.unlink(os.path.join(repo.path, 'histedit-state'))
656 os.unlink(os.path.join(repo.path, 'histedit-state'))
657 if os.path.exists(repo.sjoin('undo')):
657 if os.path.exists(repo.sjoin('undo')):
658 os.unlink(repo.sjoin('undo'))
658 os.unlink(repo.sjoin('undo'))
659
659
660 def gatherchildren(repo, ctx):
660 def gatherchildren(repo, ctx):
661 # is there any new commit between the expected parent and "."
661 # is there any new commit between the expected parent and "."
662 #
662 #
663 # note: does not take non linear new change in account (but previous
663 # note: does not take non linear new change in account (but previous
664 # implementation didn't used them anyway (issue3655)
664 # implementation didn't used them anyway (issue3655)
665 newchildren = [c.node() for c in repo.set('(%d::.)', ctx)]
665 newchildren = [c.node() for c in repo.set('(%d::.)', ctx)]
666 if ctx.node() != node.nullid:
666 if ctx.node() != node.nullid:
667 if not newchildren:
667 if not newchildren:
668 # `ctx` should match but no result. This means that
668 # `ctx` should match but no result. This means that
669 # currentnode is not a descendant from ctx.
669 # currentnode is not a descendant from ctx.
670 msg = _('%s is not an ancestor of working directory')
670 msg = _('%s is not an ancestor of working directory')
671 hint = _('use "histedit --abort" to clear broken state')
671 hint = _('use "histedit --abort" to clear broken state')
672 raise util.Abort(msg % ctx, hint=hint)
672 raise util.Abort(msg % ctx, hint=hint)
673 newchildren.pop(0) # remove ctx
673 newchildren.pop(0) # remove ctx
674 return newchildren
674 return newchildren
675
675
676 def bootstrapcontinue(ui, repo, parentctx, rules, opts):
676 def bootstrapcontinue(ui, repo, parentctx, rules, opts):
677 action, currentnode = rules.pop(0)
677 action, currentnode = rules.pop(0)
678 ctx = repo[currentnode]
678 ctx = repo[currentnode]
679
679
680 newchildren = gatherchildren(repo, parentctx)
680 newchildren = gatherchildren(repo, parentctx)
681
681
682 # Commit dirty working directory if necessary
682 # Commit dirty working directory if necessary
683 new = None
683 new = None
684 s = repo.status()
684 s = repo.status()
685 if s.modified or s.added or s.removed or s.deleted:
685 if s.modified or s.added or s.removed or s.deleted:
686 # prepare the message for the commit to comes
686 # prepare the message for the commit to comes
687 if action in ('f', 'fold', 'r', 'roll'):
687 if action in ('f', 'fold', 'r', 'roll'):
688 message = 'fold-temp-revision %s' % currentnode
688 message = 'fold-temp-revision %s' % currentnode
689 else:
689 else:
690 message = ctx.description()
690 message = ctx.description()
691 editopt = action in ('e', 'edit', 'm', 'mess')
691 editopt = action in ('e', 'edit', 'm', 'mess')
692 canonaction = {'e': 'edit', 'm': 'mess', 'p': 'pick'}
692 canonaction = {'e': 'edit', 'm': 'mess', 'p': 'pick'}
693 editform = 'histedit.%s' % canonaction.get(action, action)
693 editform = 'histedit.%s' % canonaction.get(action, action)
694 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
694 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
695 commit = commitfuncfor(repo, ctx)
695 commit = commitfuncfor(repo, ctx)
696 new = commit(text=message, user=ctx.user(),
696 new = commit(text=message, user=ctx.user(),
697 date=ctx.date(), extra=ctx.extra(),
697 date=ctx.date(), extra=ctx.extra(),
698 editor=editor)
698 editor=editor)
699 if new is not None:
699 if new is not None:
700 newchildren.append(new)
700 newchildren.append(new)
701
701
702 replacements = []
702 replacements = []
703 # track replacements
703 # track replacements
704 if ctx.node() not in newchildren:
704 if ctx.node() not in newchildren:
705 # note: new children may be empty when the changeset is dropped.
705 # note: new children may be empty when the changeset is dropped.
706 # this happen e.g during conflicting pick where we revert content
706 # this happen e.g during conflicting pick where we revert content
707 # to parent.
707 # to parent.
708 replacements.append((ctx.node(), tuple(newchildren)))
708 replacements.append((ctx.node(), tuple(newchildren)))
709
709
710 if action in ('f', 'fold', 'r', 'roll'):
710 if action in ('f', 'fold', 'r', 'roll'):
711 if newchildren:
711 if newchildren:
712 # finalize fold operation if applicable
712 # finalize fold operation if applicable
713 if new is None:
713 if new is None:
714 new = newchildren[-1]
714 new = newchildren[-1]
715 else:
715 else:
716 newchildren.pop() # remove new from internal changes
716 newchildren.pop() # remove new from internal changes
717 foldopts = opts
717 foldopts = opts
718 if action in ('r', 'roll'):
718 if action in ('r', 'roll'):
719 foldopts = foldopts.copy()
719 foldopts = foldopts.copy()
720 foldopts['rollup'] = True
720 foldopts['rollup'] = True
721 parentctx, repl = finishfold(ui, repo, parentctx, ctx, new,
721 parentctx, repl = finishfold(ui, repo, parentctx, ctx, new,
722 foldopts, newchildren)
722 foldopts, newchildren)
723 replacements.extend(repl)
723 replacements.extend(repl)
724 else:
724 else:
725 # newchildren is empty if the fold did not result in any commit
725 # newchildren is empty if the fold did not result in any commit
726 # this happen when all folded change are discarded during the
726 # this happen when all folded change are discarded during the
727 # merge.
727 # merge.
728 replacements.append((ctx.node(), (parentctx.node(),)))
728 replacements.append((ctx.node(), (parentctx.node(),)))
729 elif newchildren:
729 elif newchildren:
730 # otherwise update "parentctx" before proceeding to further operation
730 # otherwise update "parentctx" before proceeding to further operation
731 parentctx = repo[newchildren[-1]]
731 parentctx = repo[newchildren[-1]]
732 return parentctx, replacements
732 return parentctx, replacements
733
733
734
734
735 def between(repo, old, new, keep):
735 def between(repo, old, new, keep):
736 """select and validate the set of revision to edit
736 """select and validate the set of revision to edit
737
737
738 When keep is false, the specified set can't have children."""
738 When keep is false, the specified set can't have children."""
739 ctxs = list(repo.set('%n::%n', old, new))
739 ctxs = list(repo.set('%n::%n', old, new))
740 if ctxs and not keep:
740 if ctxs and not keep:
741 if (not obsolete._enabled and
741 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
742 repo.revs('(%ld::) - (%ld)', ctxs, ctxs)):
742 repo.revs('(%ld::) - (%ld)', ctxs, ctxs)):
743 raise util.Abort(_('cannot edit history that would orphan nodes'))
743 raise util.Abort(_('cannot edit history that would orphan nodes'))
744 if repo.revs('(%ld) and merge()', ctxs):
744 if repo.revs('(%ld) and merge()', ctxs):
745 raise util.Abort(_('cannot edit history that contains merges'))
745 raise util.Abort(_('cannot edit history that contains merges'))
746 root = ctxs[0] # list is already sorted by repo.set
746 root = ctxs[0] # list is already sorted by repo.set
747 if not root.mutable():
747 if not root.mutable():
748 raise util.Abort(_('cannot edit immutable changeset: %s') % root)
748 raise util.Abort(_('cannot edit immutable changeset: %s') % root)
749 return [c.node() for c in ctxs]
749 return [c.node() for c in ctxs]
750
750
751
751
752 def writestate(repo, parentnode, rules, keep, topmost, replacements):
752 def writestate(repo, parentnode, rules, keep, topmost, replacements):
753 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
753 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
754 pickle.dump((parentnode, rules, keep, topmost, replacements), fp)
754 pickle.dump((parentnode, rules, keep, topmost, replacements), fp)
755 fp.close()
755 fp.close()
756
756
757 def readstate(repo):
757 def readstate(repo):
758 """Returns a tuple of (parentnode, rules, keep, topmost, replacements).
758 """Returns a tuple of (parentnode, rules, keep, topmost, replacements).
759 """
759 """
760 try:
760 try:
761 fp = open(os.path.join(repo.path, 'histedit-state'))
761 fp = open(os.path.join(repo.path, 'histedit-state'))
762 except IOError, err:
762 except IOError, err:
763 if err.errno != errno.ENOENT:
763 if err.errno != errno.ENOENT:
764 raise
764 raise
765 raise util.Abort(_('no histedit in progress'))
765 raise util.Abort(_('no histedit in progress'))
766 return pickle.load(fp)
766 return pickle.load(fp)
767
767
768
768
769 def makedesc(c):
769 def makedesc(c):
770 """build a initial action line for a ctx `c`
770 """build a initial action line for a ctx `c`
771
771
772 line are in the form:
772 line are in the form:
773
773
774 pick <hash> <rev> <summary>
774 pick <hash> <rev> <summary>
775 """
775 """
776 summary = ''
776 summary = ''
777 if c.description():
777 if c.description():
778 summary = c.description().splitlines()[0]
778 summary = c.description().splitlines()[0]
779 line = 'pick %s %d %s' % (c, c.rev(), summary)
779 line = 'pick %s %d %s' % (c, c.rev(), summary)
780 # trim to 80 columns so it's not stupidly wide in my editor
780 # trim to 80 columns so it's not stupidly wide in my editor
781 return util.ellipsis(line, 80)
781 return util.ellipsis(line, 80)
782
782
783 def verifyrules(rules, repo, ctxs):
783 def verifyrules(rules, repo, ctxs):
784 """Verify that there exists exactly one edit rule per given changeset.
784 """Verify that there exists exactly one edit rule per given changeset.
785
785
786 Will abort if there are to many or too few rules, a malformed rule,
786 Will abort if there are to many or too few rules, a malformed rule,
787 or a rule on a changeset outside of the user-given range.
787 or a rule on a changeset outside of the user-given range.
788 """
788 """
789 parsed = []
789 parsed = []
790 expected = set(str(c) for c in ctxs)
790 expected = set(str(c) for c in ctxs)
791 seen = set()
791 seen = set()
792 for r in rules:
792 for r in rules:
793 if ' ' not in r:
793 if ' ' not in r:
794 raise util.Abort(_('malformed line "%s"') % r)
794 raise util.Abort(_('malformed line "%s"') % r)
795 action, rest = r.split(' ', 1)
795 action, rest = r.split(' ', 1)
796 ha = rest.strip().split(' ', 1)[0]
796 ha = rest.strip().split(' ', 1)[0]
797 try:
797 try:
798 ha = str(repo[ha]) # ensure its a short hash
798 ha = str(repo[ha]) # ensure its a short hash
799 except error.RepoError:
799 except error.RepoError:
800 raise util.Abort(_('unknown changeset %s listed') % ha)
800 raise util.Abort(_('unknown changeset %s listed') % ha)
801 if ha not in expected:
801 if ha not in expected:
802 raise util.Abort(
802 raise util.Abort(
803 _('may not use changesets other than the ones listed'))
803 _('may not use changesets other than the ones listed'))
804 if ha in seen:
804 if ha in seen:
805 raise util.Abort(_('duplicated command for changeset %s') % ha)
805 raise util.Abort(_('duplicated command for changeset %s') % ha)
806 seen.add(ha)
806 seen.add(ha)
807 if action not in actiontable:
807 if action not in actiontable:
808 raise util.Abort(_('unknown action "%s"') % action)
808 raise util.Abort(_('unknown action "%s"') % action)
809 parsed.append([action, ha])
809 parsed.append([action, ha])
810 missing = sorted(expected - seen) # sort to stabilize output
810 missing = sorted(expected - seen) # sort to stabilize output
811 if missing:
811 if missing:
812 raise util.Abort(_('missing rules for changeset %s') % missing[0],
812 raise util.Abort(_('missing rules for changeset %s') % missing[0],
813 hint=_('do you want to use the drop action?'))
813 hint=_('do you want to use the drop action?'))
814 return parsed
814 return parsed
815
815
816 def processreplacement(repo, replacements):
816 def processreplacement(repo, replacements):
817 """process the list of replacements to return
817 """process the list of replacements to return
818
818
819 1) the final mapping between original and created nodes
819 1) the final mapping between original and created nodes
820 2) the list of temporary node created by histedit
820 2) the list of temporary node created by histedit
821 3) the list of new commit created by histedit"""
821 3) the list of new commit created by histedit"""
822 allsuccs = set()
822 allsuccs = set()
823 replaced = set()
823 replaced = set()
824 fullmapping = {}
824 fullmapping = {}
825 # initialise basic set
825 # initialise basic set
826 # fullmapping record all operation recorded in replacement
826 # fullmapping record all operation recorded in replacement
827 for rep in replacements:
827 for rep in replacements:
828 allsuccs.update(rep[1])
828 allsuccs.update(rep[1])
829 replaced.add(rep[0])
829 replaced.add(rep[0])
830 fullmapping.setdefault(rep[0], set()).update(rep[1])
830 fullmapping.setdefault(rep[0], set()).update(rep[1])
831 new = allsuccs - replaced
831 new = allsuccs - replaced
832 tmpnodes = allsuccs & replaced
832 tmpnodes = allsuccs & replaced
833 # Reduce content fullmapping into direct relation between original nodes
833 # Reduce content fullmapping into direct relation between original nodes
834 # and final node created during history edition
834 # and final node created during history edition
835 # Dropped changeset are replaced by an empty list
835 # Dropped changeset are replaced by an empty list
836 toproceed = set(fullmapping)
836 toproceed = set(fullmapping)
837 final = {}
837 final = {}
838 while toproceed:
838 while toproceed:
839 for x in list(toproceed):
839 for x in list(toproceed):
840 succs = fullmapping[x]
840 succs = fullmapping[x]
841 for s in list(succs):
841 for s in list(succs):
842 if s in toproceed:
842 if s in toproceed:
843 # non final node with unknown closure
843 # non final node with unknown closure
844 # We can't process this now
844 # We can't process this now
845 break
845 break
846 elif s in final:
846 elif s in final:
847 # non final node, replace with closure
847 # non final node, replace with closure
848 succs.remove(s)
848 succs.remove(s)
849 succs.update(final[s])
849 succs.update(final[s])
850 else:
850 else:
851 final[x] = succs
851 final[x] = succs
852 toproceed.remove(x)
852 toproceed.remove(x)
853 # remove tmpnodes from final mapping
853 # remove tmpnodes from final mapping
854 for n in tmpnodes:
854 for n in tmpnodes:
855 del final[n]
855 del final[n]
856 # we expect all changes involved in final to exist in the repo
856 # we expect all changes involved in final to exist in the repo
857 # turn `final` into list (topologically sorted)
857 # turn `final` into list (topologically sorted)
858 nm = repo.changelog.nodemap
858 nm = repo.changelog.nodemap
859 for prec, succs in final.items():
859 for prec, succs in final.items():
860 final[prec] = sorted(succs, key=nm.get)
860 final[prec] = sorted(succs, key=nm.get)
861
861
862 # computed topmost element (necessary for bookmark)
862 # computed topmost element (necessary for bookmark)
863 if new:
863 if new:
864 newtopmost = sorted(new, key=repo.changelog.rev)[-1]
864 newtopmost = sorted(new, key=repo.changelog.rev)[-1]
865 elif not final:
865 elif not final:
866 # Nothing rewritten at all. we won't need `newtopmost`
866 # Nothing rewritten at all. we won't need `newtopmost`
867 # It is the same as `oldtopmost` and `processreplacement` know it
867 # It is the same as `oldtopmost` and `processreplacement` know it
868 newtopmost = None
868 newtopmost = None
869 else:
869 else:
870 # every body died. The newtopmost is the parent of the root.
870 # every body died. The newtopmost is the parent of the root.
871 newtopmost = repo[sorted(final, key=repo.changelog.rev)[0]].p1().node()
871 newtopmost = repo[sorted(final, key=repo.changelog.rev)[0]].p1().node()
872
872
873 return final, tmpnodes, new, newtopmost
873 return final, tmpnodes, new, newtopmost
874
874
875 def movebookmarks(ui, repo, mapping, oldtopmost, newtopmost):
875 def movebookmarks(ui, repo, mapping, oldtopmost, newtopmost):
876 """Move bookmark from old to newly created node"""
876 """Move bookmark from old to newly created node"""
877 if not mapping:
877 if not mapping:
878 # if nothing got rewritten there is not purpose for this function
878 # if nothing got rewritten there is not purpose for this function
879 return
879 return
880 moves = []
880 moves = []
881 for bk, old in sorted(repo._bookmarks.iteritems()):
881 for bk, old in sorted(repo._bookmarks.iteritems()):
882 if old == oldtopmost:
882 if old == oldtopmost:
883 # special case ensure bookmark stay on tip.
883 # special case ensure bookmark stay on tip.
884 #
884 #
885 # This is arguably a feature and we may only want that for the
885 # This is arguably a feature and we may only want that for the
886 # active bookmark. But the behavior is kept compatible with the old
886 # active bookmark. But the behavior is kept compatible with the old
887 # version for now.
887 # version for now.
888 moves.append((bk, newtopmost))
888 moves.append((bk, newtopmost))
889 continue
889 continue
890 base = old
890 base = old
891 new = mapping.get(base, None)
891 new = mapping.get(base, None)
892 if new is None:
892 if new is None:
893 continue
893 continue
894 while not new:
894 while not new:
895 # base is killed, trying with parent
895 # base is killed, trying with parent
896 base = repo[base].p1().node()
896 base = repo[base].p1().node()
897 new = mapping.get(base, (base,))
897 new = mapping.get(base, (base,))
898 # nothing to move
898 # nothing to move
899 moves.append((bk, new[-1]))
899 moves.append((bk, new[-1]))
900 if moves:
900 if moves:
901 marks = repo._bookmarks
901 marks = repo._bookmarks
902 for mark, new in moves:
902 for mark, new in moves:
903 old = marks[mark]
903 old = marks[mark]
904 ui.note(_('histedit: moving bookmarks %s from %s to %s\n')
904 ui.note(_('histedit: moving bookmarks %s from %s to %s\n')
905 % (mark, node.short(old), node.short(new)))
905 % (mark, node.short(old), node.short(new)))
906 marks[mark] = new
906 marks[mark] = new
907 marks.write()
907 marks.write()
908
908
909 def cleanupnode(ui, repo, name, nodes):
909 def cleanupnode(ui, repo, name, nodes):
910 """strip a group of nodes from the repository
910 """strip a group of nodes from the repository
911
911
912 The set of node to strip may contains unknown nodes."""
912 The set of node to strip may contains unknown nodes."""
913 ui.debug('should strip %s nodes %s\n' %
913 ui.debug('should strip %s nodes %s\n' %
914 (name, ', '.join([node.short(n) for n in nodes])))
914 (name, ', '.join([node.short(n) for n in nodes])))
915 lock = None
915 lock = None
916 try:
916 try:
917 lock = repo.lock()
917 lock = repo.lock()
918 # Find all node that need to be stripped
918 # Find all node that need to be stripped
919 # (we hg %lr instead of %ln to silently ignore unknown item
919 # (we hg %lr instead of %ln to silently ignore unknown item
920 nm = repo.changelog.nodemap
920 nm = repo.changelog.nodemap
921 nodes = sorted(n for n in nodes if n in nm)
921 nodes = sorted(n for n in nodes if n in nm)
922 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
922 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
923 for c in roots:
923 for c in roots:
924 # We should process node in reverse order to strip tip most first.
924 # We should process node in reverse order to strip tip most first.
925 # but this trigger a bug in changegroup hook.
925 # but this trigger a bug in changegroup hook.
926 # This would reduce bundle overhead
926 # This would reduce bundle overhead
927 repair.strip(ui, repo, c)
927 repair.strip(ui, repo, c)
928 finally:
928 finally:
929 release(lock)
929 release(lock)
930
930
931 def summaryhook(ui, repo):
931 def summaryhook(ui, repo):
932 if not os.path.exists(repo.join('histedit-state')):
932 if not os.path.exists(repo.join('histedit-state')):
933 return
933 return
934 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
934 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
935 if rules:
935 if rules:
936 # i18n: column positioning for "hg summary"
936 # i18n: column positioning for "hg summary"
937 ui.write(_('hist: %s (histedit --continue)\n') %
937 ui.write(_('hist: %s (histedit --continue)\n') %
938 (ui.label(_('%d remaining'), 'histedit.remaining') %
938 (ui.label(_('%d remaining'), 'histedit.remaining') %
939 len(rules)))
939 len(rules)))
940
940
941 def extsetup(ui):
941 def extsetup(ui):
942 cmdutil.summaryhooks.add('histedit', summaryhook)
942 cmdutil.summaryhooks.add('histedit', summaryhook)
943 cmdutil.unfinishedstates.append(
943 cmdutil.unfinishedstates.append(
944 ['histedit-state', False, True, _('histedit in progress'),
944 ['histedit-state', False, True, _('histedit in progress'),
945 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
945 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
@@ -1,1024 +1,1025 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 import copies
19 from mercurial import copies
20 from mercurial.commands import templateopts
20 from mercurial.commands import templateopts
21 from mercurial.node import nullrev
21 from mercurial.node import nullrev
22 from mercurial.lock import release
22 from mercurial.lock import release
23 from mercurial.i18n import _
23 from mercurial.i18n import _
24 import os, errno
24 import os, errno
25
25
26 nullmerge = -2
26 nullmerge = -2
27 revignored = -3
27 revignored = -3
28
28
29 cmdtable = {}
29 cmdtable = {}
30 command = cmdutil.command(cmdtable)
30 command = cmdutil.command(cmdtable)
31 testedwith = 'internal'
31 testedwith = 'internal'
32
32
33 def _savegraft(ctx, extra):
33 def _savegraft(ctx, extra):
34 s = ctx.extra().get('source', None)
34 s = ctx.extra().get('source', None)
35 if s is not None:
35 if s is not None:
36 extra['source'] = s
36 extra['source'] = s
37
37
38 def _savebranch(ctx, extra):
38 def _savebranch(ctx, extra):
39 extra['branch'] = ctx.branch()
39 extra['branch'] = ctx.branch()
40
40
41 def _makeextrafn(copiers):
41 def _makeextrafn(copiers):
42 """make an extrafn out of the given copy-functions.
42 """make an extrafn out of the given copy-functions.
43
43
44 A copy function takes a context and an extra dict, and mutates the
44 A copy function takes a context and an extra dict, and mutates the
45 extra dict as needed based on the given context.
45 extra dict as needed based on the given context.
46 """
46 """
47 def extrafn(ctx, extra):
47 def extrafn(ctx, extra):
48 for c in copiers:
48 for c in copiers:
49 c(ctx, extra)
49 c(ctx, extra)
50 return extrafn
50 return extrafn
51
51
52 @command('rebase',
52 @command('rebase',
53 [('s', 'source', '',
53 [('s', 'source', '',
54 _('rebase the specified changeset and descendants'), _('REV')),
54 _('rebase the specified changeset and descendants'), _('REV')),
55 ('b', 'base', '',
55 ('b', 'base', '',
56 _('rebase everything from branching point of specified changeset'),
56 _('rebase everything from branching point of specified changeset'),
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 ('i', 'interactive', False, _('(DEPRECATED)')),
72 ('i', 'interactive', False, _('(DEPRECATED)')),
73 ('t', 'tool', '', _('specify merge tool')),
73 ('t', 'tool', '', _('specify merge tool')),
74 ('c', 'continue', False, _('continue an interrupted rebase')),
74 ('c', 'continue', False, _('continue an interrupted rebase')),
75 ('a', 'abort', False, _('abort an interrupted rebase'))] +
75 ('a', 'abort', False, _('abort an interrupted rebase'))] +
76 templateopts,
76 templateopts,
77 _('[-s REV | -b REV] [-d REV] [OPTION]'))
77 _('[-s REV | -b REV] [-d REV] [OPTION]'))
78 def rebase(ui, repo, **opts):
78 def rebase(ui, repo, **opts):
79 """move changeset (and descendants) to a different branch
79 """move changeset (and descendants) to a different branch
80
80
81 Rebase uses repeated merging to graft changesets from one part of
81 Rebase uses repeated merging to graft changesets from one part of
82 history (the source) onto another (the destination). This can be
82 history (the source) onto another (the destination). This can be
83 useful for linearizing *local* changes relative to a master
83 useful for linearizing *local* changes relative to a master
84 development tree.
84 development tree.
85
85
86 You should not rebase changesets that have already been shared
86 You should not rebase changesets that have already been shared
87 with others. Doing so will force everybody else to perform the
87 with others. Doing so will force everybody else to perform the
88 same rebase or they will end up with duplicated changesets after
88 same rebase or they will end up with duplicated changesets after
89 pulling in your rebased changesets.
89 pulling in your rebased changesets.
90
90
91 In its default configuration, Mercurial will prevent you from
91 In its default configuration, Mercurial will prevent you from
92 rebasing published changes. See :hg:`help phases` for details.
92 rebasing published changes. See :hg:`help phases` for details.
93
93
94 If you don't specify a destination changeset (``-d/--dest``),
94 If you don't specify a destination changeset (``-d/--dest``),
95 rebase uses the current branch tip as the destination. (The
95 rebase uses the current branch tip as the destination. (The
96 destination changeset is not modified by rebasing, but new
96 destination changeset is not modified by rebasing, but new
97 changesets are added as its descendants.)
97 changesets are added as its descendants.)
98
98
99 You can specify which changesets to rebase in two ways: as a
99 You can specify which changesets to rebase in two ways: as a
100 "source" changeset or as a "base" changeset. Both are shorthand
100 "source" changeset or as a "base" changeset. Both are shorthand
101 for a topologically related set of changesets (the "source
101 for a topologically related set of changesets (the "source
102 branch"). If you specify source (``-s/--source``), rebase will
102 branch"). If you specify source (``-s/--source``), rebase will
103 rebase that changeset and all of its descendants onto dest. If you
103 rebase that changeset and all of its descendants onto dest. If you
104 specify base (``-b/--base``), rebase will select ancestors of base
104 specify base (``-b/--base``), rebase will select ancestors of base
105 back to but not including the common ancestor with dest. Thus,
105 back to but not including the common ancestor with dest. Thus,
106 ``-b`` is less precise but more convenient than ``-s``: you can
106 ``-b`` is less precise but more convenient than ``-s``: you can
107 specify any changeset in the source branch, and rebase will select
107 specify any changeset in the source branch, and rebase will select
108 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
108 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
109 uses the parent of the working directory as the base.
109 uses the parent of the working directory as the base.
110
110
111 For advanced usage, a third way is available through the ``--rev``
111 For advanced usage, a third way is available through the ``--rev``
112 option. It allows you to specify an arbitrary set of changesets to
112 option. It allows you to specify an arbitrary set of changesets to
113 rebase. Descendants of revs you specify with this option are not
113 rebase. Descendants of revs you specify with this option are not
114 automatically included in the rebase.
114 automatically included in the rebase.
115
115
116 By default, rebase recreates the changesets in the source branch
116 By default, rebase recreates the changesets in the source branch
117 as descendants of dest and then destroys the originals. Use
117 as descendants of dest and then destroys the originals. Use
118 ``--keep`` to preserve the original source changesets. Some
118 ``--keep`` to preserve the original source changesets. Some
119 changesets in the source branch (e.g. merges from the destination
119 changesets in the source branch (e.g. merges from the destination
120 branch) may be dropped if they no longer contribute any change.
120 branch) may be dropped if they no longer contribute any change.
121
121
122 One result of the rules for selecting the destination changeset
122 One result of the rules for selecting the destination changeset
123 and source branch is that, unlike ``merge``, rebase will do
123 and source branch is that, unlike ``merge``, rebase will do
124 nothing if you are at the branch tip of a named branch
124 nothing if you are at the branch tip of a named branch
125 with two heads. You need to explicitly specify source and/or
125 with two heads. You need to explicitly specify source and/or
126 destination (or ``update`` to the other head, if it's the head of
126 destination (or ``update`` to the other head, if it's the head of
127 the intended source branch).
127 the intended source branch).
128
128
129 If a rebase is interrupted to manually resolve a merge, it can be
129 If a rebase is interrupted to manually resolve a merge, it can be
130 continued with --continue/-c or aborted with --abort/-a.
130 continued with --continue/-c or aborted with --abort/-a.
131
131
132 .. container:: verbose
132 .. container:: verbose
133
133
134 Examples:
134 Examples:
135
135
136 - move "local changes" (current commit back to branching point)
136 - move "local changes" (current commit back to branching point)
137 to the current branch tip after a pull::
137 to the current branch tip after a pull::
138
138
139 hg rebase
139 hg rebase
140
140
141 - move a single changeset to the stable branch::
141 - move a single changeset to the stable branch::
142
142
143 hg rebase -r 5f493448 -d stable
143 hg rebase -r 5f493448 -d stable
144
144
145 - splice a commit and all its descendants onto another part of history::
145 - splice a commit and all its descendants onto another part of history::
146
146
147 hg rebase --source c0c3 --dest 4cf9
147 hg rebase --source c0c3 --dest 4cf9
148
148
149 - rebase everything on a branch marked by a bookmark onto the
149 - rebase everything on a branch marked by a bookmark onto the
150 default branch::
150 default branch::
151
151
152 hg rebase --base myfeature --dest default
152 hg rebase --base myfeature --dest default
153
153
154 - collapse a sequence of changes into a single commit::
154 - collapse a sequence of changes into a single commit::
155
155
156 hg rebase --collapse -r 1520:1525 -d .
156 hg rebase --collapse -r 1520:1525 -d .
157
157
158 - move a named branch while preserving its name::
158 - move a named branch while preserving its name::
159
159
160 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
160 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
161
161
162 Returns 0 on success, 1 if nothing to rebase or there are
162 Returns 0 on success, 1 if nothing to rebase or there are
163 unresolved conflicts.
163 unresolved conflicts.
164
164
165 """
165 """
166 originalwd = target = None
166 originalwd = target = None
167 activebookmark = None
167 activebookmark = None
168 external = nullrev
168 external = nullrev
169 state = {}
169 state = {}
170 skipped = set()
170 skipped = set()
171 targetancestors = set()
171 targetancestors = set()
172
172
173
173
174 lock = wlock = None
174 lock = wlock = None
175 try:
175 try:
176 wlock = repo.wlock()
176 wlock = repo.wlock()
177 lock = repo.lock()
177 lock = repo.lock()
178
178
179 # Validate input and define rebasing points
179 # Validate input and define rebasing points
180 destf = opts.get('dest', None)
180 destf = opts.get('dest', None)
181 srcf = opts.get('source', None)
181 srcf = opts.get('source', None)
182 basef = opts.get('base', None)
182 basef = opts.get('base', None)
183 revf = opts.get('rev', [])
183 revf = opts.get('rev', [])
184 contf = opts.get('continue')
184 contf = opts.get('continue')
185 abortf = opts.get('abort')
185 abortf = opts.get('abort')
186 collapsef = opts.get('collapse', False)
186 collapsef = opts.get('collapse', False)
187 collapsemsg = cmdutil.logmessage(ui, opts)
187 collapsemsg = cmdutil.logmessage(ui, opts)
188 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
188 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
189 extrafns = [_savegraft]
189 extrafns = [_savegraft]
190 if e:
190 if e:
191 extrafns = [e]
191 extrafns = [e]
192 keepf = opts.get('keep', False)
192 keepf = opts.get('keep', False)
193 keepbranchesf = opts.get('keepbranches', False)
193 keepbranchesf = opts.get('keepbranches', False)
194 # keepopen is not meant for use on the command line, but by
194 # keepopen is not meant for use on the command line, but by
195 # other extensions
195 # other extensions
196 keepopen = opts.get('keepopen', False)
196 keepopen = opts.get('keepopen', False)
197
197
198 if opts.get('interactive'):
198 if opts.get('interactive'):
199 msg = _("interactive history editing is supported by the "
199 msg = _("interactive history editing is supported by the "
200 "'histedit' extension (see 'hg help histedit')")
200 "'histedit' extension (see 'hg help histedit')")
201 raise util.Abort(msg)
201 raise util.Abort(msg)
202
202
203 if collapsemsg and not collapsef:
203 if collapsemsg and not collapsef:
204 raise util.Abort(
204 raise util.Abort(
205 _('message can only be specified with collapse'))
205 _('message can only be specified with collapse'))
206
206
207 if contf or abortf:
207 if contf or abortf:
208 if contf and abortf:
208 if contf and abortf:
209 raise util.Abort(_('cannot use both abort and continue'))
209 raise util.Abort(_('cannot use both abort and continue'))
210 if collapsef:
210 if collapsef:
211 raise util.Abort(
211 raise util.Abort(
212 _('cannot use collapse with continue or abort'))
212 _('cannot use collapse with continue or abort'))
213 if srcf or basef or destf:
213 if srcf or basef or destf:
214 raise util.Abort(
214 raise util.Abort(
215 _('abort and continue do not allow specifying revisions'))
215 _('abort and continue do not allow specifying revisions'))
216 if opts.get('tool', False):
216 if opts.get('tool', False):
217 ui.warn(_('tool option will be ignored\n'))
217 ui.warn(_('tool option will be ignored\n'))
218
218
219 try:
219 try:
220 (originalwd, target, state, skipped, collapsef, keepf,
220 (originalwd, target, state, skipped, collapsef, keepf,
221 keepbranchesf, external, activebookmark) = restorestatus(repo)
221 keepbranchesf, external, activebookmark) = restorestatus(repo)
222 except error.RepoLookupError:
222 except error.RepoLookupError:
223 if abortf:
223 if abortf:
224 clearstatus(repo)
224 clearstatus(repo)
225 repo.ui.warn(_('rebase aborted (no revision is removed,'
225 repo.ui.warn(_('rebase aborted (no revision is removed,'
226 ' only broken state is cleared)\n'))
226 ' only broken state is cleared)\n'))
227 return 0
227 return 0
228 else:
228 else:
229 msg = _('cannot continue inconsistent rebase')
229 msg = _('cannot continue inconsistent rebase')
230 hint = _('use "hg rebase --abort" to clear broken state')
230 hint = _('use "hg rebase --abort" to clear broken state')
231 raise util.Abort(msg, hint=hint)
231 raise util.Abort(msg, hint=hint)
232 if abortf:
232 if abortf:
233 return abort(repo, originalwd, target, state)
233 return abort(repo, originalwd, target, state)
234 else:
234 else:
235 if srcf and basef:
235 if srcf and basef:
236 raise util.Abort(_('cannot specify both a '
236 raise util.Abort(_('cannot specify both a '
237 'source and a base'))
237 'source and a base'))
238 if revf and basef:
238 if revf and basef:
239 raise util.Abort(_('cannot specify both a '
239 raise util.Abort(_('cannot specify both a '
240 'revision and a base'))
240 'revision and a base'))
241 if revf and srcf:
241 if revf and srcf:
242 raise util.Abort(_('cannot specify both a '
242 raise util.Abort(_('cannot specify both a '
243 'revision and a source'))
243 'revision and a source'))
244
244
245 cmdutil.checkunfinished(repo)
245 cmdutil.checkunfinished(repo)
246 cmdutil.bailifchanged(repo)
246 cmdutil.bailifchanged(repo)
247
247
248 if not destf:
248 if not destf:
249 # Destination defaults to the latest revision in the
249 # Destination defaults to the latest revision in the
250 # current branch
250 # current branch
251 branch = repo[None].branch()
251 branch = repo[None].branch()
252 dest = repo[branch]
252 dest = repo[branch]
253 else:
253 else:
254 dest = scmutil.revsingle(repo, destf)
254 dest = scmutil.revsingle(repo, destf)
255
255
256 if revf:
256 if revf:
257 rebaseset = scmutil.revrange(repo, revf)
257 rebaseset = scmutil.revrange(repo, revf)
258 if not rebaseset:
258 if not rebaseset:
259 ui.status(_('empty "rev" revision set - '
259 ui.status(_('empty "rev" revision set - '
260 'nothing to rebase\n'))
260 'nothing to rebase\n'))
261 return 1
261 return 1
262 elif srcf:
262 elif srcf:
263 src = scmutil.revrange(repo, [srcf])
263 src = scmutil.revrange(repo, [srcf])
264 if not src:
264 if not src:
265 ui.status(_('empty "source" revision set - '
265 ui.status(_('empty "source" revision set - '
266 'nothing to rebase\n'))
266 'nothing to rebase\n'))
267 return 1
267 return 1
268 rebaseset = repo.revs('(%ld)::', src)
268 rebaseset = repo.revs('(%ld)::', src)
269 assert rebaseset
269 assert rebaseset
270 else:
270 else:
271 base = scmutil.revrange(repo, [basef or '.'])
271 base = scmutil.revrange(repo, [basef or '.'])
272 if not base:
272 if not base:
273 ui.status(_('empty "base" revision set - '
273 ui.status(_('empty "base" revision set - '
274 "can't compute rebase set\n"))
274 "can't compute rebase set\n"))
275 return 1
275 return 1
276 rebaseset = repo.revs(
276 rebaseset = repo.revs(
277 '(children(ancestor(%ld, %d)) and ::(%ld))::',
277 '(children(ancestor(%ld, %d)) and ::(%ld))::',
278 base, dest, base)
278 base, dest, base)
279 if not rebaseset:
279 if not rebaseset:
280 # transform to list because smartsets are not comparable to
280 # transform to list because smartsets are not comparable to
281 # lists. This should be improved to honor lazyness of
281 # lists. This should be improved to honor lazyness of
282 # smartset.
282 # smartset.
283 if list(base) == [dest.rev()]:
283 if list(base) == [dest.rev()]:
284 if basef:
284 if basef:
285 ui.status(_('nothing to rebase - %s is both "base"'
285 ui.status(_('nothing to rebase - %s is both "base"'
286 ' and destination\n') % dest)
286 ' and destination\n') % dest)
287 else:
287 else:
288 ui.status(_('nothing to rebase - working directory '
288 ui.status(_('nothing to rebase - working directory '
289 'parent is also destination\n'))
289 'parent is also destination\n'))
290 elif not repo.revs('%ld - ::%d', base, dest):
290 elif not repo.revs('%ld - ::%d', base, dest):
291 if basef:
291 if basef:
292 ui.status(_('nothing to rebase - "base" %s is '
292 ui.status(_('nothing to rebase - "base" %s is '
293 'already an ancestor of destination '
293 'already an ancestor of destination '
294 '%s\n') %
294 '%s\n') %
295 ('+'.join(str(repo[r]) for r in base),
295 ('+'.join(str(repo[r]) for r in base),
296 dest))
296 dest))
297 else:
297 else:
298 ui.status(_('nothing to rebase - working '
298 ui.status(_('nothing to rebase - working '
299 'directory parent is already an '
299 'directory parent is already an '
300 'ancestor of destination %s\n') % dest)
300 'ancestor of destination %s\n') % dest)
301 else: # can it happen?
301 else: # can it happen?
302 ui.status(_('nothing to rebase from %s to %s\n') %
302 ui.status(_('nothing to rebase from %s to %s\n') %
303 ('+'.join(str(repo[r]) for r in base), dest))
303 ('+'.join(str(repo[r]) for r in base), dest))
304 return 1
304 return 1
305
305
306 if (not (keepf or obsolete._enabled)
306 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
307 if (not (keepf or allowunstable)
307 and repo.revs('first(children(%ld) - %ld)',
308 and repo.revs('first(children(%ld) - %ld)',
308 rebaseset, rebaseset)):
309 rebaseset, rebaseset)):
309 raise util.Abort(
310 raise util.Abort(
310 _("can't remove original changesets with"
311 _("can't remove original changesets with"
311 " unrebased descendants"),
312 " unrebased descendants"),
312 hint=_('use --keep to keep original changesets'))
313 hint=_('use --keep to keep original changesets'))
313
314
314 result = buildstate(repo, dest, rebaseset, collapsef)
315 result = buildstate(repo, dest, rebaseset, collapsef)
315 if not result:
316 if not result:
316 # Empty state built, nothing to rebase
317 # Empty state built, nothing to rebase
317 ui.status(_('nothing to rebase\n'))
318 ui.status(_('nothing to rebase\n'))
318 return 1
319 return 1
319
320
320 root = min(rebaseset)
321 root = min(rebaseset)
321 if not keepf and not repo[root].mutable():
322 if not keepf and not repo[root].mutable():
322 raise util.Abort(_("can't rebase immutable changeset %s")
323 raise util.Abort(_("can't rebase immutable changeset %s")
323 % repo[root],
324 % repo[root],
324 hint=_('see hg help phases for details'))
325 hint=_('see hg help phases for details'))
325
326
326 originalwd, target, state = result
327 originalwd, target, state = result
327 if collapsef:
328 if collapsef:
328 targetancestors = repo.changelog.ancestors([target],
329 targetancestors = repo.changelog.ancestors([target],
329 inclusive=True)
330 inclusive=True)
330 external = externalparent(repo, state, targetancestors)
331 external = externalparent(repo, state, targetancestors)
331
332
332 if dest.closesbranch() and not keepbranchesf:
333 if dest.closesbranch() and not keepbranchesf:
333 ui.status(_('reopening closed branch head %s\n') % dest)
334 ui.status(_('reopening closed branch head %s\n') % dest)
334
335
335 if keepbranchesf:
336 if keepbranchesf:
336 # insert _savebranch at the start of extrafns so if
337 # insert _savebranch at the start of extrafns so if
337 # there's a user-provided extrafn it can clobber branch if
338 # there's a user-provided extrafn it can clobber branch if
338 # desired
339 # desired
339 extrafns.insert(0, _savebranch)
340 extrafns.insert(0, _savebranch)
340 if collapsef:
341 if collapsef:
341 branches = set()
342 branches = set()
342 for rev in state:
343 for rev in state:
343 branches.add(repo[rev].branch())
344 branches.add(repo[rev].branch())
344 if len(branches) > 1:
345 if len(branches) > 1:
345 raise util.Abort(_('cannot collapse multiple named '
346 raise util.Abort(_('cannot collapse multiple named '
346 'branches'))
347 'branches'))
347
348
348 # Rebase
349 # Rebase
349 if not targetancestors:
350 if not targetancestors:
350 targetancestors = repo.changelog.ancestors([target], inclusive=True)
351 targetancestors = repo.changelog.ancestors([target], inclusive=True)
351
352
352 # Keep track of the current bookmarks in order to reset them later
353 # Keep track of the current bookmarks in order to reset them later
353 currentbookmarks = repo._bookmarks.copy()
354 currentbookmarks = repo._bookmarks.copy()
354 activebookmark = activebookmark or repo._bookmarkcurrent
355 activebookmark = activebookmark or repo._bookmarkcurrent
355 if activebookmark:
356 if activebookmark:
356 bookmarks.unsetcurrent(repo)
357 bookmarks.unsetcurrent(repo)
357
358
358 extrafn = _makeextrafn(extrafns)
359 extrafn = _makeextrafn(extrafns)
359
360
360 sortedstate = sorted(state)
361 sortedstate = sorted(state)
361 total = len(sortedstate)
362 total = len(sortedstate)
362 pos = 0
363 pos = 0
363 for rev in sortedstate:
364 for rev in sortedstate:
364 pos += 1
365 pos += 1
365 if state[rev] == -1:
366 if state[rev] == -1:
366 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
367 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
367 _('changesets'), total)
368 _('changesets'), total)
368 p1, p2 = defineparents(repo, rev, target, state,
369 p1, p2 = defineparents(repo, rev, target, state,
369 targetancestors)
370 targetancestors)
370 storestatus(repo, originalwd, target, state, collapsef, keepf,
371 storestatus(repo, originalwd, target, state, collapsef, keepf,
371 keepbranchesf, external, activebookmark)
372 keepbranchesf, external, activebookmark)
372 if len(repo.parents()) == 2:
373 if len(repo.parents()) == 2:
373 repo.ui.debug('resuming interrupted rebase\n')
374 repo.ui.debug('resuming interrupted rebase\n')
374 else:
375 else:
375 try:
376 try:
376 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
377 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
377 'rebase')
378 'rebase')
378 stats = rebasenode(repo, rev, p1, state, collapsef,
379 stats = rebasenode(repo, rev, p1, state, collapsef,
379 target)
380 target)
380 if stats and stats[3] > 0:
381 if stats and stats[3] > 0:
381 raise error.InterventionRequired(
382 raise error.InterventionRequired(
382 _('unresolved conflicts (see hg '
383 _('unresolved conflicts (see hg '
383 'resolve, then hg rebase --continue)'))
384 'resolve, then hg rebase --continue)'))
384 finally:
385 finally:
385 ui.setconfig('ui', 'forcemerge', '', 'rebase')
386 ui.setconfig('ui', 'forcemerge', '', 'rebase')
386 if not collapsef:
387 if not collapsef:
387 merging = repo[p2].rev() != nullrev
388 merging = repo[p2].rev() != nullrev
388 editform = cmdutil.mergeeditform(merging, 'rebase')
389 editform = cmdutil.mergeeditform(merging, 'rebase')
389 editor = cmdutil.getcommiteditor(editform=editform, **opts)
390 editor = cmdutil.getcommiteditor(editform=editform, **opts)
390 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
391 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
391 editor=editor)
392 editor=editor)
392 else:
393 else:
393 # Skip commit if we are collapsing
394 # Skip commit if we are collapsing
394 repo.dirstate.beginparentchange()
395 repo.dirstate.beginparentchange()
395 repo.setparents(repo[p1].node())
396 repo.setparents(repo[p1].node())
396 repo.dirstate.endparentchange()
397 repo.dirstate.endparentchange()
397 newrev = None
398 newrev = None
398 # Update the state
399 # Update the state
399 if newrev is not None:
400 if newrev is not None:
400 state[rev] = repo[newrev].rev()
401 state[rev] = repo[newrev].rev()
401 else:
402 else:
402 if not collapsef:
403 if not collapsef:
403 ui.note(_('no changes, revision %d skipped\n') % rev)
404 ui.note(_('no changes, revision %d skipped\n') % rev)
404 ui.debug('next revision set to %s\n' % p1)
405 ui.debug('next revision set to %s\n' % p1)
405 skipped.add(rev)
406 skipped.add(rev)
406 state[rev] = p1
407 state[rev] = p1
407
408
408 ui.progress(_('rebasing'), None)
409 ui.progress(_('rebasing'), None)
409 ui.note(_('rebase merging completed\n'))
410 ui.note(_('rebase merging completed\n'))
410
411
411 if collapsef and not keepopen:
412 if collapsef and not keepopen:
412 p1, p2 = defineparents(repo, min(state), target,
413 p1, p2 = defineparents(repo, min(state), target,
413 state, targetancestors)
414 state, targetancestors)
414 editopt = opts.get('edit')
415 editopt = opts.get('edit')
415 editform = 'rebase.collapse'
416 editform = 'rebase.collapse'
416 if collapsemsg:
417 if collapsemsg:
417 commitmsg = collapsemsg
418 commitmsg = collapsemsg
418 else:
419 else:
419 commitmsg = 'Collapsed revision'
420 commitmsg = 'Collapsed revision'
420 for rebased in state:
421 for rebased in state:
421 if rebased not in skipped and state[rebased] > nullmerge:
422 if rebased not in skipped and state[rebased] > nullmerge:
422 commitmsg += '\n* %s' % repo[rebased].description()
423 commitmsg += '\n* %s' % repo[rebased].description()
423 editopt = True
424 editopt = True
424 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
425 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
425 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
426 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
426 extrafn=extrafn, editor=editor)
427 extrafn=extrafn, editor=editor)
427 for oldrev in state.iterkeys():
428 for oldrev in state.iterkeys():
428 if state[oldrev] > nullmerge:
429 if state[oldrev] > nullmerge:
429 state[oldrev] = newrev
430 state[oldrev] = newrev
430
431
431 if 'qtip' in repo.tags():
432 if 'qtip' in repo.tags():
432 updatemq(repo, state, skipped, **opts)
433 updatemq(repo, state, skipped, **opts)
433
434
434 if currentbookmarks:
435 if currentbookmarks:
435 # Nodeids are needed to reset bookmarks
436 # Nodeids are needed to reset bookmarks
436 nstate = {}
437 nstate = {}
437 for k, v in state.iteritems():
438 for k, v in state.iteritems():
438 if v > nullmerge:
439 if v > nullmerge:
439 nstate[repo[k].node()] = repo[v].node()
440 nstate[repo[k].node()] = repo[v].node()
440 # XXX this is the same as dest.node() for the non-continue path --
441 # XXX this is the same as dest.node() for the non-continue path --
441 # this should probably be cleaned up
442 # this should probably be cleaned up
442 targetnode = repo[target].node()
443 targetnode = repo[target].node()
443
444
444 # restore original working directory
445 # restore original working directory
445 # (we do this before stripping)
446 # (we do this before stripping)
446 newwd = state.get(originalwd, originalwd)
447 newwd = state.get(originalwd, originalwd)
447 if newwd not in [c.rev() for c in repo[None].parents()]:
448 if newwd not in [c.rev() for c in repo[None].parents()]:
448 ui.note(_("update back to initial working directory parent\n"))
449 ui.note(_("update back to initial working directory parent\n"))
449 hg.updaterepo(repo, newwd, False)
450 hg.updaterepo(repo, newwd, False)
450
451
451 if not keepf:
452 if not keepf:
452 collapsedas = None
453 collapsedas = None
453 if collapsef:
454 if collapsef:
454 collapsedas = newrev
455 collapsedas = newrev
455 clearrebased(ui, repo, state, skipped, collapsedas)
456 clearrebased(ui, repo, state, skipped, collapsedas)
456
457
457 if currentbookmarks:
458 if currentbookmarks:
458 updatebookmarks(repo, targetnode, nstate, currentbookmarks)
459 updatebookmarks(repo, targetnode, nstate, currentbookmarks)
459 if activebookmark not in repo._bookmarks:
460 if activebookmark not in repo._bookmarks:
460 # active bookmark was divergent one and has been deleted
461 # active bookmark was divergent one and has been deleted
461 activebookmark = None
462 activebookmark = None
462
463
463 clearstatus(repo)
464 clearstatus(repo)
464 ui.note(_("rebase completed\n"))
465 ui.note(_("rebase completed\n"))
465 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
466 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
466 if skipped:
467 if skipped:
467 ui.note(_("%d revisions have been skipped\n") % len(skipped))
468 ui.note(_("%d revisions have been skipped\n") % len(skipped))
468
469
469 if (activebookmark and
470 if (activebookmark and
470 repo['.'].node() == repo._bookmarks[activebookmark]):
471 repo['.'].node() == repo._bookmarks[activebookmark]):
471 bookmarks.setcurrent(repo, activebookmark)
472 bookmarks.setcurrent(repo, activebookmark)
472
473
473 finally:
474 finally:
474 release(lock, wlock)
475 release(lock, wlock)
475
476
476 def externalparent(repo, state, targetancestors):
477 def externalparent(repo, state, targetancestors):
477 """Return the revision that should be used as the second parent
478 """Return the revision that should be used as the second parent
478 when the revisions in state is collapsed on top of targetancestors.
479 when the revisions in state is collapsed on top of targetancestors.
479 Abort if there is more than one parent.
480 Abort if there is more than one parent.
480 """
481 """
481 parents = set()
482 parents = set()
482 source = min(state)
483 source = min(state)
483 for rev in state:
484 for rev in state:
484 if rev == source:
485 if rev == source:
485 continue
486 continue
486 for p in repo[rev].parents():
487 for p in repo[rev].parents():
487 if (p.rev() not in state
488 if (p.rev() not in state
488 and p.rev() not in targetancestors):
489 and p.rev() not in targetancestors):
489 parents.add(p.rev())
490 parents.add(p.rev())
490 if not parents:
491 if not parents:
491 return nullrev
492 return nullrev
492 if len(parents) == 1:
493 if len(parents) == 1:
493 return parents.pop()
494 return parents.pop()
494 raise util.Abort(_('unable to collapse on top of %s, there is more '
495 raise util.Abort(_('unable to collapse on top of %s, there is more '
495 'than one external parent: %s') %
496 'than one external parent: %s') %
496 (max(targetancestors),
497 (max(targetancestors),
497 ', '.join(str(p) for p in sorted(parents))))
498 ', '.join(str(p) for p in sorted(parents))))
498
499
499 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
500 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
500 'Commit the changes and store useful information in extra'
501 'Commit the changes and store useful information in extra'
501 try:
502 try:
502 repo.dirstate.beginparentchange()
503 repo.dirstate.beginparentchange()
503 repo.setparents(repo[p1].node(), repo[p2].node())
504 repo.setparents(repo[p1].node(), repo[p2].node())
504 repo.dirstate.endparentchange()
505 repo.dirstate.endparentchange()
505 ctx = repo[rev]
506 ctx = repo[rev]
506 if commitmsg is None:
507 if commitmsg is None:
507 commitmsg = ctx.description()
508 commitmsg = ctx.description()
508 extra = {'rebase_source': ctx.hex()}
509 extra = {'rebase_source': ctx.hex()}
509 if extrafn:
510 if extrafn:
510 extrafn(ctx, extra)
511 extrafn(ctx, extra)
511
512
512 backup = repo.ui.backupconfig('phases', 'new-commit')
513 backup = repo.ui.backupconfig('phases', 'new-commit')
513 try:
514 try:
514 targetphase = max(ctx.phase(), phases.draft)
515 targetphase = max(ctx.phase(), phases.draft)
515 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
516 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
516 # Commit might fail if unresolved files exist
517 # Commit might fail if unresolved files exist
517 newrev = repo.commit(text=commitmsg, user=ctx.user(),
518 newrev = repo.commit(text=commitmsg, user=ctx.user(),
518 date=ctx.date(), extra=extra, editor=editor)
519 date=ctx.date(), extra=extra, editor=editor)
519 finally:
520 finally:
520 repo.ui.restoreconfig(backup)
521 repo.ui.restoreconfig(backup)
521
522
522 repo.dirstate.setbranch(repo[newrev].branch())
523 repo.dirstate.setbranch(repo[newrev].branch())
523 return newrev
524 return newrev
524 except util.Abort:
525 except util.Abort:
525 # Invalidate the previous setparents
526 # Invalidate the previous setparents
526 repo.dirstate.invalidate()
527 repo.dirstate.invalidate()
527 raise
528 raise
528
529
529 def rebasenode(repo, rev, p1, state, collapse, target):
530 def rebasenode(repo, rev, p1, state, collapse, target):
530 'Rebase a single revision'
531 'Rebase a single revision'
531 # Merge phase
532 # Merge phase
532 # Update to target and merge it with local
533 # Update to target and merge it with local
533 if repo['.'].rev() != repo[p1].rev():
534 if repo['.'].rev() != repo[p1].rev():
534 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
535 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
535 merge.update(repo, p1, False, True, False)
536 merge.update(repo, p1, False, True, False)
536 else:
537 else:
537 repo.ui.debug(" already in target\n")
538 repo.ui.debug(" already in target\n")
538 repo.dirstate.write()
539 repo.dirstate.write()
539 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
540 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
540 if repo[rev].rev() == repo[min(state)].rev():
541 if repo[rev].rev() == repo[min(state)].rev():
541 # Case (1) initial changeset of a non-detaching rebase.
542 # Case (1) initial changeset of a non-detaching rebase.
542 # Let the merge mechanism find the base itself.
543 # Let the merge mechanism find the base itself.
543 base = None
544 base = None
544 elif not repo[rev].p2():
545 elif not repo[rev].p2():
545 # Case (2) detaching the node with a single parent, use this parent
546 # Case (2) detaching the node with a single parent, use this parent
546 base = repo[rev].p1().node()
547 base = repo[rev].p1().node()
547 else:
548 else:
548 # In case of merge, we need to pick the right parent as merge base.
549 # In case of merge, we need to pick the right parent as merge base.
549 #
550 #
550 # Imagine we have:
551 # Imagine we have:
551 # - M: currently rebase revision in this step
552 # - M: currently rebase revision in this step
552 # - A: one parent of M
553 # - A: one parent of M
553 # - B: second parent of M
554 # - B: second parent of M
554 # - D: destination of this merge step (p1 var)
555 # - D: destination of this merge step (p1 var)
555 #
556 #
556 # If we are rebasing on D, D is the successors of A or B. The right
557 # If we are rebasing on D, D is the successors of A or B. The right
557 # merge base is the one D succeed to. We pretend it is B for the rest
558 # merge base is the one D succeed to. We pretend it is B for the rest
558 # of this comment
559 # of this comment
559 #
560 #
560 # If we pick B as the base, the merge involves:
561 # If we pick B as the base, the merge involves:
561 # - changes from B to M (actual changeset payload)
562 # - changes from B to M (actual changeset payload)
562 # - changes from B to D (induced by rebase) as D is a rebased
563 # - changes from B to D (induced by rebase) as D is a rebased
563 # version of B)
564 # version of B)
564 # Which exactly represent the rebase operation.
565 # Which exactly represent the rebase operation.
565 #
566 #
566 # If we pick the A as the base, the merge involves
567 # If we pick the A as the base, the merge involves
567 # - changes from A to M (actual changeset payload)
568 # - changes from A to M (actual changeset payload)
568 # - changes from A to D (with include changes between unrelated A and B
569 # - changes from A to D (with include changes between unrelated A and B
569 # plus changes induced by rebase)
570 # plus changes induced by rebase)
570 # Which does not represent anything sensible and creates a lot of
571 # Which does not represent anything sensible and creates a lot of
571 # conflicts.
572 # conflicts.
572 for p in repo[rev].parents():
573 for p in repo[rev].parents():
573 if state.get(p.rev()) == repo[p1].rev():
574 if state.get(p.rev()) == repo[p1].rev():
574 base = p.node()
575 base = p.node()
575 break
576 break
576 else: # fallback when base not found
577 else: # fallback when base not found
577 base = None
578 base = None
578
579
579 # Raise because this function is called wrong (see issue 4106)
580 # Raise because this function is called wrong (see issue 4106)
580 raise AssertionError('no base found to rebase on '
581 raise AssertionError('no base found to rebase on '
581 '(rebasenode called wrong)')
582 '(rebasenode called wrong)')
582 if base is not None:
583 if base is not None:
583 repo.ui.debug(" detach base %d:%s\n" % (repo[base].rev(), repo[base]))
584 repo.ui.debug(" detach base %d:%s\n" % (repo[base].rev(), repo[base]))
584 # When collapsing in-place, the parent is the common ancestor, we
585 # When collapsing in-place, the parent is the common ancestor, we
585 # have to allow merging with it.
586 # have to allow merging with it.
586 stats = merge.update(repo, rev, True, True, False, base, collapse,
587 stats = merge.update(repo, rev, True, True, False, base, collapse,
587 labels=['dest', 'source'])
588 labels=['dest', 'source'])
588 if collapse:
589 if collapse:
589 copies.duplicatecopies(repo, rev, target)
590 copies.duplicatecopies(repo, rev, target)
590 else:
591 else:
591 # If we're not using --collapse, we need to
592 # If we're not using --collapse, we need to
592 # duplicate copies between the revision we're
593 # duplicate copies between the revision we're
593 # rebasing and its first parent, but *not*
594 # rebasing and its first parent, but *not*
594 # duplicate any copies that have already been
595 # duplicate any copies that have already been
595 # performed in the destination.
596 # performed in the destination.
596 p1rev = repo[rev].p1().rev()
597 p1rev = repo[rev].p1().rev()
597 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
598 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
598 return stats
599 return stats
599
600
600 def nearestrebased(repo, rev, state):
601 def nearestrebased(repo, rev, state):
601 """return the nearest ancestors of rev in the rebase result"""
602 """return the nearest ancestors of rev in the rebase result"""
602 rebased = [r for r in state if state[r] > nullmerge]
603 rebased = [r for r in state if state[r] > nullmerge]
603 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
604 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
604 if candidates:
605 if candidates:
605 return state[candidates.first()]
606 return state[candidates.first()]
606 else:
607 else:
607 return None
608 return None
608
609
609 def defineparents(repo, rev, target, state, targetancestors):
610 def defineparents(repo, rev, target, state, targetancestors):
610 'Return the new parent relationship of the revision that will be rebased'
611 'Return the new parent relationship of the revision that will be rebased'
611 parents = repo[rev].parents()
612 parents = repo[rev].parents()
612 p1 = p2 = nullrev
613 p1 = p2 = nullrev
613
614
614 p1n = parents[0].rev()
615 p1n = parents[0].rev()
615 if p1n in targetancestors:
616 if p1n in targetancestors:
616 p1 = target
617 p1 = target
617 elif p1n in state:
618 elif p1n in state:
618 if state[p1n] == nullmerge:
619 if state[p1n] == nullmerge:
619 p1 = target
620 p1 = target
620 elif state[p1n] == revignored:
621 elif state[p1n] == revignored:
621 p1 = nearestrebased(repo, p1n, state)
622 p1 = nearestrebased(repo, p1n, state)
622 if p1 is None:
623 if p1 is None:
623 p1 = target
624 p1 = target
624 else:
625 else:
625 p1 = state[p1n]
626 p1 = state[p1n]
626 else: # p1n external
627 else: # p1n external
627 p1 = target
628 p1 = target
628 p2 = p1n
629 p2 = p1n
629
630
630 if len(parents) == 2 and parents[1].rev() not in targetancestors:
631 if len(parents) == 2 and parents[1].rev() not in targetancestors:
631 p2n = parents[1].rev()
632 p2n = parents[1].rev()
632 # interesting second parent
633 # interesting second parent
633 if p2n in state:
634 if p2n in state:
634 if p1 == target: # p1n in targetancestors or external
635 if p1 == target: # p1n in targetancestors or external
635 p1 = state[p2n]
636 p1 = state[p2n]
636 elif state[p2n] == revignored:
637 elif state[p2n] == revignored:
637 p2 = nearestrebased(repo, p2n, state)
638 p2 = nearestrebased(repo, p2n, state)
638 if p2 is None:
639 if p2 is None:
639 # no ancestors rebased yet, detach
640 # no ancestors rebased yet, detach
640 p2 = target
641 p2 = target
641 else:
642 else:
642 p2 = state[p2n]
643 p2 = state[p2n]
643 else: # p2n external
644 else: # p2n external
644 if p2 != nullrev: # p1n external too => rev is a merged revision
645 if p2 != nullrev: # p1n external too => rev is a merged revision
645 raise util.Abort(_('cannot use revision %d as base, result '
646 raise util.Abort(_('cannot use revision %d as base, result '
646 'would have 3 parents') % rev)
647 'would have 3 parents') % rev)
647 p2 = p2n
648 p2 = p2n
648 repo.ui.debug(" future parents are %d and %d\n" %
649 repo.ui.debug(" future parents are %d and %d\n" %
649 (repo[p1].rev(), repo[p2].rev()))
650 (repo[p1].rev(), repo[p2].rev()))
650 return p1, p2
651 return p1, p2
651
652
652 def isagitpatch(repo, patchname):
653 def isagitpatch(repo, patchname):
653 'Return true if the given patch is in git format'
654 'Return true if the given patch is in git format'
654 mqpatch = os.path.join(repo.mq.path, patchname)
655 mqpatch = os.path.join(repo.mq.path, patchname)
655 for line in patch.linereader(file(mqpatch, 'rb')):
656 for line in patch.linereader(file(mqpatch, 'rb')):
656 if line.startswith('diff --git'):
657 if line.startswith('diff --git'):
657 return True
658 return True
658 return False
659 return False
659
660
660 def updatemq(repo, state, skipped, **opts):
661 def updatemq(repo, state, skipped, **opts):
661 'Update rebased mq patches - finalize and then import them'
662 'Update rebased mq patches - finalize and then import them'
662 mqrebase = {}
663 mqrebase = {}
663 mq = repo.mq
664 mq = repo.mq
664 original_series = mq.fullseries[:]
665 original_series = mq.fullseries[:]
665 skippedpatches = set()
666 skippedpatches = set()
666
667
667 for p in mq.applied:
668 for p in mq.applied:
668 rev = repo[p.node].rev()
669 rev = repo[p.node].rev()
669 if rev in state:
670 if rev in state:
670 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
671 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
671 (rev, p.name))
672 (rev, p.name))
672 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
673 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
673 else:
674 else:
674 # Applied but not rebased, not sure this should happen
675 # Applied but not rebased, not sure this should happen
675 skippedpatches.add(p.name)
676 skippedpatches.add(p.name)
676
677
677 if mqrebase:
678 if mqrebase:
678 mq.finish(repo, mqrebase.keys())
679 mq.finish(repo, mqrebase.keys())
679
680
680 # We must start import from the newest revision
681 # We must start import from the newest revision
681 for rev in sorted(mqrebase, reverse=True):
682 for rev in sorted(mqrebase, reverse=True):
682 if rev not in skipped:
683 if rev not in skipped:
683 name, isgit = mqrebase[rev]
684 name, isgit = mqrebase[rev]
684 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
685 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
685 mq.qimport(repo, (), patchname=name, git=isgit,
686 mq.qimport(repo, (), patchname=name, git=isgit,
686 rev=[str(state[rev])])
687 rev=[str(state[rev])])
687 else:
688 else:
688 # Rebased and skipped
689 # Rebased and skipped
689 skippedpatches.add(mqrebase[rev][0])
690 skippedpatches.add(mqrebase[rev][0])
690
691
691 # Patches were either applied and rebased and imported in
692 # Patches were either applied and rebased and imported in
692 # order, applied and removed or unapplied. Discard the removed
693 # order, applied and removed or unapplied. Discard the removed
693 # ones while preserving the original series order and guards.
694 # ones while preserving the original series order and guards.
694 newseries = [s for s in original_series
695 newseries = [s for s in original_series
695 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
696 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
696 mq.fullseries[:] = newseries
697 mq.fullseries[:] = newseries
697 mq.seriesdirty = True
698 mq.seriesdirty = True
698 mq.savedirty()
699 mq.savedirty()
699
700
700 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
701 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
701 'Move bookmarks to their correct changesets, and delete divergent ones'
702 'Move bookmarks to their correct changesets, and delete divergent ones'
702 marks = repo._bookmarks
703 marks = repo._bookmarks
703 for k, v in originalbookmarks.iteritems():
704 for k, v in originalbookmarks.iteritems():
704 if v in nstate:
705 if v in nstate:
705 # update the bookmarks for revs that have moved
706 # update the bookmarks for revs that have moved
706 marks[k] = nstate[v]
707 marks[k] = nstate[v]
707 bookmarks.deletedivergent(repo, [targetnode], k)
708 bookmarks.deletedivergent(repo, [targetnode], k)
708
709
709 marks.write()
710 marks.write()
710
711
711 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
712 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
712 external, activebookmark):
713 external, activebookmark):
713 'Store the current status to allow recovery'
714 'Store the current status to allow recovery'
714 f = repo.opener("rebasestate", "w")
715 f = repo.opener("rebasestate", "w")
715 f.write(repo[originalwd].hex() + '\n')
716 f.write(repo[originalwd].hex() + '\n')
716 f.write(repo[target].hex() + '\n')
717 f.write(repo[target].hex() + '\n')
717 f.write(repo[external].hex() + '\n')
718 f.write(repo[external].hex() + '\n')
718 f.write('%d\n' % int(collapse))
719 f.write('%d\n' % int(collapse))
719 f.write('%d\n' % int(keep))
720 f.write('%d\n' % int(keep))
720 f.write('%d\n' % int(keepbranches))
721 f.write('%d\n' % int(keepbranches))
721 f.write('%s\n' % (activebookmark or ''))
722 f.write('%s\n' % (activebookmark or ''))
722 for d, v in state.iteritems():
723 for d, v in state.iteritems():
723 oldrev = repo[d].hex()
724 oldrev = repo[d].hex()
724 if v > nullmerge:
725 if v > nullmerge:
725 newrev = repo[v].hex()
726 newrev = repo[v].hex()
726 else:
727 else:
727 newrev = v
728 newrev = v
728 f.write("%s:%s\n" % (oldrev, newrev))
729 f.write("%s:%s\n" % (oldrev, newrev))
729 f.close()
730 f.close()
730 repo.ui.debug('rebase status stored\n')
731 repo.ui.debug('rebase status stored\n')
731
732
732 def clearstatus(repo):
733 def clearstatus(repo):
733 'Remove the status files'
734 'Remove the status files'
734 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
735 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
735
736
736 def restorestatus(repo):
737 def restorestatus(repo):
737 'Restore a previously stored status'
738 'Restore a previously stored status'
738 try:
739 try:
739 keepbranches = None
740 keepbranches = None
740 target = None
741 target = None
741 collapse = False
742 collapse = False
742 external = nullrev
743 external = nullrev
743 activebookmark = None
744 activebookmark = None
744 state = {}
745 state = {}
745 f = repo.opener("rebasestate")
746 f = repo.opener("rebasestate")
746 for i, l in enumerate(f.read().splitlines()):
747 for i, l in enumerate(f.read().splitlines()):
747 if i == 0:
748 if i == 0:
748 originalwd = repo[l].rev()
749 originalwd = repo[l].rev()
749 elif i == 1:
750 elif i == 1:
750 target = repo[l].rev()
751 target = repo[l].rev()
751 elif i == 2:
752 elif i == 2:
752 external = repo[l].rev()
753 external = repo[l].rev()
753 elif i == 3:
754 elif i == 3:
754 collapse = bool(int(l))
755 collapse = bool(int(l))
755 elif i == 4:
756 elif i == 4:
756 keep = bool(int(l))
757 keep = bool(int(l))
757 elif i == 5:
758 elif i == 5:
758 keepbranches = bool(int(l))
759 keepbranches = bool(int(l))
759 elif i == 6 and not (len(l) == 81 and ':' in l):
760 elif i == 6 and not (len(l) == 81 and ':' in l):
760 # line 6 is a recent addition, so for backwards compatibility
761 # line 6 is a recent addition, so for backwards compatibility
761 # check that the line doesn't look like the oldrev:newrev lines
762 # check that the line doesn't look like the oldrev:newrev lines
762 activebookmark = l
763 activebookmark = l
763 else:
764 else:
764 oldrev, newrev = l.split(':')
765 oldrev, newrev = l.split(':')
765 if newrev in (str(nullmerge), str(revignored)):
766 if newrev in (str(nullmerge), str(revignored)):
766 state[repo[oldrev].rev()] = int(newrev)
767 state[repo[oldrev].rev()] = int(newrev)
767 else:
768 else:
768 state[repo[oldrev].rev()] = repo[newrev].rev()
769 state[repo[oldrev].rev()] = repo[newrev].rev()
769
770
770 if keepbranches is None:
771 if keepbranches is None:
771 raise util.Abort(_('.hg/rebasestate is incomplete'))
772 raise util.Abort(_('.hg/rebasestate is incomplete'))
772
773
773 skipped = set()
774 skipped = set()
774 # recompute the set of skipped revs
775 # recompute the set of skipped revs
775 if not collapse:
776 if not collapse:
776 seen = set([target])
777 seen = set([target])
777 for old, new in sorted(state.items()):
778 for old, new in sorted(state.items()):
778 if new != nullrev and new in seen:
779 if new != nullrev and new in seen:
779 skipped.add(old)
780 skipped.add(old)
780 seen.add(new)
781 seen.add(new)
781 repo.ui.debug('computed skipped revs: %s\n' %
782 repo.ui.debug('computed skipped revs: %s\n' %
782 (' '.join(str(r) for r in sorted(skipped)) or None))
783 (' '.join(str(r) for r in sorted(skipped)) or None))
783 repo.ui.debug('rebase status resumed\n')
784 repo.ui.debug('rebase status resumed\n')
784 return (originalwd, target, state, skipped,
785 return (originalwd, target, state, skipped,
785 collapse, keep, keepbranches, external, activebookmark)
786 collapse, keep, keepbranches, external, activebookmark)
786 except IOError, err:
787 except IOError, err:
787 if err.errno != errno.ENOENT:
788 if err.errno != errno.ENOENT:
788 raise
789 raise
789 raise util.Abort(_('no rebase in progress'))
790 raise util.Abort(_('no rebase in progress'))
790
791
791 def inrebase(repo, originalwd, state):
792 def inrebase(repo, originalwd, state):
792 '''check whether the working dir is in an interrupted rebase'''
793 '''check whether the working dir is in an interrupted rebase'''
793 parents = [p.rev() for p in repo.parents()]
794 parents = [p.rev() for p in repo.parents()]
794 if originalwd in parents:
795 if originalwd in parents:
795 return True
796 return True
796
797
797 for newrev in state.itervalues():
798 for newrev in state.itervalues():
798 if newrev in parents:
799 if newrev in parents:
799 return True
800 return True
800
801
801 return False
802 return False
802
803
803 def abort(repo, originalwd, target, state):
804 def abort(repo, originalwd, target, state):
804 'Restore the repository to its original state'
805 'Restore the repository to its original state'
805 dstates = [s for s in state.values() if s > nullrev]
806 dstates = [s for s in state.values() if s > nullrev]
806 immutable = [d for d in dstates if not repo[d].mutable()]
807 immutable = [d for d in dstates if not repo[d].mutable()]
807 cleanup = True
808 cleanup = True
808 if immutable:
809 if immutable:
809 repo.ui.warn(_("warning: can't clean up immutable changesets %s\n")
810 repo.ui.warn(_("warning: can't clean up immutable changesets %s\n")
810 % ', '.join(str(repo[r]) for r in immutable),
811 % ', '.join(str(repo[r]) for r in immutable),
811 hint=_('see hg help phases for details'))
812 hint=_('see hg help phases for details'))
812 cleanup = False
813 cleanup = False
813
814
814 descendants = set()
815 descendants = set()
815 if dstates:
816 if dstates:
816 descendants = set(repo.changelog.descendants(dstates))
817 descendants = set(repo.changelog.descendants(dstates))
817 if descendants - set(dstates):
818 if descendants - set(dstates):
818 repo.ui.warn(_("warning: new changesets detected on target branch, "
819 repo.ui.warn(_("warning: new changesets detected on target branch, "
819 "can't strip\n"))
820 "can't strip\n"))
820 cleanup = False
821 cleanup = False
821
822
822 if cleanup:
823 if cleanup:
823 # Update away from the rebase if necessary
824 # Update away from the rebase if necessary
824 if inrebase(repo, originalwd, state):
825 if inrebase(repo, originalwd, state):
825 merge.update(repo, repo[originalwd].rev(), False, True, False)
826 merge.update(repo, repo[originalwd].rev(), False, True, False)
826
827
827 # Strip from the first rebased revision
828 # Strip from the first rebased revision
828 rebased = filter(lambda x: x > -1 and x != target, state.values())
829 rebased = filter(lambda x: x > -1 and x != target, state.values())
829 if rebased:
830 if rebased:
830 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
831 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
831 # no backup of rebased cset versions needed
832 # no backup of rebased cset versions needed
832 repair.strip(repo.ui, repo, strippoints)
833 repair.strip(repo.ui, repo, strippoints)
833
834
834 clearstatus(repo)
835 clearstatus(repo)
835 repo.ui.warn(_('rebase aborted\n'))
836 repo.ui.warn(_('rebase aborted\n'))
836 return 0
837 return 0
837
838
838 def buildstate(repo, dest, rebaseset, collapse):
839 def buildstate(repo, dest, rebaseset, collapse):
839 '''Define which revisions are going to be rebased and where
840 '''Define which revisions are going to be rebased and where
840
841
841 repo: repo
842 repo: repo
842 dest: context
843 dest: context
843 rebaseset: set of rev
844 rebaseset: set of rev
844 '''
845 '''
845
846
846 # This check isn't strictly necessary, since mq detects commits over an
847 # This check isn't strictly necessary, since mq detects commits over an
847 # applied patch. But it prevents messing up the working directory when
848 # applied patch. But it prevents messing up the working directory when
848 # a partially completed rebase is blocked by mq.
849 # a partially completed rebase is blocked by mq.
849 if 'qtip' in repo.tags() and (dest.node() in
850 if 'qtip' in repo.tags() and (dest.node() in
850 [s.node for s in repo.mq.applied]):
851 [s.node for s in repo.mq.applied]):
851 raise util.Abort(_('cannot rebase onto an applied mq patch'))
852 raise util.Abort(_('cannot rebase onto an applied mq patch'))
852
853
853 roots = list(repo.set('roots(%ld)', rebaseset))
854 roots = list(repo.set('roots(%ld)', rebaseset))
854 if not roots:
855 if not roots:
855 raise util.Abort(_('no matching revisions'))
856 raise util.Abort(_('no matching revisions'))
856 roots.sort()
857 roots.sort()
857 state = {}
858 state = {}
858 detachset = set()
859 detachset = set()
859 for root in roots:
860 for root in roots:
860 commonbase = root.ancestor(dest)
861 commonbase = root.ancestor(dest)
861 if commonbase == root:
862 if commonbase == root:
862 raise util.Abort(_('source is ancestor of destination'))
863 raise util.Abort(_('source is ancestor of destination'))
863 if commonbase == dest:
864 if commonbase == dest:
864 samebranch = root.branch() == dest.branch()
865 samebranch = root.branch() == dest.branch()
865 if not collapse and samebranch and root in dest.children():
866 if not collapse and samebranch and root in dest.children():
866 repo.ui.debug('source is a child of destination\n')
867 repo.ui.debug('source is a child of destination\n')
867 return None
868 return None
868
869
869 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, root))
870 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, root))
870 state.update(dict.fromkeys(rebaseset, nullrev))
871 state.update(dict.fromkeys(rebaseset, nullrev))
871 # Rebase tries to turn <dest> into a parent of <root> while
872 # Rebase tries to turn <dest> into a parent of <root> while
872 # preserving the number of parents of rebased changesets:
873 # preserving the number of parents of rebased changesets:
873 #
874 #
874 # - A changeset with a single parent will always be rebased as a
875 # - A changeset with a single parent will always be rebased as a
875 # changeset with a single parent.
876 # changeset with a single parent.
876 #
877 #
877 # - A merge will be rebased as merge unless its parents are both
878 # - A merge will be rebased as merge unless its parents are both
878 # ancestors of <dest> or are themselves in the rebased set and
879 # ancestors of <dest> or are themselves in the rebased set and
879 # pruned while rebased.
880 # pruned while rebased.
880 #
881 #
881 # If one parent of <root> is an ancestor of <dest>, the rebased
882 # If one parent of <root> is an ancestor of <dest>, the rebased
882 # version of this parent will be <dest>. This is always true with
883 # version of this parent will be <dest>. This is always true with
883 # --base option.
884 # --base option.
884 #
885 #
885 # Otherwise, we need to *replace* the original parents with
886 # Otherwise, we need to *replace* the original parents with
886 # <dest>. This "detaches" the rebased set from its former location
887 # <dest>. This "detaches" the rebased set from its former location
887 # and rebases it onto <dest>. Changes introduced by ancestors of
888 # and rebases it onto <dest>. Changes introduced by ancestors of
888 # <root> not common with <dest> (the detachset, marked as
889 # <root> not common with <dest> (the detachset, marked as
889 # nullmerge) are "removed" from the rebased changesets.
890 # nullmerge) are "removed" from the rebased changesets.
890 #
891 #
891 # - If <root> has a single parent, set it to <dest>.
892 # - If <root> has a single parent, set it to <dest>.
892 #
893 #
893 # - If <root> is a merge, we cannot decide which parent to
894 # - If <root> is a merge, we cannot decide which parent to
894 # replace, the rebase operation is not clearly defined.
895 # replace, the rebase operation is not clearly defined.
895 #
896 #
896 # The table below sums up this behavior:
897 # The table below sums up this behavior:
897 #
898 #
898 # +------------------+----------------------+-------------------------+
899 # +------------------+----------------------+-------------------------+
899 # | | one parent | merge |
900 # | | one parent | merge |
900 # +------------------+----------------------+-------------------------+
901 # +------------------+----------------------+-------------------------+
901 # | parent in | new parent is <dest> | parents in ::<dest> are |
902 # | parent in | new parent is <dest> | parents in ::<dest> are |
902 # | ::<dest> | | remapped to <dest> |
903 # | ::<dest> | | remapped to <dest> |
903 # +------------------+----------------------+-------------------------+
904 # +------------------+----------------------+-------------------------+
904 # | unrelated source | new parent is <dest> | ambiguous, abort |
905 # | unrelated source | new parent is <dest> | ambiguous, abort |
905 # +------------------+----------------------+-------------------------+
906 # +------------------+----------------------+-------------------------+
906 #
907 #
907 # The actual abort is handled by `defineparents`
908 # The actual abort is handled by `defineparents`
908 if len(root.parents()) <= 1:
909 if len(root.parents()) <= 1:
909 # ancestors of <root> not ancestors of <dest>
910 # ancestors of <root> not ancestors of <dest>
910 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
911 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
911 [root.rev()]))
912 [root.rev()]))
912 for r in detachset:
913 for r in detachset:
913 if r not in state:
914 if r not in state:
914 state[r] = nullmerge
915 state[r] = nullmerge
915 if len(roots) > 1:
916 if len(roots) > 1:
916 # If we have multiple roots, we may have "hole" in the rebase set.
917 # If we have multiple roots, we may have "hole" in the rebase set.
917 # Rebase roots that descend from those "hole" should not be detached as
918 # Rebase roots that descend from those "hole" should not be detached as
918 # other root are. We use the special `revignored` to inform rebase that
919 # other root are. We use the special `revignored` to inform rebase that
919 # the revision should be ignored but that `defineparents` should search
920 # the revision should be ignored but that `defineparents` should search
920 # a rebase destination that make sense regarding rebased topology.
921 # a rebase destination that make sense regarding rebased topology.
921 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
922 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
922 for ignored in set(rebasedomain) - set(rebaseset):
923 for ignored in set(rebasedomain) - set(rebaseset):
923 state[ignored] = revignored
924 state[ignored] = revignored
924 return repo['.'].rev(), dest.rev(), state
925 return repo['.'].rev(), dest.rev(), state
925
926
926 def clearrebased(ui, repo, state, skipped, collapsedas=None):
927 def clearrebased(ui, repo, state, skipped, collapsedas=None):
927 """dispose of rebased revision at the end of the rebase
928 """dispose of rebased revision at the end of the rebase
928
929
929 If `collapsedas` is not None, the rebase was a collapse whose result if the
930 If `collapsedas` is not None, the rebase was a collapse whose result if the
930 `collapsedas` node."""
931 `collapsedas` node."""
931 if obsolete.isenabled(repo, obsolete.createmarkersopt):
932 if obsolete.isenabled(repo, obsolete.createmarkersopt):
932 markers = []
933 markers = []
933 for rev, newrev in sorted(state.items()):
934 for rev, newrev in sorted(state.items()):
934 if newrev >= 0:
935 if newrev >= 0:
935 if rev in skipped:
936 if rev in skipped:
936 succs = ()
937 succs = ()
937 elif collapsedas is not None:
938 elif collapsedas is not None:
938 succs = (repo[collapsedas],)
939 succs = (repo[collapsedas],)
939 else:
940 else:
940 succs = (repo[newrev],)
941 succs = (repo[newrev],)
941 markers.append((repo[rev], succs))
942 markers.append((repo[rev], succs))
942 if markers:
943 if markers:
943 obsolete.createmarkers(repo, markers)
944 obsolete.createmarkers(repo, markers)
944 else:
945 else:
945 rebased = [rev for rev in state if state[rev] > nullmerge]
946 rebased = [rev for rev in state if state[rev] > nullmerge]
946 if rebased:
947 if rebased:
947 stripped = []
948 stripped = []
948 for root in repo.set('roots(%ld)', rebased):
949 for root in repo.set('roots(%ld)', rebased):
949 if set(repo.changelog.descendants([root.rev()])) - set(state):
950 if set(repo.changelog.descendants([root.rev()])) - set(state):
950 ui.warn(_("warning: new changesets detected "
951 ui.warn(_("warning: new changesets detected "
951 "on source branch, not stripping\n"))
952 "on source branch, not stripping\n"))
952 else:
953 else:
953 stripped.append(root.node())
954 stripped.append(root.node())
954 if stripped:
955 if stripped:
955 # backup the old csets by default
956 # backup the old csets by default
956 repair.strip(ui, repo, stripped, "all")
957 repair.strip(ui, repo, stripped, "all")
957
958
958
959
959 def pullrebase(orig, ui, repo, *args, **opts):
960 def pullrebase(orig, ui, repo, *args, **opts):
960 'Call rebase after pull if the latter has been invoked with --rebase'
961 'Call rebase after pull if the latter has been invoked with --rebase'
961 if opts.get('rebase'):
962 if opts.get('rebase'):
962 if opts.get('update'):
963 if opts.get('update'):
963 del opts['update']
964 del opts['update']
964 ui.debug('--update and --rebase are not compatible, ignoring '
965 ui.debug('--update and --rebase are not compatible, ignoring '
965 'the update flag\n')
966 'the update flag\n')
966
967
967 movemarkfrom = repo['.'].node()
968 movemarkfrom = repo['.'].node()
968 revsprepull = len(repo)
969 revsprepull = len(repo)
969 origpostincoming = commands.postincoming
970 origpostincoming = commands.postincoming
970 def _dummy(*args, **kwargs):
971 def _dummy(*args, **kwargs):
971 pass
972 pass
972 commands.postincoming = _dummy
973 commands.postincoming = _dummy
973 try:
974 try:
974 orig(ui, repo, *args, **opts)
975 orig(ui, repo, *args, **opts)
975 finally:
976 finally:
976 commands.postincoming = origpostincoming
977 commands.postincoming = origpostincoming
977 revspostpull = len(repo)
978 revspostpull = len(repo)
978 if revspostpull > revsprepull:
979 if revspostpull > revsprepull:
979 # --rev option from pull conflict with rebase own --rev
980 # --rev option from pull conflict with rebase own --rev
980 # dropping it
981 # dropping it
981 if 'rev' in opts:
982 if 'rev' in opts:
982 del opts['rev']
983 del opts['rev']
983 rebase(ui, repo, **opts)
984 rebase(ui, repo, **opts)
984 branch = repo[None].branch()
985 branch = repo[None].branch()
985 dest = repo[branch].rev()
986 dest = repo[branch].rev()
986 if dest != repo['.'].rev():
987 if dest != repo['.'].rev():
987 # there was nothing to rebase we force an update
988 # there was nothing to rebase we force an update
988 hg.update(repo, dest)
989 hg.update(repo, dest)
989 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
990 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
990 ui.status(_("updating bookmark %s\n")
991 ui.status(_("updating bookmark %s\n")
991 % repo._bookmarkcurrent)
992 % repo._bookmarkcurrent)
992 else:
993 else:
993 if opts.get('tool'):
994 if opts.get('tool'):
994 raise util.Abort(_('--tool can only be used with --rebase'))
995 raise util.Abort(_('--tool can only be used with --rebase'))
995 orig(ui, repo, *args, **opts)
996 orig(ui, repo, *args, **opts)
996
997
997 def summaryhook(ui, repo):
998 def summaryhook(ui, repo):
998 if not os.path.exists(repo.join('rebasestate')):
999 if not os.path.exists(repo.join('rebasestate')):
999 return
1000 return
1000 try:
1001 try:
1001 state = restorestatus(repo)[2]
1002 state = restorestatus(repo)[2]
1002 except error.RepoLookupError:
1003 except error.RepoLookupError:
1003 # i18n: column positioning for "hg summary"
1004 # i18n: column positioning for "hg summary"
1004 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1005 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1005 ui.write(msg)
1006 ui.write(msg)
1006 return
1007 return
1007 numrebased = len([i for i in state.itervalues() if i != -1])
1008 numrebased = len([i for i in state.itervalues() if i != -1])
1008 # i18n: column positioning for "hg summary"
1009 # i18n: column positioning for "hg summary"
1009 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1010 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1010 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1011 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1011 ui.label(_('%d remaining'), 'rebase.remaining') %
1012 ui.label(_('%d remaining'), 'rebase.remaining') %
1012 (len(state) - numrebased)))
1013 (len(state) - numrebased)))
1013
1014
1014 def uisetup(ui):
1015 def uisetup(ui):
1015 'Replace pull with a decorator to provide --rebase option'
1016 'Replace pull with a decorator to provide --rebase option'
1016 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1017 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1017 entry[1].append(('', 'rebase', None,
1018 entry[1].append(('', 'rebase', None,
1018 _("rebase working directory to branch head")))
1019 _("rebase working directory to branch head")))
1019 entry[1].append(('t', 'tool', '',
1020 entry[1].append(('t', 'tool', '',
1020 _("specify merge tool for rebase")))
1021 _("specify merge tool for rebase")))
1021 cmdutil.summaryhooks.add('rebase', summaryhook)
1022 cmdutil.summaryhooks.add('rebase', summaryhook)
1022 cmdutil.unfinishedstates.append(
1023 cmdutil.unfinishedstates.append(
1023 ['rebasestate', False, False, _('rebase in progress'),
1024 ['rebasestate', False, False, _('rebase in progress'),
1024 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1025 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
@@ -1,6313 +1,6314 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for 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, bin, nullid, nullrev, short
8 from node import hex, bin, nullid, nullrev, short
9 from lock import release
9 from lock import release
10 from i18n import _
10 from i18n import _
11 import os, re, difflib, time, tempfile, errno, shlex
11 import os, re, difflib, time, tempfile, errno, shlex
12 import sys, socket
12 import sys, socket
13 import hg, scmutil, util, revlog, copies, error, bookmarks
13 import hg, scmutil, util, revlog, copies, error, bookmarks
14 import patch, help, encoding, templatekw, discovery
14 import patch, help, encoding, templatekw, discovery
15 import archival, changegroup, cmdutil, hbisect
15 import archival, changegroup, cmdutil, hbisect
16 import sshserver, hgweb, commandserver
16 import sshserver, hgweb, commandserver
17 import extensions
17 import extensions
18 from hgweb import server as hgweb_server
18 from hgweb import server as hgweb_server
19 import merge as mergemod
19 import merge as mergemod
20 import minirst, revset, fileset
20 import minirst, revset, fileset
21 import dagparser, context, simplemerge, graphmod, copies
21 import dagparser, context, simplemerge, graphmod, copies
22 import random
22 import random
23 import setdiscovery, treediscovery, dagutil, pvec, localrepo
23 import setdiscovery, treediscovery, dagutil, pvec, localrepo
24 import phases, obsolete, exchange
24 import phases, obsolete, exchange
25 import ui as uimod
25 import ui as uimod
26
26
27 table = {}
27 table = {}
28
28
29 command = cmdutil.command(table)
29 command = cmdutil.command(table)
30
30
31 # Space delimited list of commands that don't require local repositories.
31 # Space delimited list of commands that don't require local repositories.
32 # This should be populated by passing norepo=True into the @command decorator.
32 # This should be populated by passing norepo=True into the @command decorator.
33 norepo = ''
33 norepo = ''
34 # Space delimited list of commands that optionally require local repositories.
34 # Space delimited list of commands that optionally require local repositories.
35 # This should be populated by passing optionalrepo=True into the @command
35 # This should be populated by passing optionalrepo=True into the @command
36 # decorator.
36 # decorator.
37 optionalrepo = ''
37 optionalrepo = ''
38 # Space delimited list of commands that will examine arguments looking for
38 # Space delimited list of commands that will examine arguments looking for
39 # a repository. This should be populated by passing inferrepo=True into the
39 # a repository. This should be populated by passing inferrepo=True into the
40 # @command decorator.
40 # @command decorator.
41 inferrepo = ''
41 inferrepo = ''
42
42
43 # common command options
43 # common command options
44
44
45 globalopts = [
45 globalopts = [
46 ('R', 'repository', '',
46 ('R', 'repository', '',
47 _('repository root directory or name of overlay bundle file'),
47 _('repository root directory or name of overlay bundle file'),
48 _('REPO')),
48 _('REPO')),
49 ('', 'cwd', '',
49 ('', 'cwd', '',
50 _('change working directory'), _('DIR')),
50 _('change working directory'), _('DIR')),
51 ('y', 'noninteractive', None,
51 ('y', 'noninteractive', None,
52 _('do not prompt, automatically pick the first choice for all prompts')),
52 _('do not prompt, automatically pick the first choice for all prompts')),
53 ('q', 'quiet', None, _('suppress output')),
53 ('q', 'quiet', None, _('suppress output')),
54 ('v', 'verbose', None, _('enable additional output')),
54 ('v', 'verbose', None, _('enable additional output')),
55 ('', 'config', [],
55 ('', 'config', [],
56 _('set/override config option (use \'section.name=value\')'),
56 _('set/override config option (use \'section.name=value\')'),
57 _('CONFIG')),
57 _('CONFIG')),
58 ('', 'debug', None, _('enable debugging output')),
58 ('', 'debug', None, _('enable debugging output')),
59 ('', 'debugger', None, _('start debugger')),
59 ('', 'debugger', None, _('start debugger')),
60 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
60 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
61 _('ENCODE')),
61 _('ENCODE')),
62 ('', 'encodingmode', encoding.encodingmode,
62 ('', 'encodingmode', encoding.encodingmode,
63 _('set the charset encoding mode'), _('MODE')),
63 _('set the charset encoding mode'), _('MODE')),
64 ('', 'traceback', None, _('always print a traceback on exception')),
64 ('', 'traceback', None, _('always print a traceback on exception')),
65 ('', 'time', None, _('time how long the command takes')),
65 ('', 'time', None, _('time how long the command takes')),
66 ('', 'profile', None, _('print command execution profile')),
66 ('', 'profile', None, _('print command execution profile')),
67 ('', 'version', None, _('output version information and exit')),
67 ('', 'version', None, _('output version information and exit')),
68 ('h', 'help', None, _('display help and exit')),
68 ('h', 'help', None, _('display help and exit')),
69 ('', 'hidden', False, _('consider hidden changesets')),
69 ('', 'hidden', False, _('consider hidden changesets')),
70 ]
70 ]
71
71
72 dryrunopts = [('n', 'dry-run', None,
72 dryrunopts = [('n', 'dry-run', None,
73 _('do not perform actions, just print output'))]
73 _('do not perform actions, just print output'))]
74
74
75 remoteopts = [
75 remoteopts = [
76 ('e', 'ssh', '',
76 ('e', 'ssh', '',
77 _('specify ssh command to use'), _('CMD')),
77 _('specify ssh command to use'), _('CMD')),
78 ('', 'remotecmd', '',
78 ('', 'remotecmd', '',
79 _('specify hg command to run on the remote side'), _('CMD')),
79 _('specify hg command to run on the remote side'), _('CMD')),
80 ('', 'insecure', None,
80 ('', 'insecure', None,
81 _('do not verify server certificate (ignoring web.cacerts config)')),
81 _('do not verify server certificate (ignoring web.cacerts config)')),
82 ]
82 ]
83
83
84 walkopts = [
84 walkopts = [
85 ('I', 'include', [],
85 ('I', 'include', [],
86 _('include names matching the given patterns'), _('PATTERN')),
86 _('include names matching the given patterns'), _('PATTERN')),
87 ('X', 'exclude', [],
87 ('X', 'exclude', [],
88 _('exclude names matching the given patterns'), _('PATTERN')),
88 _('exclude names matching the given patterns'), _('PATTERN')),
89 ]
89 ]
90
90
91 commitopts = [
91 commitopts = [
92 ('m', 'message', '',
92 ('m', 'message', '',
93 _('use text as commit message'), _('TEXT')),
93 _('use text as commit message'), _('TEXT')),
94 ('l', 'logfile', '',
94 ('l', 'logfile', '',
95 _('read commit message from file'), _('FILE')),
95 _('read commit message from file'), _('FILE')),
96 ]
96 ]
97
97
98 commitopts2 = [
98 commitopts2 = [
99 ('d', 'date', '',
99 ('d', 'date', '',
100 _('record the specified date as commit date'), _('DATE')),
100 _('record the specified date as commit date'), _('DATE')),
101 ('u', 'user', '',
101 ('u', 'user', '',
102 _('record the specified user as committer'), _('USER')),
102 _('record the specified user as committer'), _('USER')),
103 ]
103 ]
104
104
105 # hidden for now
105 # hidden for now
106 formatteropts = [
106 formatteropts = [
107 ('T', 'template', '',
107 ('T', 'template', '',
108 _('display with template (DEPRECATED)'), _('TEMPLATE')),
108 _('display with template (DEPRECATED)'), _('TEMPLATE')),
109 ]
109 ]
110
110
111 templateopts = [
111 templateopts = [
112 ('', 'style', '',
112 ('', 'style', '',
113 _('display using template map file (DEPRECATED)'), _('STYLE')),
113 _('display using template map file (DEPRECATED)'), _('STYLE')),
114 ('T', 'template', '',
114 ('T', 'template', '',
115 _('display with template'), _('TEMPLATE')),
115 _('display with template'), _('TEMPLATE')),
116 ]
116 ]
117
117
118 logopts = [
118 logopts = [
119 ('p', 'patch', None, _('show patch')),
119 ('p', 'patch', None, _('show patch')),
120 ('g', 'git', None, _('use git extended diff format')),
120 ('g', 'git', None, _('use git extended diff format')),
121 ('l', 'limit', '',
121 ('l', 'limit', '',
122 _('limit number of changes displayed'), _('NUM')),
122 _('limit number of changes displayed'), _('NUM')),
123 ('M', 'no-merges', None, _('do not show merges')),
123 ('M', 'no-merges', None, _('do not show merges')),
124 ('', 'stat', None, _('output diffstat-style summary of changes')),
124 ('', 'stat', None, _('output diffstat-style summary of changes')),
125 ('G', 'graph', None, _("show the revision DAG")),
125 ('G', 'graph', None, _("show the revision DAG")),
126 ] + templateopts
126 ] + templateopts
127
127
128 diffopts = [
128 diffopts = [
129 ('a', 'text', None, _('treat all files as text')),
129 ('a', 'text', None, _('treat all files as text')),
130 ('g', 'git', None, _('use git extended diff format')),
130 ('g', 'git', None, _('use git extended diff format')),
131 ('', 'nodates', None, _('omit dates from diff headers'))
131 ('', 'nodates', None, _('omit dates from diff headers'))
132 ]
132 ]
133
133
134 diffwsopts = [
134 diffwsopts = [
135 ('w', 'ignore-all-space', None,
135 ('w', 'ignore-all-space', None,
136 _('ignore white space when comparing lines')),
136 _('ignore white space when comparing lines')),
137 ('b', 'ignore-space-change', None,
137 ('b', 'ignore-space-change', None,
138 _('ignore changes in the amount of white space')),
138 _('ignore changes in the amount of white space')),
139 ('B', 'ignore-blank-lines', None,
139 ('B', 'ignore-blank-lines', None,
140 _('ignore changes whose lines are all blank')),
140 _('ignore changes whose lines are all blank')),
141 ]
141 ]
142
142
143 diffopts2 = [
143 diffopts2 = [
144 ('p', 'show-function', None, _('show which function each change is in')),
144 ('p', 'show-function', None, _('show which function each change is in')),
145 ('', 'reverse', None, _('produce a diff that undoes the changes')),
145 ('', 'reverse', None, _('produce a diff that undoes the changes')),
146 ] + diffwsopts + [
146 ] + diffwsopts + [
147 ('U', 'unified', '',
147 ('U', 'unified', '',
148 _('number of lines of context to show'), _('NUM')),
148 _('number of lines of context to show'), _('NUM')),
149 ('', 'stat', None, _('output diffstat-style summary of changes')),
149 ('', 'stat', None, _('output diffstat-style summary of changes')),
150 ]
150 ]
151
151
152 mergetoolopts = [
152 mergetoolopts = [
153 ('t', 'tool', '', _('specify merge tool')),
153 ('t', 'tool', '', _('specify merge tool')),
154 ]
154 ]
155
155
156 similarityopts = [
156 similarityopts = [
157 ('s', 'similarity', '',
157 ('s', 'similarity', '',
158 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
158 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
159 ]
159 ]
160
160
161 subrepoopts = [
161 subrepoopts = [
162 ('S', 'subrepos', None,
162 ('S', 'subrepos', None,
163 _('recurse into subrepositories'))
163 _('recurse into subrepositories'))
164 ]
164 ]
165
165
166 # Commands start here, listed alphabetically
166 # Commands start here, listed alphabetically
167
167
168 @command('^add',
168 @command('^add',
169 walkopts + subrepoopts + dryrunopts,
169 walkopts + subrepoopts + dryrunopts,
170 _('[OPTION]... [FILE]...'),
170 _('[OPTION]... [FILE]...'),
171 inferrepo=True)
171 inferrepo=True)
172 def add(ui, repo, *pats, **opts):
172 def add(ui, repo, *pats, **opts):
173 """add the specified files on the next commit
173 """add the specified files on the next commit
174
174
175 Schedule files to be version controlled and added to the
175 Schedule files to be version controlled and added to the
176 repository.
176 repository.
177
177
178 The files will be added to the repository at the next commit. To
178 The files will be added to the repository at the next commit. To
179 undo an add before that, see :hg:`forget`.
179 undo an add before that, see :hg:`forget`.
180
180
181 If no names are given, add all files to the repository.
181 If no names are given, add all files to the repository.
182
182
183 .. container:: verbose
183 .. container:: verbose
184
184
185 An example showing how new (unknown) files are added
185 An example showing how new (unknown) files are added
186 automatically by :hg:`add`::
186 automatically by :hg:`add`::
187
187
188 $ ls
188 $ ls
189 foo.c
189 foo.c
190 $ hg status
190 $ hg status
191 ? foo.c
191 ? foo.c
192 $ hg add
192 $ hg add
193 adding foo.c
193 adding foo.c
194 $ hg status
194 $ hg status
195 A foo.c
195 A foo.c
196
196
197 Returns 0 if all files are successfully added.
197 Returns 0 if all files are successfully added.
198 """
198 """
199
199
200 m = scmutil.match(repo[None], pats, opts)
200 m = scmutil.match(repo[None], pats, opts)
201 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
201 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
202 opts.get('subrepos'), prefix="", explicitonly=False)
202 opts.get('subrepos'), prefix="", explicitonly=False)
203 return rejected and 1 or 0
203 return rejected and 1 or 0
204
204
205 @command('addremove',
205 @command('addremove',
206 similarityopts + walkopts + dryrunopts,
206 similarityopts + walkopts + dryrunopts,
207 _('[OPTION]... [FILE]...'),
207 _('[OPTION]... [FILE]...'),
208 inferrepo=True)
208 inferrepo=True)
209 def addremove(ui, repo, *pats, **opts):
209 def addremove(ui, repo, *pats, **opts):
210 """add all new files, delete all missing files
210 """add all new files, delete all missing files
211
211
212 Add all new files and remove all missing files from the
212 Add all new files and remove all missing files from the
213 repository.
213 repository.
214
214
215 New files are ignored if they match any of the patterns in
215 New files are ignored if they match any of the patterns in
216 ``.hgignore``. As with add, these changes take effect at the next
216 ``.hgignore``. As with add, these changes take effect at the next
217 commit.
217 commit.
218
218
219 Use the -s/--similarity option to detect renamed files. This
219 Use the -s/--similarity option to detect renamed files. This
220 option takes a percentage between 0 (disabled) and 100 (files must
220 option takes a percentage between 0 (disabled) and 100 (files must
221 be identical) as its parameter. With a parameter greater than 0,
221 be identical) as its parameter. With a parameter greater than 0,
222 this compares every removed file with every added file and records
222 this compares every removed file with every added file and records
223 those similar enough as renames. Detecting renamed files this way
223 those similar enough as renames. Detecting renamed files this way
224 can be expensive. After using this option, :hg:`status -C` can be
224 can be expensive. After using this option, :hg:`status -C` can be
225 used to check which files were identified as moved or renamed. If
225 used to check which files were identified as moved or renamed. If
226 not specified, -s/--similarity defaults to 100 and only renames of
226 not specified, -s/--similarity defaults to 100 and only renames of
227 identical files are detected.
227 identical files are detected.
228
228
229 Returns 0 if all files are successfully added.
229 Returns 0 if all files are successfully added.
230 """
230 """
231 try:
231 try:
232 sim = float(opts.get('similarity') or 100)
232 sim = float(opts.get('similarity') or 100)
233 except ValueError:
233 except ValueError:
234 raise util.Abort(_('similarity must be a number'))
234 raise util.Abort(_('similarity must be a number'))
235 if sim < 0 or sim > 100:
235 if sim < 0 or sim > 100:
236 raise util.Abort(_('similarity must be between 0 and 100'))
236 raise util.Abort(_('similarity must be between 0 and 100'))
237 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
237 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
238
238
239 @command('^annotate|blame',
239 @command('^annotate|blame',
240 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
240 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
241 ('', 'follow', None,
241 ('', 'follow', None,
242 _('follow copies/renames and list the filename (DEPRECATED)')),
242 _('follow copies/renames and list the filename (DEPRECATED)')),
243 ('', 'no-follow', None, _("don't follow copies and renames")),
243 ('', 'no-follow', None, _("don't follow copies and renames")),
244 ('a', 'text', None, _('treat all files as text')),
244 ('a', 'text', None, _('treat all files as text')),
245 ('u', 'user', None, _('list the author (long with -v)')),
245 ('u', 'user', None, _('list the author (long with -v)')),
246 ('f', 'file', None, _('list the filename')),
246 ('f', 'file', None, _('list the filename')),
247 ('d', 'date', None, _('list the date (short with -q)')),
247 ('d', 'date', None, _('list the date (short with -q)')),
248 ('n', 'number', None, _('list the revision number (default)')),
248 ('n', 'number', None, _('list the revision number (default)')),
249 ('c', 'changeset', None, _('list the changeset')),
249 ('c', 'changeset', None, _('list the changeset')),
250 ('l', 'line-number', None, _('show line number at the first appearance'))
250 ('l', 'line-number', None, _('show line number at the first appearance'))
251 ] + diffwsopts + walkopts + formatteropts,
251 ] + diffwsopts + walkopts + formatteropts,
252 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
252 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
253 inferrepo=True)
253 inferrepo=True)
254 def annotate(ui, repo, *pats, **opts):
254 def annotate(ui, repo, *pats, **opts):
255 """show changeset information by line for each file
255 """show changeset information by line for each file
256
256
257 List changes in files, showing the revision id responsible for
257 List changes in files, showing the revision id responsible for
258 each line
258 each line
259
259
260 This command is useful for discovering when a change was made and
260 This command is useful for discovering when a change was made and
261 by whom.
261 by whom.
262
262
263 Without the -a/--text option, annotate will avoid processing files
263 Without the -a/--text option, annotate will avoid processing files
264 it detects as binary. With -a, annotate will annotate the file
264 it detects as binary. With -a, annotate will annotate the file
265 anyway, although the results will probably be neither useful
265 anyway, although the results will probably be neither useful
266 nor desirable.
266 nor desirable.
267
267
268 Returns 0 on success.
268 Returns 0 on success.
269 """
269 """
270 if not pats:
270 if not pats:
271 raise util.Abort(_('at least one filename or pattern is required'))
271 raise util.Abort(_('at least one filename or pattern is required'))
272
272
273 if opts.get('follow'):
273 if opts.get('follow'):
274 # --follow is deprecated and now just an alias for -f/--file
274 # --follow is deprecated and now just an alias for -f/--file
275 # to mimic the behavior of Mercurial before version 1.5
275 # to mimic the behavior of Mercurial before version 1.5
276 opts['file'] = True
276 opts['file'] = True
277
277
278 fm = ui.formatter('annotate', opts)
278 fm = ui.formatter('annotate', opts)
279 datefunc = ui.quiet and util.shortdate or util.datestr
279 datefunc = ui.quiet and util.shortdate or util.datestr
280 hexfn = fm.hexfunc
280 hexfn = fm.hexfunc
281
281
282 opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
282 opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
283 ('number', ' ', lambda x: x[0].rev(), str),
283 ('number', ' ', lambda x: x[0].rev(), str),
284 ('changeset', ' ', lambda x: hexfn(x[0].node()), str),
284 ('changeset', ' ', lambda x: hexfn(x[0].node()), str),
285 ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
285 ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
286 ('file', ' ', lambda x: x[0].path(), str),
286 ('file', ' ', lambda x: x[0].path(), str),
287 ('line_number', ':', lambda x: x[1], str),
287 ('line_number', ':', lambda x: x[1], str),
288 ]
288 ]
289 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
289 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
290
290
291 if (not opts.get('user') and not opts.get('changeset')
291 if (not opts.get('user') and not opts.get('changeset')
292 and not opts.get('date') and not opts.get('file')):
292 and not opts.get('date') and not opts.get('file')):
293 opts['number'] = True
293 opts['number'] = True
294
294
295 linenumber = opts.get('line_number') is not None
295 linenumber = opts.get('line_number') is not None
296 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
296 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
297 raise util.Abort(_('at least one of -n/-c is required for -l'))
297 raise util.Abort(_('at least one of -n/-c is required for -l'))
298
298
299 if fm:
299 if fm:
300 def makefunc(get, fmt):
300 def makefunc(get, fmt):
301 return get
301 return get
302 else:
302 else:
303 def makefunc(get, fmt):
303 def makefunc(get, fmt):
304 return lambda x: fmt(get(x))
304 return lambda x: fmt(get(x))
305 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
305 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
306 if opts.get(op)]
306 if opts.get(op)]
307 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
307 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
308 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
308 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
309 if opts.get(op))
309 if opts.get(op))
310
310
311 def bad(x, y):
311 def bad(x, y):
312 raise util.Abort("%s: %s" % (x, y))
312 raise util.Abort("%s: %s" % (x, y))
313
313
314 ctx = scmutil.revsingle(repo, opts.get('rev'))
314 ctx = scmutil.revsingle(repo, opts.get('rev'))
315 m = scmutil.match(ctx, pats, opts)
315 m = scmutil.match(ctx, pats, opts)
316 m.bad = bad
316 m.bad = bad
317 follow = not opts.get('no_follow')
317 follow = not opts.get('no_follow')
318 diffopts = patch.diffopts(ui, opts, section='annotate')
318 diffopts = patch.diffopts(ui, opts, section='annotate')
319 for abs in ctx.walk(m):
319 for abs in ctx.walk(m):
320 fctx = ctx[abs]
320 fctx = ctx[abs]
321 if not opts.get('text') and util.binary(fctx.data()):
321 if not opts.get('text') and util.binary(fctx.data()):
322 fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
322 fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
323 continue
323 continue
324
324
325 lines = fctx.annotate(follow=follow, linenumber=linenumber,
325 lines = fctx.annotate(follow=follow, linenumber=linenumber,
326 diffopts=diffopts)
326 diffopts=diffopts)
327 formats = []
327 formats = []
328 pieces = []
328 pieces = []
329
329
330 for f, sep in funcmap:
330 for f, sep in funcmap:
331 l = [f(n) for n, dummy in lines]
331 l = [f(n) for n, dummy in lines]
332 if l:
332 if l:
333 if fm:
333 if fm:
334 formats.append(['%s' for x in l])
334 formats.append(['%s' for x in l])
335 else:
335 else:
336 sizes = [encoding.colwidth(x) for x in l]
336 sizes = [encoding.colwidth(x) for x in l]
337 ml = max(sizes)
337 ml = max(sizes)
338 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
338 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
339 pieces.append(l)
339 pieces.append(l)
340
340
341 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
341 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
342 fm.startitem()
342 fm.startitem()
343 fm.write(fields, "".join(f), *p)
343 fm.write(fields, "".join(f), *p)
344 fm.write('line', ": %s", l[1])
344 fm.write('line', ": %s", l[1])
345
345
346 if lines and not lines[-1][1].endswith('\n'):
346 if lines and not lines[-1][1].endswith('\n'):
347 fm.plain('\n')
347 fm.plain('\n')
348
348
349 fm.end()
349 fm.end()
350
350
351 @command('archive',
351 @command('archive',
352 [('', 'no-decode', None, _('do not pass files through decoders')),
352 [('', 'no-decode', None, _('do not pass files through decoders')),
353 ('p', 'prefix', '', _('directory prefix for files in archive'),
353 ('p', 'prefix', '', _('directory prefix for files in archive'),
354 _('PREFIX')),
354 _('PREFIX')),
355 ('r', 'rev', '', _('revision to distribute'), _('REV')),
355 ('r', 'rev', '', _('revision to distribute'), _('REV')),
356 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
356 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
357 ] + subrepoopts + walkopts,
357 ] + subrepoopts + walkopts,
358 _('[OPTION]... DEST'))
358 _('[OPTION]... DEST'))
359 def archive(ui, repo, dest, **opts):
359 def archive(ui, repo, dest, **opts):
360 '''create an unversioned archive of a repository revision
360 '''create an unversioned archive of a repository revision
361
361
362 By default, the revision used is the parent of the working
362 By default, the revision used is the parent of the working
363 directory; use -r/--rev to specify a different revision.
363 directory; use -r/--rev to specify a different revision.
364
364
365 The archive type is automatically detected based on file
365 The archive type is automatically detected based on file
366 extension (or override using -t/--type).
366 extension (or override using -t/--type).
367
367
368 .. container:: verbose
368 .. container:: verbose
369
369
370 Examples:
370 Examples:
371
371
372 - create a zip file containing the 1.0 release::
372 - create a zip file containing the 1.0 release::
373
373
374 hg archive -r 1.0 project-1.0.zip
374 hg archive -r 1.0 project-1.0.zip
375
375
376 - create a tarball excluding .hg files::
376 - create a tarball excluding .hg files::
377
377
378 hg archive project.tar.gz -X ".hg*"
378 hg archive project.tar.gz -X ".hg*"
379
379
380 Valid types are:
380 Valid types are:
381
381
382 :``files``: a directory full of files (default)
382 :``files``: a directory full of files (default)
383 :``tar``: tar archive, uncompressed
383 :``tar``: tar archive, uncompressed
384 :``tbz2``: tar archive, compressed using bzip2
384 :``tbz2``: tar archive, compressed using bzip2
385 :``tgz``: tar archive, compressed using gzip
385 :``tgz``: tar archive, compressed using gzip
386 :``uzip``: zip archive, uncompressed
386 :``uzip``: zip archive, uncompressed
387 :``zip``: zip archive, compressed using deflate
387 :``zip``: zip archive, compressed using deflate
388
388
389 The exact name of the destination archive or directory is given
389 The exact name of the destination archive or directory is given
390 using a format string; see :hg:`help export` for details.
390 using a format string; see :hg:`help export` for details.
391
391
392 Each member added to an archive file has a directory prefix
392 Each member added to an archive file has a directory prefix
393 prepended. Use -p/--prefix to specify a format string for the
393 prepended. Use -p/--prefix to specify a format string for the
394 prefix. The default is the basename of the archive, with suffixes
394 prefix. The default is the basename of the archive, with suffixes
395 removed.
395 removed.
396
396
397 Returns 0 on success.
397 Returns 0 on success.
398 '''
398 '''
399
399
400 ctx = scmutil.revsingle(repo, opts.get('rev'))
400 ctx = scmutil.revsingle(repo, opts.get('rev'))
401 if not ctx:
401 if not ctx:
402 raise util.Abort(_('no working directory: please specify a revision'))
402 raise util.Abort(_('no working directory: please specify a revision'))
403 node = ctx.node()
403 node = ctx.node()
404 dest = cmdutil.makefilename(repo, dest, node)
404 dest = cmdutil.makefilename(repo, dest, node)
405 if os.path.realpath(dest) == repo.root:
405 if os.path.realpath(dest) == repo.root:
406 raise util.Abort(_('repository root cannot be destination'))
406 raise util.Abort(_('repository root cannot be destination'))
407
407
408 kind = opts.get('type') or archival.guesskind(dest) or 'files'
408 kind = opts.get('type') or archival.guesskind(dest) or 'files'
409 prefix = opts.get('prefix')
409 prefix = opts.get('prefix')
410
410
411 if dest == '-':
411 if dest == '-':
412 if kind == 'files':
412 if kind == 'files':
413 raise util.Abort(_('cannot archive plain files to stdout'))
413 raise util.Abort(_('cannot archive plain files to stdout'))
414 dest = cmdutil.makefileobj(repo, dest)
414 dest = cmdutil.makefileobj(repo, dest)
415 if not prefix:
415 if not prefix:
416 prefix = os.path.basename(repo.root) + '-%h'
416 prefix = os.path.basename(repo.root) + '-%h'
417
417
418 prefix = cmdutil.makefilename(repo, prefix, node)
418 prefix = cmdutil.makefilename(repo, prefix, node)
419 matchfn = scmutil.match(ctx, [], opts)
419 matchfn = scmutil.match(ctx, [], opts)
420 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
420 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
421 matchfn, prefix, subrepos=opts.get('subrepos'))
421 matchfn, prefix, subrepos=opts.get('subrepos'))
422
422
423 @command('backout',
423 @command('backout',
424 [('', 'merge', None, _('merge with old dirstate parent after backout')),
424 [('', 'merge', None, _('merge with old dirstate parent after backout')),
425 ('', 'parent', '',
425 ('', 'parent', '',
426 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
426 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
427 ('r', 'rev', '', _('revision to backout'), _('REV')),
427 ('r', 'rev', '', _('revision to backout'), _('REV')),
428 ('e', 'edit', False, _('invoke editor on commit messages')),
428 ('e', 'edit', False, _('invoke editor on commit messages')),
429 ] + mergetoolopts + walkopts + commitopts + commitopts2,
429 ] + mergetoolopts + walkopts + commitopts + commitopts2,
430 _('[OPTION]... [-r] REV'))
430 _('[OPTION]... [-r] REV'))
431 def backout(ui, repo, node=None, rev=None, **opts):
431 def backout(ui, repo, node=None, rev=None, **opts):
432 '''reverse effect of earlier changeset
432 '''reverse effect of earlier changeset
433
433
434 Prepare a new changeset with the effect of REV undone in the
434 Prepare a new changeset with the effect of REV undone in the
435 current working directory.
435 current working directory.
436
436
437 If REV is the parent of the working directory, then this new changeset
437 If REV is the parent of the working directory, then this new changeset
438 is committed automatically. Otherwise, hg needs to merge the
438 is committed automatically. Otherwise, hg needs to merge the
439 changes and the merged result is left uncommitted.
439 changes and the merged result is left uncommitted.
440
440
441 .. note::
441 .. note::
442
442
443 backout cannot be used to fix either an unwanted or
443 backout cannot be used to fix either an unwanted or
444 incorrect merge.
444 incorrect merge.
445
445
446 .. container:: verbose
446 .. container:: verbose
447
447
448 By default, the pending changeset will have one parent,
448 By default, the pending changeset will have one parent,
449 maintaining a linear history. With --merge, the pending
449 maintaining a linear history. With --merge, the pending
450 changeset will instead have two parents: the old parent of the
450 changeset will instead have two parents: the old parent of the
451 working directory and a new child of REV that simply undoes REV.
451 working directory and a new child of REV that simply undoes REV.
452
452
453 Before version 1.7, the behavior without --merge was equivalent
453 Before version 1.7, the behavior without --merge was equivalent
454 to specifying --merge followed by :hg:`update --clean .` to
454 to specifying --merge followed by :hg:`update --clean .` to
455 cancel the merge and leave the child of REV as a head to be
455 cancel the merge and leave the child of REV as a head to be
456 merged separately.
456 merged separately.
457
457
458 See :hg:`help dates` for a list of formats valid for -d/--date.
458 See :hg:`help dates` for a list of formats valid for -d/--date.
459
459
460 Returns 0 on success, 1 if nothing to backout or there are unresolved
460 Returns 0 on success, 1 if nothing to backout or there are unresolved
461 files.
461 files.
462 '''
462 '''
463 if rev and node:
463 if rev and node:
464 raise util.Abort(_("please specify just one revision"))
464 raise util.Abort(_("please specify just one revision"))
465
465
466 if not rev:
466 if not rev:
467 rev = node
467 rev = node
468
468
469 if not rev:
469 if not rev:
470 raise util.Abort(_("please specify a revision to backout"))
470 raise util.Abort(_("please specify a revision to backout"))
471
471
472 date = opts.get('date')
472 date = opts.get('date')
473 if date:
473 if date:
474 opts['date'] = util.parsedate(date)
474 opts['date'] = util.parsedate(date)
475
475
476 cmdutil.checkunfinished(repo)
476 cmdutil.checkunfinished(repo)
477 cmdutil.bailifchanged(repo)
477 cmdutil.bailifchanged(repo)
478 node = scmutil.revsingle(repo, rev).node()
478 node = scmutil.revsingle(repo, rev).node()
479
479
480 op1, op2 = repo.dirstate.parents()
480 op1, op2 = repo.dirstate.parents()
481 if not repo.changelog.isancestor(node, op1):
481 if not repo.changelog.isancestor(node, op1):
482 raise util.Abort(_('cannot backout change that is not an ancestor'))
482 raise util.Abort(_('cannot backout change that is not an ancestor'))
483
483
484 p1, p2 = repo.changelog.parents(node)
484 p1, p2 = repo.changelog.parents(node)
485 if p1 == nullid:
485 if p1 == nullid:
486 raise util.Abort(_('cannot backout a change with no parents'))
486 raise util.Abort(_('cannot backout a change with no parents'))
487 if p2 != nullid:
487 if p2 != nullid:
488 if not opts.get('parent'):
488 if not opts.get('parent'):
489 raise util.Abort(_('cannot backout a merge changeset'))
489 raise util.Abort(_('cannot backout a merge changeset'))
490 p = repo.lookup(opts['parent'])
490 p = repo.lookup(opts['parent'])
491 if p not in (p1, p2):
491 if p not in (p1, p2):
492 raise util.Abort(_('%s is not a parent of %s') %
492 raise util.Abort(_('%s is not a parent of %s') %
493 (short(p), short(node)))
493 (short(p), short(node)))
494 parent = p
494 parent = p
495 else:
495 else:
496 if opts.get('parent'):
496 if opts.get('parent'):
497 raise util.Abort(_('cannot use --parent on non-merge changeset'))
497 raise util.Abort(_('cannot use --parent on non-merge changeset'))
498 parent = p1
498 parent = p1
499
499
500 # the backout should appear on the same branch
500 # the backout should appear on the same branch
501 wlock = repo.wlock()
501 wlock = repo.wlock()
502 try:
502 try:
503 branch = repo.dirstate.branch()
503 branch = repo.dirstate.branch()
504 bheads = repo.branchheads(branch)
504 bheads = repo.branchheads(branch)
505 rctx = scmutil.revsingle(repo, hex(parent))
505 rctx = scmutil.revsingle(repo, hex(parent))
506 if not opts.get('merge') and op1 != node:
506 if not opts.get('merge') and op1 != node:
507 try:
507 try:
508 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
508 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
509 'backout')
509 'backout')
510 repo.dirstate.beginparentchange()
510 repo.dirstate.beginparentchange()
511 stats = mergemod.update(repo, parent, True, True, False,
511 stats = mergemod.update(repo, parent, True, True, False,
512 node, False)
512 node, False)
513 repo.setparents(op1, op2)
513 repo.setparents(op1, op2)
514 repo.dirstate.endparentchange()
514 repo.dirstate.endparentchange()
515 hg._showstats(repo, stats)
515 hg._showstats(repo, stats)
516 if stats[3]:
516 if stats[3]:
517 repo.ui.status(_("use 'hg resolve' to retry unresolved "
517 repo.ui.status(_("use 'hg resolve' to retry unresolved "
518 "file merges\n"))
518 "file merges\n"))
519 else:
519 else:
520 msg = _("changeset %s backed out, "
520 msg = _("changeset %s backed out, "
521 "don't forget to commit.\n")
521 "don't forget to commit.\n")
522 ui.status(msg % short(node))
522 ui.status(msg % short(node))
523 return stats[3] > 0
523 return stats[3] > 0
524 finally:
524 finally:
525 ui.setconfig('ui', 'forcemerge', '', '')
525 ui.setconfig('ui', 'forcemerge', '', '')
526 else:
526 else:
527 hg.clean(repo, node, show_stats=False)
527 hg.clean(repo, node, show_stats=False)
528 repo.dirstate.setbranch(branch)
528 repo.dirstate.setbranch(branch)
529 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
529 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
530
530
531
531
532 def commitfunc(ui, repo, message, match, opts):
532 def commitfunc(ui, repo, message, match, opts):
533 editform = 'backout'
533 editform = 'backout'
534 e = cmdutil.getcommiteditor(editform=editform, **opts)
534 e = cmdutil.getcommiteditor(editform=editform, **opts)
535 if not message:
535 if not message:
536 # we don't translate commit messages
536 # we don't translate commit messages
537 message = "Backed out changeset %s" % short(node)
537 message = "Backed out changeset %s" % short(node)
538 e = cmdutil.getcommiteditor(edit=True, editform=editform)
538 e = cmdutil.getcommiteditor(edit=True, editform=editform)
539 return repo.commit(message, opts.get('user'), opts.get('date'),
539 return repo.commit(message, opts.get('user'), opts.get('date'),
540 match, editor=e)
540 match, editor=e)
541 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
541 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
542 if not newnode:
542 if not newnode:
543 ui.status(_("nothing changed\n"))
543 ui.status(_("nothing changed\n"))
544 return 1
544 return 1
545 cmdutil.commitstatus(repo, newnode, branch, bheads)
545 cmdutil.commitstatus(repo, newnode, branch, bheads)
546
546
547 def nice(node):
547 def nice(node):
548 return '%d:%s' % (repo.changelog.rev(node), short(node))
548 return '%d:%s' % (repo.changelog.rev(node), short(node))
549 ui.status(_('changeset %s backs out changeset %s\n') %
549 ui.status(_('changeset %s backs out changeset %s\n') %
550 (nice(repo.changelog.tip()), nice(node)))
550 (nice(repo.changelog.tip()), nice(node)))
551 if opts.get('merge') and op1 != node:
551 if opts.get('merge') and op1 != node:
552 hg.clean(repo, op1, show_stats=False)
552 hg.clean(repo, op1, show_stats=False)
553 ui.status(_('merging with changeset %s\n')
553 ui.status(_('merging with changeset %s\n')
554 % nice(repo.changelog.tip()))
554 % nice(repo.changelog.tip()))
555 try:
555 try:
556 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
556 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
557 'backout')
557 'backout')
558 return hg.merge(repo, hex(repo.changelog.tip()))
558 return hg.merge(repo, hex(repo.changelog.tip()))
559 finally:
559 finally:
560 ui.setconfig('ui', 'forcemerge', '', '')
560 ui.setconfig('ui', 'forcemerge', '', '')
561 finally:
561 finally:
562 wlock.release()
562 wlock.release()
563 return 0
563 return 0
564
564
565 @command('bisect',
565 @command('bisect',
566 [('r', 'reset', False, _('reset bisect state')),
566 [('r', 'reset', False, _('reset bisect state')),
567 ('g', 'good', False, _('mark changeset good')),
567 ('g', 'good', False, _('mark changeset good')),
568 ('b', 'bad', False, _('mark changeset bad')),
568 ('b', 'bad', False, _('mark changeset bad')),
569 ('s', 'skip', False, _('skip testing changeset')),
569 ('s', 'skip', False, _('skip testing changeset')),
570 ('e', 'extend', False, _('extend the bisect range')),
570 ('e', 'extend', False, _('extend the bisect range')),
571 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
571 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
572 ('U', 'noupdate', False, _('do not update to target'))],
572 ('U', 'noupdate', False, _('do not update to target'))],
573 _("[-gbsr] [-U] [-c CMD] [REV]"))
573 _("[-gbsr] [-U] [-c CMD] [REV]"))
574 def bisect(ui, repo, rev=None, extra=None, command=None,
574 def bisect(ui, repo, rev=None, extra=None, command=None,
575 reset=None, good=None, bad=None, skip=None, extend=None,
575 reset=None, good=None, bad=None, skip=None, extend=None,
576 noupdate=None):
576 noupdate=None):
577 """subdivision search of changesets
577 """subdivision search of changesets
578
578
579 This command helps to find changesets which introduce problems. To
579 This command helps to find changesets which introduce problems. To
580 use, mark the earliest changeset you know exhibits the problem as
580 use, mark the earliest changeset you know exhibits the problem as
581 bad, then mark the latest changeset which is free from the problem
581 bad, then mark the latest changeset which is free from the problem
582 as good. Bisect will update your working directory to a revision
582 as good. Bisect will update your working directory to a revision
583 for testing (unless the -U/--noupdate option is specified). Once
583 for testing (unless the -U/--noupdate option is specified). Once
584 you have performed tests, mark the working directory as good or
584 you have performed tests, mark the working directory as good or
585 bad, and bisect will either update to another candidate changeset
585 bad, and bisect will either update to another candidate changeset
586 or announce that it has found the bad revision.
586 or announce that it has found the bad revision.
587
587
588 As a shortcut, you can also use the revision argument to mark a
588 As a shortcut, you can also use the revision argument to mark a
589 revision as good or bad without checking it out first.
589 revision as good or bad without checking it out first.
590
590
591 If you supply a command, it will be used for automatic bisection.
591 If you supply a command, it will be used for automatic bisection.
592 The environment variable HG_NODE will contain the ID of the
592 The environment variable HG_NODE will contain the ID of the
593 changeset being tested. The exit status of the command will be
593 changeset being tested. The exit status of the command will be
594 used to mark revisions as good or bad: status 0 means good, 125
594 used to mark revisions as good or bad: status 0 means good, 125
595 means to skip the revision, 127 (command not found) will abort the
595 means to skip the revision, 127 (command not found) will abort the
596 bisection, and any other non-zero exit status means the revision
596 bisection, and any other non-zero exit status means the revision
597 is bad.
597 is bad.
598
598
599 .. container:: verbose
599 .. container:: verbose
600
600
601 Some examples:
601 Some examples:
602
602
603 - start a bisection with known bad revision 34, and good revision 12::
603 - start a bisection with known bad revision 34, and good revision 12::
604
604
605 hg bisect --bad 34
605 hg bisect --bad 34
606 hg bisect --good 12
606 hg bisect --good 12
607
607
608 - advance the current bisection by marking current revision as good or
608 - advance the current bisection by marking current revision as good or
609 bad::
609 bad::
610
610
611 hg bisect --good
611 hg bisect --good
612 hg bisect --bad
612 hg bisect --bad
613
613
614 - mark the current revision, or a known revision, to be skipped (e.g. if
614 - mark the current revision, or a known revision, to be skipped (e.g. if
615 that revision is not usable because of another issue)::
615 that revision is not usable because of another issue)::
616
616
617 hg bisect --skip
617 hg bisect --skip
618 hg bisect --skip 23
618 hg bisect --skip 23
619
619
620 - skip all revisions that do not touch directories ``foo`` or ``bar``::
620 - skip all revisions that do not touch directories ``foo`` or ``bar``::
621
621
622 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
622 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
623
623
624 - forget the current bisection::
624 - forget the current bisection::
625
625
626 hg bisect --reset
626 hg bisect --reset
627
627
628 - use 'make && make tests' to automatically find the first broken
628 - use 'make && make tests' to automatically find the first broken
629 revision::
629 revision::
630
630
631 hg bisect --reset
631 hg bisect --reset
632 hg bisect --bad 34
632 hg bisect --bad 34
633 hg bisect --good 12
633 hg bisect --good 12
634 hg bisect --command "make && make tests"
634 hg bisect --command "make && make tests"
635
635
636 - see all changesets whose states are already known in the current
636 - see all changesets whose states are already known in the current
637 bisection::
637 bisection::
638
638
639 hg log -r "bisect(pruned)"
639 hg log -r "bisect(pruned)"
640
640
641 - see the changeset currently being bisected (especially useful
641 - see the changeset currently being bisected (especially useful
642 if running with -U/--noupdate)::
642 if running with -U/--noupdate)::
643
643
644 hg log -r "bisect(current)"
644 hg log -r "bisect(current)"
645
645
646 - see all changesets that took part in the current bisection::
646 - see all changesets that took part in the current bisection::
647
647
648 hg log -r "bisect(range)"
648 hg log -r "bisect(range)"
649
649
650 - you can even get a nice graph::
650 - you can even get a nice graph::
651
651
652 hg log --graph -r "bisect(range)"
652 hg log --graph -r "bisect(range)"
653
653
654 See :hg:`help revsets` for more about the `bisect()` keyword.
654 See :hg:`help revsets` for more about the `bisect()` keyword.
655
655
656 Returns 0 on success.
656 Returns 0 on success.
657 """
657 """
658 def extendbisectrange(nodes, good):
658 def extendbisectrange(nodes, good):
659 # bisect is incomplete when it ends on a merge node and
659 # bisect is incomplete when it ends on a merge node and
660 # one of the parent was not checked.
660 # one of the parent was not checked.
661 parents = repo[nodes[0]].parents()
661 parents = repo[nodes[0]].parents()
662 if len(parents) > 1:
662 if len(parents) > 1:
663 side = good and state['bad'] or state['good']
663 side = good and state['bad'] or state['good']
664 num = len(set(i.node() for i in parents) & set(side))
664 num = len(set(i.node() for i in parents) & set(side))
665 if num == 1:
665 if num == 1:
666 return parents[0].ancestor(parents[1])
666 return parents[0].ancestor(parents[1])
667 return None
667 return None
668
668
669 def print_result(nodes, good):
669 def print_result(nodes, good):
670 displayer = cmdutil.show_changeset(ui, repo, {})
670 displayer = cmdutil.show_changeset(ui, repo, {})
671 if len(nodes) == 1:
671 if len(nodes) == 1:
672 # narrowed it down to a single revision
672 # narrowed it down to a single revision
673 if good:
673 if good:
674 ui.write(_("The first good revision is:\n"))
674 ui.write(_("The first good revision is:\n"))
675 else:
675 else:
676 ui.write(_("The first bad revision is:\n"))
676 ui.write(_("The first bad revision is:\n"))
677 displayer.show(repo[nodes[0]])
677 displayer.show(repo[nodes[0]])
678 extendnode = extendbisectrange(nodes, good)
678 extendnode = extendbisectrange(nodes, good)
679 if extendnode is not None:
679 if extendnode is not None:
680 ui.write(_('Not all ancestors of this changeset have been'
680 ui.write(_('Not all ancestors of this changeset have been'
681 ' checked.\nUse bisect --extend to continue the '
681 ' checked.\nUse bisect --extend to continue the '
682 'bisection from\nthe common ancestor, %s.\n')
682 'bisection from\nthe common ancestor, %s.\n')
683 % extendnode)
683 % extendnode)
684 else:
684 else:
685 # multiple possible revisions
685 # multiple possible revisions
686 if good:
686 if good:
687 ui.write(_("Due to skipped revisions, the first "
687 ui.write(_("Due to skipped revisions, the first "
688 "good revision could be any of:\n"))
688 "good revision could be any of:\n"))
689 else:
689 else:
690 ui.write(_("Due to skipped revisions, the first "
690 ui.write(_("Due to skipped revisions, the first "
691 "bad revision could be any of:\n"))
691 "bad revision could be any of:\n"))
692 for n in nodes:
692 for n in nodes:
693 displayer.show(repo[n])
693 displayer.show(repo[n])
694 displayer.close()
694 displayer.close()
695
695
696 def check_state(state, interactive=True):
696 def check_state(state, interactive=True):
697 if not state['good'] or not state['bad']:
697 if not state['good'] or not state['bad']:
698 if (good or bad or skip or reset) and interactive:
698 if (good or bad or skip or reset) and interactive:
699 return
699 return
700 if not state['good']:
700 if not state['good']:
701 raise util.Abort(_('cannot bisect (no known good revisions)'))
701 raise util.Abort(_('cannot bisect (no known good revisions)'))
702 else:
702 else:
703 raise util.Abort(_('cannot bisect (no known bad revisions)'))
703 raise util.Abort(_('cannot bisect (no known bad revisions)'))
704 return True
704 return True
705
705
706 # backward compatibility
706 # backward compatibility
707 if rev in "good bad reset init".split():
707 if rev in "good bad reset init".split():
708 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
708 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
709 cmd, rev, extra = rev, extra, None
709 cmd, rev, extra = rev, extra, None
710 if cmd == "good":
710 if cmd == "good":
711 good = True
711 good = True
712 elif cmd == "bad":
712 elif cmd == "bad":
713 bad = True
713 bad = True
714 else:
714 else:
715 reset = True
715 reset = True
716 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
716 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
717 raise util.Abort(_('incompatible arguments'))
717 raise util.Abort(_('incompatible arguments'))
718
718
719 cmdutil.checkunfinished(repo)
719 cmdutil.checkunfinished(repo)
720
720
721 if reset:
721 if reset:
722 p = repo.join("bisect.state")
722 p = repo.join("bisect.state")
723 if os.path.exists(p):
723 if os.path.exists(p):
724 os.unlink(p)
724 os.unlink(p)
725 return
725 return
726
726
727 state = hbisect.load_state(repo)
727 state = hbisect.load_state(repo)
728
728
729 if command:
729 if command:
730 changesets = 1
730 changesets = 1
731 if noupdate:
731 if noupdate:
732 try:
732 try:
733 node = state['current'][0]
733 node = state['current'][0]
734 except LookupError:
734 except LookupError:
735 raise util.Abort(_('current bisect revision is unknown - '
735 raise util.Abort(_('current bisect revision is unknown - '
736 'start a new bisect to fix'))
736 'start a new bisect to fix'))
737 else:
737 else:
738 node, p2 = repo.dirstate.parents()
738 node, p2 = repo.dirstate.parents()
739 if p2 != nullid:
739 if p2 != nullid:
740 raise util.Abort(_('current bisect revision is a merge'))
740 raise util.Abort(_('current bisect revision is a merge'))
741 try:
741 try:
742 while changesets:
742 while changesets:
743 # update state
743 # update state
744 state['current'] = [node]
744 state['current'] = [node]
745 hbisect.save_state(repo, state)
745 hbisect.save_state(repo, state)
746 status = util.system(command,
746 status = util.system(command,
747 environ={'HG_NODE': hex(node)},
747 environ={'HG_NODE': hex(node)},
748 out=ui.fout)
748 out=ui.fout)
749 if status == 125:
749 if status == 125:
750 transition = "skip"
750 transition = "skip"
751 elif status == 0:
751 elif status == 0:
752 transition = "good"
752 transition = "good"
753 # status < 0 means process was killed
753 # status < 0 means process was killed
754 elif status == 127:
754 elif status == 127:
755 raise util.Abort(_("failed to execute %s") % command)
755 raise util.Abort(_("failed to execute %s") % command)
756 elif status < 0:
756 elif status < 0:
757 raise util.Abort(_("%s killed") % command)
757 raise util.Abort(_("%s killed") % command)
758 else:
758 else:
759 transition = "bad"
759 transition = "bad"
760 ctx = scmutil.revsingle(repo, rev, node)
760 ctx = scmutil.revsingle(repo, rev, node)
761 rev = None # clear for future iterations
761 rev = None # clear for future iterations
762 state[transition].append(ctx.node())
762 state[transition].append(ctx.node())
763 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
763 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
764 check_state(state, interactive=False)
764 check_state(state, interactive=False)
765 # bisect
765 # bisect
766 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
766 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
767 # update to next check
767 # update to next check
768 node = nodes[0]
768 node = nodes[0]
769 if not noupdate:
769 if not noupdate:
770 cmdutil.bailifchanged(repo)
770 cmdutil.bailifchanged(repo)
771 hg.clean(repo, node, show_stats=False)
771 hg.clean(repo, node, show_stats=False)
772 finally:
772 finally:
773 state['current'] = [node]
773 state['current'] = [node]
774 hbisect.save_state(repo, state)
774 hbisect.save_state(repo, state)
775 print_result(nodes, bgood)
775 print_result(nodes, bgood)
776 return
776 return
777
777
778 # update state
778 # update state
779
779
780 if rev:
780 if rev:
781 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
781 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
782 else:
782 else:
783 nodes = [repo.lookup('.')]
783 nodes = [repo.lookup('.')]
784
784
785 if good or bad or skip:
785 if good or bad or skip:
786 if good:
786 if good:
787 state['good'] += nodes
787 state['good'] += nodes
788 elif bad:
788 elif bad:
789 state['bad'] += nodes
789 state['bad'] += nodes
790 elif skip:
790 elif skip:
791 state['skip'] += nodes
791 state['skip'] += nodes
792 hbisect.save_state(repo, state)
792 hbisect.save_state(repo, state)
793
793
794 if not check_state(state):
794 if not check_state(state):
795 return
795 return
796
796
797 # actually bisect
797 # actually bisect
798 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
798 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
799 if extend:
799 if extend:
800 if not changesets:
800 if not changesets:
801 extendnode = extendbisectrange(nodes, good)
801 extendnode = extendbisectrange(nodes, good)
802 if extendnode is not None:
802 if extendnode is not None:
803 ui.write(_("Extending search to changeset %d:%s\n")
803 ui.write(_("Extending search to changeset %d:%s\n")
804 % (extendnode.rev(), extendnode))
804 % (extendnode.rev(), extendnode))
805 state['current'] = [extendnode.node()]
805 state['current'] = [extendnode.node()]
806 hbisect.save_state(repo, state)
806 hbisect.save_state(repo, state)
807 if noupdate:
807 if noupdate:
808 return
808 return
809 cmdutil.bailifchanged(repo)
809 cmdutil.bailifchanged(repo)
810 return hg.clean(repo, extendnode.node())
810 return hg.clean(repo, extendnode.node())
811 raise util.Abort(_("nothing to extend"))
811 raise util.Abort(_("nothing to extend"))
812
812
813 if changesets == 0:
813 if changesets == 0:
814 print_result(nodes, good)
814 print_result(nodes, good)
815 else:
815 else:
816 assert len(nodes) == 1 # only a single node can be tested next
816 assert len(nodes) == 1 # only a single node can be tested next
817 node = nodes[0]
817 node = nodes[0]
818 # compute the approximate number of remaining tests
818 # compute the approximate number of remaining tests
819 tests, size = 0, 2
819 tests, size = 0, 2
820 while size <= changesets:
820 while size <= changesets:
821 tests, size = tests + 1, size * 2
821 tests, size = tests + 1, size * 2
822 rev = repo.changelog.rev(node)
822 rev = repo.changelog.rev(node)
823 ui.write(_("Testing changeset %d:%s "
823 ui.write(_("Testing changeset %d:%s "
824 "(%d changesets remaining, ~%d tests)\n")
824 "(%d changesets remaining, ~%d tests)\n")
825 % (rev, short(node), changesets, tests))
825 % (rev, short(node), changesets, tests))
826 state['current'] = [node]
826 state['current'] = [node]
827 hbisect.save_state(repo, state)
827 hbisect.save_state(repo, state)
828 if not noupdate:
828 if not noupdate:
829 cmdutil.bailifchanged(repo)
829 cmdutil.bailifchanged(repo)
830 return hg.clean(repo, node)
830 return hg.clean(repo, node)
831
831
832 @command('bookmarks|bookmark',
832 @command('bookmarks|bookmark',
833 [('f', 'force', False, _('force')),
833 [('f', 'force', False, _('force')),
834 ('r', 'rev', '', _('revision'), _('REV')),
834 ('r', 'rev', '', _('revision'), _('REV')),
835 ('d', 'delete', False, _('delete a given bookmark')),
835 ('d', 'delete', False, _('delete a given bookmark')),
836 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
836 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
837 ('i', 'inactive', False, _('mark a bookmark inactive')),
837 ('i', 'inactive', False, _('mark a bookmark inactive')),
838 ] + formatteropts,
838 ] + formatteropts,
839 _('hg bookmarks [OPTIONS]... [NAME]...'))
839 _('hg bookmarks [OPTIONS]... [NAME]...'))
840 def bookmark(ui, repo, *names, **opts):
840 def bookmark(ui, repo, *names, **opts):
841 '''create a new bookmark or list existing bookmarks
841 '''create a new bookmark or list existing bookmarks
842
842
843 Bookmarks are labels on changesets to help track lines of development.
843 Bookmarks are labels on changesets to help track lines of development.
844 Bookmarks are unversioned and can be moved, renamed and deleted.
844 Bookmarks are unversioned and can be moved, renamed and deleted.
845 Deleting or moving a bookmark has no effect on the associated changesets.
845 Deleting or moving a bookmark has no effect on the associated changesets.
846
846
847 Creating or updating to a bookmark causes it to be marked as 'active'.
847 Creating or updating to a bookmark causes it to be marked as 'active'.
848 The active bookmark is indicated with a '*'.
848 The active bookmark is indicated with a '*'.
849 When a commit is made, the active bookmark will advance to the new commit.
849 When a commit is made, the active bookmark will advance to the new commit.
850 A plain :hg:`update` will also advance an active bookmark, if possible.
850 A plain :hg:`update` will also advance an active bookmark, if possible.
851 Updating away from a bookmark will cause it to be deactivated.
851 Updating away from a bookmark will cause it to be deactivated.
852
852
853 Bookmarks can be pushed and pulled between repositories (see
853 Bookmarks can be pushed and pulled between repositories (see
854 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
854 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
855 diverged, a new 'divergent bookmark' of the form 'name@path' will
855 diverged, a new 'divergent bookmark' of the form 'name@path' will
856 be created. Using :hg:'merge' will resolve the divergence.
856 be created. Using :hg:'merge' will resolve the divergence.
857
857
858 A bookmark named '@' has the special property that :hg:`clone` will
858 A bookmark named '@' has the special property that :hg:`clone` will
859 check it out by default if it exists.
859 check it out by default if it exists.
860
860
861 .. container:: verbose
861 .. container:: verbose
862
862
863 Examples:
863 Examples:
864
864
865 - create an active bookmark for a new line of development::
865 - create an active bookmark for a new line of development::
866
866
867 hg book new-feature
867 hg book new-feature
868
868
869 - create an inactive bookmark as a place marker::
869 - create an inactive bookmark as a place marker::
870
870
871 hg book -i reviewed
871 hg book -i reviewed
872
872
873 - create an inactive bookmark on another changeset::
873 - create an inactive bookmark on another changeset::
874
874
875 hg book -r .^ tested
875 hg book -r .^ tested
876
876
877 - move the '@' bookmark from another branch::
877 - move the '@' bookmark from another branch::
878
878
879 hg book -f @
879 hg book -f @
880 '''
880 '''
881 force = opts.get('force')
881 force = opts.get('force')
882 rev = opts.get('rev')
882 rev = opts.get('rev')
883 delete = opts.get('delete')
883 delete = opts.get('delete')
884 rename = opts.get('rename')
884 rename = opts.get('rename')
885 inactive = opts.get('inactive')
885 inactive = opts.get('inactive')
886
886
887 def checkformat(mark):
887 def checkformat(mark):
888 mark = mark.strip()
888 mark = mark.strip()
889 if not mark:
889 if not mark:
890 raise util.Abort(_("bookmark names cannot consist entirely of "
890 raise util.Abort(_("bookmark names cannot consist entirely of "
891 "whitespace"))
891 "whitespace"))
892 scmutil.checknewlabel(repo, mark, 'bookmark')
892 scmutil.checknewlabel(repo, mark, 'bookmark')
893 return mark
893 return mark
894
894
895 def checkconflict(repo, mark, cur, force=False, target=None):
895 def checkconflict(repo, mark, cur, force=False, target=None):
896 if mark in marks and not force:
896 if mark in marks and not force:
897 if target:
897 if target:
898 if marks[mark] == target and target == cur:
898 if marks[mark] == target and target == cur:
899 # re-activating a bookmark
899 # re-activating a bookmark
900 return
900 return
901 anc = repo.changelog.ancestors([repo[target].rev()])
901 anc = repo.changelog.ancestors([repo[target].rev()])
902 bmctx = repo[marks[mark]]
902 bmctx = repo[marks[mark]]
903 divs = [repo[b].node() for b in marks
903 divs = [repo[b].node() for b in marks
904 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
904 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
905
905
906 # allow resolving a single divergent bookmark even if moving
906 # allow resolving a single divergent bookmark even if moving
907 # the bookmark across branches when a revision is specified
907 # the bookmark across branches when a revision is specified
908 # that contains a divergent bookmark
908 # that contains a divergent bookmark
909 if bmctx.rev() not in anc and target in divs:
909 if bmctx.rev() not in anc and target in divs:
910 bookmarks.deletedivergent(repo, [target], mark)
910 bookmarks.deletedivergent(repo, [target], mark)
911 return
911 return
912
912
913 deletefrom = [b for b in divs
913 deletefrom = [b for b in divs
914 if repo[b].rev() in anc or b == target]
914 if repo[b].rev() in anc or b == target]
915 bookmarks.deletedivergent(repo, deletefrom, mark)
915 bookmarks.deletedivergent(repo, deletefrom, mark)
916 if bookmarks.validdest(repo, bmctx, repo[target]):
916 if bookmarks.validdest(repo, bmctx, repo[target]):
917 ui.status(_("moving bookmark '%s' forward from %s\n") %
917 ui.status(_("moving bookmark '%s' forward from %s\n") %
918 (mark, short(bmctx.node())))
918 (mark, short(bmctx.node())))
919 return
919 return
920 raise util.Abort(_("bookmark '%s' already exists "
920 raise util.Abort(_("bookmark '%s' already exists "
921 "(use -f to force)") % mark)
921 "(use -f to force)") % mark)
922 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
922 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
923 and not force):
923 and not force):
924 raise util.Abort(
924 raise util.Abort(
925 _("a bookmark cannot have the name of an existing branch"))
925 _("a bookmark cannot have the name of an existing branch"))
926
926
927 if delete and rename:
927 if delete and rename:
928 raise util.Abort(_("--delete and --rename are incompatible"))
928 raise util.Abort(_("--delete and --rename are incompatible"))
929 if delete and rev:
929 if delete and rev:
930 raise util.Abort(_("--rev is incompatible with --delete"))
930 raise util.Abort(_("--rev is incompatible with --delete"))
931 if rename and rev:
931 if rename and rev:
932 raise util.Abort(_("--rev is incompatible with --rename"))
932 raise util.Abort(_("--rev is incompatible with --rename"))
933 if not names and (delete or rev):
933 if not names and (delete or rev):
934 raise util.Abort(_("bookmark name required"))
934 raise util.Abort(_("bookmark name required"))
935
935
936 if delete or rename or names or inactive:
936 if delete or rename or names or inactive:
937 wlock = repo.wlock()
937 wlock = repo.wlock()
938 try:
938 try:
939 cur = repo.changectx('.').node()
939 cur = repo.changectx('.').node()
940 marks = repo._bookmarks
940 marks = repo._bookmarks
941 if delete:
941 if delete:
942 for mark in names:
942 for mark in names:
943 if mark not in marks:
943 if mark not in marks:
944 raise util.Abort(_("bookmark '%s' does not exist") %
944 raise util.Abort(_("bookmark '%s' does not exist") %
945 mark)
945 mark)
946 if mark == repo._bookmarkcurrent:
946 if mark == repo._bookmarkcurrent:
947 bookmarks.unsetcurrent(repo)
947 bookmarks.unsetcurrent(repo)
948 del marks[mark]
948 del marks[mark]
949 marks.write()
949 marks.write()
950
950
951 elif rename:
951 elif rename:
952 if not names:
952 if not names:
953 raise util.Abort(_("new bookmark name required"))
953 raise util.Abort(_("new bookmark name required"))
954 elif len(names) > 1:
954 elif len(names) > 1:
955 raise util.Abort(_("only one new bookmark name allowed"))
955 raise util.Abort(_("only one new bookmark name allowed"))
956 mark = checkformat(names[0])
956 mark = checkformat(names[0])
957 if rename not in marks:
957 if rename not in marks:
958 raise util.Abort(_("bookmark '%s' does not exist") % rename)
958 raise util.Abort(_("bookmark '%s' does not exist") % rename)
959 checkconflict(repo, mark, cur, force)
959 checkconflict(repo, mark, cur, force)
960 marks[mark] = marks[rename]
960 marks[mark] = marks[rename]
961 if repo._bookmarkcurrent == rename and not inactive:
961 if repo._bookmarkcurrent == rename and not inactive:
962 bookmarks.setcurrent(repo, mark)
962 bookmarks.setcurrent(repo, mark)
963 del marks[rename]
963 del marks[rename]
964 marks.write()
964 marks.write()
965
965
966 elif names:
966 elif names:
967 newact = None
967 newact = None
968 for mark in names:
968 for mark in names:
969 mark = checkformat(mark)
969 mark = checkformat(mark)
970 if newact is None:
970 if newact is None:
971 newact = mark
971 newact = mark
972 if inactive and mark == repo._bookmarkcurrent:
972 if inactive and mark == repo._bookmarkcurrent:
973 bookmarks.unsetcurrent(repo)
973 bookmarks.unsetcurrent(repo)
974 return
974 return
975 tgt = cur
975 tgt = cur
976 if rev:
976 if rev:
977 tgt = scmutil.revsingle(repo, rev).node()
977 tgt = scmutil.revsingle(repo, rev).node()
978 checkconflict(repo, mark, cur, force, tgt)
978 checkconflict(repo, mark, cur, force, tgt)
979 marks[mark] = tgt
979 marks[mark] = tgt
980 if not inactive and cur == marks[newact] and not rev:
980 if not inactive and cur == marks[newact] and not rev:
981 bookmarks.setcurrent(repo, newact)
981 bookmarks.setcurrent(repo, newact)
982 elif cur != tgt and newact == repo._bookmarkcurrent:
982 elif cur != tgt and newact == repo._bookmarkcurrent:
983 bookmarks.unsetcurrent(repo)
983 bookmarks.unsetcurrent(repo)
984 marks.write()
984 marks.write()
985
985
986 elif inactive:
986 elif inactive:
987 if len(marks) == 0:
987 if len(marks) == 0:
988 ui.status(_("no bookmarks set\n"))
988 ui.status(_("no bookmarks set\n"))
989 elif not repo._bookmarkcurrent:
989 elif not repo._bookmarkcurrent:
990 ui.status(_("no active bookmark\n"))
990 ui.status(_("no active bookmark\n"))
991 else:
991 else:
992 bookmarks.unsetcurrent(repo)
992 bookmarks.unsetcurrent(repo)
993 finally:
993 finally:
994 wlock.release()
994 wlock.release()
995 else: # show bookmarks
995 else: # show bookmarks
996 fm = ui.formatter('bookmarks', opts)
996 fm = ui.formatter('bookmarks', opts)
997 hexfn = fm.hexfunc
997 hexfn = fm.hexfunc
998 marks = repo._bookmarks
998 marks = repo._bookmarks
999 if len(marks) == 0 and not fm:
999 if len(marks) == 0 and not fm:
1000 ui.status(_("no bookmarks set\n"))
1000 ui.status(_("no bookmarks set\n"))
1001 for bmark, n in sorted(marks.iteritems()):
1001 for bmark, n in sorted(marks.iteritems()):
1002 current = repo._bookmarkcurrent
1002 current = repo._bookmarkcurrent
1003 if bmark == current:
1003 if bmark == current:
1004 prefix, label = '*', 'bookmarks.current'
1004 prefix, label = '*', 'bookmarks.current'
1005 else:
1005 else:
1006 prefix, label = ' ', ''
1006 prefix, label = ' ', ''
1007
1007
1008 fm.startitem()
1008 fm.startitem()
1009 if not ui.quiet:
1009 if not ui.quiet:
1010 fm.plain(' %s ' % prefix, label=label)
1010 fm.plain(' %s ' % prefix, label=label)
1011 fm.write('bookmark', '%s', bmark, label=label)
1011 fm.write('bookmark', '%s', bmark, label=label)
1012 pad = " " * (25 - encoding.colwidth(bmark))
1012 pad = " " * (25 - encoding.colwidth(bmark))
1013 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
1013 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
1014 repo.changelog.rev(n), hexfn(n), label=label)
1014 repo.changelog.rev(n), hexfn(n), label=label)
1015 fm.data(active=(bmark == current))
1015 fm.data(active=(bmark == current))
1016 fm.plain('\n')
1016 fm.plain('\n')
1017 fm.end()
1017 fm.end()
1018
1018
1019 @command('branch',
1019 @command('branch',
1020 [('f', 'force', None,
1020 [('f', 'force', None,
1021 _('set branch name even if it shadows an existing branch')),
1021 _('set branch name even if it shadows an existing branch')),
1022 ('C', 'clean', None, _('reset branch name to parent branch name'))],
1022 ('C', 'clean', None, _('reset branch name to parent branch name'))],
1023 _('[-fC] [NAME]'))
1023 _('[-fC] [NAME]'))
1024 def branch(ui, repo, label=None, **opts):
1024 def branch(ui, repo, label=None, **opts):
1025 """set or show the current branch name
1025 """set or show the current branch name
1026
1026
1027 .. note::
1027 .. note::
1028
1028
1029 Branch names are permanent and global. Use :hg:`bookmark` to create a
1029 Branch names are permanent and global. Use :hg:`bookmark` to create a
1030 light-weight bookmark instead. See :hg:`help glossary` for more
1030 light-weight bookmark instead. See :hg:`help glossary` for more
1031 information about named branches and bookmarks.
1031 information about named branches and bookmarks.
1032
1032
1033 With no argument, show the current branch name. With one argument,
1033 With no argument, show the current branch name. With one argument,
1034 set the working directory branch name (the branch will not exist
1034 set the working directory branch name (the branch will not exist
1035 in the repository until the next commit). Standard practice
1035 in the repository until the next commit). Standard practice
1036 recommends that primary development take place on the 'default'
1036 recommends that primary development take place on the 'default'
1037 branch.
1037 branch.
1038
1038
1039 Unless -f/--force is specified, branch will not let you set a
1039 Unless -f/--force is specified, branch will not let you set a
1040 branch name that already exists, even if it's inactive.
1040 branch name that already exists, even if it's inactive.
1041
1041
1042 Use -C/--clean to reset the working directory branch to that of
1042 Use -C/--clean to reset the working directory branch to that of
1043 the parent of the working directory, negating a previous branch
1043 the parent of the working directory, negating a previous branch
1044 change.
1044 change.
1045
1045
1046 Use the command :hg:`update` to switch to an existing branch. Use
1046 Use the command :hg:`update` to switch to an existing branch. Use
1047 :hg:`commit --close-branch` to mark this branch as closed.
1047 :hg:`commit --close-branch` to mark this branch as closed.
1048
1048
1049 Returns 0 on success.
1049 Returns 0 on success.
1050 """
1050 """
1051 if label:
1051 if label:
1052 label = label.strip()
1052 label = label.strip()
1053
1053
1054 if not opts.get('clean') and not label:
1054 if not opts.get('clean') and not label:
1055 ui.write("%s\n" % repo.dirstate.branch())
1055 ui.write("%s\n" % repo.dirstate.branch())
1056 return
1056 return
1057
1057
1058 wlock = repo.wlock()
1058 wlock = repo.wlock()
1059 try:
1059 try:
1060 if opts.get('clean'):
1060 if opts.get('clean'):
1061 label = repo[None].p1().branch()
1061 label = repo[None].p1().branch()
1062 repo.dirstate.setbranch(label)
1062 repo.dirstate.setbranch(label)
1063 ui.status(_('reset working directory to branch %s\n') % label)
1063 ui.status(_('reset working directory to branch %s\n') % label)
1064 elif label:
1064 elif label:
1065 if not opts.get('force') and label in repo.branchmap():
1065 if not opts.get('force') and label in repo.branchmap():
1066 if label not in [p.branch() for p in repo.parents()]:
1066 if label not in [p.branch() for p in repo.parents()]:
1067 raise util.Abort(_('a branch of the same name already'
1067 raise util.Abort(_('a branch of the same name already'
1068 ' exists'),
1068 ' exists'),
1069 # i18n: "it" refers to an existing branch
1069 # i18n: "it" refers to an existing branch
1070 hint=_("use 'hg update' to switch to it"))
1070 hint=_("use 'hg update' to switch to it"))
1071 scmutil.checknewlabel(repo, label, 'branch')
1071 scmutil.checknewlabel(repo, label, 'branch')
1072 repo.dirstate.setbranch(label)
1072 repo.dirstate.setbranch(label)
1073 ui.status(_('marked working directory as branch %s\n') % label)
1073 ui.status(_('marked working directory as branch %s\n') % label)
1074 ui.status(_('(branches are permanent and global, '
1074 ui.status(_('(branches are permanent and global, '
1075 'did you want a bookmark?)\n'))
1075 'did you want a bookmark?)\n'))
1076 finally:
1076 finally:
1077 wlock.release()
1077 wlock.release()
1078
1078
1079 @command('branches',
1079 @command('branches',
1080 [('a', 'active', False, _('show only branches that have unmerged heads')),
1080 [('a', 'active', False, _('show only branches that have unmerged heads')),
1081 ('c', 'closed', False, _('show normal and closed branches')),
1081 ('c', 'closed', False, _('show normal and closed branches')),
1082 ] + formatteropts,
1082 ] + formatteropts,
1083 _('[-ac]'))
1083 _('[-ac]'))
1084 def branches(ui, repo, active=False, closed=False, **opts):
1084 def branches(ui, repo, active=False, closed=False, **opts):
1085 """list repository named branches
1085 """list repository named branches
1086
1086
1087 List the repository's named branches, indicating which ones are
1087 List the repository's named branches, indicating which ones are
1088 inactive. If -c/--closed is specified, also list branches which have
1088 inactive. If -c/--closed is specified, also list branches which have
1089 been marked closed (see :hg:`commit --close-branch`).
1089 been marked closed (see :hg:`commit --close-branch`).
1090
1090
1091 If -a/--active is specified, only show active branches. A branch
1091 If -a/--active is specified, only show active branches. A branch
1092 is considered active if it contains repository heads.
1092 is considered active if it contains repository heads.
1093
1093
1094 Use the command :hg:`update` to switch to an existing branch.
1094 Use the command :hg:`update` to switch to an existing branch.
1095
1095
1096 Returns 0.
1096 Returns 0.
1097 """
1097 """
1098
1098
1099 fm = ui.formatter('branches', opts)
1099 fm = ui.formatter('branches', opts)
1100 hexfunc = fm.hexfunc
1100 hexfunc = fm.hexfunc
1101
1101
1102 allheads = set(repo.heads())
1102 allheads = set(repo.heads())
1103 branches = []
1103 branches = []
1104 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1104 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1105 isactive = not isclosed and bool(set(heads) & allheads)
1105 isactive = not isclosed and bool(set(heads) & allheads)
1106 branches.append((tag, repo[tip], isactive, not isclosed))
1106 branches.append((tag, repo[tip], isactive, not isclosed))
1107 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1107 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1108 reverse=True)
1108 reverse=True)
1109
1109
1110 for tag, ctx, isactive, isopen in branches:
1110 for tag, ctx, isactive, isopen in branches:
1111 if active and not isactive:
1111 if active and not isactive:
1112 continue
1112 continue
1113 if isactive:
1113 if isactive:
1114 label = 'branches.active'
1114 label = 'branches.active'
1115 notice = ''
1115 notice = ''
1116 elif not isopen:
1116 elif not isopen:
1117 if not closed:
1117 if not closed:
1118 continue
1118 continue
1119 label = 'branches.closed'
1119 label = 'branches.closed'
1120 notice = _(' (closed)')
1120 notice = _(' (closed)')
1121 else:
1121 else:
1122 label = 'branches.inactive'
1122 label = 'branches.inactive'
1123 notice = _(' (inactive)')
1123 notice = _(' (inactive)')
1124 current = (tag == repo.dirstate.branch())
1124 current = (tag == repo.dirstate.branch())
1125 if current:
1125 if current:
1126 label = 'branches.current'
1126 label = 'branches.current'
1127
1127
1128 fm.startitem()
1128 fm.startitem()
1129 fm.write('branch', '%s', tag, label=label)
1129 fm.write('branch', '%s', tag, label=label)
1130 rev = ctx.rev()
1130 rev = ctx.rev()
1131 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1131 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1132 fmt = ' ' * padsize + ' %d:%s'
1132 fmt = ' ' * padsize + ' %d:%s'
1133 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1133 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1134 label='log.changeset changeset.%s' % ctx.phasestr())
1134 label='log.changeset changeset.%s' % ctx.phasestr())
1135 fm.data(active=isactive, closed=not isopen, current=current)
1135 fm.data(active=isactive, closed=not isopen, current=current)
1136 if not ui.quiet:
1136 if not ui.quiet:
1137 fm.plain(notice)
1137 fm.plain(notice)
1138 fm.plain('\n')
1138 fm.plain('\n')
1139 fm.end()
1139 fm.end()
1140
1140
1141 @command('bundle',
1141 @command('bundle',
1142 [('f', 'force', None, _('run even when the destination is unrelated')),
1142 [('f', 'force', None, _('run even when the destination is unrelated')),
1143 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1143 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1144 _('REV')),
1144 _('REV')),
1145 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1145 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1146 _('BRANCH')),
1146 _('BRANCH')),
1147 ('', 'base', [],
1147 ('', 'base', [],
1148 _('a base changeset assumed to be available at the destination'),
1148 _('a base changeset assumed to be available at the destination'),
1149 _('REV')),
1149 _('REV')),
1150 ('a', 'all', None, _('bundle all changesets in the repository')),
1150 ('a', 'all', None, _('bundle all changesets in the repository')),
1151 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1151 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1152 ] + remoteopts,
1152 ] + remoteopts,
1153 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1153 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1154 def bundle(ui, repo, fname, dest=None, **opts):
1154 def bundle(ui, repo, fname, dest=None, **opts):
1155 """create a changegroup file
1155 """create a changegroup file
1156
1156
1157 Generate a compressed changegroup file collecting changesets not
1157 Generate a compressed changegroup file collecting changesets not
1158 known to be in another repository.
1158 known to be in another repository.
1159
1159
1160 If you omit the destination repository, then hg assumes the
1160 If you omit the destination repository, then hg assumes the
1161 destination will have all the nodes you specify with --base
1161 destination will have all the nodes you specify with --base
1162 parameters. To create a bundle containing all changesets, use
1162 parameters. To create a bundle containing all changesets, use
1163 -a/--all (or --base null).
1163 -a/--all (or --base null).
1164
1164
1165 You can change compression method with the -t/--type option.
1165 You can change compression method with the -t/--type option.
1166 The available compression methods are: none, bzip2, and
1166 The available compression methods are: none, bzip2, and
1167 gzip (by default, bundles are compressed using bzip2).
1167 gzip (by default, bundles are compressed using bzip2).
1168
1168
1169 The bundle file can then be transferred using conventional means
1169 The bundle file can then be transferred using conventional means
1170 and applied to another repository with the unbundle or pull
1170 and applied to another repository with the unbundle or pull
1171 command. This is useful when direct push and pull are not
1171 command. This is useful when direct push and pull are not
1172 available or when exporting an entire repository is undesirable.
1172 available or when exporting an entire repository is undesirable.
1173
1173
1174 Applying bundles preserves all changeset contents including
1174 Applying bundles preserves all changeset contents including
1175 permissions, copy/rename information, and revision history.
1175 permissions, copy/rename information, and revision history.
1176
1176
1177 Returns 0 on success, 1 if no changes found.
1177 Returns 0 on success, 1 if no changes found.
1178 """
1178 """
1179 revs = None
1179 revs = None
1180 if 'rev' in opts:
1180 if 'rev' in opts:
1181 revs = scmutil.revrange(repo, opts['rev'])
1181 revs = scmutil.revrange(repo, opts['rev'])
1182
1182
1183 bundletype = opts.get('type', 'bzip2').lower()
1183 bundletype = opts.get('type', 'bzip2').lower()
1184 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1184 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1185 bundletype = btypes.get(bundletype)
1185 bundletype = btypes.get(bundletype)
1186 if bundletype not in changegroup.bundletypes:
1186 if bundletype not in changegroup.bundletypes:
1187 raise util.Abort(_('unknown bundle type specified with --type'))
1187 raise util.Abort(_('unknown bundle type specified with --type'))
1188
1188
1189 if opts.get('all'):
1189 if opts.get('all'):
1190 base = ['null']
1190 base = ['null']
1191 else:
1191 else:
1192 base = scmutil.revrange(repo, opts.get('base'))
1192 base = scmutil.revrange(repo, opts.get('base'))
1193 # TODO: get desired bundlecaps from command line.
1193 # TODO: get desired bundlecaps from command line.
1194 bundlecaps = None
1194 bundlecaps = None
1195 if base:
1195 if base:
1196 if dest:
1196 if dest:
1197 raise util.Abort(_("--base is incompatible with specifying "
1197 raise util.Abort(_("--base is incompatible with specifying "
1198 "a destination"))
1198 "a destination"))
1199 common = [repo.lookup(rev) for rev in base]
1199 common = [repo.lookup(rev) for rev in base]
1200 heads = revs and map(repo.lookup, revs) or revs
1200 heads = revs and map(repo.lookup, revs) or revs
1201 cg = changegroup.getchangegroup(repo, 'bundle', heads=heads,
1201 cg = changegroup.getchangegroup(repo, 'bundle', heads=heads,
1202 common=common, bundlecaps=bundlecaps)
1202 common=common, bundlecaps=bundlecaps)
1203 outgoing = None
1203 outgoing = None
1204 else:
1204 else:
1205 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1205 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1206 dest, branches = hg.parseurl(dest, opts.get('branch'))
1206 dest, branches = hg.parseurl(dest, opts.get('branch'))
1207 other = hg.peer(repo, opts, dest)
1207 other = hg.peer(repo, opts, dest)
1208 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1208 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1209 heads = revs and map(repo.lookup, revs) or revs
1209 heads = revs and map(repo.lookup, revs) or revs
1210 outgoing = discovery.findcommonoutgoing(repo, other,
1210 outgoing = discovery.findcommonoutgoing(repo, other,
1211 onlyheads=heads,
1211 onlyheads=heads,
1212 force=opts.get('force'),
1212 force=opts.get('force'),
1213 portable=True)
1213 portable=True)
1214 cg = changegroup.getlocalchangegroup(repo, 'bundle', outgoing,
1214 cg = changegroup.getlocalchangegroup(repo, 'bundle', outgoing,
1215 bundlecaps)
1215 bundlecaps)
1216 if not cg:
1216 if not cg:
1217 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1217 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1218 return 1
1218 return 1
1219
1219
1220 changegroup.writebundle(cg, fname, bundletype)
1220 changegroup.writebundle(cg, fname, bundletype)
1221
1221
1222 @command('cat',
1222 @command('cat',
1223 [('o', 'output', '',
1223 [('o', 'output', '',
1224 _('print output to file with formatted name'), _('FORMAT')),
1224 _('print output to file with formatted name'), _('FORMAT')),
1225 ('r', 'rev', '', _('print the given revision'), _('REV')),
1225 ('r', 'rev', '', _('print the given revision'), _('REV')),
1226 ('', 'decode', None, _('apply any matching decode filter')),
1226 ('', 'decode', None, _('apply any matching decode filter')),
1227 ] + walkopts,
1227 ] + walkopts,
1228 _('[OPTION]... FILE...'),
1228 _('[OPTION]... FILE...'),
1229 inferrepo=True)
1229 inferrepo=True)
1230 def cat(ui, repo, file1, *pats, **opts):
1230 def cat(ui, repo, file1, *pats, **opts):
1231 """output the current or given revision of files
1231 """output the current or given revision of files
1232
1232
1233 Print the specified files as they were at the given revision. If
1233 Print the specified files as they were at the given revision. If
1234 no revision is given, the parent of the working directory is used.
1234 no revision is given, the parent of the working directory is used.
1235
1235
1236 Output may be to a file, in which case the name of the file is
1236 Output may be to a file, in which case the name of the file is
1237 given using a format string. The formatting rules as follows:
1237 given using a format string. The formatting rules as follows:
1238
1238
1239 :``%%``: literal "%" character
1239 :``%%``: literal "%" character
1240 :``%s``: basename of file being printed
1240 :``%s``: basename of file being printed
1241 :``%d``: dirname of file being printed, or '.' if in repository root
1241 :``%d``: dirname of file being printed, or '.' if in repository root
1242 :``%p``: root-relative path name of file being printed
1242 :``%p``: root-relative path name of file being printed
1243 :``%H``: changeset hash (40 hexadecimal digits)
1243 :``%H``: changeset hash (40 hexadecimal digits)
1244 :``%R``: changeset revision number
1244 :``%R``: changeset revision number
1245 :``%h``: short-form changeset hash (12 hexadecimal digits)
1245 :``%h``: short-form changeset hash (12 hexadecimal digits)
1246 :``%r``: zero-padded changeset revision number
1246 :``%r``: zero-padded changeset revision number
1247 :``%b``: basename of the exporting repository
1247 :``%b``: basename of the exporting repository
1248
1248
1249 Returns 0 on success.
1249 Returns 0 on success.
1250 """
1250 """
1251 ctx = scmutil.revsingle(repo, opts.get('rev'))
1251 ctx = scmutil.revsingle(repo, opts.get('rev'))
1252 m = scmutil.match(ctx, (file1,) + pats, opts)
1252 m = scmutil.match(ctx, (file1,) + pats, opts)
1253
1253
1254 return cmdutil.cat(ui, repo, ctx, m, '', **opts)
1254 return cmdutil.cat(ui, repo, ctx, m, '', **opts)
1255
1255
1256 @command('^clone',
1256 @command('^clone',
1257 [('U', 'noupdate', None,
1257 [('U', 'noupdate', None,
1258 _('the clone will include an empty working copy (only a repository)')),
1258 _('the clone will include an empty working copy (only a repository)')),
1259 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1259 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1260 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1260 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1261 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1261 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1262 ('', 'pull', None, _('use pull protocol to copy metadata')),
1262 ('', 'pull', None, _('use pull protocol to copy metadata')),
1263 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1263 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1264 ] + remoteopts,
1264 ] + remoteopts,
1265 _('[OPTION]... SOURCE [DEST]'),
1265 _('[OPTION]... SOURCE [DEST]'),
1266 norepo=True)
1266 norepo=True)
1267 def clone(ui, source, dest=None, **opts):
1267 def clone(ui, source, dest=None, **opts):
1268 """make a copy of an existing repository
1268 """make a copy of an existing repository
1269
1269
1270 Create a copy of an existing repository in a new directory.
1270 Create a copy of an existing repository in a new directory.
1271
1271
1272 If no destination directory name is specified, it defaults to the
1272 If no destination directory name is specified, it defaults to the
1273 basename of the source.
1273 basename of the source.
1274
1274
1275 The location of the source is added to the new repository's
1275 The location of the source is added to the new repository's
1276 ``.hg/hgrc`` file, as the default to be used for future pulls.
1276 ``.hg/hgrc`` file, as the default to be used for future pulls.
1277
1277
1278 Only local paths and ``ssh://`` URLs are supported as
1278 Only local paths and ``ssh://`` URLs are supported as
1279 destinations. For ``ssh://`` destinations, no working directory or
1279 destinations. For ``ssh://`` destinations, no working directory or
1280 ``.hg/hgrc`` will be created on the remote side.
1280 ``.hg/hgrc`` will be created on the remote side.
1281
1281
1282 To pull only a subset of changesets, specify one or more revisions
1282 To pull only a subset of changesets, specify one or more revisions
1283 identifiers with -r/--rev or branches with -b/--branch. The
1283 identifiers with -r/--rev or branches with -b/--branch. The
1284 resulting clone will contain only the specified changesets and
1284 resulting clone will contain only the specified changesets and
1285 their ancestors. These options (or 'clone src#rev dest') imply
1285 their ancestors. These options (or 'clone src#rev dest') imply
1286 --pull, even for local source repositories. Note that specifying a
1286 --pull, even for local source repositories. Note that specifying a
1287 tag will include the tagged changeset but not the changeset
1287 tag will include the tagged changeset but not the changeset
1288 containing the tag.
1288 containing the tag.
1289
1289
1290 If the source repository has a bookmark called '@' set, that
1290 If the source repository has a bookmark called '@' set, that
1291 revision will be checked out in the new repository by default.
1291 revision will be checked out in the new repository by default.
1292
1292
1293 To check out a particular version, use -u/--update, or
1293 To check out a particular version, use -u/--update, or
1294 -U/--noupdate to create a clone with no working directory.
1294 -U/--noupdate to create a clone with no working directory.
1295
1295
1296 .. container:: verbose
1296 .. container:: verbose
1297
1297
1298 For efficiency, hardlinks are used for cloning whenever the
1298 For efficiency, hardlinks are used for cloning whenever the
1299 source and destination are on the same filesystem (note this
1299 source and destination are on the same filesystem (note this
1300 applies only to the repository data, not to the working
1300 applies only to the repository data, not to the working
1301 directory). Some filesystems, such as AFS, implement hardlinking
1301 directory). Some filesystems, such as AFS, implement hardlinking
1302 incorrectly, but do not report errors. In these cases, use the
1302 incorrectly, but do not report errors. In these cases, use the
1303 --pull option to avoid hardlinking.
1303 --pull option to avoid hardlinking.
1304
1304
1305 In some cases, you can clone repositories and the working
1305 In some cases, you can clone repositories and the working
1306 directory using full hardlinks with ::
1306 directory using full hardlinks with ::
1307
1307
1308 $ cp -al REPO REPOCLONE
1308 $ cp -al REPO REPOCLONE
1309
1309
1310 This is the fastest way to clone, but it is not always safe. The
1310 This is the fastest way to clone, but it is not always safe. The
1311 operation is not atomic (making sure REPO is not modified during
1311 operation is not atomic (making sure REPO is not modified during
1312 the operation is up to you) and you have to make sure your
1312 the operation is up to you) and you have to make sure your
1313 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1313 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1314 so). Also, this is not compatible with certain extensions that
1314 so). Also, this is not compatible with certain extensions that
1315 place their metadata under the .hg directory, such as mq.
1315 place their metadata under the .hg directory, such as mq.
1316
1316
1317 Mercurial will update the working directory to the first applicable
1317 Mercurial will update the working directory to the first applicable
1318 revision from this list:
1318 revision from this list:
1319
1319
1320 a) null if -U or the source repository has no changesets
1320 a) null if -U or the source repository has no changesets
1321 b) if -u . and the source repository is local, the first parent of
1321 b) if -u . and the source repository is local, the first parent of
1322 the source repository's working directory
1322 the source repository's working directory
1323 c) the changeset specified with -u (if a branch name, this means the
1323 c) the changeset specified with -u (if a branch name, this means the
1324 latest head of that branch)
1324 latest head of that branch)
1325 d) the changeset specified with -r
1325 d) the changeset specified with -r
1326 e) the tipmost head specified with -b
1326 e) the tipmost head specified with -b
1327 f) the tipmost head specified with the url#branch source syntax
1327 f) the tipmost head specified with the url#branch source syntax
1328 g) the revision marked with the '@' bookmark, if present
1328 g) the revision marked with the '@' bookmark, if present
1329 h) the tipmost head of the default branch
1329 h) the tipmost head of the default branch
1330 i) tip
1330 i) tip
1331
1331
1332 Examples:
1332 Examples:
1333
1333
1334 - clone a remote repository to a new directory named hg/::
1334 - clone a remote repository to a new directory named hg/::
1335
1335
1336 hg clone http://selenic.com/hg
1336 hg clone http://selenic.com/hg
1337
1337
1338 - create a lightweight local clone::
1338 - create a lightweight local clone::
1339
1339
1340 hg clone project/ project-feature/
1340 hg clone project/ project-feature/
1341
1341
1342 - clone from an absolute path on an ssh server (note double-slash)::
1342 - clone from an absolute path on an ssh server (note double-slash)::
1343
1343
1344 hg clone ssh://user@server//home/projects/alpha/
1344 hg clone ssh://user@server//home/projects/alpha/
1345
1345
1346 - do a high-speed clone over a LAN while checking out a
1346 - do a high-speed clone over a LAN while checking out a
1347 specified version::
1347 specified version::
1348
1348
1349 hg clone --uncompressed http://server/repo -u 1.5
1349 hg clone --uncompressed http://server/repo -u 1.5
1350
1350
1351 - create a repository without changesets after a particular revision::
1351 - create a repository without changesets after a particular revision::
1352
1352
1353 hg clone -r 04e544 experimental/ good/
1353 hg clone -r 04e544 experimental/ good/
1354
1354
1355 - clone (and track) a particular named branch::
1355 - clone (and track) a particular named branch::
1356
1356
1357 hg clone http://selenic.com/hg#stable
1357 hg clone http://selenic.com/hg#stable
1358
1358
1359 See :hg:`help urls` for details on specifying URLs.
1359 See :hg:`help urls` for details on specifying URLs.
1360
1360
1361 Returns 0 on success.
1361 Returns 0 on success.
1362 """
1362 """
1363 if opts.get('noupdate') and opts.get('updaterev'):
1363 if opts.get('noupdate') and opts.get('updaterev'):
1364 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1364 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1365
1365
1366 r = hg.clone(ui, opts, source, dest,
1366 r = hg.clone(ui, opts, source, dest,
1367 pull=opts.get('pull'),
1367 pull=opts.get('pull'),
1368 stream=opts.get('uncompressed'),
1368 stream=opts.get('uncompressed'),
1369 rev=opts.get('rev'),
1369 rev=opts.get('rev'),
1370 update=opts.get('updaterev') or not opts.get('noupdate'),
1370 update=opts.get('updaterev') or not opts.get('noupdate'),
1371 branch=opts.get('branch'))
1371 branch=opts.get('branch'))
1372
1372
1373 return r is None
1373 return r is None
1374
1374
1375 @command('^commit|ci',
1375 @command('^commit|ci',
1376 [('A', 'addremove', None,
1376 [('A', 'addremove', None,
1377 _('mark new/missing files as added/removed before committing')),
1377 _('mark new/missing files as added/removed before committing')),
1378 ('', 'close-branch', None,
1378 ('', 'close-branch', None,
1379 _('mark a branch as closed, hiding it from the branch list')),
1379 _('mark a branch as closed, hiding it from the branch list')),
1380 ('', 'amend', None, _('amend the parent of the working dir')),
1380 ('', 'amend', None, _('amend the parent of the working dir')),
1381 ('s', 'secret', None, _('use the secret phase for committing')),
1381 ('s', 'secret', None, _('use the secret phase for committing')),
1382 ('e', 'edit', None, _('invoke editor on commit messages')),
1382 ('e', 'edit', None, _('invoke editor on commit messages')),
1383 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1383 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1384 _('[OPTION]... [FILE]...'),
1384 _('[OPTION]... [FILE]...'),
1385 inferrepo=True)
1385 inferrepo=True)
1386 def commit(ui, repo, *pats, **opts):
1386 def commit(ui, repo, *pats, **opts):
1387 """commit the specified files or all outstanding changes
1387 """commit the specified files or all outstanding changes
1388
1388
1389 Commit changes to the given files into the repository. Unlike a
1389 Commit changes to the given files into the repository. Unlike a
1390 centralized SCM, this operation is a local operation. See
1390 centralized SCM, this operation is a local operation. See
1391 :hg:`push` for a way to actively distribute your changes.
1391 :hg:`push` for a way to actively distribute your changes.
1392
1392
1393 If a list of files is omitted, all changes reported by :hg:`status`
1393 If a list of files is omitted, all changes reported by :hg:`status`
1394 will be committed.
1394 will be committed.
1395
1395
1396 If you are committing the result of a merge, do not provide any
1396 If you are committing the result of a merge, do not provide any
1397 filenames or -I/-X filters.
1397 filenames or -I/-X filters.
1398
1398
1399 If no commit message is specified, Mercurial starts your
1399 If no commit message is specified, Mercurial starts your
1400 configured editor where you can enter a message. In case your
1400 configured editor where you can enter a message. In case your
1401 commit fails, you will find a backup of your message in
1401 commit fails, you will find a backup of your message in
1402 ``.hg/last-message.txt``.
1402 ``.hg/last-message.txt``.
1403
1403
1404 The --amend flag can be used to amend the parent of the
1404 The --amend flag can be used to amend the parent of the
1405 working directory with a new commit that contains the changes
1405 working directory with a new commit that contains the changes
1406 in the parent in addition to those currently reported by :hg:`status`,
1406 in the parent in addition to those currently reported by :hg:`status`,
1407 if there are any. The old commit is stored in a backup bundle in
1407 if there are any. The old commit is stored in a backup bundle in
1408 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1408 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1409 on how to restore it).
1409 on how to restore it).
1410
1410
1411 Message, user and date are taken from the amended commit unless
1411 Message, user and date are taken from the amended commit unless
1412 specified. When a message isn't specified on the command line,
1412 specified. When a message isn't specified on the command line,
1413 the editor will open with the message of the amended commit.
1413 the editor will open with the message of the amended commit.
1414
1414
1415 It is not possible to amend public changesets (see :hg:`help phases`)
1415 It is not possible to amend public changesets (see :hg:`help phases`)
1416 or changesets that have children.
1416 or changesets that have children.
1417
1417
1418 See :hg:`help dates` for a list of formats valid for -d/--date.
1418 See :hg:`help dates` for a list of formats valid for -d/--date.
1419
1419
1420 Returns 0 on success, 1 if nothing changed.
1420 Returns 0 on success, 1 if nothing changed.
1421 """
1421 """
1422 if opts.get('subrepos'):
1422 if opts.get('subrepos'):
1423 if opts.get('amend'):
1423 if opts.get('amend'):
1424 raise util.Abort(_('cannot amend with --subrepos'))
1424 raise util.Abort(_('cannot amend with --subrepos'))
1425 # Let --subrepos on the command line override config setting.
1425 # Let --subrepos on the command line override config setting.
1426 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1426 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1427
1427
1428 cmdutil.checkunfinished(repo, commit=True)
1428 cmdutil.checkunfinished(repo, commit=True)
1429
1429
1430 branch = repo[None].branch()
1430 branch = repo[None].branch()
1431 bheads = repo.branchheads(branch)
1431 bheads = repo.branchheads(branch)
1432
1432
1433 extra = {}
1433 extra = {}
1434 if opts.get('close_branch'):
1434 if opts.get('close_branch'):
1435 extra['close'] = 1
1435 extra['close'] = 1
1436
1436
1437 if not bheads:
1437 if not bheads:
1438 raise util.Abort(_('can only close branch heads'))
1438 raise util.Abort(_('can only close branch heads'))
1439 elif opts.get('amend'):
1439 elif opts.get('amend'):
1440 if repo.parents()[0].p1().branch() != branch and \
1440 if repo.parents()[0].p1().branch() != branch and \
1441 repo.parents()[0].p2().branch() != branch:
1441 repo.parents()[0].p2().branch() != branch:
1442 raise util.Abort(_('can only close branch heads'))
1442 raise util.Abort(_('can only close branch heads'))
1443
1443
1444 if opts.get('amend'):
1444 if opts.get('amend'):
1445 if ui.configbool('ui', 'commitsubrepos'):
1445 if ui.configbool('ui', 'commitsubrepos'):
1446 raise util.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1446 raise util.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1447
1447
1448 old = repo['.']
1448 old = repo['.']
1449 if not old.mutable():
1449 if not old.mutable():
1450 raise util.Abort(_('cannot amend public changesets'))
1450 raise util.Abort(_('cannot amend public changesets'))
1451 if len(repo[None].parents()) > 1:
1451 if len(repo[None].parents()) > 1:
1452 raise util.Abort(_('cannot amend while merging'))
1452 raise util.Abort(_('cannot amend while merging'))
1453 if (not obsolete._enabled) and old.children():
1453 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1454 if not allowunstable and old.children():
1454 raise util.Abort(_('cannot amend changeset with children'))
1455 raise util.Abort(_('cannot amend changeset with children'))
1455
1456
1456 # commitfunc is used only for temporary amend commit by cmdutil.amend
1457 # commitfunc is used only for temporary amend commit by cmdutil.amend
1457 def commitfunc(ui, repo, message, match, opts):
1458 def commitfunc(ui, repo, message, match, opts):
1458 return repo.commit(message,
1459 return repo.commit(message,
1459 opts.get('user') or old.user(),
1460 opts.get('user') or old.user(),
1460 opts.get('date') or old.date(),
1461 opts.get('date') or old.date(),
1461 match,
1462 match,
1462 extra=extra)
1463 extra=extra)
1463
1464
1464 current = repo._bookmarkcurrent
1465 current = repo._bookmarkcurrent
1465 marks = old.bookmarks()
1466 marks = old.bookmarks()
1466 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1467 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1467 if node == old.node():
1468 if node == old.node():
1468 ui.status(_("nothing changed\n"))
1469 ui.status(_("nothing changed\n"))
1469 return 1
1470 return 1
1470 elif marks:
1471 elif marks:
1471 ui.debug('moving bookmarks %r from %s to %s\n' %
1472 ui.debug('moving bookmarks %r from %s to %s\n' %
1472 (marks, old.hex(), hex(node)))
1473 (marks, old.hex(), hex(node)))
1473 newmarks = repo._bookmarks
1474 newmarks = repo._bookmarks
1474 for bm in marks:
1475 for bm in marks:
1475 newmarks[bm] = node
1476 newmarks[bm] = node
1476 if bm == current:
1477 if bm == current:
1477 bookmarks.setcurrent(repo, bm)
1478 bookmarks.setcurrent(repo, bm)
1478 newmarks.write()
1479 newmarks.write()
1479 else:
1480 else:
1480 def commitfunc(ui, repo, message, match, opts):
1481 def commitfunc(ui, repo, message, match, opts):
1481 backup = ui.backupconfig('phases', 'new-commit')
1482 backup = ui.backupconfig('phases', 'new-commit')
1482 baseui = repo.baseui
1483 baseui = repo.baseui
1483 basebackup = baseui.backupconfig('phases', 'new-commit')
1484 basebackup = baseui.backupconfig('phases', 'new-commit')
1484 try:
1485 try:
1485 if opts.get('secret'):
1486 if opts.get('secret'):
1486 ui.setconfig('phases', 'new-commit', 'secret', 'commit')
1487 ui.setconfig('phases', 'new-commit', 'secret', 'commit')
1487 # Propagate to subrepos
1488 # Propagate to subrepos
1488 baseui.setconfig('phases', 'new-commit', 'secret', 'commit')
1489 baseui.setconfig('phases', 'new-commit', 'secret', 'commit')
1489
1490
1490 editform = cmdutil.mergeeditform(repo[None], 'commit.normal')
1491 editform = cmdutil.mergeeditform(repo[None], 'commit.normal')
1491 editor = cmdutil.getcommiteditor(editform=editform, **opts)
1492 editor = cmdutil.getcommiteditor(editform=editform, **opts)
1492 return repo.commit(message, opts.get('user'), opts.get('date'),
1493 return repo.commit(message, opts.get('user'), opts.get('date'),
1493 match,
1494 match,
1494 editor=editor,
1495 editor=editor,
1495 extra=extra)
1496 extra=extra)
1496 finally:
1497 finally:
1497 ui.restoreconfig(backup)
1498 ui.restoreconfig(backup)
1498 repo.baseui.restoreconfig(basebackup)
1499 repo.baseui.restoreconfig(basebackup)
1499
1500
1500
1501
1501 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1502 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1502
1503
1503 if not node:
1504 if not node:
1504 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1505 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1505 if stat[3]:
1506 if stat[3]:
1506 ui.status(_("nothing changed (%d missing files, see "
1507 ui.status(_("nothing changed (%d missing files, see "
1507 "'hg status')\n") % len(stat[3]))
1508 "'hg status')\n") % len(stat[3]))
1508 else:
1509 else:
1509 ui.status(_("nothing changed\n"))
1510 ui.status(_("nothing changed\n"))
1510 return 1
1511 return 1
1511
1512
1512 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1513 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1513
1514
1514 @command('config|showconfig|debugconfig',
1515 @command('config|showconfig|debugconfig',
1515 [('u', 'untrusted', None, _('show untrusted configuration options')),
1516 [('u', 'untrusted', None, _('show untrusted configuration options')),
1516 ('e', 'edit', None, _('edit user config')),
1517 ('e', 'edit', None, _('edit user config')),
1517 ('l', 'local', None, _('edit repository config')),
1518 ('l', 'local', None, _('edit repository config')),
1518 ('g', 'global', None, _('edit global config'))],
1519 ('g', 'global', None, _('edit global config'))],
1519 _('[-u] [NAME]...'),
1520 _('[-u] [NAME]...'),
1520 optionalrepo=True)
1521 optionalrepo=True)
1521 def config(ui, repo, *values, **opts):
1522 def config(ui, repo, *values, **opts):
1522 """show combined config settings from all hgrc files
1523 """show combined config settings from all hgrc files
1523
1524
1524 With no arguments, print names and values of all config items.
1525 With no arguments, print names and values of all config items.
1525
1526
1526 With one argument of the form section.name, print just the value
1527 With one argument of the form section.name, print just the value
1527 of that config item.
1528 of that config item.
1528
1529
1529 With multiple arguments, print names and values of all config
1530 With multiple arguments, print names and values of all config
1530 items with matching section names.
1531 items with matching section names.
1531
1532
1532 With --edit, start an editor on the user-level config file. With
1533 With --edit, start an editor on the user-level config file. With
1533 --global, edit the system-wide config file. With --local, edit the
1534 --global, edit the system-wide config file. With --local, edit the
1534 repository-level config file.
1535 repository-level config file.
1535
1536
1536 With --debug, the source (filename and line number) is printed
1537 With --debug, the source (filename and line number) is printed
1537 for each config item.
1538 for each config item.
1538
1539
1539 See :hg:`help config` for more information about config files.
1540 See :hg:`help config` for more information about config files.
1540
1541
1541 Returns 0 on success, 1 if NAME does not exist.
1542 Returns 0 on success, 1 if NAME does not exist.
1542
1543
1543 """
1544 """
1544
1545
1545 if opts.get('edit') or opts.get('local') or opts.get('global'):
1546 if opts.get('edit') or opts.get('local') or opts.get('global'):
1546 if opts.get('local') and opts.get('global'):
1547 if opts.get('local') and opts.get('global'):
1547 raise util.Abort(_("can't use --local and --global together"))
1548 raise util.Abort(_("can't use --local and --global together"))
1548
1549
1549 if opts.get('local'):
1550 if opts.get('local'):
1550 if not repo:
1551 if not repo:
1551 raise util.Abort(_("can't use --local outside a repository"))
1552 raise util.Abort(_("can't use --local outside a repository"))
1552 paths = [repo.join('hgrc')]
1553 paths = [repo.join('hgrc')]
1553 elif opts.get('global'):
1554 elif opts.get('global'):
1554 paths = scmutil.systemrcpath()
1555 paths = scmutil.systemrcpath()
1555 else:
1556 else:
1556 paths = scmutil.userrcpath()
1557 paths = scmutil.userrcpath()
1557
1558
1558 for f in paths:
1559 for f in paths:
1559 if os.path.exists(f):
1560 if os.path.exists(f):
1560 break
1561 break
1561 else:
1562 else:
1562 if opts.get('global'):
1563 if opts.get('global'):
1563 samplehgrc = uimod.samplehgrcs['global']
1564 samplehgrc = uimod.samplehgrcs['global']
1564 elif opts.get('local'):
1565 elif opts.get('local'):
1565 samplehgrc = uimod.samplehgrcs['local']
1566 samplehgrc = uimod.samplehgrcs['local']
1566 else:
1567 else:
1567 samplehgrc = uimod.samplehgrcs['user']
1568 samplehgrc = uimod.samplehgrcs['user']
1568
1569
1569 f = paths[0]
1570 f = paths[0]
1570 fp = open(f, "w")
1571 fp = open(f, "w")
1571 fp.write(samplehgrc)
1572 fp.write(samplehgrc)
1572 fp.close()
1573 fp.close()
1573
1574
1574 editor = ui.geteditor()
1575 editor = ui.geteditor()
1575 util.system("%s \"%s\"" % (editor, f),
1576 util.system("%s \"%s\"" % (editor, f),
1576 onerr=util.Abort, errprefix=_("edit failed"),
1577 onerr=util.Abort, errprefix=_("edit failed"),
1577 out=ui.fout)
1578 out=ui.fout)
1578 return
1579 return
1579
1580
1580 for f in scmutil.rcpath():
1581 for f in scmutil.rcpath():
1581 ui.debug('read config from: %s\n' % f)
1582 ui.debug('read config from: %s\n' % f)
1582 untrusted = bool(opts.get('untrusted'))
1583 untrusted = bool(opts.get('untrusted'))
1583 if values:
1584 if values:
1584 sections = [v for v in values if '.' not in v]
1585 sections = [v for v in values if '.' not in v]
1585 items = [v for v in values if '.' in v]
1586 items = [v for v in values if '.' in v]
1586 if len(items) > 1 or items and sections:
1587 if len(items) > 1 or items and sections:
1587 raise util.Abort(_('only one config item permitted'))
1588 raise util.Abort(_('only one config item permitted'))
1588 matched = False
1589 matched = False
1589 for section, name, value in ui.walkconfig(untrusted=untrusted):
1590 for section, name, value in ui.walkconfig(untrusted=untrusted):
1590 value = str(value).replace('\n', '\\n')
1591 value = str(value).replace('\n', '\\n')
1591 sectname = section + '.' + name
1592 sectname = section + '.' + name
1592 if values:
1593 if values:
1593 for v in values:
1594 for v in values:
1594 if v == section:
1595 if v == section:
1595 ui.debug('%s: ' %
1596 ui.debug('%s: ' %
1596 ui.configsource(section, name, untrusted))
1597 ui.configsource(section, name, untrusted))
1597 ui.write('%s=%s\n' % (sectname, value))
1598 ui.write('%s=%s\n' % (sectname, value))
1598 matched = True
1599 matched = True
1599 elif v == sectname:
1600 elif v == sectname:
1600 ui.debug('%s: ' %
1601 ui.debug('%s: ' %
1601 ui.configsource(section, name, untrusted))
1602 ui.configsource(section, name, untrusted))
1602 ui.write(value, '\n')
1603 ui.write(value, '\n')
1603 matched = True
1604 matched = True
1604 else:
1605 else:
1605 ui.debug('%s: ' %
1606 ui.debug('%s: ' %
1606 ui.configsource(section, name, untrusted))
1607 ui.configsource(section, name, untrusted))
1607 ui.write('%s=%s\n' % (sectname, value))
1608 ui.write('%s=%s\n' % (sectname, value))
1608 matched = True
1609 matched = True
1609 if matched:
1610 if matched:
1610 return 0
1611 return 0
1611 return 1
1612 return 1
1612
1613
1613 @command('copy|cp',
1614 @command('copy|cp',
1614 [('A', 'after', None, _('record a copy that has already occurred')),
1615 [('A', 'after', None, _('record a copy that has already occurred')),
1615 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1616 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1616 ] + walkopts + dryrunopts,
1617 ] + walkopts + dryrunopts,
1617 _('[OPTION]... [SOURCE]... DEST'))
1618 _('[OPTION]... [SOURCE]... DEST'))
1618 def copy(ui, repo, *pats, **opts):
1619 def copy(ui, repo, *pats, **opts):
1619 """mark files as copied for the next commit
1620 """mark files as copied for the next commit
1620
1621
1621 Mark dest as having copies of source files. If dest is a
1622 Mark dest as having copies of source files. If dest is a
1622 directory, copies are put in that directory. If dest is a file,
1623 directory, copies are put in that directory. If dest is a file,
1623 the source must be a single file.
1624 the source must be a single file.
1624
1625
1625 By default, this command copies the contents of files as they
1626 By default, this command copies the contents of files as they
1626 exist in the working directory. If invoked with -A/--after, the
1627 exist in the working directory. If invoked with -A/--after, the
1627 operation is recorded, but no copying is performed.
1628 operation is recorded, but no copying is performed.
1628
1629
1629 This command takes effect with the next commit. To undo a copy
1630 This command takes effect with the next commit. To undo a copy
1630 before that, see :hg:`revert`.
1631 before that, see :hg:`revert`.
1631
1632
1632 Returns 0 on success, 1 if errors are encountered.
1633 Returns 0 on success, 1 if errors are encountered.
1633 """
1634 """
1634 wlock = repo.wlock(False)
1635 wlock = repo.wlock(False)
1635 try:
1636 try:
1636 return cmdutil.copy(ui, repo, pats, opts)
1637 return cmdutil.copy(ui, repo, pats, opts)
1637 finally:
1638 finally:
1638 wlock.release()
1639 wlock.release()
1639
1640
1640 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
1641 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
1641 def debugancestor(ui, repo, *args):
1642 def debugancestor(ui, repo, *args):
1642 """find the ancestor revision of two revisions in a given index"""
1643 """find the ancestor revision of two revisions in a given index"""
1643 if len(args) == 3:
1644 if len(args) == 3:
1644 index, rev1, rev2 = args
1645 index, rev1, rev2 = args
1645 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1646 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1646 lookup = r.lookup
1647 lookup = r.lookup
1647 elif len(args) == 2:
1648 elif len(args) == 2:
1648 if not repo:
1649 if not repo:
1649 raise util.Abort(_("there is no Mercurial repository here "
1650 raise util.Abort(_("there is no Mercurial repository here "
1650 "(.hg not found)"))
1651 "(.hg not found)"))
1651 rev1, rev2 = args
1652 rev1, rev2 = args
1652 r = repo.changelog
1653 r = repo.changelog
1653 lookup = repo.lookup
1654 lookup = repo.lookup
1654 else:
1655 else:
1655 raise util.Abort(_('either two or three arguments required'))
1656 raise util.Abort(_('either two or three arguments required'))
1656 a = r.ancestor(lookup(rev1), lookup(rev2))
1657 a = r.ancestor(lookup(rev1), lookup(rev2))
1657 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1658 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1658
1659
1659 @command('debugbuilddag',
1660 @command('debugbuilddag',
1660 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1661 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1661 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1662 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1662 ('n', 'new-file', None, _('add new file at each rev'))],
1663 ('n', 'new-file', None, _('add new file at each rev'))],
1663 _('[OPTION]... [TEXT]'))
1664 _('[OPTION]... [TEXT]'))
1664 def debugbuilddag(ui, repo, text=None,
1665 def debugbuilddag(ui, repo, text=None,
1665 mergeable_file=False,
1666 mergeable_file=False,
1666 overwritten_file=False,
1667 overwritten_file=False,
1667 new_file=False):
1668 new_file=False):
1668 """builds a repo with a given DAG from scratch in the current empty repo
1669 """builds a repo with a given DAG from scratch in the current empty repo
1669
1670
1670 The description of the DAG is read from stdin if not given on the
1671 The description of the DAG is read from stdin if not given on the
1671 command line.
1672 command line.
1672
1673
1673 Elements:
1674 Elements:
1674
1675
1675 - "+n" is a linear run of n nodes based on the current default parent
1676 - "+n" is a linear run of n nodes based on the current default parent
1676 - "." is a single node based on the current default parent
1677 - "." is a single node based on the current default parent
1677 - "$" resets the default parent to null (implied at the start);
1678 - "$" resets the default parent to null (implied at the start);
1678 otherwise the default parent is always the last node created
1679 otherwise the default parent is always the last node created
1679 - "<p" sets the default parent to the backref p
1680 - "<p" sets the default parent to the backref p
1680 - "*p" is a fork at parent p, which is a backref
1681 - "*p" is a fork at parent p, which is a backref
1681 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1682 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1682 - "/p2" is a merge of the preceding node and p2
1683 - "/p2" is a merge of the preceding node and p2
1683 - ":tag" defines a local tag for the preceding node
1684 - ":tag" defines a local tag for the preceding node
1684 - "@branch" sets the named branch for subsequent nodes
1685 - "@branch" sets the named branch for subsequent nodes
1685 - "#...\\n" is a comment up to the end of the line
1686 - "#...\\n" is a comment up to the end of the line
1686
1687
1687 Whitespace between the above elements is ignored.
1688 Whitespace between the above elements is ignored.
1688
1689
1689 A backref is either
1690 A backref is either
1690
1691
1691 - a number n, which references the node curr-n, where curr is the current
1692 - a number n, which references the node curr-n, where curr is the current
1692 node, or
1693 node, or
1693 - the name of a local tag you placed earlier using ":tag", or
1694 - the name of a local tag you placed earlier using ":tag", or
1694 - empty to denote the default parent.
1695 - empty to denote the default parent.
1695
1696
1696 All string valued-elements are either strictly alphanumeric, or must
1697 All string valued-elements are either strictly alphanumeric, or must
1697 be enclosed in double quotes ("..."), with "\\" as escape character.
1698 be enclosed in double quotes ("..."), with "\\" as escape character.
1698 """
1699 """
1699
1700
1700 if text is None:
1701 if text is None:
1701 ui.status(_("reading DAG from stdin\n"))
1702 ui.status(_("reading DAG from stdin\n"))
1702 text = ui.fin.read()
1703 text = ui.fin.read()
1703
1704
1704 cl = repo.changelog
1705 cl = repo.changelog
1705 if len(cl) > 0:
1706 if len(cl) > 0:
1706 raise util.Abort(_('repository is not empty'))
1707 raise util.Abort(_('repository is not empty'))
1707
1708
1708 # determine number of revs in DAG
1709 # determine number of revs in DAG
1709 total = 0
1710 total = 0
1710 for type, data in dagparser.parsedag(text):
1711 for type, data in dagparser.parsedag(text):
1711 if type == 'n':
1712 if type == 'n':
1712 total += 1
1713 total += 1
1713
1714
1714 if mergeable_file:
1715 if mergeable_file:
1715 linesperrev = 2
1716 linesperrev = 2
1716 # make a file with k lines per rev
1717 # make a file with k lines per rev
1717 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1718 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1718 initialmergedlines.append("")
1719 initialmergedlines.append("")
1719
1720
1720 tags = []
1721 tags = []
1721
1722
1722 lock = tr = None
1723 lock = tr = None
1723 try:
1724 try:
1724 lock = repo.lock()
1725 lock = repo.lock()
1725 tr = repo.transaction("builddag")
1726 tr = repo.transaction("builddag")
1726
1727
1727 at = -1
1728 at = -1
1728 atbranch = 'default'
1729 atbranch = 'default'
1729 nodeids = []
1730 nodeids = []
1730 id = 0
1731 id = 0
1731 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1732 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1732 for type, data in dagparser.parsedag(text):
1733 for type, data in dagparser.parsedag(text):
1733 if type == 'n':
1734 if type == 'n':
1734 ui.note(('node %s\n' % str(data)))
1735 ui.note(('node %s\n' % str(data)))
1735 id, ps = data
1736 id, ps = data
1736
1737
1737 files = []
1738 files = []
1738 fctxs = {}
1739 fctxs = {}
1739
1740
1740 p2 = None
1741 p2 = None
1741 if mergeable_file:
1742 if mergeable_file:
1742 fn = "mf"
1743 fn = "mf"
1743 p1 = repo[ps[0]]
1744 p1 = repo[ps[0]]
1744 if len(ps) > 1:
1745 if len(ps) > 1:
1745 p2 = repo[ps[1]]
1746 p2 = repo[ps[1]]
1746 pa = p1.ancestor(p2)
1747 pa = p1.ancestor(p2)
1747 base, local, other = [x[fn].data() for x in (pa, p1,
1748 base, local, other = [x[fn].data() for x in (pa, p1,
1748 p2)]
1749 p2)]
1749 m3 = simplemerge.Merge3Text(base, local, other)
1750 m3 = simplemerge.Merge3Text(base, local, other)
1750 ml = [l.strip() for l in m3.merge_lines()]
1751 ml = [l.strip() for l in m3.merge_lines()]
1751 ml.append("")
1752 ml.append("")
1752 elif at > 0:
1753 elif at > 0:
1753 ml = p1[fn].data().split("\n")
1754 ml = p1[fn].data().split("\n")
1754 else:
1755 else:
1755 ml = initialmergedlines
1756 ml = initialmergedlines
1756 ml[id * linesperrev] += " r%i" % id
1757 ml[id * linesperrev] += " r%i" % id
1757 mergedtext = "\n".join(ml)
1758 mergedtext = "\n".join(ml)
1758 files.append(fn)
1759 files.append(fn)
1759 fctxs[fn] = context.memfilectx(repo, fn, mergedtext)
1760 fctxs[fn] = context.memfilectx(repo, fn, mergedtext)
1760
1761
1761 if overwritten_file:
1762 if overwritten_file:
1762 fn = "of"
1763 fn = "of"
1763 files.append(fn)
1764 files.append(fn)
1764 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
1765 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
1765
1766
1766 if new_file:
1767 if new_file:
1767 fn = "nf%i" % id
1768 fn = "nf%i" % id
1768 files.append(fn)
1769 files.append(fn)
1769 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
1770 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
1770 if len(ps) > 1:
1771 if len(ps) > 1:
1771 if not p2:
1772 if not p2:
1772 p2 = repo[ps[1]]
1773 p2 = repo[ps[1]]
1773 for fn in p2:
1774 for fn in p2:
1774 if fn.startswith("nf"):
1775 if fn.startswith("nf"):
1775 files.append(fn)
1776 files.append(fn)
1776 fctxs[fn] = p2[fn]
1777 fctxs[fn] = p2[fn]
1777
1778
1778 def fctxfn(repo, cx, path):
1779 def fctxfn(repo, cx, path):
1779 return fctxs.get(path)
1780 return fctxs.get(path)
1780
1781
1781 if len(ps) == 0 or ps[0] < 0:
1782 if len(ps) == 0 or ps[0] < 0:
1782 pars = [None, None]
1783 pars = [None, None]
1783 elif len(ps) == 1:
1784 elif len(ps) == 1:
1784 pars = [nodeids[ps[0]], None]
1785 pars = [nodeids[ps[0]], None]
1785 else:
1786 else:
1786 pars = [nodeids[p] for p in ps]
1787 pars = [nodeids[p] for p in ps]
1787 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1788 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1788 date=(id, 0),
1789 date=(id, 0),
1789 user="debugbuilddag",
1790 user="debugbuilddag",
1790 extra={'branch': atbranch})
1791 extra={'branch': atbranch})
1791 nodeid = repo.commitctx(cx)
1792 nodeid = repo.commitctx(cx)
1792 nodeids.append(nodeid)
1793 nodeids.append(nodeid)
1793 at = id
1794 at = id
1794 elif type == 'l':
1795 elif type == 'l':
1795 id, name = data
1796 id, name = data
1796 ui.note(('tag %s\n' % name))
1797 ui.note(('tag %s\n' % name))
1797 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1798 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1798 elif type == 'a':
1799 elif type == 'a':
1799 ui.note(('branch %s\n' % data))
1800 ui.note(('branch %s\n' % data))
1800 atbranch = data
1801 atbranch = data
1801 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1802 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1802 tr.close()
1803 tr.close()
1803
1804
1804 if tags:
1805 if tags:
1805 repo.opener.write("localtags", "".join(tags))
1806 repo.opener.write("localtags", "".join(tags))
1806 finally:
1807 finally:
1807 ui.progress(_('building'), None)
1808 ui.progress(_('building'), None)
1808 release(tr, lock)
1809 release(tr, lock)
1809
1810
1810 @command('debugbundle',
1811 @command('debugbundle',
1811 [('a', 'all', None, _('show all details'))],
1812 [('a', 'all', None, _('show all details'))],
1812 _('FILE'),
1813 _('FILE'),
1813 norepo=True)
1814 norepo=True)
1814 def debugbundle(ui, bundlepath, all=None, **opts):
1815 def debugbundle(ui, bundlepath, all=None, **opts):
1815 """lists the contents of a bundle"""
1816 """lists the contents of a bundle"""
1816 f = hg.openpath(ui, bundlepath)
1817 f = hg.openpath(ui, bundlepath)
1817 try:
1818 try:
1818 gen = exchange.readbundle(ui, f, bundlepath)
1819 gen = exchange.readbundle(ui, f, bundlepath)
1819 if all:
1820 if all:
1820 ui.write(("format: id, p1, p2, cset, delta base, len(delta)\n"))
1821 ui.write(("format: id, p1, p2, cset, delta base, len(delta)\n"))
1821
1822
1822 def showchunks(named):
1823 def showchunks(named):
1823 ui.write("\n%s\n" % named)
1824 ui.write("\n%s\n" % named)
1824 chain = None
1825 chain = None
1825 while True:
1826 while True:
1826 chunkdata = gen.deltachunk(chain)
1827 chunkdata = gen.deltachunk(chain)
1827 if not chunkdata:
1828 if not chunkdata:
1828 break
1829 break
1829 node = chunkdata['node']
1830 node = chunkdata['node']
1830 p1 = chunkdata['p1']
1831 p1 = chunkdata['p1']
1831 p2 = chunkdata['p2']
1832 p2 = chunkdata['p2']
1832 cs = chunkdata['cs']
1833 cs = chunkdata['cs']
1833 deltabase = chunkdata['deltabase']
1834 deltabase = chunkdata['deltabase']
1834 delta = chunkdata['delta']
1835 delta = chunkdata['delta']
1835 ui.write("%s %s %s %s %s %s\n" %
1836 ui.write("%s %s %s %s %s %s\n" %
1836 (hex(node), hex(p1), hex(p2),
1837 (hex(node), hex(p1), hex(p2),
1837 hex(cs), hex(deltabase), len(delta)))
1838 hex(cs), hex(deltabase), len(delta)))
1838 chain = node
1839 chain = node
1839
1840
1840 chunkdata = gen.changelogheader()
1841 chunkdata = gen.changelogheader()
1841 showchunks("changelog")
1842 showchunks("changelog")
1842 chunkdata = gen.manifestheader()
1843 chunkdata = gen.manifestheader()
1843 showchunks("manifest")
1844 showchunks("manifest")
1844 while True:
1845 while True:
1845 chunkdata = gen.filelogheader()
1846 chunkdata = gen.filelogheader()
1846 if not chunkdata:
1847 if not chunkdata:
1847 break
1848 break
1848 fname = chunkdata['filename']
1849 fname = chunkdata['filename']
1849 showchunks(fname)
1850 showchunks(fname)
1850 else:
1851 else:
1851 chunkdata = gen.changelogheader()
1852 chunkdata = gen.changelogheader()
1852 chain = None
1853 chain = None
1853 while True:
1854 while True:
1854 chunkdata = gen.deltachunk(chain)
1855 chunkdata = gen.deltachunk(chain)
1855 if not chunkdata:
1856 if not chunkdata:
1856 break
1857 break
1857 node = chunkdata['node']
1858 node = chunkdata['node']
1858 ui.write("%s\n" % hex(node))
1859 ui.write("%s\n" % hex(node))
1859 chain = node
1860 chain = node
1860 finally:
1861 finally:
1861 f.close()
1862 f.close()
1862
1863
1863 @command('debugcheckstate', [], '')
1864 @command('debugcheckstate', [], '')
1864 def debugcheckstate(ui, repo):
1865 def debugcheckstate(ui, repo):
1865 """validate the correctness of the current dirstate"""
1866 """validate the correctness of the current dirstate"""
1866 parent1, parent2 = repo.dirstate.parents()
1867 parent1, parent2 = repo.dirstate.parents()
1867 m1 = repo[parent1].manifest()
1868 m1 = repo[parent1].manifest()
1868 m2 = repo[parent2].manifest()
1869 m2 = repo[parent2].manifest()
1869 errors = 0
1870 errors = 0
1870 for f in repo.dirstate:
1871 for f in repo.dirstate:
1871 state = repo.dirstate[f]
1872 state = repo.dirstate[f]
1872 if state in "nr" and f not in m1:
1873 if state in "nr" and f not in m1:
1873 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1874 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1874 errors += 1
1875 errors += 1
1875 if state in "a" and f in m1:
1876 if state in "a" and f in m1:
1876 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1877 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1877 errors += 1
1878 errors += 1
1878 if state in "m" and f not in m1 and f not in m2:
1879 if state in "m" and f not in m1 and f not in m2:
1879 ui.warn(_("%s in state %s, but not in either manifest\n") %
1880 ui.warn(_("%s in state %s, but not in either manifest\n") %
1880 (f, state))
1881 (f, state))
1881 errors += 1
1882 errors += 1
1882 for f in m1:
1883 for f in m1:
1883 state = repo.dirstate[f]
1884 state = repo.dirstate[f]
1884 if state not in "nrm":
1885 if state not in "nrm":
1885 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1886 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1886 errors += 1
1887 errors += 1
1887 if errors:
1888 if errors:
1888 error = _(".hg/dirstate inconsistent with current parent's manifest")
1889 error = _(".hg/dirstate inconsistent with current parent's manifest")
1889 raise util.Abort(error)
1890 raise util.Abort(error)
1890
1891
1891 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1892 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1892 def debugcommands(ui, cmd='', *args):
1893 def debugcommands(ui, cmd='', *args):
1893 """list all available commands and options"""
1894 """list all available commands and options"""
1894 for cmd, vals in sorted(table.iteritems()):
1895 for cmd, vals in sorted(table.iteritems()):
1895 cmd = cmd.split('|')[0].strip('^')
1896 cmd = cmd.split('|')[0].strip('^')
1896 opts = ', '.join([i[1] for i in vals[1]])
1897 opts = ', '.join([i[1] for i in vals[1]])
1897 ui.write('%s: %s\n' % (cmd, opts))
1898 ui.write('%s: %s\n' % (cmd, opts))
1898
1899
1899 @command('debugcomplete',
1900 @command('debugcomplete',
1900 [('o', 'options', None, _('show the command options'))],
1901 [('o', 'options', None, _('show the command options'))],
1901 _('[-o] CMD'),
1902 _('[-o] CMD'),
1902 norepo=True)
1903 norepo=True)
1903 def debugcomplete(ui, cmd='', **opts):
1904 def debugcomplete(ui, cmd='', **opts):
1904 """returns the completion list associated with the given command"""
1905 """returns the completion list associated with the given command"""
1905
1906
1906 if opts.get('options'):
1907 if opts.get('options'):
1907 options = []
1908 options = []
1908 otables = [globalopts]
1909 otables = [globalopts]
1909 if cmd:
1910 if cmd:
1910 aliases, entry = cmdutil.findcmd(cmd, table, False)
1911 aliases, entry = cmdutil.findcmd(cmd, table, False)
1911 otables.append(entry[1])
1912 otables.append(entry[1])
1912 for t in otables:
1913 for t in otables:
1913 for o in t:
1914 for o in t:
1914 if "(DEPRECATED)" in o[3]:
1915 if "(DEPRECATED)" in o[3]:
1915 continue
1916 continue
1916 if o[0]:
1917 if o[0]:
1917 options.append('-%s' % o[0])
1918 options.append('-%s' % o[0])
1918 options.append('--%s' % o[1])
1919 options.append('--%s' % o[1])
1919 ui.write("%s\n" % "\n".join(options))
1920 ui.write("%s\n" % "\n".join(options))
1920 return
1921 return
1921
1922
1922 cmdlist = cmdutil.findpossible(cmd, table)
1923 cmdlist = cmdutil.findpossible(cmd, table)
1923 if ui.verbose:
1924 if ui.verbose:
1924 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1925 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1925 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1926 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1926
1927
1927 @command('debugdag',
1928 @command('debugdag',
1928 [('t', 'tags', None, _('use tags as labels')),
1929 [('t', 'tags', None, _('use tags as labels')),
1929 ('b', 'branches', None, _('annotate with branch names')),
1930 ('b', 'branches', None, _('annotate with branch names')),
1930 ('', 'dots', None, _('use dots for runs')),
1931 ('', 'dots', None, _('use dots for runs')),
1931 ('s', 'spaces', None, _('separate elements by spaces'))],
1932 ('s', 'spaces', None, _('separate elements by spaces'))],
1932 _('[OPTION]... [FILE [REV]...]'),
1933 _('[OPTION]... [FILE [REV]...]'),
1933 optionalrepo=True)
1934 optionalrepo=True)
1934 def debugdag(ui, repo, file_=None, *revs, **opts):
1935 def debugdag(ui, repo, file_=None, *revs, **opts):
1935 """format the changelog or an index DAG as a concise textual description
1936 """format the changelog or an index DAG as a concise textual description
1936
1937
1937 If you pass a revlog index, the revlog's DAG is emitted. If you list
1938 If you pass a revlog index, the revlog's DAG is emitted. If you list
1938 revision numbers, they get labeled in the output as rN.
1939 revision numbers, they get labeled in the output as rN.
1939
1940
1940 Otherwise, the changelog DAG of the current repo is emitted.
1941 Otherwise, the changelog DAG of the current repo is emitted.
1941 """
1942 """
1942 spaces = opts.get('spaces')
1943 spaces = opts.get('spaces')
1943 dots = opts.get('dots')
1944 dots = opts.get('dots')
1944 if file_:
1945 if file_:
1945 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1946 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1946 revs = set((int(r) for r in revs))
1947 revs = set((int(r) for r in revs))
1947 def events():
1948 def events():
1948 for r in rlog:
1949 for r in rlog:
1949 yield 'n', (r, list(p for p in rlog.parentrevs(r)
1950 yield 'n', (r, list(p for p in rlog.parentrevs(r)
1950 if p != -1))
1951 if p != -1))
1951 if r in revs:
1952 if r in revs:
1952 yield 'l', (r, "r%i" % r)
1953 yield 'l', (r, "r%i" % r)
1953 elif repo:
1954 elif repo:
1954 cl = repo.changelog
1955 cl = repo.changelog
1955 tags = opts.get('tags')
1956 tags = opts.get('tags')
1956 branches = opts.get('branches')
1957 branches = opts.get('branches')
1957 if tags:
1958 if tags:
1958 labels = {}
1959 labels = {}
1959 for l, n in repo.tags().items():
1960 for l, n in repo.tags().items():
1960 labels.setdefault(cl.rev(n), []).append(l)
1961 labels.setdefault(cl.rev(n), []).append(l)
1961 def events():
1962 def events():
1962 b = "default"
1963 b = "default"
1963 for r in cl:
1964 for r in cl:
1964 if branches:
1965 if branches:
1965 newb = cl.read(cl.node(r))[5]['branch']
1966 newb = cl.read(cl.node(r))[5]['branch']
1966 if newb != b:
1967 if newb != b:
1967 yield 'a', newb
1968 yield 'a', newb
1968 b = newb
1969 b = newb
1969 yield 'n', (r, list(p for p in cl.parentrevs(r)
1970 yield 'n', (r, list(p for p in cl.parentrevs(r)
1970 if p != -1))
1971 if p != -1))
1971 if tags:
1972 if tags:
1972 ls = labels.get(r)
1973 ls = labels.get(r)
1973 if ls:
1974 if ls:
1974 for l in ls:
1975 for l in ls:
1975 yield 'l', (r, l)
1976 yield 'l', (r, l)
1976 else:
1977 else:
1977 raise util.Abort(_('need repo for changelog dag'))
1978 raise util.Abort(_('need repo for changelog dag'))
1978
1979
1979 for line in dagparser.dagtextlines(events(),
1980 for line in dagparser.dagtextlines(events(),
1980 addspaces=spaces,
1981 addspaces=spaces,
1981 wraplabels=True,
1982 wraplabels=True,
1982 wrapannotations=True,
1983 wrapannotations=True,
1983 wrapnonlinear=dots,
1984 wrapnonlinear=dots,
1984 usedots=dots,
1985 usedots=dots,
1985 maxlinewidth=70):
1986 maxlinewidth=70):
1986 ui.write(line)
1987 ui.write(line)
1987 ui.write("\n")
1988 ui.write("\n")
1988
1989
1989 @command('debugdata',
1990 @command('debugdata',
1990 [('c', 'changelog', False, _('open changelog')),
1991 [('c', 'changelog', False, _('open changelog')),
1991 ('m', 'manifest', False, _('open manifest'))],
1992 ('m', 'manifest', False, _('open manifest'))],
1992 _('-c|-m|FILE REV'))
1993 _('-c|-m|FILE REV'))
1993 def debugdata(ui, repo, file_, rev=None, **opts):
1994 def debugdata(ui, repo, file_, rev=None, **opts):
1994 """dump the contents of a data file revision"""
1995 """dump the contents of a data file revision"""
1995 if opts.get('changelog') or opts.get('manifest'):
1996 if opts.get('changelog') or opts.get('manifest'):
1996 file_, rev = None, file_
1997 file_, rev = None, file_
1997 elif rev is None:
1998 elif rev is None:
1998 raise error.CommandError('debugdata', _('invalid arguments'))
1999 raise error.CommandError('debugdata', _('invalid arguments'))
1999 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
2000 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
2000 try:
2001 try:
2001 ui.write(r.revision(r.lookup(rev)))
2002 ui.write(r.revision(r.lookup(rev)))
2002 except KeyError:
2003 except KeyError:
2003 raise util.Abort(_('invalid revision identifier %s') % rev)
2004 raise util.Abort(_('invalid revision identifier %s') % rev)
2004
2005
2005 @command('debugdate',
2006 @command('debugdate',
2006 [('e', 'extended', None, _('try extended date formats'))],
2007 [('e', 'extended', None, _('try extended date formats'))],
2007 _('[-e] DATE [RANGE]'),
2008 _('[-e] DATE [RANGE]'),
2008 norepo=True, optionalrepo=True)
2009 norepo=True, optionalrepo=True)
2009 def debugdate(ui, date, range=None, **opts):
2010 def debugdate(ui, date, range=None, **opts):
2010 """parse and display a date"""
2011 """parse and display a date"""
2011 if opts["extended"]:
2012 if opts["extended"]:
2012 d = util.parsedate(date, util.extendeddateformats)
2013 d = util.parsedate(date, util.extendeddateformats)
2013 else:
2014 else:
2014 d = util.parsedate(date)
2015 d = util.parsedate(date)
2015 ui.write(("internal: %s %s\n") % d)
2016 ui.write(("internal: %s %s\n") % d)
2016 ui.write(("standard: %s\n") % util.datestr(d))
2017 ui.write(("standard: %s\n") % util.datestr(d))
2017 if range:
2018 if range:
2018 m = util.matchdate(range)
2019 m = util.matchdate(range)
2019 ui.write(("match: %s\n") % m(d[0]))
2020 ui.write(("match: %s\n") % m(d[0]))
2020
2021
2021 @command('debugdiscovery',
2022 @command('debugdiscovery',
2022 [('', 'old', None, _('use old-style discovery')),
2023 [('', 'old', None, _('use old-style discovery')),
2023 ('', 'nonheads', None,
2024 ('', 'nonheads', None,
2024 _('use old-style discovery with non-heads included')),
2025 _('use old-style discovery with non-heads included')),
2025 ] + remoteopts,
2026 ] + remoteopts,
2026 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
2027 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
2027 def debugdiscovery(ui, repo, remoteurl="default", **opts):
2028 def debugdiscovery(ui, repo, remoteurl="default", **opts):
2028 """runs the changeset discovery protocol in isolation"""
2029 """runs the changeset discovery protocol in isolation"""
2029 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
2030 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
2030 opts.get('branch'))
2031 opts.get('branch'))
2031 remote = hg.peer(repo, opts, remoteurl)
2032 remote = hg.peer(repo, opts, remoteurl)
2032 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
2033 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
2033
2034
2034 # make sure tests are repeatable
2035 # make sure tests are repeatable
2035 random.seed(12323)
2036 random.seed(12323)
2036
2037
2037 def doit(localheads, remoteheads, remote=remote):
2038 def doit(localheads, remoteheads, remote=remote):
2038 if opts.get('old'):
2039 if opts.get('old'):
2039 if localheads:
2040 if localheads:
2040 raise util.Abort('cannot use localheads with old style '
2041 raise util.Abort('cannot use localheads with old style '
2041 'discovery')
2042 'discovery')
2042 if not util.safehasattr(remote, 'branches'):
2043 if not util.safehasattr(remote, 'branches'):
2043 # enable in-client legacy support
2044 # enable in-client legacy support
2044 remote = localrepo.locallegacypeer(remote.local())
2045 remote = localrepo.locallegacypeer(remote.local())
2045 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
2046 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
2046 force=True)
2047 force=True)
2047 common = set(common)
2048 common = set(common)
2048 if not opts.get('nonheads'):
2049 if not opts.get('nonheads'):
2049 ui.write(("unpruned common: %s\n") %
2050 ui.write(("unpruned common: %s\n") %
2050 " ".join(sorted(short(n) for n in common)))
2051 " ".join(sorted(short(n) for n in common)))
2051 dag = dagutil.revlogdag(repo.changelog)
2052 dag = dagutil.revlogdag(repo.changelog)
2052 all = dag.ancestorset(dag.internalizeall(common))
2053 all = dag.ancestorset(dag.internalizeall(common))
2053 common = dag.externalizeall(dag.headsetofconnecteds(all))
2054 common = dag.externalizeall(dag.headsetofconnecteds(all))
2054 else:
2055 else:
2055 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
2056 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
2056 common = set(common)
2057 common = set(common)
2057 rheads = set(hds)
2058 rheads = set(hds)
2058 lheads = set(repo.heads())
2059 lheads = set(repo.heads())
2059 ui.write(("common heads: %s\n") %
2060 ui.write(("common heads: %s\n") %
2060 " ".join(sorted(short(n) for n in common)))
2061 " ".join(sorted(short(n) for n in common)))
2061 if lheads <= common:
2062 if lheads <= common:
2062 ui.write(("local is subset\n"))
2063 ui.write(("local is subset\n"))
2063 elif rheads <= common:
2064 elif rheads <= common:
2064 ui.write(("remote is subset\n"))
2065 ui.write(("remote is subset\n"))
2065
2066
2066 serverlogs = opts.get('serverlog')
2067 serverlogs = opts.get('serverlog')
2067 if serverlogs:
2068 if serverlogs:
2068 for filename in serverlogs:
2069 for filename in serverlogs:
2069 logfile = open(filename, 'r')
2070 logfile = open(filename, 'r')
2070 try:
2071 try:
2071 line = logfile.readline()
2072 line = logfile.readline()
2072 while line:
2073 while line:
2073 parts = line.strip().split(';')
2074 parts = line.strip().split(';')
2074 op = parts[1]
2075 op = parts[1]
2075 if op == 'cg':
2076 if op == 'cg':
2076 pass
2077 pass
2077 elif op == 'cgss':
2078 elif op == 'cgss':
2078 doit(parts[2].split(' '), parts[3].split(' '))
2079 doit(parts[2].split(' '), parts[3].split(' '))
2079 elif op == 'unb':
2080 elif op == 'unb':
2080 doit(parts[3].split(' '), parts[2].split(' '))
2081 doit(parts[3].split(' '), parts[2].split(' '))
2081 line = logfile.readline()
2082 line = logfile.readline()
2082 finally:
2083 finally:
2083 logfile.close()
2084 logfile.close()
2084
2085
2085 else:
2086 else:
2086 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
2087 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
2087 opts.get('remote_head'))
2088 opts.get('remote_head'))
2088 localrevs = opts.get('local_head')
2089 localrevs = opts.get('local_head')
2089 doit(localrevs, remoterevs)
2090 doit(localrevs, remoterevs)
2090
2091
2091 @command('debugfileset',
2092 @command('debugfileset',
2092 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
2093 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
2093 _('[-r REV] FILESPEC'))
2094 _('[-r REV] FILESPEC'))
2094 def debugfileset(ui, repo, expr, **opts):
2095 def debugfileset(ui, repo, expr, **opts):
2095 '''parse and apply a fileset specification'''
2096 '''parse and apply a fileset specification'''
2096 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
2097 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
2097 if ui.verbose:
2098 if ui.verbose:
2098 tree = fileset.parse(expr)[0]
2099 tree = fileset.parse(expr)[0]
2099 ui.note(tree, "\n")
2100 ui.note(tree, "\n")
2100
2101
2101 for f in ctx.getfileset(expr):
2102 for f in ctx.getfileset(expr):
2102 ui.write("%s\n" % f)
2103 ui.write("%s\n" % f)
2103
2104
2104 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
2105 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
2105 def debugfsinfo(ui, path="."):
2106 def debugfsinfo(ui, path="."):
2106 """show information detected about current filesystem"""
2107 """show information detected about current filesystem"""
2107 util.writefile('.debugfsinfo', '')
2108 util.writefile('.debugfsinfo', '')
2108 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
2109 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
2109 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
2110 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
2110 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
2111 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
2111 ui.write(('case-sensitive: %s\n') % (util.checkcase('.debugfsinfo')
2112 ui.write(('case-sensitive: %s\n') % (util.checkcase('.debugfsinfo')
2112 and 'yes' or 'no'))
2113 and 'yes' or 'no'))
2113 os.unlink('.debugfsinfo')
2114 os.unlink('.debugfsinfo')
2114
2115
2115 @command('debuggetbundle',
2116 @command('debuggetbundle',
2116 [('H', 'head', [], _('id of head node'), _('ID')),
2117 [('H', 'head', [], _('id of head node'), _('ID')),
2117 ('C', 'common', [], _('id of common node'), _('ID')),
2118 ('C', 'common', [], _('id of common node'), _('ID')),
2118 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
2119 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
2119 _('REPO FILE [-H|-C ID]...'),
2120 _('REPO FILE [-H|-C ID]...'),
2120 norepo=True)
2121 norepo=True)
2121 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
2122 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
2122 """retrieves a bundle from a repo
2123 """retrieves a bundle from a repo
2123
2124
2124 Every ID must be a full-length hex node id string. Saves the bundle to the
2125 Every ID must be a full-length hex node id string. Saves the bundle to the
2125 given file.
2126 given file.
2126 """
2127 """
2127 repo = hg.peer(ui, opts, repopath)
2128 repo = hg.peer(ui, opts, repopath)
2128 if not repo.capable('getbundle'):
2129 if not repo.capable('getbundle'):
2129 raise util.Abort("getbundle() not supported by target repository")
2130 raise util.Abort("getbundle() not supported by target repository")
2130 args = {}
2131 args = {}
2131 if common:
2132 if common:
2132 args['common'] = [bin(s) for s in common]
2133 args['common'] = [bin(s) for s in common]
2133 if head:
2134 if head:
2134 args['heads'] = [bin(s) for s in head]
2135 args['heads'] = [bin(s) for s in head]
2135 # TODO: get desired bundlecaps from command line.
2136 # TODO: get desired bundlecaps from command line.
2136 args['bundlecaps'] = None
2137 args['bundlecaps'] = None
2137 bundle = repo.getbundle('debug', **args)
2138 bundle = repo.getbundle('debug', **args)
2138
2139
2139 bundletype = opts.get('type', 'bzip2').lower()
2140 bundletype = opts.get('type', 'bzip2').lower()
2140 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
2141 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
2141 bundletype = btypes.get(bundletype)
2142 bundletype = btypes.get(bundletype)
2142 if bundletype not in changegroup.bundletypes:
2143 if bundletype not in changegroup.bundletypes:
2143 raise util.Abort(_('unknown bundle type specified with --type'))
2144 raise util.Abort(_('unknown bundle type specified with --type'))
2144 changegroup.writebundle(bundle, bundlepath, bundletype)
2145 changegroup.writebundle(bundle, bundlepath, bundletype)
2145
2146
2146 @command('debugignore', [], '')
2147 @command('debugignore', [], '')
2147 def debugignore(ui, repo, *values, **opts):
2148 def debugignore(ui, repo, *values, **opts):
2148 """display the combined ignore pattern"""
2149 """display the combined ignore pattern"""
2149 ignore = repo.dirstate._ignore
2150 ignore = repo.dirstate._ignore
2150 includepat = getattr(ignore, 'includepat', None)
2151 includepat = getattr(ignore, 'includepat', None)
2151 if includepat is not None:
2152 if includepat is not None:
2152 ui.write("%s\n" % includepat)
2153 ui.write("%s\n" % includepat)
2153 else:
2154 else:
2154 raise util.Abort(_("no ignore patterns found"))
2155 raise util.Abort(_("no ignore patterns found"))
2155
2156
2156 @command('debugindex',
2157 @command('debugindex',
2157 [('c', 'changelog', False, _('open changelog')),
2158 [('c', 'changelog', False, _('open changelog')),
2158 ('m', 'manifest', False, _('open manifest')),
2159 ('m', 'manifest', False, _('open manifest')),
2159 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
2160 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
2160 _('[-f FORMAT] -c|-m|FILE'),
2161 _('[-f FORMAT] -c|-m|FILE'),
2161 optionalrepo=True)
2162 optionalrepo=True)
2162 def debugindex(ui, repo, file_=None, **opts):
2163 def debugindex(ui, repo, file_=None, **opts):
2163 """dump the contents of an index file"""
2164 """dump the contents of an index file"""
2164 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
2165 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
2165 format = opts.get('format', 0)
2166 format = opts.get('format', 0)
2166 if format not in (0, 1):
2167 if format not in (0, 1):
2167 raise util.Abort(_("unknown format %d") % format)
2168 raise util.Abort(_("unknown format %d") % format)
2168
2169
2169 generaldelta = r.version & revlog.REVLOGGENERALDELTA
2170 generaldelta = r.version & revlog.REVLOGGENERALDELTA
2170 if generaldelta:
2171 if generaldelta:
2171 basehdr = ' delta'
2172 basehdr = ' delta'
2172 else:
2173 else:
2173 basehdr = ' base'
2174 basehdr = ' base'
2174
2175
2175 if format == 0:
2176 if format == 0:
2176 ui.write(" rev offset length " + basehdr + " linkrev"
2177 ui.write(" rev offset length " + basehdr + " linkrev"
2177 " nodeid p1 p2\n")
2178 " nodeid p1 p2\n")
2178 elif format == 1:
2179 elif format == 1:
2179 ui.write(" rev flag offset length"
2180 ui.write(" rev flag offset length"
2180 " size " + basehdr + " link p1 p2"
2181 " size " + basehdr + " link p1 p2"
2181 " nodeid\n")
2182 " nodeid\n")
2182
2183
2183 for i in r:
2184 for i in r:
2184 node = r.node(i)
2185 node = r.node(i)
2185 if generaldelta:
2186 if generaldelta:
2186 base = r.deltaparent(i)
2187 base = r.deltaparent(i)
2187 else:
2188 else:
2188 base = r.chainbase(i)
2189 base = r.chainbase(i)
2189 if format == 0:
2190 if format == 0:
2190 try:
2191 try:
2191 pp = r.parents(node)
2192 pp = r.parents(node)
2192 except Exception:
2193 except Exception:
2193 pp = [nullid, nullid]
2194 pp = [nullid, nullid]
2194 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
2195 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
2195 i, r.start(i), r.length(i), base, r.linkrev(i),
2196 i, r.start(i), r.length(i), base, r.linkrev(i),
2196 short(node), short(pp[0]), short(pp[1])))
2197 short(node), short(pp[0]), short(pp[1])))
2197 elif format == 1:
2198 elif format == 1:
2198 pr = r.parentrevs(i)
2199 pr = r.parentrevs(i)
2199 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
2200 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
2200 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
2201 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
2201 base, r.linkrev(i), pr[0], pr[1], short(node)))
2202 base, r.linkrev(i), pr[0], pr[1], short(node)))
2202
2203
2203 @command('debugindexdot', [], _('FILE'), optionalrepo=True)
2204 @command('debugindexdot', [], _('FILE'), optionalrepo=True)
2204 def debugindexdot(ui, repo, file_):
2205 def debugindexdot(ui, repo, file_):
2205 """dump an index DAG as a graphviz dot file"""
2206 """dump an index DAG as a graphviz dot file"""
2206 r = None
2207 r = None
2207 if repo:
2208 if repo:
2208 filelog = repo.file(file_)
2209 filelog = repo.file(file_)
2209 if len(filelog):
2210 if len(filelog):
2210 r = filelog
2211 r = filelog
2211 if not r:
2212 if not r:
2212 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
2213 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
2213 ui.write(("digraph G {\n"))
2214 ui.write(("digraph G {\n"))
2214 for i in r:
2215 for i in r:
2215 node = r.node(i)
2216 node = r.node(i)
2216 pp = r.parents(node)
2217 pp = r.parents(node)
2217 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
2218 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
2218 if pp[1] != nullid:
2219 if pp[1] != nullid:
2219 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
2220 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
2220 ui.write("}\n")
2221 ui.write("}\n")
2221
2222
2222 @command('debuginstall', [], '', norepo=True)
2223 @command('debuginstall', [], '', norepo=True)
2223 def debuginstall(ui):
2224 def debuginstall(ui):
2224 '''test Mercurial installation
2225 '''test Mercurial installation
2225
2226
2226 Returns 0 on success.
2227 Returns 0 on success.
2227 '''
2228 '''
2228
2229
2229 def writetemp(contents):
2230 def writetemp(contents):
2230 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
2231 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
2231 f = os.fdopen(fd, "wb")
2232 f = os.fdopen(fd, "wb")
2232 f.write(contents)
2233 f.write(contents)
2233 f.close()
2234 f.close()
2234 return name
2235 return name
2235
2236
2236 problems = 0
2237 problems = 0
2237
2238
2238 # encoding
2239 # encoding
2239 ui.status(_("checking encoding (%s)...\n") % encoding.encoding)
2240 ui.status(_("checking encoding (%s)...\n") % encoding.encoding)
2240 try:
2241 try:
2241 encoding.fromlocal("test")
2242 encoding.fromlocal("test")
2242 except util.Abort, inst:
2243 except util.Abort, inst:
2243 ui.write(" %s\n" % inst)
2244 ui.write(" %s\n" % inst)
2244 ui.write(_(" (check that your locale is properly set)\n"))
2245 ui.write(_(" (check that your locale is properly set)\n"))
2245 problems += 1
2246 problems += 1
2246
2247
2247 # Python
2248 # Python
2248 ui.status(_("checking Python executable (%s)\n") % sys.executable)
2249 ui.status(_("checking Python executable (%s)\n") % sys.executable)
2249 ui.status(_("checking Python version (%s)\n")
2250 ui.status(_("checking Python version (%s)\n")
2250 % ("%s.%s.%s" % sys.version_info[:3]))
2251 % ("%s.%s.%s" % sys.version_info[:3]))
2251 ui.status(_("checking Python lib (%s)...\n")
2252 ui.status(_("checking Python lib (%s)...\n")
2252 % os.path.dirname(os.__file__))
2253 % os.path.dirname(os.__file__))
2253
2254
2254 # compiled modules
2255 # compiled modules
2255 ui.status(_("checking installed modules (%s)...\n")
2256 ui.status(_("checking installed modules (%s)...\n")
2256 % os.path.dirname(__file__))
2257 % os.path.dirname(__file__))
2257 try:
2258 try:
2258 import bdiff, mpatch, base85, osutil
2259 import bdiff, mpatch, base85, osutil
2259 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
2260 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
2260 except Exception, inst:
2261 except Exception, inst:
2261 ui.write(" %s\n" % inst)
2262 ui.write(" %s\n" % inst)
2262 ui.write(_(" One or more extensions could not be found"))
2263 ui.write(_(" One or more extensions could not be found"))
2263 ui.write(_(" (check that you compiled the extensions)\n"))
2264 ui.write(_(" (check that you compiled the extensions)\n"))
2264 problems += 1
2265 problems += 1
2265
2266
2266 # templates
2267 # templates
2267 import templater
2268 import templater
2268 p = templater.templatepaths()
2269 p = templater.templatepaths()
2269 ui.status(_("checking templates (%s)...\n") % ' '.join(p))
2270 ui.status(_("checking templates (%s)...\n") % ' '.join(p))
2270 if p:
2271 if p:
2271 m = templater.templatepath("map-cmdline.default")
2272 m = templater.templatepath("map-cmdline.default")
2272 if m:
2273 if m:
2273 # template found, check if it is working
2274 # template found, check if it is working
2274 try:
2275 try:
2275 templater.templater(m)
2276 templater.templater(m)
2276 except Exception, inst:
2277 except Exception, inst:
2277 ui.write(" %s\n" % inst)
2278 ui.write(" %s\n" % inst)
2278 p = None
2279 p = None
2279 else:
2280 else:
2280 ui.write(_(" template 'default' not found\n"))
2281 ui.write(_(" template 'default' not found\n"))
2281 p = None
2282 p = None
2282 else:
2283 else:
2283 ui.write(_(" no template directories found\n"))
2284 ui.write(_(" no template directories found\n"))
2284 if not p:
2285 if not p:
2285 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
2286 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
2286 problems += 1
2287 problems += 1
2287
2288
2288 # editor
2289 # editor
2289 ui.status(_("checking commit editor...\n"))
2290 ui.status(_("checking commit editor...\n"))
2290 editor = ui.geteditor()
2291 editor = ui.geteditor()
2291 cmdpath = util.findexe(shlex.split(editor)[0])
2292 cmdpath = util.findexe(shlex.split(editor)[0])
2292 if not cmdpath:
2293 if not cmdpath:
2293 if editor == 'vi':
2294 if editor == 'vi':
2294 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
2295 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
2295 ui.write(_(" (specify a commit editor in your configuration"
2296 ui.write(_(" (specify a commit editor in your configuration"
2296 " file)\n"))
2297 " file)\n"))
2297 else:
2298 else:
2298 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
2299 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
2299 ui.write(_(" (specify a commit editor in your configuration"
2300 ui.write(_(" (specify a commit editor in your configuration"
2300 " file)\n"))
2301 " file)\n"))
2301 problems += 1
2302 problems += 1
2302
2303
2303 # check username
2304 # check username
2304 ui.status(_("checking username...\n"))
2305 ui.status(_("checking username...\n"))
2305 try:
2306 try:
2306 ui.username()
2307 ui.username()
2307 except util.Abort, e:
2308 except util.Abort, e:
2308 ui.write(" %s\n" % e)
2309 ui.write(" %s\n" % e)
2309 ui.write(_(" (specify a username in your configuration file)\n"))
2310 ui.write(_(" (specify a username in your configuration file)\n"))
2310 problems += 1
2311 problems += 1
2311
2312
2312 if not problems:
2313 if not problems:
2313 ui.status(_("no problems detected\n"))
2314 ui.status(_("no problems detected\n"))
2314 else:
2315 else:
2315 ui.write(_("%s problems detected,"
2316 ui.write(_("%s problems detected,"
2316 " please check your install!\n") % problems)
2317 " please check your install!\n") % problems)
2317
2318
2318 return problems
2319 return problems
2319
2320
2320 @command('debugknown', [], _('REPO ID...'), norepo=True)
2321 @command('debugknown', [], _('REPO ID...'), norepo=True)
2321 def debugknown(ui, repopath, *ids, **opts):
2322 def debugknown(ui, repopath, *ids, **opts):
2322 """test whether node ids are known to a repo
2323 """test whether node ids are known to a repo
2323
2324
2324 Every ID must be a full-length hex node id string. Returns a list of 0s
2325 Every ID must be a full-length hex node id string. Returns a list of 0s
2325 and 1s indicating unknown/known.
2326 and 1s indicating unknown/known.
2326 """
2327 """
2327 repo = hg.peer(ui, opts, repopath)
2328 repo = hg.peer(ui, opts, repopath)
2328 if not repo.capable('known'):
2329 if not repo.capable('known'):
2329 raise util.Abort("known() not supported by target repository")
2330 raise util.Abort("known() not supported by target repository")
2330 flags = repo.known([bin(s) for s in ids])
2331 flags = repo.known([bin(s) for s in ids])
2331 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2332 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2332
2333
2333 @command('debuglabelcomplete', [], _('LABEL...'))
2334 @command('debuglabelcomplete', [], _('LABEL...'))
2334 def debuglabelcomplete(ui, repo, *args):
2335 def debuglabelcomplete(ui, repo, *args):
2335 '''complete "labels" - tags, open branch names, bookmark names'''
2336 '''complete "labels" - tags, open branch names, bookmark names'''
2336
2337
2337 labels = set()
2338 labels = set()
2338 labels.update(t[0] for t in repo.tagslist())
2339 labels.update(t[0] for t in repo.tagslist())
2339 labels.update(repo._bookmarks.keys())
2340 labels.update(repo._bookmarks.keys())
2340 labels.update(tag for (tag, heads, tip, closed)
2341 labels.update(tag for (tag, heads, tip, closed)
2341 in repo.branchmap().iterbranches() if not closed)
2342 in repo.branchmap().iterbranches() if not closed)
2342 completions = set()
2343 completions = set()
2343 if not args:
2344 if not args:
2344 args = ['']
2345 args = ['']
2345 for a in args:
2346 for a in args:
2346 completions.update(l for l in labels if l.startswith(a))
2347 completions.update(l for l in labels if l.startswith(a))
2347 ui.write('\n'.join(sorted(completions)))
2348 ui.write('\n'.join(sorted(completions)))
2348 ui.write('\n')
2349 ui.write('\n')
2349
2350
2350 @command('debuglocks',
2351 @command('debuglocks',
2351 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
2352 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
2352 ('W', 'force-wlock', None,
2353 ('W', 'force-wlock', None,
2353 _('free the working state lock (DANGEROUS)'))],
2354 _('free the working state lock (DANGEROUS)'))],
2354 _(''))
2355 _(''))
2355 def debuglocks(ui, repo, **opts):
2356 def debuglocks(ui, repo, **opts):
2356 """show or modify state of locks
2357 """show or modify state of locks
2357
2358
2358 By default, this command will show which locks are held. This
2359 By default, this command will show which locks are held. This
2359 includes the user and process holding the lock, the amount of time
2360 includes the user and process holding the lock, the amount of time
2360 the lock has been held, and the machine name where the process is
2361 the lock has been held, and the machine name where the process is
2361 running if it's not local.
2362 running if it's not local.
2362
2363
2363 Locks protect the integrity of Mercurial's data, so should be
2364 Locks protect the integrity of Mercurial's data, so should be
2364 treated with care. System crashes or other interruptions may cause
2365 treated with care. System crashes or other interruptions may cause
2365 locks to not be properly released, though Mercurial will usually
2366 locks to not be properly released, though Mercurial will usually
2366 detect and remove such stale locks automatically.
2367 detect and remove such stale locks automatically.
2367
2368
2368 However, detecting stale locks may not always be possible (for
2369 However, detecting stale locks may not always be possible (for
2369 instance, on a shared filesystem). Removing locks may also be
2370 instance, on a shared filesystem). Removing locks may also be
2370 blocked by filesystem permissions.
2371 blocked by filesystem permissions.
2371
2372
2372 Returns 0 if no locks are held.
2373 Returns 0 if no locks are held.
2373
2374
2374 """
2375 """
2375
2376
2376 if opts.get('force_lock'):
2377 if opts.get('force_lock'):
2377 repo.svfs.unlink('lock')
2378 repo.svfs.unlink('lock')
2378 if opts.get('force_wlock'):
2379 if opts.get('force_wlock'):
2379 repo.vfs.unlink('wlock')
2380 repo.vfs.unlink('wlock')
2380 if opts.get('force_lock') or opts.get('force_lock'):
2381 if opts.get('force_lock') or opts.get('force_lock'):
2381 return 0
2382 return 0
2382
2383
2383 now = time.time()
2384 now = time.time()
2384 held = 0
2385 held = 0
2385
2386
2386 def report(vfs, name, method):
2387 def report(vfs, name, method):
2387 # this causes stale locks to get reaped for more accurate reporting
2388 # this causes stale locks to get reaped for more accurate reporting
2388 try:
2389 try:
2389 l = method(False)
2390 l = method(False)
2390 except error.LockHeld:
2391 except error.LockHeld:
2391 l = None
2392 l = None
2392
2393
2393 if l:
2394 if l:
2394 l.release()
2395 l.release()
2395 else:
2396 else:
2396 try:
2397 try:
2397 stat = repo.svfs.lstat(name)
2398 stat = repo.svfs.lstat(name)
2398 age = now - stat.st_mtime
2399 age = now - stat.st_mtime
2399 user = util.username(stat.st_uid)
2400 user = util.username(stat.st_uid)
2400 locker = vfs.readlock(name)
2401 locker = vfs.readlock(name)
2401 if ":" in locker:
2402 if ":" in locker:
2402 host, pid = locker.split(':')
2403 host, pid = locker.split(':')
2403 if host == socket.gethostname():
2404 if host == socket.gethostname():
2404 locker = 'user %s, process %s' % (user, pid)
2405 locker = 'user %s, process %s' % (user, pid)
2405 else:
2406 else:
2406 locker = 'user %s, process %s, host %s' \
2407 locker = 'user %s, process %s, host %s' \
2407 % (user, pid, host)
2408 % (user, pid, host)
2408 ui.write("%-6s %s (%ds)\n" % (name + ":", locker, age))
2409 ui.write("%-6s %s (%ds)\n" % (name + ":", locker, age))
2409 return 1
2410 return 1
2410 except OSError, e:
2411 except OSError, e:
2411 if e.errno != errno.ENOENT:
2412 if e.errno != errno.ENOENT:
2412 raise
2413 raise
2413
2414
2414 ui.write("%-6s free\n" % (name + ":"))
2415 ui.write("%-6s free\n" % (name + ":"))
2415 return 0
2416 return 0
2416
2417
2417 held += report(repo.svfs, "lock", repo.lock)
2418 held += report(repo.svfs, "lock", repo.lock)
2418 held += report(repo.vfs, "wlock", repo.wlock)
2419 held += report(repo.vfs, "wlock", repo.wlock)
2419
2420
2420 return held
2421 return held
2421
2422
2422 @command('debugobsolete',
2423 @command('debugobsolete',
2423 [('', 'flags', 0, _('markers flag')),
2424 [('', 'flags', 0, _('markers flag')),
2424 ('', 'record-parents', False,
2425 ('', 'record-parents', False,
2425 _('record parent information for the precursor')),
2426 _('record parent information for the precursor')),
2426 ('r', 'rev', [], _('display markers relevant to REV')),
2427 ('r', 'rev', [], _('display markers relevant to REV')),
2427 ] + commitopts2,
2428 ] + commitopts2,
2428 _('[OBSOLETED [REPLACEMENT] [REPL... ]'))
2429 _('[OBSOLETED [REPLACEMENT] [REPL... ]'))
2429 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2430 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2430 """create arbitrary obsolete marker
2431 """create arbitrary obsolete marker
2431
2432
2432 With no arguments, displays the list of obsolescence markers."""
2433 With no arguments, displays the list of obsolescence markers."""
2433
2434
2434 def parsenodeid(s):
2435 def parsenodeid(s):
2435 try:
2436 try:
2436 # We do not use revsingle/revrange functions here to accept
2437 # We do not use revsingle/revrange functions here to accept
2437 # arbitrary node identifiers, possibly not present in the
2438 # arbitrary node identifiers, possibly not present in the
2438 # local repository.
2439 # local repository.
2439 n = bin(s)
2440 n = bin(s)
2440 if len(n) != len(nullid):
2441 if len(n) != len(nullid):
2441 raise TypeError()
2442 raise TypeError()
2442 return n
2443 return n
2443 except TypeError:
2444 except TypeError:
2444 raise util.Abort('changeset references must be full hexadecimal '
2445 raise util.Abort('changeset references must be full hexadecimal '
2445 'node identifiers')
2446 'node identifiers')
2446
2447
2447 if precursor is not None:
2448 if precursor is not None:
2448 if opts['rev']:
2449 if opts['rev']:
2449 raise util.Abort('cannot select revision when creating marker')
2450 raise util.Abort('cannot select revision when creating marker')
2450 metadata = {}
2451 metadata = {}
2451 metadata['user'] = opts['user'] or ui.username()
2452 metadata['user'] = opts['user'] or ui.username()
2452 succs = tuple(parsenodeid(succ) for succ in successors)
2453 succs = tuple(parsenodeid(succ) for succ in successors)
2453 l = repo.lock()
2454 l = repo.lock()
2454 try:
2455 try:
2455 tr = repo.transaction('debugobsolete')
2456 tr = repo.transaction('debugobsolete')
2456 try:
2457 try:
2457 try:
2458 try:
2458 date = opts.get('date')
2459 date = opts.get('date')
2459 if date:
2460 if date:
2460 date = util.parsedate(date)
2461 date = util.parsedate(date)
2461 else:
2462 else:
2462 date = None
2463 date = None
2463 prec = parsenodeid(precursor)
2464 prec = parsenodeid(precursor)
2464 parents = None
2465 parents = None
2465 if opts['record_parents']:
2466 if opts['record_parents']:
2466 if prec not in repo.unfiltered():
2467 if prec not in repo.unfiltered():
2467 raise util.Abort('cannot used --record-parents on '
2468 raise util.Abort('cannot used --record-parents on '
2468 'unknown changesets')
2469 'unknown changesets')
2469 parents = repo.unfiltered()[prec].parents()
2470 parents = repo.unfiltered()[prec].parents()
2470 parents = tuple(p.node() for p in parents)
2471 parents = tuple(p.node() for p in parents)
2471 repo.obsstore.create(tr, prec, succs, opts['flags'],
2472 repo.obsstore.create(tr, prec, succs, opts['flags'],
2472 parents=parents, date=date,
2473 parents=parents, date=date,
2473 metadata=metadata)
2474 metadata=metadata)
2474 tr.close()
2475 tr.close()
2475 except ValueError, exc:
2476 except ValueError, exc:
2476 raise util.Abort(_('bad obsmarker input: %s') % exc)
2477 raise util.Abort(_('bad obsmarker input: %s') % exc)
2477 finally:
2478 finally:
2478 tr.release()
2479 tr.release()
2479 finally:
2480 finally:
2480 l.release()
2481 l.release()
2481 else:
2482 else:
2482 if opts['rev']:
2483 if opts['rev']:
2483 revs = scmutil.revrange(repo, opts['rev'])
2484 revs = scmutil.revrange(repo, opts['rev'])
2484 nodes = [repo[r].node() for r in revs]
2485 nodes = [repo[r].node() for r in revs]
2485 markers = list(obsolete.getmarkers(repo, nodes=nodes))
2486 markers = list(obsolete.getmarkers(repo, nodes=nodes))
2486 markers.sort(key=lambda x: x._data)
2487 markers.sort(key=lambda x: x._data)
2487 else:
2488 else:
2488 markers = obsolete.getmarkers(repo)
2489 markers = obsolete.getmarkers(repo)
2489
2490
2490 for m in markers:
2491 for m in markers:
2491 cmdutil.showmarker(ui, m)
2492 cmdutil.showmarker(ui, m)
2492
2493
2493 @command('debugpathcomplete',
2494 @command('debugpathcomplete',
2494 [('f', 'full', None, _('complete an entire path')),
2495 [('f', 'full', None, _('complete an entire path')),
2495 ('n', 'normal', None, _('show only normal files')),
2496 ('n', 'normal', None, _('show only normal files')),
2496 ('a', 'added', None, _('show only added files')),
2497 ('a', 'added', None, _('show only added files')),
2497 ('r', 'removed', None, _('show only removed files'))],
2498 ('r', 'removed', None, _('show only removed files'))],
2498 _('FILESPEC...'))
2499 _('FILESPEC...'))
2499 def debugpathcomplete(ui, repo, *specs, **opts):
2500 def debugpathcomplete(ui, repo, *specs, **opts):
2500 '''complete part or all of a tracked path
2501 '''complete part or all of a tracked path
2501
2502
2502 This command supports shells that offer path name completion. It
2503 This command supports shells that offer path name completion. It
2503 currently completes only files already known to the dirstate.
2504 currently completes only files already known to the dirstate.
2504
2505
2505 Completion extends only to the next path segment unless
2506 Completion extends only to the next path segment unless
2506 --full is specified, in which case entire paths are used.'''
2507 --full is specified, in which case entire paths are used.'''
2507
2508
2508 def complete(path, acceptable):
2509 def complete(path, acceptable):
2509 dirstate = repo.dirstate
2510 dirstate = repo.dirstate
2510 spec = os.path.normpath(os.path.join(os.getcwd(), path))
2511 spec = os.path.normpath(os.path.join(os.getcwd(), path))
2511 rootdir = repo.root + os.sep
2512 rootdir = repo.root + os.sep
2512 if spec != repo.root and not spec.startswith(rootdir):
2513 if spec != repo.root and not spec.startswith(rootdir):
2513 return [], []
2514 return [], []
2514 if os.path.isdir(spec):
2515 if os.path.isdir(spec):
2515 spec += '/'
2516 spec += '/'
2516 spec = spec[len(rootdir):]
2517 spec = spec[len(rootdir):]
2517 fixpaths = os.sep != '/'
2518 fixpaths = os.sep != '/'
2518 if fixpaths:
2519 if fixpaths:
2519 spec = spec.replace(os.sep, '/')
2520 spec = spec.replace(os.sep, '/')
2520 speclen = len(spec)
2521 speclen = len(spec)
2521 fullpaths = opts['full']
2522 fullpaths = opts['full']
2522 files, dirs = set(), set()
2523 files, dirs = set(), set()
2523 adddir, addfile = dirs.add, files.add
2524 adddir, addfile = dirs.add, files.add
2524 for f, st in dirstate.iteritems():
2525 for f, st in dirstate.iteritems():
2525 if f.startswith(spec) and st[0] in acceptable:
2526 if f.startswith(spec) and st[0] in acceptable:
2526 if fixpaths:
2527 if fixpaths:
2527 f = f.replace('/', os.sep)
2528 f = f.replace('/', os.sep)
2528 if fullpaths:
2529 if fullpaths:
2529 addfile(f)
2530 addfile(f)
2530 continue
2531 continue
2531 s = f.find(os.sep, speclen)
2532 s = f.find(os.sep, speclen)
2532 if s >= 0:
2533 if s >= 0:
2533 adddir(f[:s])
2534 adddir(f[:s])
2534 else:
2535 else:
2535 addfile(f)
2536 addfile(f)
2536 return files, dirs
2537 return files, dirs
2537
2538
2538 acceptable = ''
2539 acceptable = ''
2539 if opts['normal']:
2540 if opts['normal']:
2540 acceptable += 'nm'
2541 acceptable += 'nm'
2541 if opts['added']:
2542 if opts['added']:
2542 acceptable += 'a'
2543 acceptable += 'a'
2543 if opts['removed']:
2544 if opts['removed']:
2544 acceptable += 'r'
2545 acceptable += 'r'
2545 cwd = repo.getcwd()
2546 cwd = repo.getcwd()
2546 if not specs:
2547 if not specs:
2547 specs = ['.']
2548 specs = ['.']
2548
2549
2549 files, dirs = set(), set()
2550 files, dirs = set(), set()
2550 for spec in specs:
2551 for spec in specs:
2551 f, d = complete(spec, acceptable or 'nmar')
2552 f, d = complete(spec, acceptable or 'nmar')
2552 files.update(f)
2553 files.update(f)
2553 dirs.update(d)
2554 dirs.update(d)
2554 files.update(dirs)
2555 files.update(dirs)
2555 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
2556 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
2556 ui.write('\n')
2557 ui.write('\n')
2557
2558
2558 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
2559 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
2559 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2560 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2560 '''access the pushkey key/value protocol
2561 '''access the pushkey key/value protocol
2561
2562
2562 With two args, list the keys in the given namespace.
2563 With two args, list the keys in the given namespace.
2563
2564
2564 With five args, set a key to new if it currently is set to old.
2565 With five args, set a key to new if it currently is set to old.
2565 Reports success or failure.
2566 Reports success or failure.
2566 '''
2567 '''
2567
2568
2568 target = hg.peer(ui, {}, repopath)
2569 target = hg.peer(ui, {}, repopath)
2569 if keyinfo:
2570 if keyinfo:
2570 key, old, new = keyinfo
2571 key, old, new = keyinfo
2571 r = target.pushkey(namespace, key, old, new)
2572 r = target.pushkey(namespace, key, old, new)
2572 ui.status(str(r) + '\n')
2573 ui.status(str(r) + '\n')
2573 return not r
2574 return not r
2574 else:
2575 else:
2575 for k, v in sorted(target.listkeys(namespace).iteritems()):
2576 for k, v in sorted(target.listkeys(namespace).iteritems()):
2576 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2577 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2577 v.encode('string-escape')))
2578 v.encode('string-escape')))
2578
2579
2579 @command('debugpvec', [], _('A B'))
2580 @command('debugpvec', [], _('A B'))
2580 def debugpvec(ui, repo, a, b=None):
2581 def debugpvec(ui, repo, a, b=None):
2581 ca = scmutil.revsingle(repo, a)
2582 ca = scmutil.revsingle(repo, a)
2582 cb = scmutil.revsingle(repo, b)
2583 cb = scmutil.revsingle(repo, b)
2583 pa = pvec.ctxpvec(ca)
2584 pa = pvec.ctxpvec(ca)
2584 pb = pvec.ctxpvec(cb)
2585 pb = pvec.ctxpvec(cb)
2585 if pa == pb:
2586 if pa == pb:
2586 rel = "="
2587 rel = "="
2587 elif pa > pb:
2588 elif pa > pb:
2588 rel = ">"
2589 rel = ">"
2589 elif pa < pb:
2590 elif pa < pb:
2590 rel = "<"
2591 rel = "<"
2591 elif pa | pb:
2592 elif pa | pb:
2592 rel = "|"
2593 rel = "|"
2593 ui.write(_("a: %s\n") % pa)
2594 ui.write(_("a: %s\n") % pa)
2594 ui.write(_("b: %s\n") % pb)
2595 ui.write(_("b: %s\n") % pb)
2595 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2596 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2596 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2597 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2597 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2598 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2598 pa.distance(pb), rel))
2599 pa.distance(pb), rel))
2599
2600
2600 @command('debugrebuilddirstate|debugrebuildstate',
2601 @command('debugrebuilddirstate|debugrebuildstate',
2601 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
2602 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
2602 _('[-r REV]'))
2603 _('[-r REV]'))
2603 def debugrebuilddirstate(ui, repo, rev):
2604 def debugrebuilddirstate(ui, repo, rev):
2604 """rebuild the dirstate as it would look like for the given revision
2605 """rebuild the dirstate as it would look like for the given revision
2605
2606
2606 If no revision is specified the first current parent will be used.
2607 If no revision is specified the first current parent will be used.
2607
2608
2608 The dirstate will be set to the files of the given revision.
2609 The dirstate will be set to the files of the given revision.
2609 The actual working directory content or existing dirstate
2610 The actual working directory content or existing dirstate
2610 information such as adds or removes is not considered.
2611 information such as adds or removes is not considered.
2611
2612
2612 One use of this command is to make the next :hg:`status` invocation
2613 One use of this command is to make the next :hg:`status` invocation
2613 check the actual file content.
2614 check the actual file content.
2614 """
2615 """
2615 ctx = scmutil.revsingle(repo, rev)
2616 ctx = scmutil.revsingle(repo, rev)
2616 wlock = repo.wlock()
2617 wlock = repo.wlock()
2617 try:
2618 try:
2618 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2619 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2619 finally:
2620 finally:
2620 wlock.release()
2621 wlock.release()
2621
2622
2622 @command('debugrename',
2623 @command('debugrename',
2623 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2624 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2624 _('[-r REV] FILE'))
2625 _('[-r REV] FILE'))
2625 def debugrename(ui, repo, file1, *pats, **opts):
2626 def debugrename(ui, repo, file1, *pats, **opts):
2626 """dump rename information"""
2627 """dump rename information"""
2627
2628
2628 ctx = scmutil.revsingle(repo, opts.get('rev'))
2629 ctx = scmutil.revsingle(repo, opts.get('rev'))
2629 m = scmutil.match(ctx, (file1,) + pats, opts)
2630 m = scmutil.match(ctx, (file1,) + pats, opts)
2630 for abs in ctx.walk(m):
2631 for abs in ctx.walk(m):
2631 fctx = ctx[abs]
2632 fctx = ctx[abs]
2632 o = fctx.filelog().renamed(fctx.filenode())
2633 o = fctx.filelog().renamed(fctx.filenode())
2633 rel = m.rel(abs)
2634 rel = m.rel(abs)
2634 if o:
2635 if o:
2635 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2636 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2636 else:
2637 else:
2637 ui.write(_("%s not renamed\n") % rel)
2638 ui.write(_("%s not renamed\n") % rel)
2638
2639
2639 @command('debugrevlog',
2640 @command('debugrevlog',
2640 [('c', 'changelog', False, _('open changelog')),
2641 [('c', 'changelog', False, _('open changelog')),
2641 ('m', 'manifest', False, _('open manifest')),
2642 ('m', 'manifest', False, _('open manifest')),
2642 ('d', 'dump', False, _('dump index data'))],
2643 ('d', 'dump', False, _('dump index data'))],
2643 _('-c|-m|FILE'),
2644 _('-c|-m|FILE'),
2644 optionalrepo=True)
2645 optionalrepo=True)
2645 def debugrevlog(ui, repo, file_=None, **opts):
2646 def debugrevlog(ui, repo, file_=None, **opts):
2646 """show data and statistics about a revlog"""
2647 """show data and statistics about a revlog"""
2647 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2648 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2648
2649
2649 if opts.get("dump"):
2650 if opts.get("dump"):
2650 numrevs = len(r)
2651 numrevs = len(r)
2651 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
2652 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
2652 " rawsize totalsize compression heads chainlen\n")
2653 " rawsize totalsize compression heads chainlen\n")
2653 ts = 0
2654 ts = 0
2654 heads = set()
2655 heads = set()
2655 rindex = r.index
2656 rindex = r.index
2656
2657
2657 def chainbaseandlen(rev):
2658 def chainbaseandlen(rev):
2658 clen = 0
2659 clen = 0
2659 base = rindex[rev][3]
2660 base = rindex[rev][3]
2660 while base != rev:
2661 while base != rev:
2661 clen += 1
2662 clen += 1
2662 rev = base
2663 rev = base
2663 base = rindex[rev][3]
2664 base = rindex[rev][3]
2664 return base, clen
2665 return base, clen
2665
2666
2666 for rev in xrange(numrevs):
2667 for rev in xrange(numrevs):
2667 dbase = r.deltaparent(rev)
2668 dbase = r.deltaparent(rev)
2668 if dbase == -1:
2669 if dbase == -1:
2669 dbase = rev
2670 dbase = rev
2670 cbase, clen = chainbaseandlen(rev)
2671 cbase, clen = chainbaseandlen(rev)
2671 p1, p2 = r.parentrevs(rev)
2672 p1, p2 = r.parentrevs(rev)
2672 rs = r.rawsize(rev)
2673 rs = r.rawsize(rev)
2673 ts = ts + rs
2674 ts = ts + rs
2674 heads -= set(r.parentrevs(rev))
2675 heads -= set(r.parentrevs(rev))
2675 heads.add(rev)
2676 heads.add(rev)
2676 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
2677 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
2677 "%11d %5d %8d\n" %
2678 "%11d %5d %8d\n" %
2678 (rev, p1, p2, r.start(rev), r.end(rev),
2679 (rev, p1, p2, r.start(rev), r.end(rev),
2679 r.start(dbase), r.start(cbase),
2680 r.start(dbase), r.start(cbase),
2680 r.start(p1), r.start(p2),
2681 r.start(p1), r.start(p2),
2681 rs, ts, ts / r.end(rev), len(heads), clen))
2682 rs, ts, ts / r.end(rev), len(heads), clen))
2682 return 0
2683 return 0
2683
2684
2684 v = r.version
2685 v = r.version
2685 format = v & 0xFFFF
2686 format = v & 0xFFFF
2686 flags = []
2687 flags = []
2687 gdelta = False
2688 gdelta = False
2688 if v & revlog.REVLOGNGINLINEDATA:
2689 if v & revlog.REVLOGNGINLINEDATA:
2689 flags.append('inline')
2690 flags.append('inline')
2690 if v & revlog.REVLOGGENERALDELTA:
2691 if v & revlog.REVLOGGENERALDELTA:
2691 gdelta = True
2692 gdelta = True
2692 flags.append('generaldelta')
2693 flags.append('generaldelta')
2693 if not flags:
2694 if not flags:
2694 flags = ['(none)']
2695 flags = ['(none)']
2695
2696
2696 nummerges = 0
2697 nummerges = 0
2697 numfull = 0
2698 numfull = 0
2698 numprev = 0
2699 numprev = 0
2699 nump1 = 0
2700 nump1 = 0
2700 nump2 = 0
2701 nump2 = 0
2701 numother = 0
2702 numother = 0
2702 nump1prev = 0
2703 nump1prev = 0
2703 nump2prev = 0
2704 nump2prev = 0
2704 chainlengths = []
2705 chainlengths = []
2705
2706
2706 datasize = [None, 0, 0L]
2707 datasize = [None, 0, 0L]
2707 fullsize = [None, 0, 0L]
2708 fullsize = [None, 0, 0L]
2708 deltasize = [None, 0, 0L]
2709 deltasize = [None, 0, 0L]
2709
2710
2710 def addsize(size, l):
2711 def addsize(size, l):
2711 if l[0] is None or size < l[0]:
2712 if l[0] is None or size < l[0]:
2712 l[0] = size
2713 l[0] = size
2713 if size > l[1]:
2714 if size > l[1]:
2714 l[1] = size
2715 l[1] = size
2715 l[2] += size
2716 l[2] += size
2716
2717
2717 numrevs = len(r)
2718 numrevs = len(r)
2718 for rev in xrange(numrevs):
2719 for rev in xrange(numrevs):
2719 p1, p2 = r.parentrevs(rev)
2720 p1, p2 = r.parentrevs(rev)
2720 delta = r.deltaparent(rev)
2721 delta = r.deltaparent(rev)
2721 if format > 0:
2722 if format > 0:
2722 addsize(r.rawsize(rev), datasize)
2723 addsize(r.rawsize(rev), datasize)
2723 if p2 != nullrev:
2724 if p2 != nullrev:
2724 nummerges += 1
2725 nummerges += 1
2725 size = r.length(rev)
2726 size = r.length(rev)
2726 if delta == nullrev:
2727 if delta == nullrev:
2727 chainlengths.append(0)
2728 chainlengths.append(0)
2728 numfull += 1
2729 numfull += 1
2729 addsize(size, fullsize)
2730 addsize(size, fullsize)
2730 else:
2731 else:
2731 chainlengths.append(chainlengths[delta] + 1)
2732 chainlengths.append(chainlengths[delta] + 1)
2732 addsize(size, deltasize)
2733 addsize(size, deltasize)
2733 if delta == rev - 1:
2734 if delta == rev - 1:
2734 numprev += 1
2735 numprev += 1
2735 if delta == p1:
2736 if delta == p1:
2736 nump1prev += 1
2737 nump1prev += 1
2737 elif delta == p2:
2738 elif delta == p2:
2738 nump2prev += 1
2739 nump2prev += 1
2739 elif delta == p1:
2740 elif delta == p1:
2740 nump1 += 1
2741 nump1 += 1
2741 elif delta == p2:
2742 elif delta == p2:
2742 nump2 += 1
2743 nump2 += 1
2743 elif delta != nullrev:
2744 elif delta != nullrev:
2744 numother += 1
2745 numother += 1
2745
2746
2746 # Adjust size min value for empty cases
2747 # Adjust size min value for empty cases
2747 for size in (datasize, fullsize, deltasize):
2748 for size in (datasize, fullsize, deltasize):
2748 if size[0] is None:
2749 if size[0] is None:
2749 size[0] = 0
2750 size[0] = 0
2750
2751
2751 numdeltas = numrevs - numfull
2752 numdeltas = numrevs - numfull
2752 numoprev = numprev - nump1prev - nump2prev
2753 numoprev = numprev - nump1prev - nump2prev
2753 totalrawsize = datasize[2]
2754 totalrawsize = datasize[2]
2754 datasize[2] /= numrevs
2755 datasize[2] /= numrevs
2755 fulltotal = fullsize[2]
2756 fulltotal = fullsize[2]
2756 fullsize[2] /= numfull
2757 fullsize[2] /= numfull
2757 deltatotal = deltasize[2]
2758 deltatotal = deltasize[2]
2758 if numrevs - numfull > 0:
2759 if numrevs - numfull > 0:
2759 deltasize[2] /= numrevs - numfull
2760 deltasize[2] /= numrevs - numfull
2760 totalsize = fulltotal + deltatotal
2761 totalsize = fulltotal + deltatotal
2761 avgchainlen = sum(chainlengths) / numrevs
2762 avgchainlen = sum(chainlengths) / numrevs
2762 compratio = totalrawsize / totalsize
2763 compratio = totalrawsize / totalsize
2763
2764
2764 basedfmtstr = '%%%dd\n'
2765 basedfmtstr = '%%%dd\n'
2765 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2766 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2766
2767
2767 def dfmtstr(max):
2768 def dfmtstr(max):
2768 return basedfmtstr % len(str(max))
2769 return basedfmtstr % len(str(max))
2769 def pcfmtstr(max, padding=0):
2770 def pcfmtstr(max, padding=0):
2770 return basepcfmtstr % (len(str(max)), ' ' * padding)
2771 return basepcfmtstr % (len(str(max)), ' ' * padding)
2771
2772
2772 def pcfmt(value, total):
2773 def pcfmt(value, total):
2773 return (value, 100 * float(value) / total)
2774 return (value, 100 * float(value) / total)
2774
2775
2775 ui.write(('format : %d\n') % format)
2776 ui.write(('format : %d\n') % format)
2776 ui.write(('flags : %s\n') % ', '.join(flags))
2777 ui.write(('flags : %s\n') % ', '.join(flags))
2777
2778
2778 ui.write('\n')
2779 ui.write('\n')
2779 fmt = pcfmtstr(totalsize)
2780 fmt = pcfmtstr(totalsize)
2780 fmt2 = dfmtstr(totalsize)
2781 fmt2 = dfmtstr(totalsize)
2781 ui.write(('revisions : ') + fmt2 % numrevs)
2782 ui.write(('revisions : ') + fmt2 % numrevs)
2782 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2783 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2783 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2784 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2784 ui.write(('revisions : ') + fmt2 % numrevs)
2785 ui.write(('revisions : ') + fmt2 % numrevs)
2785 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
2786 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
2786 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2787 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2787 ui.write(('revision size : ') + fmt2 % totalsize)
2788 ui.write(('revision size : ') + fmt2 % totalsize)
2788 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
2789 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
2789 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2790 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2790
2791
2791 ui.write('\n')
2792 ui.write('\n')
2792 fmt = dfmtstr(max(avgchainlen, compratio))
2793 fmt = dfmtstr(max(avgchainlen, compratio))
2793 ui.write(('avg chain length : ') + fmt % avgchainlen)
2794 ui.write(('avg chain length : ') + fmt % avgchainlen)
2794 ui.write(('compression ratio : ') + fmt % compratio)
2795 ui.write(('compression ratio : ') + fmt % compratio)
2795
2796
2796 if format > 0:
2797 if format > 0:
2797 ui.write('\n')
2798 ui.write('\n')
2798 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2799 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2799 % tuple(datasize))
2800 % tuple(datasize))
2800 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2801 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2801 % tuple(fullsize))
2802 % tuple(fullsize))
2802 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2803 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2803 % tuple(deltasize))
2804 % tuple(deltasize))
2804
2805
2805 if numdeltas > 0:
2806 if numdeltas > 0:
2806 ui.write('\n')
2807 ui.write('\n')
2807 fmt = pcfmtstr(numdeltas)
2808 fmt = pcfmtstr(numdeltas)
2808 fmt2 = pcfmtstr(numdeltas, 4)
2809 fmt2 = pcfmtstr(numdeltas, 4)
2809 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2810 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2810 if numprev > 0:
2811 if numprev > 0:
2811 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2812 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2812 numprev))
2813 numprev))
2813 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2814 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2814 numprev))
2815 numprev))
2815 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2816 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2816 numprev))
2817 numprev))
2817 if gdelta:
2818 if gdelta:
2818 ui.write(('deltas against p1 : ')
2819 ui.write(('deltas against p1 : ')
2819 + fmt % pcfmt(nump1, numdeltas))
2820 + fmt % pcfmt(nump1, numdeltas))
2820 ui.write(('deltas against p2 : ')
2821 ui.write(('deltas against p2 : ')
2821 + fmt % pcfmt(nump2, numdeltas))
2822 + fmt % pcfmt(nump2, numdeltas))
2822 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2823 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2823 numdeltas))
2824 numdeltas))
2824
2825
2825 @command('debugrevspec',
2826 @command('debugrevspec',
2826 [('', 'optimize', None, _('print parsed tree after optimizing'))],
2827 [('', 'optimize', None, _('print parsed tree after optimizing'))],
2827 ('REVSPEC'))
2828 ('REVSPEC'))
2828 def debugrevspec(ui, repo, expr, **opts):
2829 def debugrevspec(ui, repo, expr, **opts):
2829 """parse and apply a revision specification
2830 """parse and apply a revision specification
2830
2831
2831 Use --verbose to print the parsed tree before and after aliases
2832 Use --verbose to print the parsed tree before and after aliases
2832 expansion.
2833 expansion.
2833 """
2834 """
2834 if ui.verbose:
2835 if ui.verbose:
2835 tree = revset.parse(expr)[0]
2836 tree = revset.parse(expr)[0]
2836 ui.note(revset.prettyformat(tree), "\n")
2837 ui.note(revset.prettyformat(tree), "\n")
2837 newtree = revset.findaliases(ui, tree)
2838 newtree = revset.findaliases(ui, tree)
2838 if newtree != tree:
2839 if newtree != tree:
2839 ui.note(revset.prettyformat(newtree), "\n")
2840 ui.note(revset.prettyformat(newtree), "\n")
2840 if opts["optimize"]:
2841 if opts["optimize"]:
2841 weight, optimizedtree = revset.optimize(newtree, True)
2842 weight, optimizedtree = revset.optimize(newtree, True)
2842 ui.note("* optimized:\n", revset.prettyformat(optimizedtree), "\n")
2843 ui.note("* optimized:\n", revset.prettyformat(optimizedtree), "\n")
2843 func = revset.match(ui, expr)
2844 func = revset.match(ui, expr)
2844 for c in func(repo, revset.spanset(repo)):
2845 for c in func(repo, revset.spanset(repo)):
2845 ui.write("%s\n" % c)
2846 ui.write("%s\n" % c)
2846
2847
2847 @command('debugsetparents', [], _('REV1 [REV2]'))
2848 @command('debugsetparents', [], _('REV1 [REV2]'))
2848 def debugsetparents(ui, repo, rev1, rev2=None):
2849 def debugsetparents(ui, repo, rev1, rev2=None):
2849 """manually set the parents of the current working directory
2850 """manually set the parents of the current working directory
2850
2851
2851 This is useful for writing repository conversion tools, but should
2852 This is useful for writing repository conversion tools, but should
2852 be used with care.
2853 be used with care.
2853
2854
2854 Returns 0 on success.
2855 Returns 0 on success.
2855 """
2856 """
2856
2857
2857 r1 = scmutil.revsingle(repo, rev1).node()
2858 r1 = scmutil.revsingle(repo, rev1).node()
2858 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2859 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2859
2860
2860 wlock = repo.wlock()
2861 wlock = repo.wlock()
2861 try:
2862 try:
2862 repo.dirstate.beginparentchange()
2863 repo.dirstate.beginparentchange()
2863 repo.setparents(r1, r2)
2864 repo.setparents(r1, r2)
2864 repo.dirstate.endparentchange()
2865 repo.dirstate.endparentchange()
2865 finally:
2866 finally:
2866 wlock.release()
2867 wlock.release()
2867
2868
2868 @command('debugdirstate|debugstate',
2869 @command('debugdirstate|debugstate',
2869 [('', 'nodates', None, _('do not display the saved mtime')),
2870 [('', 'nodates', None, _('do not display the saved mtime')),
2870 ('', 'datesort', None, _('sort by saved mtime'))],
2871 ('', 'datesort', None, _('sort by saved mtime'))],
2871 _('[OPTION]...'))
2872 _('[OPTION]...'))
2872 def debugstate(ui, repo, nodates=None, datesort=None):
2873 def debugstate(ui, repo, nodates=None, datesort=None):
2873 """show the contents of the current dirstate"""
2874 """show the contents of the current dirstate"""
2874 timestr = ""
2875 timestr = ""
2875 showdate = not nodates
2876 showdate = not nodates
2876 if datesort:
2877 if datesort:
2877 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2878 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2878 else:
2879 else:
2879 keyfunc = None # sort by filename
2880 keyfunc = None # sort by filename
2880 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2881 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2881 if showdate:
2882 if showdate:
2882 if ent[3] == -1:
2883 if ent[3] == -1:
2883 # Pad or slice to locale representation
2884 # Pad or slice to locale representation
2884 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2885 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2885 time.localtime(0)))
2886 time.localtime(0)))
2886 timestr = 'unset'
2887 timestr = 'unset'
2887 timestr = (timestr[:locale_len] +
2888 timestr = (timestr[:locale_len] +
2888 ' ' * (locale_len - len(timestr)))
2889 ' ' * (locale_len - len(timestr)))
2889 else:
2890 else:
2890 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2891 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2891 time.localtime(ent[3]))
2892 time.localtime(ent[3]))
2892 if ent[1] & 020000:
2893 if ent[1] & 020000:
2893 mode = 'lnk'
2894 mode = 'lnk'
2894 else:
2895 else:
2895 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2896 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2896 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2897 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2897 for f in repo.dirstate.copies():
2898 for f in repo.dirstate.copies():
2898 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2899 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2899
2900
2900 @command('debugsub',
2901 @command('debugsub',
2901 [('r', 'rev', '',
2902 [('r', 'rev', '',
2902 _('revision to check'), _('REV'))],
2903 _('revision to check'), _('REV'))],
2903 _('[-r REV] [REV]'))
2904 _('[-r REV] [REV]'))
2904 def debugsub(ui, repo, rev=None):
2905 def debugsub(ui, repo, rev=None):
2905 ctx = scmutil.revsingle(repo, rev, None)
2906 ctx = scmutil.revsingle(repo, rev, None)
2906 for k, v in sorted(ctx.substate.items()):
2907 for k, v in sorted(ctx.substate.items()):
2907 ui.write(('path %s\n') % k)
2908 ui.write(('path %s\n') % k)
2908 ui.write((' source %s\n') % v[0])
2909 ui.write((' source %s\n') % v[0])
2909 ui.write((' revision %s\n') % v[1])
2910 ui.write((' revision %s\n') % v[1])
2910
2911
2911 @command('debugsuccessorssets',
2912 @command('debugsuccessorssets',
2912 [],
2913 [],
2913 _('[REV]'))
2914 _('[REV]'))
2914 def debugsuccessorssets(ui, repo, *revs):
2915 def debugsuccessorssets(ui, repo, *revs):
2915 """show set of successors for revision
2916 """show set of successors for revision
2916
2917
2917 A successors set of changeset A is a consistent group of revisions that
2918 A successors set of changeset A is a consistent group of revisions that
2918 succeed A. It contains non-obsolete changesets only.
2919 succeed A. It contains non-obsolete changesets only.
2919
2920
2920 In most cases a changeset A has a single successors set containing a single
2921 In most cases a changeset A has a single successors set containing a single
2921 successor (changeset A replaced by A').
2922 successor (changeset A replaced by A').
2922
2923
2923 A changeset that is made obsolete with no successors are called "pruned".
2924 A changeset that is made obsolete with no successors are called "pruned".
2924 Such changesets have no successors sets at all.
2925 Such changesets have no successors sets at all.
2925
2926
2926 A changeset that has been "split" will have a successors set containing
2927 A changeset that has been "split" will have a successors set containing
2927 more than one successor.
2928 more than one successor.
2928
2929
2929 A changeset that has been rewritten in multiple different ways is called
2930 A changeset that has been rewritten in multiple different ways is called
2930 "divergent". Such changesets have multiple successor sets (each of which
2931 "divergent". Such changesets have multiple successor sets (each of which
2931 may also be split, i.e. have multiple successors).
2932 may also be split, i.e. have multiple successors).
2932
2933
2933 Results are displayed as follows::
2934 Results are displayed as follows::
2934
2935
2935 <rev1>
2936 <rev1>
2936 <successors-1A>
2937 <successors-1A>
2937 <rev2>
2938 <rev2>
2938 <successors-2A>
2939 <successors-2A>
2939 <successors-2B1> <successors-2B2> <successors-2B3>
2940 <successors-2B1> <successors-2B2> <successors-2B3>
2940
2941
2941 Here rev2 has two possible (i.e. divergent) successors sets. The first
2942 Here rev2 has two possible (i.e. divergent) successors sets. The first
2942 holds one element, whereas the second holds three (i.e. the changeset has
2943 holds one element, whereas the second holds three (i.e. the changeset has
2943 been split).
2944 been split).
2944 """
2945 """
2945 # passed to successorssets caching computation from one call to another
2946 # passed to successorssets caching computation from one call to another
2946 cache = {}
2947 cache = {}
2947 ctx2str = str
2948 ctx2str = str
2948 node2str = short
2949 node2str = short
2949 if ui.debug():
2950 if ui.debug():
2950 def ctx2str(ctx):
2951 def ctx2str(ctx):
2951 return ctx.hex()
2952 return ctx.hex()
2952 node2str = hex
2953 node2str = hex
2953 for rev in scmutil.revrange(repo, revs):
2954 for rev in scmutil.revrange(repo, revs):
2954 ctx = repo[rev]
2955 ctx = repo[rev]
2955 ui.write('%s\n'% ctx2str(ctx))
2956 ui.write('%s\n'% ctx2str(ctx))
2956 for succsset in obsolete.successorssets(repo, ctx.node(), cache):
2957 for succsset in obsolete.successorssets(repo, ctx.node(), cache):
2957 if succsset:
2958 if succsset:
2958 ui.write(' ')
2959 ui.write(' ')
2959 ui.write(node2str(succsset[0]))
2960 ui.write(node2str(succsset[0]))
2960 for node in succsset[1:]:
2961 for node in succsset[1:]:
2961 ui.write(' ')
2962 ui.write(' ')
2962 ui.write(node2str(node))
2963 ui.write(node2str(node))
2963 ui.write('\n')
2964 ui.write('\n')
2964
2965
2965 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'), inferrepo=True)
2966 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'), inferrepo=True)
2966 def debugwalk(ui, repo, *pats, **opts):
2967 def debugwalk(ui, repo, *pats, **opts):
2967 """show how files match on given patterns"""
2968 """show how files match on given patterns"""
2968 m = scmutil.match(repo[None], pats, opts)
2969 m = scmutil.match(repo[None], pats, opts)
2969 items = list(repo.walk(m))
2970 items = list(repo.walk(m))
2970 if not items:
2971 if not items:
2971 return
2972 return
2972 f = lambda fn: fn
2973 f = lambda fn: fn
2973 if ui.configbool('ui', 'slash') and os.sep != '/':
2974 if ui.configbool('ui', 'slash') and os.sep != '/':
2974 f = lambda fn: util.normpath(fn)
2975 f = lambda fn: util.normpath(fn)
2975 fmt = 'f %%-%ds %%-%ds %%s' % (
2976 fmt = 'f %%-%ds %%-%ds %%s' % (
2976 max([len(abs) for abs in items]),
2977 max([len(abs) for abs in items]),
2977 max([len(m.rel(abs)) for abs in items]))
2978 max([len(m.rel(abs)) for abs in items]))
2978 for abs in items:
2979 for abs in items:
2979 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2980 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2980 ui.write("%s\n" % line.rstrip())
2981 ui.write("%s\n" % line.rstrip())
2981
2982
2982 @command('debugwireargs',
2983 @command('debugwireargs',
2983 [('', 'three', '', 'three'),
2984 [('', 'three', '', 'three'),
2984 ('', 'four', '', 'four'),
2985 ('', 'four', '', 'four'),
2985 ('', 'five', '', 'five'),
2986 ('', 'five', '', 'five'),
2986 ] + remoteopts,
2987 ] + remoteopts,
2987 _('REPO [OPTIONS]... [ONE [TWO]]'),
2988 _('REPO [OPTIONS]... [ONE [TWO]]'),
2988 norepo=True)
2989 norepo=True)
2989 def debugwireargs(ui, repopath, *vals, **opts):
2990 def debugwireargs(ui, repopath, *vals, **opts):
2990 repo = hg.peer(ui, opts, repopath)
2991 repo = hg.peer(ui, opts, repopath)
2991 for opt in remoteopts:
2992 for opt in remoteopts:
2992 del opts[opt[1]]
2993 del opts[opt[1]]
2993 args = {}
2994 args = {}
2994 for k, v in opts.iteritems():
2995 for k, v in opts.iteritems():
2995 if v:
2996 if v:
2996 args[k] = v
2997 args[k] = v
2997 # run twice to check that we don't mess up the stream for the next command
2998 # run twice to check that we don't mess up the stream for the next command
2998 res1 = repo.debugwireargs(*vals, **args)
2999 res1 = repo.debugwireargs(*vals, **args)
2999 res2 = repo.debugwireargs(*vals, **args)
3000 res2 = repo.debugwireargs(*vals, **args)
3000 ui.write("%s\n" % res1)
3001 ui.write("%s\n" % res1)
3001 if res1 != res2:
3002 if res1 != res2:
3002 ui.warn("%s\n" % res2)
3003 ui.warn("%s\n" % res2)
3003
3004
3004 @command('^diff',
3005 @command('^diff',
3005 [('r', 'rev', [], _('revision'), _('REV')),
3006 [('r', 'rev', [], _('revision'), _('REV')),
3006 ('c', 'change', '', _('change made by revision'), _('REV'))
3007 ('c', 'change', '', _('change made by revision'), _('REV'))
3007 ] + diffopts + diffopts2 + walkopts + subrepoopts,
3008 ] + diffopts + diffopts2 + walkopts + subrepoopts,
3008 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
3009 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
3009 inferrepo=True)
3010 inferrepo=True)
3010 def diff(ui, repo, *pats, **opts):
3011 def diff(ui, repo, *pats, **opts):
3011 """diff repository (or selected files)
3012 """diff repository (or selected files)
3012
3013
3013 Show differences between revisions for the specified files.
3014 Show differences between revisions for the specified files.
3014
3015
3015 Differences between files are shown using the unified diff format.
3016 Differences between files are shown using the unified diff format.
3016
3017
3017 .. note::
3018 .. note::
3018
3019
3019 diff may generate unexpected results for merges, as it will
3020 diff may generate unexpected results for merges, as it will
3020 default to comparing against the working directory's first
3021 default to comparing against the working directory's first
3021 parent changeset if no revisions are specified.
3022 parent changeset if no revisions are specified.
3022
3023
3023 When two revision arguments are given, then changes are shown
3024 When two revision arguments are given, then changes are shown
3024 between those revisions. If only one revision is specified then
3025 between those revisions. If only one revision is specified then
3025 that revision is compared to the working directory, and, when no
3026 that revision is compared to the working directory, and, when no
3026 revisions are specified, the working directory files are compared
3027 revisions are specified, the working directory files are compared
3027 to its parent.
3028 to its parent.
3028
3029
3029 Alternatively you can specify -c/--change with a revision to see
3030 Alternatively you can specify -c/--change with a revision to see
3030 the changes in that changeset relative to its first parent.
3031 the changes in that changeset relative to its first parent.
3031
3032
3032 Without the -a/--text option, diff will avoid generating diffs of
3033 Without the -a/--text option, diff will avoid generating diffs of
3033 files it detects as binary. With -a, diff will generate a diff
3034 files it detects as binary. With -a, diff will generate a diff
3034 anyway, probably with undesirable results.
3035 anyway, probably with undesirable results.
3035
3036
3036 Use the -g/--git option to generate diffs in the git extended diff
3037 Use the -g/--git option to generate diffs in the git extended diff
3037 format. For more information, read :hg:`help diffs`.
3038 format. For more information, read :hg:`help diffs`.
3038
3039
3039 .. container:: verbose
3040 .. container:: verbose
3040
3041
3041 Examples:
3042 Examples:
3042
3043
3043 - compare a file in the current working directory to its parent::
3044 - compare a file in the current working directory to its parent::
3044
3045
3045 hg diff foo.c
3046 hg diff foo.c
3046
3047
3047 - compare two historical versions of a directory, with rename info::
3048 - compare two historical versions of a directory, with rename info::
3048
3049
3049 hg diff --git -r 1.0:1.2 lib/
3050 hg diff --git -r 1.0:1.2 lib/
3050
3051
3051 - get change stats relative to the last change on some date::
3052 - get change stats relative to the last change on some date::
3052
3053
3053 hg diff --stat -r "date('may 2')"
3054 hg diff --stat -r "date('may 2')"
3054
3055
3055 - diff all newly-added files that contain a keyword::
3056 - diff all newly-added files that contain a keyword::
3056
3057
3057 hg diff "set:added() and grep(GNU)"
3058 hg diff "set:added() and grep(GNU)"
3058
3059
3059 - compare a revision and its parents::
3060 - compare a revision and its parents::
3060
3061
3061 hg diff -c 9353 # compare against first parent
3062 hg diff -c 9353 # compare against first parent
3062 hg diff -r 9353^:9353 # same using revset syntax
3063 hg diff -r 9353^:9353 # same using revset syntax
3063 hg diff -r 9353^2:9353 # compare against the second parent
3064 hg diff -r 9353^2:9353 # compare against the second parent
3064
3065
3065 Returns 0 on success.
3066 Returns 0 on success.
3066 """
3067 """
3067
3068
3068 revs = opts.get('rev')
3069 revs = opts.get('rev')
3069 change = opts.get('change')
3070 change = opts.get('change')
3070 stat = opts.get('stat')
3071 stat = opts.get('stat')
3071 reverse = opts.get('reverse')
3072 reverse = opts.get('reverse')
3072
3073
3073 if revs and change:
3074 if revs and change:
3074 msg = _('cannot specify --rev and --change at the same time')
3075 msg = _('cannot specify --rev and --change at the same time')
3075 raise util.Abort(msg)
3076 raise util.Abort(msg)
3076 elif change:
3077 elif change:
3077 node2 = scmutil.revsingle(repo, change, None).node()
3078 node2 = scmutil.revsingle(repo, change, None).node()
3078 node1 = repo[node2].p1().node()
3079 node1 = repo[node2].p1().node()
3079 else:
3080 else:
3080 node1, node2 = scmutil.revpair(repo, revs)
3081 node1, node2 = scmutil.revpair(repo, revs)
3081
3082
3082 if reverse:
3083 if reverse:
3083 node1, node2 = node2, node1
3084 node1, node2 = node2, node1
3084
3085
3085 diffopts = patch.diffopts(ui, opts)
3086 diffopts = patch.diffopts(ui, opts)
3086 m = scmutil.match(repo[node2], pats, opts)
3087 m = scmutil.match(repo[node2], pats, opts)
3087 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
3088 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
3088 listsubrepos=opts.get('subrepos'))
3089 listsubrepos=opts.get('subrepos'))
3089
3090
3090 @command('^export',
3091 @command('^export',
3091 [('o', 'output', '',
3092 [('o', 'output', '',
3092 _('print output to file with formatted name'), _('FORMAT')),
3093 _('print output to file with formatted name'), _('FORMAT')),
3093 ('', 'switch-parent', None, _('diff against the second parent')),
3094 ('', 'switch-parent', None, _('diff against the second parent')),
3094 ('r', 'rev', [], _('revisions to export'), _('REV')),
3095 ('r', 'rev', [], _('revisions to export'), _('REV')),
3095 ] + diffopts,
3096 ] + diffopts,
3096 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
3097 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
3097 def export(ui, repo, *changesets, **opts):
3098 def export(ui, repo, *changesets, **opts):
3098 """dump the header and diffs for one or more changesets
3099 """dump the header and diffs for one or more changesets
3099
3100
3100 Print the changeset header and diffs for one or more revisions.
3101 Print the changeset header and diffs for one or more revisions.
3101 If no revision is given, the parent of the working directory is used.
3102 If no revision is given, the parent of the working directory is used.
3102
3103
3103 The information shown in the changeset header is: author, date,
3104 The information shown in the changeset header is: author, date,
3104 branch name (if non-default), changeset hash, parent(s) and commit
3105 branch name (if non-default), changeset hash, parent(s) and commit
3105 comment.
3106 comment.
3106
3107
3107 .. note::
3108 .. note::
3108
3109
3109 export may generate unexpected diff output for merge
3110 export may generate unexpected diff output for merge
3110 changesets, as it will compare the merge changeset against its
3111 changesets, as it will compare the merge changeset against its
3111 first parent only.
3112 first parent only.
3112
3113
3113 Output may be to a file, in which case the name of the file is
3114 Output may be to a file, in which case the name of the file is
3114 given using a format string. The formatting rules are as follows:
3115 given using a format string. The formatting rules are as follows:
3115
3116
3116 :``%%``: literal "%" character
3117 :``%%``: literal "%" character
3117 :``%H``: changeset hash (40 hexadecimal digits)
3118 :``%H``: changeset hash (40 hexadecimal digits)
3118 :``%N``: number of patches being generated
3119 :``%N``: number of patches being generated
3119 :``%R``: changeset revision number
3120 :``%R``: changeset revision number
3120 :``%b``: basename of the exporting repository
3121 :``%b``: basename of the exporting repository
3121 :``%h``: short-form changeset hash (12 hexadecimal digits)
3122 :``%h``: short-form changeset hash (12 hexadecimal digits)
3122 :``%m``: first line of the commit message (only alphanumeric characters)
3123 :``%m``: first line of the commit message (only alphanumeric characters)
3123 :``%n``: zero-padded sequence number, starting at 1
3124 :``%n``: zero-padded sequence number, starting at 1
3124 :``%r``: zero-padded changeset revision number
3125 :``%r``: zero-padded changeset revision number
3125
3126
3126 Without the -a/--text option, export will avoid generating diffs
3127 Without the -a/--text option, export will avoid generating diffs
3127 of files it detects as binary. With -a, export will generate a
3128 of files it detects as binary. With -a, export will generate a
3128 diff anyway, probably with undesirable results.
3129 diff anyway, probably with undesirable results.
3129
3130
3130 Use the -g/--git option to generate diffs in the git extended diff
3131 Use the -g/--git option to generate diffs in the git extended diff
3131 format. See :hg:`help diffs` for more information.
3132 format. See :hg:`help diffs` for more information.
3132
3133
3133 With the --switch-parent option, the diff will be against the
3134 With the --switch-parent option, the diff will be against the
3134 second parent. It can be useful to review a merge.
3135 second parent. It can be useful to review a merge.
3135
3136
3136 .. container:: verbose
3137 .. container:: verbose
3137
3138
3138 Examples:
3139 Examples:
3139
3140
3140 - use export and import to transplant a bugfix to the current
3141 - use export and import to transplant a bugfix to the current
3141 branch::
3142 branch::
3142
3143
3143 hg export -r 9353 | hg import -
3144 hg export -r 9353 | hg import -
3144
3145
3145 - export all the changesets between two revisions to a file with
3146 - export all the changesets between two revisions to a file with
3146 rename information::
3147 rename information::
3147
3148
3148 hg export --git -r 123:150 > changes.txt
3149 hg export --git -r 123:150 > changes.txt
3149
3150
3150 - split outgoing changes into a series of patches with
3151 - split outgoing changes into a series of patches with
3151 descriptive names::
3152 descriptive names::
3152
3153
3153 hg export -r "outgoing()" -o "%n-%m.patch"
3154 hg export -r "outgoing()" -o "%n-%m.patch"
3154
3155
3155 Returns 0 on success.
3156 Returns 0 on success.
3156 """
3157 """
3157 changesets += tuple(opts.get('rev', []))
3158 changesets += tuple(opts.get('rev', []))
3158 if not changesets:
3159 if not changesets:
3159 changesets = ['.']
3160 changesets = ['.']
3160 revs = scmutil.revrange(repo, changesets)
3161 revs = scmutil.revrange(repo, changesets)
3161 if not revs:
3162 if not revs:
3162 raise util.Abort(_("export requires at least one changeset"))
3163 raise util.Abort(_("export requires at least one changeset"))
3163 if len(revs) > 1:
3164 if len(revs) > 1:
3164 ui.note(_('exporting patches:\n'))
3165 ui.note(_('exporting patches:\n'))
3165 else:
3166 else:
3166 ui.note(_('exporting patch:\n'))
3167 ui.note(_('exporting patch:\n'))
3167 cmdutil.export(repo, revs, template=opts.get('output'),
3168 cmdutil.export(repo, revs, template=opts.get('output'),
3168 switch_parent=opts.get('switch_parent'),
3169 switch_parent=opts.get('switch_parent'),
3169 opts=patch.diffopts(ui, opts))
3170 opts=patch.diffopts(ui, opts))
3170
3171
3171 @command('files',
3172 @command('files',
3172 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3173 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3173 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3174 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3174 ] + walkopts + formatteropts,
3175 ] + walkopts + formatteropts,
3175 _('[OPTION]... [PATTERN]...'))
3176 _('[OPTION]... [PATTERN]...'))
3176 def files(ui, repo, *pats, **opts):
3177 def files(ui, repo, *pats, **opts):
3177 """list tracked files
3178 """list tracked files
3178
3179
3179 Print files under Mercurial control in the working directory or
3180 Print files under Mercurial control in the working directory or
3180 specified revision whose names match the given patterns (excluding
3181 specified revision whose names match the given patterns (excluding
3181 removed files).
3182 removed files).
3182
3183
3183 If no patterns are given to match, this command prints the names
3184 If no patterns are given to match, this command prints the names
3184 of all files under Mercurial control in the working copy.
3185 of all files under Mercurial control in the working copy.
3185
3186
3186 .. container:: verbose
3187 .. container:: verbose
3187
3188
3188 Examples:
3189 Examples:
3189
3190
3190 - list all files under the current directory::
3191 - list all files under the current directory::
3191
3192
3192 hg files .
3193 hg files .
3193
3194
3194 - shows sizes and flags for current revision::
3195 - shows sizes and flags for current revision::
3195
3196
3196 hg files -vr .
3197 hg files -vr .
3197
3198
3198 - list all files named README::
3199 - list all files named README::
3199
3200
3200 hg files -I "**/README"
3201 hg files -I "**/README"
3201
3202
3202 - list all binary files::
3203 - list all binary files::
3203
3204
3204 hg files "set:binary()"
3205 hg files "set:binary()"
3205
3206
3206 - find files containing a regular expression:
3207 - find files containing a regular expression:
3207
3208
3208 hg files "set:grep('bob')"
3209 hg files "set:grep('bob')"
3209
3210
3210 - search tracked file contents with xargs and grep::
3211 - search tracked file contents with xargs and grep::
3211
3212
3212 hg files -0 | xargs -0 grep foo
3213 hg files -0 | xargs -0 grep foo
3213
3214
3214 See :hg:'help pattern' and :hg:'help revsets' for more information
3215 See :hg:'help pattern' and :hg:'help revsets' for more information
3215 on specifying file patterns.
3216 on specifying file patterns.
3216
3217
3217 Returns 0 if a match is found, 1 otherwise.
3218 Returns 0 if a match is found, 1 otherwise.
3218
3219
3219 """
3220 """
3220 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3221 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3221 rev = ctx.rev()
3222 rev = ctx.rev()
3222 ret = 1
3223 ret = 1
3223
3224
3224 end = '\n'
3225 end = '\n'
3225 if opts.get('print0'):
3226 if opts.get('print0'):
3226 end = '\0'
3227 end = '\0'
3227 fm = ui.formatter('files', opts)
3228 fm = ui.formatter('files', opts)
3228 fmt = '%s' + end
3229 fmt = '%s' + end
3229
3230
3230 m = scmutil.match(ctx, pats, opts)
3231 m = scmutil.match(ctx, pats, opts)
3231 ds = repo.dirstate
3232 ds = repo.dirstate
3232 for f in ctx.matches(m):
3233 for f in ctx.matches(m):
3233 if rev is None and ds[f] == 'r':
3234 if rev is None and ds[f] == 'r':
3234 continue
3235 continue
3235 fm.startitem()
3236 fm.startitem()
3236 if ui.verbose:
3237 if ui.verbose:
3237 fc = ctx[f]
3238 fc = ctx[f]
3238 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
3239 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
3239 fm.data(abspath=f)
3240 fm.data(abspath=f)
3240 fm.write('path', fmt, m.rel(f))
3241 fm.write('path', fmt, m.rel(f))
3241 ret = 0
3242 ret = 0
3242
3243
3243 fm.end()
3244 fm.end()
3244
3245
3245 return ret
3246 return ret
3246
3247
3247 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
3248 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
3248 def forget(ui, repo, *pats, **opts):
3249 def forget(ui, repo, *pats, **opts):
3249 """forget the specified files on the next commit
3250 """forget the specified files on the next commit
3250
3251
3251 Mark the specified files so they will no longer be tracked
3252 Mark the specified files so they will no longer be tracked
3252 after the next commit.
3253 after the next commit.
3253
3254
3254 This only removes files from the current branch, not from the
3255 This only removes files from the current branch, not from the
3255 entire project history, and it does not delete them from the
3256 entire project history, and it does not delete them from the
3256 working directory.
3257 working directory.
3257
3258
3258 To undo a forget before the next commit, see :hg:`add`.
3259 To undo a forget before the next commit, see :hg:`add`.
3259
3260
3260 .. container:: verbose
3261 .. container:: verbose
3261
3262
3262 Examples:
3263 Examples:
3263
3264
3264 - forget newly-added binary files::
3265 - forget newly-added binary files::
3265
3266
3266 hg forget "set:added() and binary()"
3267 hg forget "set:added() and binary()"
3267
3268
3268 - forget files that would be excluded by .hgignore::
3269 - forget files that would be excluded by .hgignore::
3269
3270
3270 hg forget "set:hgignore()"
3271 hg forget "set:hgignore()"
3271
3272
3272 Returns 0 on success.
3273 Returns 0 on success.
3273 """
3274 """
3274
3275
3275 if not pats:
3276 if not pats:
3276 raise util.Abort(_('no files specified'))
3277 raise util.Abort(_('no files specified'))
3277
3278
3278 m = scmutil.match(repo[None], pats, opts)
3279 m = scmutil.match(repo[None], pats, opts)
3279 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
3280 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
3280 return rejected and 1 or 0
3281 return rejected and 1 or 0
3281
3282
3282 @command(
3283 @command(
3283 'graft',
3284 'graft',
3284 [('r', 'rev', [], _('revisions to graft'), _('REV')),
3285 [('r', 'rev', [], _('revisions to graft'), _('REV')),
3285 ('c', 'continue', False, _('resume interrupted graft')),
3286 ('c', 'continue', False, _('resume interrupted graft')),
3286 ('e', 'edit', False, _('invoke editor on commit messages')),
3287 ('e', 'edit', False, _('invoke editor on commit messages')),
3287 ('', 'log', None, _('append graft info to log message')),
3288 ('', 'log', None, _('append graft info to log message')),
3288 ('f', 'force', False, _('force graft')),
3289 ('f', 'force', False, _('force graft')),
3289 ('D', 'currentdate', False,
3290 ('D', 'currentdate', False,
3290 _('record the current date as commit date')),
3291 _('record the current date as commit date')),
3291 ('U', 'currentuser', False,
3292 ('U', 'currentuser', False,
3292 _('record the current user as committer'), _('DATE'))]
3293 _('record the current user as committer'), _('DATE'))]
3293 + commitopts2 + mergetoolopts + dryrunopts,
3294 + commitopts2 + mergetoolopts + dryrunopts,
3294 _('[OPTION]... [-r] REV...'))
3295 _('[OPTION]... [-r] REV...'))
3295 def graft(ui, repo, *revs, **opts):
3296 def graft(ui, repo, *revs, **opts):
3296 '''copy changes from other branches onto the current branch
3297 '''copy changes from other branches onto the current branch
3297
3298
3298 This command uses Mercurial's merge logic to copy individual
3299 This command uses Mercurial's merge logic to copy individual
3299 changes from other branches without merging branches in the
3300 changes from other branches without merging branches in the
3300 history graph. This is sometimes known as 'backporting' or
3301 history graph. This is sometimes known as 'backporting' or
3301 'cherry-picking'. By default, graft will copy user, date, and
3302 'cherry-picking'. By default, graft will copy user, date, and
3302 description from the source changesets.
3303 description from the source changesets.
3303
3304
3304 Changesets that are ancestors of the current revision, that have
3305 Changesets that are ancestors of the current revision, that have
3305 already been grafted, or that are merges will be skipped.
3306 already been grafted, or that are merges will be skipped.
3306
3307
3307 If --log is specified, log messages will have a comment appended
3308 If --log is specified, log messages will have a comment appended
3308 of the form::
3309 of the form::
3309
3310
3310 (grafted from CHANGESETHASH)
3311 (grafted from CHANGESETHASH)
3311
3312
3312 If --force is specified, revisions will be grafted even if they
3313 If --force is specified, revisions will be grafted even if they
3313 are already ancestors of or have been grafted to the destination.
3314 are already ancestors of or have been grafted to the destination.
3314 This is useful when the revisions have since been backed out.
3315 This is useful when the revisions have since been backed out.
3315
3316
3316 If a graft merge results in conflicts, the graft process is
3317 If a graft merge results in conflicts, the graft process is
3317 interrupted so that the current merge can be manually resolved.
3318 interrupted so that the current merge can be manually resolved.
3318 Once all conflicts are addressed, the graft process can be
3319 Once all conflicts are addressed, the graft process can be
3319 continued with the -c/--continue option.
3320 continued with the -c/--continue option.
3320
3321
3321 .. note::
3322 .. note::
3322
3323
3323 The -c/--continue option does not reapply earlier options, except
3324 The -c/--continue option does not reapply earlier options, except
3324 for --force.
3325 for --force.
3325
3326
3326 .. container:: verbose
3327 .. container:: verbose
3327
3328
3328 Examples:
3329 Examples:
3329
3330
3330 - copy a single change to the stable branch and edit its description::
3331 - copy a single change to the stable branch and edit its description::
3331
3332
3332 hg update stable
3333 hg update stable
3333 hg graft --edit 9393
3334 hg graft --edit 9393
3334
3335
3335 - graft a range of changesets with one exception, updating dates::
3336 - graft a range of changesets with one exception, updating dates::
3336
3337
3337 hg graft -D "2085::2093 and not 2091"
3338 hg graft -D "2085::2093 and not 2091"
3338
3339
3339 - continue a graft after resolving conflicts::
3340 - continue a graft after resolving conflicts::
3340
3341
3341 hg graft -c
3342 hg graft -c
3342
3343
3343 - show the source of a grafted changeset::
3344 - show the source of a grafted changeset::
3344
3345
3345 hg log --debug -r .
3346 hg log --debug -r .
3346
3347
3347 See :hg:`help revisions` and :hg:`help revsets` for more about
3348 See :hg:`help revisions` and :hg:`help revsets` for more about
3348 specifying revisions.
3349 specifying revisions.
3349
3350
3350 Returns 0 on successful completion.
3351 Returns 0 on successful completion.
3351 '''
3352 '''
3352
3353
3353 revs = list(revs)
3354 revs = list(revs)
3354 revs.extend(opts['rev'])
3355 revs.extend(opts['rev'])
3355
3356
3356 if not opts.get('user') and opts.get('currentuser'):
3357 if not opts.get('user') and opts.get('currentuser'):
3357 opts['user'] = ui.username()
3358 opts['user'] = ui.username()
3358 if not opts.get('date') and opts.get('currentdate'):
3359 if not opts.get('date') and opts.get('currentdate'):
3359 opts['date'] = "%d %d" % util.makedate()
3360 opts['date'] = "%d %d" % util.makedate()
3360
3361
3361 editor = cmdutil.getcommiteditor(editform='graft', **opts)
3362 editor = cmdutil.getcommiteditor(editform='graft', **opts)
3362
3363
3363 cont = False
3364 cont = False
3364 if opts['continue']:
3365 if opts['continue']:
3365 cont = True
3366 cont = True
3366 if revs:
3367 if revs:
3367 raise util.Abort(_("can't specify --continue and revisions"))
3368 raise util.Abort(_("can't specify --continue and revisions"))
3368 # read in unfinished revisions
3369 # read in unfinished revisions
3369 try:
3370 try:
3370 nodes = repo.opener.read('graftstate').splitlines()
3371 nodes = repo.opener.read('graftstate').splitlines()
3371 revs = [repo[node].rev() for node in nodes]
3372 revs = [repo[node].rev() for node in nodes]
3372 except IOError, inst:
3373 except IOError, inst:
3373 if inst.errno != errno.ENOENT:
3374 if inst.errno != errno.ENOENT:
3374 raise
3375 raise
3375 raise util.Abort(_("no graft state found, can't continue"))
3376 raise util.Abort(_("no graft state found, can't continue"))
3376 else:
3377 else:
3377 cmdutil.checkunfinished(repo)
3378 cmdutil.checkunfinished(repo)
3378 cmdutil.bailifchanged(repo)
3379 cmdutil.bailifchanged(repo)
3379 if not revs:
3380 if not revs:
3380 raise util.Abort(_('no revisions specified'))
3381 raise util.Abort(_('no revisions specified'))
3381 revs = scmutil.revrange(repo, revs)
3382 revs = scmutil.revrange(repo, revs)
3382
3383
3383 skipped = set()
3384 skipped = set()
3384 # check for merges
3385 # check for merges
3385 for rev in repo.revs('%ld and merge()', revs):
3386 for rev in repo.revs('%ld and merge()', revs):
3386 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
3387 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
3387 skipped.add(rev)
3388 skipped.add(rev)
3388 revs = [r for r in revs if r not in skipped]
3389 revs = [r for r in revs if r not in skipped]
3389 if not revs:
3390 if not revs:
3390 return -1
3391 return -1
3391
3392
3392 # Don't check in the --continue case, in effect retaining --force across
3393 # Don't check in the --continue case, in effect retaining --force across
3393 # --continues. That's because without --force, any revisions we decided to
3394 # --continues. That's because without --force, any revisions we decided to
3394 # skip would have been filtered out here, so they wouldn't have made their
3395 # skip would have been filtered out here, so they wouldn't have made their
3395 # way to the graftstate. With --force, any revisions we would have otherwise
3396 # way to the graftstate. With --force, any revisions we would have otherwise
3396 # skipped would not have been filtered out, and if they hadn't been applied
3397 # skipped would not have been filtered out, and if they hadn't been applied
3397 # already, they'd have been in the graftstate.
3398 # already, they'd have been in the graftstate.
3398 if not (cont or opts.get('force')):
3399 if not (cont or opts.get('force')):
3399 # check for ancestors of dest branch
3400 # check for ancestors of dest branch
3400 crev = repo['.'].rev()
3401 crev = repo['.'].rev()
3401 ancestors = repo.changelog.ancestors([crev], inclusive=True)
3402 ancestors = repo.changelog.ancestors([crev], inclusive=True)
3402 # Cannot use x.remove(y) on smart set, this has to be a list.
3403 # Cannot use x.remove(y) on smart set, this has to be a list.
3403 # XXX make this lazy in the future
3404 # XXX make this lazy in the future
3404 revs = list(revs)
3405 revs = list(revs)
3405 # don't mutate while iterating, create a copy
3406 # don't mutate while iterating, create a copy
3406 for rev in list(revs):
3407 for rev in list(revs):
3407 if rev in ancestors:
3408 if rev in ancestors:
3408 ui.warn(_('skipping ancestor revision %s\n') % rev)
3409 ui.warn(_('skipping ancestor revision %s\n') % rev)
3409 # XXX remove on list is slow
3410 # XXX remove on list is slow
3410 revs.remove(rev)
3411 revs.remove(rev)
3411 if not revs:
3412 if not revs:
3412 return -1
3413 return -1
3413
3414
3414 # analyze revs for earlier grafts
3415 # analyze revs for earlier grafts
3415 ids = {}
3416 ids = {}
3416 for ctx in repo.set("%ld", revs):
3417 for ctx in repo.set("%ld", revs):
3417 ids[ctx.hex()] = ctx.rev()
3418 ids[ctx.hex()] = ctx.rev()
3418 n = ctx.extra().get('source')
3419 n = ctx.extra().get('source')
3419 if n:
3420 if n:
3420 ids[n] = ctx.rev()
3421 ids[n] = ctx.rev()
3421
3422
3422 # check ancestors for earlier grafts
3423 # check ancestors for earlier grafts
3423 ui.debug('scanning for duplicate grafts\n')
3424 ui.debug('scanning for duplicate grafts\n')
3424
3425
3425 for rev in repo.changelog.findmissingrevs(revs, [crev]):
3426 for rev in repo.changelog.findmissingrevs(revs, [crev]):
3426 ctx = repo[rev]
3427 ctx = repo[rev]
3427 n = ctx.extra().get('source')
3428 n = ctx.extra().get('source')
3428 if n in ids:
3429 if n in ids:
3429 try:
3430 try:
3430 r = repo[n].rev()
3431 r = repo[n].rev()
3431 except error.RepoLookupError:
3432 except error.RepoLookupError:
3432 r = None
3433 r = None
3433 if r in revs:
3434 if r in revs:
3434 ui.warn(_('skipping revision %s (already grafted to %s)\n')
3435 ui.warn(_('skipping revision %s (already grafted to %s)\n')
3435 % (r, rev))
3436 % (r, rev))
3436 revs.remove(r)
3437 revs.remove(r)
3437 elif ids[n] in revs:
3438 elif ids[n] in revs:
3438 if r is None:
3439 if r is None:
3439 ui.warn(_('skipping already grafted revision %s '
3440 ui.warn(_('skipping already grafted revision %s '
3440 '(%s also has unknown origin %s)\n')
3441 '(%s also has unknown origin %s)\n')
3441 % (ids[n], rev, n))
3442 % (ids[n], rev, n))
3442 else:
3443 else:
3443 ui.warn(_('skipping already grafted revision %s '
3444 ui.warn(_('skipping already grafted revision %s '
3444 '(%s also has origin %d)\n')
3445 '(%s also has origin %d)\n')
3445 % (ids[n], rev, r))
3446 % (ids[n], rev, r))
3446 revs.remove(ids[n])
3447 revs.remove(ids[n])
3447 elif ctx.hex() in ids:
3448 elif ctx.hex() in ids:
3448 r = ids[ctx.hex()]
3449 r = ids[ctx.hex()]
3449 ui.warn(_('skipping already grafted revision %s '
3450 ui.warn(_('skipping already grafted revision %s '
3450 '(was grafted from %d)\n') % (r, rev))
3451 '(was grafted from %d)\n') % (r, rev))
3451 revs.remove(r)
3452 revs.remove(r)
3452 if not revs:
3453 if not revs:
3453 return -1
3454 return -1
3454
3455
3455 wlock = repo.wlock()
3456 wlock = repo.wlock()
3456 try:
3457 try:
3457 for pos, ctx in enumerate(repo.set("%ld", revs)):
3458 for pos, ctx in enumerate(repo.set("%ld", revs)):
3458
3459
3459 ui.status(_('grafting revision %s\n') % ctx.rev())
3460 ui.status(_('grafting revision %s\n') % ctx.rev())
3460 if opts.get('dry_run'):
3461 if opts.get('dry_run'):
3461 continue
3462 continue
3462
3463
3463 source = ctx.extra().get('source')
3464 source = ctx.extra().get('source')
3464 if not source:
3465 if not source:
3465 source = ctx.hex()
3466 source = ctx.hex()
3466 extra = {'source': source}
3467 extra = {'source': source}
3467 user = ctx.user()
3468 user = ctx.user()
3468 if opts.get('user'):
3469 if opts.get('user'):
3469 user = opts['user']
3470 user = opts['user']
3470 date = ctx.date()
3471 date = ctx.date()
3471 if opts.get('date'):
3472 if opts.get('date'):
3472 date = opts['date']
3473 date = opts['date']
3473 message = ctx.description()
3474 message = ctx.description()
3474 if opts.get('log'):
3475 if opts.get('log'):
3475 message += '\n(grafted from %s)' % ctx.hex()
3476 message += '\n(grafted from %s)' % ctx.hex()
3476
3477
3477 # we don't merge the first commit when continuing
3478 # we don't merge the first commit when continuing
3478 if not cont:
3479 if not cont:
3479 # perform the graft merge with p1(rev) as 'ancestor'
3480 # perform the graft merge with p1(rev) as 'ancestor'
3480 try:
3481 try:
3481 # ui.forcemerge is an internal variable, do not document
3482 # ui.forcemerge is an internal variable, do not document
3482 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
3483 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
3483 'graft')
3484 'graft')
3484 stats = mergemod.graft(repo, ctx, ctx.p1(),
3485 stats = mergemod.graft(repo, ctx, ctx.p1(),
3485 ['local', 'graft'])
3486 ['local', 'graft'])
3486 finally:
3487 finally:
3487 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
3488 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
3488 # report any conflicts
3489 # report any conflicts
3489 if stats and stats[3] > 0:
3490 if stats and stats[3] > 0:
3490 # write out state for --continue
3491 # write out state for --continue
3491 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
3492 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
3492 repo.opener.write('graftstate', ''.join(nodelines))
3493 repo.opener.write('graftstate', ''.join(nodelines))
3493 raise util.Abort(
3494 raise util.Abort(
3494 _("unresolved conflicts, can't continue"),
3495 _("unresolved conflicts, can't continue"),
3495 hint=_('use hg resolve and hg graft --continue'))
3496 hint=_('use hg resolve and hg graft --continue'))
3496 else:
3497 else:
3497 cont = False
3498 cont = False
3498
3499
3499 # commit
3500 # commit
3500 node = repo.commit(text=message, user=user,
3501 node = repo.commit(text=message, user=user,
3501 date=date, extra=extra, editor=editor)
3502 date=date, extra=extra, editor=editor)
3502 if node is None:
3503 if node is None:
3503 ui.status(_('graft for revision %s is empty\n') % ctx.rev())
3504 ui.status(_('graft for revision %s is empty\n') % ctx.rev())
3504 finally:
3505 finally:
3505 wlock.release()
3506 wlock.release()
3506
3507
3507 # remove state when we complete successfully
3508 # remove state when we complete successfully
3508 if not opts.get('dry_run'):
3509 if not opts.get('dry_run'):
3509 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
3510 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
3510
3511
3511 return 0
3512 return 0
3512
3513
3513 @command('grep',
3514 @command('grep',
3514 [('0', 'print0', None, _('end fields with NUL')),
3515 [('0', 'print0', None, _('end fields with NUL')),
3515 ('', 'all', None, _('print all revisions that match')),
3516 ('', 'all', None, _('print all revisions that match')),
3516 ('a', 'text', None, _('treat all files as text')),
3517 ('a', 'text', None, _('treat all files as text')),
3517 ('f', 'follow', None,
3518 ('f', 'follow', None,
3518 _('follow changeset history,'
3519 _('follow changeset history,'
3519 ' or file history across copies and renames')),
3520 ' or file history across copies and renames')),
3520 ('i', 'ignore-case', None, _('ignore case when matching')),
3521 ('i', 'ignore-case', None, _('ignore case when matching')),
3521 ('l', 'files-with-matches', None,
3522 ('l', 'files-with-matches', None,
3522 _('print only filenames and revisions that match')),
3523 _('print only filenames and revisions that match')),
3523 ('n', 'line-number', None, _('print matching line numbers')),
3524 ('n', 'line-number', None, _('print matching line numbers')),
3524 ('r', 'rev', [],
3525 ('r', 'rev', [],
3525 _('only search files changed within revision range'), _('REV')),
3526 _('only search files changed within revision range'), _('REV')),
3526 ('u', 'user', None, _('list the author (long with -v)')),
3527 ('u', 'user', None, _('list the author (long with -v)')),
3527 ('d', 'date', None, _('list the date (short with -q)')),
3528 ('d', 'date', None, _('list the date (short with -q)')),
3528 ] + walkopts,
3529 ] + walkopts,
3529 _('[OPTION]... PATTERN [FILE]...'),
3530 _('[OPTION]... PATTERN [FILE]...'),
3530 inferrepo=True)
3531 inferrepo=True)
3531 def grep(ui, repo, pattern, *pats, **opts):
3532 def grep(ui, repo, pattern, *pats, **opts):
3532 """search for a pattern in specified files and revisions
3533 """search for a pattern in specified files and revisions
3533
3534
3534 Search revisions of files for a regular expression.
3535 Search revisions of files for a regular expression.
3535
3536
3536 This command behaves differently than Unix grep. It only accepts
3537 This command behaves differently than Unix grep. It only accepts
3537 Python/Perl regexps. It searches repository history, not the
3538 Python/Perl regexps. It searches repository history, not the
3538 working directory. It always prints the revision number in which a
3539 working directory. It always prints the revision number in which a
3539 match appears.
3540 match appears.
3540
3541
3541 By default, grep only prints output for the first revision of a
3542 By default, grep only prints output for the first revision of a
3542 file in which it finds a match. To get it to print every revision
3543 file in which it finds a match. To get it to print every revision
3543 that contains a change in match status ("-" for a match that
3544 that contains a change in match status ("-" for a match that
3544 becomes a non-match, or "+" for a non-match that becomes a match),
3545 becomes a non-match, or "+" for a non-match that becomes a match),
3545 use the --all flag.
3546 use the --all flag.
3546
3547
3547 Returns 0 if a match is found, 1 otherwise.
3548 Returns 0 if a match is found, 1 otherwise.
3548 """
3549 """
3549 reflags = re.M
3550 reflags = re.M
3550 if opts.get('ignore_case'):
3551 if opts.get('ignore_case'):
3551 reflags |= re.I
3552 reflags |= re.I
3552 try:
3553 try:
3553 regexp = util.re.compile(pattern, reflags)
3554 regexp = util.re.compile(pattern, reflags)
3554 except re.error, inst:
3555 except re.error, inst:
3555 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
3556 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
3556 return 1
3557 return 1
3557 sep, eol = ':', '\n'
3558 sep, eol = ':', '\n'
3558 if opts.get('print0'):
3559 if opts.get('print0'):
3559 sep = eol = '\0'
3560 sep = eol = '\0'
3560
3561
3561 getfile = util.lrucachefunc(repo.file)
3562 getfile = util.lrucachefunc(repo.file)
3562
3563
3563 def matchlines(body):
3564 def matchlines(body):
3564 begin = 0
3565 begin = 0
3565 linenum = 0
3566 linenum = 0
3566 while begin < len(body):
3567 while begin < len(body):
3567 match = regexp.search(body, begin)
3568 match = regexp.search(body, begin)
3568 if not match:
3569 if not match:
3569 break
3570 break
3570 mstart, mend = match.span()
3571 mstart, mend = match.span()
3571 linenum += body.count('\n', begin, mstart) + 1
3572 linenum += body.count('\n', begin, mstart) + 1
3572 lstart = body.rfind('\n', begin, mstart) + 1 or begin
3573 lstart = body.rfind('\n', begin, mstart) + 1 or begin
3573 begin = body.find('\n', mend) + 1 or len(body) + 1
3574 begin = body.find('\n', mend) + 1 or len(body) + 1
3574 lend = begin - 1
3575 lend = begin - 1
3575 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3576 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3576
3577
3577 class linestate(object):
3578 class linestate(object):
3578 def __init__(self, line, linenum, colstart, colend):
3579 def __init__(self, line, linenum, colstart, colend):
3579 self.line = line
3580 self.line = line
3580 self.linenum = linenum
3581 self.linenum = linenum
3581 self.colstart = colstart
3582 self.colstart = colstart
3582 self.colend = colend
3583 self.colend = colend
3583
3584
3584 def __hash__(self):
3585 def __hash__(self):
3585 return hash((self.linenum, self.line))
3586 return hash((self.linenum, self.line))
3586
3587
3587 def __eq__(self, other):
3588 def __eq__(self, other):
3588 return self.line == other.line
3589 return self.line == other.line
3589
3590
3590 def __iter__(self):
3591 def __iter__(self):
3591 yield (self.line[:self.colstart], '')
3592 yield (self.line[:self.colstart], '')
3592 yield (self.line[self.colstart:self.colend], 'grep.match')
3593 yield (self.line[self.colstart:self.colend], 'grep.match')
3593 rest = self.line[self.colend:]
3594 rest = self.line[self.colend:]
3594 while rest != '':
3595 while rest != '':
3595 match = regexp.search(rest)
3596 match = regexp.search(rest)
3596 if not match:
3597 if not match:
3597 yield (rest, '')
3598 yield (rest, '')
3598 break
3599 break
3599 mstart, mend = match.span()
3600 mstart, mend = match.span()
3600 yield (rest[:mstart], '')
3601 yield (rest[:mstart], '')
3601 yield (rest[mstart:mend], 'grep.match')
3602 yield (rest[mstart:mend], 'grep.match')
3602 rest = rest[mend:]
3603 rest = rest[mend:]
3603
3604
3604 matches = {}
3605 matches = {}
3605 copies = {}
3606 copies = {}
3606 def grepbody(fn, rev, body):
3607 def grepbody(fn, rev, body):
3607 matches[rev].setdefault(fn, [])
3608 matches[rev].setdefault(fn, [])
3608 m = matches[rev][fn]
3609 m = matches[rev][fn]
3609 for lnum, cstart, cend, line in matchlines(body):
3610 for lnum, cstart, cend, line in matchlines(body):
3610 s = linestate(line, lnum, cstart, cend)
3611 s = linestate(line, lnum, cstart, cend)
3611 m.append(s)
3612 m.append(s)
3612
3613
3613 def difflinestates(a, b):
3614 def difflinestates(a, b):
3614 sm = difflib.SequenceMatcher(None, a, b)
3615 sm = difflib.SequenceMatcher(None, a, b)
3615 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3616 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3616 if tag == 'insert':
3617 if tag == 'insert':
3617 for i in xrange(blo, bhi):
3618 for i in xrange(blo, bhi):
3618 yield ('+', b[i])
3619 yield ('+', b[i])
3619 elif tag == 'delete':
3620 elif tag == 'delete':
3620 for i in xrange(alo, ahi):
3621 for i in xrange(alo, ahi):
3621 yield ('-', a[i])
3622 yield ('-', a[i])
3622 elif tag == 'replace':
3623 elif tag == 'replace':
3623 for i in xrange(alo, ahi):
3624 for i in xrange(alo, ahi):
3624 yield ('-', a[i])
3625 yield ('-', a[i])
3625 for i in xrange(blo, bhi):
3626 for i in xrange(blo, bhi):
3626 yield ('+', b[i])
3627 yield ('+', b[i])
3627
3628
3628 def display(fn, ctx, pstates, states):
3629 def display(fn, ctx, pstates, states):
3629 rev = ctx.rev()
3630 rev = ctx.rev()
3630 datefunc = ui.quiet and util.shortdate or util.datestr
3631 datefunc = ui.quiet and util.shortdate or util.datestr
3631 found = False
3632 found = False
3632 @util.cachefunc
3633 @util.cachefunc
3633 def binary():
3634 def binary():
3634 flog = getfile(fn)
3635 flog = getfile(fn)
3635 return util.binary(flog.read(ctx.filenode(fn)))
3636 return util.binary(flog.read(ctx.filenode(fn)))
3636
3637
3637 if opts.get('all'):
3638 if opts.get('all'):
3638 iter = difflinestates(pstates, states)
3639 iter = difflinestates(pstates, states)
3639 else:
3640 else:
3640 iter = [('', l) for l in states]
3641 iter = [('', l) for l in states]
3641 for change, l in iter:
3642 for change, l in iter:
3642 cols = [(fn, 'grep.filename'), (str(rev), 'grep.rev')]
3643 cols = [(fn, 'grep.filename'), (str(rev), 'grep.rev')]
3643
3644
3644 if opts.get('line_number'):
3645 if opts.get('line_number'):
3645 cols.append((str(l.linenum), 'grep.linenumber'))
3646 cols.append((str(l.linenum), 'grep.linenumber'))
3646 if opts.get('all'):
3647 if opts.get('all'):
3647 cols.append((change, 'grep.change'))
3648 cols.append((change, 'grep.change'))
3648 if opts.get('user'):
3649 if opts.get('user'):
3649 cols.append((ui.shortuser(ctx.user()), 'grep.user'))
3650 cols.append((ui.shortuser(ctx.user()), 'grep.user'))
3650 if opts.get('date'):
3651 if opts.get('date'):
3651 cols.append((datefunc(ctx.date()), 'grep.date'))
3652 cols.append((datefunc(ctx.date()), 'grep.date'))
3652 for col, label in cols[:-1]:
3653 for col, label in cols[:-1]:
3653 ui.write(col, label=label)
3654 ui.write(col, label=label)
3654 ui.write(sep, label='grep.sep')
3655 ui.write(sep, label='grep.sep')
3655 ui.write(cols[-1][0], label=cols[-1][1])
3656 ui.write(cols[-1][0], label=cols[-1][1])
3656 if not opts.get('files_with_matches'):
3657 if not opts.get('files_with_matches'):
3657 ui.write(sep, label='grep.sep')
3658 ui.write(sep, label='grep.sep')
3658 if not opts.get('text') and binary():
3659 if not opts.get('text') and binary():
3659 ui.write(" Binary file matches")
3660 ui.write(" Binary file matches")
3660 else:
3661 else:
3661 for s, label in l:
3662 for s, label in l:
3662 ui.write(s, label=label)
3663 ui.write(s, label=label)
3663 ui.write(eol)
3664 ui.write(eol)
3664 found = True
3665 found = True
3665 if opts.get('files_with_matches'):
3666 if opts.get('files_with_matches'):
3666 break
3667 break
3667 return found
3668 return found
3668
3669
3669 skip = {}
3670 skip = {}
3670 revfiles = {}
3671 revfiles = {}
3671 matchfn = scmutil.match(repo[None], pats, opts)
3672 matchfn = scmutil.match(repo[None], pats, opts)
3672 found = False
3673 found = False
3673 follow = opts.get('follow')
3674 follow = opts.get('follow')
3674
3675
3675 def prep(ctx, fns):
3676 def prep(ctx, fns):
3676 rev = ctx.rev()
3677 rev = ctx.rev()
3677 pctx = ctx.p1()
3678 pctx = ctx.p1()
3678 parent = pctx.rev()
3679 parent = pctx.rev()
3679 matches.setdefault(rev, {})
3680 matches.setdefault(rev, {})
3680 matches.setdefault(parent, {})
3681 matches.setdefault(parent, {})
3681 files = revfiles.setdefault(rev, [])
3682 files = revfiles.setdefault(rev, [])
3682 for fn in fns:
3683 for fn in fns:
3683 flog = getfile(fn)
3684 flog = getfile(fn)
3684 try:
3685 try:
3685 fnode = ctx.filenode(fn)
3686 fnode = ctx.filenode(fn)
3686 except error.LookupError:
3687 except error.LookupError:
3687 continue
3688 continue
3688
3689
3689 copied = flog.renamed(fnode)
3690 copied = flog.renamed(fnode)
3690 copy = follow and copied and copied[0]
3691 copy = follow and copied and copied[0]
3691 if copy:
3692 if copy:
3692 copies.setdefault(rev, {})[fn] = copy
3693 copies.setdefault(rev, {})[fn] = copy
3693 if fn in skip:
3694 if fn in skip:
3694 if copy:
3695 if copy:
3695 skip[copy] = True
3696 skip[copy] = True
3696 continue
3697 continue
3697 files.append(fn)
3698 files.append(fn)
3698
3699
3699 if fn not in matches[rev]:
3700 if fn not in matches[rev]:
3700 grepbody(fn, rev, flog.read(fnode))
3701 grepbody(fn, rev, flog.read(fnode))
3701
3702
3702 pfn = copy or fn
3703 pfn = copy or fn
3703 if pfn not in matches[parent]:
3704 if pfn not in matches[parent]:
3704 try:
3705 try:
3705 fnode = pctx.filenode(pfn)
3706 fnode = pctx.filenode(pfn)
3706 grepbody(pfn, parent, flog.read(fnode))
3707 grepbody(pfn, parent, flog.read(fnode))
3707 except error.LookupError:
3708 except error.LookupError:
3708 pass
3709 pass
3709
3710
3710 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3711 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3711 rev = ctx.rev()
3712 rev = ctx.rev()
3712 parent = ctx.p1().rev()
3713 parent = ctx.p1().rev()
3713 for fn in sorted(revfiles.get(rev, [])):
3714 for fn in sorted(revfiles.get(rev, [])):
3714 states = matches[rev][fn]
3715 states = matches[rev][fn]
3715 copy = copies.get(rev, {}).get(fn)
3716 copy = copies.get(rev, {}).get(fn)
3716 if fn in skip:
3717 if fn in skip:
3717 if copy:
3718 if copy:
3718 skip[copy] = True
3719 skip[copy] = True
3719 continue
3720 continue
3720 pstates = matches.get(parent, {}).get(copy or fn, [])
3721 pstates = matches.get(parent, {}).get(copy or fn, [])
3721 if pstates or states:
3722 if pstates or states:
3722 r = display(fn, ctx, pstates, states)
3723 r = display(fn, ctx, pstates, states)
3723 found = found or r
3724 found = found or r
3724 if r and not opts.get('all'):
3725 if r and not opts.get('all'):
3725 skip[fn] = True
3726 skip[fn] = True
3726 if copy:
3727 if copy:
3727 skip[copy] = True
3728 skip[copy] = True
3728 del matches[rev]
3729 del matches[rev]
3729 del revfiles[rev]
3730 del revfiles[rev]
3730
3731
3731 return not found
3732 return not found
3732
3733
3733 @command('heads',
3734 @command('heads',
3734 [('r', 'rev', '',
3735 [('r', 'rev', '',
3735 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3736 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3736 ('t', 'topo', False, _('show topological heads only')),
3737 ('t', 'topo', False, _('show topological heads only')),
3737 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3738 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3738 ('c', 'closed', False, _('show normal and closed branch heads')),
3739 ('c', 'closed', False, _('show normal and closed branch heads')),
3739 ] + templateopts,
3740 ] + templateopts,
3740 _('[-ct] [-r STARTREV] [REV]...'))
3741 _('[-ct] [-r STARTREV] [REV]...'))
3741 def heads(ui, repo, *branchrevs, **opts):
3742 def heads(ui, repo, *branchrevs, **opts):
3742 """show branch heads
3743 """show branch heads
3743
3744
3744 With no arguments, show all open branch heads in the repository.
3745 With no arguments, show all open branch heads in the repository.
3745 Branch heads are changesets that have no descendants on the
3746 Branch heads are changesets that have no descendants on the
3746 same branch. They are where development generally takes place and
3747 same branch. They are where development generally takes place and
3747 are the usual targets for update and merge operations.
3748 are the usual targets for update and merge operations.
3748
3749
3749 If one or more REVs are given, only open branch heads on the
3750 If one or more REVs are given, only open branch heads on the
3750 branches associated with the specified changesets are shown. This
3751 branches associated with the specified changesets are shown. This
3751 means that you can use :hg:`heads .` to see the heads on the
3752 means that you can use :hg:`heads .` to see the heads on the
3752 currently checked-out branch.
3753 currently checked-out branch.
3753
3754
3754 If -c/--closed is specified, also show branch heads marked closed
3755 If -c/--closed is specified, also show branch heads marked closed
3755 (see :hg:`commit --close-branch`).
3756 (see :hg:`commit --close-branch`).
3756
3757
3757 If STARTREV is specified, only those heads that are descendants of
3758 If STARTREV is specified, only those heads that are descendants of
3758 STARTREV will be displayed.
3759 STARTREV will be displayed.
3759
3760
3760 If -t/--topo is specified, named branch mechanics will be ignored and only
3761 If -t/--topo is specified, named branch mechanics will be ignored and only
3761 topological heads (changesets with no children) will be shown.
3762 topological heads (changesets with no children) will be shown.
3762
3763
3763 Returns 0 if matching heads are found, 1 if not.
3764 Returns 0 if matching heads are found, 1 if not.
3764 """
3765 """
3765
3766
3766 start = None
3767 start = None
3767 if 'rev' in opts:
3768 if 'rev' in opts:
3768 start = scmutil.revsingle(repo, opts['rev'], None).node()
3769 start = scmutil.revsingle(repo, opts['rev'], None).node()
3769
3770
3770 if opts.get('topo'):
3771 if opts.get('topo'):
3771 heads = [repo[h] for h in repo.heads(start)]
3772 heads = [repo[h] for h in repo.heads(start)]
3772 else:
3773 else:
3773 heads = []
3774 heads = []
3774 for branch in repo.branchmap():
3775 for branch in repo.branchmap():
3775 heads += repo.branchheads(branch, start, opts.get('closed'))
3776 heads += repo.branchheads(branch, start, opts.get('closed'))
3776 heads = [repo[h] for h in heads]
3777 heads = [repo[h] for h in heads]
3777
3778
3778 if branchrevs:
3779 if branchrevs:
3779 branches = set(repo[br].branch() for br in branchrevs)
3780 branches = set(repo[br].branch() for br in branchrevs)
3780 heads = [h for h in heads if h.branch() in branches]
3781 heads = [h for h in heads if h.branch() in branches]
3781
3782
3782 if opts.get('active') and branchrevs:
3783 if opts.get('active') and branchrevs:
3783 dagheads = repo.heads(start)
3784 dagheads = repo.heads(start)
3784 heads = [h for h in heads if h.node() in dagheads]
3785 heads = [h for h in heads if h.node() in dagheads]
3785
3786
3786 if branchrevs:
3787 if branchrevs:
3787 haveheads = set(h.branch() for h in heads)
3788 haveheads = set(h.branch() for h in heads)
3788 if branches - haveheads:
3789 if branches - haveheads:
3789 headless = ', '.join(b for b in branches - haveheads)
3790 headless = ', '.join(b for b in branches - haveheads)
3790 msg = _('no open branch heads found on branches %s')
3791 msg = _('no open branch heads found on branches %s')
3791 if opts.get('rev'):
3792 if opts.get('rev'):
3792 msg += _(' (started at %s)') % opts['rev']
3793 msg += _(' (started at %s)') % opts['rev']
3793 ui.warn((msg + '\n') % headless)
3794 ui.warn((msg + '\n') % headless)
3794
3795
3795 if not heads:
3796 if not heads:
3796 return 1
3797 return 1
3797
3798
3798 heads = sorted(heads, key=lambda x: -x.rev())
3799 heads = sorted(heads, key=lambda x: -x.rev())
3799 displayer = cmdutil.show_changeset(ui, repo, opts)
3800 displayer = cmdutil.show_changeset(ui, repo, opts)
3800 for ctx in heads:
3801 for ctx in heads:
3801 displayer.show(ctx)
3802 displayer.show(ctx)
3802 displayer.close()
3803 displayer.close()
3803
3804
3804 @command('help',
3805 @command('help',
3805 [('e', 'extension', None, _('show only help for extensions')),
3806 [('e', 'extension', None, _('show only help for extensions')),
3806 ('c', 'command', None, _('show only help for commands')),
3807 ('c', 'command', None, _('show only help for commands')),
3807 ('k', 'keyword', '', _('show topics matching keyword')),
3808 ('k', 'keyword', '', _('show topics matching keyword')),
3808 ],
3809 ],
3809 _('[-ec] [TOPIC]'),
3810 _('[-ec] [TOPIC]'),
3810 norepo=True)
3811 norepo=True)
3811 def help_(ui, name=None, **opts):
3812 def help_(ui, name=None, **opts):
3812 """show help for a given topic or a help overview
3813 """show help for a given topic or a help overview
3813
3814
3814 With no arguments, print a list of commands with short help messages.
3815 With no arguments, print a list of commands with short help messages.
3815
3816
3816 Given a topic, extension, or command name, print help for that
3817 Given a topic, extension, or command name, print help for that
3817 topic.
3818 topic.
3818
3819
3819 Returns 0 if successful.
3820 Returns 0 if successful.
3820 """
3821 """
3821
3822
3822 textwidth = min(ui.termwidth(), 80) - 2
3823 textwidth = min(ui.termwidth(), 80) - 2
3823
3824
3824 keep = []
3825 keep = []
3825 if ui.verbose:
3826 if ui.verbose:
3826 keep.append('verbose')
3827 keep.append('verbose')
3827 if sys.platform.startswith('win'):
3828 if sys.platform.startswith('win'):
3828 keep.append('windows')
3829 keep.append('windows')
3829 elif sys.platform == 'OpenVMS':
3830 elif sys.platform == 'OpenVMS':
3830 keep.append('vms')
3831 keep.append('vms')
3831 elif sys.platform == 'plan9':
3832 elif sys.platform == 'plan9':
3832 keep.append('plan9')
3833 keep.append('plan9')
3833 else:
3834 else:
3834 keep.append('unix')
3835 keep.append('unix')
3835 keep.append(sys.platform.lower())
3836 keep.append(sys.platform.lower())
3836
3837
3837 section = None
3838 section = None
3838 if name and '.' in name:
3839 if name and '.' in name:
3839 name, section = name.split('.')
3840 name, section = name.split('.')
3840
3841
3841 text = help.help_(ui, name, **opts)
3842 text = help.help_(ui, name, **opts)
3842
3843
3843 formatted, pruned = minirst.format(text, textwidth, keep=keep,
3844 formatted, pruned = minirst.format(text, textwidth, keep=keep,
3844 section=section)
3845 section=section)
3845 if section and not formatted:
3846 if section and not formatted:
3846 raise util.Abort(_("help section not found"))
3847 raise util.Abort(_("help section not found"))
3847
3848
3848 if 'verbose' in pruned:
3849 if 'verbose' in pruned:
3849 keep.append('omitted')
3850 keep.append('omitted')
3850 else:
3851 else:
3851 keep.append('notomitted')
3852 keep.append('notomitted')
3852 formatted, pruned = minirst.format(text, textwidth, keep=keep,
3853 formatted, pruned = minirst.format(text, textwidth, keep=keep,
3853 section=section)
3854 section=section)
3854 ui.write(formatted)
3855 ui.write(formatted)
3855
3856
3856
3857
3857 @command('identify|id',
3858 @command('identify|id',
3858 [('r', 'rev', '',
3859 [('r', 'rev', '',
3859 _('identify the specified revision'), _('REV')),
3860 _('identify the specified revision'), _('REV')),
3860 ('n', 'num', None, _('show local revision number')),
3861 ('n', 'num', None, _('show local revision number')),
3861 ('i', 'id', None, _('show global revision id')),
3862 ('i', 'id', None, _('show global revision id')),
3862 ('b', 'branch', None, _('show branch')),
3863 ('b', 'branch', None, _('show branch')),
3863 ('t', 'tags', None, _('show tags')),
3864 ('t', 'tags', None, _('show tags')),
3864 ('B', 'bookmarks', None, _('show bookmarks')),
3865 ('B', 'bookmarks', None, _('show bookmarks')),
3865 ] + remoteopts,
3866 ] + remoteopts,
3866 _('[-nibtB] [-r REV] [SOURCE]'),
3867 _('[-nibtB] [-r REV] [SOURCE]'),
3867 optionalrepo=True)
3868 optionalrepo=True)
3868 def identify(ui, repo, source=None, rev=None,
3869 def identify(ui, repo, source=None, rev=None,
3869 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3870 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3870 """identify the working copy or specified revision
3871 """identify the working copy or specified revision
3871
3872
3872 Print a summary identifying the repository state at REV using one or
3873 Print a summary identifying the repository state at REV using one or
3873 two parent hash identifiers, followed by a "+" if the working
3874 two parent hash identifiers, followed by a "+" if the working
3874 directory has uncommitted changes, the branch name (if not default),
3875 directory has uncommitted changes, the branch name (if not default),
3875 a list of tags, and a list of bookmarks.
3876 a list of tags, and a list of bookmarks.
3876
3877
3877 When REV is not given, print a summary of the current state of the
3878 When REV is not given, print a summary of the current state of the
3878 repository.
3879 repository.
3879
3880
3880 Specifying a path to a repository root or Mercurial bundle will
3881 Specifying a path to a repository root or Mercurial bundle will
3881 cause lookup to operate on that repository/bundle.
3882 cause lookup to operate on that repository/bundle.
3882
3883
3883 .. container:: verbose
3884 .. container:: verbose
3884
3885
3885 Examples:
3886 Examples:
3886
3887
3887 - generate a build identifier for the working directory::
3888 - generate a build identifier for the working directory::
3888
3889
3889 hg id --id > build-id.dat
3890 hg id --id > build-id.dat
3890
3891
3891 - find the revision corresponding to a tag::
3892 - find the revision corresponding to a tag::
3892
3893
3893 hg id -n -r 1.3
3894 hg id -n -r 1.3
3894
3895
3895 - check the most recent revision of a remote repository::
3896 - check the most recent revision of a remote repository::
3896
3897
3897 hg id -r tip http://selenic.com/hg/
3898 hg id -r tip http://selenic.com/hg/
3898
3899
3899 Returns 0 if successful.
3900 Returns 0 if successful.
3900 """
3901 """
3901
3902
3902 if not repo and not source:
3903 if not repo and not source:
3903 raise util.Abort(_("there is no Mercurial repository here "
3904 raise util.Abort(_("there is no Mercurial repository here "
3904 "(.hg not found)"))
3905 "(.hg not found)"))
3905
3906
3906 hexfunc = ui.debugflag and hex or short
3907 hexfunc = ui.debugflag and hex or short
3907 default = not (num or id or branch or tags or bookmarks)
3908 default = not (num or id or branch or tags or bookmarks)
3908 output = []
3909 output = []
3909 revs = []
3910 revs = []
3910
3911
3911 if source:
3912 if source:
3912 source, branches = hg.parseurl(ui.expandpath(source))
3913 source, branches = hg.parseurl(ui.expandpath(source))
3913 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3914 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3914 repo = peer.local()
3915 repo = peer.local()
3915 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3916 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3916
3917
3917 if not repo:
3918 if not repo:
3918 if num or branch or tags:
3919 if num or branch or tags:
3919 raise util.Abort(
3920 raise util.Abort(
3920 _("can't query remote revision number, branch, or tags"))
3921 _("can't query remote revision number, branch, or tags"))
3921 if not rev and revs:
3922 if not rev and revs:
3922 rev = revs[0]
3923 rev = revs[0]
3923 if not rev:
3924 if not rev:
3924 rev = "tip"
3925 rev = "tip"
3925
3926
3926 remoterev = peer.lookup(rev)
3927 remoterev = peer.lookup(rev)
3927 if default or id:
3928 if default or id:
3928 output = [hexfunc(remoterev)]
3929 output = [hexfunc(remoterev)]
3929
3930
3930 def getbms():
3931 def getbms():
3931 bms = []
3932 bms = []
3932
3933
3933 if 'bookmarks' in peer.listkeys('namespaces'):
3934 if 'bookmarks' in peer.listkeys('namespaces'):
3934 hexremoterev = hex(remoterev)
3935 hexremoterev = hex(remoterev)
3935 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3936 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3936 if bmr == hexremoterev]
3937 if bmr == hexremoterev]
3937
3938
3938 return sorted(bms)
3939 return sorted(bms)
3939
3940
3940 if bookmarks:
3941 if bookmarks:
3941 output.extend(getbms())
3942 output.extend(getbms())
3942 elif default and not ui.quiet:
3943 elif default and not ui.quiet:
3943 # multiple bookmarks for a single parent separated by '/'
3944 # multiple bookmarks for a single parent separated by '/'
3944 bm = '/'.join(getbms())
3945 bm = '/'.join(getbms())
3945 if bm:
3946 if bm:
3946 output.append(bm)
3947 output.append(bm)
3947 else:
3948 else:
3948 if not rev:
3949 if not rev:
3949 ctx = repo[None]
3950 ctx = repo[None]
3950 parents = ctx.parents()
3951 parents = ctx.parents()
3951 changed = ""
3952 changed = ""
3952 if default or id or num:
3953 if default or id or num:
3953 if (util.any(repo.status())
3954 if (util.any(repo.status())
3954 or util.any(ctx.sub(s).dirty() for s in ctx.substate)):
3955 or util.any(ctx.sub(s).dirty() for s in ctx.substate)):
3955 changed = '+'
3956 changed = '+'
3956 if default or id:
3957 if default or id:
3957 output = ["%s%s" %
3958 output = ["%s%s" %
3958 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3959 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3959 if num:
3960 if num:
3960 output.append("%s%s" %
3961 output.append("%s%s" %
3961 ('+'.join([str(p.rev()) for p in parents]), changed))
3962 ('+'.join([str(p.rev()) for p in parents]), changed))
3962 else:
3963 else:
3963 ctx = scmutil.revsingle(repo, rev)
3964 ctx = scmutil.revsingle(repo, rev)
3964 if default or id:
3965 if default or id:
3965 output = [hexfunc(ctx.node())]
3966 output = [hexfunc(ctx.node())]
3966 if num:
3967 if num:
3967 output.append(str(ctx.rev()))
3968 output.append(str(ctx.rev()))
3968
3969
3969 if default and not ui.quiet:
3970 if default and not ui.quiet:
3970 b = ctx.branch()
3971 b = ctx.branch()
3971 if b != 'default':
3972 if b != 'default':
3972 output.append("(%s)" % b)
3973 output.append("(%s)" % b)
3973
3974
3974 # multiple tags for a single parent separated by '/'
3975 # multiple tags for a single parent separated by '/'
3975 t = '/'.join(ctx.tags())
3976 t = '/'.join(ctx.tags())
3976 if t:
3977 if t:
3977 output.append(t)
3978 output.append(t)
3978
3979
3979 # multiple bookmarks for a single parent separated by '/'
3980 # multiple bookmarks for a single parent separated by '/'
3980 bm = '/'.join(ctx.bookmarks())
3981 bm = '/'.join(ctx.bookmarks())
3981 if bm:
3982 if bm:
3982 output.append(bm)
3983 output.append(bm)
3983 else:
3984 else:
3984 if branch:
3985 if branch:
3985 output.append(ctx.branch())
3986 output.append(ctx.branch())
3986
3987
3987 if tags:
3988 if tags:
3988 output.extend(ctx.tags())
3989 output.extend(ctx.tags())
3989
3990
3990 if bookmarks:
3991 if bookmarks:
3991 output.extend(ctx.bookmarks())
3992 output.extend(ctx.bookmarks())
3992
3993
3993 ui.write("%s\n" % ' '.join(output))
3994 ui.write("%s\n" % ' '.join(output))
3994
3995
3995 @command('import|patch',
3996 @command('import|patch',
3996 [('p', 'strip', 1,
3997 [('p', 'strip', 1,
3997 _('directory strip option for patch. This has the same '
3998 _('directory strip option for patch. This has the same '
3998 'meaning as the corresponding patch option'), _('NUM')),
3999 'meaning as the corresponding patch option'), _('NUM')),
3999 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
4000 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
4000 ('e', 'edit', False, _('invoke editor on commit messages')),
4001 ('e', 'edit', False, _('invoke editor on commit messages')),
4001 ('f', 'force', None,
4002 ('f', 'force', None,
4002 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
4003 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
4003 ('', 'no-commit', None,
4004 ('', 'no-commit', None,
4004 _("don't commit, just update the working directory")),
4005 _("don't commit, just update the working directory")),
4005 ('', 'bypass', None,
4006 ('', 'bypass', None,
4006 _("apply patch without touching the working directory")),
4007 _("apply patch without touching the working directory")),
4007 ('', 'partial', None,
4008 ('', 'partial', None,
4008 _('commit even if some hunks fail')),
4009 _('commit even if some hunks fail')),
4009 ('', 'exact', None,
4010 ('', 'exact', None,
4010 _('apply patch to the nodes from which it was generated')),
4011 _('apply patch to the nodes from which it was generated')),
4011 ('', 'import-branch', None,
4012 ('', 'import-branch', None,
4012 _('use any branch information in patch (implied by --exact)'))] +
4013 _('use any branch information in patch (implied by --exact)'))] +
4013 commitopts + commitopts2 + similarityopts,
4014 commitopts + commitopts2 + similarityopts,
4014 _('[OPTION]... PATCH...'))
4015 _('[OPTION]... PATCH...'))
4015 def import_(ui, repo, patch1=None, *patches, **opts):
4016 def import_(ui, repo, patch1=None, *patches, **opts):
4016 """import an ordered set of patches
4017 """import an ordered set of patches
4017
4018
4018 Import a list of patches and commit them individually (unless
4019 Import a list of patches and commit them individually (unless
4019 --no-commit is specified).
4020 --no-commit is specified).
4020
4021
4021 Because import first applies changes to the working directory,
4022 Because import first applies changes to the working directory,
4022 import will abort if there are outstanding changes.
4023 import will abort if there are outstanding changes.
4023
4024
4024 You can import a patch straight from a mail message. Even patches
4025 You can import a patch straight from a mail message. Even patches
4025 as attachments work (to use the body part, it must have type
4026 as attachments work (to use the body part, it must have type
4026 text/plain or text/x-patch). From and Subject headers of email
4027 text/plain or text/x-patch). From and Subject headers of email
4027 message are used as default committer and commit message. All
4028 message are used as default committer and commit message. All
4028 text/plain body parts before first diff are added to commit
4029 text/plain body parts before first diff are added to commit
4029 message.
4030 message.
4030
4031
4031 If the imported patch was generated by :hg:`export`, user and
4032 If the imported patch was generated by :hg:`export`, user and
4032 description from patch override values from message headers and
4033 description from patch override values from message headers and
4033 body. Values given on command line with -m/--message and -u/--user
4034 body. Values given on command line with -m/--message and -u/--user
4034 override these.
4035 override these.
4035
4036
4036 If --exact is specified, import will set the working directory to
4037 If --exact is specified, import will set the working directory to
4037 the parent of each patch before applying it, and will abort if the
4038 the parent of each patch before applying it, and will abort if the
4038 resulting changeset has a different ID than the one recorded in
4039 resulting changeset has a different ID than the one recorded in
4039 the patch. This may happen due to character set problems or other
4040 the patch. This may happen due to character set problems or other
4040 deficiencies in the text patch format.
4041 deficiencies in the text patch format.
4041
4042
4042 Use --bypass to apply and commit patches directly to the
4043 Use --bypass to apply and commit patches directly to the
4043 repository, not touching the working directory. Without --exact,
4044 repository, not touching the working directory. Without --exact,
4044 patches will be applied on top of the working directory parent
4045 patches will be applied on top of the working directory parent
4045 revision.
4046 revision.
4046
4047
4047 With -s/--similarity, hg will attempt to discover renames and
4048 With -s/--similarity, hg will attempt to discover renames and
4048 copies in the patch in the same way as :hg:`addremove`.
4049 copies in the patch in the same way as :hg:`addremove`.
4049
4050
4050 Use --partial to ensure a changeset will be created from the patch
4051 Use --partial to ensure a changeset will be created from the patch
4051 even if some hunks fail to apply. Hunks that fail to apply will be
4052 even if some hunks fail to apply. Hunks that fail to apply will be
4052 written to a <target-file>.rej file. Conflicts can then be resolved
4053 written to a <target-file>.rej file. Conflicts can then be resolved
4053 by hand before :hg:`commit --amend` is run to update the created
4054 by hand before :hg:`commit --amend` is run to update the created
4054 changeset. This flag exists to let people import patches that
4055 changeset. This flag exists to let people import patches that
4055 partially apply without losing the associated metadata (author,
4056 partially apply without losing the associated metadata (author,
4056 date, description, ...). Note that when none of the hunk applies
4057 date, description, ...). Note that when none of the hunk applies
4057 cleanly, :hg:`import --partial` will create an empty changeset,
4058 cleanly, :hg:`import --partial` will create an empty changeset,
4058 importing only the patch metadata.
4059 importing only the patch metadata.
4059
4060
4060 To read a patch from standard input, use "-" as the patch name. If
4061 To read a patch from standard input, use "-" as the patch name. If
4061 a URL is specified, the patch will be downloaded from it.
4062 a URL is specified, the patch will be downloaded from it.
4062 See :hg:`help dates` for a list of formats valid for -d/--date.
4063 See :hg:`help dates` for a list of formats valid for -d/--date.
4063
4064
4064 .. container:: verbose
4065 .. container:: verbose
4065
4066
4066 Examples:
4067 Examples:
4067
4068
4068 - import a traditional patch from a website and detect renames::
4069 - import a traditional patch from a website and detect renames::
4069
4070
4070 hg import -s 80 http://example.com/bugfix.patch
4071 hg import -s 80 http://example.com/bugfix.patch
4071
4072
4072 - import a changeset from an hgweb server::
4073 - import a changeset from an hgweb server::
4073
4074
4074 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
4075 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
4075
4076
4076 - import all the patches in an Unix-style mbox::
4077 - import all the patches in an Unix-style mbox::
4077
4078
4078 hg import incoming-patches.mbox
4079 hg import incoming-patches.mbox
4079
4080
4080 - attempt to exactly restore an exported changeset (not always
4081 - attempt to exactly restore an exported changeset (not always
4081 possible)::
4082 possible)::
4082
4083
4083 hg import --exact proposed-fix.patch
4084 hg import --exact proposed-fix.patch
4084
4085
4085 Returns 0 on success, 1 on partial success (see --partial).
4086 Returns 0 on success, 1 on partial success (see --partial).
4086 """
4087 """
4087
4088
4088 if not patch1:
4089 if not patch1:
4089 raise util.Abort(_('need at least one patch to import'))
4090 raise util.Abort(_('need at least one patch to import'))
4090
4091
4091 patches = (patch1,) + patches
4092 patches = (patch1,) + patches
4092
4093
4093 date = opts.get('date')
4094 date = opts.get('date')
4094 if date:
4095 if date:
4095 opts['date'] = util.parsedate(date)
4096 opts['date'] = util.parsedate(date)
4096
4097
4097 update = not opts.get('bypass')
4098 update = not opts.get('bypass')
4098 if not update and opts.get('no_commit'):
4099 if not update and opts.get('no_commit'):
4099 raise util.Abort(_('cannot use --no-commit with --bypass'))
4100 raise util.Abort(_('cannot use --no-commit with --bypass'))
4100 try:
4101 try:
4101 sim = float(opts.get('similarity') or 0)
4102 sim = float(opts.get('similarity') or 0)
4102 except ValueError:
4103 except ValueError:
4103 raise util.Abort(_('similarity must be a number'))
4104 raise util.Abort(_('similarity must be a number'))
4104 if sim < 0 or sim > 100:
4105 if sim < 0 or sim > 100:
4105 raise util.Abort(_('similarity must be between 0 and 100'))
4106 raise util.Abort(_('similarity must be between 0 and 100'))
4106 if sim and not update:
4107 if sim and not update:
4107 raise util.Abort(_('cannot use --similarity with --bypass'))
4108 raise util.Abort(_('cannot use --similarity with --bypass'))
4108 if opts.get('exact') and opts.get('edit'):
4109 if opts.get('exact') and opts.get('edit'):
4109 raise util.Abort(_('cannot use --exact with --edit'))
4110 raise util.Abort(_('cannot use --exact with --edit'))
4110
4111
4111 if update:
4112 if update:
4112 cmdutil.checkunfinished(repo)
4113 cmdutil.checkunfinished(repo)
4113 if (opts.get('exact') or not opts.get('force')) and update:
4114 if (opts.get('exact') or not opts.get('force')) and update:
4114 cmdutil.bailifchanged(repo)
4115 cmdutil.bailifchanged(repo)
4115
4116
4116 base = opts["base"]
4117 base = opts["base"]
4117 wlock = lock = tr = None
4118 wlock = lock = tr = None
4118 msgs = []
4119 msgs = []
4119 ret = 0
4120 ret = 0
4120
4121
4121
4122
4122 try:
4123 try:
4123 try:
4124 try:
4124 wlock = repo.wlock()
4125 wlock = repo.wlock()
4125 repo.dirstate.beginparentchange()
4126 repo.dirstate.beginparentchange()
4126 if not opts.get('no_commit'):
4127 if not opts.get('no_commit'):
4127 lock = repo.lock()
4128 lock = repo.lock()
4128 tr = repo.transaction('import')
4129 tr = repo.transaction('import')
4129 parents = repo.parents()
4130 parents = repo.parents()
4130 for patchurl in patches:
4131 for patchurl in patches:
4131 if patchurl == '-':
4132 if patchurl == '-':
4132 ui.status(_('applying patch from stdin\n'))
4133 ui.status(_('applying patch from stdin\n'))
4133 patchfile = ui.fin
4134 patchfile = ui.fin
4134 patchurl = 'stdin' # for error message
4135 patchurl = 'stdin' # for error message
4135 else:
4136 else:
4136 patchurl = os.path.join(base, patchurl)
4137 patchurl = os.path.join(base, patchurl)
4137 ui.status(_('applying %s\n') % patchurl)
4138 ui.status(_('applying %s\n') % patchurl)
4138 patchfile = hg.openpath(ui, patchurl)
4139 patchfile = hg.openpath(ui, patchurl)
4139
4140
4140 haspatch = False
4141 haspatch = False
4141 for hunk in patch.split(patchfile):
4142 for hunk in patch.split(patchfile):
4142 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
4143 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
4143 parents, opts,
4144 parents, opts,
4144 msgs, hg.clean)
4145 msgs, hg.clean)
4145 if msg:
4146 if msg:
4146 haspatch = True
4147 haspatch = True
4147 ui.note(msg + '\n')
4148 ui.note(msg + '\n')
4148 if update or opts.get('exact'):
4149 if update or opts.get('exact'):
4149 parents = repo.parents()
4150 parents = repo.parents()
4150 else:
4151 else:
4151 parents = [repo[node]]
4152 parents = [repo[node]]
4152 if rej:
4153 if rej:
4153 ui.write_err(_("patch applied partially\n"))
4154 ui.write_err(_("patch applied partially\n"))
4154 ui.write_err(_("(fix the .rej files and run "
4155 ui.write_err(_("(fix the .rej files and run "
4155 "`hg commit --amend`)\n"))
4156 "`hg commit --amend`)\n"))
4156 ret = 1
4157 ret = 1
4157 break
4158 break
4158
4159
4159 if not haspatch:
4160 if not haspatch:
4160 raise util.Abort(_('%s: no diffs found') % patchurl)
4161 raise util.Abort(_('%s: no diffs found') % patchurl)
4161
4162
4162 if tr:
4163 if tr:
4163 tr.close()
4164 tr.close()
4164 if msgs:
4165 if msgs:
4165 repo.savecommitmessage('\n* * *\n'.join(msgs))
4166 repo.savecommitmessage('\n* * *\n'.join(msgs))
4166 repo.dirstate.endparentchange()
4167 repo.dirstate.endparentchange()
4167 return ret
4168 return ret
4168 except: # re-raises
4169 except: # re-raises
4169 # wlock.release() indirectly calls dirstate.write(): since
4170 # wlock.release() indirectly calls dirstate.write(): since
4170 # we're crashing, we do not want to change the working dir
4171 # we're crashing, we do not want to change the working dir
4171 # parent after all, so make sure it writes nothing
4172 # parent after all, so make sure it writes nothing
4172 repo.dirstate.invalidate()
4173 repo.dirstate.invalidate()
4173 raise
4174 raise
4174 finally:
4175 finally:
4175 if tr:
4176 if tr:
4176 tr.release()
4177 tr.release()
4177 release(lock, wlock)
4178 release(lock, wlock)
4178
4179
4179 @command('incoming|in',
4180 @command('incoming|in',
4180 [('f', 'force', None,
4181 [('f', 'force', None,
4181 _('run even if remote repository is unrelated')),
4182 _('run even if remote repository is unrelated')),
4182 ('n', 'newest-first', None, _('show newest record first')),
4183 ('n', 'newest-first', None, _('show newest record first')),
4183 ('', 'bundle', '',
4184 ('', 'bundle', '',
4184 _('file to store the bundles into'), _('FILE')),
4185 _('file to store the bundles into'), _('FILE')),
4185 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4186 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4186 ('B', 'bookmarks', False, _("compare bookmarks")),
4187 ('B', 'bookmarks', False, _("compare bookmarks")),
4187 ('b', 'branch', [],
4188 ('b', 'branch', [],
4188 _('a specific branch you would like to pull'), _('BRANCH')),
4189 _('a specific branch you would like to pull'), _('BRANCH')),
4189 ] + logopts + remoteopts + subrepoopts,
4190 ] + logopts + remoteopts + subrepoopts,
4190 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
4191 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
4191 def incoming(ui, repo, source="default", **opts):
4192 def incoming(ui, repo, source="default", **opts):
4192 """show new changesets found in source
4193 """show new changesets found in source
4193
4194
4194 Show new changesets found in the specified path/URL or the default
4195 Show new changesets found in the specified path/URL or the default
4195 pull location. These are the changesets that would have been pulled
4196 pull location. These are the changesets that would have been pulled
4196 if a pull at the time you issued this command.
4197 if a pull at the time you issued this command.
4197
4198
4198 For remote repository, using --bundle avoids downloading the
4199 For remote repository, using --bundle avoids downloading the
4199 changesets twice if the incoming is followed by a pull.
4200 changesets twice if the incoming is followed by a pull.
4200
4201
4201 See pull for valid source format details.
4202 See pull for valid source format details.
4202
4203
4203 .. container:: verbose
4204 .. container:: verbose
4204
4205
4205 Examples:
4206 Examples:
4206
4207
4207 - show incoming changes with patches and full description::
4208 - show incoming changes with patches and full description::
4208
4209
4209 hg incoming -vp
4210 hg incoming -vp
4210
4211
4211 - show incoming changes excluding merges, store a bundle::
4212 - show incoming changes excluding merges, store a bundle::
4212
4213
4213 hg in -vpM --bundle incoming.hg
4214 hg in -vpM --bundle incoming.hg
4214 hg pull incoming.hg
4215 hg pull incoming.hg
4215
4216
4216 - briefly list changes inside a bundle::
4217 - briefly list changes inside a bundle::
4217
4218
4218 hg in changes.hg -T "{desc|firstline}\\n"
4219 hg in changes.hg -T "{desc|firstline}\\n"
4219
4220
4220 Returns 0 if there are incoming changes, 1 otherwise.
4221 Returns 0 if there are incoming changes, 1 otherwise.
4221 """
4222 """
4222 if opts.get('graph'):
4223 if opts.get('graph'):
4223 cmdutil.checkunsupportedgraphflags([], opts)
4224 cmdutil.checkunsupportedgraphflags([], opts)
4224 def display(other, chlist, displayer):
4225 def display(other, chlist, displayer):
4225 revdag = cmdutil.graphrevs(other, chlist, opts)
4226 revdag = cmdutil.graphrevs(other, chlist, opts)
4226 showparents = [ctx.node() for ctx in repo[None].parents()]
4227 showparents = [ctx.node() for ctx in repo[None].parents()]
4227 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4228 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4228 graphmod.asciiedges)
4229 graphmod.asciiedges)
4229
4230
4230 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4231 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4231 return 0
4232 return 0
4232
4233
4233 if opts.get('bundle') and opts.get('subrepos'):
4234 if opts.get('bundle') and opts.get('subrepos'):
4234 raise util.Abort(_('cannot combine --bundle and --subrepos'))
4235 raise util.Abort(_('cannot combine --bundle and --subrepos'))
4235
4236
4236 if opts.get('bookmarks'):
4237 if opts.get('bookmarks'):
4237 source, branches = hg.parseurl(ui.expandpath(source),
4238 source, branches = hg.parseurl(ui.expandpath(source),
4238 opts.get('branch'))
4239 opts.get('branch'))
4239 other = hg.peer(repo, opts, source)
4240 other = hg.peer(repo, opts, source)
4240 if 'bookmarks' not in other.listkeys('namespaces'):
4241 if 'bookmarks' not in other.listkeys('namespaces'):
4241 ui.warn(_("remote doesn't support bookmarks\n"))
4242 ui.warn(_("remote doesn't support bookmarks\n"))
4242 return 0
4243 return 0
4243 ui.status(_('comparing with %s\n') % util.hidepassword(source))
4244 ui.status(_('comparing with %s\n') % util.hidepassword(source))
4244 return bookmarks.diff(ui, repo, other)
4245 return bookmarks.diff(ui, repo, other)
4245
4246
4246 repo._subtoppath = ui.expandpath(source)
4247 repo._subtoppath = ui.expandpath(source)
4247 try:
4248 try:
4248 return hg.incoming(ui, repo, source, opts)
4249 return hg.incoming(ui, repo, source, opts)
4249 finally:
4250 finally:
4250 del repo._subtoppath
4251 del repo._subtoppath
4251
4252
4252
4253
4253 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
4254 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
4254 norepo=True)
4255 norepo=True)
4255 def init(ui, dest=".", **opts):
4256 def init(ui, dest=".", **opts):
4256 """create a new repository in the given directory
4257 """create a new repository in the given directory
4257
4258
4258 Initialize a new repository in the given directory. If the given
4259 Initialize a new repository in the given directory. If the given
4259 directory does not exist, it will be created.
4260 directory does not exist, it will be created.
4260
4261
4261 If no directory is given, the current directory is used.
4262 If no directory is given, the current directory is used.
4262
4263
4263 It is possible to specify an ``ssh://`` URL as the destination.
4264 It is possible to specify an ``ssh://`` URL as the destination.
4264 See :hg:`help urls` for more information.
4265 See :hg:`help urls` for more information.
4265
4266
4266 Returns 0 on success.
4267 Returns 0 on success.
4267 """
4268 """
4268 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4269 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4269
4270
4270 @command('locate',
4271 @command('locate',
4271 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
4272 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
4272 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4273 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4273 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
4274 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
4274 ] + walkopts,
4275 ] + walkopts,
4275 _('[OPTION]... [PATTERN]...'))
4276 _('[OPTION]... [PATTERN]...'))
4276 def locate(ui, repo, *pats, **opts):
4277 def locate(ui, repo, *pats, **opts):
4277 """locate files matching specific patterns (DEPRECATED)
4278 """locate files matching specific patterns (DEPRECATED)
4278
4279
4279 Print files under Mercurial control in the working directory whose
4280 Print files under Mercurial control in the working directory whose
4280 names match the given patterns.
4281 names match the given patterns.
4281
4282
4282 By default, this command searches all directories in the working
4283 By default, this command searches all directories in the working
4283 directory. To search just the current directory and its
4284 directory. To search just the current directory and its
4284 subdirectories, use "--include .".
4285 subdirectories, use "--include .".
4285
4286
4286 If no patterns are given to match, this command prints the names
4287 If no patterns are given to match, this command prints the names
4287 of all files under Mercurial control in the working directory.
4288 of all files under Mercurial control in the working directory.
4288
4289
4289 If you want to feed the output of this command into the "xargs"
4290 If you want to feed the output of this command into the "xargs"
4290 command, use the -0 option to both this command and "xargs". This
4291 command, use the -0 option to both this command and "xargs". This
4291 will avoid the problem of "xargs" treating single filenames that
4292 will avoid the problem of "xargs" treating single filenames that
4292 contain whitespace as multiple filenames.
4293 contain whitespace as multiple filenames.
4293
4294
4294 See :hg:`help files` for a more versatile command.
4295 See :hg:`help files` for a more versatile command.
4295
4296
4296 Returns 0 if a match is found, 1 otherwise.
4297 Returns 0 if a match is found, 1 otherwise.
4297 """
4298 """
4298 end = opts.get('print0') and '\0' or '\n'
4299 end = opts.get('print0') and '\0' or '\n'
4299 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
4300 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
4300
4301
4301 ret = 1
4302 ret = 1
4302 ctx = repo[rev]
4303 ctx = repo[rev]
4303 m = scmutil.match(ctx, pats, opts, default='relglob')
4304 m = scmutil.match(ctx, pats, opts, default='relglob')
4304 m.bad = lambda x, y: False
4305 m.bad = lambda x, y: False
4305
4306
4306 for abs in ctx.matches(m):
4307 for abs in ctx.matches(m):
4307 if opts.get('fullpath'):
4308 if opts.get('fullpath'):
4308 ui.write(repo.wjoin(abs), end)
4309 ui.write(repo.wjoin(abs), end)
4309 else:
4310 else:
4310 ui.write(((pats and m.rel(abs)) or abs), end)
4311 ui.write(((pats and m.rel(abs)) or abs), end)
4311 ret = 0
4312 ret = 0
4312
4313
4313 return ret
4314 return ret
4314
4315
4315 @command('^log|history',
4316 @command('^log|history',
4316 [('f', 'follow', None,
4317 [('f', 'follow', None,
4317 _('follow changeset history, or file history across copies and renames')),
4318 _('follow changeset history, or file history across copies and renames')),
4318 ('', 'follow-first', None,
4319 ('', 'follow-first', None,
4319 _('only follow the first parent of merge changesets (DEPRECATED)')),
4320 _('only follow the first parent of merge changesets (DEPRECATED)')),
4320 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
4321 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
4321 ('C', 'copies', None, _('show copied files')),
4322 ('C', 'copies', None, _('show copied files')),
4322 ('k', 'keyword', [],
4323 ('k', 'keyword', [],
4323 _('do case-insensitive search for a given text'), _('TEXT')),
4324 _('do case-insensitive search for a given text'), _('TEXT')),
4324 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
4325 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
4325 ('', 'removed', None, _('include revisions where files were removed')),
4326 ('', 'removed', None, _('include revisions where files were removed')),
4326 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
4327 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
4327 ('u', 'user', [], _('revisions committed by user'), _('USER')),
4328 ('u', 'user', [], _('revisions committed by user'), _('USER')),
4328 ('', 'only-branch', [],
4329 ('', 'only-branch', [],
4329 _('show only changesets within the given named branch (DEPRECATED)'),
4330 _('show only changesets within the given named branch (DEPRECATED)'),
4330 _('BRANCH')),
4331 _('BRANCH')),
4331 ('b', 'branch', [],
4332 ('b', 'branch', [],
4332 _('show changesets within the given named branch'), _('BRANCH')),
4333 _('show changesets within the given named branch'), _('BRANCH')),
4333 ('P', 'prune', [],
4334 ('P', 'prune', [],
4334 _('do not display revision or any of its ancestors'), _('REV')),
4335 _('do not display revision or any of its ancestors'), _('REV')),
4335 ] + logopts + walkopts,
4336 ] + logopts + walkopts,
4336 _('[OPTION]... [FILE]'),
4337 _('[OPTION]... [FILE]'),
4337 inferrepo=True)
4338 inferrepo=True)
4338 def log(ui, repo, *pats, **opts):
4339 def log(ui, repo, *pats, **opts):
4339 """show revision history of entire repository or files
4340 """show revision history of entire repository or files
4340
4341
4341 Print the revision history of the specified files or the entire
4342 Print the revision history of the specified files or the entire
4342 project.
4343 project.
4343
4344
4344 If no revision range is specified, the default is ``tip:0`` unless
4345 If no revision range is specified, the default is ``tip:0`` unless
4345 --follow is set, in which case the working directory parent is
4346 --follow is set, in which case the working directory parent is
4346 used as the starting revision.
4347 used as the starting revision.
4347
4348
4348 File history is shown without following rename or copy history of
4349 File history is shown without following rename or copy history of
4349 files. Use -f/--follow with a filename to follow history across
4350 files. Use -f/--follow with a filename to follow history across
4350 renames and copies. --follow without a filename will only show
4351 renames and copies. --follow without a filename will only show
4351 ancestors or descendants of the starting revision.
4352 ancestors or descendants of the starting revision.
4352
4353
4353 By default this command prints revision number and changeset id,
4354 By default this command prints revision number and changeset id,
4354 tags, non-trivial parents, user, date and time, and a summary for
4355 tags, non-trivial parents, user, date and time, and a summary for
4355 each commit. When the -v/--verbose switch is used, the list of
4356 each commit. When the -v/--verbose switch is used, the list of
4356 changed files and full commit message are shown.
4357 changed files and full commit message are shown.
4357
4358
4358 With --graph the revisions are shown as an ASCII art DAG with the most
4359 With --graph the revisions are shown as an ASCII art DAG with the most
4359 recent changeset at the top.
4360 recent changeset at the top.
4360 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
4361 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
4361 and '+' represents a fork where the changeset from the lines below is a
4362 and '+' represents a fork where the changeset from the lines below is a
4362 parent of the 'o' merge on the same line.
4363 parent of the 'o' merge on the same line.
4363
4364
4364 .. note::
4365 .. note::
4365
4366
4366 log -p/--patch may generate unexpected diff output for merge
4367 log -p/--patch may generate unexpected diff output for merge
4367 changesets, as it will only compare the merge changeset against
4368 changesets, as it will only compare the merge changeset against
4368 its first parent. Also, only files different from BOTH parents
4369 its first parent. Also, only files different from BOTH parents
4369 will appear in files:.
4370 will appear in files:.
4370
4371
4371 .. note::
4372 .. note::
4372
4373
4373 for performance reasons, log FILE may omit duplicate changes
4374 for performance reasons, log FILE may omit duplicate changes
4374 made on branches and will not show removals or mode changes. To
4375 made on branches and will not show removals or mode changes. To
4375 see all such changes, use the --removed switch.
4376 see all such changes, use the --removed switch.
4376
4377
4377 .. container:: verbose
4378 .. container:: verbose
4378
4379
4379 Some examples:
4380 Some examples:
4380
4381
4381 - changesets with full descriptions and file lists::
4382 - changesets with full descriptions and file lists::
4382
4383
4383 hg log -v
4384 hg log -v
4384
4385
4385 - changesets ancestral to the working directory::
4386 - changesets ancestral to the working directory::
4386
4387
4387 hg log -f
4388 hg log -f
4388
4389
4389 - last 10 commits on the current branch::
4390 - last 10 commits on the current branch::
4390
4391
4391 hg log -l 10 -b .
4392 hg log -l 10 -b .
4392
4393
4393 - changesets showing all modifications of a file, including removals::
4394 - changesets showing all modifications of a file, including removals::
4394
4395
4395 hg log --removed file.c
4396 hg log --removed file.c
4396
4397
4397 - all changesets that touch a directory, with diffs, excluding merges::
4398 - all changesets that touch a directory, with diffs, excluding merges::
4398
4399
4399 hg log -Mp lib/
4400 hg log -Mp lib/
4400
4401
4401 - all revision numbers that match a keyword::
4402 - all revision numbers that match a keyword::
4402
4403
4403 hg log -k bug --template "{rev}\\n"
4404 hg log -k bug --template "{rev}\\n"
4404
4405
4405 - list available log templates::
4406 - list available log templates::
4406
4407
4407 hg log -T list
4408 hg log -T list
4408
4409
4409 - check if a given changeset is included in a tagged release::
4410 - check if a given changeset is included in a tagged release::
4410
4411
4411 hg log -r "a21ccf and ancestor(1.9)"
4412 hg log -r "a21ccf and ancestor(1.9)"
4412
4413
4413 - find all changesets by some user in a date range::
4414 - find all changesets by some user in a date range::
4414
4415
4415 hg log -k alice -d "may 2008 to jul 2008"
4416 hg log -k alice -d "may 2008 to jul 2008"
4416
4417
4417 - summary of all changesets after the last tag::
4418 - summary of all changesets after the last tag::
4418
4419
4419 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4420 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4420
4421
4421 See :hg:`help dates` for a list of formats valid for -d/--date.
4422 See :hg:`help dates` for a list of formats valid for -d/--date.
4422
4423
4423 See :hg:`help revisions` and :hg:`help revsets` for more about
4424 See :hg:`help revisions` and :hg:`help revsets` for more about
4424 specifying revisions.
4425 specifying revisions.
4425
4426
4426 See :hg:`help templates` for more about pre-packaged styles and
4427 See :hg:`help templates` for more about pre-packaged styles and
4427 specifying custom templates.
4428 specifying custom templates.
4428
4429
4429 Returns 0 on success.
4430 Returns 0 on success.
4430
4431
4431 """
4432 """
4432 if opts.get('graph'):
4433 if opts.get('graph'):
4433 return cmdutil.graphlog(ui, repo, *pats, **opts)
4434 return cmdutil.graphlog(ui, repo, *pats, **opts)
4434
4435
4435 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
4436 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
4436 limit = cmdutil.loglimit(opts)
4437 limit = cmdutil.loglimit(opts)
4437 count = 0
4438 count = 0
4438
4439
4439 getrenamed = None
4440 getrenamed = None
4440 if opts.get('copies'):
4441 if opts.get('copies'):
4441 endrev = None
4442 endrev = None
4442 if opts.get('rev'):
4443 if opts.get('rev'):
4443 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
4444 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
4444 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
4445 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
4445
4446
4446 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4447 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4447 for rev in revs:
4448 for rev in revs:
4448 if count == limit:
4449 if count == limit:
4449 break
4450 break
4450 ctx = repo[rev]
4451 ctx = repo[rev]
4451 copies = None
4452 copies = None
4452 if getrenamed is not None and rev:
4453 if getrenamed is not None and rev:
4453 copies = []
4454 copies = []
4454 for fn in ctx.files():
4455 for fn in ctx.files():
4455 rename = getrenamed(fn, rev)
4456 rename = getrenamed(fn, rev)
4456 if rename:
4457 if rename:
4457 copies.append((fn, rename[0]))
4458 copies.append((fn, rename[0]))
4458 revmatchfn = filematcher and filematcher(ctx.rev()) or None
4459 revmatchfn = filematcher and filematcher(ctx.rev()) or None
4459 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
4460 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
4460 if displayer.flush(rev):
4461 if displayer.flush(rev):
4461 count += 1
4462 count += 1
4462
4463
4463 displayer.close()
4464 displayer.close()
4464
4465
4465 @command('manifest',
4466 @command('manifest',
4466 [('r', 'rev', '', _('revision to display'), _('REV')),
4467 [('r', 'rev', '', _('revision to display'), _('REV')),
4467 ('', 'all', False, _("list files from all revisions"))]
4468 ('', 'all', False, _("list files from all revisions"))]
4468 + formatteropts,
4469 + formatteropts,
4469 _('[-r REV]'))
4470 _('[-r REV]'))
4470 def manifest(ui, repo, node=None, rev=None, **opts):
4471 def manifest(ui, repo, node=None, rev=None, **opts):
4471 """output the current or given revision of the project manifest
4472 """output the current or given revision of the project manifest
4472
4473
4473 Print a list of version controlled files for the given revision.
4474 Print a list of version controlled files for the given revision.
4474 If no revision is given, the first parent of the working directory
4475 If no revision is given, the first parent of the working directory
4475 is used, or the null revision if no revision is checked out.
4476 is used, or the null revision if no revision is checked out.
4476
4477
4477 With -v, print file permissions, symlink and executable bits.
4478 With -v, print file permissions, symlink and executable bits.
4478 With --debug, print file revision hashes.
4479 With --debug, print file revision hashes.
4479
4480
4480 If option --all is specified, the list of all files from all revisions
4481 If option --all is specified, the list of all files from all revisions
4481 is printed. This includes deleted and renamed files.
4482 is printed. This includes deleted and renamed files.
4482
4483
4483 Returns 0 on success.
4484 Returns 0 on success.
4484 """
4485 """
4485
4486
4486 fm = ui.formatter('manifest', opts)
4487 fm = ui.formatter('manifest', opts)
4487
4488
4488 if opts.get('all'):
4489 if opts.get('all'):
4489 if rev or node:
4490 if rev or node:
4490 raise util.Abort(_("can't specify a revision with --all"))
4491 raise util.Abort(_("can't specify a revision with --all"))
4491
4492
4492 res = []
4493 res = []
4493 prefix = "data/"
4494 prefix = "data/"
4494 suffix = ".i"
4495 suffix = ".i"
4495 plen = len(prefix)
4496 plen = len(prefix)
4496 slen = len(suffix)
4497 slen = len(suffix)
4497 lock = repo.lock()
4498 lock = repo.lock()
4498 try:
4499 try:
4499 for fn, b, size in repo.store.datafiles():
4500 for fn, b, size in repo.store.datafiles():
4500 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
4501 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
4501 res.append(fn[plen:-slen])
4502 res.append(fn[plen:-slen])
4502 finally:
4503 finally:
4503 lock.release()
4504 lock.release()
4504 for f in res:
4505 for f in res:
4505 fm.startitem()
4506 fm.startitem()
4506 fm.write("path", '%s\n', f)
4507 fm.write("path", '%s\n', f)
4507 fm.end()
4508 fm.end()
4508 return
4509 return
4509
4510
4510 if rev and node:
4511 if rev and node:
4511 raise util.Abort(_("please specify just one revision"))
4512 raise util.Abort(_("please specify just one revision"))
4512
4513
4513 if not node:
4514 if not node:
4514 node = rev
4515 node = rev
4515
4516
4516 char = {'l': '@', 'x': '*', '': ''}
4517 char = {'l': '@', 'x': '*', '': ''}
4517 mode = {'l': '644', 'x': '755', '': '644'}
4518 mode = {'l': '644', 'x': '755', '': '644'}
4518 ctx = scmutil.revsingle(repo, node)
4519 ctx = scmutil.revsingle(repo, node)
4519 mf = ctx.manifest()
4520 mf = ctx.manifest()
4520 for f in ctx:
4521 for f in ctx:
4521 fm.startitem()
4522 fm.startitem()
4522 fl = ctx[f].flags()
4523 fl = ctx[f].flags()
4523 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
4524 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
4524 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
4525 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
4525 fm.write('path', '%s\n', f)
4526 fm.write('path', '%s\n', f)
4526 fm.end()
4527 fm.end()
4527
4528
4528 @command('^merge',
4529 @command('^merge',
4529 [('f', 'force', None,
4530 [('f', 'force', None,
4530 _('force a merge including outstanding changes (DEPRECATED)')),
4531 _('force a merge including outstanding changes (DEPRECATED)')),
4531 ('r', 'rev', '', _('revision to merge'), _('REV')),
4532 ('r', 'rev', '', _('revision to merge'), _('REV')),
4532 ('P', 'preview', None,
4533 ('P', 'preview', None,
4533 _('review revisions to merge (no merge is performed)'))
4534 _('review revisions to merge (no merge is performed)'))
4534 ] + mergetoolopts,
4535 ] + mergetoolopts,
4535 _('[-P] [-f] [[-r] REV]'))
4536 _('[-P] [-f] [[-r] REV]'))
4536 def merge(ui, repo, node=None, **opts):
4537 def merge(ui, repo, node=None, **opts):
4537 """merge working directory with another revision
4538 """merge working directory with another revision
4538
4539
4539 The current working directory is updated with all changes made in
4540 The current working directory is updated with all changes made in
4540 the requested revision since the last common predecessor revision.
4541 the requested revision since the last common predecessor revision.
4541
4542
4542 Files that changed between either parent are marked as changed for
4543 Files that changed between either parent are marked as changed for
4543 the next commit and a commit must be performed before any further
4544 the next commit and a commit must be performed before any further
4544 updates to the repository are allowed. The next commit will have
4545 updates to the repository are allowed. The next commit will have
4545 two parents.
4546 two parents.
4546
4547
4547 ``--tool`` can be used to specify the merge tool used for file
4548 ``--tool`` can be used to specify the merge tool used for file
4548 merges. It overrides the HGMERGE environment variable and your
4549 merges. It overrides the HGMERGE environment variable and your
4549 configuration files. See :hg:`help merge-tools` for options.
4550 configuration files. See :hg:`help merge-tools` for options.
4550
4551
4551 If no revision is specified, the working directory's parent is a
4552 If no revision is specified, the working directory's parent is a
4552 head revision, and the current branch contains exactly one other
4553 head revision, and the current branch contains exactly one other
4553 head, the other head is merged with by default. Otherwise, an
4554 head, the other head is merged with by default. Otherwise, an
4554 explicit revision with which to merge with must be provided.
4555 explicit revision with which to merge with must be provided.
4555
4556
4556 :hg:`resolve` must be used to resolve unresolved files.
4557 :hg:`resolve` must be used to resolve unresolved files.
4557
4558
4558 To undo an uncommitted merge, use :hg:`update --clean .` which
4559 To undo an uncommitted merge, use :hg:`update --clean .` which
4559 will check out a clean copy of the original merge parent, losing
4560 will check out a clean copy of the original merge parent, losing
4560 all changes.
4561 all changes.
4561
4562
4562 Returns 0 on success, 1 if there are unresolved files.
4563 Returns 0 on success, 1 if there are unresolved files.
4563 """
4564 """
4564
4565
4565 if opts.get('rev') and node:
4566 if opts.get('rev') and node:
4566 raise util.Abort(_("please specify just one revision"))
4567 raise util.Abort(_("please specify just one revision"))
4567 if not node:
4568 if not node:
4568 node = opts.get('rev')
4569 node = opts.get('rev')
4569
4570
4570 if node:
4571 if node:
4571 node = scmutil.revsingle(repo, node).node()
4572 node = scmutil.revsingle(repo, node).node()
4572
4573
4573 if not node and repo._bookmarkcurrent:
4574 if not node and repo._bookmarkcurrent:
4574 bmheads = repo.bookmarkheads(repo._bookmarkcurrent)
4575 bmheads = repo.bookmarkheads(repo._bookmarkcurrent)
4575 curhead = repo[repo._bookmarkcurrent].node()
4576 curhead = repo[repo._bookmarkcurrent].node()
4576 if len(bmheads) == 2:
4577 if len(bmheads) == 2:
4577 if curhead == bmheads[0]:
4578 if curhead == bmheads[0]:
4578 node = bmheads[1]
4579 node = bmheads[1]
4579 else:
4580 else:
4580 node = bmheads[0]
4581 node = bmheads[0]
4581 elif len(bmheads) > 2:
4582 elif len(bmheads) > 2:
4582 raise util.Abort(_("multiple matching bookmarks to merge - "
4583 raise util.Abort(_("multiple matching bookmarks to merge - "
4583 "please merge with an explicit rev or bookmark"),
4584 "please merge with an explicit rev or bookmark"),
4584 hint=_("run 'hg heads' to see all heads"))
4585 hint=_("run 'hg heads' to see all heads"))
4585 elif len(bmheads) <= 1:
4586 elif len(bmheads) <= 1:
4586 raise util.Abort(_("no matching bookmark to merge - "
4587 raise util.Abort(_("no matching bookmark to merge - "
4587 "please merge with an explicit rev or bookmark"),
4588 "please merge with an explicit rev or bookmark"),
4588 hint=_("run 'hg heads' to see all heads"))
4589 hint=_("run 'hg heads' to see all heads"))
4589
4590
4590 if not node and not repo._bookmarkcurrent:
4591 if not node and not repo._bookmarkcurrent:
4591 branch = repo[None].branch()
4592 branch = repo[None].branch()
4592 bheads = repo.branchheads(branch)
4593 bheads = repo.branchheads(branch)
4593 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
4594 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
4594
4595
4595 if len(nbhs) > 2:
4596 if len(nbhs) > 2:
4596 raise util.Abort(_("branch '%s' has %d heads - "
4597 raise util.Abort(_("branch '%s' has %d heads - "
4597 "please merge with an explicit rev")
4598 "please merge with an explicit rev")
4598 % (branch, len(bheads)),
4599 % (branch, len(bheads)),
4599 hint=_("run 'hg heads .' to see heads"))
4600 hint=_("run 'hg heads .' to see heads"))
4600
4601
4601 parent = repo.dirstate.p1()
4602 parent = repo.dirstate.p1()
4602 if len(nbhs) <= 1:
4603 if len(nbhs) <= 1:
4603 if len(bheads) > 1:
4604 if len(bheads) > 1:
4604 raise util.Abort(_("heads are bookmarked - "
4605 raise util.Abort(_("heads are bookmarked - "
4605 "please merge with an explicit rev"),
4606 "please merge with an explicit rev"),
4606 hint=_("run 'hg heads' to see all heads"))
4607 hint=_("run 'hg heads' to see all heads"))
4607 if len(repo.heads()) > 1:
4608 if len(repo.heads()) > 1:
4608 raise util.Abort(_("branch '%s' has one head - "
4609 raise util.Abort(_("branch '%s' has one head - "
4609 "please merge with an explicit rev")
4610 "please merge with an explicit rev")
4610 % branch,
4611 % branch,
4611 hint=_("run 'hg heads' to see all heads"))
4612 hint=_("run 'hg heads' to see all heads"))
4612 msg, hint = _('nothing to merge'), None
4613 msg, hint = _('nothing to merge'), None
4613 if parent != repo.lookup(branch):
4614 if parent != repo.lookup(branch):
4614 hint = _("use 'hg update' instead")
4615 hint = _("use 'hg update' instead")
4615 raise util.Abort(msg, hint=hint)
4616 raise util.Abort(msg, hint=hint)
4616
4617
4617 if parent not in bheads:
4618 if parent not in bheads:
4618 raise util.Abort(_('working directory not at a head revision'),
4619 raise util.Abort(_('working directory not at a head revision'),
4619 hint=_("use 'hg update' or merge with an "
4620 hint=_("use 'hg update' or merge with an "
4620 "explicit revision"))
4621 "explicit revision"))
4621 if parent == nbhs[0]:
4622 if parent == nbhs[0]:
4622 node = nbhs[-1]
4623 node = nbhs[-1]
4623 else:
4624 else:
4624 node = nbhs[0]
4625 node = nbhs[0]
4625
4626
4626 if opts.get('preview'):
4627 if opts.get('preview'):
4627 # find nodes that are ancestors of p2 but not of p1
4628 # find nodes that are ancestors of p2 but not of p1
4628 p1 = repo.lookup('.')
4629 p1 = repo.lookup('.')
4629 p2 = repo.lookup(node)
4630 p2 = repo.lookup(node)
4630 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4631 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4631
4632
4632 displayer = cmdutil.show_changeset(ui, repo, opts)
4633 displayer = cmdutil.show_changeset(ui, repo, opts)
4633 for node in nodes:
4634 for node in nodes:
4634 displayer.show(repo[node])
4635 displayer.show(repo[node])
4635 displayer.close()
4636 displayer.close()
4636 return 0
4637 return 0
4637
4638
4638 try:
4639 try:
4639 # ui.forcemerge is an internal variable, do not document
4640 # ui.forcemerge is an internal variable, do not document
4640 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
4641 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
4641 return hg.merge(repo, node, force=opts.get('force'))
4642 return hg.merge(repo, node, force=opts.get('force'))
4642 finally:
4643 finally:
4643 ui.setconfig('ui', 'forcemerge', '', 'merge')
4644 ui.setconfig('ui', 'forcemerge', '', 'merge')
4644
4645
4645 @command('outgoing|out',
4646 @command('outgoing|out',
4646 [('f', 'force', None, _('run even when the destination is unrelated')),
4647 [('f', 'force', None, _('run even when the destination is unrelated')),
4647 ('r', 'rev', [],
4648 ('r', 'rev', [],
4648 _('a changeset intended to be included in the destination'), _('REV')),
4649 _('a changeset intended to be included in the destination'), _('REV')),
4649 ('n', 'newest-first', None, _('show newest record first')),
4650 ('n', 'newest-first', None, _('show newest record first')),
4650 ('B', 'bookmarks', False, _('compare bookmarks')),
4651 ('B', 'bookmarks', False, _('compare bookmarks')),
4651 ('b', 'branch', [], _('a specific branch you would like to push'),
4652 ('b', 'branch', [], _('a specific branch you would like to push'),
4652 _('BRANCH')),
4653 _('BRANCH')),
4653 ] + logopts + remoteopts + subrepoopts,
4654 ] + logopts + remoteopts + subrepoopts,
4654 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4655 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4655 def outgoing(ui, repo, dest=None, **opts):
4656 def outgoing(ui, repo, dest=None, **opts):
4656 """show changesets not found in the destination
4657 """show changesets not found in the destination
4657
4658
4658 Show changesets not found in the specified destination repository
4659 Show changesets not found in the specified destination repository
4659 or the default push location. These are the changesets that would
4660 or the default push location. These are the changesets that would
4660 be pushed if a push was requested.
4661 be pushed if a push was requested.
4661
4662
4662 See pull for details of valid destination formats.
4663 See pull for details of valid destination formats.
4663
4664
4664 Returns 0 if there are outgoing changes, 1 otherwise.
4665 Returns 0 if there are outgoing changes, 1 otherwise.
4665 """
4666 """
4666 if opts.get('graph'):
4667 if opts.get('graph'):
4667 cmdutil.checkunsupportedgraphflags([], opts)
4668 cmdutil.checkunsupportedgraphflags([], opts)
4668 o, other = hg._outgoing(ui, repo, dest, opts)
4669 o, other = hg._outgoing(ui, repo, dest, opts)
4669 if not o:
4670 if not o:
4670 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4671 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4671 return
4672 return
4672
4673
4673 revdag = cmdutil.graphrevs(repo, o, opts)
4674 revdag = cmdutil.graphrevs(repo, o, opts)
4674 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4675 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4675 showparents = [ctx.node() for ctx in repo[None].parents()]
4676 showparents = [ctx.node() for ctx in repo[None].parents()]
4676 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4677 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4677 graphmod.asciiedges)
4678 graphmod.asciiedges)
4678 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4679 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4679 return 0
4680 return 0
4680
4681
4681 if opts.get('bookmarks'):
4682 if opts.get('bookmarks'):
4682 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4683 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4683 dest, branches = hg.parseurl(dest, opts.get('branch'))
4684 dest, branches = hg.parseurl(dest, opts.get('branch'))
4684 other = hg.peer(repo, opts, dest)
4685 other = hg.peer(repo, opts, dest)
4685 if 'bookmarks' not in other.listkeys('namespaces'):
4686 if 'bookmarks' not in other.listkeys('namespaces'):
4686 ui.warn(_("remote doesn't support bookmarks\n"))
4687 ui.warn(_("remote doesn't support bookmarks\n"))
4687 return 0
4688 return 0
4688 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4689 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4689 return bookmarks.diff(ui, other, repo)
4690 return bookmarks.diff(ui, other, repo)
4690
4691
4691 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4692 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4692 try:
4693 try:
4693 return hg.outgoing(ui, repo, dest, opts)
4694 return hg.outgoing(ui, repo, dest, opts)
4694 finally:
4695 finally:
4695 del repo._subtoppath
4696 del repo._subtoppath
4696
4697
4697 @command('parents',
4698 @command('parents',
4698 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4699 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4699 ] + templateopts,
4700 ] + templateopts,
4700 _('[-r REV] [FILE]'),
4701 _('[-r REV] [FILE]'),
4701 inferrepo=True)
4702 inferrepo=True)
4702 def parents(ui, repo, file_=None, **opts):
4703 def parents(ui, repo, file_=None, **opts):
4703 """show the parents of the working directory or revision (DEPRECATED)
4704 """show the parents of the working directory or revision (DEPRECATED)
4704
4705
4705 Print the working directory's parent revisions. If a revision is
4706 Print the working directory's parent revisions. If a revision is
4706 given via -r/--rev, the parent of that revision will be printed.
4707 given via -r/--rev, the parent of that revision will be printed.
4707 If a file argument is given, the revision in which the file was
4708 If a file argument is given, the revision in which the file was
4708 last changed (before the working directory revision or the
4709 last changed (before the working directory revision or the
4709 argument to --rev if given) is printed.
4710 argument to --rev if given) is printed.
4710
4711
4711 See :hg:`summary` and :hg:`help revsets` for related information.
4712 See :hg:`summary` and :hg:`help revsets` for related information.
4712
4713
4713 Returns 0 on success.
4714 Returns 0 on success.
4714 """
4715 """
4715
4716
4716 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4717 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4717
4718
4718 if file_:
4719 if file_:
4719 m = scmutil.match(ctx, (file_,), opts)
4720 m = scmutil.match(ctx, (file_,), opts)
4720 if m.anypats() or len(m.files()) != 1:
4721 if m.anypats() or len(m.files()) != 1:
4721 raise util.Abort(_('can only specify an explicit filename'))
4722 raise util.Abort(_('can only specify an explicit filename'))
4722 file_ = m.files()[0]
4723 file_ = m.files()[0]
4723 filenodes = []
4724 filenodes = []
4724 for cp in ctx.parents():
4725 for cp in ctx.parents():
4725 if not cp:
4726 if not cp:
4726 continue
4727 continue
4727 try:
4728 try:
4728 filenodes.append(cp.filenode(file_))
4729 filenodes.append(cp.filenode(file_))
4729 except error.LookupError:
4730 except error.LookupError:
4730 pass
4731 pass
4731 if not filenodes:
4732 if not filenodes:
4732 raise util.Abort(_("'%s' not found in manifest!") % file_)
4733 raise util.Abort(_("'%s' not found in manifest!") % file_)
4733 p = []
4734 p = []
4734 for fn in filenodes:
4735 for fn in filenodes:
4735 fctx = repo.filectx(file_, fileid=fn)
4736 fctx = repo.filectx(file_, fileid=fn)
4736 p.append(fctx.node())
4737 p.append(fctx.node())
4737 else:
4738 else:
4738 p = [cp.node() for cp in ctx.parents()]
4739 p = [cp.node() for cp in ctx.parents()]
4739
4740
4740 displayer = cmdutil.show_changeset(ui, repo, opts)
4741 displayer = cmdutil.show_changeset(ui, repo, opts)
4741 for n in p:
4742 for n in p:
4742 if n != nullid:
4743 if n != nullid:
4743 displayer.show(repo[n])
4744 displayer.show(repo[n])
4744 displayer.close()
4745 displayer.close()
4745
4746
4746 @command('paths', [], _('[NAME]'), optionalrepo=True)
4747 @command('paths', [], _('[NAME]'), optionalrepo=True)
4747 def paths(ui, repo, search=None):
4748 def paths(ui, repo, search=None):
4748 """show aliases for remote repositories
4749 """show aliases for remote repositories
4749
4750
4750 Show definition of symbolic path name NAME. If no name is given,
4751 Show definition of symbolic path name NAME. If no name is given,
4751 show definition of all available names.
4752 show definition of all available names.
4752
4753
4753 Option -q/--quiet suppresses all output when searching for NAME
4754 Option -q/--quiet suppresses all output when searching for NAME
4754 and shows only the path names when listing all definitions.
4755 and shows only the path names when listing all definitions.
4755
4756
4756 Path names are defined in the [paths] section of your
4757 Path names are defined in the [paths] section of your
4757 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4758 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4758 repository, ``.hg/hgrc`` is used, too.
4759 repository, ``.hg/hgrc`` is used, too.
4759
4760
4760 The path names ``default`` and ``default-push`` have a special
4761 The path names ``default`` and ``default-push`` have a special
4761 meaning. When performing a push or pull operation, they are used
4762 meaning. When performing a push or pull operation, they are used
4762 as fallbacks if no location is specified on the command-line.
4763 as fallbacks if no location is specified on the command-line.
4763 When ``default-push`` is set, it will be used for push and
4764 When ``default-push`` is set, it will be used for push and
4764 ``default`` will be used for pull; otherwise ``default`` is used
4765 ``default`` will be used for pull; otherwise ``default`` is used
4765 as the fallback for both. When cloning a repository, the clone
4766 as the fallback for both. When cloning a repository, the clone
4766 source is written as ``default`` in ``.hg/hgrc``. Note that
4767 source is written as ``default`` in ``.hg/hgrc``. Note that
4767 ``default`` and ``default-push`` apply to all inbound (e.g.
4768 ``default`` and ``default-push`` apply to all inbound (e.g.
4768 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4769 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4769 :hg:`bundle`) operations.
4770 :hg:`bundle`) operations.
4770
4771
4771 See :hg:`help urls` for more information.
4772 See :hg:`help urls` for more information.
4772
4773
4773 Returns 0 on success.
4774 Returns 0 on success.
4774 """
4775 """
4775 if search:
4776 if search:
4776 for name, path in ui.configitems("paths"):
4777 for name, path in ui.configitems("paths"):
4777 if name == search:
4778 if name == search:
4778 ui.status("%s\n" % util.hidepassword(path))
4779 ui.status("%s\n" % util.hidepassword(path))
4779 return
4780 return
4780 if not ui.quiet:
4781 if not ui.quiet:
4781 ui.warn(_("not found!\n"))
4782 ui.warn(_("not found!\n"))
4782 return 1
4783 return 1
4783 else:
4784 else:
4784 for name, path in ui.configitems("paths"):
4785 for name, path in ui.configitems("paths"):
4785 if ui.quiet:
4786 if ui.quiet:
4786 ui.write("%s\n" % name)
4787 ui.write("%s\n" % name)
4787 else:
4788 else:
4788 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4789 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4789
4790
4790 @command('phase',
4791 @command('phase',
4791 [('p', 'public', False, _('set changeset phase to public')),
4792 [('p', 'public', False, _('set changeset phase to public')),
4792 ('d', 'draft', False, _('set changeset phase to draft')),
4793 ('d', 'draft', False, _('set changeset phase to draft')),
4793 ('s', 'secret', False, _('set changeset phase to secret')),
4794 ('s', 'secret', False, _('set changeset phase to secret')),
4794 ('f', 'force', False, _('allow to move boundary backward')),
4795 ('f', 'force', False, _('allow to move boundary backward')),
4795 ('r', 'rev', [], _('target revision'), _('REV')),
4796 ('r', 'rev', [], _('target revision'), _('REV')),
4796 ],
4797 ],
4797 _('[-p|-d|-s] [-f] [-r] REV...'))
4798 _('[-p|-d|-s] [-f] [-r] REV...'))
4798 def phase(ui, repo, *revs, **opts):
4799 def phase(ui, repo, *revs, **opts):
4799 """set or show the current phase name
4800 """set or show the current phase name
4800
4801
4801 With no argument, show the phase name of specified revisions.
4802 With no argument, show the phase name of specified revisions.
4802
4803
4803 With one of -p/--public, -d/--draft or -s/--secret, change the
4804 With one of -p/--public, -d/--draft or -s/--secret, change the
4804 phase value of the specified revisions.
4805 phase value of the specified revisions.
4805
4806
4806 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
4807 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
4807 lower phase to an higher phase. Phases are ordered as follows::
4808 lower phase to an higher phase. Phases are ordered as follows::
4808
4809
4809 public < draft < secret
4810 public < draft < secret
4810
4811
4811 Returns 0 on success, 1 if no phases were changed or some could not
4812 Returns 0 on success, 1 if no phases were changed or some could not
4812 be changed.
4813 be changed.
4813 """
4814 """
4814 # search for a unique phase argument
4815 # search for a unique phase argument
4815 targetphase = None
4816 targetphase = None
4816 for idx, name in enumerate(phases.phasenames):
4817 for idx, name in enumerate(phases.phasenames):
4817 if opts[name]:
4818 if opts[name]:
4818 if targetphase is not None:
4819 if targetphase is not None:
4819 raise util.Abort(_('only one phase can be specified'))
4820 raise util.Abort(_('only one phase can be specified'))
4820 targetphase = idx
4821 targetphase = idx
4821
4822
4822 # look for specified revision
4823 # look for specified revision
4823 revs = list(revs)
4824 revs = list(revs)
4824 revs.extend(opts['rev'])
4825 revs.extend(opts['rev'])
4825 if not revs:
4826 if not revs:
4826 raise util.Abort(_('no revisions specified'))
4827 raise util.Abort(_('no revisions specified'))
4827
4828
4828 revs = scmutil.revrange(repo, revs)
4829 revs = scmutil.revrange(repo, revs)
4829
4830
4830 lock = None
4831 lock = None
4831 ret = 0
4832 ret = 0
4832 if targetphase is None:
4833 if targetphase is None:
4833 # display
4834 # display
4834 for r in revs:
4835 for r in revs:
4835 ctx = repo[r]
4836 ctx = repo[r]
4836 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4837 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4837 else:
4838 else:
4838 tr = None
4839 tr = None
4839 lock = repo.lock()
4840 lock = repo.lock()
4840 try:
4841 try:
4841 tr = repo.transaction("phase")
4842 tr = repo.transaction("phase")
4842 # set phase
4843 # set phase
4843 if not revs:
4844 if not revs:
4844 raise util.Abort(_('empty revision set'))
4845 raise util.Abort(_('empty revision set'))
4845 nodes = [repo[r].node() for r in revs]
4846 nodes = [repo[r].node() for r in revs]
4846 # moving revision from public to draft may hide them
4847 # moving revision from public to draft may hide them
4847 # We have to check result on an unfiltered repository
4848 # We have to check result on an unfiltered repository
4848 unfi = repo.unfiltered()
4849 unfi = repo.unfiltered()
4849 getphase = unfi._phasecache.phase
4850 getphase = unfi._phasecache.phase
4850 olddata = [getphase(unfi, r) for r in unfi]
4851 olddata = [getphase(unfi, r) for r in unfi]
4851 phases.advanceboundary(repo, tr, targetphase, nodes)
4852 phases.advanceboundary(repo, tr, targetphase, nodes)
4852 if opts['force']:
4853 if opts['force']:
4853 phases.retractboundary(repo, tr, targetphase, nodes)
4854 phases.retractboundary(repo, tr, targetphase, nodes)
4854 tr.close()
4855 tr.close()
4855 finally:
4856 finally:
4856 if tr is not None:
4857 if tr is not None:
4857 tr.release()
4858 tr.release()
4858 lock.release()
4859 lock.release()
4859 getphase = unfi._phasecache.phase
4860 getphase = unfi._phasecache.phase
4860 newdata = [getphase(unfi, r) for r in unfi]
4861 newdata = [getphase(unfi, r) for r in unfi]
4861 changes = sum(newdata[r] != olddata[r] for r in unfi)
4862 changes = sum(newdata[r] != olddata[r] for r in unfi)
4862 cl = unfi.changelog
4863 cl = unfi.changelog
4863 rejected = [n for n in nodes
4864 rejected = [n for n in nodes
4864 if newdata[cl.rev(n)] < targetphase]
4865 if newdata[cl.rev(n)] < targetphase]
4865 if rejected:
4866 if rejected:
4866 ui.warn(_('cannot move %i changesets to a higher '
4867 ui.warn(_('cannot move %i changesets to a higher '
4867 'phase, use --force\n') % len(rejected))
4868 'phase, use --force\n') % len(rejected))
4868 ret = 1
4869 ret = 1
4869 if changes:
4870 if changes:
4870 msg = _('phase changed for %i changesets\n') % changes
4871 msg = _('phase changed for %i changesets\n') % changes
4871 if ret:
4872 if ret:
4872 ui.status(msg)
4873 ui.status(msg)
4873 else:
4874 else:
4874 ui.note(msg)
4875 ui.note(msg)
4875 else:
4876 else:
4876 ui.warn(_('no phases changed\n'))
4877 ui.warn(_('no phases changed\n'))
4877 ret = 1
4878 ret = 1
4878 return ret
4879 return ret
4879
4880
4880 def postincoming(ui, repo, modheads, optupdate, checkout):
4881 def postincoming(ui, repo, modheads, optupdate, checkout):
4881 if modheads == 0:
4882 if modheads == 0:
4882 return
4883 return
4883 if optupdate:
4884 if optupdate:
4884 checkout, movemarkfrom = bookmarks.calculateupdate(ui, repo, checkout)
4885 checkout, movemarkfrom = bookmarks.calculateupdate(ui, repo, checkout)
4885 try:
4886 try:
4886 ret = hg.update(repo, checkout)
4887 ret = hg.update(repo, checkout)
4887 except util.Abort, inst:
4888 except util.Abort, inst:
4888 ui.warn(_("not updating: %s\n") % str(inst))
4889 ui.warn(_("not updating: %s\n") % str(inst))
4889 if inst.hint:
4890 if inst.hint:
4890 ui.warn(_("(%s)\n") % inst.hint)
4891 ui.warn(_("(%s)\n") % inst.hint)
4891 return 0
4892 return 0
4892 if not ret and not checkout:
4893 if not ret and not checkout:
4893 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
4894 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
4894 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
4895 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
4895 return ret
4896 return ret
4896 if modheads > 1:
4897 if modheads > 1:
4897 currentbranchheads = len(repo.branchheads())
4898 currentbranchheads = len(repo.branchheads())
4898 if currentbranchheads == modheads:
4899 if currentbranchheads == modheads:
4899 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4900 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4900 elif currentbranchheads > 1:
4901 elif currentbranchheads > 1:
4901 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4902 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4902 "merge)\n"))
4903 "merge)\n"))
4903 else:
4904 else:
4904 ui.status(_("(run 'hg heads' to see heads)\n"))
4905 ui.status(_("(run 'hg heads' to see heads)\n"))
4905 else:
4906 else:
4906 ui.status(_("(run 'hg update' to get a working copy)\n"))
4907 ui.status(_("(run 'hg update' to get a working copy)\n"))
4907
4908
4908 @command('^pull',
4909 @command('^pull',
4909 [('u', 'update', None,
4910 [('u', 'update', None,
4910 _('update to new branch head if changesets were pulled')),
4911 _('update to new branch head if changesets were pulled')),
4911 ('f', 'force', None, _('run even when remote repository is unrelated')),
4912 ('f', 'force', None, _('run even when remote repository is unrelated')),
4912 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4913 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4913 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4914 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4914 ('b', 'branch', [], _('a specific branch you would like to pull'),
4915 ('b', 'branch', [], _('a specific branch you would like to pull'),
4915 _('BRANCH')),
4916 _('BRANCH')),
4916 ] + remoteopts,
4917 ] + remoteopts,
4917 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4918 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4918 def pull(ui, repo, source="default", **opts):
4919 def pull(ui, repo, source="default", **opts):
4919 """pull changes from the specified source
4920 """pull changes from the specified source
4920
4921
4921 Pull changes from a remote repository to a local one.
4922 Pull changes from a remote repository to a local one.
4922
4923
4923 This finds all changes from the repository at the specified path
4924 This finds all changes from the repository at the specified path
4924 or URL and adds them to a local repository (the current one unless
4925 or URL and adds them to a local repository (the current one unless
4925 -R is specified). By default, this does not update the copy of the
4926 -R is specified). By default, this does not update the copy of the
4926 project in the working directory.
4927 project in the working directory.
4927
4928
4928 Use :hg:`incoming` if you want to see what would have been added
4929 Use :hg:`incoming` if you want to see what would have been added
4929 by a pull at the time you issued this command. If you then decide
4930 by a pull at the time you issued this command. If you then decide
4930 to add those changes to the repository, you should use :hg:`pull
4931 to add those changes to the repository, you should use :hg:`pull
4931 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4932 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4932
4933
4933 If SOURCE is omitted, the 'default' path will be used.
4934 If SOURCE is omitted, the 'default' path will be used.
4934 See :hg:`help urls` for more information.
4935 See :hg:`help urls` for more information.
4935
4936
4936 Returns 0 on success, 1 if an update had unresolved files.
4937 Returns 0 on success, 1 if an update had unresolved files.
4937 """
4938 """
4938 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4939 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4939 other = hg.peer(repo, opts, source)
4940 other = hg.peer(repo, opts, source)
4940 try:
4941 try:
4941 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4942 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4942 revs, checkout = hg.addbranchrevs(repo, other, branches,
4943 revs, checkout = hg.addbranchrevs(repo, other, branches,
4943 opts.get('rev'))
4944 opts.get('rev'))
4944
4945
4945 remotebookmarks = other.listkeys('bookmarks')
4946 remotebookmarks = other.listkeys('bookmarks')
4946
4947
4947 if opts.get('bookmark'):
4948 if opts.get('bookmark'):
4948 if not revs:
4949 if not revs:
4949 revs = []
4950 revs = []
4950 for b in opts['bookmark']:
4951 for b in opts['bookmark']:
4951 if b not in remotebookmarks:
4952 if b not in remotebookmarks:
4952 raise util.Abort(_('remote bookmark %s not found!') % b)
4953 raise util.Abort(_('remote bookmark %s not found!') % b)
4953 revs.append(remotebookmarks[b])
4954 revs.append(remotebookmarks[b])
4954
4955
4955 if revs:
4956 if revs:
4956 try:
4957 try:
4957 revs = [other.lookup(rev) for rev in revs]
4958 revs = [other.lookup(rev) for rev in revs]
4958 except error.CapabilityError:
4959 except error.CapabilityError:
4959 err = _("other repository doesn't support revision lookup, "
4960 err = _("other repository doesn't support revision lookup, "
4960 "so a rev cannot be specified.")
4961 "so a rev cannot be specified.")
4961 raise util.Abort(err)
4962 raise util.Abort(err)
4962
4963
4963 modheads = exchange.pull(repo, other, heads=revs,
4964 modheads = exchange.pull(repo, other, heads=revs,
4964 force=opts.get('force'),
4965 force=opts.get('force'),
4965 bookmarks=opts.get('bookmark', ())).cgresult
4966 bookmarks=opts.get('bookmark', ())).cgresult
4966 if checkout:
4967 if checkout:
4967 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4968 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4968 repo._subtoppath = source
4969 repo._subtoppath = source
4969 try:
4970 try:
4970 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4971 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4971
4972
4972 finally:
4973 finally:
4973 del repo._subtoppath
4974 del repo._subtoppath
4974
4975
4975 finally:
4976 finally:
4976 other.close()
4977 other.close()
4977 return ret
4978 return ret
4978
4979
4979 @command('^push',
4980 @command('^push',
4980 [('f', 'force', None, _('force push')),
4981 [('f', 'force', None, _('force push')),
4981 ('r', 'rev', [],
4982 ('r', 'rev', [],
4982 _('a changeset intended to be included in the destination'),
4983 _('a changeset intended to be included in the destination'),
4983 _('REV')),
4984 _('REV')),
4984 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4985 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4985 ('b', 'branch', [],
4986 ('b', 'branch', [],
4986 _('a specific branch you would like to push'), _('BRANCH')),
4987 _('a specific branch you would like to push'), _('BRANCH')),
4987 ('', 'new-branch', False, _('allow pushing a new branch')),
4988 ('', 'new-branch', False, _('allow pushing a new branch')),
4988 ] + remoteopts,
4989 ] + remoteopts,
4989 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4990 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4990 def push(ui, repo, dest=None, **opts):
4991 def push(ui, repo, dest=None, **opts):
4991 """push changes to the specified destination
4992 """push changes to the specified destination
4992
4993
4993 Push changesets from the local repository to the specified
4994 Push changesets from the local repository to the specified
4994 destination.
4995 destination.
4995
4996
4996 This operation is symmetrical to pull: it is identical to a pull
4997 This operation is symmetrical to pull: it is identical to a pull
4997 in the destination repository from the current one.
4998 in the destination repository from the current one.
4998
4999
4999 By default, push will not allow creation of new heads at the
5000 By default, push will not allow creation of new heads at the
5000 destination, since multiple heads would make it unclear which head
5001 destination, since multiple heads would make it unclear which head
5001 to use. In this situation, it is recommended to pull and merge
5002 to use. In this situation, it is recommended to pull and merge
5002 before pushing.
5003 before pushing.
5003
5004
5004 Use --new-branch if you want to allow push to create a new named
5005 Use --new-branch if you want to allow push to create a new named
5005 branch that is not present at the destination. This allows you to
5006 branch that is not present at the destination. This allows you to
5006 only create a new branch without forcing other changes.
5007 only create a new branch without forcing other changes.
5007
5008
5008 .. note::
5009 .. note::
5009
5010
5010 Extra care should be taken with the -f/--force option,
5011 Extra care should be taken with the -f/--force option,
5011 which will push all new heads on all branches, an action which will
5012 which will push all new heads on all branches, an action which will
5012 almost always cause confusion for collaborators.
5013 almost always cause confusion for collaborators.
5013
5014
5014 If -r/--rev is used, the specified revision and all its ancestors
5015 If -r/--rev is used, the specified revision and all its ancestors
5015 will be pushed to the remote repository.
5016 will be pushed to the remote repository.
5016
5017
5017 If -B/--bookmark is used, the specified bookmarked revision, its
5018 If -B/--bookmark is used, the specified bookmarked revision, its
5018 ancestors, and the bookmark will be pushed to the remote
5019 ancestors, and the bookmark will be pushed to the remote
5019 repository.
5020 repository.
5020
5021
5021 Please see :hg:`help urls` for important details about ``ssh://``
5022 Please see :hg:`help urls` for important details about ``ssh://``
5022 URLs. If DESTINATION is omitted, a default path will be used.
5023 URLs. If DESTINATION is omitted, a default path will be used.
5023
5024
5024 Returns 0 if push was successful, 1 if nothing to push.
5025 Returns 0 if push was successful, 1 if nothing to push.
5025 """
5026 """
5026
5027
5027 if opts.get('bookmark'):
5028 if opts.get('bookmark'):
5028 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
5029 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
5029 for b in opts['bookmark']:
5030 for b in opts['bookmark']:
5030 # translate -B options to -r so changesets get pushed
5031 # translate -B options to -r so changesets get pushed
5031 if b in repo._bookmarks:
5032 if b in repo._bookmarks:
5032 opts.setdefault('rev', []).append(b)
5033 opts.setdefault('rev', []).append(b)
5033 else:
5034 else:
5034 # if we try to push a deleted bookmark, translate it to null
5035 # if we try to push a deleted bookmark, translate it to null
5035 # this lets simultaneous -r, -b options continue working
5036 # this lets simultaneous -r, -b options continue working
5036 opts.setdefault('rev', []).append("null")
5037 opts.setdefault('rev', []).append("null")
5037
5038
5038 dest = ui.expandpath(dest or 'default-push', dest or 'default')
5039 dest = ui.expandpath(dest or 'default-push', dest or 'default')
5039 dest, branches = hg.parseurl(dest, opts.get('branch'))
5040 dest, branches = hg.parseurl(dest, opts.get('branch'))
5040 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
5041 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
5041 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
5042 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
5042 try:
5043 try:
5043 other = hg.peer(repo, opts, dest)
5044 other = hg.peer(repo, opts, dest)
5044 except error.RepoError:
5045 except error.RepoError:
5045 if dest == "default-push":
5046 if dest == "default-push":
5046 raise util.Abort(_("default repository not configured!"),
5047 raise util.Abort(_("default repository not configured!"),
5047 hint=_('see the "path" section in "hg help config"'))
5048 hint=_('see the "path" section in "hg help config"'))
5048 else:
5049 else:
5049 raise
5050 raise
5050
5051
5051 if revs:
5052 if revs:
5052 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
5053 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
5053
5054
5054 repo._subtoppath = dest
5055 repo._subtoppath = dest
5055 try:
5056 try:
5056 # push subrepos depth-first for coherent ordering
5057 # push subrepos depth-first for coherent ordering
5057 c = repo['']
5058 c = repo['']
5058 subs = c.substate # only repos that are committed
5059 subs = c.substate # only repos that are committed
5059 for s in sorted(subs):
5060 for s in sorted(subs):
5060 result = c.sub(s).push(opts)
5061 result = c.sub(s).push(opts)
5061 if result == 0:
5062 if result == 0:
5062 return not result
5063 return not result
5063 finally:
5064 finally:
5064 del repo._subtoppath
5065 del repo._subtoppath
5065 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
5066 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
5066 newbranch=opts.get('new_branch'),
5067 newbranch=opts.get('new_branch'),
5067 bookmarks=opts.get('bookmark', ()))
5068 bookmarks=opts.get('bookmark', ()))
5068
5069
5069 result = not pushop.cgresult
5070 result = not pushop.cgresult
5070
5071
5071 if pushop.bkresult is not None:
5072 if pushop.bkresult is not None:
5072 if pushop.bkresult == 2:
5073 if pushop.bkresult == 2:
5073 result = 2
5074 result = 2
5074 elif not result and pushop.bkresult:
5075 elif not result and pushop.bkresult:
5075 result = 2
5076 result = 2
5076
5077
5077 return result
5078 return result
5078
5079
5079 @command('recover', [])
5080 @command('recover', [])
5080 def recover(ui, repo):
5081 def recover(ui, repo):
5081 """roll back an interrupted transaction
5082 """roll back an interrupted transaction
5082
5083
5083 Recover from an interrupted commit or pull.
5084 Recover from an interrupted commit or pull.
5084
5085
5085 This command tries to fix the repository status after an
5086 This command tries to fix the repository status after an
5086 interrupted operation. It should only be necessary when Mercurial
5087 interrupted operation. It should only be necessary when Mercurial
5087 suggests it.
5088 suggests it.
5088
5089
5089 Returns 0 if successful, 1 if nothing to recover or verify fails.
5090 Returns 0 if successful, 1 if nothing to recover or verify fails.
5090 """
5091 """
5091 if repo.recover():
5092 if repo.recover():
5092 return hg.verify(repo)
5093 return hg.verify(repo)
5093 return 1
5094 return 1
5094
5095
5095 @command('^remove|rm',
5096 @command('^remove|rm',
5096 [('A', 'after', None, _('record delete for missing files')),
5097 [('A', 'after', None, _('record delete for missing files')),
5097 ('f', 'force', None,
5098 ('f', 'force', None,
5098 _('remove (and delete) file even if added or modified')),
5099 _('remove (and delete) file even if added or modified')),
5099 ] + walkopts,
5100 ] + walkopts,
5100 _('[OPTION]... FILE...'),
5101 _('[OPTION]... FILE...'),
5101 inferrepo=True)
5102 inferrepo=True)
5102 def remove(ui, repo, *pats, **opts):
5103 def remove(ui, repo, *pats, **opts):
5103 """remove the specified files on the next commit
5104 """remove the specified files on the next commit
5104
5105
5105 Schedule the indicated files for removal from the current branch.
5106 Schedule the indicated files for removal from the current branch.
5106
5107
5107 This command schedules the files to be removed at the next commit.
5108 This command schedules the files to be removed at the next commit.
5108 To undo a remove before that, see :hg:`revert`. To undo added
5109 To undo a remove before that, see :hg:`revert`. To undo added
5109 files, see :hg:`forget`.
5110 files, see :hg:`forget`.
5110
5111
5111 .. container:: verbose
5112 .. container:: verbose
5112
5113
5113 -A/--after can be used to remove only files that have already
5114 -A/--after can be used to remove only files that have already
5114 been deleted, -f/--force can be used to force deletion, and -Af
5115 been deleted, -f/--force can be used to force deletion, and -Af
5115 can be used to remove files from the next revision without
5116 can be used to remove files from the next revision without
5116 deleting them from the working directory.
5117 deleting them from the working directory.
5117
5118
5118 The following table details the behavior of remove for different
5119 The following table details the behavior of remove for different
5119 file states (columns) and option combinations (rows). The file
5120 file states (columns) and option combinations (rows). The file
5120 states are Added [A], Clean [C], Modified [M] and Missing [!]
5121 states are Added [A], Clean [C], Modified [M] and Missing [!]
5121 (as reported by :hg:`status`). The actions are Warn, Remove
5122 (as reported by :hg:`status`). The actions are Warn, Remove
5122 (from branch) and Delete (from disk):
5123 (from branch) and Delete (from disk):
5123
5124
5124 ========= == == == ==
5125 ========= == == == ==
5125 opt/state A C M !
5126 opt/state A C M !
5126 ========= == == == ==
5127 ========= == == == ==
5127 none W RD W R
5128 none W RD W R
5128 -f R RD RD R
5129 -f R RD RD R
5129 -A W W W R
5130 -A W W W R
5130 -Af R R R R
5131 -Af R R R R
5131 ========= == == == ==
5132 ========= == == == ==
5132
5133
5133 Note that remove never deletes files in Added [A] state from the
5134 Note that remove never deletes files in Added [A] state from the
5134 working directory, not even if option --force is specified.
5135 working directory, not even if option --force is specified.
5135
5136
5136 Returns 0 on success, 1 if any warnings encountered.
5137 Returns 0 on success, 1 if any warnings encountered.
5137 """
5138 """
5138
5139
5139 ret = 0
5140 ret = 0
5140 after, force = opts.get('after'), opts.get('force')
5141 after, force = opts.get('after'), opts.get('force')
5141 if not pats and not after:
5142 if not pats and not after:
5142 raise util.Abort(_('no files specified'))
5143 raise util.Abort(_('no files specified'))
5143
5144
5144 m = scmutil.match(repo[None], pats, opts)
5145 m = scmutil.match(repo[None], pats, opts)
5145 s = repo.status(match=m, clean=True)
5146 s = repo.status(match=m, clean=True)
5146 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
5147 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
5147
5148
5148 # warn about failure to delete explicit files/dirs
5149 # warn about failure to delete explicit files/dirs
5149 wctx = repo[None]
5150 wctx = repo[None]
5150 for f in m.files():
5151 for f in m.files():
5151 if f in repo.dirstate or f in wctx.dirs():
5152 if f in repo.dirstate or f in wctx.dirs():
5152 continue
5153 continue
5153 if os.path.exists(m.rel(f)):
5154 if os.path.exists(m.rel(f)):
5154 if os.path.isdir(m.rel(f)):
5155 if os.path.isdir(m.rel(f)):
5155 ui.warn(_('not removing %s: no tracked files\n') % m.rel(f))
5156 ui.warn(_('not removing %s: no tracked files\n') % m.rel(f))
5156 else:
5157 else:
5157 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
5158 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
5158 # missing files will generate a warning elsewhere
5159 # missing files will generate a warning elsewhere
5159 ret = 1
5160 ret = 1
5160
5161
5161 if force:
5162 if force:
5162 list = modified + deleted + clean + added
5163 list = modified + deleted + clean + added
5163 elif after:
5164 elif after:
5164 list = deleted
5165 list = deleted
5165 for f in modified + added + clean:
5166 for f in modified + added + clean:
5166 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
5167 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
5167 ret = 1
5168 ret = 1
5168 else:
5169 else:
5169 list = deleted + clean
5170 list = deleted + clean
5170 for f in modified:
5171 for f in modified:
5171 ui.warn(_('not removing %s: file is modified (use -f'
5172 ui.warn(_('not removing %s: file is modified (use -f'
5172 ' to force removal)\n') % m.rel(f))
5173 ' to force removal)\n') % m.rel(f))
5173 ret = 1
5174 ret = 1
5174 for f in added:
5175 for f in added:
5175 ui.warn(_('not removing %s: file has been marked for add'
5176 ui.warn(_('not removing %s: file has been marked for add'
5176 ' (use forget to undo)\n') % m.rel(f))
5177 ' (use forget to undo)\n') % m.rel(f))
5177 ret = 1
5178 ret = 1
5178
5179
5179 for f in sorted(list):
5180 for f in sorted(list):
5180 if ui.verbose or not m.exact(f):
5181 if ui.verbose or not m.exact(f):
5181 ui.status(_('removing %s\n') % m.rel(f))
5182 ui.status(_('removing %s\n') % m.rel(f))
5182
5183
5183 wlock = repo.wlock()
5184 wlock = repo.wlock()
5184 try:
5185 try:
5185 if not after:
5186 if not after:
5186 for f in list:
5187 for f in list:
5187 if f in added:
5188 if f in added:
5188 continue # we never unlink added files on remove
5189 continue # we never unlink added files on remove
5189 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
5190 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
5190 repo[None].forget(list)
5191 repo[None].forget(list)
5191 finally:
5192 finally:
5192 wlock.release()
5193 wlock.release()
5193
5194
5194 return ret
5195 return ret
5195
5196
5196 @command('rename|move|mv',
5197 @command('rename|move|mv',
5197 [('A', 'after', None, _('record a rename that has already occurred')),
5198 [('A', 'after', None, _('record a rename that has already occurred')),
5198 ('f', 'force', None, _('forcibly copy over an existing managed file')),
5199 ('f', 'force', None, _('forcibly copy over an existing managed file')),
5199 ] + walkopts + dryrunopts,
5200 ] + walkopts + dryrunopts,
5200 _('[OPTION]... SOURCE... DEST'))
5201 _('[OPTION]... SOURCE... DEST'))
5201 def rename(ui, repo, *pats, **opts):
5202 def rename(ui, repo, *pats, **opts):
5202 """rename files; equivalent of copy + remove
5203 """rename files; equivalent of copy + remove
5203
5204
5204 Mark dest as copies of sources; mark sources for deletion. If dest
5205 Mark dest as copies of sources; mark sources for deletion. If dest
5205 is a directory, copies are put in that directory. If dest is a
5206 is a directory, copies are put in that directory. If dest is a
5206 file, there can only be one source.
5207 file, there can only be one source.
5207
5208
5208 By default, this command copies the contents of files as they
5209 By default, this command copies the contents of files as they
5209 exist in the working directory. If invoked with -A/--after, the
5210 exist in the working directory. If invoked with -A/--after, the
5210 operation is recorded, but no copying is performed.
5211 operation is recorded, but no copying is performed.
5211
5212
5212 This command takes effect at the next commit. To undo a rename
5213 This command takes effect at the next commit. To undo a rename
5213 before that, see :hg:`revert`.
5214 before that, see :hg:`revert`.
5214
5215
5215 Returns 0 on success, 1 if errors are encountered.
5216 Returns 0 on success, 1 if errors are encountered.
5216 """
5217 """
5217 wlock = repo.wlock(False)
5218 wlock = repo.wlock(False)
5218 try:
5219 try:
5219 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5220 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5220 finally:
5221 finally:
5221 wlock.release()
5222 wlock.release()
5222
5223
5223 @command('resolve',
5224 @command('resolve',
5224 [('a', 'all', None, _('select all unresolved files')),
5225 [('a', 'all', None, _('select all unresolved files')),
5225 ('l', 'list', None, _('list state of files needing merge')),
5226 ('l', 'list', None, _('list state of files needing merge')),
5226 ('m', 'mark', None, _('mark files as resolved')),
5227 ('m', 'mark', None, _('mark files as resolved')),
5227 ('u', 'unmark', None, _('mark files as unresolved')),
5228 ('u', 'unmark', None, _('mark files as unresolved')),
5228 ('n', 'no-status', None, _('hide status prefix'))]
5229 ('n', 'no-status', None, _('hide status prefix'))]
5229 + mergetoolopts + walkopts,
5230 + mergetoolopts + walkopts,
5230 _('[OPTION]... [FILE]...'),
5231 _('[OPTION]... [FILE]...'),
5231 inferrepo=True)
5232 inferrepo=True)
5232 def resolve(ui, repo, *pats, **opts):
5233 def resolve(ui, repo, *pats, **opts):
5233 """redo merges or set/view the merge status of files
5234 """redo merges or set/view the merge status of files
5234
5235
5235 Merges with unresolved conflicts are often the result of
5236 Merges with unresolved conflicts are often the result of
5236 non-interactive merging using the ``internal:merge`` configuration
5237 non-interactive merging using the ``internal:merge`` configuration
5237 setting, or a command-line merge tool like ``diff3``. The resolve
5238 setting, or a command-line merge tool like ``diff3``. The resolve
5238 command is used to manage the files involved in a merge, after
5239 command is used to manage the files involved in a merge, after
5239 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5240 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5240 working directory must have two parents). See :hg:`help
5241 working directory must have two parents). See :hg:`help
5241 merge-tools` for information on configuring merge tools.
5242 merge-tools` for information on configuring merge tools.
5242
5243
5243 The resolve command can be used in the following ways:
5244 The resolve command can be used in the following ways:
5244
5245
5245 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
5246 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
5246 files, discarding any previous merge attempts. Re-merging is not
5247 files, discarding any previous merge attempts. Re-merging is not
5247 performed for files already marked as resolved. Use ``--all/-a``
5248 performed for files already marked as resolved. Use ``--all/-a``
5248 to select all unresolved files. ``--tool`` can be used to specify
5249 to select all unresolved files. ``--tool`` can be used to specify
5249 the merge tool used for the given files. It overrides the HGMERGE
5250 the merge tool used for the given files. It overrides the HGMERGE
5250 environment variable and your configuration files. Previous file
5251 environment variable and your configuration files. Previous file
5251 contents are saved with a ``.orig`` suffix.
5252 contents are saved with a ``.orig`` suffix.
5252
5253
5253 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5254 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5254 (e.g. after having manually fixed-up the files). The default is
5255 (e.g. after having manually fixed-up the files). The default is
5255 to mark all unresolved files.
5256 to mark all unresolved files.
5256
5257
5257 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5258 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5258 default is to mark all resolved files.
5259 default is to mark all resolved files.
5259
5260
5260 - :hg:`resolve -l`: list files which had or still have conflicts.
5261 - :hg:`resolve -l`: list files which had or still have conflicts.
5261 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5262 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5262
5263
5263 Note that Mercurial will not let you commit files with unresolved
5264 Note that Mercurial will not let you commit files with unresolved
5264 merge conflicts. You must use :hg:`resolve -m ...` before you can
5265 merge conflicts. You must use :hg:`resolve -m ...` before you can
5265 commit after a conflicting merge.
5266 commit after a conflicting merge.
5266
5267
5267 Returns 0 on success, 1 if any files fail a resolve attempt.
5268 Returns 0 on success, 1 if any files fail a resolve attempt.
5268 """
5269 """
5269
5270
5270 all, mark, unmark, show, nostatus = \
5271 all, mark, unmark, show, nostatus = \
5271 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
5272 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
5272
5273
5273 if (show and (mark or unmark)) or (mark and unmark):
5274 if (show and (mark or unmark)) or (mark and unmark):
5274 raise util.Abort(_("too many options specified"))
5275 raise util.Abort(_("too many options specified"))
5275 if pats and all:
5276 if pats and all:
5276 raise util.Abort(_("can't specify --all and patterns"))
5277 raise util.Abort(_("can't specify --all and patterns"))
5277 if not (all or pats or show or mark or unmark):
5278 if not (all or pats or show or mark or unmark):
5278 raise util.Abort(_('no files or directories specified'),
5279 raise util.Abort(_('no files or directories specified'),
5279 hint=('use --all to remerge all files'))
5280 hint=('use --all to remerge all files'))
5280
5281
5281 wlock = repo.wlock()
5282 wlock = repo.wlock()
5282 try:
5283 try:
5283 ms = mergemod.mergestate(repo)
5284 ms = mergemod.mergestate(repo)
5284
5285
5285 if not ms.active() and not show:
5286 if not ms.active() and not show:
5286 raise util.Abort(
5287 raise util.Abort(
5287 _('resolve command not applicable when not merging'))
5288 _('resolve command not applicable when not merging'))
5288
5289
5289 m = scmutil.match(repo[None], pats, opts)
5290 m = scmutil.match(repo[None], pats, opts)
5290 ret = 0
5291 ret = 0
5291 didwork = False
5292 didwork = False
5292
5293
5293 for f in ms:
5294 for f in ms:
5294 if not m(f):
5295 if not m(f):
5295 continue
5296 continue
5296
5297
5297 didwork = True
5298 didwork = True
5298
5299
5299 if show:
5300 if show:
5300 if nostatus:
5301 if nostatus:
5301 ui.write("%s\n" % f)
5302 ui.write("%s\n" % f)
5302 else:
5303 else:
5303 ui.write("%s %s\n" % (ms[f].upper(), f),
5304 ui.write("%s %s\n" % (ms[f].upper(), f),
5304 label='resolve.' +
5305 label='resolve.' +
5305 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
5306 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
5306 elif mark:
5307 elif mark:
5307 ms.mark(f, "r")
5308 ms.mark(f, "r")
5308 elif unmark:
5309 elif unmark:
5309 ms.mark(f, "u")
5310 ms.mark(f, "u")
5310 else:
5311 else:
5311 wctx = repo[None]
5312 wctx = repo[None]
5312
5313
5313 # backup pre-resolve (merge uses .orig for its own purposes)
5314 # backup pre-resolve (merge uses .orig for its own purposes)
5314 a = repo.wjoin(f)
5315 a = repo.wjoin(f)
5315 util.copyfile(a, a + ".resolve")
5316 util.copyfile(a, a + ".resolve")
5316
5317
5317 try:
5318 try:
5318 # resolve file
5319 # resolve file
5319 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
5320 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
5320 'resolve')
5321 'resolve')
5321 if ms.resolve(f, wctx):
5322 if ms.resolve(f, wctx):
5322 ret = 1
5323 ret = 1
5323 finally:
5324 finally:
5324 ui.setconfig('ui', 'forcemerge', '', 'resolve')
5325 ui.setconfig('ui', 'forcemerge', '', 'resolve')
5325 ms.commit()
5326 ms.commit()
5326
5327
5327 # replace filemerge's .orig file with our resolve file
5328 # replace filemerge's .orig file with our resolve file
5328 util.rename(a + ".resolve", a + ".orig")
5329 util.rename(a + ".resolve", a + ".orig")
5329
5330
5330 ms.commit()
5331 ms.commit()
5331
5332
5332 if not didwork and pats:
5333 if not didwork and pats:
5333 ui.warn(_("arguments do not match paths that need resolving\n"))
5334 ui.warn(_("arguments do not match paths that need resolving\n"))
5334
5335
5335 finally:
5336 finally:
5336 wlock.release()
5337 wlock.release()
5337
5338
5338 # Nudge users into finishing an unfinished operation. We don't print
5339 # Nudge users into finishing an unfinished operation. We don't print
5339 # this with the list/show operation because we want list/show to remain
5340 # this with the list/show operation because we want list/show to remain
5340 # machine readable.
5341 # machine readable.
5341 if not list(ms.unresolved()) and not show:
5342 if not list(ms.unresolved()) and not show:
5342 ui.status(_('(no more unresolved files)\n'))
5343 ui.status(_('(no more unresolved files)\n'))
5343
5344
5344 return ret
5345 return ret
5345
5346
5346 @command('revert',
5347 @command('revert',
5347 [('a', 'all', None, _('revert all changes when no arguments given')),
5348 [('a', 'all', None, _('revert all changes when no arguments given')),
5348 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5349 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5349 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5350 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5350 ('C', 'no-backup', None, _('do not save backup copies of files')),
5351 ('C', 'no-backup', None, _('do not save backup copies of files')),
5351 ] + walkopts + dryrunopts,
5352 ] + walkopts + dryrunopts,
5352 _('[OPTION]... [-r REV] [NAME]...'))
5353 _('[OPTION]... [-r REV] [NAME]...'))
5353 def revert(ui, repo, *pats, **opts):
5354 def revert(ui, repo, *pats, **opts):
5354 """restore files to their checkout state
5355 """restore files to their checkout state
5355
5356
5356 .. note::
5357 .. note::
5357
5358
5358 To check out earlier revisions, you should use :hg:`update REV`.
5359 To check out earlier revisions, you should use :hg:`update REV`.
5359 To cancel an uncommitted merge (and lose your changes),
5360 To cancel an uncommitted merge (and lose your changes),
5360 use :hg:`update --clean .`.
5361 use :hg:`update --clean .`.
5361
5362
5362 With no revision specified, revert the specified files or directories
5363 With no revision specified, revert the specified files or directories
5363 to the contents they had in the parent of the working directory.
5364 to the contents they had in the parent of the working directory.
5364 This restores the contents of files to an unmodified
5365 This restores the contents of files to an unmodified
5365 state and unschedules adds, removes, copies, and renames. If the
5366 state and unschedules adds, removes, copies, and renames. If the
5366 working directory has two parents, you must explicitly specify a
5367 working directory has two parents, you must explicitly specify a
5367 revision.
5368 revision.
5368
5369
5369 Using the -r/--rev or -d/--date options, revert the given files or
5370 Using the -r/--rev or -d/--date options, revert the given files or
5370 directories to their states as of a specific revision. Because
5371 directories to their states as of a specific revision. Because
5371 revert does not change the working directory parents, this will
5372 revert does not change the working directory parents, this will
5372 cause these files to appear modified. This can be helpful to "back
5373 cause these files to appear modified. This can be helpful to "back
5373 out" some or all of an earlier change. See :hg:`backout` for a
5374 out" some or all of an earlier change. See :hg:`backout` for a
5374 related method.
5375 related method.
5375
5376
5376 Modified files are saved with a .orig suffix before reverting.
5377 Modified files are saved with a .orig suffix before reverting.
5377 To disable these backups, use --no-backup.
5378 To disable these backups, use --no-backup.
5378
5379
5379 See :hg:`help dates` for a list of formats valid for -d/--date.
5380 See :hg:`help dates` for a list of formats valid for -d/--date.
5380
5381
5381 Returns 0 on success.
5382 Returns 0 on success.
5382 """
5383 """
5383
5384
5384 if opts.get("date"):
5385 if opts.get("date"):
5385 if opts.get("rev"):
5386 if opts.get("rev"):
5386 raise util.Abort(_("you can't specify a revision and a date"))
5387 raise util.Abort(_("you can't specify a revision and a date"))
5387 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5388 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5388
5389
5389 parent, p2 = repo.dirstate.parents()
5390 parent, p2 = repo.dirstate.parents()
5390 if not opts.get('rev') and p2 != nullid:
5391 if not opts.get('rev') and p2 != nullid:
5391 # revert after merge is a trap for new users (issue2915)
5392 # revert after merge is a trap for new users (issue2915)
5392 raise util.Abort(_('uncommitted merge with no revision specified'),
5393 raise util.Abort(_('uncommitted merge with no revision specified'),
5393 hint=_('use "hg update" or see "hg help revert"'))
5394 hint=_('use "hg update" or see "hg help revert"'))
5394
5395
5395 ctx = scmutil.revsingle(repo, opts.get('rev'))
5396 ctx = scmutil.revsingle(repo, opts.get('rev'))
5396
5397
5397 if not pats and not opts.get('all'):
5398 if not pats and not opts.get('all'):
5398 msg = _("no files or directories specified")
5399 msg = _("no files or directories specified")
5399 if p2 != nullid:
5400 if p2 != nullid:
5400 hint = _("uncommitted merge, use --all to discard all changes,"
5401 hint = _("uncommitted merge, use --all to discard all changes,"
5401 " or 'hg update -C .' to abort the merge")
5402 " or 'hg update -C .' to abort the merge")
5402 raise util.Abort(msg, hint=hint)
5403 raise util.Abort(msg, hint=hint)
5403 dirty = util.any(repo.status())
5404 dirty = util.any(repo.status())
5404 node = ctx.node()
5405 node = ctx.node()
5405 if node != parent:
5406 if node != parent:
5406 if dirty:
5407 if dirty:
5407 hint = _("uncommitted changes, use --all to discard all"
5408 hint = _("uncommitted changes, use --all to discard all"
5408 " changes, or 'hg update %s' to update") % ctx.rev()
5409 " changes, or 'hg update %s' to update") % ctx.rev()
5409 else:
5410 else:
5410 hint = _("use --all to revert all files,"
5411 hint = _("use --all to revert all files,"
5411 " or 'hg update %s' to update") % ctx.rev()
5412 " or 'hg update %s' to update") % ctx.rev()
5412 elif dirty:
5413 elif dirty:
5413 hint = _("uncommitted changes, use --all to discard all changes")
5414 hint = _("uncommitted changes, use --all to discard all changes")
5414 else:
5415 else:
5415 hint = _("use --all to revert all files")
5416 hint = _("use --all to revert all files")
5416 raise util.Abort(msg, hint=hint)
5417 raise util.Abort(msg, hint=hint)
5417
5418
5418 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
5419 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
5419
5420
5420 @command('rollback', dryrunopts +
5421 @command('rollback', dryrunopts +
5421 [('f', 'force', False, _('ignore safety measures'))])
5422 [('f', 'force', False, _('ignore safety measures'))])
5422 def rollback(ui, repo, **opts):
5423 def rollback(ui, repo, **opts):
5423 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5424 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5424
5425
5425 Please use :hg:`commit --amend` instead of rollback to correct
5426 Please use :hg:`commit --amend` instead of rollback to correct
5426 mistakes in the last commit.
5427 mistakes in the last commit.
5427
5428
5428 This command should be used with care. There is only one level of
5429 This command should be used with care. There is only one level of
5429 rollback, and there is no way to undo a rollback. It will also
5430 rollback, and there is no way to undo a rollback. It will also
5430 restore the dirstate at the time of the last transaction, losing
5431 restore the dirstate at the time of the last transaction, losing
5431 any dirstate changes since that time. This command does not alter
5432 any dirstate changes since that time. This command does not alter
5432 the working directory.
5433 the working directory.
5433
5434
5434 Transactions are used to encapsulate the effects of all commands
5435 Transactions are used to encapsulate the effects of all commands
5435 that create new changesets or propagate existing changesets into a
5436 that create new changesets or propagate existing changesets into a
5436 repository.
5437 repository.
5437
5438
5438 .. container:: verbose
5439 .. container:: verbose
5439
5440
5440 For example, the following commands are transactional, and their
5441 For example, the following commands are transactional, and their
5441 effects can be rolled back:
5442 effects can be rolled back:
5442
5443
5443 - commit
5444 - commit
5444 - import
5445 - import
5445 - pull
5446 - pull
5446 - push (with this repository as the destination)
5447 - push (with this repository as the destination)
5447 - unbundle
5448 - unbundle
5448
5449
5449 To avoid permanent data loss, rollback will refuse to rollback a
5450 To avoid permanent data loss, rollback will refuse to rollback a
5450 commit transaction if it isn't checked out. Use --force to
5451 commit transaction if it isn't checked out. Use --force to
5451 override this protection.
5452 override this protection.
5452
5453
5453 This command is not intended for use on public repositories. Once
5454 This command is not intended for use on public repositories. Once
5454 changes are visible for pull by other users, rolling a transaction
5455 changes are visible for pull by other users, rolling a transaction
5455 back locally is ineffective (someone else may already have pulled
5456 back locally is ineffective (someone else may already have pulled
5456 the changes). Furthermore, a race is possible with readers of the
5457 the changes). Furthermore, a race is possible with readers of the
5457 repository; for example an in-progress pull from the repository
5458 repository; for example an in-progress pull from the repository
5458 may fail if a rollback is performed.
5459 may fail if a rollback is performed.
5459
5460
5460 Returns 0 on success, 1 if no rollback data is available.
5461 Returns 0 on success, 1 if no rollback data is available.
5461 """
5462 """
5462 return repo.rollback(dryrun=opts.get('dry_run'),
5463 return repo.rollback(dryrun=opts.get('dry_run'),
5463 force=opts.get('force'))
5464 force=opts.get('force'))
5464
5465
5465 @command('root', [])
5466 @command('root', [])
5466 def root(ui, repo):
5467 def root(ui, repo):
5467 """print the root (top) of the current working directory
5468 """print the root (top) of the current working directory
5468
5469
5469 Print the root directory of the current repository.
5470 Print the root directory of the current repository.
5470
5471
5471 Returns 0 on success.
5472 Returns 0 on success.
5472 """
5473 """
5473 ui.write(repo.root + "\n")
5474 ui.write(repo.root + "\n")
5474
5475
5475 @command('^serve',
5476 @command('^serve',
5476 [('A', 'accesslog', '', _('name of access log file to write to'),
5477 [('A', 'accesslog', '', _('name of access log file to write to'),
5477 _('FILE')),
5478 _('FILE')),
5478 ('d', 'daemon', None, _('run server in background')),
5479 ('d', 'daemon', None, _('run server in background')),
5479 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
5480 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
5480 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5481 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5481 # use string type, then we can check if something was passed
5482 # use string type, then we can check if something was passed
5482 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5483 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5483 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5484 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5484 _('ADDR')),
5485 _('ADDR')),
5485 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5486 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5486 _('PREFIX')),
5487 _('PREFIX')),
5487 ('n', 'name', '',
5488 ('n', 'name', '',
5488 _('name to show in web pages (default: working directory)'), _('NAME')),
5489 _('name to show in web pages (default: working directory)'), _('NAME')),
5489 ('', 'web-conf', '',
5490 ('', 'web-conf', '',
5490 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
5491 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
5491 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5492 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5492 _('FILE')),
5493 _('FILE')),
5493 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5494 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5494 ('', 'stdio', None, _('for remote clients')),
5495 ('', 'stdio', None, _('for remote clients')),
5495 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
5496 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
5496 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5497 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5497 ('', 'style', '', _('template style to use'), _('STYLE')),
5498 ('', 'style', '', _('template style to use'), _('STYLE')),
5498 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5499 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5499 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
5500 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
5500 _('[OPTION]...'),
5501 _('[OPTION]...'),
5501 optionalrepo=True)
5502 optionalrepo=True)
5502 def serve(ui, repo, **opts):
5503 def serve(ui, repo, **opts):
5503 """start stand-alone webserver
5504 """start stand-alone webserver
5504
5505
5505 Start a local HTTP repository browser and pull server. You can use
5506 Start a local HTTP repository browser and pull server. You can use
5506 this for ad-hoc sharing and browsing of repositories. It is
5507 this for ad-hoc sharing and browsing of repositories. It is
5507 recommended to use a real web server to serve a repository for
5508 recommended to use a real web server to serve a repository for
5508 longer periods of time.
5509 longer periods of time.
5509
5510
5510 Please note that the server does not implement access control.
5511 Please note that the server does not implement access control.
5511 This means that, by default, anybody can read from the server and
5512 This means that, by default, anybody can read from the server and
5512 nobody can write to it by default. Set the ``web.allow_push``
5513 nobody can write to it by default. Set the ``web.allow_push``
5513 option to ``*`` to allow everybody to push to the server. You
5514 option to ``*`` to allow everybody to push to the server. You
5514 should use a real web server if you need to authenticate users.
5515 should use a real web server if you need to authenticate users.
5515
5516
5516 By default, the server logs accesses to stdout and errors to
5517 By default, the server logs accesses to stdout and errors to
5517 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5518 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5518 files.
5519 files.
5519
5520
5520 To have the server choose a free port number to listen on, specify
5521 To have the server choose a free port number to listen on, specify
5521 a port number of 0; in this case, the server will print the port
5522 a port number of 0; in this case, the server will print the port
5522 number it uses.
5523 number it uses.
5523
5524
5524 Returns 0 on success.
5525 Returns 0 on success.
5525 """
5526 """
5526
5527
5527 if opts["stdio"] and opts["cmdserver"]:
5528 if opts["stdio"] and opts["cmdserver"]:
5528 raise util.Abort(_("cannot use --stdio with --cmdserver"))
5529 raise util.Abort(_("cannot use --stdio with --cmdserver"))
5529
5530
5530 if opts["stdio"]:
5531 if opts["stdio"]:
5531 if repo is None:
5532 if repo is None:
5532 raise error.RepoError(_("there is no Mercurial repository here"
5533 raise error.RepoError(_("there is no Mercurial repository here"
5533 " (.hg not found)"))
5534 " (.hg not found)"))
5534 s = sshserver.sshserver(ui, repo)
5535 s = sshserver.sshserver(ui, repo)
5535 s.serve_forever()
5536 s.serve_forever()
5536
5537
5537 if opts["cmdserver"]:
5538 if opts["cmdserver"]:
5538 s = commandserver.server(ui, repo, opts["cmdserver"])
5539 s = commandserver.server(ui, repo, opts["cmdserver"])
5539 return s.serve()
5540 return s.serve()
5540
5541
5541 # this way we can check if something was given in the command-line
5542 # this way we can check if something was given in the command-line
5542 if opts.get('port'):
5543 if opts.get('port'):
5543 opts['port'] = util.getport(opts.get('port'))
5544 opts['port'] = util.getport(opts.get('port'))
5544
5545
5545 baseui = repo and repo.baseui or ui
5546 baseui = repo and repo.baseui or ui
5546 optlist = ("name templates style address port prefix ipv6"
5547 optlist = ("name templates style address port prefix ipv6"
5547 " accesslog errorlog certificate encoding")
5548 " accesslog errorlog certificate encoding")
5548 for o in optlist.split():
5549 for o in optlist.split():
5549 val = opts.get(o, '')
5550 val = opts.get(o, '')
5550 if val in (None, ''): # should check against default options instead
5551 if val in (None, ''): # should check against default options instead
5551 continue
5552 continue
5552 baseui.setconfig("web", o, val, 'serve')
5553 baseui.setconfig("web", o, val, 'serve')
5553 if repo and repo.ui != baseui:
5554 if repo and repo.ui != baseui:
5554 repo.ui.setconfig("web", o, val, 'serve')
5555 repo.ui.setconfig("web", o, val, 'serve')
5555
5556
5556 o = opts.get('web_conf') or opts.get('webdir_conf')
5557 o = opts.get('web_conf') or opts.get('webdir_conf')
5557 if not o:
5558 if not o:
5558 if not repo:
5559 if not repo:
5559 raise error.RepoError(_("there is no Mercurial repository"
5560 raise error.RepoError(_("there is no Mercurial repository"
5560 " here (.hg not found)"))
5561 " here (.hg not found)"))
5561 o = repo
5562 o = repo
5562
5563
5563 app = hgweb.hgweb(o, baseui=baseui)
5564 app = hgweb.hgweb(o, baseui=baseui)
5564 service = httpservice(ui, app, opts)
5565 service = httpservice(ui, app, opts)
5565 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5566 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5566
5567
5567 class httpservice(object):
5568 class httpservice(object):
5568 def __init__(self, ui, app, opts):
5569 def __init__(self, ui, app, opts):
5569 self.ui = ui
5570 self.ui = ui
5570 self.app = app
5571 self.app = app
5571 self.opts = opts
5572 self.opts = opts
5572
5573
5573 def init(self):
5574 def init(self):
5574 util.setsignalhandler()
5575 util.setsignalhandler()
5575 self.httpd = hgweb_server.create_server(self.ui, self.app)
5576 self.httpd = hgweb_server.create_server(self.ui, self.app)
5576
5577
5577 if self.opts['port'] and not self.ui.verbose:
5578 if self.opts['port'] and not self.ui.verbose:
5578 return
5579 return
5579
5580
5580 if self.httpd.prefix:
5581 if self.httpd.prefix:
5581 prefix = self.httpd.prefix.strip('/') + '/'
5582 prefix = self.httpd.prefix.strip('/') + '/'
5582 else:
5583 else:
5583 prefix = ''
5584 prefix = ''
5584
5585
5585 port = ':%d' % self.httpd.port
5586 port = ':%d' % self.httpd.port
5586 if port == ':80':
5587 if port == ':80':
5587 port = ''
5588 port = ''
5588
5589
5589 bindaddr = self.httpd.addr
5590 bindaddr = self.httpd.addr
5590 if bindaddr == '0.0.0.0':
5591 if bindaddr == '0.0.0.0':
5591 bindaddr = '*'
5592 bindaddr = '*'
5592 elif ':' in bindaddr: # IPv6
5593 elif ':' in bindaddr: # IPv6
5593 bindaddr = '[%s]' % bindaddr
5594 bindaddr = '[%s]' % bindaddr
5594
5595
5595 fqaddr = self.httpd.fqaddr
5596 fqaddr = self.httpd.fqaddr
5596 if ':' in fqaddr:
5597 if ':' in fqaddr:
5597 fqaddr = '[%s]' % fqaddr
5598 fqaddr = '[%s]' % fqaddr
5598 if self.opts['port']:
5599 if self.opts['port']:
5599 write = self.ui.status
5600 write = self.ui.status
5600 else:
5601 else:
5601 write = self.ui.write
5602 write = self.ui.write
5602 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5603 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5603 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5604 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5604 self.ui.flush() # avoid buffering of status message
5605 self.ui.flush() # avoid buffering of status message
5605
5606
5606 def run(self):
5607 def run(self):
5607 self.httpd.serve_forever()
5608 self.httpd.serve_forever()
5608
5609
5609
5610
5610 @command('^status|st',
5611 @command('^status|st',
5611 [('A', 'all', None, _('show status of all files')),
5612 [('A', 'all', None, _('show status of all files')),
5612 ('m', 'modified', None, _('show only modified files')),
5613 ('m', 'modified', None, _('show only modified files')),
5613 ('a', 'added', None, _('show only added files')),
5614 ('a', 'added', None, _('show only added files')),
5614 ('r', 'removed', None, _('show only removed files')),
5615 ('r', 'removed', None, _('show only removed files')),
5615 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5616 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5616 ('c', 'clean', None, _('show only files without changes')),
5617 ('c', 'clean', None, _('show only files without changes')),
5617 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5618 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5618 ('i', 'ignored', None, _('show only ignored files')),
5619 ('i', 'ignored', None, _('show only ignored files')),
5619 ('n', 'no-status', None, _('hide status prefix')),
5620 ('n', 'no-status', None, _('hide status prefix')),
5620 ('C', 'copies', None, _('show source of copied files')),
5621 ('C', 'copies', None, _('show source of copied files')),
5621 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5622 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5622 ('', 'rev', [], _('show difference from revision'), _('REV')),
5623 ('', 'rev', [], _('show difference from revision'), _('REV')),
5623 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5624 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5624 ] + walkopts + subrepoopts + formatteropts,
5625 ] + walkopts + subrepoopts + formatteropts,
5625 _('[OPTION]... [FILE]...'),
5626 _('[OPTION]... [FILE]...'),
5626 inferrepo=True)
5627 inferrepo=True)
5627 def status(ui, repo, *pats, **opts):
5628 def status(ui, repo, *pats, **opts):
5628 """show changed files in the working directory
5629 """show changed files in the working directory
5629
5630
5630 Show status of files in the repository. If names are given, only
5631 Show status of files in the repository. If names are given, only
5631 files that match are shown. Files that are clean or ignored or
5632 files that match are shown. Files that are clean or ignored or
5632 the source of a copy/move operation, are not listed unless
5633 the source of a copy/move operation, are not listed unless
5633 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5634 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5634 Unless options described with "show only ..." are given, the
5635 Unless options described with "show only ..." are given, the
5635 options -mardu are used.
5636 options -mardu are used.
5636
5637
5637 Option -q/--quiet hides untracked (unknown and ignored) files
5638 Option -q/--quiet hides untracked (unknown and ignored) files
5638 unless explicitly requested with -u/--unknown or -i/--ignored.
5639 unless explicitly requested with -u/--unknown or -i/--ignored.
5639
5640
5640 .. note::
5641 .. note::
5641
5642
5642 status may appear to disagree with diff if permissions have
5643 status may appear to disagree with diff if permissions have
5643 changed or a merge has occurred. The standard diff format does
5644 changed or a merge has occurred. The standard diff format does
5644 not report permission changes and diff only reports changes
5645 not report permission changes and diff only reports changes
5645 relative to one merge parent.
5646 relative to one merge parent.
5646
5647
5647 If one revision is given, it is used as the base revision.
5648 If one revision is given, it is used as the base revision.
5648 If two revisions are given, the differences between them are
5649 If two revisions are given, the differences between them are
5649 shown. The --change option can also be used as a shortcut to list
5650 shown. The --change option can also be used as a shortcut to list
5650 the changed files of a revision from its first parent.
5651 the changed files of a revision from its first parent.
5651
5652
5652 The codes used to show the status of files are::
5653 The codes used to show the status of files are::
5653
5654
5654 M = modified
5655 M = modified
5655 A = added
5656 A = added
5656 R = removed
5657 R = removed
5657 C = clean
5658 C = clean
5658 ! = missing (deleted by non-hg command, but still tracked)
5659 ! = missing (deleted by non-hg command, but still tracked)
5659 ? = not tracked
5660 ? = not tracked
5660 I = ignored
5661 I = ignored
5661 = origin of the previous file (with --copies)
5662 = origin of the previous file (with --copies)
5662
5663
5663 .. container:: verbose
5664 .. container:: verbose
5664
5665
5665 Examples:
5666 Examples:
5666
5667
5667 - show changes in the working directory relative to a
5668 - show changes in the working directory relative to a
5668 changeset::
5669 changeset::
5669
5670
5670 hg status --rev 9353
5671 hg status --rev 9353
5671
5672
5672 - show all changes including copies in an existing changeset::
5673 - show all changes including copies in an existing changeset::
5673
5674
5674 hg status --copies --change 9353
5675 hg status --copies --change 9353
5675
5676
5676 - get a NUL separated list of added files, suitable for xargs::
5677 - get a NUL separated list of added files, suitable for xargs::
5677
5678
5678 hg status -an0
5679 hg status -an0
5679
5680
5680 Returns 0 on success.
5681 Returns 0 on success.
5681 """
5682 """
5682
5683
5683 revs = opts.get('rev')
5684 revs = opts.get('rev')
5684 change = opts.get('change')
5685 change = opts.get('change')
5685
5686
5686 if revs and change:
5687 if revs and change:
5687 msg = _('cannot specify --rev and --change at the same time')
5688 msg = _('cannot specify --rev and --change at the same time')
5688 raise util.Abort(msg)
5689 raise util.Abort(msg)
5689 elif change:
5690 elif change:
5690 node2 = scmutil.revsingle(repo, change, None).node()
5691 node2 = scmutil.revsingle(repo, change, None).node()
5691 node1 = repo[node2].p1().node()
5692 node1 = repo[node2].p1().node()
5692 else:
5693 else:
5693 node1, node2 = scmutil.revpair(repo, revs)
5694 node1, node2 = scmutil.revpair(repo, revs)
5694
5695
5695 cwd = (pats and repo.getcwd()) or ''
5696 cwd = (pats and repo.getcwd()) or ''
5696 end = opts.get('print0') and '\0' or '\n'
5697 end = opts.get('print0') and '\0' or '\n'
5697 copy = {}
5698 copy = {}
5698 states = 'modified added removed deleted unknown ignored clean'.split()
5699 states = 'modified added removed deleted unknown ignored clean'.split()
5699 show = [k for k in states if opts.get(k)]
5700 show = [k for k in states if opts.get(k)]
5700 if opts.get('all'):
5701 if opts.get('all'):
5701 show += ui.quiet and (states[:4] + ['clean']) or states
5702 show += ui.quiet and (states[:4] + ['clean']) or states
5702 if not show:
5703 if not show:
5703 show = ui.quiet and states[:4] or states[:5]
5704 show = ui.quiet and states[:4] or states[:5]
5704
5705
5705 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5706 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5706 'ignored' in show, 'clean' in show, 'unknown' in show,
5707 'ignored' in show, 'clean' in show, 'unknown' in show,
5707 opts.get('subrepos'))
5708 opts.get('subrepos'))
5708 changestates = zip(states, 'MAR!?IC', stat)
5709 changestates = zip(states, 'MAR!?IC', stat)
5709
5710
5710 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5711 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5711 copy = copies.pathcopies(repo[node1], repo[node2])
5712 copy = copies.pathcopies(repo[node1], repo[node2])
5712
5713
5713 fm = ui.formatter('status', opts)
5714 fm = ui.formatter('status', opts)
5714 fmt = '%s' + end
5715 fmt = '%s' + end
5715 showchar = not opts.get('no_status')
5716 showchar = not opts.get('no_status')
5716
5717
5717 for state, char, files in changestates:
5718 for state, char, files in changestates:
5718 if state in show:
5719 if state in show:
5719 label = 'status.' + state
5720 label = 'status.' + state
5720 for f in files:
5721 for f in files:
5721 fm.startitem()
5722 fm.startitem()
5722 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5723 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5723 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
5724 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
5724 if f in copy:
5725 if f in copy:
5725 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5726 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5726 label='status.copied')
5727 label='status.copied')
5727 fm.end()
5728 fm.end()
5728
5729
5729 @command('^summary|sum',
5730 @command('^summary|sum',
5730 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5731 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5731 def summary(ui, repo, **opts):
5732 def summary(ui, repo, **opts):
5732 """summarize working directory state
5733 """summarize working directory state
5733
5734
5734 This generates a brief summary of the working directory state,
5735 This generates a brief summary of the working directory state,
5735 including parents, branch, commit status, and available updates.
5736 including parents, branch, commit status, and available updates.
5736
5737
5737 With the --remote option, this will check the default paths for
5738 With the --remote option, this will check the default paths for
5738 incoming and outgoing changes. This can be time-consuming.
5739 incoming and outgoing changes. This can be time-consuming.
5739
5740
5740 Returns 0 on success.
5741 Returns 0 on success.
5741 """
5742 """
5742
5743
5743 ctx = repo[None]
5744 ctx = repo[None]
5744 parents = ctx.parents()
5745 parents = ctx.parents()
5745 pnode = parents[0].node()
5746 pnode = parents[0].node()
5746 marks = []
5747 marks = []
5747
5748
5748 for p in parents:
5749 for p in parents:
5749 # label with log.changeset (instead of log.parent) since this
5750 # label with log.changeset (instead of log.parent) since this
5750 # shows a working directory parent *changeset*:
5751 # shows a working directory parent *changeset*:
5751 # i18n: column positioning for "hg summary"
5752 # i18n: column positioning for "hg summary"
5752 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5753 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5753 label='log.changeset changeset.%s' % p.phasestr())
5754 label='log.changeset changeset.%s' % p.phasestr())
5754 ui.write(' '.join(p.tags()), label='log.tag')
5755 ui.write(' '.join(p.tags()), label='log.tag')
5755 if p.bookmarks():
5756 if p.bookmarks():
5756 marks.extend(p.bookmarks())
5757 marks.extend(p.bookmarks())
5757 if p.rev() == -1:
5758 if p.rev() == -1:
5758 if not len(repo):
5759 if not len(repo):
5759 ui.write(_(' (empty repository)'))
5760 ui.write(_(' (empty repository)'))
5760 else:
5761 else:
5761 ui.write(_(' (no revision checked out)'))
5762 ui.write(_(' (no revision checked out)'))
5762 ui.write('\n')
5763 ui.write('\n')
5763 if p.description():
5764 if p.description():
5764 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5765 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5765 label='log.summary')
5766 label='log.summary')
5766
5767
5767 branch = ctx.branch()
5768 branch = ctx.branch()
5768 bheads = repo.branchheads(branch)
5769 bheads = repo.branchheads(branch)
5769 # i18n: column positioning for "hg summary"
5770 # i18n: column positioning for "hg summary"
5770 m = _('branch: %s\n') % branch
5771 m = _('branch: %s\n') % branch
5771 if branch != 'default':
5772 if branch != 'default':
5772 ui.write(m, label='log.branch')
5773 ui.write(m, label='log.branch')
5773 else:
5774 else:
5774 ui.status(m, label='log.branch')
5775 ui.status(m, label='log.branch')
5775
5776
5776 if marks:
5777 if marks:
5777 current = repo._bookmarkcurrent
5778 current = repo._bookmarkcurrent
5778 # i18n: column positioning for "hg summary"
5779 # i18n: column positioning for "hg summary"
5779 ui.write(_('bookmarks:'), label='log.bookmark')
5780 ui.write(_('bookmarks:'), label='log.bookmark')
5780 if current is not None:
5781 if current is not None:
5781 if current in marks:
5782 if current in marks:
5782 ui.write(' *' + current, label='bookmarks.current')
5783 ui.write(' *' + current, label='bookmarks.current')
5783 marks.remove(current)
5784 marks.remove(current)
5784 else:
5785 else:
5785 ui.write(' [%s]' % current, label='bookmarks.current')
5786 ui.write(' [%s]' % current, label='bookmarks.current')
5786 for m in marks:
5787 for m in marks:
5787 ui.write(' ' + m, label='log.bookmark')
5788 ui.write(' ' + m, label='log.bookmark')
5788 ui.write('\n', label='log.bookmark')
5789 ui.write('\n', label='log.bookmark')
5789
5790
5790 status = repo.status(unknown=True)
5791 status = repo.status(unknown=True)
5791
5792
5792 c = repo.dirstate.copies()
5793 c = repo.dirstate.copies()
5793 copied, renamed = [], []
5794 copied, renamed = [], []
5794 for d, s in c.iteritems():
5795 for d, s in c.iteritems():
5795 if s in status.removed:
5796 if s in status.removed:
5796 status.removed.remove(s)
5797 status.removed.remove(s)
5797 renamed.append(d)
5798 renamed.append(d)
5798 else:
5799 else:
5799 copied.append(d)
5800 copied.append(d)
5800 if d in status.added:
5801 if d in status.added:
5801 status.added.remove(d)
5802 status.added.remove(d)
5802
5803
5803 ms = mergemod.mergestate(repo)
5804 ms = mergemod.mergestate(repo)
5804 unresolved = [f for f in ms if ms[f] == 'u']
5805 unresolved = [f for f in ms if ms[f] == 'u']
5805
5806
5806 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5807 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5807
5808
5808 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5809 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5809 (ui.label(_('%d added'), 'status.added'), status.added),
5810 (ui.label(_('%d added'), 'status.added'), status.added),
5810 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5811 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5811 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5812 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5812 (ui.label(_('%d copied'), 'status.copied'), copied),
5813 (ui.label(_('%d copied'), 'status.copied'), copied),
5813 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5814 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5814 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5815 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5815 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5816 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5816 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5817 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5817 t = []
5818 t = []
5818 for l, s in labels:
5819 for l, s in labels:
5819 if s:
5820 if s:
5820 t.append(l % len(s))
5821 t.append(l % len(s))
5821
5822
5822 t = ', '.join(t)
5823 t = ', '.join(t)
5823 cleanworkdir = False
5824 cleanworkdir = False
5824
5825
5825 if repo.vfs.exists('updatestate'):
5826 if repo.vfs.exists('updatestate'):
5826 t += _(' (interrupted update)')
5827 t += _(' (interrupted update)')
5827 elif len(parents) > 1:
5828 elif len(parents) > 1:
5828 t += _(' (merge)')
5829 t += _(' (merge)')
5829 elif branch != parents[0].branch():
5830 elif branch != parents[0].branch():
5830 t += _(' (new branch)')
5831 t += _(' (new branch)')
5831 elif (parents[0].closesbranch() and
5832 elif (parents[0].closesbranch() and
5832 pnode in repo.branchheads(branch, closed=True)):
5833 pnode in repo.branchheads(branch, closed=True)):
5833 t += _(' (head closed)')
5834 t += _(' (head closed)')
5834 elif not (status.modified or status.added or status.removed or renamed or
5835 elif not (status.modified or status.added or status.removed or renamed or
5835 copied or subs):
5836 copied or subs):
5836 t += _(' (clean)')
5837 t += _(' (clean)')
5837 cleanworkdir = True
5838 cleanworkdir = True
5838 elif pnode not in bheads:
5839 elif pnode not in bheads:
5839 t += _(' (new branch head)')
5840 t += _(' (new branch head)')
5840
5841
5841 if cleanworkdir:
5842 if cleanworkdir:
5842 # i18n: column positioning for "hg summary"
5843 # i18n: column positioning for "hg summary"
5843 ui.status(_('commit: %s\n') % t.strip())
5844 ui.status(_('commit: %s\n') % t.strip())
5844 else:
5845 else:
5845 # i18n: column positioning for "hg summary"
5846 # i18n: column positioning for "hg summary"
5846 ui.write(_('commit: %s\n') % t.strip())
5847 ui.write(_('commit: %s\n') % t.strip())
5847
5848
5848 # all ancestors of branch heads - all ancestors of parent = new csets
5849 # all ancestors of branch heads - all ancestors of parent = new csets
5849 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5850 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5850 bheads))
5851 bheads))
5851
5852
5852 if new == 0:
5853 if new == 0:
5853 # i18n: column positioning for "hg summary"
5854 # i18n: column positioning for "hg summary"
5854 ui.status(_('update: (current)\n'))
5855 ui.status(_('update: (current)\n'))
5855 elif pnode not in bheads:
5856 elif pnode not in bheads:
5856 # i18n: column positioning for "hg summary"
5857 # i18n: column positioning for "hg summary"
5857 ui.write(_('update: %d new changesets (update)\n') % new)
5858 ui.write(_('update: %d new changesets (update)\n') % new)
5858 else:
5859 else:
5859 # i18n: column positioning for "hg summary"
5860 # i18n: column positioning for "hg summary"
5860 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5861 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5861 (new, len(bheads)))
5862 (new, len(bheads)))
5862
5863
5863 cmdutil.summaryhooks(ui, repo)
5864 cmdutil.summaryhooks(ui, repo)
5864
5865
5865 if opts.get('remote'):
5866 if opts.get('remote'):
5866 needsincoming, needsoutgoing = True, True
5867 needsincoming, needsoutgoing = True, True
5867 else:
5868 else:
5868 needsincoming, needsoutgoing = False, False
5869 needsincoming, needsoutgoing = False, False
5869 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5870 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5870 if i:
5871 if i:
5871 needsincoming = True
5872 needsincoming = True
5872 if o:
5873 if o:
5873 needsoutgoing = True
5874 needsoutgoing = True
5874 if not needsincoming and not needsoutgoing:
5875 if not needsincoming and not needsoutgoing:
5875 return
5876 return
5876
5877
5877 def getincoming():
5878 def getincoming():
5878 source, branches = hg.parseurl(ui.expandpath('default'))
5879 source, branches = hg.parseurl(ui.expandpath('default'))
5879 sbranch = branches[0]
5880 sbranch = branches[0]
5880 try:
5881 try:
5881 other = hg.peer(repo, {}, source)
5882 other = hg.peer(repo, {}, source)
5882 except error.RepoError:
5883 except error.RepoError:
5883 if opts.get('remote'):
5884 if opts.get('remote'):
5884 raise
5885 raise
5885 return source, sbranch, None, None, None
5886 return source, sbranch, None, None, None
5886 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5887 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5887 if revs:
5888 if revs:
5888 revs = [other.lookup(rev) for rev in revs]
5889 revs = [other.lookup(rev) for rev in revs]
5889 ui.debug('comparing with %s\n' % util.hidepassword(source))
5890 ui.debug('comparing with %s\n' % util.hidepassword(source))
5890 repo.ui.pushbuffer()
5891 repo.ui.pushbuffer()
5891 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5892 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5892 repo.ui.popbuffer()
5893 repo.ui.popbuffer()
5893 return source, sbranch, other, commoninc, commoninc[1]
5894 return source, sbranch, other, commoninc, commoninc[1]
5894
5895
5895 if needsincoming:
5896 if needsincoming:
5896 source, sbranch, sother, commoninc, incoming = getincoming()
5897 source, sbranch, sother, commoninc, incoming = getincoming()
5897 else:
5898 else:
5898 source = sbranch = sother = commoninc = incoming = None
5899 source = sbranch = sother = commoninc = incoming = None
5899
5900
5900 def getoutgoing():
5901 def getoutgoing():
5901 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5902 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5902 dbranch = branches[0]
5903 dbranch = branches[0]
5903 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5904 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5904 if source != dest:
5905 if source != dest:
5905 try:
5906 try:
5906 dother = hg.peer(repo, {}, dest)
5907 dother = hg.peer(repo, {}, dest)
5907 except error.RepoError:
5908 except error.RepoError:
5908 if opts.get('remote'):
5909 if opts.get('remote'):
5909 raise
5910 raise
5910 return dest, dbranch, None, None
5911 return dest, dbranch, None, None
5911 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5912 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5912 elif sother is None:
5913 elif sother is None:
5913 # there is no explicit destination peer, but source one is invalid
5914 # there is no explicit destination peer, but source one is invalid
5914 return dest, dbranch, None, None
5915 return dest, dbranch, None, None
5915 else:
5916 else:
5916 dother = sother
5917 dother = sother
5917 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5918 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5918 common = None
5919 common = None
5919 else:
5920 else:
5920 common = commoninc
5921 common = commoninc
5921 if revs:
5922 if revs:
5922 revs = [repo.lookup(rev) for rev in revs]
5923 revs = [repo.lookup(rev) for rev in revs]
5923 repo.ui.pushbuffer()
5924 repo.ui.pushbuffer()
5924 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5925 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5925 commoninc=common)
5926 commoninc=common)
5926 repo.ui.popbuffer()
5927 repo.ui.popbuffer()
5927 return dest, dbranch, dother, outgoing
5928 return dest, dbranch, dother, outgoing
5928
5929
5929 if needsoutgoing:
5930 if needsoutgoing:
5930 dest, dbranch, dother, outgoing = getoutgoing()
5931 dest, dbranch, dother, outgoing = getoutgoing()
5931 else:
5932 else:
5932 dest = dbranch = dother = outgoing = None
5933 dest = dbranch = dother = outgoing = None
5933
5934
5934 if opts.get('remote'):
5935 if opts.get('remote'):
5935 t = []
5936 t = []
5936 if incoming:
5937 if incoming:
5937 t.append(_('1 or more incoming'))
5938 t.append(_('1 or more incoming'))
5938 o = outgoing.missing
5939 o = outgoing.missing
5939 if o:
5940 if o:
5940 t.append(_('%d outgoing') % len(o))
5941 t.append(_('%d outgoing') % len(o))
5941 other = dother or sother
5942 other = dother or sother
5942 if 'bookmarks' in other.listkeys('namespaces'):
5943 if 'bookmarks' in other.listkeys('namespaces'):
5943 lmarks = repo.listkeys('bookmarks')
5944 lmarks = repo.listkeys('bookmarks')
5944 rmarks = other.listkeys('bookmarks')
5945 rmarks = other.listkeys('bookmarks')
5945 diff = set(rmarks) - set(lmarks)
5946 diff = set(rmarks) - set(lmarks)
5946 if len(diff) > 0:
5947 if len(diff) > 0:
5947 t.append(_('%d incoming bookmarks') % len(diff))
5948 t.append(_('%d incoming bookmarks') % len(diff))
5948 diff = set(lmarks) - set(rmarks)
5949 diff = set(lmarks) - set(rmarks)
5949 if len(diff) > 0:
5950 if len(diff) > 0:
5950 t.append(_('%d outgoing bookmarks') % len(diff))
5951 t.append(_('%d outgoing bookmarks') % len(diff))
5951
5952
5952 if t:
5953 if t:
5953 # i18n: column positioning for "hg summary"
5954 # i18n: column positioning for "hg summary"
5954 ui.write(_('remote: %s\n') % (', '.join(t)))
5955 ui.write(_('remote: %s\n') % (', '.join(t)))
5955 else:
5956 else:
5956 # i18n: column positioning for "hg summary"
5957 # i18n: column positioning for "hg summary"
5957 ui.status(_('remote: (synced)\n'))
5958 ui.status(_('remote: (synced)\n'))
5958
5959
5959 cmdutil.summaryremotehooks(ui, repo, opts,
5960 cmdutil.summaryremotehooks(ui, repo, opts,
5960 ((source, sbranch, sother, commoninc),
5961 ((source, sbranch, sother, commoninc),
5961 (dest, dbranch, dother, outgoing)))
5962 (dest, dbranch, dother, outgoing)))
5962
5963
5963 @command('tag',
5964 @command('tag',
5964 [('f', 'force', None, _('force tag')),
5965 [('f', 'force', None, _('force tag')),
5965 ('l', 'local', None, _('make the tag local')),
5966 ('l', 'local', None, _('make the tag local')),
5966 ('r', 'rev', '', _('revision to tag'), _('REV')),
5967 ('r', 'rev', '', _('revision to tag'), _('REV')),
5967 ('', 'remove', None, _('remove a tag')),
5968 ('', 'remove', None, _('remove a tag')),
5968 # -l/--local is already there, commitopts cannot be used
5969 # -l/--local is already there, commitopts cannot be used
5969 ('e', 'edit', None, _('invoke editor on commit messages')),
5970 ('e', 'edit', None, _('invoke editor on commit messages')),
5970 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5971 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5971 ] + commitopts2,
5972 ] + commitopts2,
5972 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5973 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5973 def tag(ui, repo, name1, *names, **opts):
5974 def tag(ui, repo, name1, *names, **opts):
5974 """add one or more tags for the current or given revision
5975 """add one or more tags for the current or given revision
5975
5976
5976 Name a particular revision using <name>.
5977 Name a particular revision using <name>.
5977
5978
5978 Tags are used to name particular revisions of the repository and are
5979 Tags are used to name particular revisions of the repository and are
5979 very useful to compare different revisions, to go back to significant
5980 very useful to compare different revisions, to go back to significant
5980 earlier versions or to mark branch points as releases, etc. Changing
5981 earlier versions or to mark branch points as releases, etc. Changing
5981 an existing tag is normally disallowed; use -f/--force to override.
5982 an existing tag is normally disallowed; use -f/--force to override.
5982
5983
5983 If no revision is given, the parent of the working directory is
5984 If no revision is given, the parent of the working directory is
5984 used.
5985 used.
5985
5986
5986 To facilitate version control, distribution, and merging of tags,
5987 To facilitate version control, distribution, and merging of tags,
5987 they are stored as a file named ".hgtags" which is managed similarly
5988 they are stored as a file named ".hgtags" which is managed similarly
5988 to other project files and can be hand-edited if necessary. This
5989 to other project files and can be hand-edited if necessary. This
5989 also means that tagging creates a new commit. The file
5990 also means that tagging creates a new commit. The file
5990 ".hg/localtags" is used for local tags (not shared among
5991 ".hg/localtags" is used for local tags (not shared among
5991 repositories).
5992 repositories).
5992
5993
5993 Tag commits are usually made at the head of a branch. If the parent
5994 Tag commits are usually made at the head of a branch. If the parent
5994 of the working directory is not a branch head, :hg:`tag` aborts; use
5995 of the working directory is not a branch head, :hg:`tag` aborts; use
5995 -f/--force to force the tag commit to be based on a non-head
5996 -f/--force to force the tag commit to be based on a non-head
5996 changeset.
5997 changeset.
5997
5998
5998 See :hg:`help dates` for a list of formats valid for -d/--date.
5999 See :hg:`help dates` for a list of formats valid for -d/--date.
5999
6000
6000 Since tag names have priority over branch names during revision
6001 Since tag names have priority over branch names during revision
6001 lookup, using an existing branch name as a tag name is discouraged.
6002 lookup, using an existing branch name as a tag name is discouraged.
6002
6003
6003 Returns 0 on success.
6004 Returns 0 on success.
6004 """
6005 """
6005 wlock = lock = None
6006 wlock = lock = None
6006 try:
6007 try:
6007 wlock = repo.wlock()
6008 wlock = repo.wlock()
6008 lock = repo.lock()
6009 lock = repo.lock()
6009 rev_ = "."
6010 rev_ = "."
6010 names = [t.strip() for t in (name1,) + names]
6011 names = [t.strip() for t in (name1,) + names]
6011 if len(names) != len(set(names)):
6012 if len(names) != len(set(names)):
6012 raise util.Abort(_('tag names must be unique'))
6013 raise util.Abort(_('tag names must be unique'))
6013 for n in names:
6014 for n in names:
6014 scmutil.checknewlabel(repo, n, 'tag')
6015 scmutil.checknewlabel(repo, n, 'tag')
6015 if not n:
6016 if not n:
6016 raise util.Abort(_('tag names cannot consist entirely of '
6017 raise util.Abort(_('tag names cannot consist entirely of '
6017 'whitespace'))
6018 'whitespace'))
6018 if opts.get('rev') and opts.get('remove'):
6019 if opts.get('rev') and opts.get('remove'):
6019 raise util.Abort(_("--rev and --remove are incompatible"))
6020 raise util.Abort(_("--rev and --remove are incompatible"))
6020 if opts.get('rev'):
6021 if opts.get('rev'):
6021 rev_ = opts['rev']
6022 rev_ = opts['rev']
6022 message = opts.get('message')
6023 message = opts.get('message')
6023 if opts.get('remove'):
6024 if opts.get('remove'):
6024 expectedtype = opts.get('local') and 'local' or 'global'
6025 expectedtype = opts.get('local') and 'local' or 'global'
6025 for n in names:
6026 for n in names:
6026 if not repo.tagtype(n):
6027 if not repo.tagtype(n):
6027 raise util.Abort(_("tag '%s' does not exist") % n)
6028 raise util.Abort(_("tag '%s' does not exist") % n)
6028 if repo.tagtype(n) != expectedtype:
6029 if repo.tagtype(n) != expectedtype:
6029 if expectedtype == 'global':
6030 if expectedtype == 'global':
6030 raise util.Abort(_("tag '%s' is not a global tag") % n)
6031 raise util.Abort(_("tag '%s' is not a global tag") % n)
6031 else:
6032 else:
6032 raise util.Abort(_("tag '%s' is not a local tag") % n)
6033 raise util.Abort(_("tag '%s' is not a local tag") % n)
6033 rev_ = nullid
6034 rev_ = nullid
6034 if not message:
6035 if not message:
6035 # we don't translate commit messages
6036 # we don't translate commit messages
6036 message = 'Removed tag %s' % ', '.join(names)
6037 message = 'Removed tag %s' % ', '.join(names)
6037 elif not opts.get('force'):
6038 elif not opts.get('force'):
6038 for n in names:
6039 for n in names:
6039 if n in repo.tags():
6040 if n in repo.tags():
6040 raise util.Abort(_("tag '%s' already exists "
6041 raise util.Abort(_("tag '%s' already exists "
6041 "(use -f to force)") % n)
6042 "(use -f to force)") % n)
6042 if not opts.get('local'):
6043 if not opts.get('local'):
6043 p1, p2 = repo.dirstate.parents()
6044 p1, p2 = repo.dirstate.parents()
6044 if p2 != nullid:
6045 if p2 != nullid:
6045 raise util.Abort(_('uncommitted merge'))
6046 raise util.Abort(_('uncommitted merge'))
6046 bheads = repo.branchheads()
6047 bheads = repo.branchheads()
6047 if not opts.get('force') and bheads and p1 not in bheads:
6048 if not opts.get('force') and bheads and p1 not in bheads:
6048 raise util.Abort(_('not at a branch head (use -f to force)'))
6049 raise util.Abort(_('not at a branch head (use -f to force)'))
6049 r = scmutil.revsingle(repo, rev_).node()
6050 r = scmutil.revsingle(repo, rev_).node()
6050
6051
6051 if not message:
6052 if not message:
6052 # we don't translate commit messages
6053 # we don't translate commit messages
6053 message = ('Added tag %s for changeset %s' %
6054 message = ('Added tag %s for changeset %s' %
6054 (', '.join(names), short(r)))
6055 (', '.join(names), short(r)))
6055
6056
6056 date = opts.get('date')
6057 date = opts.get('date')
6057 if date:
6058 if date:
6058 date = util.parsedate(date)
6059 date = util.parsedate(date)
6059
6060
6060 if opts.get('remove'):
6061 if opts.get('remove'):
6061 editform = 'tag.remove'
6062 editform = 'tag.remove'
6062 else:
6063 else:
6063 editform = 'tag.add'
6064 editform = 'tag.add'
6064 editor = cmdutil.getcommiteditor(editform=editform, **opts)
6065 editor = cmdutil.getcommiteditor(editform=editform, **opts)
6065
6066
6066 # don't allow tagging the null rev
6067 # don't allow tagging the null rev
6067 if (not opts.get('remove') and
6068 if (not opts.get('remove') and
6068 scmutil.revsingle(repo, rev_).rev() == nullrev):
6069 scmutil.revsingle(repo, rev_).rev() == nullrev):
6069 raise util.Abort(_("cannot tag null revision"))
6070 raise util.Abort(_("cannot tag null revision"))
6070
6071
6071 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date,
6072 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date,
6072 editor=editor)
6073 editor=editor)
6073 finally:
6074 finally:
6074 release(lock, wlock)
6075 release(lock, wlock)
6075
6076
6076 @command('tags', formatteropts, '')
6077 @command('tags', formatteropts, '')
6077 def tags(ui, repo, **opts):
6078 def tags(ui, repo, **opts):
6078 """list repository tags
6079 """list repository tags
6079
6080
6080 This lists both regular and local tags. When the -v/--verbose
6081 This lists both regular and local tags. When the -v/--verbose
6081 switch is used, a third column "local" is printed for local tags.
6082 switch is used, a third column "local" is printed for local tags.
6082
6083
6083 Returns 0 on success.
6084 Returns 0 on success.
6084 """
6085 """
6085
6086
6086 fm = ui.formatter('tags', opts)
6087 fm = ui.formatter('tags', opts)
6087 hexfunc = fm.hexfunc
6088 hexfunc = fm.hexfunc
6088 tagtype = ""
6089 tagtype = ""
6089
6090
6090 for t, n in reversed(repo.tagslist()):
6091 for t, n in reversed(repo.tagslist()):
6091 hn = hexfunc(n)
6092 hn = hexfunc(n)
6092 label = 'tags.normal'
6093 label = 'tags.normal'
6093 tagtype = ''
6094 tagtype = ''
6094 if repo.tagtype(t) == 'local':
6095 if repo.tagtype(t) == 'local':
6095 label = 'tags.local'
6096 label = 'tags.local'
6096 tagtype = 'local'
6097 tagtype = 'local'
6097
6098
6098 fm.startitem()
6099 fm.startitem()
6099 fm.write('tag', '%s', t, label=label)
6100 fm.write('tag', '%s', t, label=label)
6100 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
6101 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
6101 fm.condwrite(not ui.quiet, 'rev node', fmt,
6102 fm.condwrite(not ui.quiet, 'rev node', fmt,
6102 repo.changelog.rev(n), hn, label=label)
6103 repo.changelog.rev(n), hn, label=label)
6103 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
6104 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
6104 tagtype, label=label)
6105 tagtype, label=label)
6105 fm.plain('\n')
6106 fm.plain('\n')
6106 fm.end()
6107 fm.end()
6107
6108
6108 @command('tip',
6109 @command('tip',
6109 [('p', 'patch', None, _('show patch')),
6110 [('p', 'patch', None, _('show patch')),
6110 ('g', 'git', None, _('use git extended diff format')),
6111 ('g', 'git', None, _('use git extended diff format')),
6111 ] + templateopts,
6112 ] + templateopts,
6112 _('[-p] [-g]'))
6113 _('[-p] [-g]'))
6113 def tip(ui, repo, **opts):
6114 def tip(ui, repo, **opts):
6114 """show the tip revision (DEPRECATED)
6115 """show the tip revision (DEPRECATED)
6115
6116
6116 The tip revision (usually just called the tip) is the changeset
6117 The tip revision (usually just called the tip) is the changeset
6117 most recently added to the repository (and therefore the most
6118 most recently added to the repository (and therefore the most
6118 recently changed head).
6119 recently changed head).
6119
6120
6120 If you have just made a commit, that commit will be the tip. If
6121 If you have just made a commit, that commit will be the tip. If
6121 you have just pulled changes from another repository, the tip of
6122 you have just pulled changes from another repository, the tip of
6122 that repository becomes the current tip. The "tip" tag is special
6123 that repository becomes the current tip. The "tip" tag is special
6123 and cannot be renamed or assigned to a different changeset.
6124 and cannot be renamed or assigned to a different changeset.
6124
6125
6125 This command is deprecated, please use :hg:`heads` instead.
6126 This command is deprecated, please use :hg:`heads` instead.
6126
6127
6127 Returns 0 on success.
6128 Returns 0 on success.
6128 """
6129 """
6129 displayer = cmdutil.show_changeset(ui, repo, opts)
6130 displayer = cmdutil.show_changeset(ui, repo, opts)
6130 displayer.show(repo['tip'])
6131 displayer.show(repo['tip'])
6131 displayer.close()
6132 displayer.close()
6132
6133
6133 @command('unbundle',
6134 @command('unbundle',
6134 [('u', 'update', None,
6135 [('u', 'update', None,
6135 _('update to new branch head if changesets were unbundled'))],
6136 _('update to new branch head if changesets were unbundled'))],
6136 _('[-u] FILE...'))
6137 _('[-u] FILE...'))
6137 def unbundle(ui, repo, fname1, *fnames, **opts):
6138 def unbundle(ui, repo, fname1, *fnames, **opts):
6138 """apply one or more changegroup files
6139 """apply one or more changegroup files
6139
6140
6140 Apply one or more compressed changegroup files generated by the
6141 Apply one or more compressed changegroup files generated by the
6141 bundle command.
6142 bundle command.
6142
6143
6143 Returns 0 on success, 1 if an update has unresolved files.
6144 Returns 0 on success, 1 if an update has unresolved files.
6144 """
6145 """
6145 fnames = (fname1,) + fnames
6146 fnames = (fname1,) + fnames
6146
6147
6147 lock = repo.lock()
6148 lock = repo.lock()
6148 try:
6149 try:
6149 for fname in fnames:
6150 for fname in fnames:
6150 f = hg.openpath(ui, fname)
6151 f = hg.openpath(ui, fname)
6151 gen = exchange.readbundle(ui, f, fname)
6152 gen = exchange.readbundle(ui, f, fname)
6152 modheads = changegroup.addchangegroup(repo, gen, 'unbundle',
6153 modheads = changegroup.addchangegroup(repo, gen, 'unbundle',
6153 'bundle:' + fname)
6154 'bundle:' + fname)
6154 finally:
6155 finally:
6155 lock.release()
6156 lock.release()
6156
6157
6157 return postincoming(ui, repo, modheads, opts.get('update'), None)
6158 return postincoming(ui, repo, modheads, opts.get('update'), None)
6158
6159
6159 @command('^update|up|checkout|co',
6160 @command('^update|up|checkout|co',
6160 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6161 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6161 ('c', 'check', None,
6162 ('c', 'check', None,
6162 _('update across branches if no uncommitted changes')),
6163 _('update across branches if no uncommitted changes')),
6163 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6164 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6164 ('r', 'rev', '', _('revision'), _('REV'))
6165 ('r', 'rev', '', _('revision'), _('REV'))
6165 ] + mergetoolopts,
6166 ] + mergetoolopts,
6166 _('[-c] [-C] [-d DATE] [[-r] REV]'))
6167 _('[-c] [-C] [-d DATE] [[-r] REV]'))
6167 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
6168 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
6168 tool=None):
6169 tool=None):
6169 """update working directory (or switch revisions)
6170 """update working directory (or switch revisions)
6170
6171
6171 Update the repository's working directory to the specified
6172 Update the repository's working directory to the specified
6172 changeset. If no changeset is specified, update to the tip of the
6173 changeset. If no changeset is specified, update to the tip of the
6173 current named branch and move the current bookmark (see :hg:`help
6174 current named branch and move the current bookmark (see :hg:`help
6174 bookmarks`).
6175 bookmarks`).
6175
6176
6176 Update sets the working directory's parent revision to the specified
6177 Update sets the working directory's parent revision to the specified
6177 changeset (see :hg:`help parents`).
6178 changeset (see :hg:`help parents`).
6178
6179
6179 If the changeset is not a descendant or ancestor of the working
6180 If the changeset is not a descendant or ancestor of the working
6180 directory's parent, the update is aborted. With the -c/--check
6181 directory's parent, the update is aborted. With the -c/--check
6181 option, the working directory is checked for uncommitted changes; if
6182 option, the working directory is checked for uncommitted changes; if
6182 none are found, the working directory is updated to the specified
6183 none are found, the working directory is updated to the specified
6183 changeset.
6184 changeset.
6184
6185
6185 .. container:: verbose
6186 .. container:: verbose
6186
6187
6187 The following rules apply when the working directory contains
6188 The following rules apply when the working directory contains
6188 uncommitted changes:
6189 uncommitted changes:
6189
6190
6190 1. If neither -c/--check nor -C/--clean is specified, and if
6191 1. If neither -c/--check nor -C/--clean is specified, and if
6191 the requested changeset is an ancestor or descendant of
6192 the requested changeset is an ancestor or descendant of
6192 the working directory's parent, the uncommitted changes
6193 the working directory's parent, the uncommitted changes
6193 are merged into the requested changeset and the merged
6194 are merged into the requested changeset and the merged
6194 result is left uncommitted. If the requested changeset is
6195 result is left uncommitted. If the requested changeset is
6195 not an ancestor or descendant (that is, it is on another
6196 not an ancestor or descendant (that is, it is on another
6196 branch), the update is aborted and the uncommitted changes
6197 branch), the update is aborted and the uncommitted changes
6197 are preserved.
6198 are preserved.
6198
6199
6199 2. With the -c/--check option, the update is aborted and the
6200 2. With the -c/--check option, the update is aborted and the
6200 uncommitted changes are preserved.
6201 uncommitted changes are preserved.
6201
6202
6202 3. With the -C/--clean option, uncommitted changes are discarded and
6203 3. With the -C/--clean option, uncommitted changes are discarded and
6203 the working directory is updated to the requested changeset.
6204 the working directory is updated to the requested changeset.
6204
6205
6205 To cancel an uncommitted merge (and lose your changes), use
6206 To cancel an uncommitted merge (and lose your changes), use
6206 :hg:`update --clean .`.
6207 :hg:`update --clean .`.
6207
6208
6208 Use null as the changeset to remove the working directory (like
6209 Use null as the changeset to remove the working directory (like
6209 :hg:`clone -U`).
6210 :hg:`clone -U`).
6210
6211
6211 If you want to revert just one file to an older revision, use
6212 If you want to revert just one file to an older revision, use
6212 :hg:`revert [-r REV] NAME`.
6213 :hg:`revert [-r REV] NAME`.
6213
6214
6214 See :hg:`help dates` for a list of formats valid for -d/--date.
6215 See :hg:`help dates` for a list of formats valid for -d/--date.
6215
6216
6216 Returns 0 on success, 1 if there are unresolved files.
6217 Returns 0 on success, 1 if there are unresolved files.
6217 """
6218 """
6218 if rev and node:
6219 if rev and node:
6219 raise util.Abort(_("please specify just one revision"))
6220 raise util.Abort(_("please specify just one revision"))
6220
6221
6221 if rev is None or rev == '':
6222 if rev is None or rev == '':
6222 rev = node
6223 rev = node
6223
6224
6224 cmdutil.clearunfinished(repo)
6225 cmdutil.clearunfinished(repo)
6225
6226
6226 # with no argument, we also move the current bookmark, if any
6227 # with no argument, we also move the current bookmark, if any
6227 rev, movemarkfrom = bookmarks.calculateupdate(ui, repo, rev)
6228 rev, movemarkfrom = bookmarks.calculateupdate(ui, repo, rev)
6228
6229
6229 # if we defined a bookmark, we have to remember the original bookmark name
6230 # if we defined a bookmark, we have to remember the original bookmark name
6230 brev = rev
6231 brev = rev
6231 rev = scmutil.revsingle(repo, rev, rev).rev()
6232 rev = scmutil.revsingle(repo, rev, rev).rev()
6232
6233
6233 if check and clean:
6234 if check and clean:
6234 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
6235 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
6235
6236
6236 if date:
6237 if date:
6237 if rev is not None:
6238 if rev is not None:
6238 raise util.Abort(_("you can't specify a revision and a date"))
6239 raise util.Abort(_("you can't specify a revision and a date"))
6239 rev = cmdutil.finddate(ui, repo, date)
6240 rev = cmdutil.finddate(ui, repo, date)
6240
6241
6241 if check:
6242 if check:
6242 c = repo[None]
6243 c = repo[None]
6243 if c.dirty(merge=False, branch=False, missing=True):
6244 if c.dirty(merge=False, branch=False, missing=True):
6244 raise util.Abort(_("uncommitted changes"))
6245 raise util.Abort(_("uncommitted changes"))
6245 if rev is None:
6246 if rev is None:
6246 rev = repo[repo[None].branch()].rev()
6247 rev = repo[repo[None].branch()].rev()
6247 mergemod._checkunknown(repo, repo[None], repo[rev])
6248 mergemod._checkunknown(repo, repo[None], repo[rev])
6248
6249
6249 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
6250 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
6250
6251
6251 if clean:
6252 if clean:
6252 ret = hg.clean(repo, rev)
6253 ret = hg.clean(repo, rev)
6253 else:
6254 else:
6254 ret = hg.update(repo, rev)
6255 ret = hg.update(repo, rev)
6255
6256
6256 if not ret and movemarkfrom:
6257 if not ret and movemarkfrom:
6257 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
6258 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
6258 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
6259 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
6259 elif brev in repo._bookmarks:
6260 elif brev in repo._bookmarks:
6260 bookmarks.setcurrent(repo, brev)
6261 bookmarks.setcurrent(repo, brev)
6261 ui.status(_("(activating bookmark %s)\n") % brev)
6262 ui.status(_("(activating bookmark %s)\n") % brev)
6262 elif brev:
6263 elif brev:
6263 if repo._bookmarkcurrent:
6264 if repo._bookmarkcurrent:
6264 ui.status(_("(leaving bookmark %s)\n") %
6265 ui.status(_("(leaving bookmark %s)\n") %
6265 repo._bookmarkcurrent)
6266 repo._bookmarkcurrent)
6266 bookmarks.unsetcurrent(repo)
6267 bookmarks.unsetcurrent(repo)
6267
6268
6268 return ret
6269 return ret
6269
6270
6270 @command('verify', [])
6271 @command('verify', [])
6271 def verify(ui, repo):
6272 def verify(ui, repo):
6272 """verify the integrity of the repository
6273 """verify the integrity of the repository
6273
6274
6274 Verify the integrity of the current repository.
6275 Verify the integrity of the current repository.
6275
6276
6276 This will perform an extensive check of the repository's
6277 This will perform an extensive check of the repository's
6277 integrity, validating the hashes and checksums of each entry in
6278 integrity, validating the hashes and checksums of each entry in
6278 the changelog, manifest, and tracked files, as well as the
6279 the changelog, manifest, and tracked files, as well as the
6279 integrity of their crosslinks and indices.
6280 integrity of their crosslinks and indices.
6280
6281
6281 Please see http://mercurial.selenic.com/wiki/RepositoryCorruption
6282 Please see http://mercurial.selenic.com/wiki/RepositoryCorruption
6282 for more information about recovery from corruption of the
6283 for more information about recovery from corruption of the
6283 repository.
6284 repository.
6284
6285
6285 Returns 0 on success, 1 if errors are encountered.
6286 Returns 0 on success, 1 if errors are encountered.
6286 """
6287 """
6287 return hg.verify(repo)
6288 return hg.verify(repo)
6288
6289
6289 @command('version', [], norepo=True)
6290 @command('version', [], norepo=True)
6290 def version_(ui):
6291 def version_(ui):
6291 """output version and copyright information"""
6292 """output version and copyright information"""
6292 ui.write(_("Mercurial Distributed SCM (version %s)\n")
6293 ui.write(_("Mercurial Distributed SCM (version %s)\n")
6293 % util.version())
6294 % util.version())
6294 ui.status(_(
6295 ui.status(_(
6295 "(see http://mercurial.selenic.com for more information)\n"
6296 "(see http://mercurial.selenic.com for more information)\n"
6296 "\nCopyright (C) 2005-2014 Matt Mackall and others\n"
6297 "\nCopyright (C) 2005-2014 Matt Mackall and others\n"
6297 "This is free software; see the source for copying conditions. "
6298 "This is free software; see the source for copying conditions. "
6298 "There is NO\nwarranty; "
6299 "There is NO\nwarranty; "
6299 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6300 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6300 ))
6301 ))
6301
6302
6302 ui.note(_("\nEnabled extensions:\n\n"))
6303 ui.note(_("\nEnabled extensions:\n\n"))
6303 if ui.verbose:
6304 if ui.verbose:
6304 # format names and versions into columns
6305 # format names and versions into columns
6305 names = []
6306 names = []
6306 vers = []
6307 vers = []
6307 for name, module in extensions.extensions():
6308 for name, module in extensions.extensions():
6308 names.append(name)
6309 names.append(name)
6309 vers.append(extensions.moduleversion(module))
6310 vers.append(extensions.moduleversion(module))
6310 if names:
6311 if names:
6311 maxnamelen = max(len(n) for n in names)
6312 maxnamelen = max(len(n) for n in names)
6312 for i, name in enumerate(names):
6313 for i, name in enumerate(names):
6313 ui.write(" %-*s %s\n" % (maxnamelen, name, vers[i]))
6314 ui.write(" %-*s %s\n" % (maxnamelen, name, vers[i]))
@@ -1,1166 +1,1167 b''
1 # obsolete.py - obsolete markers handling
1 # obsolete.py - obsolete markers handling
2 #
2 #
3 # Copyright 2012 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
3 # Copyright 2012 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
4 # Logilab SA <contact@logilab.fr>
4 # Logilab SA <contact@logilab.fr>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 """Obsolete marker handling
9 """Obsolete marker handling
10
10
11 An obsolete marker maps an old changeset to a list of new
11 An obsolete marker maps an old changeset to a list of new
12 changesets. If the list of new changesets is empty, the old changeset
12 changesets. If the list of new changesets is empty, the old changeset
13 is said to be "killed". Otherwise, the old changeset is being
13 is said to be "killed". Otherwise, the old changeset is being
14 "replaced" by the new changesets.
14 "replaced" by the new changesets.
15
15
16 Obsolete markers can be used to record and distribute changeset graph
16 Obsolete markers can be used to record and distribute changeset graph
17 transformations performed by history rewrite operations, and help
17 transformations performed by history rewrite operations, and help
18 building new tools to reconcile conflicting rewrite actions. To
18 building new tools to reconcile conflicting rewrite actions. To
19 facilitate conflict resolution, markers include various annotations
19 facilitate conflict resolution, markers include various annotations
20 besides old and news changeset identifiers, such as creation date or
20 besides old and news changeset identifiers, such as creation date or
21 author name.
21 author name.
22
22
23 The old obsoleted changeset is called a "precursor" and possible
23 The old obsoleted changeset is called a "precursor" and possible
24 replacements are called "successors". Markers that used changeset X as
24 replacements are called "successors". Markers that used changeset X as
25 a precursor are called "successor markers of X" because they hold
25 a precursor are called "successor markers of X" because they hold
26 information about the successors of X. Markers that use changeset Y as
26 information about the successors of X. Markers that use changeset Y as
27 a successors are call "precursor markers of Y" because they hold
27 a successors are call "precursor markers of Y" because they hold
28 information about the precursors of Y.
28 information about the precursors of Y.
29
29
30 Examples:
30 Examples:
31
31
32 - When changeset A is replaced by changeset A', one marker is stored:
32 - When changeset A is replaced by changeset A', one marker is stored:
33
33
34 (A, (A',))
34 (A, (A',))
35
35
36 - When changesets A and B are folded into a new changeset C, two markers are
36 - When changesets A and B are folded into a new changeset C, two markers are
37 stored:
37 stored:
38
38
39 (A, (C,)) and (B, (C,))
39 (A, (C,)) and (B, (C,))
40
40
41 - When changeset A is simply "pruned" from the graph, a marker is created:
41 - When changeset A is simply "pruned" from the graph, a marker is created:
42
42
43 (A, ())
43 (A, ())
44
44
45 - When changeset A is split into B and C, a single marker are used:
45 - When changeset A is split into B and C, a single marker are used:
46
46
47 (A, (C, C))
47 (A, (C, C))
48
48
49 We use a single marker to distinguish the "split" case from the "divergence"
49 We use a single marker to distinguish the "split" case from the "divergence"
50 case. If two independent operations rewrite the same changeset A in to A' and
50 case. If two independent operations rewrite the same changeset A in to A' and
51 A'', we have an error case: divergent rewriting. We can detect it because
51 A'', we have an error case: divergent rewriting. We can detect it because
52 two markers will be created independently:
52 two markers will be created independently:
53
53
54 (A, (B,)) and (A, (C,))
54 (A, (B,)) and (A, (C,))
55
55
56 Format
56 Format
57 ------
57 ------
58
58
59 Markers are stored in an append-only file stored in
59 Markers are stored in an append-only file stored in
60 '.hg/store/obsstore'.
60 '.hg/store/obsstore'.
61
61
62 The file starts with a version header:
62 The file starts with a version header:
63
63
64 - 1 unsigned byte: version number, starting at zero.
64 - 1 unsigned byte: version number, starting at zero.
65
65
66 The header is followed by the markers. Marker format depend of the version. See
66 The header is followed by the markers. Marker format depend of the version. See
67 comment associated with each format for details.
67 comment associated with each format for details.
68
68
69 """
69 """
70 import struct
70 import struct
71 import util, base85, node
71 import util, base85, node
72 import phases
72 import phases
73 from i18n import _
73 from i18n import _
74
74
75 _pack = struct.pack
75 _pack = struct.pack
76 _unpack = struct.unpack
76 _unpack = struct.unpack
77
77
78 _SEEK_END = 2 # os.SEEK_END was introduced in Python 2.5
78 _SEEK_END = 2 # os.SEEK_END was introduced in Python 2.5
79
79
80 # the obsolete feature is not mature enough to be enabled by default.
80 # the obsolete feature is not mature enough to be enabled by default.
81 # you have to rely on third party extension extension to enable this.
81 # you have to rely on third party extension extension to enable this.
82 _enabled = False
82 _enabled = False
83
83
84 # Options for obsolescence
84 # Options for obsolescence
85 createmarkersopt = 'createmarkers'
85 createmarkersopt = 'createmarkers'
86 allowunstableopt = 'allowunstable'
86
87
87 ### obsolescence marker flag
88 ### obsolescence marker flag
88
89
89 ## bumpedfix flag
90 ## bumpedfix flag
90 #
91 #
91 # When a changeset A' succeed to a changeset A which became public, we call A'
92 # When a changeset A' succeed to a changeset A which became public, we call A'
92 # "bumped" because it's a successors of a public changesets
93 # "bumped" because it's a successors of a public changesets
93 #
94 #
94 # o A' (bumped)
95 # o A' (bumped)
95 # |`:
96 # |`:
96 # | o A
97 # | o A
97 # |/
98 # |/
98 # o Z
99 # o Z
99 #
100 #
100 # The way to solve this situation is to create a new changeset Ad as children
101 # The way to solve this situation is to create a new changeset Ad as children
101 # of A. This changeset have the same content than A'. So the diff from A to A'
102 # of A. This changeset have the same content than A'. So the diff from A to A'
102 # is the same than the diff from A to Ad. Ad is marked as a successors of A'
103 # is the same than the diff from A to Ad. Ad is marked as a successors of A'
103 #
104 #
104 # o Ad
105 # o Ad
105 # |`:
106 # |`:
106 # | x A'
107 # | x A'
107 # |'|
108 # |'|
108 # o | A
109 # o | A
109 # |/
110 # |/
110 # o Z
111 # o Z
111 #
112 #
112 # But by transitivity Ad is also a successors of A. To avoid having Ad marked
113 # But by transitivity Ad is also a successors of A. To avoid having Ad marked
113 # as bumped too, we add the `bumpedfix` flag to the marker. <A', (Ad,)>.
114 # as bumped too, we add the `bumpedfix` flag to the marker. <A', (Ad,)>.
114 # This flag mean that the successors express the changes between the public and
115 # This flag mean that the successors express the changes between the public and
115 # bumped version and fix the situation, breaking the transitivity of
116 # bumped version and fix the situation, breaking the transitivity of
116 # "bumped" here.
117 # "bumped" here.
117 bumpedfix = 1
118 bumpedfix = 1
118 usingsha256 = 2
119 usingsha256 = 2
119
120
120 ## Parsing and writing of version "0"
121 ## Parsing and writing of version "0"
121 #
122 #
122 # The header is followed by the markers. Each marker is made of:
123 # The header is followed by the markers. Each marker is made of:
123 #
124 #
124 # - 1 uint8 : number of new changesets "N", can be zero.
125 # - 1 uint8 : number of new changesets "N", can be zero.
125 #
126 #
126 # - 1 uint32: metadata size "M" in bytes.
127 # - 1 uint32: metadata size "M" in bytes.
127 #
128 #
128 # - 1 byte: a bit field. It is reserved for flags used in common
129 # - 1 byte: a bit field. It is reserved for flags used in common
129 # obsolete marker operations, to avoid repeated decoding of metadata
130 # obsolete marker operations, to avoid repeated decoding of metadata
130 # entries.
131 # entries.
131 #
132 #
132 # - 20 bytes: obsoleted changeset identifier.
133 # - 20 bytes: obsoleted changeset identifier.
133 #
134 #
134 # - N*20 bytes: new changesets identifiers.
135 # - N*20 bytes: new changesets identifiers.
135 #
136 #
136 # - M bytes: metadata as a sequence of nul-terminated strings. Each
137 # - M bytes: metadata as a sequence of nul-terminated strings. Each
137 # string contains a key and a value, separated by a colon ':', without
138 # string contains a key and a value, separated by a colon ':', without
138 # additional encoding. Keys cannot contain '\0' or ':' and values
139 # additional encoding. Keys cannot contain '\0' or ':' and values
139 # cannot contain '\0'.
140 # cannot contain '\0'.
140 _fm0version = 0
141 _fm0version = 0
141 _fm0fixed = '>BIB20s'
142 _fm0fixed = '>BIB20s'
142 _fm0node = '20s'
143 _fm0node = '20s'
143 _fm0fsize = struct.calcsize(_fm0fixed)
144 _fm0fsize = struct.calcsize(_fm0fixed)
144 _fm0fnodesize = struct.calcsize(_fm0node)
145 _fm0fnodesize = struct.calcsize(_fm0node)
145
146
146 def _fm0readmarkers(data, off=0):
147 def _fm0readmarkers(data, off=0):
147 # Loop on markers
148 # Loop on markers
148 l = len(data)
149 l = len(data)
149 while off + _fm0fsize <= l:
150 while off + _fm0fsize <= l:
150 # read fixed part
151 # read fixed part
151 cur = data[off:off + _fm0fsize]
152 cur = data[off:off + _fm0fsize]
152 off += _fm0fsize
153 off += _fm0fsize
153 numsuc, mdsize, flags, pre = _unpack(_fm0fixed, cur)
154 numsuc, mdsize, flags, pre = _unpack(_fm0fixed, cur)
154 # read replacement
155 # read replacement
155 sucs = ()
156 sucs = ()
156 if numsuc:
157 if numsuc:
157 s = (_fm0fnodesize * numsuc)
158 s = (_fm0fnodesize * numsuc)
158 cur = data[off:off + s]
159 cur = data[off:off + s]
159 sucs = _unpack(_fm0node * numsuc, cur)
160 sucs = _unpack(_fm0node * numsuc, cur)
160 off += s
161 off += s
161 # read metadata
162 # read metadata
162 # (metadata will be decoded on demand)
163 # (metadata will be decoded on demand)
163 metadata = data[off:off + mdsize]
164 metadata = data[off:off + mdsize]
164 if len(metadata) != mdsize:
165 if len(metadata) != mdsize:
165 raise util.Abort(_('parsing obsolete marker: metadata is too '
166 raise util.Abort(_('parsing obsolete marker: metadata is too '
166 'short, %d bytes expected, got %d')
167 'short, %d bytes expected, got %d')
167 % (mdsize, len(metadata)))
168 % (mdsize, len(metadata)))
168 off += mdsize
169 off += mdsize
169 metadata = _fm0decodemeta(metadata)
170 metadata = _fm0decodemeta(metadata)
170 try:
171 try:
171 when, offset = metadata.pop('date', '0 0').split(' ')
172 when, offset = metadata.pop('date', '0 0').split(' ')
172 date = float(when), int(offset)
173 date = float(when), int(offset)
173 except ValueError:
174 except ValueError:
174 date = (0., 0)
175 date = (0., 0)
175 parents = None
176 parents = None
176 if 'p2' in metadata:
177 if 'p2' in metadata:
177 parents = (metadata.pop('p1', None), metadata.pop('p2', None))
178 parents = (metadata.pop('p1', None), metadata.pop('p2', None))
178 elif 'p1' in metadata:
179 elif 'p1' in metadata:
179 parents = (metadata.pop('p1', None),)
180 parents = (metadata.pop('p1', None),)
180 elif 'p0' in metadata:
181 elif 'p0' in metadata:
181 parents = ()
182 parents = ()
182 if parents is not None:
183 if parents is not None:
183 try:
184 try:
184 parents = tuple(node.bin(p) for p in parents)
185 parents = tuple(node.bin(p) for p in parents)
185 # if parent content is not a nodeid, drop the data
186 # if parent content is not a nodeid, drop the data
186 for p in parents:
187 for p in parents:
187 if len(p) != 20:
188 if len(p) != 20:
188 parents = None
189 parents = None
189 break
190 break
190 except TypeError:
191 except TypeError:
191 # if content cannot be translated to nodeid drop the data.
192 # if content cannot be translated to nodeid drop the data.
192 parents = None
193 parents = None
193
194
194 metadata = tuple(sorted(metadata.iteritems()))
195 metadata = tuple(sorted(metadata.iteritems()))
195
196
196 yield (pre, sucs, flags, metadata, date, parents)
197 yield (pre, sucs, flags, metadata, date, parents)
197
198
198 def _fm0encodeonemarker(marker):
199 def _fm0encodeonemarker(marker):
199 pre, sucs, flags, metadata, date, parents = marker
200 pre, sucs, flags, metadata, date, parents = marker
200 if flags & usingsha256:
201 if flags & usingsha256:
201 raise util.Abort(_('cannot handle sha256 with old obsstore format'))
202 raise util.Abort(_('cannot handle sha256 with old obsstore format'))
202 metadata = dict(metadata)
203 metadata = dict(metadata)
203 metadata['date'] = '%d %i' % date
204 metadata['date'] = '%d %i' % date
204 if parents is not None:
205 if parents is not None:
205 if not parents:
206 if not parents:
206 # mark that we explicitly recorded no parents
207 # mark that we explicitly recorded no parents
207 metadata['p0'] = ''
208 metadata['p0'] = ''
208 for i, p in enumerate(parents):
209 for i, p in enumerate(parents):
209 metadata['p%i' % (i + 1)] = node.hex(p)
210 metadata['p%i' % (i + 1)] = node.hex(p)
210 metadata = _fm0encodemeta(metadata)
211 metadata = _fm0encodemeta(metadata)
211 numsuc = len(sucs)
212 numsuc = len(sucs)
212 format = _fm0fixed + (_fm0node * numsuc)
213 format = _fm0fixed + (_fm0node * numsuc)
213 data = [numsuc, len(metadata), flags, pre]
214 data = [numsuc, len(metadata), flags, pre]
214 data.extend(sucs)
215 data.extend(sucs)
215 return _pack(format, *data) + metadata
216 return _pack(format, *data) + metadata
216
217
217 def _fm0encodemeta(meta):
218 def _fm0encodemeta(meta):
218 """Return encoded metadata string to string mapping.
219 """Return encoded metadata string to string mapping.
219
220
220 Assume no ':' in key and no '\0' in both key and value."""
221 Assume no ':' in key and no '\0' in both key and value."""
221 for key, value in meta.iteritems():
222 for key, value in meta.iteritems():
222 if ':' in key or '\0' in key:
223 if ':' in key or '\0' in key:
223 raise ValueError("':' and '\0' are forbidden in metadata key'")
224 raise ValueError("':' and '\0' are forbidden in metadata key'")
224 if '\0' in value:
225 if '\0' in value:
225 raise ValueError("':' is forbidden in metadata value'")
226 raise ValueError("':' is forbidden in metadata value'")
226 return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
227 return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
227
228
228 def _fm0decodemeta(data):
229 def _fm0decodemeta(data):
229 """Return string to string dictionary from encoded version."""
230 """Return string to string dictionary from encoded version."""
230 d = {}
231 d = {}
231 for l in data.split('\0'):
232 for l in data.split('\0'):
232 if l:
233 if l:
233 key, value = l.split(':')
234 key, value = l.split(':')
234 d[key] = value
235 d[key] = value
235 return d
236 return d
236
237
237 ## Parsing and writing of version "1"
238 ## Parsing and writing of version "1"
238 #
239 #
239 # The header is followed by the markers. Each marker is made of:
240 # The header is followed by the markers. Each marker is made of:
240 #
241 #
241 # - uint32: total size of the marker (including this field)
242 # - uint32: total size of the marker (including this field)
242 #
243 #
243 # - float64: date in seconds since epoch
244 # - float64: date in seconds since epoch
244 #
245 #
245 # - int16: timezone offset in minutes
246 # - int16: timezone offset in minutes
246 #
247 #
247 # - uint16: a bit field. It is reserved for flags used in common
248 # - uint16: a bit field. It is reserved for flags used in common
248 # obsolete marker operations, to avoid repeated decoding of metadata
249 # obsolete marker operations, to avoid repeated decoding of metadata
249 # entries.
250 # entries.
250 #
251 #
251 # - uint8: number of successors "N", can be zero.
252 # - uint8: number of successors "N", can be zero.
252 #
253 #
253 # - uint8: number of parents "P", can be zero.
254 # - uint8: number of parents "P", can be zero.
254 #
255 #
255 # 0: parents data stored but no parent,
256 # 0: parents data stored but no parent,
256 # 1: one parent stored,
257 # 1: one parent stored,
257 # 2: two parents stored,
258 # 2: two parents stored,
258 # 3: no parent data stored
259 # 3: no parent data stored
259 #
260 #
260 # - uint8: number of metadata entries M
261 # - uint8: number of metadata entries M
261 #
262 #
262 # - 20 or 32 bytes: precursor changeset identifier.
263 # - 20 or 32 bytes: precursor changeset identifier.
263 #
264 #
264 # - N*(20 or 32) bytes: successors changesets identifiers.
265 # - N*(20 or 32) bytes: successors changesets identifiers.
265 #
266 #
266 # - P*(20 or 32) bytes: parents of the precursors changesets.
267 # - P*(20 or 32) bytes: parents of the precursors changesets.
267 #
268 #
268 # - M*(uint8, uint8): size of all metadata entries (key and value)
269 # - M*(uint8, uint8): size of all metadata entries (key and value)
269 #
270 #
270 # - remaining bytes: the metadata, each (key, value) pair after the other.
271 # - remaining bytes: the metadata, each (key, value) pair after the other.
271 _fm1version = 1
272 _fm1version = 1
272 _fm1fixed = '>IdhHBBB20s'
273 _fm1fixed = '>IdhHBBB20s'
273 _fm1nodesha1 = '20s'
274 _fm1nodesha1 = '20s'
274 _fm1nodesha256 = '32s'
275 _fm1nodesha256 = '32s'
275 _fm1fsize = struct.calcsize(_fm1fixed)
276 _fm1fsize = struct.calcsize(_fm1fixed)
276 _fm1parentnone = 3
277 _fm1parentnone = 3
277 _fm1parentshift = 14
278 _fm1parentshift = 14
278 _fm1parentmask = (_fm1parentnone << _fm1parentshift)
279 _fm1parentmask = (_fm1parentnone << _fm1parentshift)
279 _fm1metapair = 'BB'
280 _fm1metapair = 'BB'
280 _fm1metapairsize = struct.calcsize('BB')
281 _fm1metapairsize = struct.calcsize('BB')
281
282
282 def _fm1readmarkers(data, off=0):
283 def _fm1readmarkers(data, off=0):
283 # Loop on markers
284 # Loop on markers
284 l = len(data)
285 l = len(data)
285 while off + _fm1fsize <= l:
286 while off + _fm1fsize <= l:
286 # read fixed part
287 # read fixed part
287 cur = data[off:off + _fm1fsize]
288 cur = data[off:off + _fm1fsize]
288 off += _fm1fsize
289 off += _fm1fsize
289 fixeddata = _unpack(_fm1fixed, cur)
290 fixeddata = _unpack(_fm1fixed, cur)
290 ttsize, seconds, tz, flags, numsuc, numpar, nummeta, prec = fixeddata
291 ttsize, seconds, tz, flags, numsuc, numpar, nummeta, prec = fixeddata
291 # extract the number of parents information
292 # extract the number of parents information
292 if numpar == _fm1parentnone:
293 if numpar == _fm1parentnone:
293 numpar = None
294 numpar = None
294 # build the date tuple (upgrade tz minutes to seconds)
295 # build the date tuple (upgrade tz minutes to seconds)
295 date = (seconds, tz * 60)
296 date = (seconds, tz * 60)
296 _fm1node = _fm1nodesha1
297 _fm1node = _fm1nodesha1
297 if flags & usingsha256:
298 if flags & usingsha256:
298 _fm1node = _fm1nodesha256
299 _fm1node = _fm1nodesha256
299 fnodesize = struct.calcsize(_fm1node)
300 fnodesize = struct.calcsize(_fm1node)
300 # read replacement
301 # read replacement
301 sucs = ()
302 sucs = ()
302 if numsuc:
303 if numsuc:
303 s = (fnodesize * numsuc)
304 s = (fnodesize * numsuc)
304 cur = data[off:off + s]
305 cur = data[off:off + s]
305 sucs = _unpack(_fm1node * numsuc, cur)
306 sucs = _unpack(_fm1node * numsuc, cur)
306 off += s
307 off += s
307 # read parents
308 # read parents
308 if numpar is None:
309 if numpar is None:
309 parents = None
310 parents = None
310 elif numpar == 0:
311 elif numpar == 0:
311 parents = ()
312 parents = ()
312 elif numpar: # neither None nor zero
313 elif numpar: # neither None nor zero
313 s = (fnodesize * numpar)
314 s = (fnodesize * numpar)
314 cur = data[off:off + s]
315 cur = data[off:off + s]
315 parents = _unpack(_fm1node * numpar, cur)
316 parents = _unpack(_fm1node * numpar, cur)
316 off += s
317 off += s
317 # read metadata
318 # read metadata
318 metaformat = '>' + (_fm1metapair * nummeta)
319 metaformat = '>' + (_fm1metapair * nummeta)
319 s = _fm1metapairsize * nummeta
320 s = _fm1metapairsize * nummeta
320 metapairsize = _unpack(metaformat, data[off:off + s])
321 metapairsize = _unpack(metaformat, data[off:off + s])
321 off += s
322 off += s
322 metadata = []
323 metadata = []
323 for idx in xrange(0, len(metapairsize), 2):
324 for idx in xrange(0, len(metapairsize), 2):
324 sk = metapairsize[idx]
325 sk = metapairsize[idx]
325 sv = metapairsize[idx + 1]
326 sv = metapairsize[idx + 1]
326 key = data[off:off + sk]
327 key = data[off:off + sk]
327 value = data[off + sk:off + sk + sv]
328 value = data[off + sk:off + sk + sv]
328 assert len(key) == sk
329 assert len(key) == sk
329 assert len(value) == sv
330 assert len(value) == sv
330 metadata.append((key, value))
331 metadata.append((key, value))
331 off += sk + sv
332 off += sk + sv
332 metadata = tuple(metadata)
333 metadata = tuple(metadata)
333
334
334 yield (prec, sucs, flags, metadata, date, parents)
335 yield (prec, sucs, flags, metadata, date, parents)
335
336
336 def _fm1encodeonemarker(marker):
337 def _fm1encodeonemarker(marker):
337 pre, sucs, flags, metadata, date, parents = marker
338 pre, sucs, flags, metadata, date, parents = marker
338 # determine node size
339 # determine node size
339 _fm1node = _fm1nodesha1
340 _fm1node = _fm1nodesha1
340 if flags & usingsha256:
341 if flags & usingsha256:
341 _fm1node = _fm1nodesha256
342 _fm1node = _fm1nodesha256
342 numsuc = len(sucs)
343 numsuc = len(sucs)
343 numextranodes = numsuc
344 numextranodes = numsuc
344 if parents is None:
345 if parents is None:
345 numpar = _fm1parentnone
346 numpar = _fm1parentnone
346 else:
347 else:
347 numpar = len(parents)
348 numpar = len(parents)
348 numextranodes += numpar
349 numextranodes += numpar
349 formatnodes = _fm1node * numextranodes
350 formatnodes = _fm1node * numextranodes
350 formatmeta = _fm1metapair * len(metadata)
351 formatmeta = _fm1metapair * len(metadata)
351 format = _fm1fixed + formatnodes + formatmeta
352 format = _fm1fixed + formatnodes + formatmeta
352 # tz is stored in minutes so we divide by 60
353 # tz is stored in minutes so we divide by 60
353 tz = date[1]//60
354 tz = date[1]//60
354 data = [None, date[0], tz, flags, numsuc, numpar, len(metadata), pre]
355 data = [None, date[0], tz, flags, numsuc, numpar, len(metadata), pre]
355 data.extend(sucs)
356 data.extend(sucs)
356 if parents is not None:
357 if parents is not None:
357 data.extend(parents)
358 data.extend(parents)
358 totalsize = struct.calcsize(format)
359 totalsize = struct.calcsize(format)
359 for key, value in metadata:
360 for key, value in metadata:
360 lk = len(key)
361 lk = len(key)
361 lv = len(value)
362 lv = len(value)
362 data.append(lk)
363 data.append(lk)
363 data.append(lv)
364 data.append(lv)
364 totalsize += lk + lv
365 totalsize += lk + lv
365 data[0] = totalsize
366 data[0] = totalsize
366 data = [_pack(format, *data)]
367 data = [_pack(format, *data)]
367 for key, value in metadata:
368 for key, value in metadata:
368 data.append(key)
369 data.append(key)
369 data.append(value)
370 data.append(value)
370 return ''.join(data)
371 return ''.join(data)
371
372
372 # mapping to read/write various marker formats
373 # mapping to read/write various marker formats
373 # <version> -> (decoder, encoder)
374 # <version> -> (decoder, encoder)
374 formats = {_fm0version: (_fm0readmarkers, _fm0encodeonemarker),
375 formats = {_fm0version: (_fm0readmarkers, _fm0encodeonemarker),
375 _fm1version: (_fm1readmarkers, _fm1encodeonemarker)}
376 _fm1version: (_fm1readmarkers, _fm1encodeonemarker)}
376
377
377 def _readmarkers(data):
378 def _readmarkers(data):
378 """Read and enumerate markers from raw data"""
379 """Read and enumerate markers from raw data"""
379 off = 0
380 off = 0
380 diskversion = _unpack('>B', data[off:off + 1])[0]
381 diskversion = _unpack('>B', data[off:off + 1])[0]
381 off += 1
382 off += 1
382 if diskversion not in formats:
383 if diskversion not in formats:
383 raise util.Abort(_('parsing obsolete marker: unknown version %r')
384 raise util.Abort(_('parsing obsolete marker: unknown version %r')
384 % diskversion)
385 % diskversion)
385 return diskversion, formats[diskversion][0](data, off)
386 return diskversion, formats[diskversion][0](data, off)
386
387
387 def encodemarkers(markers, addheader=False, version=_fm0version):
388 def encodemarkers(markers, addheader=False, version=_fm0version):
388 # Kept separate from flushmarkers(), it will be reused for
389 # Kept separate from flushmarkers(), it will be reused for
389 # markers exchange.
390 # markers exchange.
390 encodeone = formats[version][1]
391 encodeone = formats[version][1]
391 if addheader:
392 if addheader:
392 yield _pack('>B', version)
393 yield _pack('>B', version)
393 for marker in markers:
394 for marker in markers:
394 yield encodeone(marker)
395 yield encodeone(marker)
395
396
396
397
397 class marker(object):
398 class marker(object):
398 """Wrap obsolete marker raw data"""
399 """Wrap obsolete marker raw data"""
399
400
400 def __init__(self, repo, data):
401 def __init__(self, repo, data):
401 # the repo argument will be used to create changectx in later version
402 # the repo argument will be used to create changectx in later version
402 self._repo = repo
403 self._repo = repo
403 self._data = data
404 self._data = data
404 self._decodedmeta = None
405 self._decodedmeta = None
405
406
406 def __hash__(self):
407 def __hash__(self):
407 return hash(self._data)
408 return hash(self._data)
408
409
409 def __eq__(self, other):
410 def __eq__(self, other):
410 if type(other) != type(self):
411 if type(other) != type(self):
411 return False
412 return False
412 return self._data == other._data
413 return self._data == other._data
413
414
414 def precnode(self):
415 def precnode(self):
415 """Precursor changeset node identifier"""
416 """Precursor changeset node identifier"""
416 return self._data[0]
417 return self._data[0]
417
418
418 def succnodes(self):
419 def succnodes(self):
419 """List of successor changesets node identifiers"""
420 """List of successor changesets node identifiers"""
420 return self._data[1]
421 return self._data[1]
421
422
422 def parentnodes(self):
423 def parentnodes(self):
423 """Parents of the precursors (None if not recorded)"""
424 """Parents of the precursors (None if not recorded)"""
424 return self._data[5]
425 return self._data[5]
425
426
426 def metadata(self):
427 def metadata(self):
427 """Decoded metadata dictionary"""
428 """Decoded metadata dictionary"""
428 return dict(self._data[3])
429 return dict(self._data[3])
429
430
430 def date(self):
431 def date(self):
431 """Creation date as (unixtime, offset)"""
432 """Creation date as (unixtime, offset)"""
432 return self._data[4]
433 return self._data[4]
433
434
434 def flags(self):
435 def flags(self):
435 """The flags field of the marker"""
436 """The flags field of the marker"""
436 return self._data[2]
437 return self._data[2]
437
438
438 class obsstore(object):
439 class obsstore(object):
439 """Store obsolete markers
440 """Store obsolete markers
440
441
441 Markers can be accessed with two mappings:
442 Markers can be accessed with two mappings:
442 - precursors[x] -> set(markers on precursors edges of x)
443 - precursors[x] -> set(markers on precursors edges of x)
443 - successors[x] -> set(markers on successors edges of x)
444 - successors[x] -> set(markers on successors edges of x)
444 - children[x] -> set(markers on precursors edges of children(x)
445 - children[x] -> set(markers on precursors edges of children(x)
445 """
446 """
446
447
447 fields = ('prec', 'succs', 'flag', 'meta', 'date', 'parents')
448 fields = ('prec', 'succs', 'flag', 'meta', 'date', 'parents')
448 # prec: nodeid, precursor changesets
449 # prec: nodeid, precursor changesets
449 # succs: tuple of nodeid, successor changesets (0-N length)
450 # succs: tuple of nodeid, successor changesets (0-N length)
450 # flag: integer, flag field carrying modifier for the markers (see doc)
451 # flag: integer, flag field carrying modifier for the markers (see doc)
451 # meta: binary blob, encoded metadata dictionary
452 # meta: binary blob, encoded metadata dictionary
452 # date: (float, int) tuple, date of marker creation
453 # date: (float, int) tuple, date of marker creation
453 # parents: (tuple of nodeid) or None, parents of precursors
454 # parents: (tuple of nodeid) or None, parents of precursors
454 # None is used when no data has been recorded
455 # None is used when no data has been recorded
455
456
456 def __init__(self, sopener, defaultformat=_fm1version, readonly=False):
457 def __init__(self, sopener, defaultformat=_fm1version, readonly=False):
457 # caches for various obsolescence related cache
458 # caches for various obsolescence related cache
458 self.caches = {}
459 self.caches = {}
459 self._all = []
460 self._all = []
460 self.precursors = {}
461 self.precursors = {}
461 self.successors = {}
462 self.successors = {}
462 self.children = {}
463 self.children = {}
463 self.sopener = sopener
464 self.sopener = sopener
464 data = sopener.tryread('obsstore')
465 data = sopener.tryread('obsstore')
465 self._version = defaultformat
466 self._version = defaultformat
466 self._readonly = readonly
467 self._readonly = readonly
467 if data:
468 if data:
468 self._version, markers = _readmarkers(data)
469 self._version, markers = _readmarkers(data)
469 self._load(markers)
470 self._load(markers)
470
471
471 def __iter__(self):
472 def __iter__(self):
472 return iter(self._all)
473 return iter(self._all)
473
474
474 def __len__(self):
475 def __len__(self):
475 return len(self._all)
476 return len(self._all)
476
477
477 def __nonzero__(self):
478 def __nonzero__(self):
478 return bool(self._all)
479 return bool(self._all)
479
480
480 def create(self, transaction, prec, succs=(), flag=0, parents=None,
481 def create(self, transaction, prec, succs=(), flag=0, parents=None,
481 date=None, metadata=None):
482 date=None, metadata=None):
482 """obsolete: add a new obsolete marker
483 """obsolete: add a new obsolete marker
483
484
484 * ensuring it is hashable
485 * ensuring it is hashable
485 * check mandatory metadata
486 * check mandatory metadata
486 * encode metadata
487 * encode metadata
487
488
488 If you are a human writing code creating marker you want to use the
489 If you are a human writing code creating marker you want to use the
489 `createmarkers` function in this module instead.
490 `createmarkers` function in this module instead.
490
491
491 return True if a new marker have been added, False if the markers
492 return True if a new marker have been added, False if the markers
492 already existed (no op).
493 already existed (no op).
493 """
494 """
494 if metadata is None:
495 if metadata is None:
495 metadata = {}
496 metadata = {}
496 if date is None:
497 if date is None:
497 if 'date' in metadata:
498 if 'date' in metadata:
498 # as a courtesy for out-of-tree extensions
499 # as a courtesy for out-of-tree extensions
499 date = util.parsedate(metadata.pop('date'))
500 date = util.parsedate(metadata.pop('date'))
500 else:
501 else:
501 date = util.makedate()
502 date = util.makedate()
502 if len(prec) != 20:
503 if len(prec) != 20:
503 raise ValueError(prec)
504 raise ValueError(prec)
504 for succ in succs:
505 for succ in succs:
505 if len(succ) != 20:
506 if len(succ) != 20:
506 raise ValueError(succ)
507 raise ValueError(succ)
507 if prec in succs:
508 if prec in succs:
508 raise ValueError(_('in-marker cycle with %s') % node.hex(prec))
509 raise ValueError(_('in-marker cycle with %s') % node.hex(prec))
509
510
510 metadata = tuple(sorted(metadata.iteritems()))
511 metadata = tuple(sorted(metadata.iteritems()))
511
512
512 marker = (str(prec), tuple(succs), int(flag), metadata, date, parents)
513 marker = (str(prec), tuple(succs), int(flag), metadata, date, parents)
513 return bool(self.add(transaction, [marker]))
514 return bool(self.add(transaction, [marker]))
514
515
515 def add(self, transaction, markers):
516 def add(self, transaction, markers):
516 """Add new markers to the store
517 """Add new markers to the store
517
518
518 Take care of filtering duplicate.
519 Take care of filtering duplicate.
519 Return the number of new marker."""
520 Return the number of new marker."""
520 if self._readonly:
521 if self._readonly:
521 raise util.Abort('creating obsolete markers is not enabled on this '
522 raise util.Abort('creating obsolete markers is not enabled on this '
522 'repo')
523 'repo')
523 known = set(self._all)
524 known = set(self._all)
524 new = []
525 new = []
525 for m in markers:
526 for m in markers:
526 if m not in known:
527 if m not in known:
527 known.add(m)
528 known.add(m)
528 new.append(m)
529 new.append(m)
529 if new:
530 if new:
530 f = self.sopener('obsstore', 'ab')
531 f = self.sopener('obsstore', 'ab')
531 try:
532 try:
532 # Whether the file's current position is at the begin or at
533 # Whether the file's current position is at the begin or at
533 # the end after opening a file for appending is implementation
534 # the end after opening a file for appending is implementation
534 # defined. So we must seek to the end before calling tell(),
535 # defined. So we must seek to the end before calling tell(),
535 # or we may get a zero offset for non-zero sized files on
536 # or we may get a zero offset for non-zero sized files on
536 # some platforms (issue3543).
537 # some platforms (issue3543).
537 f.seek(0, _SEEK_END)
538 f.seek(0, _SEEK_END)
538 offset = f.tell()
539 offset = f.tell()
539 transaction.add('obsstore', offset)
540 transaction.add('obsstore', offset)
540 # offset == 0: new file - add the version header
541 # offset == 0: new file - add the version header
541 for bytes in encodemarkers(new, offset == 0, self._version):
542 for bytes in encodemarkers(new, offset == 0, self._version):
542 f.write(bytes)
543 f.write(bytes)
543 finally:
544 finally:
544 # XXX: f.close() == filecache invalidation == obsstore rebuilt.
545 # XXX: f.close() == filecache invalidation == obsstore rebuilt.
545 # call 'filecacheentry.refresh()' here
546 # call 'filecacheentry.refresh()' here
546 f.close()
547 f.close()
547 self._load(new)
548 self._load(new)
548 # new marker *may* have changed several set. invalidate the cache.
549 # new marker *may* have changed several set. invalidate the cache.
549 self.caches.clear()
550 self.caches.clear()
550 # records the number of new markers for the transaction hooks
551 # records the number of new markers for the transaction hooks
551 previous = int(transaction.hookargs.get('new_obsmarkers', '0'))
552 previous = int(transaction.hookargs.get('new_obsmarkers', '0'))
552 transaction.hookargs['new_obsmarkers'] = str(previous + len(new))
553 transaction.hookargs['new_obsmarkers'] = str(previous + len(new))
553 return len(new)
554 return len(new)
554
555
555 def mergemarkers(self, transaction, data):
556 def mergemarkers(self, transaction, data):
556 """merge a binary stream of markers inside the obsstore
557 """merge a binary stream of markers inside the obsstore
557
558
558 Returns the number of new markers added."""
559 Returns the number of new markers added."""
559 version, markers = _readmarkers(data)
560 version, markers = _readmarkers(data)
560 return self.add(transaction, markers)
561 return self.add(transaction, markers)
561
562
562 def _load(self, markers):
563 def _load(self, markers):
563 for mark in markers:
564 for mark in markers:
564 self._all.append(mark)
565 self._all.append(mark)
565 pre, sucs = mark[:2]
566 pre, sucs = mark[:2]
566 self.successors.setdefault(pre, set()).add(mark)
567 self.successors.setdefault(pre, set()).add(mark)
567 for suc in sucs:
568 for suc in sucs:
568 self.precursors.setdefault(suc, set()).add(mark)
569 self.precursors.setdefault(suc, set()).add(mark)
569 parents = mark[5]
570 parents = mark[5]
570 if parents is not None:
571 if parents is not None:
571 for p in parents:
572 for p in parents:
572 self.children.setdefault(p, set()).add(mark)
573 self.children.setdefault(p, set()).add(mark)
573 if node.nullid in self.precursors:
574 if node.nullid in self.precursors:
574 raise util.Abort(_('bad obsolescence marker detected: '
575 raise util.Abort(_('bad obsolescence marker detected: '
575 'invalid successors nullid'))
576 'invalid successors nullid'))
576 def relevantmarkers(self, nodes):
577 def relevantmarkers(self, nodes):
577 """return a set of all obsolescence markers relevant to a set of nodes.
578 """return a set of all obsolescence markers relevant to a set of nodes.
578
579
579 "relevant" to a set of nodes mean:
580 "relevant" to a set of nodes mean:
580
581
581 - marker that use this changeset as successor
582 - marker that use this changeset as successor
582 - prune marker of direct children on this changeset
583 - prune marker of direct children on this changeset
583 - recursive application of the two rules on precursors of these markers
584 - recursive application of the two rules on precursors of these markers
584
585
585 It is a set so you cannot rely on order."""
586 It is a set so you cannot rely on order."""
586
587
587 pendingnodes = set(nodes)
588 pendingnodes = set(nodes)
588 seenmarkers = set()
589 seenmarkers = set()
589 seennodes = set(pendingnodes)
590 seennodes = set(pendingnodes)
590 precursorsmarkers = self.precursors
591 precursorsmarkers = self.precursors
591 children = self.children
592 children = self.children
592 while pendingnodes:
593 while pendingnodes:
593 direct = set()
594 direct = set()
594 for current in pendingnodes:
595 for current in pendingnodes:
595 direct.update(precursorsmarkers.get(current, ()))
596 direct.update(precursorsmarkers.get(current, ()))
596 pruned = [m for m in children.get(current, ()) if not m[1]]
597 pruned = [m for m in children.get(current, ()) if not m[1]]
597 direct.update(pruned)
598 direct.update(pruned)
598 direct -= seenmarkers
599 direct -= seenmarkers
599 pendingnodes = set([m[0] for m in direct])
600 pendingnodes = set([m[0] for m in direct])
600 seenmarkers |= direct
601 seenmarkers |= direct
601 pendingnodes -= seennodes
602 pendingnodes -= seennodes
602 seennodes |= pendingnodes
603 seennodes |= pendingnodes
603 return seenmarkers
604 return seenmarkers
604
605
605 def commonversion(versions):
606 def commonversion(versions):
606 """Return the newest version listed in both versions and our local formats.
607 """Return the newest version listed in both versions and our local formats.
607
608
608 Returns None if no common version exists.
609 Returns None if no common version exists.
609 """
610 """
610 versions.sort(reverse=True)
611 versions.sort(reverse=True)
611 # search for highest version known on both side
612 # search for highest version known on both side
612 for v in versions:
613 for v in versions:
613 if v in formats:
614 if v in formats:
614 return v
615 return v
615 return None
616 return None
616
617
617 # arbitrary picked to fit into 8K limit from HTTP server
618 # arbitrary picked to fit into 8K limit from HTTP server
618 # you have to take in account:
619 # you have to take in account:
619 # - the version header
620 # - the version header
620 # - the base85 encoding
621 # - the base85 encoding
621 _maxpayload = 5300
622 _maxpayload = 5300
622
623
623 def _pushkeyescape(markers):
624 def _pushkeyescape(markers):
624 """encode markers into a dict suitable for pushkey exchange
625 """encode markers into a dict suitable for pushkey exchange
625
626
626 - binary data is base85 encoded
627 - binary data is base85 encoded
627 - split in chunks smaller than 5300 bytes"""
628 - split in chunks smaller than 5300 bytes"""
628 keys = {}
629 keys = {}
629 parts = []
630 parts = []
630 currentlen = _maxpayload * 2 # ensure we create a new part
631 currentlen = _maxpayload * 2 # ensure we create a new part
631 for marker in markers:
632 for marker in markers:
632 nextdata = _fm0encodeonemarker(marker)
633 nextdata = _fm0encodeonemarker(marker)
633 if (len(nextdata) + currentlen > _maxpayload):
634 if (len(nextdata) + currentlen > _maxpayload):
634 currentpart = []
635 currentpart = []
635 currentlen = 0
636 currentlen = 0
636 parts.append(currentpart)
637 parts.append(currentpart)
637 currentpart.append(nextdata)
638 currentpart.append(nextdata)
638 currentlen += len(nextdata)
639 currentlen += len(nextdata)
639 for idx, part in enumerate(reversed(parts)):
640 for idx, part in enumerate(reversed(parts)):
640 data = ''.join([_pack('>B', _fm0version)] + part)
641 data = ''.join([_pack('>B', _fm0version)] + part)
641 keys['dump%i' % idx] = base85.b85encode(data)
642 keys['dump%i' % idx] = base85.b85encode(data)
642 return keys
643 return keys
643
644
644 def listmarkers(repo):
645 def listmarkers(repo):
645 """List markers over pushkey"""
646 """List markers over pushkey"""
646 if not repo.obsstore:
647 if not repo.obsstore:
647 return {}
648 return {}
648 return _pushkeyescape(repo.obsstore)
649 return _pushkeyescape(repo.obsstore)
649
650
650 def pushmarker(repo, key, old, new):
651 def pushmarker(repo, key, old, new):
651 """Push markers over pushkey"""
652 """Push markers over pushkey"""
652 if not key.startswith('dump'):
653 if not key.startswith('dump'):
653 repo.ui.warn(_('unknown key: %r') % key)
654 repo.ui.warn(_('unknown key: %r') % key)
654 return 0
655 return 0
655 if old:
656 if old:
656 repo.ui.warn(_('unexpected old value for %r') % key)
657 repo.ui.warn(_('unexpected old value for %r') % key)
657 return 0
658 return 0
658 data = base85.b85decode(new)
659 data = base85.b85decode(new)
659 lock = repo.lock()
660 lock = repo.lock()
660 try:
661 try:
661 tr = repo.transaction('pushkey: obsolete markers')
662 tr = repo.transaction('pushkey: obsolete markers')
662 try:
663 try:
663 repo.obsstore.mergemarkers(tr, data)
664 repo.obsstore.mergemarkers(tr, data)
664 tr.close()
665 tr.close()
665 return 1
666 return 1
666 finally:
667 finally:
667 tr.release()
668 tr.release()
668 finally:
669 finally:
669 lock.release()
670 lock.release()
670
671
671 def getmarkers(repo, nodes=None):
672 def getmarkers(repo, nodes=None):
672 """returns markers known in a repository
673 """returns markers known in a repository
673
674
674 If <nodes> is specified, only markers "relevant" to those nodes are are
675 If <nodes> is specified, only markers "relevant" to those nodes are are
675 returned"""
676 returned"""
676 if nodes is None:
677 if nodes is None:
677 rawmarkers = repo.obsstore
678 rawmarkers = repo.obsstore
678 else:
679 else:
679 rawmarkers = repo.obsstore.relevantmarkers(nodes)
680 rawmarkers = repo.obsstore.relevantmarkers(nodes)
680
681
681 for markerdata in rawmarkers:
682 for markerdata in rawmarkers:
682 yield marker(repo, markerdata)
683 yield marker(repo, markerdata)
683
684
684 def relevantmarkers(repo, node):
685 def relevantmarkers(repo, node):
685 """all obsolete markers relevant to some revision"""
686 """all obsolete markers relevant to some revision"""
686 for markerdata in repo.obsstore.relevantmarkers(node):
687 for markerdata in repo.obsstore.relevantmarkers(node):
687 yield marker(repo, markerdata)
688 yield marker(repo, markerdata)
688
689
689
690
690 def precursormarkers(ctx):
691 def precursormarkers(ctx):
691 """obsolete marker marking this changeset as a successors"""
692 """obsolete marker marking this changeset as a successors"""
692 for data in ctx._repo.obsstore.precursors.get(ctx.node(), ()):
693 for data in ctx._repo.obsstore.precursors.get(ctx.node(), ()):
693 yield marker(ctx._repo, data)
694 yield marker(ctx._repo, data)
694
695
695 def successormarkers(ctx):
696 def successormarkers(ctx):
696 """obsolete marker making this changeset obsolete"""
697 """obsolete marker making this changeset obsolete"""
697 for data in ctx._repo.obsstore.successors.get(ctx.node(), ()):
698 for data in ctx._repo.obsstore.successors.get(ctx.node(), ()):
698 yield marker(ctx._repo, data)
699 yield marker(ctx._repo, data)
699
700
700 def allsuccessors(obsstore, nodes, ignoreflags=0):
701 def allsuccessors(obsstore, nodes, ignoreflags=0):
701 """Yield node for every successor of <nodes>.
702 """Yield node for every successor of <nodes>.
702
703
703 Some successors may be unknown locally.
704 Some successors may be unknown locally.
704
705
705 This is a linear yield unsuited to detecting split changesets. It includes
706 This is a linear yield unsuited to detecting split changesets. It includes
706 initial nodes too."""
707 initial nodes too."""
707 remaining = set(nodes)
708 remaining = set(nodes)
708 seen = set(remaining)
709 seen = set(remaining)
709 while remaining:
710 while remaining:
710 current = remaining.pop()
711 current = remaining.pop()
711 yield current
712 yield current
712 for mark in obsstore.successors.get(current, ()):
713 for mark in obsstore.successors.get(current, ()):
713 # ignore marker flagged with specified flag
714 # ignore marker flagged with specified flag
714 if mark[2] & ignoreflags:
715 if mark[2] & ignoreflags:
715 continue
716 continue
716 for suc in mark[1]:
717 for suc in mark[1]:
717 if suc not in seen:
718 if suc not in seen:
718 seen.add(suc)
719 seen.add(suc)
719 remaining.add(suc)
720 remaining.add(suc)
720
721
721 def allprecursors(obsstore, nodes, ignoreflags=0):
722 def allprecursors(obsstore, nodes, ignoreflags=0):
722 """Yield node for every precursors of <nodes>.
723 """Yield node for every precursors of <nodes>.
723
724
724 Some precursors may be unknown locally.
725 Some precursors may be unknown locally.
725
726
726 This is a linear yield unsuited to detecting folded changesets. It includes
727 This is a linear yield unsuited to detecting folded changesets. It includes
727 initial nodes too."""
728 initial nodes too."""
728
729
729 remaining = set(nodes)
730 remaining = set(nodes)
730 seen = set(remaining)
731 seen = set(remaining)
731 while remaining:
732 while remaining:
732 current = remaining.pop()
733 current = remaining.pop()
733 yield current
734 yield current
734 for mark in obsstore.precursors.get(current, ()):
735 for mark in obsstore.precursors.get(current, ()):
735 # ignore marker flagged with specified flag
736 # ignore marker flagged with specified flag
736 if mark[2] & ignoreflags:
737 if mark[2] & ignoreflags:
737 continue
738 continue
738 suc = mark[0]
739 suc = mark[0]
739 if suc not in seen:
740 if suc not in seen:
740 seen.add(suc)
741 seen.add(suc)
741 remaining.add(suc)
742 remaining.add(suc)
742
743
743 def foreground(repo, nodes):
744 def foreground(repo, nodes):
744 """return all nodes in the "foreground" of other node
745 """return all nodes in the "foreground" of other node
745
746
746 The foreground of a revision is anything reachable using parent -> children
747 The foreground of a revision is anything reachable using parent -> children
747 or precursor -> successor relation. It is very similar to "descendant" but
748 or precursor -> successor relation. It is very similar to "descendant" but
748 augmented with obsolescence information.
749 augmented with obsolescence information.
749
750
750 Beware that possible obsolescence cycle may result if complex situation.
751 Beware that possible obsolescence cycle may result if complex situation.
751 """
752 """
752 repo = repo.unfiltered()
753 repo = repo.unfiltered()
753 foreground = set(repo.set('%ln::', nodes))
754 foreground = set(repo.set('%ln::', nodes))
754 if repo.obsstore:
755 if repo.obsstore:
755 # We only need this complicated logic if there is obsolescence
756 # We only need this complicated logic if there is obsolescence
756 # XXX will probably deserve an optimised revset.
757 # XXX will probably deserve an optimised revset.
757 nm = repo.changelog.nodemap
758 nm = repo.changelog.nodemap
758 plen = -1
759 plen = -1
759 # compute the whole set of successors or descendants
760 # compute the whole set of successors or descendants
760 while len(foreground) != plen:
761 while len(foreground) != plen:
761 plen = len(foreground)
762 plen = len(foreground)
762 succs = set(c.node() for c in foreground)
763 succs = set(c.node() for c in foreground)
763 mutable = [c.node() for c in foreground if c.mutable()]
764 mutable = [c.node() for c in foreground if c.mutable()]
764 succs.update(allsuccessors(repo.obsstore, mutable))
765 succs.update(allsuccessors(repo.obsstore, mutable))
765 known = (n for n in succs if n in nm)
766 known = (n for n in succs if n in nm)
766 foreground = set(repo.set('%ln::', known))
767 foreground = set(repo.set('%ln::', known))
767 return set(c.node() for c in foreground)
768 return set(c.node() for c in foreground)
768
769
769
770
770 def successorssets(repo, initialnode, cache=None):
771 def successorssets(repo, initialnode, cache=None):
771 """Return all set of successors of initial nodes
772 """Return all set of successors of initial nodes
772
773
773 The successors set of a changeset A are a group of revisions that succeed
774 The successors set of a changeset A are a group of revisions that succeed
774 A. It succeeds A as a consistent whole, each revision being only a partial
775 A. It succeeds A as a consistent whole, each revision being only a partial
775 replacement. The successors set contains non-obsolete changesets only.
776 replacement. The successors set contains non-obsolete changesets only.
776
777
777 This function returns the full list of successor sets which is why it
778 This function returns the full list of successor sets which is why it
778 returns a list of tuples and not just a single tuple. Each tuple is a valid
779 returns a list of tuples and not just a single tuple. Each tuple is a valid
779 successors set. Not that (A,) may be a valid successors set for changeset A
780 successors set. Not that (A,) may be a valid successors set for changeset A
780 (see below).
781 (see below).
781
782
782 In most cases, a changeset A will have a single element (e.g. the changeset
783 In most cases, a changeset A will have a single element (e.g. the changeset
783 A is replaced by A') in its successors set. Though, it is also common for a
784 A is replaced by A') in its successors set. Though, it is also common for a
784 changeset A to have no elements in its successor set (e.g. the changeset
785 changeset A to have no elements in its successor set (e.g. the changeset
785 has been pruned). Therefore, the returned list of successors sets will be
786 has been pruned). Therefore, the returned list of successors sets will be
786 [(A',)] or [], respectively.
787 [(A',)] or [], respectively.
787
788
788 When a changeset A is split into A' and B', however, it will result in a
789 When a changeset A is split into A' and B', however, it will result in a
789 successors set containing more than a single element, i.e. [(A',B')].
790 successors set containing more than a single element, i.e. [(A',B')].
790 Divergent changesets will result in multiple successors sets, i.e. [(A',),
791 Divergent changesets will result in multiple successors sets, i.e. [(A',),
791 (A'')].
792 (A'')].
792
793
793 If a changeset A is not obsolete, then it will conceptually have no
794 If a changeset A is not obsolete, then it will conceptually have no
794 successors set. To distinguish this from a pruned changeset, the successor
795 successors set. To distinguish this from a pruned changeset, the successor
795 set will only contain itself, i.e. [(A,)].
796 set will only contain itself, i.e. [(A,)].
796
797
797 Finally, successors unknown locally are considered to be pruned (obsoleted
798 Finally, successors unknown locally are considered to be pruned (obsoleted
798 without any successors).
799 without any successors).
799
800
800 The optional `cache` parameter is a dictionary that may contain precomputed
801 The optional `cache` parameter is a dictionary that may contain precomputed
801 successors sets. It is meant to reuse the computation of a previous call to
802 successors sets. It is meant to reuse the computation of a previous call to
802 `successorssets` when multiple calls are made at the same time. The cache
803 `successorssets` when multiple calls are made at the same time. The cache
803 dictionary is updated in place. The caller is responsible for its live
804 dictionary is updated in place. The caller is responsible for its live
804 spawn. Code that makes multiple calls to `successorssets` *must* use this
805 spawn. Code that makes multiple calls to `successorssets` *must* use this
805 cache mechanism or suffer terrible performances.
806 cache mechanism or suffer terrible performances.
806
807
807 """
808 """
808
809
809 succmarkers = repo.obsstore.successors
810 succmarkers = repo.obsstore.successors
810
811
811 # Stack of nodes we search successors sets for
812 # Stack of nodes we search successors sets for
812 toproceed = [initialnode]
813 toproceed = [initialnode]
813 # set version of above list for fast loop detection
814 # set version of above list for fast loop detection
814 # element added to "toproceed" must be added here
815 # element added to "toproceed" must be added here
815 stackedset = set(toproceed)
816 stackedset = set(toproceed)
816 if cache is None:
817 if cache is None:
817 cache = {}
818 cache = {}
818
819
819 # This while loop is the flattened version of a recursive search for
820 # This while loop is the flattened version of a recursive search for
820 # successors sets
821 # successors sets
821 #
822 #
822 # def successorssets(x):
823 # def successorssets(x):
823 # successors = directsuccessors(x)
824 # successors = directsuccessors(x)
824 # ss = [[]]
825 # ss = [[]]
825 # for succ in directsuccessors(x):
826 # for succ in directsuccessors(x):
826 # # product as in itertools cartesian product
827 # # product as in itertools cartesian product
827 # ss = product(ss, successorssets(succ))
828 # ss = product(ss, successorssets(succ))
828 # return ss
829 # return ss
829 #
830 #
830 # But we can not use plain recursive calls here:
831 # But we can not use plain recursive calls here:
831 # - that would blow the python call stack
832 # - that would blow the python call stack
832 # - obsolescence markers may have cycles, we need to handle them.
833 # - obsolescence markers may have cycles, we need to handle them.
833 #
834 #
834 # The `toproceed` list act as our call stack. Every node we search
835 # The `toproceed` list act as our call stack. Every node we search
835 # successors set for are stacked there.
836 # successors set for are stacked there.
836 #
837 #
837 # The `stackedset` is set version of this stack used to check if a node is
838 # The `stackedset` is set version of this stack used to check if a node is
838 # already stacked. This check is used to detect cycles and prevent infinite
839 # already stacked. This check is used to detect cycles and prevent infinite
839 # loop.
840 # loop.
840 #
841 #
841 # successors set of all nodes are stored in the `cache` dictionary.
842 # successors set of all nodes are stored in the `cache` dictionary.
842 #
843 #
843 # After this while loop ends we use the cache to return the successors sets
844 # After this while loop ends we use the cache to return the successors sets
844 # for the node requested by the caller.
845 # for the node requested by the caller.
845 while toproceed:
846 while toproceed:
846 # Every iteration tries to compute the successors sets of the topmost
847 # Every iteration tries to compute the successors sets of the topmost
847 # node of the stack: CURRENT.
848 # node of the stack: CURRENT.
848 #
849 #
849 # There are four possible outcomes:
850 # There are four possible outcomes:
850 #
851 #
851 # 1) We already know the successors sets of CURRENT:
852 # 1) We already know the successors sets of CURRENT:
852 # -> mission accomplished, pop it from the stack.
853 # -> mission accomplished, pop it from the stack.
853 # 2) Node is not obsolete:
854 # 2) Node is not obsolete:
854 # -> the node is its own successors sets. Add it to the cache.
855 # -> the node is its own successors sets. Add it to the cache.
855 # 3) We do not know successors set of direct successors of CURRENT:
856 # 3) We do not know successors set of direct successors of CURRENT:
856 # -> We add those successors to the stack.
857 # -> We add those successors to the stack.
857 # 4) We know successors sets of all direct successors of CURRENT:
858 # 4) We know successors sets of all direct successors of CURRENT:
858 # -> We can compute CURRENT successors set and add it to the
859 # -> We can compute CURRENT successors set and add it to the
859 # cache.
860 # cache.
860 #
861 #
861 current = toproceed[-1]
862 current = toproceed[-1]
862 if current in cache:
863 if current in cache:
863 # case (1): We already know the successors sets
864 # case (1): We already know the successors sets
864 stackedset.remove(toproceed.pop())
865 stackedset.remove(toproceed.pop())
865 elif current not in succmarkers:
866 elif current not in succmarkers:
866 # case (2): The node is not obsolete.
867 # case (2): The node is not obsolete.
867 if current in repo:
868 if current in repo:
868 # We have a valid last successors.
869 # We have a valid last successors.
869 cache[current] = [(current,)]
870 cache[current] = [(current,)]
870 else:
871 else:
871 # Final obsolete version is unknown locally.
872 # Final obsolete version is unknown locally.
872 # Do not count that as a valid successors
873 # Do not count that as a valid successors
873 cache[current] = []
874 cache[current] = []
874 else:
875 else:
875 # cases (3) and (4)
876 # cases (3) and (4)
876 #
877 #
877 # We proceed in two phases. Phase 1 aims to distinguish case (3)
878 # We proceed in two phases. Phase 1 aims to distinguish case (3)
878 # from case (4):
879 # from case (4):
879 #
880 #
880 # For each direct successors of CURRENT, we check whether its
881 # For each direct successors of CURRENT, we check whether its
881 # successors sets are known. If they are not, we stack the
882 # successors sets are known. If they are not, we stack the
882 # unknown node and proceed to the next iteration of the while
883 # unknown node and proceed to the next iteration of the while
883 # loop. (case 3)
884 # loop. (case 3)
884 #
885 #
885 # During this step, we may detect obsolescence cycles: a node
886 # During this step, we may detect obsolescence cycles: a node
886 # with unknown successors sets but already in the call stack.
887 # with unknown successors sets but already in the call stack.
887 # In such a situation, we arbitrary set the successors sets of
888 # In such a situation, we arbitrary set the successors sets of
888 # the node to nothing (node pruned) to break the cycle.
889 # the node to nothing (node pruned) to break the cycle.
889 #
890 #
890 # If no break was encountered we proceed to phase 2.
891 # If no break was encountered we proceed to phase 2.
891 #
892 #
892 # Phase 2 computes successors sets of CURRENT (case 4); see details
893 # Phase 2 computes successors sets of CURRENT (case 4); see details
893 # in phase 2 itself.
894 # in phase 2 itself.
894 #
895 #
895 # Note the two levels of iteration in each phase.
896 # Note the two levels of iteration in each phase.
896 # - The first one handles obsolescence markers using CURRENT as
897 # - The first one handles obsolescence markers using CURRENT as
897 # precursor (successors markers of CURRENT).
898 # precursor (successors markers of CURRENT).
898 #
899 #
899 # Having multiple entry here means divergence.
900 # Having multiple entry here means divergence.
900 #
901 #
901 # - The second one handles successors defined in each marker.
902 # - The second one handles successors defined in each marker.
902 #
903 #
903 # Having none means pruned node, multiple successors means split,
904 # Having none means pruned node, multiple successors means split,
904 # single successors are standard replacement.
905 # single successors are standard replacement.
905 #
906 #
906 for mark in sorted(succmarkers[current]):
907 for mark in sorted(succmarkers[current]):
907 for suc in mark[1]:
908 for suc in mark[1]:
908 if suc not in cache:
909 if suc not in cache:
909 if suc in stackedset:
910 if suc in stackedset:
910 # cycle breaking
911 # cycle breaking
911 cache[suc] = []
912 cache[suc] = []
912 else:
913 else:
913 # case (3) If we have not computed successors sets
914 # case (3) If we have not computed successors sets
914 # of one of those successors we add it to the
915 # of one of those successors we add it to the
915 # `toproceed` stack and stop all work for this
916 # `toproceed` stack and stop all work for this
916 # iteration.
917 # iteration.
917 toproceed.append(suc)
918 toproceed.append(suc)
918 stackedset.add(suc)
919 stackedset.add(suc)
919 break
920 break
920 else:
921 else:
921 continue
922 continue
922 break
923 break
923 else:
924 else:
924 # case (4): we know all successors sets of all direct
925 # case (4): we know all successors sets of all direct
925 # successors
926 # successors
926 #
927 #
927 # Successors set contributed by each marker depends on the
928 # Successors set contributed by each marker depends on the
928 # successors sets of all its "successors" node.
929 # successors sets of all its "successors" node.
929 #
930 #
930 # Each different marker is a divergence in the obsolescence
931 # Each different marker is a divergence in the obsolescence
931 # history. It contributes successors sets distinct from other
932 # history. It contributes successors sets distinct from other
932 # markers.
933 # markers.
933 #
934 #
934 # Within a marker, a successor may have divergent successors
935 # Within a marker, a successor may have divergent successors
935 # sets. In such a case, the marker will contribute multiple
936 # sets. In such a case, the marker will contribute multiple
936 # divergent successors sets. If multiple successors have
937 # divergent successors sets. If multiple successors have
937 # divergent successors sets, a Cartesian product is used.
938 # divergent successors sets, a Cartesian product is used.
938 #
939 #
939 # At the end we post-process successors sets to remove
940 # At the end we post-process successors sets to remove
940 # duplicated entry and successors set that are strict subset of
941 # duplicated entry and successors set that are strict subset of
941 # another one.
942 # another one.
942 succssets = []
943 succssets = []
943 for mark in sorted(succmarkers[current]):
944 for mark in sorted(succmarkers[current]):
944 # successors sets contributed by this marker
945 # successors sets contributed by this marker
945 markss = [[]]
946 markss = [[]]
946 for suc in mark[1]:
947 for suc in mark[1]:
947 # cardinal product with previous successors
948 # cardinal product with previous successors
948 productresult = []
949 productresult = []
949 for prefix in markss:
950 for prefix in markss:
950 for suffix in cache[suc]:
951 for suffix in cache[suc]:
951 newss = list(prefix)
952 newss = list(prefix)
952 for part in suffix:
953 for part in suffix:
953 # do not duplicated entry in successors set
954 # do not duplicated entry in successors set
954 # first entry wins.
955 # first entry wins.
955 if part not in newss:
956 if part not in newss:
956 newss.append(part)
957 newss.append(part)
957 productresult.append(newss)
958 productresult.append(newss)
958 markss = productresult
959 markss = productresult
959 succssets.extend(markss)
960 succssets.extend(markss)
960 # remove duplicated and subset
961 # remove duplicated and subset
961 seen = []
962 seen = []
962 final = []
963 final = []
963 candidate = sorted(((set(s), s) for s in succssets if s),
964 candidate = sorted(((set(s), s) for s in succssets if s),
964 key=lambda x: len(x[1]), reverse=True)
965 key=lambda x: len(x[1]), reverse=True)
965 for setversion, listversion in candidate:
966 for setversion, listversion in candidate:
966 for seenset in seen:
967 for seenset in seen:
967 if setversion.issubset(seenset):
968 if setversion.issubset(seenset):
968 break
969 break
969 else:
970 else:
970 final.append(listversion)
971 final.append(listversion)
971 seen.append(setversion)
972 seen.append(setversion)
972 final.reverse() # put small successors set first
973 final.reverse() # put small successors set first
973 cache[current] = final
974 cache[current] = final
974 return cache[initialnode]
975 return cache[initialnode]
975
976
976 def _knownrevs(repo, nodes):
977 def _knownrevs(repo, nodes):
977 """yield revision numbers of known nodes passed in parameters
978 """yield revision numbers of known nodes passed in parameters
978
979
979 Unknown revisions are silently ignored."""
980 Unknown revisions are silently ignored."""
980 torev = repo.changelog.nodemap.get
981 torev = repo.changelog.nodemap.get
981 for n in nodes:
982 for n in nodes:
982 rev = torev(n)
983 rev = torev(n)
983 if rev is not None:
984 if rev is not None:
984 yield rev
985 yield rev
985
986
986 # mapping of 'set-name' -> <function to compute this set>
987 # mapping of 'set-name' -> <function to compute this set>
987 cachefuncs = {}
988 cachefuncs = {}
988 def cachefor(name):
989 def cachefor(name):
989 """Decorator to register a function as computing the cache for a set"""
990 """Decorator to register a function as computing the cache for a set"""
990 def decorator(func):
991 def decorator(func):
991 assert name not in cachefuncs
992 assert name not in cachefuncs
992 cachefuncs[name] = func
993 cachefuncs[name] = func
993 return func
994 return func
994 return decorator
995 return decorator
995
996
996 def getrevs(repo, name):
997 def getrevs(repo, name):
997 """Return the set of revision that belong to the <name> set
998 """Return the set of revision that belong to the <name> set
998
999
999 Such access may compute the set and cache it for future use"""
1000 Such access may compute the set and cache it for future use"""
1000 repo = repo.unfiltered()
1001 repo = repo.unfiltered()
1001 if not repo.obsstore:
1002 if not repo.obsstore:
1002 return frozenset()
1003 return frozenset()
1003 if name not in repo.obsstore.caches:
1004 if name not in repo.obsstore.caches:
1004 repo.obsstore.caches[name] = cachefuncs[name](repo)
1005 repo.obsstore.caches[name] = cachefuncs[name](repo)
1005 return repo.obsstore.caches[name]
1006 return repo.obsstore.caches[name]
1006
1007
1007 # To be simple we need to invalidate obsolescence cache when:
1008 # To be simple we need to invalidate obsolescence cache when:
1008 #
1009 #
1009 # - new changeset is added:
1010 # - new changeset is added:
1010 # - public phase is changed
1011 # - public phase is changed
1011 # - obsolescence marker are added
1012 # - obsolescence marker are added
1012 # - strip is used a repo
1013 # - strip is used a repo
1013 def clearobscaches(repo):
1014 def clearobscaches(repo):
1014 """Remove all obsolescence related cache from a repo
1015 """Remove all obsolescence related cache from a repo
1015
1016
1016 This remove all cache in obsstore is the obsstore already exist on the
1017 This remove all cache in obsstore is the obsstore already exist on the
1017 repo.
1018 repo.
1018
1019
1019 (We could be smarter here given the exact event that trigger the cache
1020 (We could be smarter here given the exact event that trigger the cache
1020 clearing)"""
1021 clearing)"""
1021 # only clear cache is there is obsstore data in this repo
1022 # only clear cache is there is obsstore data in this repo
1022 if 'obsstore' in repo._filecache:
1023 if 'obsstore' in repo._filecache:
1023 repo.obsstore.caches.clear()
1024 repo.obsstore.caches.clear()
1024
1025
1025 @cachefor('obsolete')
1026 @cachefor('obsolete')
1026 def _computeobsoleteset(repo):
1027 def _computeobsoleteset(repo):
1027 """the set of obsolete revisions"""
1028 """the set of obsolete revisions"""
1028 obs = set()
1029 obs = set()
1029 getrev = repo.changelog.nodemap.get
1030 getrev = repo.changelog.nodemap.get
1030 getphase = repo._phasecache.phase
1031 getphase = repo._phasecache.phase
1031 for n in repo.obsstore.successors:
1032 for n in repo.obsstore.successors:
1032 rev = getrev(n)
1033 rev = getrev(n)
1033 if rev is not None and getphase(repo, rev):
1034 if rev is not None and getphase(repo, rev):
1034 obs.add(rev)
1035 obs.add(rev)
1035 return obs
1036 return obs
1036
1037
1037 @cachefor('unstable')
1038 @cachefor('unstable')
1038 def _computeunstableset(repo):
1039 def _computeunstableset(repo):
1039 """the set of non obsolete revisions with obsolete parents"""
1040 """the set of non obsolete revisions with obsolete parents"""
1040 # revset is not efficient enough here
1041 # revset is not efficient enough here
1041 # we do (obsolete()::) - obsolete() by hand
1042 # we do (obsolete()::) - obsolete() by hand
1042 obs = getrevs(repo, 'obsolete')
1043 obs = getrevs(repo, 'obsolete')
1043 if not obs:
1044 if not obs:
1044 return set()
1045 return set()
1045 cl = repo.changelog
1046 cl = repo.changelog
1046 return set(r for r in cl.descendants(obs) if r not in obs)
1047 return set(r for r in cl.descendants(obs) if r not in obs)
1047
1048
1048 @cachefor('suspended')
1049 @cachefor('suspended')
1049 def _computesuspendedset(repo):
1050 def _computesuspendedset(repo):
1050 """the set of obsolete parents with non obsolete descendants"""
1051 """the set of obsolete parents with non obsolete descendants"""
1051 suspended = repo.changelog.ancestors(getrevs(repo, 'unstable'))
1052 suspended = repo.changelog.ancestors(getrevs(repo, 'unstable'))
1052 return set(r for r in getrevs(repo, 'obsolete') if r in suspended)
1053 return set(r for r in getrevs(repo, 'obsolete') if r in suspended)
1053
1054
1054 @cachefor('extinct')
1055 @cachefor('extinct')
1055 def _computeextinctset(repo):
1056 def _computeextinctset(repo):
1056 """the set of obsolete parents without non obsolete descendants"""
1057 """the set of obsolete parents without non obsolete descendants"""
1057 return getrevs(repo, 'obsolete') - getrevs(repo, 'suspended')
1058 return getrevs(repo, 'obsolete') - getrevs(repo, 'suspended')
1058
1059
1059
1060
1060 @cachefor('bumped')
1061 @cachefor('bumped')
1061 def _computebumpedset(repo):
1062 def _computebumpedset(repo):
1062 """the set of revs trying to obsolete public revisions"""
1063 """the set of revs trying to obsolete public revisions"""
1063 bumped = set()
1064 bumped = set()
1064 # util function (avoid attribute lookup in the loop)
1065 # util function (avoid attribute lookup in the loop)
1065 phase = repo._phasecache.phase # would be faster to grab the full list
1066 phase = repo._phasecache.phase # would be faster to grab the full list
1066 public = phases.public
1067 public = phases.public
1067 cl = repo.changelog
1068 cl = repo.changelog
1068 torev = cl.nodemap.get
1069 torev = cl.nodemap.get
1069 obs = getrevs(repo, 'obsolete')
1070 obs = getrevs(repo, 'obsolete')
1070 for rev in repo:
1071 for rev in repo:
1071 # We only evaluate mutable, non-obsolete revision
1072 # We only evaluate mutable, non-obsolete revision
1072 if (public < phase(repo, rev)) and (rev not in obs):
1073 if (public < phase(repo, rev)) and (rev not in obs):
1073 node = cl.node(rev)
1074 node = cl.node(rev)
1074 # (future) A cache of precursors may worth if split is very common
1075 # (future) A cache of precursors may worth if split is very common
1075 for pnode in allprecursors(repo.obsstore, [node],
1076 for pnode in allprecursors(repo.obsstore, [node],
1076 ignoreflags=bumpedfix):
1077 ignoreflags=bumpedfix):
1077 prev = torev(pnode) # unfiltered! but so is phasecache
1078 prev = torev(pnode) # unfiltered! but so is phasecache
1078 if (prev is not None) and (phase(repo, prev) <= public):
1079 if (prev is not None) and (phase(repo, prev) <= public):
1079 # we have a public precursors
1080 # we have a public precursors
1080 bumped.add(rev)
1081 bumped.add(rev)
1081 break # Next draft!
1082 break # Next draft!
1082 return bumped
1083 return bumped
1083
1084
1084 @cachefor('divergent')
1085 @cachefor('divergent')
1085 def _computedivergentset(repo):
1086 def _computedivergentset(repo):
1086 """the set of rev that compete to be the final successors of some revision.
1087 """the set of rev that compete to be the final successors of some revision.
1087 """
1088 """
1088 divergent = set()
1089 divergent = set()
1089 obsstore = repo.obsstore
1090 obsstore = repo.obsstore
1090 newermap = {}
1091 newermap = {}
1091 for ctx in repo.set('(not public()) - obsolete()'):
1092 for ctx in repo.set('(not public()) - obsolete()'):
1092 mark = obsstore.precursors.get(ctx.node(), ())
1093 mark = obsstore.precursors.get(ctx.node(), ())
1093 toprocess = set(mark)
1094 toprocess = set(mark)
1094 while toprocess:
1095 while toprocess:
1095 prec = toprocess.pop()[0]
1096 prec = toprocess.pop()[0]
1096 if prec not in newermap:
1097 if prec not in newermap:
1097 successorssets(repo, prec, newermap)
1098 successorssets(repo, prec, newermap)
1098 newer = [n for n in newermap[prec] if n]
1099 newer = [n for n in newermap[prec] if n]
1099 if len(newer) > 1:
1100 if len(newer) > 1:
1100 divergent.add(ctx.rev())
1101 divergent.add(ctx.rev())
1101 break
1102 break
1102 toprocess.update(obsstore.precursors.get(prec, ()))
1103 toprocess.update(obsstore.precursors.get(prec, ()))
1103 return divergent
1104 return divergent
1104
1105
1105
1106
1106 def createmarkers(repo, relations, flag=0, date=None, metadata=None):
1107 def createmarkers(repo, relations, flag=0, date=None, metadata=None):
1107 """Add obsolete markers between changesets in a repo
1108 """Add obsolete markers between changesets in a repo
1108
1109
1109 <relations> must be an iterable of (<old>, (<new>, ...)[,{metadata}])
1110 <relations> must be an iterable of (<old>, (<new>, ...)[,{metadata}])
1110 tuple. `old` and `news` are changectx. metadata is an optional dictionary
1111 tuple. `old` and `news` are changectx. metadata is an optional dictionary
1111 containing metadata for this marker only. It is merged with the global
1112 containing metadata for this marker only. It is merged with the global
1112 metadata specified through the `metadata` argument of this function,
1113 metadata specified through the `metadata` argument of this function,
1113
1114
1114 Trying to obsolete a public changeset will raise an exception.
1115 Trying to obsolete a public changeset will raise an exception.
1115
1116
1116 Current user and date are used except if specified otherwise in the
1117 Current user and date are used except if specified otherwise in the
1117 metadata attribute.
1118 metadata attribute.
1118
1119
1119 This function operates within a transaction of its own, but does
1120 This function operates within a transaction of its own, but does
1120 not take any lock on the repo.
1121 not take any lock on the repo.
1121 """
1122 """
1122 # prepare metadata
1123 # prepare metadata
1123 if metadata is None:
1124 if metadata is None:
1124 metadata = {}
1125 metadata = {}
1125 if 'user' not in metadata:
1126 if 'user' not in metadata:
1126 metadata['user'] = repo.ui.username()
1127 metadata['user'] = repo.ui.username()
1127 tr = repo.transaction('add-obsolescence-marker')
1128 tr = repo.transaction('add-obsolescence-marker')
1128 try:
1129 try:
1129 for rel in relations:
1130 for rel in relations:
1130 prec = rel[0]
1131 prec = rel[0]
1131 sucs = rel[1]
1132 sucs = rel[1]
1132 localmetadata = metadata.copy()
1133 localmetadata = metadata.copy()
1133 if 2 < len(rel):
1134 if 2 < len(rel):
1134 localmetadata.update(rel[2])
1135 localmetadata.update(rel[2])
1135
1136
1136 if not prec.mutable():
1137 if not prec.mutable():
1137 raise util.Abort("cannot obsolete immutable changeset: %s"
1138 raise util.Abort("cannot obsolete immutable changeset: %s"
1138 % prec)
1139 % prec)
1139 nprec = prec.node()
1140 nprec = prec.node()
1140 nsucs = tuple(s.node() for s in sucs)
1141 nsucs = tuple(s.node() for s in sucs)
1141 npare = None
1142 npare = None
1142 if not nsucs:
1143 if not nsucs:
1143 npare = tuple(p.node() for p in prec.parents())
1144 npare = tuple(p.node() for p in prec.parents())
1144 if nprec in nsucs:
1145 if nprec in nsucs:
1145 raise util.Abort("changeset %s cannot obsolete itself" % prec)
1146 raise util.Abort("changeset %s cannot obsolete itself" % prec)
1146 repo.obsstore.create(tr, nprec, nsucs, flag, parents=npare,
1147 repo.obsstore.create(tr, nprec, nsucs, flag, parents=npare,
1147 date=date, metadata=localmetadata)
1148 date=date, metadata=localmetadata)
1148 repo.filteredrevcache.clear()
1149 repo.filteredrevcache.clear()
1149 tr.close()
1150 tr.close()
1150 finally:
1151 finally:
1151 tr.release()
1152 tr.release()
1152
1153
1153 def isenabled(repo, option):
1154 def isenabled(repo, option):
1154 """Returns True if the given repository has the given obsolete option
1155 """Returns True if the given repository has the given obsolete option
1155 enabled.
1156 enabled.
1156 """
1157 """
1157 result = set(repo.ui.configlist('experimental', 'evolution'))
1158 result = set(repo.ui.configlist('experimental', 'evolution'))
1158 if 'all' in result:
1159 if 'all' in result:
1159 return True
1160 return True
1160
1161
1161 # For migration purposes, temporarily return true if the config hasn't been
1162 # For migration purposes, temporarily return true if the config hasn't been
1162 # set but _enabled is true.
1163 # set but _enabled is true.
1163 if len(result) == 0 and _enabled:
1164 if len(result) == 0 and _enabled:
1164 return True
1165 return True
1165
1166
1166 return option in result
1167 return option in result
General Comments 0
You need to be logged in to leave comments. Login now