##// END OF EJS Templates
peer: introduce peer methods to prepare for peer classes...
Sune Foldager -
r17191:58848126 default
parent child Browse files
Show More

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

@@ -1,695 +1,695 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 633536316234 and 7c2fd3b9020c
31 # Edit history between 633536316234 and 7c2fd3b9020c
32 #
32 #
33 # Commands:
33 # Commands:
34 # p, pick = use commit
34 # p, pick = use commit
35 # e, edit = use commit, but stop for amending
35 # e, edit = use commit, but stop for amending
36 # f, fold = use commit, but fold into previous commit
36 # f, fold = use commit, but fold into previous commit
37 # d, drop = remove commit from history
37 # d, drop = remove commit from history
38 # m, mess = edit message without changing commit content
38 # m, mess = edit message without changing commit content
39 #
39 #
40 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
40 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
41
41
42 In this file, lines beginning with ``#`` are ignored. You must specify a rule
42 In this file, lines beginning with ``#`` are ignored. You must specify a rule
43 for each revision in your history. For example, if you had meant to add gamma
43 for each revision in your history. For example, if you had meant to add gamma
44 before beta, and then wanted to add delta in the same revision as beta, you
44 before beta, and then wanted to add delta in the same revision as beta, you
45 would reorganize the file to look like this::
45 would reorganize the file to look like this::
46
46
47 pick 030b686bedc4 Add gamma
47 pick 030b686bedc4 Add gamma
48 pick c561b4e977df Add beta
48 pick c561b4e977df Add beta
49 fold 7c2fd3b9020c Add delta
49 fold 7c2fd3b9020c Add delta
50
50
51 # Edit history between 633536316234 and 7c2fd3b9020c
51 # Edit history between 633536316234 and 7c2fd3b9020c
52 #
52 #
53 # Commands:
53 # Commands:
54 # p, pick = use commit
54 # p, pick = use commit
55 # e, edit = use commit, but stop for amending
55 # e, edit = use commit, but stop for amending
56 # f, fold = use commit, but fold into previous commit
56 # f, fold = use commit, but fold into previous commit
57 # d, drop = remove commit from history
57 # d, drop = remove commit from history
58 # m, mess = edit message without changing commit content
58 # m, mess = edit message without changing commit content
59 #
59 #
60 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
60 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
61
61
62 At which point you close the editor and ``histedit`` starts working. When you
62 At which point you close the editor and ``histedit`` starts working. When you
63 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
63 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
64 those revisions together, offering you a chance to clean up the commit message::
64 those revisions together, offering you a chance to clean up the commit message::
65
65
66 Add beta
66 Add beta
67 ***
67 ***
68 Add delta
68 Add delta
69
69
70 Edit the commit message to your liking, then close the editor. For
70 Edit the commit message to your liking, then close the editor. For
71 this example, let's assume that the commit message was changed to
71 this example, let's assume that the commit message was changed to
72 ``Add beta and delta.`` After histedit has run and had a chance to
72 ``Add beta and delta.`` After histedit has run and had a chance to
73 remove any old or temporary revisions it needed, the history looks
73 remove any old or temporary revisions it needed, the history looks
74 like this::
74 like this::
75
75
76 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
76 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
77 | Add beta and delta.
77 | Add beta and delta.
78 |
78 |
79 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
79 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
80 | Add gamma
80 | Add gamma
81 |
81 |
82 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
82 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
83 Add alpha
83 Add alpha
84
84
85 Note that ``histedit`` does *not* remove any revisions (even its own temporary
85 Note that ``histedit`` does *not* remove any revisions (even its own temporary
86 ones) until after it has completed all the editing operations, so it will
86 ones) until after it has completed all the editing operations, so it will
87 probably perform several strip operations when it's done. For the above example,
87 probably perform several strip operations when it's done. For the above example,
88 it had to run strip twice. Strip can be slow depending on a variety of factors,
88 it had to run strip twice. Strip can be slow depending on a variety of factors,
89 so you might need to be a little patient. You can choose to keep the original
89 so you might need to be a little patient. You can choose to keep the original
90 revisions by passing the ``--keep`` flag.
90 revisions by passing the ``--keep`` flag.
91
91
92 The ``edit`` operation will drop you back to a command prompt,
92 The ``edit`` operation will drop you back to a command prompt,
93 allowing you to edit files freely, or even use ``hg record`` to commit
93 allowing you to edit files freely, or even use ``hg record`` to commit
94 some changes as a separate commit. When you're done, any remaining
94 some changes as a separate commit. When you're done, any remaining
95 uncommitted changes will be committed as well. When done, run ``hg
95 uncommitted changes will be committed as well. When done, run ``hg
96 histedit --continue`` to finish this step. You'll be prompted for a
96 histedit --continue`` to finish this step. You'll be prompted for a
97 new commit message, but the default commit message will be the
97 new commit message, but the default commit message will be the
98 original message for the ``edit`` ed revision.
98 original message for the ``edit`` ed revision.
99
99
100 The ``message`` operation will give you a chance to revise a commit
100 The ``message`` operation will give you a chance to revise a commit
101 message without changing the contents. It's a shortcut for doing
101 message without changing the contents. It's a shortcut for doing
102 ``edit`` immediately followed by `hg histedit --continue``.
102 ``edit`` immediately followed by `hg histedit --continue``.
103
103
104 If ``histedit`` encounters a conflict when moving a revision (while
104 If ``histedit`` encounters a conflict when moving a revision (while
105 handling ``pick`` or ``fold``), it'll stop in a similar manner to
105 handling ``pick`` or ``fold``), it'll stop in a similar manner to
106 ``edit`` with the difference that it won't prompt you for a commit
106 ``edit`` with the difference that it won't prompt you for a commit
107 message when done. If you decide at this point that you don't like how
107 message when done. If you decide at this point that you don't like how
108 much work it will be to rearrange history, or that you made a mistake,
108 much work it will be to rearrange history, or that you made a mistake,
109 you can use ``hg histedit --abort`` to abandon the new changes you
109 you can use ``hg histedit --abort`` to abandon the new changes you
110 have made and return to the state before you attempted to edit your
110 have made and return to the state before you attempted to edit your
111 history.
111 history.
112
112
113 If we clone the example repository above and add three more changes, such that
113 If we clone the example repository above and add three more changes, such that
114 we have the following history::
114 we have the following history::
115
115
116 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
116 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
117 | Add theta
117 | Add theta
118 |
118 |
119 o 5 140988835471 2009-04-27 18:04 -0500 stefan
119 o 5 140988835471 2009-04-27 18:04 -0500 stefan
120 | Add eta
120 | Add eta
121 |
121 |
122 o 4 122930637314 2009-04-27 18:04 -0500 stefan
122 o 4 122930637314 2009-04-27 18:04 -0500 stefan
123 | Add zeta
123 | Add zeta
124 |
124 |
125 o 3 836302820282 2009-04-27 18:04 -0500 stefan
125 o 3 836302820282 2009-04-27 18:04 -0500 stefan
126 | Add epsilon
126 | Add epsilon
127 |
127 |
128 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
128 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
129 | Add beta and delta.
129 | Add beta and delta.
130 |
130 |
131 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
131 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
132 | Add gamma
132 | Add gamma
133 |
133 |
134 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
134 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
135 Add alpha
135 Add alpha
136
136
137 If you run ``hg histedit --outgoing`` on the clone then it is the same
137 If you run ``hg histedit --outgoing`` on the clone then it is the same
138 as running ``hg histedit 836302820282``. If you need plan to push to a
138 as running ``hg histedit 836302820282``. If you need plan to push to a
139 repository that Mercurial does not detect to be related to the source
139 repository that Mercurial does not detect to be related to the source
140 repo, you can add a ``--force`` option.
140 repo, you can add a ``--force`` option.
141 """
141 """
142
142
143 try:
143 try:
144 import cPickle as pickle
144 import cPickle as pickle
145 except ImportError:
145 except ImportError:
146 import pickle
146 import pickle
147 import tempfile
147 import tempfile
148 import os
148 import os
149
149
150 from mercurial import bookmarks
150 from mercurial import bookmarks
151 from mercurial import cmdutil
151 from mercurial import cmdutil
152 from mercurial import discovery
152 from mercurial import discovery
153 from mercurial import error
153 from mercurial import error
154 from mercurial import hg
154 from mercurial import hg
155 from mercurial import node
155 from mercurial import node
156 from mercurial import patch
156 from mercurial import patch
157 from mercurial import repair
157 from mercurial import repair
158 from mercurial import scmutil
158 from mercurial import scmutil
159 from mercurial import util
159 from mercurial import util
160 from mercurial.i18n import _
160 from mercurial.i18n import _
161
161
162 cmdtable = {}
162 cmdtable = {}
163 command = cmdutil.command(cmdtable)
163 command = cmdutil.command(cmdtable)
164
164
165 testedwith = 'internal'
165 testedwith = 'internal'
166
166
167 editcomment = """
167 editcomment = """
168
168
169 # Edit history between %s and %s
169 # Edit history between %s and %s
170 #
170 #
171 # Commands:
171 # Commands:
172 # p, pick = use commit
172 # p, pick = use commit
173 # e, edit = use commit, but stop for amending
173 # e, edit = use commit, but stop for amending
174 # f, fold = use commit, but fold into previous commit (combines N and N-1)
174 # f, fold = use commit, but fold into previous commit (combines N and N-1)
175 # d, drop = remove commit from history
175 # d, drop = remove commit from history
176 # m, mess = edit message without changing commit content
176 # m, mess = edit message without changing commit content
177 #
177 #
178 """
178 """
179
179
180 def between(repo, old, new, keep):
180 def between(repo, old, new, keep):
181 revs = [old]
181 revs = [old]
182 current = old
182 current = old
183 while current != new:
183 while current != new:
184 ctx = repo[current]
184 ctx = repo[current]
185 if not keep and len(ctx.children()) > 1:
185 if not keep and len(ctx.children()) > 1:
186 raise util.Abort(_('cannot edit history that would orphan nodes'))
186 raise util.Abort(_('cannot edit history that would orphan nodes'))
187 if len(ctx.parents()) != 1 and ctx.parents()[1] != node.nullid:
187 if len(ctx.parents()) != 1 and ctx.parents()[1] != node.nullid:
188 raise util.Abort(_("can't edit history with merges"))
188 raise util.Abort(_("can't edit history with merges"))
189 if not ctx.children():
189 if not ctx.children():
190 current = new
190 current = new
191 else:
191 else:
192 current = ctx.children()[0].node()
192 current = ctx.children()[0].node()
193 revs.append(current)
193 revs.append(current)
194 if len(repo[current].children()) and not keep:
194 if len(repo[current].children()) and not keep:
195 raise util.Abort(_('cannot edit history that would orphan nodes'))
195 raise util.Abort(_('cannot edit history that would orphan nodes'))
196 return revs
196 return revs
197
197
198
198
199 def pick(ui, repo, ctx, ha, opts):
199 def pick(ui, repo, ctx, ha, opts):
200 oldctx = repo[ha]
200 oldctx = repo[ha]
201 if oldctx.parents()[0] == ctx:
201 if oldctx.parents()[0] == ctx:
202 ui.debug('node %s unchanged\n' % ha)
202 ui.debug('node %s unchanged\n' % ha)
203 return oldctx, [], [], []
203 return oldctx, [], [], []
204 hg.update(repo, ctx.node())
204 hg.update(repo, ctx.node())
205 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
205 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
206 fp = os.fdopen(fd, 'w')
206 fp = os.fdopen(fd, 'w')
207 diffopts = patch.diffopts(ui, opts)
207 diffopts = patch.diffopts(ui, opts)
208 diffopts.git = True
208 diffopts.git = True
209 diffopts.ignorews = False
209 diffopts.ignorews = False
210 diffopts.ignorewsamount = False
210 diffopts.ignorewsamount = False
211 diffopts.ignoreblanklines = False
211 diffopts.ignoreblanklines = False
212 gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
212 gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
213 for chunk in gen:
213 for chunk in gen:
214 fp.write(chunk)
214 fp.write(chunk)
215 fp.close()
215 fp.close()
216 try:
216 try:
217 files = set()
217 files = set()
218 try:
218 try:
219 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
219 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
220 if not files:
220 if not files:
221 ui.warn(_('%s: empty changeset')
221 ui.warn(_('%s: empty changeset')
222 % node.hex(ha))
222 % node.hex(ha))
223 return ctx, [], [], []
223 return ctx, [], [], []
224 finally:
224 finally:
225 os.unlink(patchfile)
225 os.unlink(patchfile)
226 except Exception:
226 except Exception:
227 raise util.Abort(_('Fix up the change and run '
227 raise util.Abort(_('Fix up the change and run '
228 'hg histedit --continue'))
228 'hg histedit --continue'))
229 n = repo.commit(text=oldctx.description(), user=oldctx.user(),
229 n = repo.commit(text=oldctx.description(), user=oldctx.user(),
230 date=oldctx.date(), extra=oldctx.extra())
230 date=oldctx.date(), extra=oldctx.extra())
231 return repo[n], [n], [oldctx.node()], []
231 return repo[n], [n], [oldctx.node()], []
232
232
233
233
234 def edit(ui, repo, ctx, ha, opts):
234 def edit(ui, repo, ctx, ha, opts):
235 oldctx = repo[ha]
235 oldctx = repo[ha]
236 hg.update(repo, ctx.node())
236 hg.update(repo, ctx.node())
237 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
237 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
238 fp = os.fdopen(fd, 'w')
238 fp = os.fdopen(fd, 'w')
239 diffopts = patch.diffopts(ui, opts)
239 diffopts = patch.diffopts(ui, opts)
240 diffopts.git = True
240 diffopts.git = True
241 diffopts.ignorews = False
241 diffopts.ignorews = False
242 diffopts.ignorewsamount = False
242 diffopts.ignorewsamount = False
243 diffopts.ignoreblanklines = False
243 diffopts.ignoreblanklines = False
244 gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
244 gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
245 for chunk in gen:
245 for chunk in gen:
246 fp.write(chunk)
246 fp.write(chunk)
247 fp.close()
247 fp.close()
248 try:
248 try:
249 files = set()
249 files = set()
250 try:
250 try:
251 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
251 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
252 finally:
252 finally:
253 os.unlink(patchfile)
253 os.unlink(patchfile)
254 except Exception:
254 except Exception:
255 pass
255 pass
256 raise util.Abort(_('Make changes as needed, you may commit or record as '
256 raise util.Abort(_('Make changes as needed, you may commit or record as '
257 'needed now.\nWhen you are finished, run hg'
257 'needed now.\nWhen you are finished, run hg'
258 ' histedit --continue to resume.'))
258 ' histedit --continue to resume.'))
259
259
260 def fold(ui, repo, ctx, ha, opts):
260 def fold(ui, repo, ctx, ha, opts):
261 oldctx = repo[ha]
261 oldctx = repo[ha]
262 hg.update(repo, ctx.node())
262 hg.update(repo, ctx.node())
263 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
263 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
264 fp = os.fdopen(fd, 'w')
264 fp = os.fdopen(fd, 'w')
265 diffopts = patch.diffopts(ui, opts)
265 diffopts = patch.diffopts(ui, opts)
266 diffopts.git = True
266 diffopts.git = True
267 diffopts.ignorews = False
267 diffopts.ignorews = False
268 diffopts.ignorewsamount = False
268 diffopts.ignorewsamount = False
269 diffopts.ignoreblanklines = False
269 diffopts.ignoreblanklines = False
270 gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
270 gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
271 for chunk in gen:
271 for chunk in gen:
272 fp.write(chunk)
272 fp.write(chunk)
273 fp.close()
273 fp.close()
274 try:
274 try:
275 files = set()
275 files = set()
276 try:
276 try:
277 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
277 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
278 if not files:
278 if not files:
279 ui.warn(_('%s: empty changeset')
279 ui.warn(_('%s: empty changeset')
280 % node.hex(ha))
280 % node.hex(ha))
281 return ctx, [], [], []
281 return ctx, [], [], []
282 finally:
282 finally:
283 os.unlink(patchfile)
283 os.unlink(patchfile)
284 except Exception:
284 except Exception:
285 raise util.Abort(_('Fix up the change and run '
285 raise util.Abort(_('Fix up the change and run '
286 'hg histedit --continue'))
286 'hg histedit --continue'))
287 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
287 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
288 date=oldctx.date(), extra=oldctx.extra())
288 date=oldctx.date(), extra=oldctx.extra())
289 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
289 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
290
290
291 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
291 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
292 parent = ctx.parents()[0].node()
292 parent = ctx.parents()[0].node()
293 hg.update(repo, parent)
293 hg.update(repo, parent)
294 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
294 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
295 fp = os.fdopen(fd, 'w')
295 fp = os.fdopen(fd, 'w')
296 diffopts = patch.diffopts(ui, opts)
296 diffopts = patch.diffopts(ui, opts)
297 diffopts.git = True
297 diffopts.git = True
298 diffopts.ignorews = False
298 diffopts.ignorews = False
299 diffopts.ignorewsamount = False
299 diffopts.ignorewsamount = False
300 diffopts.ignoreblanklines = False
300 diffopts.ignoreblanklines = False
301 gen = patch.diff(repo, parent, newnode, opts=diffopts)
301 gen = patch.diff(repo, parent, newnode, opts=diffopts)
302 for chunk in gen:
302 for chunk in gen:
303 fp.write(chunk)
303 fp.write(chunk)
304 fp.close()
304 fp.close()
305 files = set()
305 files = set()
306 try:
306 try:
307 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
307 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
308 finally:
308 finally:
309 os.unlink(patchfile)
309 os.unlink(patchfile)
310 newmessage = '\n***\n'.join(
310 newmessage = '\n***\n'.join(
311 [ctx.description()] +
311 [ctx.description()] +
312 [repo[r].description() for r in internalchanges] +
312 [repo[r].description() for r in internalchanges] +
313 [oldctx.description()])
313 [oldctx.description()])
314 # If the changesets are from the same author, keep it.
314 # If the changesets are from the same author, keep it.
315 if ctx.user() == oldctx.user():
315 if ctx.user() == oldctx.user():
316 username = ctx.user()
316 username = ctx.user()
317 else:
317 else:
318 username = ui.username()
318 username = ui.username()
319 newmessage = ui.edit(newmessage, username)
319 newmessage = ui.edit(newmessage, username)
320 n = repo.commit(text=newmessage, user=username,
320 n = repo.commit(text=newmessage, user=username,
321 date=max(ctx.date(), oldctx.date()), extra=oldctx.extra())
321 date=max(ctx.date(), oldctx.date()), extra=oldctx.extra())
322 return repo[n], [n], [oldctx.node(), ctx.node()], [newnode]
322 return repo[n], [n], [oldctx.node(), ctx.node()], [newnode]
323
323
324 def drop(ui, repo, ctx, ha, opts):
324 def drop(ui, repo, ctx, ha, opts):
325 return ctx, [], [repo[ha].node()], []
325 return ctx, [], [repo[ha].node()], []
326
326
327
327
328 def message(ui, repo, ctx, ha, opts):
328 def message(ui, repo, ctx, ha, opts):
329 oldctx = repo[ha]
329 oldctx = repo[ha]
330 hg.update(repo, ctx.node())
330 hg.update(repo, ctx.node())
331 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
331 fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
332 fp = os.fdopen(fd, 'w')
332 fp = os.fdopen(fd, 'w')
333 diffopts = patch.diffopts(ui, opts)
333 diffopts = patch.diffopts(ui, opts)
334 diffopts.git = True
334 diffopts.git = True
335 diffopts.ignorews = False
335 diffopts.ignorews = False
336 diffopts.ignorewsamount = False
336 diffopts.ignorewsamount = False
337 diffopts.ignoreblanklines = False
337 diffopts.ignoreblanklines = False
338 gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
338 gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
339 for chunk in gen:
339 for chunk in gen:
340 fp.write(chunk)
340 fp.write(chunk)
341 fp.close()
341 fp.close()
342 try:
342 try:
343 files = set()
343 files = set()
344 try:
344 try:
345 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
345 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
346 finally:
346 finally:
347 os.unlink(patchfile)
347 os.unlink(patchfile)
348 except Exception:
348 except Exception:
349 raise util.Abort(_('Fix up the change and run '
349 raise util.Abort(_('Fix up the change and run '
350 'hg histedit --continue'))
350 'hg histedit --continue'))
351 message = oldctx.description()
351 message = oldctx.description()
352 message = ui.edit(message, ui.username())
352 message = ui.edit(message, ui.username())
353 new = repo.commit(text=message, user=oldctx.user(), date=oldctx.date(),
353 new = repo.commit(text=message, user=oldctx.user(), date=oldctx.date(),
354 extra=oldctx.extra())
354 extra=oldctx.extra())
355 newctx = repo[new]
355 newctx = repo[new]
356 if oldctx.node() != newctx.node():
356 if oldctx.node() != newctx.node():
357 return newctx, [new], [oldctx.node()], []
357 return newctx, [new], [oldctx.node()], []
358 # We didn't make an edit, so just indicate no replaced nodes
358 # We didn't make an edit, so just indicate no replaced nodes
359 return newctx, [new], [], []
359 return newctx, [new], [], []
360
360
361
361
362 def makedesc(c):
362 def makedesc(c):
363 summary = ''
363 summary = ''
364 if c.description():
364 if c.description():
365 summary = c.description().splitlines()[0]
365 summary = c.description().splitlines()[0]
366 line = 'pick %s %d %s' % (c.hex()[:12], c.rev(), summary)
366 line = 'pick %s %d %s' % (c.hex()[:12], c.rev(), summary)
367 return line[:80] # trim to 80 chars so it's not stupidly wide in my editor
367 return line[:80] # trim to 80 chars so it's not stupidly wide in my editor
368
368
369 actiontable = {'p': pick,
369 actiontable = {'p': pick,
370 'pick': pick,
370 'pick': pick,
371 'e': edit,
371 'e': edit,
372 'edit': edit,
372 'edit': edit,
373 'f': fold,
373 'f': fold,
374 'fold': fold,
374 'fold': fold,
375 'd': drop,
375 'd': drop,
376 'drop': drop,
376 'drop': drop,
377 'm': message,
377 'm': message,
378 'mess': message,
378 'mess': message,
379 }
379 }
380
380
381 @command('histedit',
381 @command('histedit',
382 [('', 'commands', '',
382 [('', 'commands', '',
383 _('Read history edits from the specified file.')),
383 _('Read history edits from the specified file.')),
384 ('c', 'continue', False, _('continue an edit already in progress')),
384 ('c', 'continue', False, _('continue an edit already in progress')),
385 ('k', 'keep', False,
385 ('k', 'keep', False,
386 _("don't strip old nodes after edit is complete")),
386 _("don't strip old nodes after edit is complete")),
387 ('', 'abort', False, _('abort an edit in progress')),
387 ('', 'abort', False, _('abort an edit in progress')),
388 ('o', 'outgoing', False, _('changesets not found in destination')),
388 ('o', 'outgoing', False, _('changesets not found in destination')),
389 ('f', 'force', False,
389 ('f', 'force', False,
390 _('force outgoing even for unrelated repositories')),
390 _('force outgoing even for unrelated repositories')),
391 ('r', 'rev', [], _('first revision to be edited'))],
391 ('r', 'rev', [], _('first revision to be edited'))],
392 _("[PARENT]"))
392 _("[PARENT]"))
393 def histedit(ui, repo, *parent, **opts):
393 def histedit(ui, repo, *parent, **opts):
394 """interactively edit changeset history
394 """interactively edit changeset history
395 """
395 """
396 # TODO only abort if we try and histedit mq patches, not just
396 # TODO only abort if we try and histedit mq patches, not just
397 # blanket if mq patches are applied somewhere
397 # blanket if mq patches are applied somewhere
398 mq = getattr(repo, 'mq', None)
398 mq = getattr(repo, 'mq', None)
399 if mq and mq.applied:
399 if mq and mq.applied:
400 raise util.Abort(_('source has mq patches applied'))
400 raise util.Abort(_('source has mq patches applied'))
401
401
402 parent = list(parent) + opts.get('rev', [])
402 parent = list(parent) + opts.get('rev', [])
403 if opts.get('outgoing'):
403 if opts.get('outgoing'):
404 if len(parent) > 1:
404 if len(parent) > 1:
405 raise util.Abort(
405 raise util.Abort(
406 _('only one repo argument allowed with --outgoing'))
406 _('only one repo argument allowed with --outgoing'))
407 elif parent:
407 elif parent:
408 parent = parent[0]
408 parent = parent[0]
409
409
410 dest = ui.expandpath(parent or 'default-push', parent or 'default')
410 dest = ui.expandpath(parent or 'default-push', parent or 'default')
411 dest, revs = hg.parseurl(dest, None)[:2]
411 dest, revs = hg.parseurl(dest, None)[:2]
412 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
412 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
413
413
414 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
414 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
415 other = hg.repository(hg.remoteui(repo, opts), dest)
415 other = hg.peer(repo, opts, dest)
416
416
417 if revs:
417 if revs:
418 revs = [repo.lookup(rev) for rev in revs]
418 revs = [repo.lookup(rev) for rev in revs]
419
419
420 parent = discovery.findcommonoutgoing(
420 parent = discovery.findcommonoutgoing(
421 repo, other, [], force=opts.get('force')).missing[0:1]
421 repo, other, [], force=opts.get('force')).missing[0:1]
422 else:
422 else:
423 if opts.get('force'):
423 if opts.get('force'):
424 raise util.Abort(_('--force only allowed with --outgoing'))
424 raise util.Abort(_('--force only allowed with --outgoing'))
425
425
426 if opts.get('continue', False):
426 if opts.get('continue', False):
427 if len(parent) != 0:
427 if len(parent) != 0:
428 raise util.Abort(_('no arguments allowed with --continue'))
428 raise util.Abort(_('no arguments allowed with --continue'))
429 (parentctxnode, created, replaced,
429 (parentctxnode, created, replaced,
430 tmpnodes, existing, rules, keep, tip, replacemap) = readstate(repo)
430 tmpnodes, existing, rules, keep, tip, replacemap) = readstate(repo)
431 currentparent, wantnull = repo.dirstate.parents()
431 currentparent, wantnull = repo.dirstate.parents()
432 parentctx = repo[parentctxnode]
432 parentctx = repo[parentctxnode]
433 # discover any nodes the user has added in the interim
433 # discover any nodes the user has added in the interim
434 newchildren = [c for c in parentctx.children()
434 newchildren = [c for c in parentctx.children()
435 if c.node() not in existing]
435 if c.node() not in existing]
436 action, currentnode = rules.pop(0)
436 action, currentnode = rules.pop(0)
437 while newchildren:
437 while newchildren:
438 if action in ('f', 'fold'):
438 if action in ('f', 'fold'):
439 tmpnodes.extend([n.node() for n in newchildren])
439 tmpnodes.extend([n.node() for n in newchildren])
440 else:
440 else:
441 created.extend([n.node() for n in newchildren])
441 created.extend([n.node() for n in newchildren])
442 filtered = []
442 filtered = []
443 for r in newchildren:
443 for r in newchildren:
444 filtered += [c for c in r.children() if c.node not in existing]
444 filtered += [c for c in r.children() if c.node not in existing]
445 newchildren = filtered
445 newchildren = filtered
446 m, a, r, d = repo.status()[:4]
446 m, a, r, d = repo.status()[:4]
447 oldctx = repo[currentnode]
447 oldctx = repo[currentnode]
448 message = oldctx.description()
448 message = oldctx.description()
449 if action in ('e', 'edit', 'm', 'mess'):
449 if action in ('e', 'edit', 'm', 'mess'):
450 message = ui.edit(message, ui.username())
450 message = ui.edit(message, ui.username())
451 elif action in ('f', 'fold'):
451 elif action in ('f', 'fold'):
452 message = 'fold-temp-revision %s' % currentnode
452 message = 'fold-temp-revision %s' % currentnode
453 new = None
453 new = None
454 if m or a or r or d:
454 if m or a or r or d:
455 new = repo.commit(text=message, user=oldctx.user(),
455 new = repo.commit(text=message, user=oldctx.user(),
456 date=oldctx.date(), extra=oldctx.extra())
456 date=oldctx.date(), extra=oldctx.extra())
457
457
458 # If we're resuming a fold and we have new changes, mark the
458 # If we're resuming a fold and we have new changes, mark the
459 # replacements and finish the fold. If not, it's more like a
459 # replacements and finish the fold. If not, it's more like a
460 # drop of the changesets that disappeared, and we can skip
460 # drop of the changesets that disappeared, and we can skip
461 # this step.
461 # this step.
462 if action in ('f', 'fold') and (new or newchildren):
462 if action in ('f', 'fold') and (new or newchildren):
463 if new:
463 if new:
464 tmpnodes.append(new)
464 tmpnodes.append(new)
465 else:
465 else:
466 new = newchildren[-1]
466 new = newchildren[-1]
467 (parentctx, created_, replaced_, tmpnodes_) = finishfold(
467 (parentctx, created_, replaced_, tmpnodes_) = finishfold(
468 ui, repo, parentctx, oldctx, new, opts, newchildren)
468 ui, repo, parentctx, oldctx, new, opts, newchildren)
469 replaced.extend(replaced_)
469 replaced.extend(replaced_)
470 created.extend(created_)
470 created.extend(created_)
471 tmpnodes.extend(tmpnodes_)
471 tmpnodes.extend(tmpnodes_)
472 elif action not in ('d', 'drop'):
472 elif action not in ('d', 'drop'):
473 if new != oldctx.node():
473 if new != oldctx.node():
474 replaced.append(oldctx.node())
474 replaced.append(oldctx.node())
475 if new:
475 if new:
476 if new != oldctx.node():
476 if new != oldctx.node():
477 created.append(new)
477 created.append(new)
478 parentctx = repo[new]
478 parentctx = repo[new]
479
479
480 elif opts.get('abort', False):
480 elif opts.get('abort', False):
481 if len(parent) != 0:
481 if len(parent) != 0:
482 raise util.Abort(_('no arguments allowed with --abort'))
482 raise util.Abort(_('no arguments allowed with --abort'))
483 (parentctxnode, created, replaced, tmpnodes,
483 (parentctxnode, created, replaced, tmpnodes,
484 existing, rules, keep, tip, replacemap) = readstate(repo)
484 existing, rules, keep, tip, replacemap) = readstate(repo)
485 ui.debug('restore wc to old tip %s\n' % node.hex(tip))
485 ui.debug('restore wc to old tip %s\n' % node.hex(tip))
486 hg.clean(repo, tip)
486 hg.clean(repo, tip)
487 ui.debug('should strip created nodes %s\n' %
487 ui.debug('should strip created nodes %s\n' %
488 ', '.join([node.hex(n)[:12] for n in created]))
488 ', '.join([node.hex(n)[:12] for n in created]))
489 ui.debug('should strip temp nodes %s\n' %
489 ui.debug('should strip temp nodes %s\n' %
490 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
490 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
491 for nodes in (created, tmpnodes):
491 for nodes in (created, tmpnodes):
492 for n in reversed(nodes):
492 for n in reversed(nodes):
493 try:
493 try:
494 repair.strip(ui, repo, n)
494 repair.strip(ui, repo, n)
495 except error.LookupError:
495 except error.LookupError:
496 pass
496 pass
497 os.unlink(os.path.join(repo.path, 'histedit-state'))
497 os.unlink(os.path.join(repo.path, 'histedit-state'))
498 return
498 return
499 else:
499 else:
500 cmdutil.bailifchanged(repo)
500 cmdutil.bailifchanged(repo)
501 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
501 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
502 raise util.Abort(_('history edit already in progress, try '
502 raise util.Abort(_('history edit already in progress, try '
503 '--continue or --abort'))
503 '--continue or --abort'))
504
504
505 tip, empty = repo.dirstate.parents()
505 tip, empty = repo.dirstate.parents()
506
506
507
507
508 if len(parent) != 1:
508 if len(parent) != 1:
509 raise util.Abort(_('histedit requires exactly one parent revision'))
509 raise util.Abort(_('histedit requires exactly one parent revision'))
510 parent = scmutil.revsingle(repo, parent[0]).node()
510 parent = scmutil.revsingle(repo, parent[0]).node()
511
511
512 keep = opts.get('keep', False)
512 keep = opts.get('keep', False)
513 revs = between(repo, parent, tip, keep)
513 revs = between(repo, parent, tip, keep)
514
514
515 ctxs = [repo[r] for r in revs]
515 ctxs = [repo[r] for r in revs]
516 existing = [r.node() for r in ctxs]
516 existing = [r.node() for r in ctxs]
517 rules = opts.get('commands', '')
517 rules = opts.get('commands', '')
518 if not rules:
518 if not rules:
519 rules = '\n'.join([makedesc(c) for c in ctxs])
519 rules = '\n'.join([makedesc(c) for c in ctxs])
520 rules += editcomment % (node.hex(parent)[:12], node.hex(tip)[:12])
520 rules += editcomment % (node.hex(parent)[:12], node.hex(tip)[:12])
521 rules = ui.edit(rules, ui.username())
521 rules = ui.edit(rules, ui.username())
522 # Save edit rules in .hg/histedit-last-edit.txt in case
522 # Save edit rules in .hg/histedit-last-edit.txt in case
523 # the user needs to ask for help after something
523 # the user needs to ask for help after something
524 # surprising happens.
524 # surprising happens.
525 f = open(repo.join('histedit-last-edit.txt'), 'w')
525 f = open(repo.join('histedit-last-edit.txt'), 'w')
526 f.write(rules)
526 f.write(rules)
527 f.close()
527 f.close()
528 else:
528 else:
529 f = open(rules)
529 f = open(rules)
530 rules = f.read()
530 rules = f.read()
531 f.close()
531 f.close()
532 rules = [l for l in (r.strip() for r in rules.splitlines())
532 rules = [l for l in (r.strip() for r in rules.splitlines())
533 if l and not l[0] == '#']
533 if l and not l[0] == '#']
534 rules = verifyrules(rules, repo, ctxs)
534 rules = verifyrules(rules, repo, ctxs)
535
535
536 parentctx = repo[parent].parents()[0]
536 parentctx = repo[parent].parents()[0]
537 keep = opts.get('keep', False)
537 keep = opts.get('keep', False)
538 replaced = []
538 replaced = []
539 replacemap = {}
539 replacemap = {}
540 tmpnodes = []
540 tmpnodes = []
541 created = []
541 created = []
542
542
543
543
544 while rules:
544 while rules:
545 writestate(repo, parentctx.node(), created, replaced,
545 writestate(repo, parentctx.node(), created, replaced,
546 tmpnodes, existing, rules, keep, tip, replacemap)
546 tmpnodes, existing, rules, keep, tip, replacemap)
547 action, ha = rules.pop(0)
547 action, ha = rules.pop(0)
548 (parentctx, created_, replaced_, tmpnodes_) = actiontable[action](
548 (parentctx, created_, replaced_, tmpnodes_) = actiontable[action](
549 ui, repo, parentctx, ha, opts)
549 ui, repo, parentctx, ha, opts)
550
550
551 if replaced_:
551 if replaced_:
552 clen, rlen = len(created_), len(replaced_)
552 clen, rlen = len(created_), len(replaced_)
553 if clen == rlen == 1:
553 if clen == rlen == 1:
554 ui.debug('histedit: exact replacement of %s with %s\n' % (
554 ui.debug('histedit: exact replacement of %s with %s\n' % (
555 node.short(replaced_[0]), node.short(created_[0])))
555 node.short(replaced_[0]), node.short(created_[0])))
556
556
557 replacemap[replaced_[0]] = created_[0]
557 replacemap[replaced_[0]] = created_[0]
558 elif clen > rlen:
558 elif clen > rlen:
559 assert rlen == 1, ('unexpected replacement of '
559 assert rlen == 1, ('unexpected replacement of '
560 '%d changes with %d changes' % (rlen, clen))
560 '%d changes with %d changes' % (rlen, clen))
561 # made more changesets than we're replacing
561 # made more changesets than we're replacing
562 # TODO synthesize patch names for created patches
562 # TODO synthesize patch names for created patches
563 replacemap[replaced_[0]] = created_[-1]
563 replacemap[replaced_[0]] = created_[-1]
564 ui.debug('histedit: created many, assuming %s replaced by %s' %
564 ui.debug('histedit: created many, assuming %s replaced by %s' %
565 (node.short(replaced_[0]), node.short(created_[-1])))
565 (node.short(replaced_[0]), node.short(created_[-1])))
566 elif rlen > clen:
566 elif rlen > clen:
567 if not created_:
567 if not created_:
568 # This must be a drop. Try and put our metadata on
568 # This must be a drop. Try and put our metadata on
569 # the parent change.
569 # the parent change.
570 assert rlen == 1
570 assert rlen == 1
571 r = replaced_[0]
571 r = replaced_[0]
572 ui.debug('histedit: %s seems replaced with nothing, '
572 ui.debug('histedit: %s seems replaced with nothing, '
573 'finding a parent\n' % (node.short(r)))
573 'finding a parent\n' % (node.short(r)))
574 pctx = repo[r].parents()[0]
574 pctx = repo[r].parents()[0]
575 if pctx.node() in replacemap:
575 if pctx.node() in replacemap:
576 ui.debug('histedit: parent is already replaced\n')
576 ui.debug('histedit: parent is already replaced\n')
577 replacemap[r] = replacemap[pctx.node()]
577 replacemap[r] = replacemap[pctx.node()]
578 else:
578 else:
579 replacemap[r] = pctx.node()
579 replacemap[r] = pctx.node()
580 ui.debug('histedit: %s best replaced by %s\n' % (
580 ui.debug('histedit: %s best replaced by %s\n' % (
581 node.short(r), node.short(replacemap[r])))
581 node.short(r), node.short(replacemap[r])))
582 else:
582 else:
583 assert len(created_) == 1
583 assert len(created_) == 1
584 for r in replaced_:
584 for r in replaced_:
585 ui.debug('histedit: %s replaced by %s\n' % (
585 ui.debug('histedit: %s replaced by %s\n' % (
586 node.short(r), node.short(created_[0])))
586 node.short(r), node.short(created_[0])))
587 replacemap[r] = created_[0]
587 replacemap[r] = created_[0]
588 else:
588 else:
589 assert False, (
589 assert False, (
590 'Unhandled case in replacement mapping! '
590 'Unhandled case in replacement mapping! '
591 'replacing %d changes with %d changes' % (rlen, clen))
591 'replacing %d changes with %d changes' % (rlen, clen))
592 created.extend(created_)
592 created.extend(created_)
593 replaced.extend(replaced_)
593 replaced.extend(replaced_)
594 tmpnodes.extend(tmpnodes_)
594 tmpnodes.extend(tmpnodes_)
595
595
596 hg.update(repo, parentctx.node())
596 hg.update(repo, parentctx.node())
597
597
598 if not keep:
598 if not keep:
599 if replacemap:
599 if replacemap:
600 ui.note(_('histedit: Should update metadata for the following '
600 ui.note(_('histedit: Should update metadata for the following '
601 'changes:\n'))
601 'changes:\n'))
602
602
603 def copybms(old, new):
603 def copybms(old, new):
604 if old in tmpnodes or old in created:
604 if old in tmpnodes or old in created:
605 # can't have any metadata we'd want to update
605 # can't have any metadata we'd want to update
606 return
606 return
607 while new in replacemap:
607 while new in replacemap:
608 new = replacemap[new]
608 new = replacemap[new]
609 ui.note(_('histedit: %s to %s\n') % (node.short(old),
609 ui.note(_('histedit: %s to %s\n') % (node.short(old),
610 node.short(new)))
610 node.short(new)))
611 octx = repo[old]
611 octx = repo[old]
612 marks = octx.bookmarks()
612 marks = octx.bookmarks()
613 if marks:
613 if marks:
614 ui.note(_('histedit: moving bookmarks %s\n') %
614 ui.note(_('histedit: moving bookmarks %s\n') %
615 ', '.join(marks))
615 ', '.join(marks))
616 for mark in marks:
616 for mark in marks:
617 repo._bookmarks[mark] = new
617 repo._bookmarks[mark] = new
618 bookmarks.write(repo)
618 bookmarks.write(repo)
619
619
620 # We assume that bookmarks on the tip should remain
620 # We assume that bookmarks on the tip should remain
621 # tipmost, but bookmarks on non-tip changesets should go
621 # tipmost, but bookmarks on non-tip changesets should go
622 # to their most reasonable successor. As a result, find
622 # to their most reasonable successor. As a result, find
623 # the old tip and new tip and copy those bookmarks first,
623 # the old tip and new tip and copy those bookmarks first,
624 # then do the rest of the bookmark copies.
624 # then do the rest of the bookmark copies.
625 oldtip = sorted(replacemap.keys(), key=repo.changelog.rev)[-1]
625 oldtip = sorted(replacemap.keys(), key=repo.changelog.rev)[-1]
626 newtip = sorted(replacemap.values(), key=repo.changelog.rev)[-1]
626 newtip = sorted(replacemap.values(), key=repo.changelog.rev)[-1]
627 copybms(oldtip, newtip)
627 copybms(oldtip, newtip)
628
628
629 for old, new in sorted(replacemap.iteritems()):
629 for old, new in sorted(replacemap.iteritems()):
630 copybms(old, new)
630 copybms(old, new)
631 # TODO update mq state
631 # TODO update mq state
632
632
633 ui.debug('should strip replaced nodes %s\n' %
633 ui.debug('should strip replaced nodes %s\n' %
634 ', '.join([node.hex(n)[:12] for n in replaced]))
634 ', '.join([node.hex(n)[:12] for n in replaced]))
635 for n in sorted(replaced, key=lambda x: repo[x].rev()):
635 for n in sorted(replaced, key=lambda x: repo[x].rev()):
636 try:
636 try:
637 repair.strip(ui, repo, n)
637 repair.strip(ui, repo, n)
638 except error.LookupError:
638 except error.LookupError:
639 pass
639 pass
640
640
641 ui.debug('should strip temp nodes %s\n' %
641 ui.debug('should strip temp nodes %s\n' %
642 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
642 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
643 for n in reversed(tmpnodes):
643 for n in reversed(tmpnodes):
644 try:
644 try:
645 repair.strip(ui, repo, n)
645 repair.strip(ui, repo, n)
646 except error.LookupError:
646 except error.LookupError:
647 pass
647 pass
648 os.unlink(os.path.join(repo.path, 'histedit-state'))
648 os.unlink(os.path.join(repo.path, 'histedit-state'))
649 if os.path.exists(repo.sjoin('undo')):
649 if os.path.exists(repo.sjoin('undo')):
650 os.unlink(repo.sjoin('undo'))
650 os.unlink(repo.sjoin('undo'))
651
651
652
652
653 def writestate(repo, parentctxnode, created, replaced,
653 def writestate(repo, parentctxnode, created, replaced,
654 tmpnodes, existing, rules, keep, oldtip, replacemap):
654 tmpnodes, existing, rules, keep, oldtip, replacemap):
655 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
655 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
656 pickle.dump((parentctxnode, created, replaced,
656 pickle.dump((parentctxnode, created, replaced,
657 tmpnodes, existing, rules, keep, oldtip, replacemap),
657 tmpnodes, existing, rules, keep, oldtip, replacemap),
658 fp)
658 fp)
659 fp.close()
659 fp.close()
660
660
661 def readstate(repo):
661 def readstate(repo):
662 """Returns a tuple of (parentnode, created, replaced, tmp, existing, rules,
662 """Returns a tuple of (parentnode, created, replaced, tmp, existing, rules,
663 keep, oldtip, replacemap ).
663 keep, oldtip, replacemap ).
664 """
664 """
665 fp = open(os.path.join(repo.path, 'histedit-state'))
665 fp = open(os.path.join(repo.path, 'histedit-state'))
666 return pickle.load(fp)
666 return pickle.load(fp)
667
667
668
668
669 def verifyrules(rules, repo, ctxs):
669 def verifyrules(rules, repo, ctxs):
670 """Verify that there exists exactly one edit rule per given changeset.
670 """Verify that there exists exactly one edit rule per given changeset.
671
671
672 Will abort if there are to many or too few rules, a malformed rule,
672 Will abort if there are to many or too few rules, a malformed rule,
673 or a rule on a changeset outside of the user-given range.
673 or a rule on a changeset outside of the user-given range.
674 """
674 """
675 parsed = []
675 parsed = []
676 if len(rules) != len(ctxs):
676 if len(rules) != len(ctxs):
677 raise util.Abort(_('must specify a rule for each changeset once'))
677 raise util.Abort(_('must specify a rule for each changeset once'))
678 for r in rules:
678 for r in rules:
679 if ' ' not in r:
679 if ' ' not in r:
680 raise util.Abort(_('malformed line "%s"') % r)
680 raise util.Abort(_('malformed line "%s"') % r)
681 action, rest = r.split(' ', 1)
681 action, rest = r.split(' ', 1)
682 if ' ' in rest.strip():
682 if ' ' in rest.strip():
683 ha, rest = rest.split(' ', 1)
683 ha, rest = rest.split(' ', 1)
684 else:
684 else:
685 ha = r.strip()
685 ha = r.strip()
686 try:
686 try:
687 if repo[ha] not in ctxs:
687 if repo[ha] not in ctxs:
688 raise util.Abort(
688 raise util.Abort(
689 _('may not use changesets other than the ones listed'))
689 _('may not use changesets other than the ones listed'))
690 except error.RepoError:
690 except error.RepoError:
691 raise util.Abort(_('unknown changeset %s listed') % ha)
691 raise util.Abort(_('unknown changeset %s listed') % ha)
692 if action not in actiontable:
692 if action not in actiontable:
693 raise util.Abort(_('unknown action "%s"') % action)
693 raise util.Abort(_('unknown action "%s"') % action)
694 parsed.append([action, ha])
694 parsed.append([action, ha])
695 return parsed
695 return parsed
@@ -1,82 +1,82 b''
1 # Copyright 2009-2010 Gregory P. Ward
1 # Copyright 2009-2010 Gregory P. Ward
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 # Copyright 2010-2011 Fog Creek Software
3 # Copyright 2010-2011 Fog Creek Software
4 # Copyright 2010-2011 Unity Technologies
4 # Copyright 2010-2011 Unity Technologies
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 '''store class for local filesystem'''
9 '''store class for local filesystem'''
10
10
11 import os
11 import os
12
12
13 from mercurial import util
13 from mercurial import util
14 from mercurial.i18n import _
14 from mercurial.i18n import _
15
15
16 import lfutil
16 import lfutil
17 import basestore
17 import basestore
18
18
19 class localstore(basestore.basestore):
19 class localstore(basestore.basestore):
20 '''localstore first attempts to grab files out of the store in the remote
20 '''localstore first attempts to grab files out of the store in the remote
21 Mercurial repository. Failling that, it attempts to grab the files from
21 Mercurial repository. Failling that, it attempts to grab the files from
22 the user cache.'''
22 the user cache.'''
23
23
24 def __init__(self, ui, repo, remote):
24 def __init__(self, ui, repo, remote):
25 url = os.path.join(remote.path, '.hg', lfutil.longname)
25 url = os.path.join(remote.local().path, '.hg', lfutil.longname)
26 super(localstore, self).__init__(ui, repo, util.expandpath(url))
26 super(localstore, self).__init__(ui, repo, util.expandpath(url))
27 self.remote = remote
27 self.remote = remote.local()
28
28
29 def put(self, source, hash):
29 def put(self, source, hash):
30 util.makedirs(os.path.dirname(lfutil.storepath(self.remote, hash)))
30 util.makedirs(os.path.dirname(lfutil.storepath(self.remote, hash)))
31 if lfutil.instore(self.remote, hash):
31 if lfutil.instore(self.remote, hash):
32 return
32 return
33 lfutil.link(lfutil.storepath(self.repo, hash),
33 lfutil.link(lfutil.storepath(self.repo, hash),
34 lfutil.storepath(self.remote, hash))
34 lfutil.storepath(self.remote, hash))
35
35
36 def exists(self, hash):
36 def exists(self, hash):
37 return lfutil.instore(self.remote, hash)
37 return lfutil.instore(self.remote, hash)
38
38
39 def _getfile(self, tmpfile, filename, hash):
39 def _getfile(self, tmpfile, filename, hash):
40 if lfutil.instore(self.remote, hash):
40 if lfutil.instore(self.remote, hash):
41 path = lfutil.storepath(self.remote, hash)
41 path = lfutil.storepath(self.remote, hash)
42 elif lfutil.inusercache(self.ui, hash):
42 elif lfutil.inusercache(self.ui, hash):
43 path = lfutil.usercachepath(self.ui, hash)
43 path = lfutil.usercachepath(self.ui, hash)
44 else:
44 else:
45 raise basestore.StoreError(filename, hash, '',
45 raise basestore.StoreError(filename, hash, '',
46 _("can't get file locally"))
46 _("can't get file locally"))
47 fd = open(path, 'rb')
47 fd = open(path, 'rb')
48 try:
48 try:
49 return lfutil.copyandhash(fd, tmpfile)
49 return lfutil.copyandhash(fd, tmpfile)
50 finally:
50 finally:
51 fd.close()
51 fd.close()
52
52
53 def _verifyfile(self, cctx, cset, contents, standin, verified):
53 def _verifyfile(self, cctx, cset, contents, standin, verified):
54 filename = lfutil.splitstandin(standin)
54 filename = lfutil.splitstandin(standin)
55 if not filename:
55 if not filename:
56 return False
56 return False
57 fctx = cctx[standin]
57 fctx = cctx[standin]
58 key = (filename, fctx.filenode())
58 key = (filename, fctx.filenode())
59 if key in verified:
59 if key in verified:
60 return False
60 return False
61
61
62 expecthash = fctx.data()[0:40]
62 expecthash = fctx.data()[0:40]
63 verified.add(key)
63 verified.add(key)
64 if not lfutil.instore(self.remote, expecthash):
64 if not lfutil.instore(self.remote, expecthash):
65 self.ui.warn(
65 self.ui.warn(
66 _('changeset %s: %s missing\n'
66 _('changeset %s: %s missing\n'
67 ' (looked for hash %s)\n')
67 ' (looked for hash %s)\n')
68 % (cset, filename, expecthash))
68 % (cset, filename, expecthash))
69 return True # failed
69 return True # failed
70
70
71 if contents:
71 if contents:
72 storepath = lfutil.storepath(self.remote, expecthash)
72 storepath = lfutil.storepath(self.remote, expecthash)
73 actualhash = lfutil.hashfile(storepath)
73 actualhash = lfutil.hashfile(storepath)
74 if actualhash != expecthash:
74 if actualhash != expecthash:
75 self.ui.warn(
75 self.ui.warn(
76 _('changeset %s: %s: contents differ\n'
76 _('changeset %s: %s: contents differ\n'
77 ' (%s:\n'
77 ' (%s:\n'
78 ' expected hash %s,\n'
78 ' expected hash %s,\n'
79 ' but got %s)\n')
79 ' but got %s)\n')
80 % (cset, filename, storepath, expecthash, actualhash))
80 % (cset, filename, storepath, expecthash, actualhash))
81 return True # failed
81 return True # failed
82 return False
82 return False
@@ -1,1072 +1,1072 b''
1 # Copyright 2009-2010 Gregory P. Ward
1 # Copyright 2009-2010 Gregory P. Ward
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 # Copyright 2010-2011 Fog Creek Software
3 # Copyright 2010-2011 Fog Creek Software
4 # Copyright 2010-2011 Unity Technologies
4 # Copyright 2010-2011 Unity Technologies
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 '''Overridden Mercurial commands and functions for the largefiles extension'''
9 '''Overridden Mercurial commands and functions for the largefiles extension'''
10
10
11 import os
11 import os
12 import copy
12 import copy
13
13
14 from mercurial import hg, commands, util, cmdutil, scmutil, match as match_, \
14 from mercurial import hg, commands, util, cmdutil, scmutil, match as match_, \
15 node, archival, error, merge
15 node, archival, error, merge
16 from mercurial.i18n import _
16 from mercurial.i18n import _
17 from mercurial.node import hex
17 from mercurial.node import hex
18 from hgext import rebase
18 from hgext import rebase
19
19
20 import lfutil
20 import lfutil
21 import lfcommands
21 import lfcommands
22
22
23 # -- Utility functions: commonly/repeatedly needed functionality ---------------
23 # -- Utility functions: commonly/repeatedly needed functionality ---------------
24
24
25 def installnormalfilesmatchfn(manifest):
25 def installnormalfilesmatchfn(manifest):
26 '''overrides scmutil.match so that the matcher it returns will ignore all
26 '''overrides scmutil.match so that the matcher it returns will ignore all
27 largefiles'''
27 largefiles'''
28 oldmatch = None # for the closure
28 oldmatch = None # for the closure
29 def overridematch(ctx, pats=[], opts={}, globbed=False,
29 def overridematch(ctx, pats=[], opts={}, globbed=False,
30 default='relpath'):
30 default='relpath'):
31 match = oldmatch(ctx, pats, opts, globbed, default)
31 match = oldmatch(ctx, pats, opts, globbed, default)
32 m = copy.copy(match)
32 m = copy.copy(match)
33 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
33 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
34 manifest)
34 manifest)
35 m._files = filter(notlfile, m._files)
35 m._files = filter(notlfile, m._files)
36 m._fmap = set(m._files)
36 m._fmap = set(m._files)
37 origmatchfn = m.matchfn
37 origmatchfn = m.matchfn
38 m.matchfn = lambda f: notlfile(f) and origmatchfn(f) or None
38 m.matchfn = lambda f: notlfile(f) and origmatchfn(f) or None
39 return m
39 return m
40 oldmatch = installmatchfn(overridematch)
40 oldmatch = installmatchfn(overridematch)
41
41
42 def installmatchfn(f):
42 def installmatchfn(f):
43 oldmatch = scmutil.match
43 oldmatch = scmutil.match
44 setattr(f, 'oldmatch', oldmatch)
44 setattr(f, 'oldmatch', oldmatch)
45 scmutil.match = f
45 scmutil.match = f
46 return oldmatch
46 return oldmatch
47
47
48 def restorematchfn():
48 def restorematchfn():
49 '''restores scmutil.match to what it was before installnormalfilesmatchfn
49 '''restores scmutil.match to what it was before installnormalfilesmatchfn
50 was called. no-op if scmutil.match is its original function.
50 was called. no-op if scmutil.match is its original function.
51
51
52 Note that n calls to installnormalfilesmatchfn will require n calls to
52 Note that n calls to installnormalfilesmatchfn will require n calls to
53 restore matchfn to reverse'''
53 restore matchfn to reverse'''
54 scmutil.match = getattr(scmutil.match, 'oldmatch', scmutil.match)
54 scmutil.match = getattr(scmutil.match, 'oldmatch', scmutil.match)
55
55
56 def addlargefiles(ui, repo, *pats, **opts):
56 def addlargefiles(ui, repo, *pats, **opts):
57 large = opts.pop('large', None)
57 large = opts.pop('large', None)
58 lfsize = lfutil.getminsize(
58 lfsize = lfutil.getminsize(
59 ui, lfutil.islfilesrepo(repo), opts.pop('lfsize', None))
59 ui, lfutil.islfilesrepo(repo), opts.pop('lfsize', None))
60
60
61 lfmatcher = None
61 lfmatcher = None
62 if lfutil.islfilesrepo(repo):
62 if lfutil.islfilesrepo(repo):
63 lfpats = ui.configlist(lfutil.longname, 'patterns', default=[])
63 lfpats = ui.configlist(lfutil.longname, 'patterns', default=[])
64 if lfpats:
64 if lfpats:
65 lfmatcher = match_.match(repo.root, '', list(lfpats))
65 lfmatcher = match_.match(repo.root, '', list(lfpats))
66
66
67 lfnames = []
67 lfnames = []
68 m = scmutil.match(repo[None], pats, opts)
68 m = scmutil.match(repo[None], pats, opts)
69 m.bad = lambda x, y: None
69 m.bad = lambda x, y: None
70 wctx = repo[None]
70 wctx = repo[None]
71 for f in repo.walk(m):
71 for f in repo.walk(m):
72 exact = m.exact(f)
72 exact = m.exact(f)
73 lfile = lfutil.standin(f) in wctx
73 lfile = lfutil.standin(f) in wctx
74 nfile = f in wctx
74 nfile = f in wctx
75 exists = lfile or nfile
75 exists = lfile or nfile
76
76
77 # Don't warn the user when they attempt to add a normal tracked file.
77 # Don't warn the user when they attempt to add a normal tracked file.
78 # The normal add code will do that for us.
78 # The normal add code will do that for us.
79 if exact and exists:
79 if exact and exists:
80 if lfile:
80 if lfile:
81 ui.warn(_('%s already a largefile\n') % f)
81 ui.warn(_('%s already a largefile\n') % f)
82 continue
82 continue
83
83
84 if exact or not exists:
84 if exact or not exists:
85 abovemin = (lfsize and
85 abovemin = (lfsize and
86 os.lstat(repo.wjoin(f)).st_size >= lfsize * 1024 * 1024)
86 os.lstat(repo.wjoin(f)).st_size >= lfsize * 1024 * 1024)
87 if large or abovemin or (lfmatcher and lfmatcher(f)):
87 if large or abovemin or (lfmatcher and lfmatcher(f)):
88 lfnames.append(f)
88 lfnames.append(f)
89 if ui.verbose or not exact:
89 if ui.verbose or not exact:
90 ui.status(_('adding %s as a largefile\n') % m.rel(f))
90 ui.status(_('adding %s as a largefile\n') % m.rel(f))
91
91
92 bad = []
92 bad = []
93 standins = []
93 standins = []
94
94
95 # Need to lock, otherwise there could be a race condition between
95 # Need to lock, otherwise there could be a race condition between
96 # when standins are created and added to the repo.
96 # when standins are created and added to the repo.
97 wlock = repo.wlock()
97 wlock = repo.wlock()
98 try:
98 try:
99 if not opts.get('dry_run'):
99 if not opts.get('dry_run'):
100 lfdirstate = lfutil.openlfdirstate(ui, repo)
100 lfdirstate = lfutil.openlfdirstate(ui, repo)
101 for f in lfnames:
101 for f in lfnames:
102 standinname = lfutil.standin(f)
102 standinname = lfutil.standin(f)
103 lfutil.writestandin(repo, standinname, hash='',
103 lfutil.writestandin(repo, standinname, hash='',
104 executable=lfutil.getexecutable(repo.wjoin(f)))
104 executable=lfutil.getexecutable(repo.wjoin(f)))
105 standins.append(standinname)
105 standins.append(standinname)
106 if lfdirstate[f] == 'r':
106 if lfdirstate[f] == 'r':
107 lfdirstate.normallookup(f)
107 lfdirstate.normallookup(f)
108 else:
108 else:
109 lfdirstate.add(f)
109 lfdirstate.add(f)
110 lfdirstate.write()
110 lfdirstate.write()
111 bad += [lfutil.splitstandin(f)
111 bad += [lfutil.splitstandin(f)
112 for f in lfutil.repoadd(repo, standins)
112 for f in lfutil.repoadd(repo, standins)
113 if f in m.files()]
113 if f in m.files()]
114 finally:
114 finally:
115 wlock.release()
115 wlock.release()
116 return bad
116 return bad
117
117
118 def removelargefiles(ui, repo, *pats, **opts):
118 def removelargefiles(ui, repo, *pats, **opts):
119 after = opts.get('after')
119 after = opts.get('after')
120 if not pats and not after:
120 if not pats and not after:
121 raise util.Abort(_('no files specified'))
121 raise util.Abort(_('no files specified'))
122 m = scmutil.match(repo[None], pats, opts)
122 m = scmutil.match(repo[None], pats, opts)
123 try:
123 try:
124 repo.lfstatus = True
124 repo.lfstatus = True
125 s = repo.status(match=m, clean=True)
125 s = repo.status(match=m, clean=True)
126 finally:
126 finally:
127 repo.lfstatus = False
127 repo.lfstatus = False
128 manifest = repo[None].manifest()
128 manifest = repo[None].manifest()
129 modified, added, deleted, clean = [[f for f in list
129 modified, added, deleted, clean = [[f for f in list
130 if lfutil.standin(f) in manifest]
130 if lfutil.standin(f) in manifest]
131 for list in [s[0], s[1], s[3], s[6]]]
131 for list in [s[0], s[1], s[3], s[6]]]
132
132
133 def warn(files, reason):
133 def warn(files, reason):
134 for f in files:
134 for f in files:
135 ui.warn(_('not removing %s: %s (use forget to undo)\n')
135 ui.warn(_('not removing %s: %s (use forget to undo)\n')
136 % (m.rel(f), reason))
136 % (m.rel(f), reason))
137
137
138 if after:
138 if after:
139 remove, forget = deleted, []
139 remove, forget = deleted, []
140 warn(modified + added + clean, _('file still exists'))
140 warn(modified + added + clean, _('file still exists'))
141 else:
141 else:
142 remove, forget = deleted + clean, []
142 remove, forget = deleted + clean, []
143 warn(modified, _('file is modified'))
143 warn(modified, _('file is modified'))
144 warn(added, _('file has been marked for add'))
144 warn(added, _('file has been marked for add'))
145
145
146 for f in sorted(remove + forget):
146 for f in sorted(remove + forget):
147 if ui.verbose or not m.exact(f):
147 if ui.verbose or not m.exact(f):
148 ui.status(_('removing %s\n') % m.rel(f))
148 ui.status(_('removing %s\n') % m.rel(f))
149
149
150 # Need to lock because standin files are deleted then removed from the
150 # Need to lock because standin files are deleted then removed from the
151 # repository and we could race inbetween.
151 # repository and we could race inbetween.
152 wlock = repo.wlock()
152 wlock = repo.wlock()
153 try:
153 try:
154 lfdirstate = lfutil.openlfdirstate(ui, repo)
154 lfdirstate = lfutil.openlfdirstate(ui, repo)
155 for f in remove:
155 for f in remove:
156 if not after:
156 if not after:
157 # If this is being called by addremove, notify the user that we
157 # If this is being called by addremove, notify the user that we
158 # are removing the file.
158 # are removing the file.
159 if getattr(repo, "_isaddremove", False):
159 if getattr(repo, "_isaddremove", False):
160 ui.status(_('removing %s\n') % f)
160 ui.status(_('removing %s\n') % f)
161 if os.path.exists(repo.wjoin(f)):
161 if os.path.exists(repo.wjoin(f)):
162 util.unlinkpath(repo.wjoin(f))
162 util.unlinkpath(repo.wjoin(f))
163 lfdirstate.remove(f)
163 lfdirstate.remove(f)
164 lfdirstate.write()
164 lfdirstate.write()
165 forget = [lfutil.standin(f) for f in forget]
165 forget = [lfutil.standin(f) for f in forget]
166 remove = [lfutil.standin(f) for f in remove]
166 remove = [lfutil.standin(f) for f in remove]
167 lfutil.repoforget(repo, forget)
167 lfutil.repoforget(repo, forget)
168 # If this is being called by addremove, let the original addremove
168 # If this is being called by addremove, let the original addremove
169 # function handle this.
169 # function handle this.
170 if not getattr(repo, "_isaddremove", False):
170 if not getattr(repo, "_isaddremove", False):
171 lfutil.reporemove(repo, remove, unlink=True)
171 lfutil.reporemove(repo, remove, unlink=True)
172 else:
172 else:
173 lfutil.reporemove(repo, remove, unlink=False)
173 lfutil.reporemove(repo, remove, unlink=False)
174 finally:
174 finally:
175 wlock.release()
175 wlock.release()
176
176
177 # For overriding mercurial.hgweb.webcommands so that largefiles will
177 # For overriding mercurial.hgweb.webcommands so that largefiles will
178 # appear at their right place in the manifests.
178 # appear at their right place in the manifests.
179 def decodepath(orig, path):
179 def decodepath(orig, path):
180 return lfutil.splitstandin(path) or path
180 return lfutil.splitstandin(path) or path
181
181
182 # -- Wrappers: modify existing commands --------------------------------
182 # -- Wrappers: modify existing commands --------------------------------
183
183
184 # Add works by going through the files that the user wanted to add and
184 # Add works by going through the files that the user wanted to add and
185 # checking if they should be added as largefiles. Then it makes a new
185 # checking if they should be added as largefiles. Then it makes a new
186 # matcher which matches only the normal files and runs the original
186 # matcher which matches only the normal files and runs the original
187 # version of add.
187 # version of add.
188 def overrideadd(orig, ui, repo, *pats, **opts):
188 def overrideadd(orig, ui, repo, *pats, **opts):
189 normal = opts.pop('normal')
189 normal = opts.pop('normal')
190 if normal:
190 if normal:
191 if opts.get('large'):
191 if opts.get('large'):
192 raise util.Abort(_('--normal cannot be used with --large'))
192 raise util.Abort(_('--normal cannot be used with --large'))
193 return orig(ui, repo, *pats, **opts)
193 return orig(ui, repo, *pats, **opts)
194 bad = addlargefiles(ui, repo, *pats, **opts)
194 bad = addlargefiles(ui, repo, *pats, **opts)
195 installnormalfilesmatchfn(repo[None].manifest())
195 installnormalfilesmatchfn(repo[None].manifest())
196 result = orig(ui, repo, *pats, **opts)
196 result = orig(ui, repo, *pats, **opts)
197 restorematchfn()
197 restorematchfn()
198
198
199 return (result == 1 or bad) and 1 or 0
199 return (result == 1 or bad) and 1 or 0
200
200
201 def overrideremove(orig, ui, repo, *pats, **opts):
201 def overrideremove(orig, ui, repo, *pats, **opts):
202 installnormalfilesmatchfn(repo[None].manifest())
202 installnormalfilesmatchfn(repo[None].manifest())
203 orig(ui, repo, *pats, **opts)
203 orig(ui, repo, *pats, **opts)
204 restorematchfn()
204 restorematchfn()
205 removelargefiles(ui, repo, *pats, **opts)
205 removelargefiles(ui, repo, *pats, **opts)
206
206
207 def overridestatusfn(orig, repo, rev2, **opts):
207 def overridestatusfn(orig, repo, rev2, **opts):
208 try:
208 try:
209 repo._repo.lfstatus = True
209 repo._repo.lfstatus = True
210 return orig(repo, rev2, **opts)
210 return orig(repo, rev2, **opts)
211 finally:
211 finally:
212 repo._repo.lfstatus = False
212 repo._repo.lfstatus = False
213
213
214 def overridestatus(orig, ui, repo, *pats, **opts):
214 def overridestatus(orig, ui, repo, *pats, **opts):
215 try:
215 try:
216 repo.lfstatus = True
216 repo.lfstatus = True
217 return orig(ui, repo, *pats, **opts)
217 return orig(ui, repo, *pats, **opts)
218 finally:
218 finally:
219 repo.lfstatus = False
219 repo.lfstatus = False
220
220
221 def overridedirty(orig, repo, ignoreupdate=False):
221 def overridedirty(orig, repo, ignoreupdate=False):
222 try:
222 try:
223 repo._repo.lfstatus = True
223 repo._repo.lfstatus = True
224 return orig(repo, ignoreupdate)
224 return orig(repo, ignoreupdate)
225 finally:
225 finally:
226 repo._repo.lfstatus = False
226 repo._repo.lfstatus = False
227
227
228 def overridelog(orig, ui, repo, *pats, **opts):
228 def overridelog(orig, ui, repo, *pats, **opts):
229 try:
229 try:
230 repo.lfstatus = True
230 repo.lfstatus = True
231 orig(ui, repo, *pats, **opts)
231 orig(ui, repo, *pats, **opts)
232 finally:
232 finally:
233 repo.lfstatus = False
233 repo.lfstatus = False
234
234
235 def overrideverify(orig, ui, repo, *pats, **opts):
235 def overrideverify(orig, ui, repo, *pats, **opts):
236 large = opts.pop('large', False)
236 large = opts.pop('large', False)
237 all = opts.pop('lfa', False)
237 all = opts.pop('lfa', False)
238 contents = opts.pop('lfc', False)
238 contents = opts.pop('lfc', False)
239
239
240 result = orig(ui, repo, *pats, **opts)
240 result = orig(ui, repo, *pats, **opts)
241 if large:
241 if large:
242 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
242 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
243 return result
243 return result
244
244
245 # Override needs to refresh standins so that update's normal merge
245 # Override needs to refresh standins so that update's normal merge
246 # will go through properly. Then the other update hook (overriding repo.update)
246 # will go through properly. Then the other update hook (overriding repo.update)
247 # will get the new files. Filemerge is also overriden so that the merge
247 # will get the new files. Filemerge is also overriden so that the merge
248 # will merge standins correctly.
248 # will merge standins correctly.
249 def overrideupdate(orig, ui, repo, *pats, **opts):
249 def overrideupdate(orig, ui, repo, *pats, **opts):
250 lfdirstate = lfutil.openlfdirstate(ui, repo)
250 lfdirstate = lfutil.openlfdirstate(ui, repo)
251 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
251 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
252 False, False)
252 False, False)
253 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
253 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
254
254
255 # Need to lock between the standins getting updated and their
255 # Need to lock between the standins getting updated and their
256 # largefiles getting updated
256 # largefiles getting updated
257 wlock = repo.wlock()
257 wlock = repo.wlock()
258 try:
258 try:
259 if opts['check']:
259 if opts['check']:
260 mod = len(modified) > 0
260 mod = len(modified) > 0
261 for lfile in unsure:
261 for lfile in unsure:
262 standin = lfutil.standin(lfile)
262 standin = lfutil.standin(lfile)
263 if repo['.'][standin].data().strip() != \
263 if repo['.'][standin].data().strip() != \
264 lfutil.hashfile(repo.wjoin(lfile)):
264 lfutil.hashfile(repo.wjoin(lfile)):
265 mod = True
265 mod = True
266 else:
266 else:
267 lfdirstate.normal(lfile)
267 lfdirstate.normal(lfile)
268 lfdirstate.write()
268 lfdirstate.write()
269 if mod:
269 if mod:
270 raise util.Abort(_('uncommitted local changes'))
270 raise util.Abort(_('uncommitted local changes'))
271 # XXX handle removed differently
271 # XXX handle removed differently
272 if not opts['clean']:
272 if not opts['clean']:
273 for lfile in unsure + modified + added:
273 for lfile in unsure + modified + added:
274 lfutil.updatestandin(repo, lfutil.standin(lfile))
274 lfutil.updatestandin(repo, lfutil.standin(lfile))
275 finally:
275 finally:
276 wlock.release()
276 wlock.release()
277 return orig(ui, repo, *pats, **opts)
277 return orig(ui, repo, *pats, **opts)
278
278
279 # Before starting the manifest merge, merge.updates will call
279 # Before starting the manifest merge, merge.updates will call
280 # _checkunknown to check if there are any files in the merged-in
280 # _checkunknown to check if there are any files in the merged-in
281 # changeset that collide with unknown files in the working copy.
281 # changeset that collide with unknown files in the working copy.
282 #
282 #
283 # The largefiles are seen as unknown, so this prevents us from merging
283 # The largefiles are seen as unknown, so this prevents us from merging
284 # in a file 'foo' if we already have a largefile with the same name.
284 # in a file 'foo' if we already have a largefile with the same name.
285 #
285 #
286 # The overridden function filters the unknown files by removing any
286 # The overridden function filters the unknown files by removing any
287 # largefiles. This makes the merge proceed and we can then handle this
287 # largefiles. This makes the merge proceed and we can then handle this
288 # case further in the overridden manifestmerge function below.
288 # case further in the overridden manifestmerge function below.
289 def overridecheckunknownfile(origfn, repo, wctx, mctx, f):
289 def overridecheckunknownfile(origfn, repo, wctx, mctx, f):
290 if lfutil.standin(f) in wctx:
290 if lfutil.standin(f) in wctx:
291 return False
291 return False
292 return origfn(repo, wctx, mctx, f)
292 return origfn(repo, wctx, mctx, f)
293
293
294 # The manifest merge handles conflicts on the manifest level. We want
294 # The manifest merge handles conflicts on the manifest level. We want
295 # to handle changes in largefile-ness of files at this level too.
295 # to handle changes in largefile-ness of files at this level too.
296 #
296 #
297 # The strategy is to run the original manifestmerge and then process
297 # The strategy is to run the original manifestmerge and then process
298 # the action list it outputs. There are two cases we need to deal with:
298 # the action list it outputs. There are two cases we need to deal with:
299 #
299 #
300 # 1. Normal file in p1, largefile in p2. Here the largefile is
300 # 1. Normal file in p1, largefile in p2. Here the largefile is
301 # detected via its standin file, which will enter the working copy
301 # detected via its standin file, which will enter the working copy
302 # with a "get" action. It is not "merge" since the standin is all
302 # with a "get" action. It is not "merge" since the standin is all
303 # Mercurial is concerned with at this level -- the link to the
303 # Mercurial is concerned with at this level -- the link to the
304 # existing normal file is not relevant here.
304 # existing normal file is not relevant here.
305 #
305 #
306 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
306 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
307 # since the largefile will be present in the working copy and
307 # since the largefile will be present in the working copy and
308 # different from the normal file in p2. Mercurial therefore
308 # different from the normal file in p2. Mercurial therefore
309 # triggers a merge action.
309 # triggers a merge action.
310 #
310 #
311 # In both cases, we prompt the user and emit new actions to either
311 # In both cases, we prompt the user and emit new actions to either
312 # remove the standin (if the normal file was kept) or to remove the
312 # remove the standin (if the normal file was kept) or to remove the
313 # normal file and get the standin (if the largefile was kept). The
313 # normal file and get the standin (if the largefile was kept). The
314 # default prompt answer is to use the largefile version since it was
314 # default prompt answer is to use the largefile version since it was
315 # presumably changed on purpose.
315 # presumably changed on purpose.
316 #
316 #
317 # Finally, the merge.applyupdates function will then take care of
317 # Finally, the merge.applyupdates function will then take care of
318 # writing the files into the working copy and lfcommands.updatelfiles
318 # writing the files into the working copy and lfcommands.updatelfiles
319 # will update the largefiles.
319 # will update the largefiles.
320 def overridemanifestmerge(origfn, repo, p1, p2, pa, overwrite, partial):
320 def overridemanifestmerge(origfn, repo, p1, p2, pa, overwrite, partial):
321 actions = origfn(repo, p1, p2, pa, overwrite, partial)
321 actions = origfn(repo, p1, p2, pa, overwrite, partial)
322 processed = []
322 processed = []
323
323
324 for action in actions:
324 for action in actions:
325 if overwrite:
325 if overwrite:
326 processed.append(action)
326 processed.append(action)
327 continue
327 continue
328 f, m = action[:2]
328 f, m = action[:2]
329
329
330 choices = (_('&Largefile'), _('&Normal file'))
330 choices = (_('&Largefile'), _('&Normal file'))
331 if m == "g" and lfutil.splitstandin(f) in p1 and f in p2:
331 if m == "g" and lfutil.splitstandin(f) in p1 and f in p2:
332 # Case 1: normal file in the working copy, largefile in
332 # Case 1: normal file in the working copy, largefile in
333 # the second parent
333 # the second parent
334 lfile = lfutil.splitstandin(f)
334 lfile = lfutil.splitstandin(f)
335 standin = f
335 standin = f
336 msg = _('%s has been turned into a largefile\n'
336 msg = _('%s has been turned into a largefile\n'
337 'use (l)argefile or keep as (n)ormal file?') % lfile
337 'use (l)argefile or keep as (n)ormal file?') % lfile
338 if repo.ui.promptchoice(msg, choices, 0) == 0:
338 if repo.ui.promptchoice(msg, choices, 0) == 0:
339 processed.append((lfile, "r"))
339 processed.append((lfile, "r"))
340 processed.append((standin, "g", p2.flags(standin)))
340 processed.append((standin, "g", p2.flags(standin)))
341 else:
341 else:
342 processed.append((standin, "r"))
342 processed.append((standin, "r"))
343 elif m == "g" and lfutil.standin(f) in p1 and f in p2:
343 elif m == "g" and lfutil.standin(f) in p1 and f in p2:
344 # Case 2: largefile in the working copy, normal file in
344 # Case 2: largefile in the working copy, normal file in
345 # the second parent
345 # the second parent
346 standin = lfutil.standin(f)
346 standin = lfutil.standin(f)
347 lfile = f
347 lfile = f
348 msg = _('%s has been turned into a normal file\n'
348 msg = _('%s has been turned into a normal file\n'
349 'keep as (l)argefile or use (n)ormal file?') % lfile
349 'keep as (l)argefile or use (n)ormal file?') % lfile
350 if repo.ui.promptchoice(msg, choices, 0) == 0:
350 if repo.ui.promptchoice(msg, choices, 0) == 0:
351 processed.append((lfile, "r"))
351 processed.append((lfile, "r"))
352 else:
352 else:
353 processed.append((standin, "r"))
353 processed.append((standin, "r"))
354 processed.append((lfile, "g", p2.flags(lfile)))
354 processed.append((lfile, "g", p2.flags(lfile)))
355 else:
355 else:
356 processed.append(action)
356 processed.append(action)
357
357
358 return processed
358 return processed
359
359
360 # Override filemerge to prompt the user about how they wish to merge
360 # Override filemerge to prompt the user about how they wish to merge
361 # largefiles. This will handle identical edits, and copy/rename +
361 # largefiles. This will handle identical edits, and copy/rename +
362 # edit without prompting the user.
362 # edit without prompting the user.
363 def overridefilemerge(origfn, repo, mynode, orig, fcd, fco, fca):
363 def overridefilemerge(origfn, repo, mynode, orig, fcd, fco, fca):
364 # Use better variable names here. Because this is a wrapper we cannot
364 # Use better variable names here. Because this is a wrapper we cannot
365 # change the variable names in the function declaration.
365 # change the variable names in the function declaration.
366 fcdest, fcother, fcancestor = fcd, fco, fca
366 fcdest, fcother, fcancestor = fcd, fco, fca
367 if not lfutil.isstandin(orig):
367 if not lfutil.isstandin(orig):
368 return origfn(repo, mynode, orig, fcdest, fcother, fcancestor)
368 return origfn(repo, mynode, orig, fcdest, fcother, fcancestor)
369 else:
369 else:
370 if not fcother.cmp(fcdest): # files identical?
370 if not fcother.cmp(fcdest): # files identical?
371 return None
371 return None
372
372
373 # backwards, use working dir parent as ancestor
373 # backwards, use working dir parent as ancestor
374 if fcancestor == fcother:
374 if fcancestor == fcother:
375 fcancestor = fcdest.parents()[0]
375 fcancestor = fcdest.parents()[0]
376
376
377 if orig != fcother.path():
377 if orig != fcother.path():
378 repo.ui.status(_('merging %s and %s to %s\n')
378 repo.ui.status(_('merging %s and %s to %s\n')
379 % (lfutil.splitstandin(orig),
379 % (lfutil.splitstandin(orig),
380 lfutil.splitstandin(fcother.path()),
380 lfutil.splitstandin(fcother.path()),
381 lfutil.splitstandin(fcdest.path())))
381 lfutil.splitstandin(fcdest.path())))
382 else:
382 else:
383 repo.ui.status(_('merging %s\n')
383 repo.ui.status(_('merging %s\n')
384 % lfutil.splitstandin(fcdest.path()))
384 % lfutil.splitstandin(fcdest.path()))
385
385
386 if fcancestor.path() != fcother.path() and fcother.data() == \
386 if fcancestor.path() != fcother.path() and fcother.data() == \
387 fcancestor.data():
387 fcancestor.data():
388 return 0
388 return 0
389 if fcancestor.path() != fcdest.path() and fcdest.data() == \
389 if fcancestor.path() != fcdest.path() and fcdest.data() == \
390 fcancestor.data():
390 fcancestor.data():
391 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
391 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
392 return 0
392 return 0
393
393
394 if repo.ui.promptchoice(_('largefile %s has a merge conflict\n'
394 if repo.ui.promptchoice(_('largefile %s has a merge conflict\n'
395 'keep (l)ocal or take (o)ther?') %
395 'keep (l)ocal or take (o)ther?') %
396 lfutil.splitstandin(orig),
396 lfutil.splitstandin(orig),
397 (_('&Local'), _('&Other')), 0) == 0:
397 (_('&Local'), _('&Other')), 0) == 0:
398 return 0
398 return 0
399 else:
399 else:
400 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
400 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
401 return 0
401 return 0
402
402
403 # Copy first changes the matchers to match standins instead of
403 # Copy first changes the matchers to match standins instead of
404 # largefiles. Then it overrides util.copyfile in that function it
404 # largefiles. Then it overrides util.copyfile in that function it
405 # checks if the destination largefile already exists. It also keeps a
405 # checks if the destination largefile already exists. It also keeps a
406 # list of copied files so that the largefiles can be copied and the
406 # list of copied files so that the largefiles can be copied and the
407 # dirstate updated.
407 # dirstate updated.
408 def overridecopy(orig, ui, repo, pats, opts, rename=False):
408 def overridecopy(orig, ui, repo, pats, opts, rename=False):
409 # doesn't remove largefile on rename
409 # doesn't remove largefile on rename
410 if len(pats) < 2:
410 if len(pats) < 2:
411 # this isn't legal, let the original function deal with it
411 # this isn't legal, let the original function deal with it
412 return orig(ui, repo, pats, opts, rename)
412 return orig(ui, repo, pats, opts, rename)
413
413
414 def makestandin(relpath):
414 def makestandin(relpath):
415 path = scmutil.canonpath(repo.root, repo.getcwd(), relpath)
415 path = scmutil.canonpath(repo.root, repo.getcwd(), relpath)
416 return os.path.join(repo.wjoin(lfutil.standin(path)))
416 return os.path.join(repo.wjoin(lfutil.standin(path)))
417
417
418 fullpats = scmutil.expandpats(pats)
418 fullpats = scmutil.expandpats(pats)
419 dest = fullpats[-1]
419 dest = fullpats[-1]
420
420
421 if os.path.isdir(dest):
421 if os.path.isdir(dest):
422 if not os.path.isdir(makestandin(dest)):
422 if not os.path.isdir(makestandin(dest)):
423 os.makedirs(makestandin(dest))
423 os.makedirs(makestandin(dest))
424 # This could copy both lfiles and normal files in one command,
424 # This could copy both lfiles and normal files in one command,
425 # but we don't want to do that. First replace their matcher to
425 # but we don't want to do that. First replace their matcher to
426 # only match normal files and run it, then replace it to just
426 # only match normal files and run it, then replace it to just
427 # match largefiles and run it again.
427 # match largefiles and run it again.
428 nonormalfiles = False
428 nonormalfiles = False
429 nolfiles = False
429 nolfiles = False
430 try:
430 try:
431 try:
431 try:
432 installnormalfilesmatchfn(repo[None].manifest())
432 installnormalfilesmatchfn(repo[None].manifest())
433 result = orig(ui, repo, pats, opts, rename)
433 result = orig(ui, repo, pats, opts, rename)
434 except util.Abort, e:
434 except util.Abort, e:
435 if str(e) != 'no files to copy':
435 if str(e) != 'no files to copy':
436 raise e
436 raise e
437 else:
437 else:
438 nonormalfiles = True
438 nonormalfiles = True
439 result = 0
439 result = 0
440 finally:
440 finally:
441 restorematchfn()
441 restorematchfn()
442
442
443 # The first rename can cause our current working directory to be removed.
443 # The first rename can cause our current working directory to be removed.
444 # In that case there is nothing left to copy/rename so just quit.
444 # In that case there is nothing left to copy/rename so just quit.
445 try:
445 try:
446 repo.getcwd()
446 repo.getcwd()
447 except OSError:
447 except OSError:
448 return result
448 return result
449
449
450 try:
450 try:
451 try:
451 try:
452 # When we call orig below it creates the standins but we don't add
452 # When we call orig below it creates the standins but we don't add
453 # them to the dir state until later so lock during that time.
453 # them to the dir state until later so lock during that time.
454 wlock = repo.wlock()
454 wlock = repo.wlock()
455
455
456 manifest = repo[None].manifest()
456 manifest = repo[None].manifest()
457 oldmatch = None # for the closure
457 oldmatch = None # for the closure
458 def overridematch(ctx, pats=[], opts={}, globbed=False,
458 def overridematch(ctx, pats=[], opts={}, globbed=False,
459 default='relpath'):
459 default='relpath'):
460 newpats = []
460 newpats = []
461 # The patterns were previously mangled to add the standin
461 # The patterns were previously mangled to add the standin
462 # directory; we need to remove that now
462 # directory; we need to remove that now
463 for pat in pats:
463 for pat in pats:
464 if match_.patkind(pat) is None and lfutil.shortname in pat:
464 if match_.patkind(pat) is None and lfutil.shortname in pat:
465 newpats.append(pat.replace(lfutil.shortname, ''))
465 newpats.append(pat.replace(lfutil.shortname, ''))
466 else:
466 else:
467 newpats.append(pat)
467 newpats.append(pat)
468 match = oldmatch(ctx, newpats, opts, globbed, default)
468 match = oldmatch(ctx, newpats, opts, globbed, default)
469 m = copy.copy(match)
469 m = copy.copy(match)
470 lfile = lambda f: lfutil.standin(f) in manifest
470 lfile = lambda f: lfutil.standin(f) in manifest
471 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
471 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
472 m._fmap = set(m._files)
472 m._fmap = set(m._files)
473 origmatchfn = m.matchfn
473 origmatchfn = m.matchfn
474 m.matchfn = lambda f: (lfutil.isstandin(f) and
474 m.matchfn = lambda f: (lfutil.isstandin(f) and
475 (f in manifest) and
475 (f in manifest) and
476 origmatchfn(lfutil.splitstandin(f)) or
476 origmatchfn(lfutil.splitstandin(f)) or
477 None)
477 None)
478 return m
478 return m
479 oldmatch = installmatchfn(overridematch)
479 oldmatch = installmatchfn(overridematch)
480 listpats = []
480 listpats = []
481 for pat in pats:
481 for pat in pats:
482 if match_.patkind(pat) is not None:
482 if match_.patkind(pat) is not None:
483 listpats.append(pat)
483 listpats.append(pat)
484 else:
484 else:
485 listpats.append(makestandin(pat))
485 listpats.append(makestandin(pat))
486
486
487 try:
487 try:
488 origcopyfile = util.copyfile
488 origcopyfile = util.copyfile
489 copiedfiles = []
489 copiedfiles = []
490 def overridecopyfile(src, dest):
490 def overridecopyfile(src, dest):
491 if (lfutil.shortname in src and
491 if (lfutil.shortname in src and
492 dest.startswith(repo.wjoin(lfutil.shortname))):
492 dest.startswith(repo.wjoin(lfutil.shortname))):
493 destlfile = dest.replace(lfutil.shortname, '')
493 destlfile = dest.replace(lfutil.shortname, '')
494 if not opts['force'] and os.path.exists(destlfile):
494 if not opts['force'] and os.path.exists(destlfile):
495 raise IOError('',
495 raise IOError('',
496 _('destination largefile already exists'))
496 _('destination largefile already exists'))
497 copiedfiles.append((src, dest))
497 copiedfiles.append((src, dest))
498 origcopyfile(src, dest)
498 origcopyfile(src, dest)
499
499
500 util.copyfile = overridecopyfile
500 util.copyfile = overridecopyfile
501 result += orig(ui, repo, listpats, opts, rename)
501 result += orig(ui, repo, listpats, opts, rename)
502 finally:
502 finally:
503 util.copyfile = origcopyfile
503 util.copyfile = origcopyfile
504
504
505 lfdirstate = lfutil.openlfdirstate(ui, repo)
505 lfdirstate = lfutil.openlfdirstate(ui, repo)
506 for (src, dest) in copiedfiles:
506 for (src, dest) in copiedfiles:
507 if (lfutil.shortname in src and
507 if (lfutil.shortname in src and
508 dest.startswith(repo.wjoin(lfutil.shortname))):
508 dest.startswith(repo.wjoin(lfutil.shortname))):
509 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
509 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
510 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
510 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
511 destlfiledir = os.path.dirname(destlfile) or '.'
511 destlfiledir = os.path.dirname(destlfile) or '.'
512 if not os.path.isdir(destlfiledir):
512 if not os.path.isdir(destlfiledir):
513 os.makedirs(destlfiledir)
513 os.makedirs(destlfiledir)
514 if rename:
514 if rename:
515 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
515 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
516 lfdirstate.remove(srclfile)
516 lfdirstate.remove(srclfile)
517 else:
517 else:
518 util.copyfile(srclfile, destlfile)
518 util.copyfile(srclfile, destlfile)
519 lfdirstate.add(destlfile)
519 lfdirstate.add(destlfile)
520 lfdirstate.write()
520 lfdirstate.write()
521 except util.Abort, e:
521 except util.Abort, e:
522 if str(e) != 'no files to copy':
522 if str(e) != 'no files to copy':
523 raise e
523 raise e
524 else:
524 else:
525 nolfiles = True
525 nolfiles = True
526 finally:
526 finally:
527 restorematchfn()
527 restorematchfn()
528 wlock.release()
528 wlock.release()
529
529
530 if nolfiles and nonormalfiles:
530 if nolfiles and nonormalfiles:
531 raise util.Abort(_('no files to copy'))
531 raise util.Abort(_('no files to copy'))
532
532
533 return result
533 return result
534
534
535 # When the user calls revert, we have to be careful to not revert any
535 # When the user calls revert, we have to be careful to not revert any
536 # changes to other largefiles accidentally. This means we have to keep
536 # changes to other largefiles accidentally. This means we have to keep
537 # track of the largefiles that are being reverted so we only pull down
537 # track of the largefiles that are being reverted so we only pull down
538 # the necessary largefiles.
538 # the necessary largefiles.
539 #
539 #
540 # Standins are only updated (to match the hash of largefiles) before
540 # Standins are only updated (to match the hash of largefiles) before
541 # commits. Update the standins then run the original revert, changing
541 # commits. Update the standins then run the original revert, changing
542 # the matcher to hit standins instead of largefiles. Based on the
542 # the matcher to hit standins instead of largefiles. Based on the
543 # resulting standins update the largefiles. Then return the standins
543 # resulting standins update the largefiles. Then return the standins
544 # to their proper state
544 # to their proper state
545 def overriderevert(orig, ui, repo, *pats, **opts):
545 def overriderevert(orig, ui, repo, *pats, **opts):
546 # Because we put the standins in a bad state (by updating them)
546 # Because we put the standins in a bad state (by updating them)
547 # and then return them to a correct state we need to lock to
547 # and then return them to a correct state we need to lock to
548 # prevent others from changing them in their incorrect state.
548 # prevent others from changing them in their incorrect state.
549 wlock = repo.wlock()
549 wlock = repo.wlock()
550 try:
550 try:
551 lfdirstate = lfutil.openlfdirstate(ui, repo)
551 lfdirstate = lfutil.openlfdirstate(ui, repo)
552 (modified, added, removed, missing, unknown, ignored, clean) = \
552 (modified, added, removed, missing, unknown, ignored, clean) = \
553 lfutil.lfdirstatestatus(lfdirstate, repo, repo['.'].rev())
553 lfutil.lfdirstatestatus(lfdirstate, repo, repo['.'].rev())
554 for lfile in modified:
554 for lfile in modified:
555 lfutil.updatestandin(repo, lfutil.standin(lfile))
555 lfutil.updatestandin(repo, lfutil.standin(lfile))
556 for lfile in missing:
556 for lfile in missing:
557 if (os.path.exists(repo.wjoin(lfutil.standin(lfile)))):
557 if (os.path.exists(repo.wjoin(lfutil.standin(lfile)))):
558 os.unlink(repo.wjoin(lfutil.standin(lfile)))
558 os.unlink(repo.wjoin(lfutil.standin(lfile)))
559
559
560 try:
560 try:
561 ctx = repo[opts.get('rev')]
561 ctx = repo[opts.get('rev')]
562 oldmatch = None # for the closure
562 oldmatch = None # for the closure
563 def overridematch(ctx, pats=[], opts={}, globbed=False,
563 def overridematch(ctx, pats=[], opts={}, globbed=False,
564 default='relpath'):
564 default='relpath'):
565 match = oldmatch(ctx, pats, opts, globbed, default)
565 match = oldmatch(ctx, pats, opts, globbed, default)
566 m = copy.copy(match)
566 m = copy.copy(match)
567 def tostandin(f):
567 def tostandin(f):
568 if lfutil.standin(f) in ctx:
568 if lfutil.standin(f) in ctx:
569 return lfutil.standin(f)
569 return lfutil.standin(f)
570 elif lfutil.standin(f) in repo[None]:
570 elif lfutil.standin(f) in repo[None]:
571 return None
571 return None
572 return f
572 return f
573 m._files = [tostandin(f) for f in m._files]
573 m._files = [tostandin(f) for f in m._files]
574 m._files = [f for f in m._files if f is not None]
574 m._files = [f for f in m._files if f is not None]
575 m._fmap = set(m._files)
575 m._fmap = set(m._files)
576 origmatchfn = m.matchfn
576 origmatchfn = m.matchfn
577 def matchfn(f):
577 def matchfn(f):
578 if lfutil.isstandin(f):
578 if lfutil.isstandin(f):
579 # We need to keep track of what largefiles are being
579 # We need to keep track of what largefiles are being
580 # matched so we know which ones to update later --
580 # matched so we know which ones to update later --
581 # otherwise we accidentally revert changes to other
581 # otherwise we accidentally revert changes to other
582 # largefiles. This is repo-specific, so duckpunch the
582 # largefiles. This is repo-specific, so duckpunch the
583 # repo object to keep the list of largefiles for us
583 # repo object to keep the list of largefiles for us
584 # later.
584 # later.
585 if origmatchfn(lfutil.splitstandin(f)) and \
585 if origmatchfn(lfutil.splitstandin(f)) and \
586 (f in repo[None] or f in ctx):
586 (f in repo[None] or f in ctx):
587 lfileslist = getattr(repo, '_lfilestoupdate', [])
587 lfileslist = getattr(repo, '_lfilestoupdate', [])
588 lfileslist.append(lfutil.splitstandin(f))
588 lfileslist.append(lfutil.splitstandin(f))
589 repo._lfilestoupdate = lfileslist
589 repo._lfilestoupdate = lfileslist
590 return True
590 return True
591 else:
591 else:
592 return False
592 return False
593 return origmatchfn(f)
593 return origmatchfn(f)
594 m.matchfn = matchfn
594 m.matchfn = matchfn
595 return m
595 return m
596 oldmatch = installmatchfn(overridematch)
596 oldmatch = installmatchfn(overridematch)
597 scmutil.match
597 scmutil.match
598 matches = overridematch(repo[None], pats, opts)
598 matches = overridematch(repo[None], pats, opts)
599 orig(ui, repo, *pats, **opts)
599 orig(ui, repo, *pats, **opts)
600 finally:
600 finally:
601 restorematchfn()
601 restorematchfn()
602 lfileslist = getattr(repo, '_lfilestoupdate', [])
602 lfileslist = getattr(repo, '_lfilestoupdate', [])
603 lfcommands.updatelfiles(ui, repo, filelist=lfileslist,
603 lfcommands.updatelfiles(ui, repo, filelist=lfileslist,
604 printmessage=False)
604 printmessage=False)
605
605
606 # empty out the largefiles list so we start fresh next time
606 # empty out the largefiles list so we start fresh next time
607 repo._lfilestoupdate = []
607 repo._lfilestoupdate = []
608 for lfile in modified:
608 for lfile in modified:
609 if lfile in lfileslist:
609 if lfile in lfileslist:
610 if os.path.exists(repo.wjoin(lfutil.standin(lfile))) and lfile\
610 if os.path.exists(repo.wjoin(lfutil.standin(lfile))) and lfile\
611 in repo['.']:
611 in repo['.']:
612 lfutil.writestandin(repo, lfutil.standin(lfile),
612 lfutil.writestandin(repo, lfutil.standin(lfile),
613 repo['.'][lfile].data().strip(),
613 repo['.'][lfile].data().strip(),
614 'x' in repo['.'][lfile].flags())
614 'x' in repo['.'][lfile].flags())
615 lfdirstate = lfutil.openlfdirstate(ui, repo)
615 lfdirstate = lfutil.openlfdirstate(ui, repo)
616 for lfile in added:
616 for lfile in added:
617 standin = lfutil.standin(lfile)
617 standin = lfutil.standin(lfile)
618 if standin not in ctx and (standin in matches or opts.get('all')):
618 if standin not in ctx and (standin in matches or opts.get('all')):
619 if lfile in lfdirstate:
619 if lfile in lfdirstate:
620 lfdirstate.drop(lfile)
620 lfdirstate.drop(lfile)
621 util.unlinkpath(repo.wjoin(standin))
621 util.unlinkpath(repo.wjoin(standin))
622 lfdirstate.write()
622 lfdirstate.write()
623 finally:
623 finally:
624 wlock.release()
624 wlock.release()
625
625
626 def hgupdate(orig, repo, node):
626 def hgupdate(orig, repo, node):
627 # Only call updatelfiles the standins that have changed to save time
627 # Only call updatelfiles the standins that have changed to save time
628 oldstandins = lfutil.getstandinsstate(repo)
628 oldstandins = lfutil.getstandinsstate(repo)
629 result = orig(repo, node)
629 result = orig(repo, node)
630 newstandins = lfutil.getstandinsstate(repo)
630 newstandins = lfutil.getstandinsstate(repo)
631 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
631 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
632 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist, printmessage=True)
632 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist, printmessage=True)
633 return result
633 return result
634
634
635 def hgclean(orig, repo, node, show_stats=True):
635 def hgclean(orig, repo, node, show_stats=True):
636 result = orig(repo, node, show_stats)
636 result = orig(repo, node, show_stats)
637 lfcommands.updatelfiles(repo.ui, repo)
637 lfcommands.updatelfiles(repo.ui, repo)
638 return result
638 return result
639
639
640 def hgmerge(orig, repo, node, force=None, remind=True):
640 def hgmerge(orig, repo, node, force=None, remind=True):
641 # Mark the repo as being in the middle of a merge, so that
641 # Mark the repo as being in the middle of a merge, so that
642 # updatelfiles() will know that it needs to trust the standins in
642 # updatelfiles() will know that it needs to trust the standins in
643 # the working copy, not in the standins in the current node
643 # the working copy, not in the standins in the current node
644 repo._ismerging = True
644 repo._ismerging = True
645 try:
645 try:
646 result = orig(repo, node, force, remind)
646 result = orig(repo, node, force, remind)
647 lfcommands.updatelfiles(repo.ui, repo)
647 lfcommands.updatelfiles(repo.ui, repo)
648 finally:
648 finally:
649 repo._ismerging = False
649 repo._ismerging = False
650 return result
650 return result
651
651
652 # When we rebase a repository with remotely changed largefiles, we need to
652 # When we rebase a repository with remotely changed largefiles, we need to
653 # take some extra care so that the largefiles are correctly updated in the
653 # take some extra care so that the largefiles are correctly updated in the
654 # working copy
654 # working copy
655 def overridepull(orig, ui, repo, source=None, **opts):
655 def overridepull(orig, ui, repo, source=None, **opts):
656 revsprepull = len(repo)
656 revsprepull = len(repo)
657 if opts.get('rebase', False):
657 if opts.get('rebase', False):
658 repo._isrebasing = True
658 repo._isrebasing = True
659 try:
659 try:
660 if opts.get('update'):
660 if opts.get('update'):
661 del opts['update']
661 del opts['update']
662 ui.debug('--update and --rebase are not compatible, ignoring '
662 ui.debug('--update and --rebase are not compatible, ignoring '
663 'the update flag\n')
663 'the update flag\n')
664 del opts['rebase']
664 del opts['rebase']
665 cmdutil.bailifchanged(repo)
665 cmdutil.bailifchanged(repo)
666 origpostincoming = commands.postincoming
666 origpostincoming = commands.postincoming
667 def _dummy(*args, **kwargs):
667 def _dummy(*args, **kwargs):
668 pass
668 pass
669 commands.postincoming = _dummy
669 commands.postincoming = _dummy
670 repo.lfpullsource = source
670 repo.lfpullsource = source
671 if not source:
671 if not source:
672 source = 'default'
672 source = 'default'
673 try:
673 try:
674 result = commands.pull(ui, repo, source, **opts)
674 result = commands.pull(ui, repo, source, **opts)
675 finally:
675 finally:
676 commands.postincoming = origpostincoming
676 commands.postincoming = origpostincoming
677 revspostpull = len(repo)
677 revspostpull = len(repo)
678 if revspostpull > revsprepull:
678 if revspostpull > revsprepull:
679 result = result or rebase.rebase(ui, repo)
679 result = result or rebase.rebase(ui, repo)
680 finally:
680 finally:
681 repo._isrebasing = False
681 repo._isrebasing = False
682 else:
682 else:
683 repo.lfpullsource = source
683 repo.lfpullsource = source
684 if not source:
684 if not source:
685 source = 'default'
685 source = 'default'
686 oldheads = lfutil.getcurrentheads(repo)
686 oldheads = lfutil.getcurrentheads(repo)
687 result = orig(ui, repo, source, **opts)
687 result = orig(ui, repo, source, **opts)
688 # If we do not have the new largefiles for any new heads we pulled, we
688 # If we do not have the new largefiles for any new heads we pulled, we
689 # will run into a problem later if we try to merge or rebase with one of
689 # will run into a problem later if we try to merge or rebase with one of
690 # these heads, so cache the largefiles now direclty into the system
690 # these heads, so cache the largefiles now direclty into the system
691 # cache.
691 # cache.
692 ui.status(_("caching new largefiles\n"))
692 ui.status(_("caching new largefiles\n"))
693 numcached = 0
693 numcached = 0
694 heads = lfutil.getcurrentheads(repo)
694 heads = lfutil.getcurrentheads(repo)
695 newheads = set(heads).difference(set(oldheads))
695 newheads = set(heads).difference(set(oldheads))
696 for head in newheads:
696 for head in newheads:
697 (cached, missing) = lfcommands.cachelfiles(ui, repo, head)
697 (cached, missing) = lfcommands.cachelfiles(ui, repo, head)
698 numcached += len(cached)
698 numcached += len(cached)
699 ui.status(_("%d largefiles cached\n") % numcached)
699 ui.status(_("%d largefiles cached\n") % numcached)
700 if opts.get('all_largefiles'):
700 if opts.get('all_largefiles'):
701 revspostpull = len(repo)
701 revspostpull = len(repo)
702 revs = []
702 revs = []
703 for rev in xrange(revsprepull + 1, revspostpull):
703 for rev in xrange(revsprepull + 1, revspostpull):
704 revs.append(repo[rev].rev())
704 revs.append(repo[rev].rev())
705 lfcommands.downloadlfiles(ui, repo, revs)
705 lfcommands.downloadlfiles(ui, repo, revs)
706 return result
706 return result
707
707
708 def overrideclone(orig, ui, source, dest=None, **opts):
708 def overrideclone(orig, ui, source, dest=None, **opts):
709 if dest is None:
709 if dest is None:
710 dest = hg.defaultdest(source)
710 dest = hg.defaultdest(source)
711 if opts.get('all_largefiles') and not hg.islocal(dest):
711 if opts.get('all_largefiles') and not hg.islocal(dest):
712 raise util.Abort(_(
712 raise util.Abort(_(
713 '--all-largefiles is incompatible with non-local destination %s' %
713 '--all-largefiles is incompatible with non-local destination %s' %
714 dest))
714 dest))
715 result = hg.clone(ui, opts, source, dest,
715 result = hg.clone(ui, opts, source, dest,
716 pull=opts.get('pull'),
716 pull=opts.get('pull'),
717 stream=opts.get('uncompressed'),
717 stream=opts.get('uncompressed'),
718 rev=opts.get('rev'),
718 rev=opts.get('rev'),
719 update=True, # required for successful walkchangerevs
719 update=True, # required for successful walkchangerevs
720 branch=opts.get('branch'))
720 branch=opts.get('branch'))
721 if result is None:
721 if result is None:
722 return True
722 return True
723 if opts.get('all_largefiles'):
723 if opts.get('all_largefiles'):
724 sourcerepo, destrepo = result
724 sourcerepo, destrepo = result
725 success, missing = lfcommands.downloadlfiles(ui, destrepo, None)
725 success, missing = lfcommands.downloadlfiles(ui, destrepo.local(), None)
726 return missing != 0
726 return missing != 0
727 return result is None
727 return result is None
728
728
729 def overriderebase(orig, ui, repo, **opts):
729 def overriderebase(orig, ui, repo, **opts):
730 repo._isrebasing = True
730 repo._isrebasing = True
731 try:
731 try:
732 orig(ui, repo, **opts)
732 orig(ui, repo, **opts)
733 finally:
733 finally:
734 repo._isrebasing = False
734 repo._isrebasing = False
735
735
736 def overridearchive(orig, repo, dest, node, kind, decode=True, matchfn=None,
736 def overridearchive(orig, repo, dest, node, kind, decode=True, matchfn=None,
737 prefix=None, mtime=None, subrepos=None):
737 prefix=None, mtime=None, subrepos=None):
738 # No need to lock because we are only reading history and
738 # No need to lock because we are only reading history and
739 # largefile caches, neither of which are modified.
739 # largefile caches, neither of which are modified.
740 lfcommands.cachelfiles(repo.ui, repo, node)
740 lfcommands.cachelfiles(repo.ui, repo, node)
741
741
742 if kind not in archival.archivers:
742 if kind not in archival.archivers:
743 raise util.Abort(_("unknown archive type '%s'") % kind)
743 raise util.Abort(_("unknown archive type '%s'") % kind)
744
744
745 ctx = repo[node]
745 ctx = repo[node]
746
746
747 if kind == 'files':
747 if kind == 'files':
748 if prefix:
748 if prefix:
749 raise util.Abort(
749 raise util.Abort(
750 _('cannot give prefix when archiving to files'))
750 _('cannot give prefix when archiving to files'))
751 else:
751 else:
752 prefix = archival.tidyprefix(dest, kind, prefix)
752 prefix = archival.tidyprefix(dest, kind, prefix)
753
753
754 def write(name, mode, islink, getdata):
754 def write(name, mode, islink, getdata):
755 if matchfn and not matchfn(name):
755 if matchfn and not matchfn(name):
756 return
756 return
757 data = getdata()
757 data = getdata()
758 if decode:
758 if decode:
759 data = repo.wwritedata(name, data)
759 data = repo.wwritedata(name, data)
760 archiver.addfile(prefix + name, mode, islink, data)
760 archiver.addfile(prefix + name, mode, islink, data)
761
761
762 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
762 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
763
763
764 if repo.ui.configbool("ui", "archivemeta", True):
764 if repo.ui.configbool("ui", "archivemeta", True):
765 def metadata():
765 def metadata():
766 base = 'repo: %s\nnode: %s\nbranch: %s\n' % (
766 base = 'repo: %s\nnode: %s\nbranch: %s\n' % (
767 hex(repo.changelog.node(0)), hex(node), ctx.branch())
767 hex(repo.changelog.node(0)), hex(node), ctx.branch())
768
768
769 tags = ''.join('tag: %s\n' % t for t in ctx.tags()
769 tags = ''.join('tag: %s\n' % t for t in ctx.tags()
770 if repo.tagtype(t) == 'global')
770 if repo.tagtype(t) == 'global')
771 if not tags:
771 if not tags:
772 repo.ui.pushbuffer()
772 repo.ui.pushbuffer()
773 opts = {'template': '{latesttag}\n{latesttagdistance}',
773 opts = {'template': '{latesttag}\n{latesttagdistance}',
774 'style': '', 'patch': None, 'git': None}
774 'style': '', 'patch': None, 'git': None}
775 cmdutil.show_changeset(repo.ui, repo, opts).show(ctx)
775 cmdutil.show_changeset(repo.ui, repo, opts).show(ctx)
776 ltags, dist = repo.ui.popbuffer().split('\n')
776 ltags, dist = repo.ui.popbuffer().split('\n')
777 tags = ''.join('latesttag: %s\n' % t for t in ltags.split(':'))
777 tags = ''.join('latesttag: %s\n' % t for t in ltags.split(':'))
778 tags += 'latesttagdistance: %s\n' % dist
778 tags += 'latesttagdistance: %s\n' % dist
779
779
780 return base + tags
780 return base + tags
781
781
782 write('.hg_archival.txt', 0644, False, metadata)
782 write('.hg_archival.txt', 0644, False, metadata)
783
783
784 for f in ctx:
784 for f in ctx:
785 ff = ctx.flags(f)
785 ff = ctx.flags(f)
786 getdata = ctx[f].data
786 getdata = ctx[f].data
787 if lfutil.isstandin(f):
787 if lfutil.isstandin(f):
788 path = lfutil.findfile(repo, getdata().strip())
788 path = lfutil.findfile(repo, getdata().strip())
789 if path is None:
789 if path is None:
790 raise util.Abort(
790 raise util.Abort(
791 _('largefile %s not found in repo store or system cache')
791 _('largefile %s not found in repo store or system cache')
792 % lfutil.splitstandin(f))
792 % lfutil.splitstandin(f))
793 f = lfutil.splitstandin(f)
793 f = lfutil.splitstandin(f)
794
794
795 def getdatafn():
795 def getdatafn():
796 fd = None
796 fd = None
797 try:
797 try:
798 fd = open(path, 'rb')
798 fd = open(path, 'rb')
799 return fd.read()
799 return fd.read()
800 finally:
800 finally:
801 if fd:
801 if fd:
802 fd.close()
802 fd.close()
803
803
804 getdata = getdatafn
804 getdata = getdatafn
805 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
805 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
806
806
807 if subrepos:
807 if subrepos:
808 for subpath in ctx.substate:
808 for subpath in ctx.substate:
809 sub = ctx.sub(subpath)
809 sub = ctx.sub(subpath)
810 submatch = match_.narrowmatcher(subpath, matchfn)
810 submatch = match_.narrowmatcher(subpath, matchfn)
811 sub.archive(repo.ui, archiver, prefix, submatch)
811 sub.archive(repo.ui, archiver, prefix, submatch)
812
812
813 archiver.done()
813 archiver.done()
814
814
815 def hgsubrepoarchive(orig, repo, ui, archiver, prefix, match=None):
815 def hgsubrepoarchive(orig, repo, ui, archiver, prefix, match=None):
816 rev = repo._state[1]
816 rev = repo._state[1]
817 ctx = repo._repo[rev]
817 ctx = repo._repo[rev]
818
818
819 lfcommands.cachelfiles(ui, repo._repo, ctx.node())
819 lfcommands.cachelfiles(ui, repo._repo, ctx.node())
820
820
821 def write(name, mode, islink, getdata):
821 def write(name, mode, islink, getdata):
822 # At this point, the standin has been replaced with the largefile name,
822 # At this point, the standin has been replaced with the largefile name,
823 # so the normal matcher works here without the lfutil variants.
823 # so the normal matcher works here without the lfutil variants.
824 if match and not match(f):
824 if match and not match(f):
825 return
825 return
826 data = getdata()
826 data = getdata()
827
827
828 archiver.addfile(prefix + repo._path + '/' + name, mode, islink, data)
828 archiver.addfile(prefix + repo._path + '/' + name, mode, islink, data)
829
829
830 for f in ctx:
830 for f in ctx:
831 ff = ctx.flags(f)
831 ff = ctx.flags(f)
832 getdata = ctx[f].data
832 getdata = ctx[f].data
833 if lfutil.isstandin(f):
833 if lfutil.isstandin(f):
834 path = lfutil.findfile(repo._repo, getdata().strip())
834 path = lfutil.findfile(repo._repo, getdata().strip())
835 if path is None:
835 if path is None:
836 raise util.Abort(
836 raise util.Abort(
837 _('largefile %s not found in repo store or system cache')
837 _('largefile %s not found in repo store or system cache')
838 % lfutil.splitstandin(f))
838 % lfutil.splitstandin(f))
839 f = lfutil.splitstandin(f)
839 f = lfutil.splitstandin(f)
840
840
841 def getdatafn():
841 def getdatafn():
842 fd = None
842 fd = None
843 try:
843 try:
844 fd = open(os.path.join(prefix, path), 'rb')
844 fd = open(os.path.join(prefix, path), 'rb')
845 return fd.read()
845 return fd.read()
846 finally:
846 finally:
847 if fd:
847 if fd:
848 fd.close()
848 fd.close()
849
849
850 getdata = getdatafn
850 getdata = getdatafn
851
851
852 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
852 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
853
853
854 for subpath in ctx.substate:
854 for subpath in ctx.substate:
855 sub = ctx.sub(subpath)
855 sub = ctx.sub(subpath)
856 submatch = match_.narrowmatcher(subpath, match)
856 submatch = match_.narrowmatcher(subpath, match)
857 sub.archive(ui, archiver, os.path.join(prefix, repo._path) + '/',
857 sub.archive(ui, archiver, os.path.join(prefix, repo._path) + '/',
858 submatch)
858 submatch)
859
859
860 # If a largefile is modified, the change is not reflected in its
860 # If a largefile is modified, the change is not reflected in its
861 # standin until a commit. cmdutil.bailifchanged() raises an exception
861 # standin until a commit. cmdutil.bailifchanged() raises an exception
862 # if the repo has uncommitted changes. Wrap it to also check if
862 # if the repo has uncommitted changes. Wrap it to also check if
863 # largefiles were changed. This is used by bisect and backout.
863 # largefiles were changed. This is used by bisect and backout.
864 def overridebailifchanged(orig, repo):
864 def overridebailifchanged(orig, repo):
865 orig(repo)
865 orig(repo)
866 repo.lfstatus = True
866 repo.lfstatus = True
867 modified, added, removed, deleted = repo.status()[:4]
867 modified, added, removed, deleted = repo.status()[:4]
868 repo.lfstatus = False
868 repo.lfstatus = False
869 if modified or added or removed or deleted:
869 if modified or added or removed or deleted:
870 raise util.Abort(_('outstanding uncommitted changes'))
870 raise util.Abort(_('outstanding uncommitted changes'))
871
871
872 # Fetch doesn't use cmdutil.bailifchanged so override it to add the check
872 # Fetch doesn't use cmdutil.bailifchanged so override it to add the check
873 def overridefetch(orig, ui, repo, *pats, **opts):
873 def overridefetch(orig, ui, repo, *pats, **opts):
874 repo.lfstatus = True
874 repo.lfstatus = True
875 modified, added, removed, deleted = repo.status()[:4]
875 modified, added, removed, deleted = repo.status()[:4]
876 repo.lfstatus = False
876 repo.lfstatus = False
877 if modified or added or removed or deleted:
877 if modified or added or removed or deleted:
878 raise util.Abort(_('outstanding uncommitted changes'))
878 raise util.Abort(_('outstanding uncommitted changes'))
879 return orig(ui, repo, *pats, **opts)
879 return orig(ui, repo, *pats, **opts)
880
880
881 def overrideforget(orig, ui, repo, *pats, **opts):
881 def overrideforget(orig, ui, repo, *pats, **opts):
882 installnormalfilesmatchfn(repo[None].manifest())
882 installnormalfilesmatchfn(repo[None].manifest())
883 orig(ui, repo, *pats, **opts)
883 orig(ui, repo, *pats, **opts)
884 restorematchfn()
884 restorematchfn()
885 m = scmutil.match(repo[None], pats, opts)
885 m = scmutil.match(repo[None], pats, opts)
886
886
887 try:
887 try:
888 repo.lfstatus = True
888 repo.lfstatus = True
889 s = repo.status(match=m, clean=True)
889 s = repo.status(match=m, clean=True)
890 finally:
890 finally:
891 repo.lfstatus = False
891 repo.lfstatus = False
892 forget = sorted(s[0] + s[1] + s[3] + s[6])
892 forget = sorted(s[0] + s[1] + s[3] + s[6])
893 forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()]
893 forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()]
894
894
895 for f in forget:
895 for f in forget:
896 if lfutil.standin(f) not in repo.dirstate and not \
896 if lfutil.standin(f) not in repo.dirstate and not \
897 os.path.isdir(m.rel(lfutil.standin(f))):
897 os.path.isdir(m.rel(lfutil.standin(f))):
898 ui.warn(_('not removing %s: file is already untracked\n')
898 ui.warn(_('not removing %s: file is already untracked\n')
899 % m.rel(f))
899 % m.rel(f))
900
900
901 for f in forget:
901 for f in forget:
902 if ui.verbose or not m.exact(f):
902 if ui.verbose or not m.exact(f):
903 ui.status(_('removing %s\n') % m.rel(f))
903 ui.status(_('removing %s\n') % m.rel(f))
904
904
905 # Need to lock because standin files are deleted then removed from the
905 # Need to lock because standin files are deleted then removed from the
906 # repository and we could race inbetween.
906 # repository and we could race inbetween.
907 wlock = repo.wlock()
907 wlock = repo.wlock()
908 try:
908 try:
909 lfdirstate = lfutil.openlfdirstate(ui, repo)
909 lfdirstate = lfutil.openlfdirstate(ui, repo)
910 for f in forget:
910 for f in forget:
911 if lfdirstate[f] == 'a':
911 if lfdirstate[f] == 'a':
912 lfdirstate.drop(f)
912 lfdirstate.drop(f)
913 else:
913 else:
914 lfdirstate.remove(f)
914 lfdirstate.remove(f)
915 lfdirstate.write()
915 lfdirstate.write()
916 lfutil.reporemove(repo, [lfutil.standin(f) for f in forget],
916 lfutil.reporemove(repo, [lfutil.standin(f) for f in forget],
917 unlink=True)
917 unlink=True)
918 finally:
918 finally:
919 wlock.release()
919 wlock.release()
920
920
921 def getoutgoinglfiles(ui, repo, dest=None, **opts):
921 def getoutgoinglfiles(ui, repo, dest=None, **opts):
922 dest = ui.expandpath(dest or 'default-push', dest or 'default')
922 dest = ui.expandpath(dest or 'default-push', dest or 'default')
923 dest, branches = hg.parseurl(dest, opts.get('branch'))
923 dest, branches = hg.parseurl(dest, opts.get('branch'))
924 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
924 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
925 if revs:
925 if revs:
926 revs = [repo.lookup(rev) for rev in revs]
926 revs = [repo.lookup(rev) for rev in revs]
927
927
928 remoteui = hg.remoteui
928 remoteui = hg.remoteui
929
929
930 try:
930 try:
931 remote = hg.repository(remoteui(repo, opts), dest)
931 remote = hg.repository(remoteui(repo, opts), dest)
932 except error.RepoError:
932 except error.RepoError:
933 return None
933 return None
934 o = lfutil.findoutgoing(repo, remote, False)
934 o = lfutil.findoutgoing(repo, remote, False)
935 if not o:
935 if not o:
936 return None
936 return None
937 o = repo.changelog.nodesbetween(o, revs)[0]
937 o = repo.changelog.nodesbetween(o, revs)[0]
938 if opts.get('newest_first'):
938 if opts.get('newest_first'):
939 o.reverse()
939 o.reverse()
940
940
941 toupload = set()
941 toupload = set()
942 for n in o:
942 for n in o:
943 parents = [p for p in repo.changelog.parents(n) if p != node.nullid]
943 parents = [p for p in repo.changelog.parents(n) if p != node.nullid]
944 ctx = repo[n]
944 ctx = repo[n]
945 files = set(ctx.files())
945 files = set(ctx.files())
946 if len(parents) == 2:
946 if len(parents) == 2:
947 mc = ctx.manifest()
947 mc = ctx.manifest()
948 mp1 = ctx.parents()[0].manifest()
948 mp1 = ctx.parents()[0].manifest()
949 mp2 = ctx.parents()[1].manifest()
949 mp2 = ctx.parents()[1].manifest()
950 for f in mp1:
950 for f in mp1:
951 if f not in mc:
951 if f not in mc:
952 files.add(f)
952 files.add(f)
953 for f in mp2:
953 for f in mp2:
954 if f not in mc:
954 if f not in mc:
955 files.add(f)
955 files.add(f)
956 for f in mc:
956 for f in mc:
957 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
957 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
958 files.add(f)
958 files.add(f)
959 toupload = toupload.union(
959 toupload = toupload.union(
960 set([f for f in files if lfutil.isstandin(f) and f in ctx]))
960 set([f for f in files if lfutil.isstandin(f) and f in ctx]))
961 return toupload
961 return toupload
962
962
963 def overrideoutgoing(orig, ui, repo, dest=None, **opts):
963 def overrideoutgoing(orig, ui, repo, dest=None, **opts):
964 orig(ui, repo, dest, **opts)
964 orig(ui, repo, dest, **opts)
965
965
966 if opts.pop('large', None):
966 if opts.pop('large', None):
967 toupload = getoutgoinglfiles(ui, repo, dest, **opts)
967 toupload = getoutgoinglfiles(ui, repo, dest, **opts)
968 if toupload is None:
968 if toupload is None:
969 ui.status(_('largefiles: No remote repo\n'))
969 ui.status(_('largefiles: No remote repo\n'))
970 else:
970 else:
971 ui.status(_('largefiles to upload:\n'))
971 ui.status(_('largefiles to upload:\n'))
972 for file in toupload:
972 for file in toupload:
973 ui.status(lfutil.splitstandin(file) + '\n')
973 ui.status(lfutil.splitstandin(file) + '\n')
974 ui.status('\n')
974 ui.status('\n')
975
975
976 def overridesummary(orig, ui, repo, *pats, **opts):
976 def overridesummary(orig, ui, repo, *pats, **opts):
977 try:
977 try:
978 repo.lfstatus = True
978 repo.lfstatus = True
979 orig(ui, repo, *pats, **opts)
979 orig(ui, repo, *pats, **opts)
980 finally:
980 finally:
981 repo.lfstatus = False
981 repo.lfstatus = False
982
982
983 if opts.pop('large', None):
983 if opts.pop('large', None):
984 toupload = getoutgoinglfiles(ui, repo, None, **opts)
984 toupload = getoutgoinglfiles(ui, repo, None, **opts)
985 if toupload is None:
985 if toupload is None:
986 ui.status(_('largefiles: No remote repo\n'))
986 ui.status(_('largefiles: No remote repo\n'))
987 else:
987 else:
988 ui.status(_('largefiles: %d to upload\n') % len(toupload))
988 ui.status(_('largefiles: %d to upload\n') % len(toupload))
989
989
990 def overrideaddremove(orig, ui, repo, *pats, **opts):
990 def overrideaddremove(orig, ui, repo, *pats, **opts):
991 if not lfutil.islfilesrepo(repo):
991 if not lfutil.islfilesrepo(repo):
992 return orig(ui, repo, *pats, **opts)
992 return orig(ui, repo, *pats, **opts)
993 # Get the list of missing largefiles so we can remove them
993 # Get the list of missing largefiles so we can remove them
994 lfdirstate = lfutil.openlfdirstate(ui, repo)
994 lfdirstate = lfutil.openlfdirstate(ui, repo)
995 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
995 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
996 False, False)
996 False, False)
997 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
997 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
998
998
999 # Call into the normal remove code, but the removing of the standin, we want
999 # Call into the normal remove code, but the removing of the standin, we want
1000 # to have handled by original addremove. Monkey patching here makes sure
1000 # to have handled by original addremove. Monkey patching here makes sure
1001 # we don't remove the standin in the largefiles code, preventing a very
1001 # we don't remove the standin in the largefiles code, preventing a very
1002 # confused state later.
1002 # confused state later.
1003 if missing:
1003 if missing:
1004 repo._isaddremove = True
1004 repo._isaddremove = True
1005 removelargefiles(ui, repo, *missing, **opts)
1005 removelargefiles(ui, repo, *missing, **opts)
1006 repo._isaddremove = False
1006 repo._isaddremove = False
1007 # Call into the normal add code, and any files that *should* be added as
1007 # Call into the normal add code, and any files that *should* be added as
1008 # largefiles will be
1008 # largefiles will be
1009 addlargefiles(ui, repo, *pats, **opts)
1009 addlargefiles(ui, repo, *pats, **opts)
1010 # Now that we've handled largefiles, hand off to the original addremove
1010 # Now that we've handled largefiles, hand off to the original addremove
1011 # function to take care of the rest. Make sure it doesn't do anything with
1011 # function to take care of the rest. Make sure it doesn't do anything with
1012 # largefiles by installing a matcher that will ignore them.
1012 # largefiles by installing a matcher that will ignore them.
1013 installnormalfilesmatchfn(repo[None].manifest())
1013 installnormalfilesmatchfn(repo[None].manifest())
1014 result = orig(ui, repo, *pats, **opts)
1014 result = orig(ui, repo, *pats, **opts)
1015 restorematchfn()
1015 restorematchfn()
1016 return result
1016 return result
1017
1017
1018 # Calling purge with --all will cause the largefiles to be deleted.
1018 # Calling purge with --all will cause the largefiles to be deleted.
1019 # Override repo.status to prevent this from happening.
1019 # Override repo.status to prevent this from happening.
1020 def overridepurge(orig, ui, repo, *dirs, **opts):
1020 def overridepurge(orig, ui, repo, *dirs, **opts):
1021 oldstatus = repo.status
1021 oldstatus = repo.status
1022 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1022 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1023 clean=False, unknown=False, listsubrepos=False):
1023 clean=False, unknown=False, listsubrepos=False):
1024 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1024 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1025 listsubrepos)
1025 listsubrepos)
1026 lfdirstate = lfutil.openlfdirstate(ui, repo)
1026 lfdirstate = lfutil.openlfdirstate(ui, repo)
1027 modified, added, removed, deleted, unknown, ignored, clean = r
1027 modified, added, removed, deleted, unknown, ignored, clean = r
1028 unknown = [f for f in unknown if lfdirstate[f] == '?']
1028 unknown = [f for f in unknown if lfdirstate[f] == '?']
1029 ignored = [f for f in ignored if lfdirstate[f] == '?']
1029 ignored = [f for f in ignored if lfdirstate[f] == '?']
1030 return modified, added, removed, deleted, unknown, ignored, clean
1030 return modified, added, removed, deleted, unknown, ignored, clean
1031 repo.status = overridestatus
1031 repo.status = overridestatus
1032 orig(ui, repo, *dirs, **opts)
1032 orig(ui, repo, *dirs, **opts)
1033 repo.status = oldstatus
1033 repo.status = oldstatus
1034
1034
1035 def overriderollback(orig, ui, repo, **opts):
1035 def overriderollback(orig, ui, repo, **opts):
1036 result = orig(ui, repo, **opts)
1036 result = orig(ui, repo, **opts)
1037 merge.update(repo, node=None, branchmerge=False, force=True,
1037 merge.update(repo, node=None, branchmerge=False, force=True,
1038 partial=lfutil.isstandin)
1038 partial=lfutil.isstandin)
1039 wlock = repo.wlock()
1039 wlock = repo.wlock()
1040 try:
1040 try:
1041 lfdirstate = lfutil.openlfdirstate(ui, repo)
1041 lfdirstate = lfutil.openlfdirstate(ui, repo)
1042 lfiles = lfutil.listlfiles(repo)
1042 lfiles = lfutil.listlfiles(repo)
1043 oldlfiles = lfutil.listlfiles(repo, repo[None].parents()[0].rev())
1043 oldlfiles = lfutil.listlfiles(repo, repo[None].parents()[0].rev())
1044 for file in lfiles:
1044 for file in lfiles:
1045 if file in oldlfiles:
1045 if file in oldlfiles:
1046 lfdirstate.normallookup(file)
1046 lfdirstate.normallookup(file)
1047 else:
1047 else:
1048 lfdirstate.add(file)
1048 lfdirstate.add(file)
1049 lfdirstate.write()
1049 lfdirstate.write()
1050 finally:
1050 finally:
1051 wlock.release()
1051 wlock.release()
1052 return result
1052 return result
1053
1053
1054 def overridetransplant(orig, ui, repo, *revs, **opts):
1054 def overridetransplant(orig, ui, repo, *revs, **opts):
1055 try:
1055 try:
1056 oldstandins = lfutil.getstandinsstate(repo)
1056 oldstandins = lfutil.getstandinsstate(repo)
1057 repo._istransplanting = True
1057 repo._istransplanting = True
1058 result = orig(ui, repo, *revs, **opts)
1058 result = orig(ui, repo, *revs, **opts)
1059 newstandins = lfutil.getstandinsstate(repo)
1059 newstandins = lfutil.getstandinsstate(repo)
1060 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1060 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1061 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1061 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1062 printmessage=True)
1062 printmessage=True)
1063 finally:
1063 finally:
1064 repo._istransplanting = False
1064 repo._istransplanting = False
1065 return result
1065 return result
1066
1066
1067 def overridecat(orig, ui, repo, file1, *pats, **opts):
1067 def overridecat(orig, ui, repo, file1, *pats, **opts):
1068 rev = opts.get('rev')
1068 rev = opts.get('rev')
1069 if not lfutil.standin(file1) in repo[rev]:
1069 if not lfutil.standin(file1) in repo[rev]:
1070 result = orig(ui, repo, file1, *pats, **opts)
1070 result = orig(ui, repo, file1, *pats, **opts)
1071 return result
1071 return result
1072 return lfcommands.catlfile(repo, file1, opts.get('rev'), opts.get('output'))
1072 return lfcommands.catlfile(repo, file1, opts.get('rev'), opts.get('output'))
@@ -1,3595 +1,3597 b''
1 # mq.py - patch queues for mercurial
1 # mq.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''manage a stack of patches
8 '''manage a stack of patches
9
9
10 This extension lets you work with a stack of patches in a Mercurial
10 This extension lets you work with a stack of patches in a Mercurial
11 repository. It manages two stacks of patches - all known patches, and
11 repository. It manages two stacks of patches - all known patches, and
12 applied patches (subset of known patches).
12 applied patches (subset of known patches).
13
13
14 Known patches are represented as patch files in the .hg/patches
14 Known patches are represented as patch files in the .hg/patches
15 directory. Applied patches are both patch files and changesets.
15 directory. Applied patches are both patch files and changesets.
16
16
17 Common tasks (use :hg:`help command` for more details)::
17 Common tasks (use :hg:`help command` for more details)::
18
18
19 create new patch qnew
19 create new patch qnew
20 import existing patch qimport
20 import existing patch qimport
21
21
22 print patch series qseries
22 print patch series qseries
23 print applied patches qapplied
23 print applied patches qapplied
24
24
25 add known patch to applied stack qpush
25 add known patch to applied stack qpush
26 remove patch from applied stack qpop
26 remove patch from applied stack qpop
27 refresh contents of top applied patch qrefresh
27 refresh contents of top applied patch qrefresh
28
28
29 By default, mq will automatically use git patches when required to
29 By default, mq will automatically use git patches when required to
30 avoid losing file mode changes, copy records, binary files or empty
30 avoid losing file mode changes, copy records, binary files or empty
31 files creations or deletions. This behaviour can be configured with::
31 files creations or deletions. This behaviour can be configured with::
32
32
33 [mq]
33 [mq]
34 git = auto/keep/yes/no
34 git = auto/keep/yes/no
35
35
36 If set to 'keep', mq will obey the [diff] section configuration while
36 If set to 'keep', mq will obey the [diff] section configuration while
37 preserving existing git patches upon qrefresh. If set to 'yes' or
37 preserving existing git patches upon qrefresh. If set to 'yes' or
38 'no', mq will override the [diff] section and always generate git or
38 'no', mq will override the [diff] section and always generate git or
39 regular patches, possibly losing data in the second case.
39 regular patches, possibly losing data in the second case.
40
40
41 It may be desirable for mq changesets to be kept in the secret phase (see
41 It may be desirable for mq changesets to be kept in the secret phase (see
42 :hg:`help phases`), which can be enabled with the following setting::
42 :hg:`help phases`), which can be enabled with the following setting::
43
43
44 [mq]
44 [mq]
45 secret = True
45 secret = True
46
46
47 You will by default be managing a patch queue named "patches". You can
47 You will by default be managing a patch queue named "patches". You can
48 create other, independent patch queues with the :hg:`qqueue` command.
48 create other, independent patch queues with the :hg:`qqueue` command.
49
49
50 If the working directory contains uncommitted files, qpush, qpop and
50 If the working directory contains uncommitted files, qpush, qpop and
51 qgoto abort immediately. If -f/--force is used, the changes are
51 qgoto abort immediately. If -f/--force is used, the changes are
52 discarded. Setting::
52 discarded. Setting::
53
53
54 [mq]
54 [mq]
55 keepchanges = True
55 keepchanges = True
56
56
57 make them behave as if --keep-changes were passed, and non-conflicting
57 make them behave as if --keep-changes were passed, and non-conflicting
58 local changes will be tolerated and preserved. If incompatible options
58 local changes will be tolerated and preserved. If incompatible options
59 such as -f/--force or --exact are passed, this setting is ignored.
59 such as -f/--force or --exact are passed, this setting is ignored.
60 '''
60 '''
61
61
62 from mercurial.i18n import _
62 from mercurial.i18n import _
63 from mercurial.node import bin, hex, short, nullid, nullrev
63 from mercurial.node import bin, hex, short, nullid, nullrev
64 from mercurial.lock import release
64 from mercurial.lock import release
65 from mercurial import commands, cmdutil, hg, scmutil, util, revset
65 from mercurial import commands, cmdutil, hg, scmutil, util, revset
66 from mercurial import repair, extensions, url, error, phases
66 from mercurial import repair, extensions, url, error, phases
67 from mercurial import patch as patchmod
67 from mercurial import patch as patchmod
68 import os, re, errno, shutil
68 import os, re, errno, shutil
69
69
70 commands.norepo += " qclone"
70 commands.norepo += " qclone"
71
71
72 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
72 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
73
73
74 cmdtable = {}
74 cmdtable = {}
75 command = cmdutil.command(cmdtable)
75 command = cmdutil.command(cmdtable)
76 testedwith = 'internal'
76 testedwith = 'internal'
77
77
78 # Patch names looks like unix-file names.
78 # Patch names looks like unix-file names.
79 # They must be joinable with queue directory and result in the patch path.
79 # They must be joinable with queue directory and result in the patch path.
80 normname = util.normpath
80 normname = util.normpath
81
81
82 class statusentry(object):
82 class statusentry(object):
83 def __init__(self, node, name):
83 def __init__(self, node, name):
84 self.node, self.name = node, name
84 self.node, self.name = node, name
85 def __repr__(self):
85 def __repr__(self):
86 return hex(self.node) + ':' + self.name
86 return hex(self.node) + ':' + self.name
87
87
88 class patchheader(object):
88 class patchheader(object):
89 def __init__(self, pf, plainmode=False):
89 def __init__(self, pf, plainmode=False):
90 def eatdiff(lines):
90 def eatdiff(lines):
91 while lines:
91 while lines:
92 l = lines[-1]
92 l = lines[-1]
93 if (l.startswith("diff -") or
93 if (l.startswith("diff -") or
94 l.startswith("Index:") or
94 l.startswith("Index:") or
95 l.startswith("===========")):
95 l.startswith("===========")):
96 del lines[-1]
96 del lines[-1]
97 else:
97 else:
98 break
98 break
99 def eatempty(lines):
99 def eatempty(lines):
100 while lines:
100 while lines:
101 if not lines[-1].strip():
101 if not lines[-1].strip():
102 del lines[-1]
102 del lines[-1]
103 else:
103 else:
104 break
104 break
105
105
106 message = []
106 message = []
107 comments = []
107 comments = []
108 user = None
108 user = None
109 date = None
109 date = None
110 parent = None
110 parent = None
111 format = None
111 format = None
112 subject = None
112 subject = None
113 branch = None
113 branch = None
114 nodeid = None
114 nodeid = None
115 diffstart = 0
115 diffstart = 0
116
116
117 for line in file(pf):
117 for line in file(pf):
118 line = line.rstrip()
118 line = line.rstrip()
119 if (line.startswith('diff --git')
119 if (line.startswith('diff --git')
120 or (diffstart and line.startswith('+++ '))):
120 or (diffstart and line.startswith('+++ '))):
121 diffstart = 2
121 diffstart = 2
122 break
122 break
123 diffstart = 0 # reset
123 diffstart = 0 # reset
124 if line.startswith("--- "):
124 if line.startswith("--- "):
125 diffstart = 1
125 diffstart = 1
126 continue
126 continue
127 elif format == "hgpatch":
127 elif format == "hgpatch":
128 # parse values when importing the result of an hg export
128 # parse values when importing the result of an hg export
129 if line.startswith("# User "):
129 if line.startswith("# User "):
130 user = line[7:]
130 user = line[7:]
131 elif line.startswith("# Date "):
131 elif line.startswith("# Date "):
132 date = line[7:]
132 date = line[7:]
133 elif line.startswith("# Parent "):
133 elif line.startswith("# Parent "):
134 parent = line[9:].lstrip()
134 parent = line[9:].lstrip()
135 elif line.startswith("# Branch "):
135 elif line.startswith("# Branch "):
136 branch = line[9:]
136 branch = line[9:]
137 elif line.startswith("# Node ID "):
137 elif line.startswith("# Node ID "):
138 nodeid = line[10:]
138 nodeid = line[10:]
139 elif not line.startswith("# ") and line:
139 elif not line.startswith("# ") and line:
140 message.append(line)
140 message.append(line)
141 format = None
141 format = None
142 elif line == '# HG changeset patch':
142 elif line == '# HG changeset patch':
143 message = []
143 message = []
144 format = "hgpatch"
144 format = "hgpatch"
145 elif (format != "tagdone" and (line.startswith("Subject: ") or
145 elif (format != "tagdone" and (line.startswith("Subject: ") or
146 line.startswith("subject: "))):
146 line.startswith("subject: "))):
147 subject = line[9:]
147 subject = line[9:]
148 format = "tag"
148 format = "tag"
149 elif (format != "tagdone" and (line.startswith("From: ") or
149 elif (format != "tagdone" and (line.startswith("From: ") or
150 line.startswith("from: "))):
150 line.startswith("from: "))):
151 user = line[6:]
151 user = line[6:]
152 format = "tag"
152 format = "tag"
153 elif (format != "tagdone" and (line.startswith("Date: ") or
153 elif (format != "tagdone" and (line.startswith("Date: ") or
154 line.startswith("date: "))):
154 line.startswith("date: "))):
155 date = line[6:]
155 date = line[6:]
156 format = "tag"
156 format = "tag"
157 elif format == "tag" and line == "":
157 elif format == "tag" and line == "":
158 # when looking for tags (subject: from: etc) they
158 # when looking for tags (subject: from: etc) they
159 # end once you find a blank line in the source
159 # end once you find a blank line in the source
160 format = "tagdone"
160 format = "tagdone"
161 elif message or line:
161 elif message or line:
162 message.append(line)
162 message.append(line)
163 comments.append(line)
163 comments.append(line)
164
164
165 eatdiff(message)
165 eatdiff(message)
166 eatdiff(comments)
166 eatdiff(comments)
167 # Remember the exact starting line of the patch diffs before consuming
167 # Remember the exact starting line of the patch diffs before consuming
168 # empty lines, for external use by TortoiseHg and others
168 # empty lines, for external use by TortoiseHg and others
169 self.diffstartline = len(comments)
169 self.diffstartline = len(comments)
170 eatempty(message)
170 eatempty(message)
171 eatempty(comments)
171 eatempty(comments)
172
172
173 # make sure message isn't empty
173 # make sure message isn't empty
174 if format and format.startswith("tag") and subject:
174 if format and format.startswith("tag") and subject:
175 message.insert(0, "")
175 message.insert(0, "")
176 message.insert(0, subject)
176 message.insert(0, subject)
177
177
178 self.message = message
178 self.message = message
179 self.comments = comments
179 self.comments = comments
180 self.user = user
180 self.user = user
181 self.date = date
181 self.date = date
182 self.parent = parent
182 self.parent = parent
183 # nodeid and branch are for external use by TortoiseHg and others
183 # nodeid and branch are for external use by TortoiseHg and others
184 self.nodeid = nodeid
184 self.nodeid = nodeid
185 self.branch = branch
185 self.branch = branch
186 self.haspatch = diffstart > 1
186 self.haspatch = diffstart > 1
187 self.plainmode = plainmode
187 self.plainmode = plainmode
188
188
189 def setuser(self, user):
189 def setuser(self, user):
190 if not self.updateheader(['From: ', '# User '], user):
190 if not self.updateheader(['From: ', '# User '], user):
191 try:
191 try:
192 patchheaderat = self.comments.index('# HG changeset patch')
192 patchheaderat = self.comments.index('# HG changeset patch')
193 self.comments.insert(patchheaderat + 1, '# User ' + user)
193 self.comments.insert(patchheaderat + 1, '# User ' + user)
194 except ValueError:
194 except ValueError:
195 if self.plainmode or self._hasheader(['Date: ']):
195 if self.plainmode or self._hasheader(['Date: ']):
196 self.comments = ['From: ' + user] + self.comments
196 self.comments = ['From: ' + user] + self.comments
197 else:
197 else:
198 tmp = ['# HG changeset patch', '# User ' + user, '']
198 tmp = ['# HG changeset patch', '# User ' + user, '']
199 self.comments = tmp + self.comments
199 self.comments = tmp + self.comments
200 self.user = user
200 self.user = user
201
201
202 def setdate(self, date):
202 def setdate(self, date):
203 if not self.updateheader(['Date: ', '# Date '], date):
203 if not self.updateheader(['Date: ', '# Date '], date):
204 try:
204 try:
205 patchheaderat = self.comments.index('# HG changeset patch')
205 patchheaderat = self.comments.index('# HG changeset patch')
206 self.comments.insert(patchheaderat + 1, '# Date ' + date)
206 self.comments.insert(patchheaderat + 1, '# Date ' + date)
207 except ValueError:
207 except ValueError:
208 if self.plainmode or self._hasheader(['From: ']):
208 if self.plainmode or self._hasheader(['From: ']):
209 self.comments = ['Date: ' + date] + self.comments
209 self.comments = ['Date: ' + date] + self.comments
210 else:
210 else:
211 tmp = ['# HG changeset patch', '# Date ' + date, '']
211 tmp = ['# HG changeset patch', '# Date ' + date, '']
212 self.comments = tmp + self.comments
212 self.comments = tmp + self.comments
213 self.date = date
213 self.date = date
214
214
215 def setparent(self, parent):
215 def setparent(self, parent):
216 if not self.updateheader(['# Parent '], parent):
216 if not self.updateheader(['# Parent '], parent):
217 try:
217 try:
218 patchheaderat = self.comments.index('# HG changeset patch')
218 patchheaderat = self.comments.index('# HG changeset patch')
219 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
219 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
220 except ValueError:
220 except ValueError:
221 pass
221 pass
222 self.parent = parent
222 self.parent = parent
223
223
224 def setmessage(self, message):
224 def setmessage(self, message):
225 if self.comments:
225 if self.comments:
226 self._delmsg()
226 self._delmsg()
227 self.message = [message]
227 self.message = [message]
228 self.comments += self.message
228 self.comments += self.message
229
229
230 def updateheader(self, prefixes, new):
230 def updateheader(self, prefixes, new):
231 '''Update all references to a field in the patch header.
231 '''Update all references to a field in the patch header.
232 Return whether the field is present.'''
232 Return whether the field is present.'''
233 res = False
233 res = False
234 for prefix in prefixes:
234 for prefix in prefixes:
235 for i in xrange(len(self.comments)):
235 for i in xrange(len(self.comments)):
236 if self.comments[i].startswith(prefix):
236 if self.comments[i].startswith(prefix):
237 self.comments[i] = prefix + new
237 self.comments[i] = prefix + new
238 res = True
238 res = True
239 break
239 break
240 return res
240 return res
241
241
242 def _hasheader(self, prefixes):
242 def _hasheader(self, prefixes):
243 '''Check if a header starts with any of the given prefixes.'''
243 '''Check if a header starts with any of the given prefixes.'''
244 for prefix in prefixes:
244 for prefix in prefixes:
245 for comment in self.comments:
245 for comment in self.comments:
246 if comment.startswith(prefix):
246 if comment.startswith(prefix):
247 return True
247 return True
248 return False
248 return False
249
249
250 def __str__(self):
250 def __str__(self):
251 if not self.comments:
251 if not self.comments:
252 return ''
252 return ''
253 return '\n'.join(self.comments) + '\n\n'
253 return '\n'.join(self.comments) + '\n\n'
254
254
255 def _delmsg(self):
255 def _delmsg(self):
256 '''Remove existing message, keeping the rest of the comments fields.
256 '''Remove existing message, keeping the rest of the comments fields.
257 If comments contains 'subject: ', message will prepend
257 If comments contains 'subject: ', message will prepend
258 the field and a blank line.'''
258 the field and a blank line.'''
259 if self.message:
259 if self.message:
260 subj = 'subject: ' + self.message[0].lower()
260 subj = 'subject: ' + self.message[0].lower()
261 for i in xrange(len(self.comments)):
261 for i in xrange(len(self.comments)):
262 if subj == self.comments[i].lower():
262 if subj == self.comments[i].lower():
263 del self.comments[i]
263 del self.comments[i]
264 self.message = self.message[2:]
264 self.message = self.message[2:]
265 break
265 break
266 ci = 0
266 ci = 0
267 for mi in self.message:
267 for mi in self.message:
268 while mi != self.comments[ci]:
268 while mi != self.comments[ci]:
269 ci += 1
269 ci += 1
270 del self.comments[ci]
270 del self.comments[ci]
271
271
272 def newcommit(repo, phase, *args, **kwargs):
272 def newcommit(repo, phase, *args, **kwargs):
273 """helper dedicated to ensure a commit respect mq.secret setting
273 """helper dedicated to ensure a commit respect mq.secret setting
274
274
275 It should be used instead of repo.commit inside the mq source for operation
275 It should be used instead of repo.commit inside the mq source for operation
276 creating new changeset.
276 creating new changeset.
277 """
277 """
278 if phase is None:
278 if phase is None:
279 if repo.ui.configbool('mq', 'secret', False):
279 if repo.ui.configbool('mq', 'secret', False):
280 phase = phases.secret
280 phase = phases.secret
281 if phase is not None:
281 if phase is not None:
282 backup = repo.ui.backupconfig('phases', 'new-commit')
282 backup = repo.ui.backupconfig('phases', 'new-commit')
283 # Marking the repository as committing an mq patch can be used
283 # Marking the repository as committing an mq patch can be used
284 # to optimize operations like _branchtags().
284 # to optimize operations like _branchtags().
285 repo._committingpatch = True
285 repo._committingpatch = True
286 try:
286 try:
287 if phase is not None:
287 if phase is not None:
288 repo.ui.setconfig('phases', 'new-commit', phase)
288 repo.ui.setconfig('phases', 'new-commit', phase)
289 return repo.commit(*args, **kwargs)
289 return repo.commit(*args, **kwargs)
290 finally:
290 finally:
291 repo._committingpatch = False
291 repo._committingpatch = False
292 if phase is not None:
292 if phase is not None:
293 repo.ui.restoreconfig(backup)
293 repo.ui.restoreconfig(backup)
294
294
295 class AbortNoCleanup(error.Abort):
295 class AbortNoCleanup(error.Abort):
296 pass
296 pass
297
297
298 class queue(object):
298 class queue(object):
299 def __init__(self, ui, path, patchdir=None):
299 def __init__(self, ui, path, patchdir=None):
300 self.basepath = path
300 self.basepath = path
301 try:
301 try:
302 fh = open(os.path.join(path, 'patches.queue'))
302 fh = open(os.path.join(path, 'patches.queue'))
303 cur = fh.read().rstrip()
303 cur = fh.read().rstrip()
304 fh.close()
304 fh.close()
305 if not cur:
305 if not cur:
306 curpath = os.path.join(path, 'patches')
306 curpath = os.path.join(path, 'patches')
307 else:
307 else:
308 curpath = os.path.join(path, 'patches-' + cur)
308 curpath = os.path.join(path, 'patches-' + cur)
309 except IOError:
309 except IOError:
310 curpath = os.path.join(path, 'patches')
310 curpath = os.path.join(path, 'patches')
311 self.path = patchdir or curpath
311 self.path = patchdir or curpath
312 self.opener = scmutil.opener(self.path)
312 self.opener = scmutil.opener(self.path)
313 self.ui = ui
313 self.ui = ui
314 self.applieddirty = False
314 self.applieddirty = False
315 self.seriesdirty = False
315 self.seriesdirty = False
316 self.added = []
316 self.added = []
317 self.seriespath = "series"
317 self.seriespath = "series"
318 self.statuspath = "status"
318 self.statuspath = "status"
319 self.guardspath = "guards"
319 self.guardspath = "guards"
320 self.activeguards = None
320 self.activeguards = None
321 self.guardsdirty = False
321 self.guardsdirty = False
322 # Handle mq.git as a bool with extended values
322 # Handle mq.git as a bool with extended values
323 try:
323 try:
324 gitmode = ui.configbool('mq', 'git', None)
324 gitmode = ui.configbool('mq', 'git', None)
325 if gitmode is None:
325 if gitmode is None:
326 raise error.ConfigError
326 raise error.ConfigError
327 self.gitmode = gitmode and 'yes' or 'no'
327 self.gitmode = gitmode and 'yes' or 'no'
328 except error.ConfigError:
328 except error.ConfigError:
329 self.gitmode = ui.config('mq', 'git', 'auto').lower()
329 self.gitmode = ui.config('mq', 'git', 'auto').lower()
330 self.plainmode = ui.configbool('mq', 'plain', False)
330 self.plainmode = ui.configbool('mq', 'plain', False)
331
331
332 @util.propertycache
332 @util.propertycache
333 def applied(self):
333 def applied(self):
334 def parselines(lines):
334 def parselines(lines):
335 for l in lines:
335 for l in lines:
336 entry = l.split(':', 1)
336 entry = l.split(':', 1)
337 if len(entry) > 1:
337 if len(entry) > 1:
338 n, name = entry
338 n, name = entry
339 yield statusentry(bin(n), name)
339 yield statusentry(bin(n), name)
340 elif l.strip():
340 elif l.strip():
341 self.ui.warn(_('malformated mq status line: %s\n') % entry)
341 self.ui.warn(_('malformated mq status line: %s\n') % entry)
342 # else we ignore empty lines
342 # else we ignore empty lines
343 try:
343 try:
344 lines = self.opener.read(self.statuspath).splitlines()
344 lines = self.opener.read(self.statuspath).splitlines()
345 return list(parselines(lines))
345 return list(parselines(lines))
346 except IOError, e:
346 except IOError, e:
347 if e.errno == errno.ENOENT:
347 if e.errno == errno.ENOENT:
348 return []
348 return []
349 raise
349 raise
350
350
351 @util.propertycache
351 @util.propertycache
352 def fullseries(self):
352 def fullseries(self):
353 try:
353 try:
354 return self.opener.read(self.seriespath).splitlines()
354 return self.opener.read(self.seriespath).splitlines()
355 except IOError, e:
355 except IOError, e:
356 if e.errno == errno.ENOENT:
356 if e.errno == errno.ENOENT:
357 return []
357 return []
358 raise
358 raise
359
359
360 @util.propertycache
360 @util.propertycache
361 def series(self):
361 def series(self):
362 self.parseseries()
362 self.parseseries()
363 return self.series
363 return self.series
364
364
365 @util.propertycache
365 @util.propertycache
366 def seriesguards(self):
366 def seriesguards(self):
367 self.parseseries()
367 self.parseseries()
368 return self.seriesguards
368 return self.seriesguards
369
369
370 def invalidate(self):
370 def invalidate(self):
371 for a in 'applied fullseries series seriesguards'.split():
371 for a in 'applied fullseries series seriesguards'.split():
372 if a in self.__dict__:
372 if a in self.__dict__:
373 delattr(self, a)
373 delattr(self, a)
374 self.applieddirty = False
374 self.applieddirty = False
375 self.seriesdirty = False
375 self.seriesdirty = False
376 self.guardsdirty = False
376 self.guardsdirty = False
377 self.activeguards = None
377 self.activeguards = None
378
378
379 def diffopts(self, opts={}, patchfn=None):
379 def diffopts(self, opts={}, patchfn=None):
380 diffopts = patchmod.diffopts(self.ui, opts)
380 diffopts = patchmod.diffopts(self.ui, opts)
381 if self.gitmode == 'auto':
381 if self.gitmode == 'auto':
382 diffopts.upgrade = True
382 diffopts.upgrade = True
383 elif self.gitmode == 'keep':
383 elif self.gitmode == 'keep':
384 pass
384 pass
385 elif self.gitmode in ('yes', 'no'):
385 elif self.gitmode in ('yes', 'no'):
386 diffopts.git = self.gitmode == 'yes'
386 diffopts.git = self.gitmode == 'yes'
387 else:
387 else:
388 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
388 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
389 ' got %s') % self.gitmode)
389 ' got %s') % self.gitmode)
390 if patchfn:
390 if patchfn:
391 diffopts = self.patchopts(diffopts, patchfn)
391 diffopts = self.patchopts(diffopts, patchfn)
392 return diffopts
392 return diffopts
393
393
394 def patchopts(self, diffopts, *patches):
394 def patchopts(self, diffopts, *patches):
395 """Return a copy of input diff options with git set to true if
395 """Return a copy of input diff options with git set to true if
396 referenced patch is a git patch and should be preserved as such.
396 referenced patch is a git patch and should be preserved as such.
397 """
397 """
398 diffopts = diffopts.copy()
398 diffopts = diffopts.copy()
399 if not diffopts.git and self.gitmode == 'keep':
399 if not diffopts.git and self.gitmode == 'keep':
400 for patchfn in patches:
400 for patchfn in patches:
401 patchf = self.opener(patchfn, 'r')
401 patchf = self.opener(patchfn, 'r')
402 # if the patch was a git patch, refresh it as a git patch
402 # if the patch was a git patch, refresh it as a git patch
403 for line in patchf:
403 for line in patchf:
404 if line.startswith('diff --git'):
404 if line.startswith('diff --git'):
405 diffopts.git = True
405 diffopts.git = True
406 break
406 break
407 patchf.close()
407 patchf.close()
408 return diffopts
408 return diffopts
409
409
410 def join(self, *p):
410 def join(self, *p):
411 return os.path.join(self.path, *p)
411 return os.path.join(self.path, *p)
412
412
413 def findseries(self, patch):
413 def findseries(self, patch):
414 def matchpatch(l):
414 def matchpatch(l):
415 l = l.split('#', 1)[0]
415 l = l.split('#', 1)[0]
416 return l.strip() == patch
416 return l.strip() == patch
417 for index, l in enumerate(self.fullseries):
417 for index, l in enumerate(self.fullseries):
418 if matchpatch(l):
418 if matchpatch(l):
419 return index
419 return index
420 return None
420 return None
421
421
422 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
422 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
423
423
424 def parseseries(self):
424 def parseseries(self):
425 self.series = []
425 self.series = []
426 self.seriesguards = []
426 self.seriesguards = []
427 for l in self.fullseries:
427 for l in self.fullseries:
428 h = l.find('#')
428 h = l.find('#')
429 if h == -1:
429 if h == -1:
430 patch = l
430 patch = l
431 comment = ''
431 comment = ''
432 elif h == 0:
432 elif h == 0:
433 continue
433 continue
434 else:
434 else:
435 patch = l[:h]
435 patch = l[:h]
436 comment = l[h:]
436 comment = l[h:]
437 patch = patch.strip()
437 patch = patch.strip()
438 if patch:
438 if patch:
439 if patch in self.series:
439 if patch in self.series:
440 raise util.Abort(_('%s appears more than once in %s') %
440 raise util.Abort(_('%s appears more than once in %s') %
441 (patch, self.join(self.seriespath)))
441 (patch, self.join(self.seriespath)))
442 self.series.append(patch)
442 self.series.append(patch)
443 self.seriesguards.append(self.guard_re.findall(comment))
443 self.seriesguards.append(self.guard_re.findall(comment))
444
444
445 def checkguard(self, guard):
445 def checkguard(self, guard):
446 if not guard:
446 if not guard:
447 return _('guard cannot be an empty string')
447 return _('guard cannot be an empty string')
448 bad_chars = '# \t\r\n\f'
448 bad_chars = '# \t\r\n\f'
449 first = guard[0]
449 first = guard[0]
450 if first in '-+':
450 if first in '-+':
451 return (_('guard %r starts with invalid character: %r') %
451 return (_('guard %r starts with invalid character: %r') %
452 (guard, first))
452 (guard, first))
453 for c in bad_chars:
453 for c in bad_chars:
454 if c in guard:
454 if c in guard:
455 return _('invalid character in guard %r: %r') % (guard, c)
455 return _('invalid character in guard %r: %r') % (guard, c)
456
456
457 def setactive(self, guards):
457 def setactive(self, guards):
458 for guard in guards:
458 for guard in guards:
459 bad = self.checkguard(guard)
459 bad = self.checkguard(guard)
460 if bad:
460 if bad:
461 raise util.Abort(bad)
461 raise util.Abort(bad)
462 guards = sorted(set(guards))
462 guards = sorted(set(guards))
463 self.ui.debug('active guards: %s\n' % ' '.join(guards))
463 self.ui.debug('active guards: %s\n' % ' '.join(guards))
464 self.activeguards = guards
464 self.activeguards = guards
465 self.guardsdirty = True
465 self.guardsdirty = True
466
466
467 def active(self):
467 def active(self):
468 if self.activeguards is None:
468 if self.activeguards is None:
469 self.activeguards = []
469 self.activeguards = []
470 try:
470 try:
471 guards = self.opener.read(self.guardspath).split()
471 guards = self.opener.read(self.guardspath).split()
472 except IOError, err:
472 except IOError, err:
473 if err.errno != errno.ENOENT:
473 if err.errno != errno.ENOENT:
474 raise
474 raise
475 guards = []
475 guards = []
476 for i, guard in enumerate(guards):
476 for i, guard in enumerate(guards):
477 bad = self.checkguard(guard)
477 bad = self.checkguard(guard)
478 if bad:
478 if bad:
479 self.ui.warn('%s:%d: %s\n' %
479 self.ui.warn('%s:%d: %s\n' %
480 (self.join(self.guardspath), i + 1, bad))
480 (self.join(self.guardspath), i + 1, bad))
481 else:
481 else:
482 self.activeguards.append(guard)
482 self.activeguards.append(guard)
483 return self.activeguards
483 return self.activeguards
484
484
485 def setguards(self, idx, guards):
485 def setguards(self, idx, guards):
486 for g in guards:
486 for g in guards:
487 if len(g) < 2:
487 if len(g) < 2:
488 raise util.Abort(_('guard %r too short') % g)
488 raise util.Abort(_('guard %r too short') % g)
489 if g[0] not in '-+':
489 if g[0] not in '-+':
490 raise util.Abort(_('guard %r starts with invalid char') % g)
490 raise util.Abort(_('guard %r starts with invalid char') % g)
491 bad = self.checkguard(g[1:])
491 bad = self.checkguard(g[1:])
492 if bad:
492 if bad:
493 raise util.Abort(bad)
493 raise util.Abort(bad)
494 drop = self.guard_re.sub('', self.fullseries[idx])
494 drop = self.guard_re.sub('', self.fullseries[idx])
495 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
495 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
496 self.parseseries()
496 self.parseseries()
497 self.seriesdirty = True
497 self.seriesdirty = True
498
498
499 def pushable(self, idx):
499 def pushable(self, idx):
500 if isinstance(idx, str):
500 if isinstance(idx, str):
501 idx = self.series.index(idx)
501 idx = self.series.index(idx)
502 patchguards = self.seriesguards[idx]
502 patchguards = self.seriesguards[idx]
503 if not patchguards:
503 if not patchguards:
504 return True, None
504 return True, None
505 guards = self.active()
505 guards = self.active()
506 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
506 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
507 if exactneg:
507 if exactneg:
508 return False, repr(exactneg[0])
508 return False, repr(exactneg[0])
509 pos = [g for g in patchguards if g[0] == '+']
509 pos = [g for g in patchguards if g[0] == '+']
510 exactpos = [g for g in pos if g[1:] in guards]
510 exactpos = [g for g in pos if g[1:] in guards]
511 if pos:
511 if pos:
512 if exactpos:
512 if exactpos:
513 return True, repr(exactpos[0])
513 return True, repr(exactpos[0])
514 return False, ' '.join(map(repr, pos))
514 return False, ' '.join(map(repr, pos))
515 return True, ''
515 return True, ''
516
516
517 def explainpushable(self, idx, all_patches=False):
517 def explainpushable(self, idx, all_patches=False):
518 write = all_patches and self.ui.write or self.ui.warn
518 write = all_patches and self.ui.write or self.ui.warn
519 if all_patches or self.ui.verbose:
519 if all_patches or self.ui.verbose:
520 if isinstance(idx, str):
520 if isinstance(idx, str):
521 idx = self.series.index(idx)
521 idx = self.series.index(idx)
522 pushable, why = self.pushable(idx)
522 pushable, why = self.pushable(idx)
523 if all_patches and pushable:
523 if all_patches and pushable:
524 if why is None:
524 if why is None:
525 write(_('allowing %s - no guards in effect\n') %
525 write(_('allowing %s - no guards in effect\n') %
526 self.series[idx])
526 self.series[idx])
527 else:
527 else:
528 if not why:
528 if not why:
529 write(_('allowing %s - no matching negative guards\n') %
529 write(_('allowing %s - no matching negative guards\n') %
530 self.series[idx])
530 self.series[idx])
531 else:
531 else:
532 write(_('allowing %s - guarded by %s\n') %
532 write(_('allowing %s - guarded by %s\n') %
533 (self.series[idx], why))
533 (self.series[idx], why))
534 if not pushable:
534 if not pushable:
535 if why:
535 if why:
536 write(_('skipping %s - guarded by %s\n') %
536 write(_('skipping %s - guarded by %s\n') %
537 (self.series[idx], why))
537 (self.series[idx], why))
538 else:
538 else:
539 write(_('skipping %s - no matching guards\n') %
539 write(_('skipping %s - no matching guards\n') %
540 self.series[idx])
540 self.series[idx])
541
541
542 def savedirty(self):
542 def savedirty(self):
543 def writelist(items, path):
543 def writelist(items, path):
544 fp = self.opener(path, 'w')
544 fp = self.opener(path, 'w')
545 for i in items:
545 for i in items:
546 fp.write("%s\n" % i)
546 fp.write("%s\n" % i)
547 fp.close()
547 fp.close()
548 if self.applieddirty:
548 if self.applieddirty:
549 writelist(map(str, self.applied), self.statuspath)
549 writelist(map(str, self.applied), self.statuspath)
550 self.applieddirty = False
550 self.applieddirty = False
551 if self.seriesdirty:
551 if self.seriesdirty:
552 writelist(self.fullseries, self.seriespath)
552 writelist(self.fullseries, self.seriespath)
553 self.seriesdirty = False
553 self.seriesdirty = False
554 if self.guardsdirty:
554 if self.guardsdirty:
555 writelist(self.activeguards, self.guardspath)
555 writelist(self.activeguards, self.guardspath)
556 self.guardsdirty = False
556 self.guardsdirty = False
557 if self.added:
557 if self.added:
558 qrepo = self.qrepo()
558 qrepo = self.qrepo()
559 if qrepo:
559 if qrepo:
560 qrepo[None].add(f for f in self.added if f not in qrepo[None])
560 qrepo[None].add(f for f in self.added if f not in qrepo[None])
561 self.added = []
561 self.added = []
562
562
563 def removeundo(self, repo):
563 def removeundo(self, repo):
564 undo = repo.sjoin('undo')
564 undo = repo.sjoin('undo')
565 if not os.path.exists(undo):
565 if not os.path.exists(undo):
566 return
566 return
567 try:
567 try:
568 os.unlink(undo)
568 os.unlink(undo)
569 except OSError, inst:
569 except OSError, inst:
570 self.ui.warn(_('error removing undo: %s\n') % str(inst))
570 self.ui.warn(_('error removing undo: %s\n') % str(inst))
571
571
572 def backup(self, repo, files, copy=False):
572 def backup(self, repo, files, copy=False):
573 # backup local changes in --force case
573 # backup local changes in --force case
574 for f in sorted(files):
574 for f in sorted(files):
575 absf = repo.wjoin(f)
575 absf = repo.wjoin(f)
576 if os.path.lexists(absf):
576 if os.path.lexists(absf):
577 self.ui.note(_('saving current version of %s as %s\n') %
577 self.ui.note(_('saving current version of %s as %s\n') %
578 (f, f + '.orig'))
578 (f, f + '.orig'))
579 if copy:
579 if copy:
580 util.copyfile(absf, absf + '.orig')
580 util.copyfile(absf, absf + '.orig')
581 else:
581 else:
582 util.rename(absf, absf + '.orig')
582 util.rename(absf, absf + '.orig')
583
583
584 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
584 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
585 fp=None, changes=None, opts={}):
585 fp=None, changes=None, opts={}):
586 stat = opts.get('stat')
586 stat = opts.get('stat')
587 m = scmutil.match(repo[node1], files, opts)
587 m = scmutil.match(repo[node1], files, opts)
588 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
588 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
589 changes, stat, fp)
589 changes, stat, fp)
590
590
591 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
591 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
592 # first try just applying the patch
592 # first try just applying the patch
593 (err, n) = self.apply(repo, [patch], update_status=False,
593 (err, n) = self.apply(repo, [patch], update_status=False,
594 strict=True, merge=rev)
594 strict=True, merge=rev)
595
595
596 if err == 0:
596 if err == 0:
597 return (err, n)
597 return (err, n)
598
598
599 if n is None:
599 if n is None:
600 raise util.Abort(_("apply failed for patch %s") % patch)
600 raise util.Abort(_("apply failed for patch %s") % patch)
601
601
602 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
602 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
603
603
604 # apply failed, strip away that rev and merge.
604 # apply failed, strip away that rev and merge.
605 hg.clean(repo, head)
605 hg.clean(repo, head)
606 self.strip(repo, [n], update=False, backup='strip')
606 self.strip(repo, [n], update=False, backup='strip')
607
607
608 ctx = repo[rev]
608 ctx = repo[rev]
609 ret = hg.merge(repo, rev)
609 ret = hg.merge(repo, rev)
610 if ret:
610 if ret:
611 raise util.Abort(_("update returned %d") % ret)
611 raise util.Abort(_("update returned %d") % ret)
612 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
612 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
613 if n is None:
613 if n is None:
614 raise util.Abort(_("repo commit failed"))
614 raise util.Abort(_("repo commit failed"))
615 try:
615 try:
616 ph = patchheader(mergeq.join(patch), self.plainmode)
616 ph = patchheader(mergeq.join(patch), self.plainmode)
617 except Exception:
617 except Exception:
618 raise util.Abort(_("unable to read %s") % patch)
618 raise util.Abort(_("unable to read %s") % patch)
619
619
620 diffopts = self.patchopts(diffopts, patch)
620 diffopts = self.patchopts(diffopts, patch)
621 patchf = self.opener(patch, "w")
621 patchf = self.opener(patch, "w")
622 comments = str(ph)
622 comments = str(ph)
623 if comments:
623 if comments:
624 patchf.write(comments)
624 patchf.write(comments)
625 self.printdiff(repo, diffopts, head, n, fp=patchf)
625 self.printdiff(repo, diffopts, head, n, fp=patchf)
626 patchf.close()
626 patchf.close()
627 self.removeundo(repo)
627 self.removeundo(repo)
628 return (0, n)
628 return (0, n)
629
629
630 def qparents(self, repo, rev=None):
630 def qparents(self, repo, rev=None):
631 if rev is None:
631 if rev is None:
632 (p1, p2) = repo.dirstate.parents()
632 (p1, p2) = repo.dirstate.parents()
633 if p2 == nullid:
633 if p2 == nullid:
634 return p1
634 return p1
635 if not self.applied:
635 if not self.applied:
636 return None
636 return None
637 return self.applied[-1].node
637 return self.applied[-1].node
638 p1, p2 = repo.changelog.parents(rev)
638 p1, p2 = repo.changelog.parents(rev)
639 if p2 != nullid and p2 in [x.node for x in self.applied]:
639 if p2 != nullid and p2 in [x.node for x in self.applied]:
640 return p2
640 return p2
641 return p1
641 return p1
642
642
643 def mergepatch(self, repo, mergeq, series, diffopts):
643 def mergepatch(self, repo, mergeq, series, diffopts):
644 if not self.applied:
644 if not self.applied:
645 # each of the patches merged in will have two parents. This
645 # each of the patches merged in will have two parents. This
646 # can confuse the qrefresh, qdiff, and strip code because it
646 # can confuse the qrefresh, qdiff, and strip code because it
647 # needs to know which parent is actually in the patch queue.
647 # needs to know which parent is actually in the patch queue.
648 # so, we insert a merge marker with only one parent. This way
648 # so, we insert a merge marker with only one parent. This way
649 # the first patch in the queue is never a merge patch
649 # the first patch in the queue is never a merge patch
650 #
650 #
651 pname = ".hg.patches.merge.marker"
651 pname = ".hg.patches.merge.marker"
652 n = newcommit(repo, None, '[mq]: merge marker', force=True)
652 n = newcommit(repo, None, '[mq]: merge marker', force=True)
653 self.removeundo(repo)
653 self.removeundo(repo)
654 self.applied.append(statusentry(n, pname))
654 self.applied.append(statusentry(n, pname))
655 self.applieddirty = True
655 self.applieddirty = True
656
656
657 head = self.qparents(repo)
657 head = self.qparents(repo)
658
658
659 for patch in series:
659 for patch in series:
660 patch = mergeq.lookup(patch, strict=True)
660 patch = mergeq.lookup(patch, strict=True)
661 if not patch:
661 if not patch:
662 self.ui.warn(_("patch %s does not exist\n") % patch)
662 self.ui.warn(_("patch %s does not exist\n") % patch)
663 return (1, None)
663 return (1, None)
664 pushable, reason = self.pushable(patch)
664 pushable, reason = self.pushable(patch)
665 if not pushable:
665 if not pushable:
666 self.explainpushable(patch, all_patches=True)
666 self.explainpushable(patch, all_patches=True)
667 continue
667 continue
668 info = mergeq.isapplied(patch)
668 info = mergeq.isapplied(patch)
669 if not info:
669 if not info:
670 self.ui.warn(_("patch %s is not applied\n") % patch)
670 self.ui.warn(_("patch %s is not applied\n") % patch)
671 return (1, None)
671 return (1, None)
672 rev = info[1]
672 rev = info[1]
673 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
673 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
674 if head:
674 if head:
675 self.applied.append(statusentry(head, patch))
675 self.applied.append(statusentry(head, patch))
676 self.applieddirty = True
676 self.applieddirty = True
677 if err:
677 if err:
678 return (err, head)
678 return (err, head)
679 self.savedirty()
679 self.savedirty()
680 return (0, head)
680 return (0, head)
681
681
682 def patch(self, repo, patchfile):
682 def patch(self, repo, patchfile):
683 '''Apply patchfile to the working directory.
683 '''Apply patchfile to the working directory.
684 patchfile: name of patch file'''
684 patchfile: name of patch file'''
685 files = set()
685 files = set()
686 try:
686 try:
687 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
687 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
688 files=files, eolmode=None)
688 files=files, eolmode=None)
689 return (True, list(files), fuzz)
689 return (True, list(files), fuzz)
690 except Exception, inst:
690 except Exception, inst:
691 self.ui.note(str(inst) + '\n')
691 self.ui.note(str(inst) + '\n')
692 if not self.ui.verbose:
692 if not self.ui.verbose:
693 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
693 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
694 self.ui.traceback()
694 self.ui.traceback()
695 return (False, list(files), False)
695 return (False, list(files), False)
696
696
697 def apply(self, repo, series, list=False, update_status=True,
697 def apply(self, repo, series, list=False, update_status=True,
698 strict=False, patchdir=None, merge=None, all_files=None,
698 strict=False, patchdir=None, merge=None, all_files=None,
699 tobackup=None, keepchanges=False):
699 tobackup=None, keepchanges=False):
700 wlock = lock = tr = None
700 wlock = lock = tr = None
701 try:
701 try:
702 wlock = repo.wlock()
702 wlock = repo.wlock()
703 lock = repo.lock()
703 lock = repo.lock()
704 tr = repo.transaction("qpush")
704 tr = repo.transaction("qpush")
705 try:
705 try:
706 ret = self._apply(repo, series, list, update_status,
706 ret = self._apply(repo, series, list, update_status,
707 strict, patchdir, merge, all_files=all_files,
707 strict, patchdir, merge, all_files=all_files,
708 tobackup=tobackup, keepchanges=keepchanges)
708 tobackup=tobackup, keepchanges=keepchanges)
709 tr.close()
709 tr.close()
710 self.savedirty()
710 self.savedirty()
711 return ret
711 return ret
712 except AbortNoCleanup:
712 except AbortNoCleanup:
713 tr.close()
713 tr.close()
714 self.savedirty()
714 self.savedirty()
715 return 2, repo.dirstate.p1()
715 return 2, repo.dirstate.p1()
716 except: # re-raises
716 except: # re-raises
717 try:
717 try:
718 tr.abort()
718 tr.abort()
719 finally:
719 finally:
720 repo.invalidate()
720 repo.invalidate()
721 repo.dirstate.invalidate()
721 repo.dirstate.invalidate()
722 self.invalidate()
722 self.invalidate()
723 raise
723 raise
724 finally:
724 finally:
725 release(tr, lock, wlock)
725 release(tr, lock, wlock)
726 self.removeundo(repo)
726 self.removeundo(repo)
727
727
728 def _apply(self, repo, series, list=False, update_status=True,
728 def _apply(self, repo, series, list=False, update_status=True,
729 strict=False, patchdir=None, merge=None, all_files=None,
729 strict=False, patchdir=None, merge=None, all_files=None,
730 tobackup=None, keepchanges=False):
730 tobackup=None, keepchanges=False):
731 """returns (error, hash)
731 """returns (error, hash)
732
732
733 error = 1 for unable to read, 2 for patch failed, 3 for patch
733 error = 1 for unable to read, 2 for patch failed, 3 for patch
734 fuzz. tobackup is None or a set of files to backup before they
734 fuzz. tobackup is None or a set of files to backup before they
735 are modified by a patch.
735 are modified by a patch.
736 """
736 """
737 # TODO unify with commands.py
737 # TODO unify with commands.py
738 if not patchdir:
738 if not patchdir:
739 patchdir = self.path
739 patchdir = self.path
740 err = 0
740 err = 0
741 n = None
741 n = None
742 for patchname in series:
742 for patchname in series:
743 pushable, reason = self.pushable(patchname)
743 pushable, reason = self.pushable(patchname)
744 if not pushable:
744 if not pushable:
745 self.explainpushable(patchname, all_patches=True)
745 self.explainpushable(patchname, all_patches=True)
746 continue
746 continue
747 self.ui.status(_("applying %s\n") % patchname)
747 self.ui.status(_("applying %s\n") % patchname)
748 pf = os.path.join(patchdir, patchname)
748 pf = os.path.join(patchdir, patchname)
749
749
750 try:
750 try:
751 ph = patchheader(self.join(patchname), self.plainmode)
751 ph = patchheader(self.join(patchname), self.plainmode)
752 except IOError:
752 except IOError:
753 self.ui.warn(_("unable to read %s\n") % patchname)
753 self.ui.warn(_("unable to read %s\n") % patchname)
754 err = 1
754 err = 1
755 break
755 break
756
756
757 message = ph.message
757 message = ph.message
758 if not message:
758 if not message:
759 # The commit message should not be translated
759 # The commit message should not be translated
760 message = "imported patch %s\n" % patchname
760 message = "imported patch %s\n" % patchname
761 else:
761 else:
762 if list:
762 if list:
763 # The commit message should not be translated
763 # The commit message should not be translated
764 message.append("\nimported patch %s" % patchname)
764 message.append("\nimported patch %s" % patchname)
765 message = '\n'.join(message)
765 message = '\n'.join(message)
766
766
767 if ph.haspatch:
767 if ph.haspatch:
768 if tobackup:
768 if tobackup:
769 touched = patchmod.changedfiles(self.ui, repo, pf)
769 touched = patchmod.changedfiles(self.ui, repo, pf)
770 touched = set(touched) & tobackup
770 touched = set(touched) & tobackup
771 if touched and keepchanges:
771 if touched and keepchanges:
772 raise AbortNoCleanup(
772 raise AbortNoCleanup(
773 _("local changes found, refresh first"))
773 _("local changes found, refresh first"))
774 self.backup(repo, touched, copy=True)
774 self.backup(repo, touched, copy=True)
775 tobackup = tobackup - touched
775 tobackup = tobackup - touched
776 (patcherr, files, fuzz) = self.patch(repo, pf)
776 (patcherr, files, fuzz) = self.patch(repo, pf)
777 if all_files is not None:
777 if all_files is not None:
778 all_files.update(files)
778 all_files.update(files)
779 patcherr = not patcherr
779 patcherr = not patcherr
780 else:
780 else:
781 self.ui.warn(_("patch %s is empty\n") % patchname)
781 self.ui.warn(_("patch %s is empty\n") % patchname)
782 patcherr, files, fuzz = 0, [], 0
782 patcherr, files, fuzz = 0, [], 0
783
783
784 if merge and files:
784 if merge and files:
785 # Mark as removed/merged and update dirstate parent info
785 # Mark as removed/merged and update dirstate parent info
786 removed = []
786 removed = []
787 merged = []
787 merged = []
788 for f in files:
788 for f in files:
789 if os.path.lexists(repo.wjoin(f)):
789 if os.path.lexists(repo.wjoin(f)):
790 merged.append(f)
790 merged.append(f)
791 else:
791 else:
792 removed.append(f)
792 removed.append(f)
793 for f in removed:
793 for f in removed:
794 repo.dirstate.remove(f)
794 repo.dirstate.remove(f)
795 for f in merged:
795 for f in merged:
796 repo.dirstate.merge(f)
796 repo.dirstate.merge(f)
797 p1, p2 = repo.dirstate.parents()
797 p1, p2 = repo.dirstate.parents()
798 repo.setparents(p1, merge)
798 repo.setparents(p1, merge)
799
799
800 match = scmutil.matchfiles(repo, files or [])
800 match = scmutil.matchfiles(repo, files or [])
801 oldtip = repo['tip']
801 oldtip = repo['tip']
802 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
802 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
803 force=True)
803 force=True)
804 if repo['tip'] == oldtip:
804 if repo['tip'] == oldtip:
805 raise util.Abort(_("qpush exactly duplicates child changeset"))
805 raise util.Abort(_("qpush exactly duplicates child changeset"))
806 if n is None:
806 if n is None:
807 raise util.Abort(_("repository commit failed"))
807 raise util.Abort(_("repository commit failed"))
808
808
809 if update_status:
809 if update_status:
810 self.applied.append(statusentry(n, patchname))
810 self.applied.append(statusentry(n, patchname))
811
811
812 if patcherr:
812 if patcherr:
813 self.ui.warn(_("patch failed, rejects left in working dir\n"))
813 self.ui.warn(_("patch failed, rejects left in working dir\n"))
814 err = 2
814 err = 2
815 break
815 break
816
816
817 if fuzz and strict:
817 if fuzz and strict:
818 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
818 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
819 err = 3
819 err = 3
820 break
820 break
821 return (err, n)
821 return (err, n)
822
822
823 def _cleanup(self, patches, numrevs, keep=False):
823 def _cleanup(self, patches, numrevs, keep=False):
824 if not keep:
824 if not keep:
825 r = self.qrepo()
825 r = self.qrepo()
826 if r:
826 if r:
827 r[None].forget(patches)
827 r[None].forget(patches)
828 for p in patches:
828 for p in patches:
829 os.unlink(self.join(p))
829 os.unlink(self.join(p))
830
830
831 qfinished = []
831 qfinished = []
832 if numrevs:
832 if numrevs:
833 qfinished = self.applied[:numrevs]
833 qfinished = self.applied[:numrevs]
834 del self.applied[:numrevs]
834 del self.applied[:numrevs]
835 self.applieddirty = True
835 self.applieddirty = True
836
836
837 unknown = []
837 unknown = []
838
838
839 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
839 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
840 reverse=True):
840 reverse=True):
841 if i is not None:
841 if i is not None:
842 del self.fullseries[i]
842 del self.fullseries[i]
843 else:
843 else:
844 unknown.append(p)
844 unknown.append(p)
845
845
846 if unknown:
846 if unknown:
847 if numrevs:
847 if numrevs:
848 rev = dict((entry.name, entry.node) for entry in qfinished)
848 rev = dict((entry.name, entry.node) for entry in qfinished)
849 for p in unknown:
849 for p in unknown:
850 msg = _('revision %s refers to unknown patches: %s\n')
850 msg = _('revision %s refers to unknown patches: %s\n')
851 self.ui.warn(msg % (short(rev[p]), p))
851 self.ui.warn(msg % (short(rev[p]), p))
852 else:
852 else:
853 msg = _('unknown patches: %s\n')
853 msg = _('unknown patches: %s\n')
854 raise util.Abort(''.join(msg % p for p in unknown))
854 raise util.Abort(''.join(msg % p for p in unknown))
855
855
856 self.parseseries()
856 self.parseseries()
857 self.seriesdirty = True
857 self.seriesdirty = True
858 return [entry.node for entry in qfinished]
858 return [entry.node for entry in qfinished]
859
859
860 def _revpatches(self, repo, revs):
860 def _revpatches(self, repo, revs):
861 firstrev = repo[self.applied[0].node].rev()
861 firstrev = repo[self.applied[0].node].rev()
862 patches = []
862 patches = []
863 for i, rev in enumerate(revs):
863 for i, rev in enumerate(revs):
864
864
865 if rev < firstrev:
865 if rev < firstrev:
866 raise util.Abort(_('revision %d is not managed') % rev)
866 raise util.Abort(_('revision %d is not managed') % rev)
867
867
868 ctx = repo[rev]
868 ctx = repo[rev]
869 base = self.applied[i].node
869 base = self.applied[i].node
870 if ctx.node() != base:
870 if ctx.node() != base:
871 msg = _('cannot delete revision %d above applied patches')
871 msg = _('cannot delete revision %d above applied patches')
872 raise util.Abort(msg % rev)
872 raise util.Abort(msg % rev)
873
873
874 patch = self.applied[i].name
874 patch = self.applied[i].name
875 for fmt in ('[mq]: %s', 'imported patch %s'):
875 for fmt in ('[mq]: %s', 'imported patch %s'):
876 if ctx.description() == fmt % patch:
876 if ctx.description() == fmt % patch:
877 msg = _('patch %s finalized without changeset message\n')
877 msg = _('patch %s finalized without changeset message\n')
878 repo.ui.status(msg % patch)
878 repo.ui.status(msg % patch)
879 break
879 break
880
880
881 patches.append(patch)
881 patches.append(patch)
882 return patches
882 return patches
883
883
884 def finish(self, repo, revs):
884 def finish(self, repo, revs):
885 # Manually trigger phase computation to ensure phasedefaults is
885 # Manually trigger phase computation to ensure phasedefaults is
886 # executed before we remove the patches.
886 # executed before we remove the patches.
887 repo._phasecache
887 repo._phasecache
888 patches = self._revpatches(repo, sorted(revs))
888 patches = self._revpatches(repo, sorted(revs))
889 qfinished = self._cleanup(patches, len(patches))
889 qfinished = self._cleanup(patches, len(patches))
890 if qfinished and repo.ui.configbool('mq', 'secret', False):
890 if qfinished and repo.ui.configbool('mq', 'secret', False):
891 # only use this logic when the secret option is added
891 # only use this logic when the secret option is added
892 oldqbase = repo[qfinished[0]]
892 oldqbase = repo[qfinished[0]]
893 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
893 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
894 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
894 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
895 phases.advanceboundary(repo, tphase, qfinished)
895 phases.advanceboundary(repo, tphase, qfinished)
896
896
897 def delete(self, repo, patches, opts):
897 def delete(self, repo, patches, opts):
898 if not patches and not opts.get('rev'):
898 if not patches and not opts.get('rev'):
899 raise util.Abort(_('qdelete requires at least one revision or '
899 raise util.Abort(_('qdelete requires at least one revision or '
900 'patch name'))
900 'patch name'))
901
901
902 realpatches = []
902 realpatches = []
903 for patch in patches:
903 for patch in patches:
904 patch = self.lookup(patch, strict=True)
904 patch = self.lookup(patch, strict=True)
905 info = self.isapplied(patch)
905 info = self.isapplied(patch)
906 if info:
906 if info:
907 raise util.Abort(_("cannot delete applied patch %s") % patch)
907 raise util.Abort(_("cannot delete applied patch %s") % patch)
908 if patch not in self.series:
908 if patch not in self.series:
909 raise util.Abort(_("patch %s not in series file") % patch)
909 raise util.Abort(_("patch %s not in series file") % patch)
910 if patch not in realpatches:
910 if patch not in realpatches:
911 realpatches.append(patch)
911 realpatches.append(patch)
912
912
913 numrevs = 0
913 numrevs = 0
914 if opts.get('rev'):
914 if opts.get('rev'):
915 if not self.applied:
915 if not self.applied:
916 raise util.Abort(_('no patches applied'))
916 raise util.Abort(_('no patches applied'))
917 revs = scmutil.revrange(repo, opts.get('rev'))
917 revs = scmutil.revrange(repo, opts.get('rev'))
918 if len(revs) > 1 and revs[0] > revs[1]:
918 if len(revs) > 1 and revs[0] > revs[1]:
919 revs.reverse()
919 revs.reverse()
920 revpatches = self._revpatches(repo, revs)
920 revpatches = self._revpatches(repo, revs)
921 realpatches += revpatches
921 realpatches += revpatches
922 numrevs = len(revpatches)
922 numrevs = len(revpatches)
923
923
924 self._cleanup(realpatches, numrevs, opts.get('keep'))
924 self._cleanup(realpatches, numrevs, opts.get('keep'))
925
925
926 def checktoppatch(self, repo):
926 def checktoppatch(self, repo):
927 if self.applied:
927 if self.applied:
928 top = self.applied[-1].node
928 top = self.applied[-1].node
929 patch = self.applied[-1].name
929 patch = self.applied[-1].name
930 pp = repo.dirstate.parents()
930 pp = repo.dirstate.parents()
931 if top not in pp:
931 if top not in pp:
932 raise util.Abort(_("working directory revision is not qtip"))
932 raise util.Abort(_("working directory revision is not qtip"))
933 return top, patch
933 return top, patch
934 return None, None
934 return None, None
935
935
936 def checksubstate(self, repo, baserev=None):
936 def checksubstate(self, repo, baserev=None):
937 '''return list of subrepos at a different revision than substate.
937 '''return list of subrepos at a different revision than substate.
938 Abort if any subrepos have uncommitted changes.'''
938 Abort if any subrepos have uncommitted changes.'''
939 inclsubs = []
939 inclsubs = []
940 wctx = repo[None]
940 wctx = repo[None]
941 if baserev:
941 if baserev:
942 bctx = repo[baserev]
942 bctx = repo[baserev]
943 else:
943 else:
944 bctx = wctx.parents()[0]
944 bctx = wctx.parents()[0]
945 for s in wctx.substate:
945 for s in wctx.substate:
946 if wctx.sub(s).dirty(True):
946 if wctx.sub(s).dirty(True):
947 raise util.Abort(
947 raise util.Abort(
948 _("uncommitted changes in subrepository %s") % s)
948 _("uncommitted changes in subrepository %s") % s)
949 elif s not in bctx.substate or bctx.sub(s).dirty():
949 elif s not in bctx.substate or bctx.sub(s).dirty():
950 inclsubs.append(s)
950 inclsubs.append(s)
951 return inclsubs
951 return inclsubs
952
952
953 def putsubstate2changes(self, substatestate, changes):
953 def putsubstate2changes(self, substatestate, changes):
954 for files in changes[:3]:
954 for files in changes[:3]:
955 if '.hgsubstate' in files:
955 if '.hgsubstate' in files:
956 return # already listed up
956 return # already listed up
957 # not yet listed up
957 # not yet listed up
958 if substatestate in 'a?':
958 if substatestate in 'a?':
959 changes[1].append('.hgsubstate')
959 changes[1].append('.hgsubstate')
960 elif substatestate in 'r':
960 elif substatestate in 'r':
961 changes[2].append('.hgsubstate')
961 changes[2].append('.hgsubstate')
962 else: # modified
962 else: # modified
963 changes[0].append('.hgsubstate')
963 changes[0].append('.hgsubstate')
964
964
965 def localchangesfound(self, refresh=True):
965 def localchangesfound(self, refresh=True):
966 if refresh:
966 if refresh:
967 raise util.Abort(_("local changes found, refresh first"))
967 raise util.Abort(_("local changes found, refresh first"))
968 else:
968 else:
969 raise util.Abort(_("local changes found"))
969 raise util.Abort(_("local changes found"))
970
970
971 def checklocalchanges(self, repo, force=False, refresh=True):
971 def checklocalchanges(self, repo, force=False, refresh=True):
972 m, a, r, d = repo.status()[:4]
972 m, a, r, d = repo.status()[:4]
973 if (m or a or r or d) and not force:
973 if (m or a or r or d) and not force:
974 self.localchangesfound(refresh)
974 self.localchangesfound(refresh)
975 return m, a, r, d
975 return m, a, r, d
976
976
977 _reserved = ('series', 'status', 'guards', '.', '..')
977 _reserved = ('series', 'status', 'guards', '.', '..')
978 def checkreservedname(self, name):
978 def checkreservedname(self, name):
979 if name in self._reserved:
979 if name in self._reserved:
980 raise util.Abort(_('"%s" cannot be used as the name of a patch')
980 raise util.Abort(_('"%s" cannot be used as the name of a patch')
981 % name)
981 % name)
982 for prefix in ('.hg', '.mq'):
982 for prefix in ('.hg', '.mq'):
983 if name.startswith(prefix):
983 if name.startswith(prefix):
984 raise util.Abort(_('patch name cannot begin with "%s"')
984 raise util.Abort(_('patch name cannot begin with "%s"')
985 % prefix)
985 % prefix)
986 for c in ('#', ':'):
986 for c in ('#', ':'):
987 if c in name:
987 if c in name:
988 raise util.Abort(_('"%s" cannot be used in the name of a patch')
988 raise util.Abort(_('"%s" cannot be used in the name of a patch')
989 % c)
989 % c)
990
990
991 def checkpatchname(self, name, force=False):
991 def checkpatchname(self, name, force=False):
992 self.checkreservedname(name)
992 self.checkreservedname(name)
993 if not force and os.path.exists(self.join(name)):
993 if not force and os.path.exists(self.join(name)):
994 if os.path.isdir(self.join(name)):
994 if os.path.isdir(self.join(name)):
995 raise util.Abort(_('"%s" already exists as a directory')
995 raise util.Abort(_('"%s" already exists as a directory')
996 % name)
996 % name)
997 else:
997 else:
998 raise util.Abort(_('patch "%s" already exists') % name)
998 raise util.Abort(_('patch "%s" already exists') % name)
999
999
1000 def checkkeepchanges(self, keepchanges, force):
1000 def checkkeepchanges(self, keepchanges, force):
1001 if force and keepchanges:
1001 if force and keepchanges:
1002 raise util.Abort(_('cannot use both --force and --keep-changes'))
1002 raise util.Abort(_('cannot use both --force and --keep-changes'))
1003
1003
1004 def new(self, repo, patchfn, *pats, **opts):
1004 def new(self, repo, patchfn, *pats, **opts):
1005 """options:
1005 """options:
1006 msg: a string or a no-argument function returning a string
1006 msg: a string or a no-argument function returning a string
1007 """
1007 """
1008 msg = opts.get('msg')
1008 msg = opts.get('msg')
1009 user = opts.get('user')
1009 user = opts.get('user')
1010 date = opts.get('date')
1010 date = opts.get('date')
1011 if date:
1011 if date:
1012 date = util.parsedate(date)
1012 date = util.parsedate(date)
1013 diffopts = self.diffopts({'git': opts.get('git')})
1013 diffopts = self.diffopts({'git': opts.get('git')})
1014 if opts.get('checkname', True):
1014 if opts.get('checkname', True):
1015 self.checkpatchname(patchfn)
1015 self.checkpatchname(patchfn)
1016 inclsubs = self.checksubstate(repo)
1016 inclsubs = self.checksubstate(repo)
1017 if inclsubs:
1017 if inclsubs:
1018 inclsubs.append('.hgsubstate')
1018 inclsubs.append('.hgsubstate')
1019 substatestate = repo.dirstate['.hgsubstate']
1019 substatestate = repo.dirstate['.hgsubstate']
1020 if opts.get('include') or opts.get('exclude') or pats:
1020 if opts.get('include') or opts.get('exclude') or pats:
1021 if inclsubs:
1021 if inclsubs:
1022 pats = list(pats or []) + inclsubs
1022 pats = list(pats or []) + inclsubs
1023 match = scmutil.match(repo[None], pats, opts)
1023 match = scmutil.match(repo[None], pats, opts)
1024 # detect missing files in pats
1024 # detect missing files in pats
1025 def badfn(f, msg):
1025 def badfn(f, msg):
1026 if f != '.hgsubstate': # .hgsubstate is auto-created
1026 if f != '.hgsubstate': # .hgsubstate is auto-created
1027 raise util.Abort('%s: %s' % (f, msg))
1027 raise util.Abort('%s: %s' % (f, msg))
1028 match.bad = badfn
1028 match.bad = badfn
1029 changes = repo.status(match=match)
1029 changes = repo.status(match=match)
1030 m, a, r, d = changes[:4]
1030 m, a, r, d = changes[:4]
1031 else:
1031 else:
1032 changes = self.checklocalchanges(repo, force=True)
1032 changes = self.checklocalchanges(repo, force=True)
1033 m, a, r, d = changes
1033 m, a, r, d = changes
1034 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
1034 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
1035 if len(repo[None].parents()) > 1:
1035 if len(repo[None].parents()) > 1:
1036 raise util.Abort(_('cannot manage merge changesets'))
1036 raise util.Abort(_('cannot manage merge changesets'))
1037 commitfiles = m + a + r
1037 commitfiles = m + a + r
1038 self.checktoppatch(repo)
1038 self.checktoppatch(repo)
1039 insert = self.fullseriesend()
1039 insert = self.fullseriesend()
1040 wlock = repo.wlock()
1040 wlock = repo.wlock()
1041 try:
1041 try:
1042 try:
1042 try:
1043 # if patch file write fails, abort early
1043 # if patch file write fails, abort early
1044 p = self.opener(patchfn, "w")
1044 p = self.opener(patchfn, "w")
1045 except IOError, e:
1045 except IOError, e:
1046 raise util.Abort(_('cannot write patch "%s": %s')
1046 raise util.Abort(_('cannot write patch "%s": %s')
1047 % (patchfn, e.strerror))
1047 % (patchfn, e.strerror))
1048 try:
1048 try:
1049 if self.plainmode:
1049 if self.plainmode:
1050 if user:
1050 if user:
1051 p.write("From: " + user + "\n")
1051 p.write("From: " + user + "\n")
1052 if not date:
1052 if not date:
1053 p.write("\n")
1053 p.write("\n")
1054 if date:
1054 if date:
1055 p.write("Date: %d %d\n\n" % date)
1055 p.write("Date: %d %d\n\n" % date)
1056 else:
1056 else:
1057 p.write("# HG changeset patch\n")
1057 p.write("# HG changeset patch\n")
1058 p.write("# Parent "
1058 p.write("# Parent "
1059 + hex(repo[None].p1().node()) + "\n")
1059 + hex(repo[None].p1().node()) + "\n")
1060 if user:
1060 if user:
1061 p.write("# User " + user + "\n")
1061 p.write("# User " + user + "\n")
1062 if date:
1062 if date:
1063 p.write("# Date %s %s\n\n" % date)
1063 p.write("# Date %s %s\n\n" % date)
1064 if util.safehasattr(msg, '__call__'):
1064 if util.safehasattr(msg, '__call__'):
1065 msg = msg()
1065 msg = msg()
1066 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
1066 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
1067 n = newcommit(repo, None, commitmsg, user, date, match=match,
1067 n = newcommit(repo, None, commitmsg, user, date, match=match,
1068 force=True)
1068 force=True)
1069 if n is None:
1069 if n is None:
1070 raise util.Abort(_("repo commit failed"))
1070 raise util.Abort(_("repo commit failed"))
1071 try:
1071 try:
1072 self.fullseries[insert:insert] = [patchfn]
1072 self.fullseries[insert:insert] = [patchfn]
1073 self.applied.append(statusentry(n, patchfn))
1073 self.applied.append(statusentry(n, patchfn))
1074 self.parseseries()
1074 self.parseseries()
1075 self.seriesdirty = True
1075 self.seriesdirty = True
1076 self.applieddirty = True
1076 self.applieddirty = True
1077 if msg:
1077 if msg:
1078 msg = msg + "\n\n"
1078 msg = msg + "\n\n"
1079 p.write(msg)
1079 p.write(msg)
1080 if commitfiles:
1080 if commitfiles:
1081 parent = self.qparents(repo, n)
1081 parent = self.qparents(repo, n)
1082 if inclsubs:
1082 if inclsubs:
1083 self.putsubstate2changes(substatestate, changes)
1083 self.putsubstate2changes(substatestate, changes)
1084 chunks = patchmod.diff(repo, node1=parent, node2=n,
1084 chunks = patchmod.diff(repo, node1=parent, node2=n,
1085 changes=changes, opts=diffopts)
1085 changes=changes, opts=diffopts)
1086 for chunk in chunks:
1086 for chunk in chunks:
1087 p.write(chunk)
1087 p.write(chunk)
1088 p.close()
1088 p.close()
1089 r = self.qrepo()
1089 r = self.qrepo()
1090 if r:
1090 if r:
1091 r[None].add([patchfn])
1091 r[None].add([patchfn])
1092 except: # re-raises
1092 except: # re-raises
1093 repo.rollback()
1093 repo.rollback()
1094 raise
1094 raise
1095 except Exception:
1095 except Exception:
1096 patchpath = self.join(patchfn)
1096 patchpath = self.join(patchfn)
1097 try:
1097 try:
1098 os.unlink(patchpath)
1098 os.unlink(patchpath)
1099 except OSError:
1099 except OSError:
1100 self.ui.warn(_('error unlinking %s\n') % patchpath)
1100 self.ui.warn(_('error unlinking %s\n') % patchpath)
1101 raise
1101 raise
1102 self.removeundo(repo)
1102 self.removeundo(repo)
1103 finally:
1103 finally:
1104 release(wlock)
1104 release(wlock)
1105
1105
1106 def strip(self, repo, revs, update=True, backup="all", force=None):
1106 def strip(self, repo, revs, update=True, backup="all", force=None):
1107 wlock = lock = None
1107 wlock = lock = None
1108 try:
1108 try:
1109 wlock = repo.wlock()
1109 wlock = repo.wlock()
1110 lock = repo.lock()
1110 lock = repo.lock()
1111
1111
1112 if update:
1112 if update:
1113 self.checklocalchanges(repo, force=force, refresh=False)
1113 self.checklocalchanges(repo, force=force, refresh=False)
1114 urev = self.qparents(repo, revs[0])
1114 urev = self.qparents(repo, revs[0])
1115 hg.clean(repo, urev)
1115 hg.clean(repo, urev)
1116 repo.dirstate.write()
1116 repo.dirstate.write()
1117
1117
1118 repair.strip(self.ui, repo, revs, backup)
1118 repair.strip(self.ui, repo, revs, backup)
1119 finally:
1119 finally:
1120 release(lock, wlock)
1120 release(lock, wlock)
1121
1121
1122 def isapplied(self, patch):
1122 def isapplied(self, patch):
1123 """returns (index, rev, patch)"""
1123 """returns (index, rev, patch)"""
1124 for i, a in enumerate(self.applied):
1124 for i, a in enumerate(self.applied):
1125 if a.name == patch:
1125 if a.name == patch:
1126 return (i, a.node, a.name)
1126 return (i, a.node, a.name)
1127 return None
1127 return None
1128
1128
1129 # if the exact patch name does not exist, we try a few
1129 # if the exact patch name does not exist, we try a few
1130 # variations. If strict is passed, we try only #1
1130 # variations. If strict is passed, we try only #1
1131 #
1131 #
1132 # 1) a number (as string) to indicate an offset in the series file
1132 # 1) a number (as string) to indicate an offset in the series file
1133 # 2) a unique substring of the patch name was given
1133 # 2) a unique substring of the patch name was given
1134 # 3) patchname[-+]num to indicate an offset in the series file
1134 # 3) patchname[-+]num to indicate an offset in the series file
1135 def lookup(self, patch, strict=False):
1135 def lookup(self, patch, strict=False):
1136 def partialname(s):
1136 def partialname(s):
1137 if s in self.series:
1137 if s in self.series:
1138 return s
1138 return s
1139 matches = [x for x in self.series if s in x]
1139 matches = [x for x in self.series if s in x]
1140 if len(matches) > 1:
1140 if len(matches) > 1:
1141 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1141 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1142 for m in matches:
1142 for m in matches:
1143 self.ui.warn(' %s\n' % m)
1143 self.ui.warn(' %s\n' % m)
1144 return None
1144 return None
1145 if matches:
1145 if matches:
1146 return matches[0]
1146 return matches[0]
1147 if self.series and self.applied:
1147 if self.series and self.applied:
1148 if s == 'qtip':
1148 if s == 'qtip':
1149 return self.series[self.seriesend(True)-1]
1149 return self.series[self.seriesend(True)-1]
1150 if s == 'qbase':
1150 if s == 'qbase':
1151 return self.series[0]
1151 return self.series[0]
1152 return None
1152 return None
1153
1153
1154 if patch in self.series:
1154 if patch in self.series:
1155 return patch
1155 return patch
1156
1156
1157 if not os.path.isfile(self.join(patch)):
1157 if not os.path.isfile(self.join(patch)):
1158 try:
1158 try:
1159 sno = int(patch)
1159 sno = int(patch)
1160 except (ValueError, OverflowError):
1160 except (ValueError, OverflowError):
1161 pass
1161 pass
1162 else:
1162 else:
1163 if -len(self.series) <= sno < len(self.series):
1163 if -len(self.series) <= sno < len(self.series):
1164 return self.series[sno]
1164 return self.series[sno]
1165
1165
1166 if not strict:
1166 if not strict:
1167 res = partialname(patch)
1167 res = partialname(patch)
1168 if res:
1168 if res:
1169 return res
1169 return res
1170 minus = patch.rfind('-')
1170 minus = patch.rfind('-')
1171 if minus >= 0:
1171 if minus >= 0:
1172 res = partialname(patch[:minus])
1172 res = partialname(patch[:minus])
1173 if res:
1173 if res:
1174 i = self.series.index(res)
1174 i = self.series.index(res)
1175 try:
1175 try:
1176 off = int(patch[minus + 1:] or 1)
1176 off = int(patch[minus + 1:] or 1)
1177 except (ValueError, OverflowError):
1177 except (ValueError, OverflowError):
1178 pass
1178 pass
1179 else:
1179 else:
1180 if i - off >= 0:
1180 if i - off >= 0:
1181 return self.series[i - off]
1181 return self.series[i - off]
1182 plus = patch.rfind('+')
1182 plus = patch.rfind('+')
1183 if plus >= 0:
1183 if plus >= 0:
1184 res = partialname(patch[:plus])
1184 res = partialname(patch[:plus])
1185 if res:
1185 if res:
1186 i = self.series.index(res)
1186 i = self.series.index(res)
1187 try:
1187 try:
1188 off = int(patch[plus + 1:] or 1)
1188 off = int(patch[plus + 1:] or 1)
1189 except (ValueError, OverflowError):
1189 except (ValueError, OverflowError):
1190 pass
1190 pass
1191 else:
1191 else:
1192 if i + off < len(self.series):
1192 if i + off < len(self.series):
1193 return self.series[i + off]
1193 return self.series[i + off]
1194 raise util.Abort(_("patch %s not in series") % patch)
1194 raise util.Abort(_("patch %s not in series") % patch)
1195
1195
1196 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1196 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1197 all=False, move=False, exact=False, nobackup=False,
1197 all=False, move=False, exact=False, nobackup=False,
1198 keepchanges=False):
1198 keepchanges=False):
1199 self.checkkeepchanges(keepchanges, force)
1199 self.checkkeepchanges(keepchanges, force)
1200 diffopts = self.diffopts()
1200 diffopts = self.diffopts()
1201 wlock = repo.wlock()
1201 wlock = repo.wlock()
1202 try:
1202 try:
1203 heads = []
1203 heads = []
1204 for b, ls in repo.branchmap().iteritems():
1204 for b, ls in repo.branchmap().iteritems():
1205 heads += ls
1205 heads += ls
1206 if not heads:
1206 if not heads:
1207 heads = [nullid]
1207 heads = [nullid]
1208 if repo.dirstate.p1() not in heads and not exact:
1208 if repo.dirstate.p1() not in heads and not exact:
1209 self.ui.status(_("(working directory not at a head)\n"))
1209 self.ui.status(_("(working directory not at a head)\n"))
1210
1210
1211 if not self.series:
1211 if not self.series:
1212 self.ui.warn(_('no patches in series\n'))
1212 self.ui.warn(_('no patches in series\n'))
1213 return 0
1213 return 0
1214
1214
1215 # Suppose our series file is: A B C and the current 'top'
1215 # Suppose our series file is: A B C and the current 'top'
1216 # patch is B. qpush C should be performed (moving forward)
1216 # patch is B. qpush C should be performed (moving forward)
1217 # qpush B is a NOP (no change) qpush A is an error (can't
1217 # qpush B is a NOP (no change) qpush A is an error (can't
1218 # go backwards with qpush)
1218 # go backwards with qpush)
1219 if patch:
1219 if patch:
1220 patch = self.lookup(patch)
1220 patch = self.lookup(patch)
1221 info = self.isapplied(patch)
1221 info = self.isapplied(patch)
1222 if info and info[0] >= len(self.applied) - 1:
1222 if info and info[0] >= len(self.applied) - 1:
1223 self.ui.warn(
1223 self.ui.warn(
1224 _('qpush: %s is already at the top\n') % patch)
1224 _('qpush: %s is already at the top\n') % patch)
1225 return 0
1225 return 0
1226
1226
1227 pushable, reason = self.pushable(patch)
1227 pushable, reason = self.pushable(patch)
1228 if pushable:
1228 if pushable:
1229 if self.series.index(patch) < self.seriesend():
1229 if self.series.index(patch) < self.seriesend():
1230 raise util.Abort(
1230 raise util.Abort(
1231 _("cannot push to a previous patch: %s") % patch)
1231 _("cannot push to a previous patch: %s") % patch)
1232 else:
1232 else:
1233 if reason:
1233 if reason:
1234 reason = _('guarded by %s') % reason
1234 reason = _('guarded by %s') % reason
1235 else:
1235 else:
1236 reason = _('no matching guards')
1236 reason = _('no matching guards')
1237 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1237 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1238 return 1
1238 return 1
1239 elif all:
1239 elif all:
1240 patch = self.series[-1]
1240 patch = self.series[-1]
1241 if self.isapplied(patch):
1241 if self.isapplied(patch):
1242 self.ui.warn(_('all patches are currently applied\n'))
1242 self.ui.warn(_('all patches are currently applied\n'))
1243 return 0
1243 return 0
1244
1244
1245 # Following the above example, starting at 'top' of B:
1245 # Following the above example, starting at 'top' of B:
1246 # qpush should be performed (pushes C), but a subsequent
1246 # qpush should be performed (pushes C), but a subsequent
1247 # qpush without an argument is an error (nothing to
1247 # qpush without an argument is an error (nothing to
1248 # apply). This allows a loop of "...while hg qpush..." to
1248 # apply). This allows a loop of "...while hg qpush..." to
1249 # work as it detects an error when done
1249 # work as it detects an error when done
1250 start = self.seriesend()
1250 start = self.seriesend()
1251 if start == len(self.series):
1251 if start == len(self.series):
1252 self.ui.warn(_('patch series already fully applied\n'))
1252 self.ui.warn(_('patch series already fully applied\n'))
1253 return 1
1253 return 1
1254 if not force and not keepchanges:
1254 if not force and not keepchanges:
1255 self.checklocalchanges(repo, refresh=self.applied)
1255 self.checklocalchanges(repo, refresh=self.applied)
1256
1256
1257 if exact:
1257 if exact:
1258 if keepchanges:
1258 if keepchanges:
1259 raise util.Abort(
1259 raise util.Abort(
1260 _("cannot use --exact and --keep-changes together"))
1260 _("cannot use --exact and --keep-changes together"))
1261 if move:
1261 if move:
1262 raise util.Abort(_('cannot use --exact and --move '
1262 raise util.Abort(_('cannot use --exact and --move '
1263 'together'))
1263 'together'))
1264 if self.applied:
1264 if self.applied:
1265 raise util.Abort(_('cannot push --exact with applied '
1265 raise util.Abort(_('cannot push --exact with applied '
1266 'patches'))
1266 'patches'))
1267 root = self.series[start]
1267 root = self.series[start]
1268 target = patchheader(self.join(root), self.plainmode).parent
1268 target = patchheader(self.join(root), self.plainmode).parent
1269 if not target:
1269 if not target:
1270 raise util.Abort(
1270 raise util.Abort(
1271 _("%s does not have a parent recorded") % root)
1271 _("%s does not have a parent recorded") % root)
1272 if not repo[target] == repo['.']:
1272 if not repo[target] == repo['.']:
1273 hg.update(repo, target)
1273 hg.update(repo, target)
1274
1274
1275 if move:
1275 if move:
1276 if not patch:
1276 if not patch:
1277 raise util.Abort(_("please specify the patch to move"))
1277 raise util.Abort(_("please specify the patch to move"))
1278 for fullstart, rpn in enumerate(self.fullseries):
1278 for fullstart, rpn in enumerate(self.fullseries):
1279 # strip markers for patch guards
1279 # strip markers for patch guards
1280 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1280 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1281 break
1281 break
1282 for i, rpn in enumerate(self.fullseries[fullstart:]):
1282 for i, rpn in enumerate(self.fullseries[fullstart:]):
1283 # strip markers for patch guards
1283 # strip markers for patch guards
1284 if self.guard_re.split(rpn, 1)[0] == patch:
1284 if self.guard_re.split(rpn, 1)[0] == patch:
1285 break
1285 break
1286 index = fullstart + i
1286 index = fullstart + i
1287 assert index < len(self.fullseries)
1287 assert index < len(self.fullseries)
1288 fullpatch = self.fullseries[index]
1288 fullpatch = self.fullseries[index]
1289 del self.fullseries[index]
1289 del self.fullseries[index]
1290 self.fullseries.insert(fullstart, fullpatch)
1290 self.fullseries.insert(fullstart, fullpatch)
1291 self.parseseries()
1291 self.parseseries()
1292 self.seriesdirty = True
1292 self.seriesdirty = True
1293
1293
1294 self.applieddirty = True
1294 self.applieddirty = True
1295 if start > 0:
1295 if start > 0:
1296 self.checktoppatch(repo)
1296 self.checktoppatch(repo)
1297 if not patch:
1297 if not patch:
1298 patch = self.series[start]
1298 patch = self.series[start]
1299 end = start + 1
1299 end = start + 1
1300 else:
1300 else:
1301 end = self.series.index(patch, start) + 1
1301 end = self.series.index(patch, start) + 1
1302
1302
1303 tobackup = set()
1303 tobackup = set()
1304 if (not nobackup and force) or keepchanges:
1304 if (not nobackup and force) or keepchanges:
1305 m, a, r, d = self.checklocalchanges(repo, force=True)
1305 m, a, r, d = self.checklocalchanges(repo, force=True)
1306 if keepchanges:
1306 if keepchanges:
1307 tobackup.update(m + a + r + d)
1307 tobackup.update(m + a + r + d)
1308 else:
1308 else:
1309 tobackup.update(m + a)
1309 tobackup.update(m + a)
1310
1310
1311 s = self.series[start:end]
1311 s = self.series[start:end]
1312 all_files = set()
1312 all_files = set()
1313 try:
1313 try:
1314 if mergeq:
1314 if mergeq:
1315 ret = self.mergepatch(repo, mergeq, s, diffopts)
1315 ret = self.mergepatch(repo, mergeq, s, diffopts)
1316 else:
1316 else:
1317 ret = self.apply(repo, s, list, all_files=all_files,
1317 ret = self.apply(repo, s, list, all_files=all_files,
1318 tobackup=tobackup, keepchanges=keepchanges)
1318 tobackup=tobackup, keepchanges=keepchanges)
1319 except: # re-raises
1319 except: # re-raises
1320 self.ui.warn(_('cleaning up working directory...'))
1320 self.ui.warn(_('cleaning up working directory...'))
1321 node = repo.dirstate.p1()
1321 node = repo.dirstate.p1()
1322 hg.revert(repo, node, None)
1322 hg.revert(repo, node, None)
1323 # only remove unknown files that we know we touched or
1323 # only remove unknown files that we know we touched or
1324 # created while patching
1324 # created while patching
1325 for f in all_files:
1325 for f in all_files:
1326 if f not in repo.dirstate:
1326 if f not in repo.dirstate:
1327 try:
1327 try:
1328 util.unlinkpath(repo.wjoin(f))
1328 util.unlinkpath(repo.wjoin(f))
1329 except OSError, inst:
1329 except OSError, inst:
1330 if inst.errno != errno.ENOENT:
1330 if inst.errno != errno.ENOENT:
1331 raise
1331 raise
1332 self.ui.warn(_('done\n'))
1332 self.ui.warn(_('done\n'))
1333 raise
1333 raise
1334
1334
1335 if not self.applied:
1335 if not self.applied:
1336 return ret[0]
1336 return ret[0]
1337 top = self.applied[-1].name
1337 top = self.applied[-1].name
1338 if ret[0] and ret[0] > 1:
1338 if ret[0] and ret[0] > 1:
1339 msg = _("errors during apply, please fix and refresh %s\n")
1339 msg = _("errors during apply, please fix and refresh %s\n")
1340 self.ui.write(msg % top)
1340 self.ui.write(msg % top)
1341 else:
1341 else:
1342 self.ui.write(_("now at: %s\n") % top)
1342 self.ui.write(_("now at: %s\n") % top)
1343 return ret[0]
1343 return ret[0]
1344
1344
1345 finally:
1345 finally:
1346 wlock.release()
1346 wlock.release()
1347
1347
1348 def pop(self, repo, patch=None, force=False, update=True, all=False,
1348 def pop(self, repo, patch=None, force=False, update=True, all=False,
1349 nobackup=False, keepchanges=False):
1349 nobackup=False, keepchanges=False):
1350 self.checkkeepchanges(keepchanges, force)
1350 self.checkkeepchanges(keepchanges, force)
1351 wlock = repo.wlock()
1351 wlock = repo.wlock()
1352 try:
1352 try:
1353 if patch:
1353 if patch:
1354 # index, rev, patch
1354 # index, rev, patch
1355 info = self.isapplied(patch)
1355 info = self.isapplied(patch)
1356 if not info:
1356 if not info:
1357 patch = self.lookup(patch)
1357 patch = self.lookup(patch)
1358 info = self.isapplied(patch)
1358 info = self.isapplied(patch)
1359 if not info:
1359 if not info:
1360 raise util.Abort(_("patch %s is not applied") % patch)
1360 raise util.Abort(_("patch %s is not applied") % patch)
1361
1361
1362 if not self.applied:
1362 if not self.applied:
1363 # Allow qpop -a to work repeatedly,
1363 # Allow qpop -a to work repeatedly,
1364 # but not qpop without an argument
1364 # but not qpop without an argument
1365 self.ui.warn(_("no patches applied\n"))
1365 self.ui.warn(_("no patches applied\n"))
1366 return not all
1366 return not all
1367
1367
1368 if all:
1368 if all:
1369 start = 0
1369 start = 0
1370 elif patch:
1370 elif patch:
1371 start = info[0] + 1
1371 start = info[0] + 1
1372 else:
1372 else:
1373 start = len(self.applied) - 1
1373 start = len(self.applied) - 1
1374
1374
1375 if start >= len(self.applied):
1375 if start >= len(self.applied):
1376 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1376 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1377 return
1377 return
1378
1378
1379 if not update:
1379 if not update:
1380 parents = repo.dirstate.parents()
1380 parents = repo.dirstate.parents()
1381 rr = [x.node for x in self.applied]
1381 rr = [x.node for x in self.applied]
1382 for p in parents:
1382 for p in parents:
1383 if p in rr:
1383 if p in rr:
1384 self.ui.warn(_("qpop: forcing dirstate update\n"))
1384 self.ui.warn(_("qpop: forcing dirstate update\n"))
1385 update = True
1385 update = True
1386 else:
1386 else:
1387 parents = [p.node() for p in repo[None].parents()]
1387 parents = [p.node() for p in repo[None].parents()]
1388 needupdate = False
1388 needupdate = False
1389 for entry in self.applied[start:]:
1389 for entry in self.applied[start:]:
1390 if entry.node in parents:
1390 if entry.node in parents:
1391 needupdate = True
1391 needupdate = True
1392 break
1392 break
1393 update = needupdate
1393 update = needupdate
1394
1394
1395 tobackup = set()
1395 tobackup = set()
1396 if update:
1396 if update:
1397 m, a, r, d = self.checklocalchanges(
1397 m, a, r, d = self.checklocalchanges(
1398 repo, force=force or keepchanges)
1398 repo, force=force or keepchanges)
1399 if force:
1399 if force:
1400 if not nobackup:
1400 if not nobackup:
1401 tobackup.update(m + a)
1401 tobackup.update(m + a)
1402 elif keepchanges:
1402 elif keepchanges:
1403 tobackup.update(m + a + r + d)
1403 tobackup.update(m + a + r + d)
1404
1404
1405 self.applieddirty = True
1405 self.applieddirty = True
1406 end = len(self.applied)
1406 end = len(self.applied)
1407 rev = self.applied[start].node
1407 rev = self.applied[start].node
1408 if update:
1408 if update:
1409 top = self.checktoppatch(repo)[0]
1409 top = self.checktoppatch(repo)[0]
1410
1410
1411 try:
1411 try:
1412 heads = repo.changelog.heads(rev)
1412 heads = repo.changelog.heads(rev)
1413 except error.LookupError:
1413 except error.LookupError:
1414 node = short(rev)
1414 node = short(rev)
1415 raise util.Abort(_('trying to pop unknown node %s') % node)
1415 raise util.Abort(_('trying to pop unknown node %s') % node)
1416
1416
1417 if heads != [self.applied[-1].node]:
1417 if heads != [self.applied[-1].node]:
1418 raise util.Abort(_("popping would remove a revision not "
1418 raise util.Abort(_("popping would remove a revision not "
1419 "managed by this patch queue"))
1419 "managed by this patch queue"))
1420 if not repo[self.applied[-1].node].mutable():
1420 if not repo[self.applied[-1].node].mutable():
1421 raise util.Abort(
1421 raise util.Abort(
1422 _("popping would remove an immutable revision"),
1422 _("popping would remove an immutable revision"),
1423 hint=_('see "hg help phases" for details'))
1423 hint=_('see "hg help phases" for details'))
1424
1424
1425 # we know there are no local changes, so we can make a simplified
1425 # we know there are no local changes, so we can make a simplified
1426 # form of hg.update.
1426 # form of hg.update.
1427 if update:
1427 if update:
1428 qp = self.qparents(repo, rev)
1428 qp = self.qparents(repo, rev)
1429 ctx = repo[qp]
1429 ctx = repo[qp]
1430 m, a, r, d = repo.status(qp, top)[:4]
1430 m, a, r, d = repo.status(qp, top)[:4]
1431 if d:
1431 if d:
1432 raise util.Abort(_("deletions found between repo revs"))
1432 raise util.Abort(_("deletions found between repo revs"))
1433
1433
1434 tobackup = set(a + m + r) & tobackup
1434 tobackup = set(a + m + r) & tobackup
1435 if keepchanges and tobackup:
1435 if keepchanges and tobackup:
1436 self.localchangesfound()
1436 self.localchangesfound()
1437 self.backup(repo, tobackup)
1437 self.backup(repo, tobackup)
1438
1438
1439 for f in a:
1439 for f in a:
1440 try:
1440 try:
1441 util.unlinkpath(repo.wjoin(f))
1441 util.unlinkpath(repo.wjoin(f))
1442 except OSError, e:
1442 except OSError, e:
1443 if e.errno != errno.ENOENT:
1443 if e.errno != errno.ENOENT:
1444 raise
1444 raise
1445 repo.dirstate.drop(f)
1445 repo.dirstate.drop(f)
1446 for f in m + r:
1446 for f in m + r:
1447 fctx = ctx[f]
1447 fctx = ctx[f]
1448 repo.wwrite(f, fctx.data(), fctx.flags())
1448 repo.wwrite(f, fctx.data(), fctx.flags())
1449 repo.dirstate.normal(f)
1449 repo.dirstate.normal(f)
1450 repo.setparents(qp, nullid)
1450 repo.setparents(qp, nullid)
1451 for patch in reversed(self.applied[start:end]):
1451 for patch in reversed(self.applied[start:end]):
1452 self.ui.status(_("popping %s\n") % patch.name)
1452 self.ui.status(_("popping %s\n") % patch.name)
1453 del self.applied[start:end]
1453 del self.applied[start:end]
1454 self.strip(repo, [rev], update=False, backup='strip')
1454 self.strip(repo, [rev], update=False, backup='strip')
1455 if self.applied:
1455 if self.applied:
1456 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1456 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1457 else:
1457 else:
1458 self.ui.write(_("patch queue now empty\n"))
1458 self.ui.write(_("patch queue now empty\n"))
1459 finally:
1459 finally:
1460 wlock.release()
1460 wlock.release()
1461
1461
1462 def diff(self, repo, pats, opts):
1462 def diff(self, repo, pats, opts):
1463 top, patch = self.checktoppatch(repo)
1463 top, patch = self.checktoppatch(repo)
1464 if not top:
1464 if not top:
1465 self.ui.write(_("no patches applied\n"))
1465 self.ui.write(_("no patches applied\n"))
1466 return
1466 return
1467 qp = self.qparents(repo, top)
1467 qp = self.qparents(repo, top)
1468 if opts.get('reverse'):
1468 if opts.get('reverse'):
1469 node1, node2 = None, qp
1469 node1, node2 = None, qp
1470 else:
1470 else:
1471 node1, node2 = qp, None
1471 node1, node2 = qp, None
1472 diffopts = self.diffopts(opts, patch)
1472 diffopts = self.diffopts(opts, patch)
1473 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1473 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1474
1474
1475 def refresh(self, repo, pats=None, **opts):
1475 def refresh(self, repo, pats=None, **opts):
1476 if not self.applied:
1476 if not self.applied:
1477 self.ui.write(_("no patches applied\n"))
1477 self.ui.write(_("no patches applied\n"))
1478 return 1
1478 return 1
1479 msg = opts.get('msg', '').rstrip()
1479 msg = opts.get('msg', '').rstrip()
1480 newuser = opts.get('user')
1480 newuser = opts.get('user')
1481 newdate = opts.get('date')
1481 newdate = opts.get('date')
1482 if newdate:
1482 if newdate:
1483 newdate = '%d %d' % util.parsedate(newdate)
1483 newdate = '%d %d' % util.parsedate(newdate)
1484 wlock = repo.wlock()
1484 wlock = repo.wlock()
1485
1485
1486 try:
1486 try:
1487 self.checktoppatch(repo)
1487 self.checktoppatch(repo)
1488 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1488 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1489 if repo.changelog.heads(top) != [top]:
1489 if repo.changelog.heads(top) != [top]:
1490 raise util.Abort(_("cannot refresh a revision with children"))
1490 raise util.Abort(_("cannot refresh a revision with children"))
1491 if not repo[top].mutable():
1491 if not repo[top].mutable():
1492 raise util.Abort(_("cannot refresh immutable revision"),
1492 raise util.Abort(_("cannot refresh immutable revision"),
1493 hint=_('see "hg help phases" for details'))
1493 hint=_('see "hg help phases" for details'))
1494
1494
1495 cparents = repo.changelog.parents(top)
1495 cparents = repo.changelog.parents(top)
1496 patchparent = self.qparents(repo, top)
1496 patchparent = self.qparents(repo, top)
1497
1497
1498 inclsubs = self.checksubstate(repo, hex(patchparent))
1498 inclsubs = self.checksubstate(repo, hex(patchparent))
1499 if inclsubs:
1499 if inclsubs:
1500 inclsubs.append('.hgsubstate')
1500 inclsubs.append('.hgsubstate')
1501 substatestate = repo.dirstate['.hgsubstate']
1501 substatestate = repo.dirstate['.hgsubstate']
1502
1502
1503 ph = patchheader(self.join(patchfn), self.plainmode)
1503 ph = patchheader(self.join(patchfn), self.plainmode)
1504 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1504 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1505 if msg:
1505 if msg:
1506 ph.setmessage(msg)
1506 ph.setmessage(msg)
1507 if newuser:
1507 if newuser:
1508 ph.setuser(newuser)
1508 ph.setuser(newuser)
1509 if newdate:
1509 if newdate:
1510 ph.setdate(newdate)
1510 ph.setdate(newdate)
1511 ph.setparent(hex(patchparent))
1511 ph.setparent(hex(patchparent))
1512
1512
1513 # only commit new patch when write is complete
1513 # only commit new patch when write is complete
1514 patchf = self.opener(patchfn, 'w', atomictemp=True)
1514 patchf = self.opener(patchfn, 'w', atomictemp=True)
1515
1515
1516 comments = str(ph)
1516 comments = str(ph)
1517 if comments:
1517 if comments:
1518 patchf.write(comments)
1518 patchf.write(comments)
1519
1519
1520 # update the dirstate in place, strip off the qtip commit
1520 # update the dirstate in place, strip off the qtip commit
1521 # and then commit.
1521 # and then commit.
1522 #
1522 #
1523 # this should really read:
1523 # this should really read:
1524 # mm, dd, aa = repo.status(top, patchparent)[:3]
1524 # mm, dd, aa = repo.status(top, patchparent)[:3]
1525 # but we do it backwards to take advantage of manifest/chlog
1525 # but we do it backwards to take advantage of manifest/chlog
1526 # caching against the next repo.status call
1526 # caching against the next repo.status call
1527 mm, aa, dd = repo.status(patchparent, top)[:3]
1527 mm, aa, dd = repo.status(patchparent, top)[:3]
1528 changes = repo.changelog.read(top)
1528 changes = repo.changelog.read(top)
1529 man = repo.manifest.read(changes[0])
1529 man = repo.manifest.read(changes[0])
1530 aaa = aa[:]
1530 aaa = aa[:]
1531 matchfn = scmutil.match(repo[None], pats, opts)
1531 matchfn = scmutil.match(repo[None], pats, opts)
1532 # in short mode, we only diff the files included in the
1532 # in short mode, we only diff the files included in the
1533 # patch already plus specified files
1533 # patch already plus specified files
1534 if opts.get('short'):
1534 if opts.get('short'):
1535 # if amending a patch, we start with existing
1535 # if amending a patch, we start with existing
1536 # files plus specified files - unfiltered
1536 # files plus specified files - unfiltered
1537 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1537 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1538 # filter with inc/exl options
1538 # filter with inc/exl options
1539 matchfn = scmutil.match(repo[None], opts=opts)
1539 matchfn = scmutil.match(repo[None], opts=opts)
1540 else:
1540 else:
1541 match = scmutil.matchall(repo)
1541 match = scmutil.matchall(repo)
1542 m, a, r, d = repo.status(match=match)[:4]
1542 m, a, r, d = repo.status(match=match)[:4]
1543 mm = set(mm)
1543 mm = set(mm)
1544 aa = set(aa)
1544 aa = set(aa)
1545 dd = set(dd)
1545 dd = set(dd)
1546
1546
1547 # we might end up with files that were added between
1547 # we might end up with files that were added between
1548 # qtip and the dirstate parent, but then changed in the
1548 # qtip and the dirstate parent, but then changed in the
1549 # local dirstate. in this case, we want them to only
1549 # local dirstate. in this case, we want them to only
1550 # show up in the added section
1550 # show up in the added section
1551 for x in m:
1551 for x in m:
1552 if x not in aa:
1552 if x not in aa:
1553 mm.add(x)
1553 mm.add(x)
1554 # we might end up with files added by the local dirstate that
1554 # we might end up with files added by the local dirstate that
1555 # were deleted by the patch. In this case, they should only
1555 # were deleted by the patch. In this case, they should only
1556 # show up in the changed section.
1556 # show up in the changed section.
1557 for x in a:
1557 for x in a:
1558 if x in dd:
1558 if x in dd:
1559 dd.remove(x)
1559 dd.remove(x)
1560 mm.add(x)
1560 mm.add(x)
1561 else:
1561 else:
1562 aa.add(x)
1562 aa.add(x)
1563 # make sure any files deleted in the local dirstate
1563 # make sure any files deleted in the local dirstate
1564 # are not in the add or change column of the patch
1564 # are not in the add or change column of the patch
1565 forget = []
1565 forget = []
1566 for x in d + r:
1566 for x in d + r:
1567 if x in aa:
1567 if x in aa:
1568 aa.remove(x)
1568 aa.remove(x)
1569 forget.append(x)
1569 forget.append(x)
1570 continue
1570 continue
1571 else:
1571 else:
1572 mm.discard(x)
1572 mm.discard(x)
1573 dd.add(x)
1573 dd.add(x)
1574
1574
1575 m = list(mm)
1575 m = list(mm)
1576 r = list(dd)
1576 r = list(dd)
1577 a = list(aa)
1577 a = list(aa)
1578 c = [filter(matchfn, l) for l in (m, a, r)]
1578 c = [filter(matchfn, l) for l in (m, a, r)]
1579 match = scmutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1579 match = scmutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1580
1580
1581 try:
1581 try:
1582 if diffopts.git or diffopts.upgrade:
1582 if diffopts.git or diffopts.upgrade:
1583 copies = {}
1583 copies = {}
1584 for dst in a:
1584 for dst in a:
1585 src = repo.dirstate.copied(dst)
1585 src = repo.dirstate.copied(dst)
1586 # during qfold, the source file for copies may
1586 # during qfold, the source file for copies may
1587 # be removed. Treat this as a simple add.
1587 # be removed. Treat this as a simple add.
1588 if src is not None and src in repo.dirstate:
1588 if src is not None and src in repo.dirstate:
1589 copies.setdefault(src, []).append(dst)
1589 copies.setdefault(src, []).append(dst)
1590 repo.dirstate.add(dst)
1590 repo.dirstate.add(dst)
1591 # remember the copies between patchparent and qtip
1591 # remember the copies between patchparent and qtip
1592 for dst in aaa:
1592 for dst in aaa:
1593 f = repo.file(dst)
1593 f = repo.file(dst)
1594 src = f.renamed(man[dst])
1594 src = f.renamed(man[dst])
1595 if src:
1595 if src:
1596 copies.setdefault(src[0], []).extend(
1596 copies.setdefault(src[0], []).extend(
1597 copies.get(dst, []))
1597 copies.get(dst, []))
1598 if dst in a:
1598 if dst in a:
1599 copies[src[0]].append(dst)
1599 copies[src[0]].append(dst)
1600 # we can't copy a file created by the patch itself
1600 # we can't copy a file created by the patch itself
1601 if dst in copies:
1601 if dst in copies:
1602 del copies[dst]
1602 del copies[dst]
1603 for src, dsts in copies.iteritems():
1603 for src, dsts in copies.iteritems():
1604 for dst in dsts:
1604 for dst in dsts:
1605 repo.dirstate.copy(src, dst)
1605 repo.dirstate.copy(src, dst)
1606 else:
1606 else:
1607 for dst in a:
1607 for dst in a:
1608 repo.dirstate.add(dst)
1608 repo.dirstate.add(dst)
1609 # Drop useless copy information
1609 # Drop useless copy information
1610 for f in list(repo.dirstate.copies()):
1610 for f in list(repo.dirstate.copies()):
1611 repo.dirstate.copy(None, f)
1611 repo.dirstate.copy(None, f)
1612 for f in r:
1612 for f in r:
1613 repo.dirstate.remove(f)
1613 repo.dirstate.remove(f)
1614 # if the patch excludes a modified file, mark that
1614 # if the patch excludes a modified file, mark that
1615 # file with mtime=0 so status can see it.
1615 # file with mtime=0 so status can see it.
1616 mm = []
1616 mm = []
1617 for i in xrange(len(m)-1, -1, -1):
1617 for i in xrange(len(m)-1, -1, -1):
1618 if not matchfn(m[i]):
1618 if not matchfn(m[i]):
1619 mm.append(m[i])
1619 mm.append(m[i])
1620 del m[i]
1620 del m[i]
1621 for f in m:
1621 for f in m:
1622 repo.dirstate.normal(f)
1622 repo.dirstate.normal(f)
1623 for f in mm:
1623 for f in mm:
1624 repo.dirstate.normallookup(f)
1624 repo.dirstate.normallookup(f)
1625 for f in forget:
1625 for f in forget:
1626 repo.dirstate.drop(f)
1626 repo.dirstate.drop(f)
1627
1627
1628 if not msg:
1628 if not msg:
1629 if not ph.message:
1629 if not ph.message:
1630 message = "[mq]: %s\n" % patchfn
1630 message = "[mq]: %s\n" % patchfn
1631 else:
1631 else:
1632 message = "\n".join(ph.message)
1632 message = "\n".join(ph.message)
1633 else:
1633 else:
1634 message = msg
1634 message = msg
1635
1635
1636 user = ph.user or changes[1]
1636 user = ph.user or changes[1]
1637
1637
1638 oldphase = repo[top].phase()
1638 oldphase = repo[top].phase()
1639
1639
1640 # assumes strip can roll itself back if interrupted
1640 # assumes strip can roll itself back if interrupted
1641 repo.setparents(*cparents)
1641 repo.setparents(*cparents)
1642 self.applied.pop()
1642 self.applied.pop()
1643 self.applieddirty = True
1643 self.applieddirty = True
1644 self.strip(repo, [top], update=False,
1644 self.strip(repo, [top], update=False,
1645 backup='strip')
1645 backup='strip')
1646 except: # re-raises
1646 except: # re-raises
1647 repo.dirstate.invalidate()
1647 repo.dirstate.invalidate()
1648 raise
1648 raise
1649
1649
1650 try:
1650 try:
1651 # might be nice to attempt to roll back strip after this
1651 # might be nice to attempt to roll back strip after this
1652
1652
1653 # Ensure we create a new changeset in the same phase than
1653 # Ensure we create a new changeset in the same phase than
1654 # the old one.
1654 # the old one.
1655 n = newcommit(repo, oldphase, message, user, ph.date,
1655 n = newcommit(repo, oldphase, message, user, ph.date,
1656 match=match, force=True)
1656 match=match, force=True)
1657 # only write patch after a successful commit
1657 # only write patch after a successful commit
1658 if inclsubs:
1658 if inclsubs:
1659 self.putsubstate2changes(substatestate, c)
1659 self.putsubstate2changes(substatestate, c)
1660 chunks = patchmod.diff(repo, patchparent,
1660 chunks = patchmod.diff(repo, patchparent,
1661 changes=c, opts=diffopts)
1661 changes=c, opts=diffopts)
1662 for chunk in chunks:
1662 for chunk in chunks:
1663 patchf.write(chunk)
1663 patchf.write(chunk)
1664 patchf.close()
1664 patchf.close()
1665 self.applied.append(statusentry(n, patchfn))
1665 self.applied.append(statusentry(n, patchfn))
1666 except: # re-raises
1666 except: # re-raises
1667 ctx = repo[cparents[0]]
1667 ctx = repo[cparents[0]]
1668 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1668 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1669 self.savedirty()
1669 self.savedirty()
1670 self.ui.warn(_('refresh interrupted while patch was popped! '
1670 self.ui.warn(_('refresh interrupted while patch was popped! '
1671 '(revert --all, qpush to recover)\n'))
1671 '(revert --all, qpush to recover)\n'))
1672 raise
1672 raise
1673 finally:
1673 finally:
1674 wlock.release()
1674 wlock.release()
1675 self.removeundo(repo)
1675 self.removeundo(repo)
1676
1676
1677 def init(self, repo, create=False):
1677 def init(self, repo, create=False):
1678 if not create and os.path.isdir(self.path):
1678 if not create and os.path.isdir(self.path):
1679 raise util.Abort(_("patch queue directory already exists"))
1679 raise util.Abort(_("patch queue directory already exists"))
1680 try:
1680 try:
1681 os.mkdir(self.path)
1681 os.mkdir(self.path)
1682 except OSError, inst:
1682 except OSError, inst:
1683 if inst.errno != errno.EEXIST or not create:
1683 if inst.errno != errno.EEXIST or not create:
1684 raise
1684 raise
1685 if create:
1685 if create:
1686 return self.qrepo(create=True)
1686 return self.qrepo(create=True)
1687
1687
1688 def unapplied(self, repo, patch=None):
1688 def unapplied(self, repo, patch=None):
1689 if patch and patch not in self.series:
1689 if patch and patch not in self.series:
1690 raise util.Abort(_("patch %s is not in series file") % patch)
1690 raise util.Abort(_("patch %s is not in series file") % patch)
1691 if not patch:
1691 if not patch:
1692 start = self.seriesend()
1692 start = self.seriesend()
1693 else:
1693 else:
1694 start = self.series.index(patch) + 1
1694 start = self.series.index(patch) + 1
1695 unapplied = []
1695 unapplied = []
1696 for i in xrange(start, len(self.series)):
1696 for i in xrange(start, len(self.series)):
1697 pushable, reason = self.pushable(i)
1697 pushable, reason = self.pushable(i)
1698 if pushable:
1698 if pushable:
1699 unapplied.append((i, self.series[i]))
1699 unapplied.append((i, self.series[i]))
1700 self.explainpushable(i)
1700 self.explainpushable(i)
1701 return unapplied
1701 return unapplied
1702
1702
1703 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1703 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1704 summary=False):
1704 summary=False):
1705 def displayname(pfx, patchname, state):
1705 def displayname(pfx, patchname, state):
1706 if pfx:
1706 if pfx:
1707 self.ui.write(pfx)
1707 self.ui.write(pfx)
1708 if summary:
1708 if summary:
1709 ph = patchheader(self.join(patchname), self.plainmode)
1709 ph = patchheader(self.join(patchname), self.plainmode)
1710 msg = ph.message and ph.message[0] or ''
1710 msg = ph.message and ph.message[0] or ''
1711 if self.ui.formatted():
1711 if self.ui.formatted():
1712 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1712 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1713 if width > 0:
1713 if width > 0:
1714 msg = util.ellipsis(msg, width)
1714 msg = util.ellipsis(msg, width)
1715 else:
1715 else:
1716 msg = ''
1716 msg = ''
1717 self.ui.write(patchname, label='qseries.' + state)
1717 self.ui.write(patchname, label='qseries.' + state)
1718 self.ui.write(': ')
1718 self.ui.write(': ')
1719 self.ui.write(msg, label='qseries.message.' + state)
1719 self.ui.write(msg, label='qseries.message.' + state)
1720 else:
1720 else:
1721 self.ui.write(patchname, label='qseries.' + state)
1721 self.ui.write(patchname, label='qseries.' + state)
1722 self.ui.write('\n')
1722 self.ui.write('\n')
1723
1723
1724 applied = set([p.name for p in self.applied])
1724 applied = set([p.name for p in self.applied])
1725 if length is None:
1725 if length is None:
1726 length = len(self.series) - start
1726 length = len(self.series) - start
1727 if not missing:
1727 if not missing:
1728 if self.ui.verbose:
1728 if self.ui.verbose:
1729 idxwidth = len(str(start + length - 1))
1729 idxwidth = len(str(start + length - 1))
1730 for i in xrange(start, start + length):
1730 for i in xrange(start, start + length):
1731 patch = self.series[i]
1731 patch = self.series[i]
1732 if patch in applied:
1732 if patch in applied:
1733 char, state = 'A', 'applied'
1733 char, state = 'A', 'applied'
1734 elif self.pushable(i)[0]:
1734 elif self.pushable(i)[0]:
1735 char, state = 'U', 'unapplied'
1735 char, state = 'U', 'unapplied'
1736 else:
1736 else:
1737 char, state = 'G', 'guarded'
1737 char, state = 'G', 'guarded'
1738 pfx = ''
1738 pfx = ''
1739 if self.ui.verbose:
1739 if self.ui.verbose:
1740 pfx = '%*d %s ' % (idxwidth, i, char)
1740 pfx = '%*d %s ' % (idxwidth, i, char)
1741 elif status and status != char:
1741 elif status and status != char:
1742 continue
1742 continue
1743 displayname(pfx, patch, state)
1743 displayname(pfx, patch, state)
1744 else:
1744 else:
1745 msng_list = []
1745 msng_list = []
1746 for root, dirs, files in os.walk(self.path):
1746 for root, dirs, files in os.walk(self.path):
1747 d = root[len(self.path) + 1:]
1747 d = root[len(self.path) + 1:]
1748 for f in files:
1748 for f in files:
1749 fl = os.path.join(d, f)
1749 fl = os.path.join(d, f)
1750 if (fl not in self.series and
1750 if (fl not in self.series and
1751 fl not in (self.statuspath, self.seriespath,
1751 fl not in (self.statuspath, self.seriespath,
1752 self.guardspath)
1752 self.guardspath)
1753 and not fl.startswith('.')):
1753 and not fl.startswith('.')):
1754 msng_list.append(fl)
1754 msng_list.append(fl)
1755 for x in sorted(msng_list):
1755 for x in sorted(msng_list):
1756 pfx = self.ui.verbose and ('D ') or ''
1756 pfx = self.ui.verbose and ('D ') or ''
1757 displayname(pfx, x, 'missing')
1757 displayname(pfx, x, 'missing')
1758
1758
1759 def issaveline(self, l):
1759 def issaveline(self, l):
1760 if l.name == '.hg.patches.save.line':
1760 if l.name == '.hg.patches.save.line':
1761 return True
1761 return True
1762
1762
1763 def qrepo(self, create=False):
1763 def qrepo(self, create=False):
1764 ui = self.ui.copy()
1764 ui = self.ui.copy()
1765 ui.setconfig('paths', 'default', '', overlay=False)
1765 ui.setconfig('paths', 'default', '', overlay=False)
1766 ui.setconfig('paths', 'default-push', '', overlay=False)
1766 ui.setconfig('paths', 'default-push', '', overlay=False)
1767 if create or os.path.isdir(self.join(".hg")):
1767 if create or os.path.isdir(self.join(".hg")):
1768 return hg.repository(ui, path=self.path, create=create)
1768 return hg.repository(ui, path=self.path, create=create)
1769
1769
1770 def restore(self, repo, rev, delete=None, qupdate=None):
1770 def restore(self, repo, rev, delete=None, qupdate=None):
1771 desc = repo[rev].description().strip()
1771 desc = repo[rev].description().strip()
1772 lines = desc.splitlines()
1772 lines = desc.splitlines()
1773 i = 0
1773 i = 0
1774 datastart = None
1774 datastart = None
1775 series = []
1775 series = []
1776 applied = []
1776 applied = []
1777 qpp = None
1777 qpp = None
1778 for i, line in enumerate(lines):
1778 for i, line in enumerate(lines):
1779 if line == 'Patch Data:':
1779 if line == 'Patch Data:':
1780 datastart = i + 1
1780 datastart = i + 1
1781 elif line.startswith('Dirstate:'):
1781 elif line.startswith('Dirstate:'):
1782 l = line.rstrip()
1782 l = line.rstrip()
1783 l = l[10:].split(' ')
1783 l = l[10:].split(' ')
1784 qpp = [bin(x) for x in l]
1784 qpp = [bin(x) for x in l]
1785 elif datastart is not None:
1785 elif datastart is not None:
1786 l = line.rstrip()
1786 l = line.rstrip()
1787 n, name = l.split(':', 1)
1787 n, name = l.split(':', 1)
1788 if n:
1788 if n:
1789 applied.append(statusentry(bin(n), name))
1789 applied.append(statusentry(bin(n), name))
1790 else:
1790 else:
1791 series.append(l)
1791 series.append(l)
1792 if datastart is None:
1792 if datastart is None:
1793 self.ui.warn(_("no saved patch data found\n"))
1793 self.ui.warn(_("no saved patch data found\n"))
1794 return 1
1794 return 1
1795 self.ui.warn(_("restoring status: %s\n") % lines[0])
1795 self.ui.warn(_("restoring status: %s\n") % lines[0])
1796 self.fullseries = series
1796 self.fullseries = series
1797 self.applied = applied
1797 self.applied = applied
1798 self.parseseries()
1798 self.parseseries()
1799 self.seriesdirty = True
1799 self.seriesdirty = True
1800 self.applieddirty = True
1800 self.applieddirty = True
1801 heads = repo.changelog.heads()
1801 heads = repo.changelog.heads()
1802 if delete:
1802 if delete:
1803 if rev not in heads:
1803 if rev not in heads:
1804 self.ui.warn(_("save entry has children, leaving it alone\n"))
1804 self.ui.warn(_("save entry has children, leaving it alone\n"))
1805 else:
1805 else:
1806 self.ui.warn(_("removing save entry %s\n") % short(rev))
1806 self.ui.warn(_("removing save entry %s\n") % short(rev))
1807 pp = repo.dirstate.parents()
1807 pp = repo.dirstate.parents()
1808 if rev in pp:
1808 if rev in pp:
1809 update = True
1809 update = True
1810 else:
1810 else:
1811 update = False
1811 update = False
1812 self.strip(repo, [rev], update=update, backup='strip')
1812 self.strip(repo, [rev], update=update, backup='strip')
1813 if qpp:
1813 if qpp:
1814 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1814 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1815 (short(qpp[0]), short(qpp[1])))
1815 (short(qpp[0]), short(qpp[1])))
1816 if qupdate:
1816 if qupdate:
1817 self.ui.status(_("updating queue directory\n"))
1817 self.ui.status(_("updating queue directory\n"))
1818 r = self.qrepo()
1818 r = self.qrepo()
1819 if not r:
1819 if not r:
1820 self.ui.warn(_("unable to load queue repository\n"))
1820 self.ui.warn(_("unable to load queue repository\n"))
1821 return 1
1821 return 1
1822 hg.clean(r, qpp[0])
1822 hg.clean(r, qpp[0])
1823
1823
1824 def save(self, repo, msg=None):
1824 def save(self, repo, msg=None):
1825 if not self.applied:
1825 if not self.applied:
1826 self.ui.warn(_("save: no patches applied, exiting\n"))
1826 self.ui.warn(_("save: no patches applied, exiting\n"))
1827 return 1
1827 return 1
1828 if self.issaveline(self.applied[-1]):
1828 if self.issaveline(self.applied[-1]):
1829 self.ui.warn(_("status is already saved\n"))
1829 self.ui.warn(_("status is already saved\n"))
1830 return 1
1830 return 1
1831
1831
1832 if not msg:
1832 if not msg:
1833 msg = _("hg patches saved state")
1833 msg = _("hg patches saved state")
1834 else:
1834 else:
1835 msg = "hg patches: " + msg.rstrip('\r\n')
1835 msg = "hg patches: " + msg.rstrip('\r\n')
1836 r = self.qrepo()
1836 r = self.qrepo()
1837 if r:
1837 if r:
1838 pp = r.dirstate.parents()
1838 pp = r.dirstate.parents()
1839 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1839 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1840 msg += "\n\nPatch Data:\n"
1840 msg += "\n\nPatch Data:\n"
1841 msg += ''.join('%s\n' % x for x in self.applied)
1841 msg += ''.join('%s\n' % x for x in self.applied)
1842 msg += ''.join(':%s\n' % x for x in self.fullseries)
1842 msg += ''.join(':%s\n' % x for x in self.fullseries)
1843 n = repo.commit(msg, force=True)
1843 n = repo.commit(msg, force=True)
1844 if not n:
1844 if not n:
1845 self.ui.warn(_("repo commit failed\n"))
1845 self.ui.warn(_("repo commit failed\n"))
1846 return 1
1846 return 1
1847 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1847 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1848 self.applieddirty = True
1848 self.applieddirty = True
1849 self.removeundo(repo)
1849 self.removeundo(repo)
1850
1850
1851 def fullseriesend(self):
1851 def fullseriesend(self):
1852 if self.applied:
1852 if self.applied:
1853 p = self.applied[-1].name
1853 p = self.applied[-1].name
1854 end = self.findseries(p)
1854 end = self.findseries(p)
1855 if end is None:
1855 if end is None:
1856 return len(self.fullseries)
1856 return len(self.fullseries)
1857 return end + 1
1857 return end + 1
1858 return 0
1858 return 0
1859
1859
1860 def seriesend(self, all_patches=False):
1860 def seriesend(self, all_patches=False):
1861 """If all_patches is False, return the index of the next pushable patch
1861 """If all_patches is False, return the index of the next pushable patch
1862 in the series, or the series length. If all_patches is True, return the
1862 in the series, or the series length. If all_patches is True, return the
1863 index of the first patch past the last applied one.
1863 index of the first patch past the last applied one.
1864 """
1864 """
1865 end = 0
1865 end = 0
1866 def next(start):
1866 def next(start):
1867 if all_patches or start >= len(self.series):
1867 if all_patches or start >= len(self.series):
1868 return start
1868 return start
1869 for i in xrange(start, len(self.series)):
1869 for i in xrange(start, len(self.series)):
1870 p, reason = self.pushable(i)
1870 p, reason = self.pushable(i)
1871 if p:
1871 if p:
1872 return i
1872 return i
1873 self.explainpushable(i)
1873 self.explainpushable(i)
1874 return len(self.series)
1874 return len(self.series)
1875 if self.applied:
1875 if self.applied:
1876 p = self.applied[-1].name
1876 p = self.applied[-1].name
1877 try:
1877 try:
1878 end = self.series.index(p)
1878 end = self.series.index(p)
1879 except ValueError:
1879 except ValueError:
1880 return 0
1880 return 0
1881 return next(end + 1)
1881 return next(end + 1)
1882 return next(end)
1882 return next(end)
1883
1883
1884 def appliedname(self, index):
1884 def appliedname(self, index):
1885 pname = self.applied[index].name
1885 pname = self.applied[index].name
1886 if not self.ui.verbose:
1886 if not self.ui.verbose:
1887 p = pname
1887 p = pname
1888 else:
1888 else:
1889 p = str(self.series.index(pname)) + " " + pname
1889 p = str(self.series.index(pname)) + " " + pname
1890 return p
1890 return p
1891
1891
1892 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1892 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1893 force=None, git=False):
1893 force=None, git=False):
1894 def checkseries(patchname):
1894 def checkseries(patchname):
1895 if patchname in self.series:
1895 if patchname in self.series:
1896 raise util.Abort(_('patch %s is already in the series file')
1896 raise util.Abort(_('patch %s is already in the series file')
1897 % patchname)
1897 % patchname)
1898
1898
1899 if rev:
1899 if rev:
1900 if files:
1900 if files:
1901 raise util.Abort(_('option "-r" not valid when importing '
1901 raise util.Abort(_('option "-r" not valid when importing '
1902 'files'))
1902 'files'))
1903 rev = scmutil.revrange(repo, rev)
1903 rev = scmutil.revrange(repo, rev)
1904 rev.sort(reverse=True)
1904 rev.sort(reverse=True)
1905 elif not files:
1905 elif not files:
1906 raise util.Abort(_('no files or revisions specified'))
1906 raise util.Abort(_('no files or revisions specified'))
1907 if (len(files) > 1 or len(rev) > 1) and patchname:
1907 if (len(files) > 1 or len(rev) > 1) and patchname:
1908 raise util.Abort(_('option "-n" not valid when importing multiple '
1908 raise util.Abort(_('option "-n" not valid when importing multiple '
1909 'patches'))
1909 'patches'))
1910 imported = []
1910 imported = []
1911 if rev:
1911 if rev:
1912 # If mq patches are applied, we can only import revisions
1912 # If mq patches are applied, we can only import revisions
1913 # that form a linear path to qbase.
1913 # that form a linear path to qbase.
1914 # Otherwise, they should form a linear path to a head.
1914 # Otherwise, they should form a linear path to a head.
1915 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1915 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1916 if len(heads) > 1:
1916 if len(heads) > 1:
1917 raise util.Abort(_('revision %d is the root of more than one '
1917 raise util.Abort(_('revision %d is the root of more than one '
1918 'branch') % rev[-1])
1918 'branch') % rev[-1])
1919 if self.applied:
1919 if self.applied:
1920 base = repo.changelog.node(rev[0])
1920 base = repo.changelog.node(rev[0])
1921 if base in [n.node for n in self.applied]:
1921 if base in [n.node for n in self.applied]:
1922 raise util.Abort(_('revision %d is already managed')
1922 raise util.Abort(_('revision %d is already managed')
1923 % rev[0])
1923 % rev[0])
1924 if heads != [self.applied[-1].node]:
1924 if heads != [self.applied[-1].node]:
1925 raise util.Abort(_('revision %d is not the parent of '
1925 raise util.Abort(_('revision %d is not the parent of '
1926 'the queue') % rev[0])
1926 'the queue') % rev[0])
1927 base = repo.changelog.rev(self.applied[0].node)
1927 base = repo.changelog.rev(self.applied[0].node)
1928 lastparent = repo.changelog.parentrevs(base)[0]
1928 lastparent = repo.changelog.parentrevs(base)[0]
1929 else:
1929 else:
1930 if heads != [repo.changelog.node(rev[0])]:
1930 if heads != [repo.changelog.node(rev[0])]:
1931 raise util.Abort(_('revision %d has unmanaged children')
1931 raise util.Abort(_('revision %d has unmanaged children')
1932 % rev[0])
1932 % rev[0])
1933 lastparent = None
1933 lastparent = None
1934
1934
1935 diffopts = self.diffopts({'git': git})
1935 diffopts = self.diffopts({'git': git})
1936 for r in rev:
1936 for r in rev:
1937 if not repo[r].mutable():
1937 if not repo[r].mutable():
1938 raise util.Abort(_('revision %d is not mutable') % r,
1938 raise util.Abort(_('revision %d is not mutable') % r,
1939 hint=_('see "hg help phases" for details'))
1939 hint=_('see "hg help phases" for details'))
1940 p1, p2 = repo.changelog.parentrevs(r)
1940 p1, p2 = repo.changelog.parentrevs(r)
1941 n = repo.changelog.node(r)
1941 n = repo.changelog.node(r)
1942 if p2 != nullrev:
1942 if p2 != nullrev:
1943 raise util.Abort(_('cannot import merge revision %d') % r)
1943 raise util.Abort(_('cannot import merge revision %d') % r)
1944 if lastparent and lastparent != r:
1944 if lastparent and lastparent != r:
1945 raise util.Abort(_('revision %d is not the parent of %d')
1945 raise util.Abort(_('revision %d is not the parent of %d')
1946 % (r, lastparent))
1946 % (r, lastparent))
1947 lastparent = p1
1947 lastparent = p1
1948
1948
1949 if not patchname:
1949 if not patchname:
1950 patchname = normname('%d.diff' % r)
1950 patchname = normname('%d.diff' % r)
1951 checkseries(patchname)
1951 checkseries(patchname)
1952 self.checkpatchname(patchname, force)
1952 self.checkpatchname(patchname, force)
1953 self.fullseries.insert(0, patchname)
1953 self.fullseries.insert(0, patchname)
1954
1954
1955 patchf = self.opener(patchname, "w")
1955 patchf = self.opener(patchname, "w")
1956 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1956 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1957 patchf.close()
1957 patchf.close()
1958
1958
1959 se = statusentry(n, patchname)
1959 se = statusentry(n, patchname)
1960 self.applied.insert(0, se)
1960 self.applied.insert(0, se)
1961
1961
1962 self.added.append(patchname)
1962 self.added.append(patchname)
1963 imported.append(patchname)
1963 imported.append(patchname)
1964 patchname = None
1964 patchname = None
1965 if rev and repo.ui.configbool('mq', 'secret', False):
1965 if rev and repo.ui.configbool('mq', 'secret', False):
1966 # if we added anything with --rev, we must move the secret root
1966 # if we added anything with --rev, we must move the secret root
1967 phases.retractboundary(repo, phases.secret, [n])
1967 phases.retractboundary(repo, phases.secret, [n])
1968 self.parseseries()
1968 self.parseseries()
1969 self.applieddirty = True
1969 self.applieddirty = True
1970 self.seriesdirty = True
1970 self.seriesdirty = True
1971
1971
1972 for i, filename in enumerate(files):
1972 for i, filename in enumerate(files):
1973 if existing:
1973 if existing:
1974 if filename == '-':
1974 if filename == '-':
1975 raise util.Abort(_('-e is incompatible with import from -'))
1975 raise util.Abort(_('-e is incompatible with import from -'))
1976 filename = normname(filename)
1976 filename = normname(filename)
1977 self.checkreservedname(filename)
1977 self.checkreservedname(filename)
1978 originpath = self.join(filename)
1978 originpath = self.join(filename)
1979 if not os.path.isfile(originpath):
1979 if not os.path.isfile(originpath):
1980 raise util.Abort(_("patch %s does not exist") % filename)
1980 raise util.Abort(_("patch %s does not exist") % filename)
1981
1981
1982 if patchname:
1982 if patchname:
1983 self.checkpatchname(patchname, force)
1983 self.checkpatchname(patchname, force)
1984
1984
1985 self.ui.write(_('renaming %s to %s\n')
1985 self.ui.write(_('renaming %s to %s\n')
1986 % (filename, patchname))
1986 % (filename, patchname))
1987 util.rename(originpath, self.join(patchname))
1987 util.rename(originpath, self.join(patchname))
1988 else:
1988 else:
1989 patchname = filename
1989 patchname = filename
1990
1990
1991 else:
1991 else:
1992 if filename == '-' and not patchname:
1992 if filename == '-' and not patchname:
1993 raise util.Abort(_('need --name to import a patch from -'))
1993 raise util.Abort(_('need --name to import a patch from -'))
1994 elif not patchname:
1994 elif not patchname:
1995 patchname = normname(os.path.basename(filename.rstrip('/')))
1995 patchname = normname(os.path.basename(filename.rstrip('/')))
1996 self.checkpatchname(patchname, force)
1996 self.checkpatchname(patchname, force)
1997 try:
1997 try:
1998 if filename == '-':
1998 if filename == '-':
1999 text = self.ui.fin.read()
1999 text = self.ui.fin.read()
2000 else:
2000 else:
2001 fp = url.open(self.ui, filename)
2001 fp = url.open(self.ui, filename)
2002 text = fp.read()
2002 text = fp.read()
2003 fp.close()
2003 fp.close()
2004 except (OSError, IOError):
2004 except (OSError, IOError):
2005 raise util.Abort(_("unable to read file %s") % filename)
2005 raise util.Abort(_("unable to read file %s") % filename)
2006 patchf = self.opener(patchname, "w")
2006 patchf = self.opener(patchname, "w")
2007 patchf.write(text)
2007 patchf.write(text)
2008 patchf.close()
2008 patchf.close()
2009 if not force:
2009 if not force:
2010 checkseries(patchname)
2010 checkseries(patchname)
2011 if patchname not in self.series:
2011 if patchname not in self.series:
2012 index = self.fullseriesend() + i
2012 index = self.fullseriesend() + i
2013 self.fullseries[index:index] = [patchname]
2013 self.fullseries[index:index] = [patchname]
2014 self.parseseries()
2014 self.parseseries()
2015 self.seriesdirty = True
2015 self.seriesdirty = True
2016 self.ui.warn(_("adding %s to series file\n") % patchname)
2016 self.ui.warn(_("adding %s to series file\n") % patchname)
2017 self.added.append(patchname)
2017 self.added.append(patchname)
2018 imported.append(patchname)
2018 imported.append(patchname)
2019 patchname = None
2019 patchname = None
2020
2020
2021 self.removeundo(repo)
2021 self.removeundo(repo)
2022 return imported
2022 return imported
2023
2023
2024 def fixkeepchangesopts(ui, opts):
2024 def fixkeepchangesopts(ui, opts):
2025 if (not ui.configbool('mq', 'keepchanges') or opts.get('force')
2025 if (not ui.configbool('mq', 'keepchanges') or opts.get('force')
2026 or opts.get('exact')):
2026 or opts.get('exact')):
2027 return opts
2027 return opts
2028 opts = dict(opts)
2028 opts = dict(opts)
2029 opts['keep_changes'] = True
2029 opts['keep_changes'] = True
2030 return opts
2030 return opts
2031
2031
2032 @command("qdelete|qremove|qrm",
2032 @command("qdelete|qremove|qrm",
2033 [('k', 'keep', None, _('keep patch file')),
2033 [('k', 'keep', None, _('keep patch file')),
2034 ('r', 'rev', [],
2034 ('r', 'rev', [],
2035 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2035 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2036 _('hg qdelete [-k] [PATCH]...'))
2036 _('hg qdelete [-k] [PATCH]...'))
2037 def delete(ui, repo, *patches, **opts):
2037 def delete(ui, repo, *patches, **opts):
2038 """remove patches from queue
2038 """remove patches from queue
2039
2039
2040 The patches must not be applied, and at least one patch is required. Exact
2040 The patches must not be applied, and at least one patch is required. Exact
2041 patch identifiers must be given. With -k/--keep, the patch files are
2041 patch identifiers must be given. With -k/--keep, the patch files are
2042 preserved in the patch directory.
2042 preserved in the patch directory.
2043
2043
2044 To stop managing a patch and move it into permanent history,
2044 To stop managing a patch and move it into permanent history,
2045 use the :hg:`qfinish` command."""
2045 use the :hg:`qfinish` command."""
2046 q = repo.mq
2046 q = repo.mq
2047 q.delete(repo, patches, opts)
2047 q.delete(repo, patches, opts)
2048 q.savedirty()
2048 q.savedirty()
2049 return 0
2049 return 0
2050
2050
2051 @command("qapplied",
2051 @command("qapplied",
2052 [('1', 'last', None, _('show only the preceding applied patch'))
2052 [('1', 'last', None, _('show only the preceding applied patch'))
2053 ] + seriesopts,
2053 ] + seriesopts,
2054 _('hg qapplied [-1] [-s] [PATCH]'))
2054 _('hg qapplied [-1] [-s] [PATCH]'))
2055 def applied(ui, repo, patch=None, **opts):
2055 def applied(ui, repo, patch=None, **opts):
2056 """print the patches already applied
2056 """print the patches already applied
2057
2057
2058 Returns 0 on success."""
2058 Returns 0 on success."""
2059
2059
2060 q = repo.mq
2060 q = repo.mq
2061
2061
2062 if patch:
2062 if patch:
2063 if patch not in q.series:
2063 if patch not in q.series:
2064 raise util.Abort(_("patch %s is not in series file") % patch)
2064 raise util.Abort(_("patch %s is not in series file") % patch)
2065 end = q.series.index(patch) + 1
2065 end = q.series.index(patch) + 1
2066 else:
2066 else:
2067 end = q.seriesend(True)
2067 end = q.seriesend(True)
2068
2068
2069 if opts.get('last') and not end:
2069 if opts.get('last') and not end:
2070 ui.write(_("no patches applied\n"))
2070 ui.write(_("no patches applied\n"))
2071 return 1
2071 return 1
2072 elif opts.get('last') and end == 1:
2072 elif opts.get('last') and end == 1:
2073 ui.write(_("only one patch applied\n"))
2073 ui.write(_("only one patch applied\n"))
2074 return 1
2074 return 1
2075 elif opts.get('last'):
2075 elif opts.get('last'):
2076 start = end - 2
2076 start = end - 2
2077 end = 1
2077 end = 1
2078 else:
2078 else:
2079 start = 0
2079 start = 0
2080
2080
2081 q.qseries(repo, length=end, start=start, status='A',
2081 q.qseries(repo, length=end, start=start, status='A',
2082 summary=opts.get('summary'))
2082 summary=opts.get('summary'))
2083
2083
2084
2084
2085 @command("qunapplied",
2085 @command("qunapplied",
2086 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2086 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2087 _('hg qunapplied [-1] [-s] [PATCH]'))
2087 _('hg qunapplied [-1] [-s] [PATCH]'))
2088 def unapplied(ui, repo, patch=None, **opts):
2088 def unapplied(ui, repo, patch=None, **opts):
2089 """print the patches not yet applied
2089 """print the patches not yet applied
2090
2090
2091 Returns 0 on success."""
2091 Returns 0 on success."""
2092
2092
2093 q = repo.mq
2093 q = repo.mq
2094 if patch:
2094 if patch:
2095 if patch not in q.series:
2095 if patch not in q.series:
2096 raise util.Abort(_("patch %s is not in series file") % patch)
2096 raise util.Abort(_("patch %s is not in series file") % patch)
2097 start = q.series.index(patch) + 1
2097 start = q.series.index(patch) + 1
2098 else:
2098 else:
2099 start = q.seriesend(True)
2099 start = q.seriesend(True)
2100
2100
2101 if start == len(q.series) and opts.get('first'):
2101 if start == len(q.series) and opts.get('first'):
2102 ui.write(_("all patches applied\n"))
2102 ui.write(_("all patches applied\n"))
2103 return 1
2103 return 1
2104
2104
2105 length = opts.get('first') and 1 or None
2105 length = opts.get('first') and 1 or None
2106 q.qseries(repo, start=start, length=length, status='U',
2106 q.qseries(repo, start=start, length=length, status='U',
2107 summary=opts.get('summary'))
2107 summary=opts.get('summary'))
2108
2108
2109 @command("qimport",
2109 @command("qimport",
2110 [('e', 'existing', None, _('import file in patch directory')),
2110 [('e', 'existing', None, _('import file in patch directory')),
2111 ('n', 'name', '',
2111 ('n', 'name', '',
2112 _('name of patch file'), _('NAME')),
2112 _('name of patch file'), _('NAME')),
2113 ('f', 'force', None, _('overwrite existing files')),
2113 ('f', 'force', None, _('overwrite existing files')),
2114 ('r', 'rev', [],
2114 ('r', 'rev', [],
2115 _('place existing revisions under mq control'), _('REV')),
2115 _('place existing revisions under mq control'), _('REV')),
2116 ('g', 'git', None, _('use git extended diff format')),
2116 ('g', 'git', None, _('use git extended diff format')),
2117 ('P', 'push', None, _('qpush after importing'))],
2117 ('P', 'push', None, _('qpush after importing'))],
2118 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'))
2118 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'))
2119 def qimport(ui, repo, *filename, **opts):
2119 def qimport(ui, repo, *filename, **opts):
2120 """import a patch or existing changeset
2120 """import a patch or existing changeset
2121
2121
2122 The patch is inserted into the series after the last applied
2122 The patch is inserted into the series after the last applied
2123 patch. If no patches have been applied, qimport prepends the patch
2123 patch. If no patches have been applied, qimport prepends the patch
2124 to the series.
2124 to the series.
2125
2125
2126 The patch will have the same name as its source file unless you
2126 The patch will have the same name as its source file unless you
2127 give it a new one with -n/--name.
2127 give it a new one with -n/--name.
2128
2128
2129 You can register an existing patch inside the patch directory with
2129 You can register an existing patch inside the patch directory with
2130 the -e/--existing flag.
2130 the -e/--existing flag.
2131
2131
2132 With -f/--force, an existing patch of the same name will be
2132 With -f/--force, an existing patch of the same name will be
2133 overwritten.
2133 overwritten.
2134
2134
2135 An existing changeset may be placed under mq control with -r/--rev
2135 An existing changeset may be placed under mq control with -r/--rev
2136 (e.g. qimport --rev tip -n patch will place tip under mq control).
2136 (e.g. qimport --rev tip -n patch will place tip under mq control).
2137 With -g/--git, patches imported with --rev will use the git diff
2137 With -g/--git, patches imported with --rev will use the git diff
2138 format. See the diffs help topic for information on why this is
2138 format. See the diffs help topic for information on why this is
2139 important for preserving rename/copy information and permission
2139 important for preserving rename/copy information and permission
2140 changes. Use :hg:`qfinish` to remove changesets from mq control.
2140 changes. Use :hg:`qfinish` to remove changesets from mq control.
2141
2141
2142 To import a patch from standard input, pass - as the patch file.
2142 To import a patch from standard input, pass - as the patch file.
2143 When importing from standard input, a patch name must be specified
2143 When importing from standard input, a patch name must be specified
2144 using the --name flag.
2144 using the --name flag.
2145
2145
2146 To import an existing patch while renaming it::
2146 To import an existing patch while renaming it::
2147
2147
2148 hg qimport -e existing-patch -n new-name
2148 hg qimport -e existing-patch -n new-name
2149
2149
2150 Returns 0 if import succeeded.
2150 Returns 0 if import succeeded.
2151 """
2151 """
2152 lock = repo.lock() # cause this may move phase
2152 lock = repo.lock() # cause this may move phase
2153 try:
2153 try:
2154 q = repo.mq
2154 q = repo.mq
2155 try:
2155 try:
2156 imported = q.qimport(
2156 imported = q.qimport(
2157 repo, filename, patchname=opts.get('name'),
2157 repo, filename, patchname=opts.get('name'),
2158 existing=opts.get('existing'), force=opts.get('force'),
2158 existing=opts.get('existing'), force=opts.get('force'),
2159 rev=opts.get('rev'), git=opts.get('git'))
2159 rev=opts.get('rev'), git=opts.get('git'))
2160 finally:
2160 finally:
2161 q.savedirty()
2161 q.savedirty()
2162 finally:
2162 finally:
2163 lock.release()
2163 lock.release()
2164
2164
2165 if imported and opts.get('push') and not opts.get('rev'):
2165 if imported and opts.get('push') and not opts.get('rev'):
2166 return q.push(repo, imported[-1])
2166 return q.push(repo, imported[-1])
2167 return 0
2167 return 0
2168
2168
2169 def qinit(ui, repo, create):
2169 def qinit(ui, repo, create):
2170 """initialize a new queue repository
2170 """initialize a new queue repository
2171
2171
2172 This command also creates a series file for ordering patches, and
2172 This command also creates a series file for ordering patches, and
2173 an mq-specific .hgignore file in the queue repository, to exclude
2173 an mq-specific .hgignore file in the queue repository, to exclude
2174 the status and guards files (these contain mostly transient state).
2174 the status and guards files (these contain mostly transient state).
2175
2175
2176 Returns 0 if initialization succeeded."""
2176 Returns 0 if initialization succeeded."""
2177 q = repo.mq
2177 q = repo.mq
2178 r = q.init(repo, create)
2178 r = q.init(repo, create)
2179 q.savedirty()
2179 q.savedirty()
2180 if r:
2180 if r:
2181 if not os.path.exists(r.wjoin('.hgignore')):
2181 if not os.path.exists(r.wjoin('.hgignore')):
2182 fp = r.wopener('.hgignore', 'w')
2182 fp = r.wopener('.hgignore', 'w')
2183 fp.write('^\\.hg\n')
2183 fp.write('^\\.hg\n')
2184 fp.write('^\\.mq\n')
2184 fp.write('^\\.mq\n')
2185 fp.write('syntax: glob\n')
2185 fp.write('syntax: glob\n')
2186 fp.write('status\n')
2186 fp.write('status\n')
2187 fp.write('guards\n')
2187 fp.write('guards\n')
2188 fp.close()
2188 fp.close()
2189 if not os.path.exists(r.wjoin('series')):
2189 if not os.path.exists(r.wjoin('series')):
2190 r.wopener('series', 'w').close()
2190 r.wopener('series', 'w').close()
2191 r[None].add(['.hgignore', 'series'])
2191 r[None].add(['.hgignore', 'series'])
2192 commands.add(ui, r)
2192 commands.add(ui, r)
2193 return 0
2193 return 0
2194
2194
2195 @command("^qinit",
2195 @command("^qinit",
2196 [('c', 'create-repo', None, _('create queue repository'))],
2196 [('c', 'create-repo', None, _('create queue repository'))],
2197 _('hg qinit [-c]'))
2197 _('hg qinit [-c]'))
2198 def init(ui, repo, **opts):
2198 def init(ui, repo, **opts):
2199 """init a new queue repository (DEPRECATED)
2199 """init a new queue repository (DEPRECATED)
2200
2200
2201 The queue repository is unversioned by default. If
2201 The queue repository is unversioned by default. If
2202 -c/--create-repo is specified, qinit will create a separate nested
2202 -c/--create-repo is specified, qinit will create a separate nested
2203 repository for patches (qinit -c may also be run later to convert
2203 repository for patches (qinit -c may also be run later to convert
2204 an unversioned patch repository into a versioned one). You can use
2204 an unversioned patch repository into a versioned one). You can use
2205 qcommit to commit changes to this queue repository.
2205 qcommit to commit changes to this queue repository.
2206
2206
2207 This command is deprecated. Without -c, it's implied by other relevant
2207 This command is deprecated. Without -c, it's implied by other relevant
2208 commands. With -c, use :hg:`init --mq` instead."""
2208 commands. With -c, use :hg:`init --mq` instead."""
2209 return qinit(ui, repo, create=opts.get('create_repo'))
2209 return qinit(ui, repo, create=opts.get('create_repo'))
2210
2210
2211 @command("qclone",
2211 @command("qclone",
2212 [('', 'pull', None, _('use pull protocol to copy metadata')),
2212 [('', 'pull', None, _('use pull protocol to copy metadata')),
2213 ('U', 'noupdate', None,
2213 ('U', 'noupdate', None,
2214 _('do not update the new working directories')),
2214 _('do not update the new working directories')),
2215 ('', 'uncompressed', None,
2215 ('', 'uncompressed', None,
2216 _('use uncompressed transfer (fast over LAN)')),
2216 _('use uncompressed transfer (fast over LAN)')),
2217 ('p', 'patches', '',
2217 ('p', 'patches', '',
2218 _('location of source patch repository'), _('REPO')),
2218 _('location of source patch repository'), _('REPO')),
2219 ] + commands.remoteopts,
2219 ] + commands.remoteopts,
2220 _('hg qclone [OPTION]... SOURCE [DEST]'))
2220 _('hg qclone [OPTION]... SOURCE [DEST]'))
2221 def clone(ui, source, dest=None, **opts):
2221 def clone(ui, source, dest=None, **opts):
2222 '''clone main and patch repository at same time
2222 '''clone main and patch repository at same time
2223
2223
2224 If source is local, destination will have no patches applied. If
2224 If source is local, destination will have no patches applied. If
2225 source is remote, this command can not check if patches are
2225 source is remote, this command can not check if patches are
2226 applied in source, so cannot guarantee that patches are not
2226 applied in source, so cannot guarantee that patches are not
2227 applied in destination. If you clone remote repository, be sure
2227 applied in destination. If you clone remote repository, be sure
2228 before that it has no patches applied.
2228 before that it has no patches applied.
2229
2229
2230 Source patch repository is looked for in <src>/.hg/patches by
2230 Source patch repository is looked for in <src>/.hg/patches by
2231 default. Use -p <url> to change.
2231 default. Use -p <url> to change.
2232
2232
2233 The patch directory must be a nested Mercurial repository, as
2233 The patch directory must be a nested Mercurial repository, as
2234 would be created by :hg:`init --mq`.
2234 would be created by :hg:`init --mq`.
2235
2235
2236 Return 0 on success.
2236 Return 0 on success.
2237 '''
2237 '''
2238 def patchdir(repo):
2238 def patchdir(repo):
2239 """compute a patch repo url from a repo object"""
2239 """compute a patch repo url from a repo object"""
2240 url = repo.url()
2240 url = repo.url()
2241 if url.endswith('/'):
2241 if url.endswith('/'):
2242 url = url[:-1]
2242 url = url[:-1]
2243 return url + '/.hg/patches'
2243 return url + '/.hg/patches'
2244
2244
2245 # main repo (destination and sources)
2245 # main repo (destination and sources)
2246 if dest is None:
2246 if dest is None:
2247 dest = hg.defaultdest(source)
2247 dest = hg.defaultdest(source)
2248 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
2248 sr = hg.peer(ui, opts, ui.expandpath(source))
2249
2249
2250 # patches repo (source only)
2250 # patches repo (source only)
2251 if opts.get('patches'):
2251 if opts.get('patches'):
2252 patchespath = ui.expandpath(opts.get('patches'))
2252 patchespath = ui.expandpath(opts.get('patches'))
2253 else:
2253 else:
2254 patchespath = patchdir(sr)
2254 patchespath = patchdir(sr)
2255 try:
2255 try:
2256 hg.repository(ui, patchespath)
2256 hg.peer(ui, opts, patchespath)
2257 except error.RepoError:
2257 except error.RepoError:
2258 raise util.Abort(_('versioned patch repository not found'
2258 raise util.Abort(_('versioned patch repository not found'
2259 ' (see init --mq)'))
2259 ' (see init --mq)'))
2260 qbase, destrev = None, None
2260 qbase, destrev = None, None
2261 if sr.local():
2261 if sr.local():
2262 if sr.mq.applied and sr[qbase].phase() != phases.secret:
2262 repo = sr.local()
2263 qbase = sr.mq.applied[0].node
2263 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2264 qbase = repo.mq.applied[0].node
2264 if not hg.islocal(dest):
2265 if not hg.islocal(dest):
2265 heads = set(sr.heads())
2266 heads = set(repo.heads())
2266 destrev = list(heads.difference(sr.heads(qbase)))
2267 destrev = list(heads.difference(repo.heads(qbase)))
2267 destrev.append(sr.changelog.parents(qbase)[0])
2268 destrev.append(repo.changelog.parents(qbase)[0])
2268 elif sr.capable('lookup'):
2269 elif sr.capable('lookup'):
2269 try:
2270 try:
2270 qbase = sr.lookup('qbase')
2271 qbase = sr.lookup('qbase')
2271 except error.RepoError:
2272 except error.RepoError:
2272 pass
2273 pass
2273
2274
2274 ui.note(_('cloning main repository\n'))
2275 ui.note(_('cloning main repository\n'))
2275 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2276 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2276 pull=opts.get('pull'),
2277 pull=opts.get('pull'),
2277 rev=destrev,
2278 rev=destrev,
2278 update=False,
2279 update=False,
2279 stream=opts.get('uncompressed'))
2280 stream=opts.get('uncompressed'))
2280
2281
2281 ui.note(_('cloning patch repository\n'))
2282 ui.note(_('cloning patch repository\n'))
2282 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2283 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2283 pull=opts.get('pull'), update=not opts.get('noupdate'),
2284 pull=opts.get('pull'), update=not opts.get('noupdate'),
2284 stream=opts.get('uncompressed'))
2285 stream=opts.get('uncompressed'))
2285
2286
2286 if dr.local():
2287 if dr.local():
2288 repo = dr.local()
2287 if qbase:
2289 if qbase:
2288 ui.note(_('stripping applied patches from destination '
2290 ui.note(_('stripping applied patches from destination '
2289 'repository\n'))
2291 'repository\n'))
2290 dr.mq.strip(dr, [qbase], update=False, backup=None)
2292 repo.mq.strip(repo, [qbase], update=False, backup=None)
2291 if not opts.get('noupdate'):
2293 if not opts.get('noupdate'):
2292 ui.note(_('updating destination repository\n'))
2294 ui.note(_('updating destination repository\n'))
2293 hg.update(dr, dr.changelog.tip())
2295 hg.update(repo, repo.changelog.tip())
2294
2296
2295 @command("qcommit|qci",
2297 @command("qcommit|qci",
2296 commands.table["^commit|ci"][1],
2298 commands.table["^commit|ci"][1],
2297 _('hg qcommit [OPTION]... [FILE]...'))
2299 _('hg qcommit [OPTION]... [FILE]...'))
2298 def commit(ui, repo, *pats, **opts):
2300 def commit(ui, repo, *pats, **opts):
2299 """commit changes in the queue repository (DEPRECATED)
2301 """commit changes in the queue repository (DEPRECATED)
2300
2302
2301 This command is deprecated; use :hg:`commit --mq` instead."""
2303 This command is deprecated; use :hg:`commit --mq` instead."""
2302 q = repo.mq
2304 q = repo.mq
2303 r = q.qrepo()
2305 r = q.qrepo()
2304 if not r:
2306 if not r:
2305 raise util.Abort('no queue repository')
2307 raise util.Abort('no queue repository')
2306 commands.commit(r.ui, r, *pats, **opts)
2308 commands.commit(r.ui, r, *pats, **opts)
2307
2309
2308 @command("qseries",
2310 @command("qseries",
2309 [('m', 'missing', None, _('print patches not in series')),
2311 [('m', 'missing', None, _('print patches not in series')),
2310 ] + seriesopts,
2312 ] + seriesopts,
2311 _('hg qseries [-ms]'))
2313 _('hg qseries [-ms]'))
2312 def series(ui, repo, **opts):
2314 def series(ui, repo, **opts):
2313 """print the entire series file
2315 """print the entire series file
2314
2316
2315 Returns 0 on success."""
2317 Returns 0 on success."""
2316 repo.mq.qseries(repo, missing=opts.get('missing'),
2318 repo.mq.qseries(repo, missing=opts.get('missing'),
2317 summary=opts.get('summary'))
2319 summary=opts.get('summary'))
2318 return 0
2320 return 0
2319
2321
2320 @command("qtop", seriesopts, _('hg qtop [-s]'))
2322 @command("qtop", seriesopts, _('hg qtop [-s]'))
2321 def top(ui, repo, **opts):
2323 def top(ui, repo, **opts):
2322 """print the name of the current patch
2324 """print the name of the current patch
2323
2325
2324 Returns 0 on success."""
2326 Returns 0 on success."""
2325 q = repo.mq
2327 q = repo.mq
2326 t = q.applied and q.seriesend(True) or 0
2328 t = q.applied and q.seriesend(True) or 0
2327 if t:
2329 if t:
2328 q.qseries(repo, start=t - 1, length=1, status='A',
2330 q.qseries(repo, start=t - 1, length=1, status='A',
2329 summary=opts.get('summary'))
2331 summary=opts.get('summary'))
2330 else:
2332 else:
2331 ui.write(_("no patches applied\n"))
2333 ui.write(_("no patches applied\n"))
2332 return 1
2334 return 1
2333
2335
2334 @command("qnext", seriesopts, _('hg qnext [-s]'))
2336 @command("qnext", seriesopts, _('hg qnext [-s]'))
2335 def next(ui, repo, **opts):
2337 def next(ui, repo, **opts):
2336 """print the name of the next pushable patch
2338 """print the name of the next pushable patch
2337
2339
2338 Returns 0 on success."""
2340 Returns 0 on success."""
2339 q = repo.mq
2341 q = repo.mq
2340 end = q.seriesend()
2342 end = q.seriesend()
2341 if end == len(q.series):
2343 if end == len(q.series):
2342 ui.write(_("all patches applied\n"))
2344 ui.write(_("all patches applied\n"))
2343 return 1
2345 return 1
2344 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2346 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2345
2347
2346 @command("qprev", seriesopts, _('hg qprev [-s]'))
2348 @command("qprev", seriesopts, _('hg qprev [-s]'))
2347 def prev(ui, repo, **opts):
2349 def prev(ui, repo, **opts):
2348 """print the name of the preceding applied patch
2350 """print the name of the preceding applied patch
2349
2351
2350 Returns 0 on success."""
2352 Returns 0 on success."""
2351 q = repo.mq
2353 q = repo.mq
2352 l = len(q.applied)
2354 l = len(q.applied)
2353 if l == 1:
2355 if l == 1:
2354 ui.write(_("only one patch applied\n"))
2356 ui.write(_("only one patch applied\n"))
2355 return 1
2357 return 1
2356 if not l:
2358 if not l:
2357 ui.write(_("no patches applied\n"))
2359 ui.write(_("no patches applied\n"))
2358 return 1
2360 return 1
2359 idx = q.series.index(q.applied[-2].name)
2361 idx = q.series.index(q.applied[-2].name)
2360 q.qseries(repo, start=idx, length=1, status='A',
2362 q.qseries(repo, start=idx, length=1, status='A',
2361 summary=opts.get('summary'))
2363 summary=opts.get('summary'))
2362
2364
2363 def setupheaderopts(ui, opts):
2365 def setupheaderopts(ui, opts):
2364 if not opts.get('user') and opts.get('currentuser'):
2366 if not opts.get('user') and opts.get('currentuser'):
2365 opts['user'] = ui.username()
2367 opts['user'] = ui.username()
2366 if not opts.get('date') and opts.get('currentdate'):
2368 if not opts.get('date') and opts.get('currentdate'):
2367 opts['date'] = "%d %d" % util.makedate()
2369 opts['date'] = "%d %d" % util.makedate()
2368
2370
2369 @command("^qnew",
2371 @command("^qnew",
2370 [('e', 'edit', None, _('edit commit message')),
2372 [('e', 'edit', None, _('edit commit message')),
2371 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2373 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2372 ('g', 'git', None, _('use git extended diff format')),
2374 ('g', 'git', None, _('use git extended diff format')),
2373 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2375 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2374 ('u', 'user', '',
2376 ('u', 'user', '',
2375 _('add "From: <USER>" to patch'), _('USER')),
2377 _('add "From: <USER>" to patch'), _('USER')),
2376 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2378 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2377 ('d', 'date', '',
2379 ('d', 'date', '',
2378 _('add "Date: <DATE>" to patch'), _('DATE'))
2380 _('add "Date: <DATE>" to patch'), _('DATE'))
2379 ] + commands.walkopts + commands.commitopts,
2381 ] + commands.walkopts + commands.commitopts,
2380 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2382 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2381 def new(ui, repo, patch, *args, **opts):
2383 def new(ui, repo, patch, *args, **opts):
2382 """create a new patch
2384 """create a new patch
2383
2385
2384 qnew creates a new patch on top of the currently-applied patch (if
2386 qnew creates a new patch on top of the currently-applied patch (if
2385 any). The patch will be initialized with any outstanding changes
2387 any). The patch will be initialized with any outstanding changes
2386 in the working directory. You may also use -I/--include,
2388 in the working directory. You may also use -I/--include,
2387 -X/--exclude, and/or a list of files after the patch name to add
2389 -X/--exclude, and/or a list of files after the patch name to add
2388 only changes to matching files to the new patch, leaving the rest
2390 only changes to matching files to the new patch, leaving the rest
2389 as uncommitted modifications.
2391 as uncommitted modifications.
2390
2392
2391 -u/--user and -d/--date can be used to set the (given) user and
2393 -u/--user and -d/--date can be used to set the (given) user and
2392 date, respectively. -U/--currentuser and -D/--currentdate set user
2394 date, respectively. -U/--currentuser and -D/--currentdate set user
2393 to current user and date to current date.
2395 to current user and date to current date.
2394
2396
2395 -e/--edit, -m/--message or -l/--logfile set the patch header as
2397 -e/--edit, -m/--message or -l/--logfile set the patch header as
2396 well as the commit message. If none is specified, the header is
2398 well as the commit message. If none is specified, the header is
2397 empty and the commit message is '[mq]: PATCH'.
2399 empty and the commit message is '[mq]: PATCH'.
2398
2400
2399 Use the -g/--git option to keep the patch in the git extended diff
2401 Use the -g/--git option to keep the patch in the git extended diff
2400 format. Read the diffs help topic for more information on why this
2402 format. Read the diffs help topic for more information on why this
2401 is important for preserving permission changes and copy/rename
2403 is important for preserving permission changes and copy/rename
2402 information.
2404 information.
2403
2405
2404 Returns 0 on successful creation of a new patch.
2406 Returns 0 on successful creation of a new patch.
2405 """
2407 """
2406 msg = cmdutil.logmessage(ui, opts)
2408 msg = cmdutil.logmessage(ui, opts)
2407 def getmsg():
2409 def getmsg():
2408 return ui.edit(msg, opts.get('user') or ui.username())
2410 return ui.edit(msg, opts.get('user') or ui.username())
2409 q = repo.mq
2411 q = repo.mq
2410 opts['msg'] = msg
2412 opts['msg'] = msg
2411 if opts.get('edit'):
2413 if opts.get('edit'):
2412 opts['msg'] = getmsg
2414 opts['msg'] = getmsg
2413 else:
2415 else:
2414 opts['msg'] = msg
2416 opts['msg'] = msg
2415 setupheaderopts(ui, opts)
2417 setupheaderopts(ui, opts)
2416 q.new(repo, patch, *args, **opts)
2418 q.new(repo, patch, *args, **opts)
2417 q.savedirty()
2419 q.savedirty()
2418 return 0
2420 return 0
2419
2421
2420 @command("^qrefresh",
2422 @command("^qrefresh",
2421 [('e', 'edit', None, _('edit commit message')),
2423 [('e', 'edit', None, _('edit commit message')),
2422 ('g', 'git', None, _('use git extended diff format')),
2424 ('g', 'git', None, _('use git extended diff format')),
2423 ('s', 'short', None,
2425 ('s', 'short', None,
2424 _('refresh only files already in the patch and specified files')),
2426 _('refresh only files already in the patch and specified files')),
2425 ('U', 'currentuser', None,
2427 ('U', 'currentuser', None,
2426 _('add/update author field in patch with current user')),
2428 _('add/update author field in patch with current user')),
2427 ('u', 'user', '',
2429 ('u', 'user', '',
2428 _('add/update author field in patch with given user'), _('USER')),
2430 _('add/update author field in patch with given user'), _('USER')),
2429 ('D', 'currentdate', None,
2431 ('D', 'currentdate', None,
2430 _('add/update date field in patch with current date')),
2432 _('add/update date field in patch with current date')),
2431 ('d', 'date', '',
2433 ('d', 'date', '',
2432 _('add/update date field in patch with given date'), _('DATE'))
2434 _('add/update date field in patch with given date'), _('DATE'))
2433 ] + commands.walkopts + commands.commitopts,
2435 ] + commands.walkopts + commands.commitopts,
2434 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2436 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2435 def refresh(ui, repo, *pats, **opts):
2437 def refresh(ui, repo, *pats, **opts):
2436 """update the current patch
2438 """update the current patch
2437
2439
2438 If any file patterns are provided, the refreshed patch will
2440 If any file patterns are provided, the refreshed patch will
2439 contain only the modifications that match those patterns; the
2441 contain only the modifications that match those patterns; the
2440 remaining modifications will remain in the working directory.
2442 remaining modifications will remain in the working directory.
2441
2443
2442 If -s/--short is specified, files currently included in the patch
2444 If -s/--short is specified, files currently included in the patch
2443 will be refreshed just like matched files and remain in the patch.
2445 will be refreshed just like matched files and remain in the patch.
2444
2446
2445 If -e/--edit is specified, Mercurial will start your configured editor for
2447 If -e/--edit is specified, Mercurial will start your configured editor for
2446 you to enter a message. In case qrefresh fails, you will find a backup of
2448 you to enter a message. In case qrefresh fails, you will find a backup of
2447 your message in ``.hg/last-message.txt``.
2449 your message in ``.hg/last-message.txt``.
2448
2450
2449 hg add/remove/copy/rename work as usual, though you might want to
2451 hg add/remove/copy/rename work as usual, though you might want to
2450 use git-style patches (-g/--git or [diff] git=1) to track copies
2452 use git-style patches (-g/--git or [diff] git=1) to track copies
2451 and renames. See the diffs help topic for more information on the
2453 and renames. See the diffs help topic for more information on the
2452 git diff format.
2454 git diff format.
2453
2455
2454 Returns 0 on success.
2456 Returns 0 on success.
2455 """
2457 """
2456 q = repo.mq
2458 q = repo.mq
2457 message = cmdutil.logmessage(ui, opts)
2459 message = cmdutil.logmessage(ui, opts)
2458 if opts.get('edit'):
2460 if opts.get('edit'):
2459 if not q.applied:
2461 if not q.applied:
2460 ui.write(_("no patches applied\n"))
2462 ui.write(_("no patches applied\n"))
2461 return 1
2463 return 1
2462 if message:
2464 if message:
2463 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2465 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2464 patch = q.applied[-1].name
2466 patch = q.applied[-1].name
2465 ph = patchheader(q.join(patch), q.plainmode)
2467 ph = patchheader(q.join(patch), q.plainmode)
2466 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2468 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2467 # We don't want to lose the patch message if qrefresh fails (issue2062)
2469 # We don't want to lose the patch message if qrefresh fails (issue2062)
2468 repo.savecommitmessage(message)
2470 repo.savecommitmessage(message)
2469 setupheaderopts(ui, opts)
2471 setupheaderopts(ui, opts)
2470 wlock = repo.wlock()
2472 wlock = repo.wlock()
2471 try:
2473 try:
2472 ret = q.refresh(repo, pats, msg=message, **opts)
2474 ret = q.refresh(repo, pats, msg=message, **opts)
2473 q.savedirty()
2475 q.savedirty()
2474 return ret
2476 return ret
2475 finally:
2477 finally:
2476 wlock.release()
2478 wlock.release()
2477
2479
2478 @command("^qdiff",
2480 @command("^qdiff",
2479 commands.diffopts + commands.diffopts2 + commands.walkopts,
2481 commands.diffopts + commands.diffopts2 + commands.walkopts,
2480 _('hg qdiff [OPTION]... [FILE]...'))
2482 _('hg qdiff [OPTION]... [FILE]...'))
2481 def diff(ui, repo, *pats, **opts):
2483 def diff(ui, repo, *pats, **opts):
2482 """diff of the current patch and subsequent modifications
2484 """diff of the current patch and subsequent modifications
2483
2485
2484 Shows a diff which includes the current patch as well as any
2486 Shows a diff which includes the current patch as well as any
2485 changes which have been made in the working directory since the
2487 changes which have been made in the working directory since the
2486 last refresh (thus showing what the current patch would become
2488 last refresh (thus showing what the current patch would become
2487 after a qrefresh).
2489 after a qrefresh).
2488
2490
2489 Use :hg:`diff` if you only want to see the changes made since the
2491 Use :hg:`diff` if you only want to see the changes made since the
2490 last qrefresh, or :hg:`export qtip` if you want to see changes
2492 last qrefresh, or :hg:`export qtip` if you want to see changes
2491 made by the current patch without including changes made since the
2493 made by the current patch without including changes made since the
2492 qrefresh.
2494 qrefresh.
2493
2495
2494 Returns 0 on success.
2496 Returns 0 on success.
2495 """
2497 """
2496 repo.mq.diff(repo, pats, opts)
2498 repo.mq.diff(repo, pats, opts)
2497 return 0
2499 return 0
2498
2500
2499 @command('qfold',
2501 @command('qfold',
2500 [('e', 'edit', None, _('edit patch header')),
2502 [('e', 'edit', None, _('edit patch header')),
2501 ('k', 'keep', None, _('keep folded patch files')),
2503 ('k', 'keep', None, _('keep folded patch files')),
2502 ] + commands.commitopts,
2504 ] + commands.commitopts,
2503 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2505 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2504 def fold(ui, repo, *files, **opts):
2506 def fold(ui, repo, *files, **opts):
2505 """fold the named patches into the current patch
2507 """fold the named patches into the current patch
2506
2508
2507 Patches must not yet be applied. Each patch will be successively
2509 Patches must not yet be applied. Each patch will be successively
2508 applied to the current patch in the order given. If all the
2510 applied to the current patch in the order given. If all the
2509 patches apply successfully, the current patch will be refreshed
2511 patches apply successfully, the current patch will be refreshed
2510 with the new cumulative patch, and the folded patches will be
2512 with the new cumulative patch, and the folded patches will be
2511 deleted. With -k/--keep, the folded patch files will not be
2513 deleted. With -k/--keep, the folded patch files will not be
2512 removed afterwards.
2514 removed afterwards.
2513
2515
2514 The header for each folded patch will be concatenated with the
2516 The header for each folded patch will be concatenated with the
2515 current patch header, separated by a line of ``* * *``.
2517 current patch header, separated by a line of ``* * *``.
2516
2518
2517 Returns 0 on success."""
2519 Returns 0 on success."""
2518 q = repo.mq
2520 q = repo.mq
2519 if not files:
2521 if not files:
2520 raise util.Abort(_('qfold requires at least one patch name'))
2522 raise util.Abort(_('qfold requires at least one patch name'))
2521 if not q.checktoppatch(repo)[0]:
2523 if not q.checktoppatch(repo)[0]:
2522 raise util.Abort(_('no patches applied'))
2524 raise util.Abort(_('no patches applied'))
2523 q.checklocalchanges(repo)
2525 q.checklocalchanges(repo)
2524
2526
2525 message = cmdutil.logmessage(ui, opts)
2527 message = cmdutil.logmessage(ui, opts)
2526 if opts.get('edit'):
2528 if opts.get('edit'):
2527 if message:
2529 if message:
2528 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2530 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2529
2531
2530 parent = q.lookup('qtip')
2532 parent = q.lookup('qtip')
2531 patches = []
2533 patches = []
2532 messages = []
2534 messages = []
2533 for f in files:
2535 for f in files:
2534 p = q.lookup(f)
2536 p = q.lookup(f)
2535 if p in patches or p == parent:
2537 if p in patches or p == parent:
2536 ui.warn(_('skipping already folded patch %s\n') % p)
2538 ui.warn(_('skipping already folded patch %s\n') % p)
2537 if q.isapplied(p):
2539 if q.isapplied(p):
2538 raise util.Abort(_('qfold cannot fold already applied patch %s')
2540 raise util.Abort(_('qfold cannot fold already applied patch %s')
2539 % p)
2541 % p)
2540 patches.append(p)
2542 patches.append(p)
2541
2543
2542 for p in patches:
2544 for p in patches:
2543 if not message:
2545 if not message:
2544 ph = patchheader(q.join(p), q.plainmode)
2546 ph = patchheader(q.join(p), q.plainmode)
2545 if ph.message:
2547 if ph.message:
2546 messages.append(ph.message)
2548 messages.append(ph.message)
2547 pf = q.join(p)
2549 pf = q.join(p)
2548 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2550 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2549 if not patchsuccess:
2551 if not patchsuccess:
2550 raise util.Abort(_('error folding patch %s') % p)
2552 raise util.Abort(_('error folding patch %s') % p)
2551
2553
2552 if not message:
2554 if not message:
2553 ph = patchheader(q.join(parent), q.plainmode)
2555 ph = patchheader(q.join(parent), q.plainmode)
2554 message, user = ph.message, ph.user
2556 message, user = ph.message, ph.user
2555 for msg in messages:
2557 for msg in messages:
2556 message.append('* * *')
2558 message.append('* * *')
2557 message.extend(msg)
2559 message.extend(msg)
2558 message = '\n'.join(message)
2560 message = '\n'.join(message)
2559
2561
2560 if opts.get('edit'):
2562 if opts.get('edit'):
2561 message = ui.edit(message, user or ui.username())
2563 message = ui.edit(message, user or ui.username())
2562
2564
2563 diffopts = q.patchopts(q.diffopts(), *patches)
2565 diffopts = q.patchopts(q.diffopts(), *patches)
2564 wlock = repo.wlock()
2566 wlock = repo.wlock()
2565 try:
2567 try:
2566 q.refresh(repo, msg=message, git=diffopts.git)
2568 q.refresh(repo, msg=message, git=diffopts.git)
2567 q.delete(repo, patches, opts)
2569 q.delete(repo, patches, opts)
2568 q.savedirty()
2570 q.savedirty()
2569 finally:
2571 finally:
2570 wlock.release()
2572 wlock.release()
2571
2573
2572 @command("qgoto",
2574 @command("qgoto",
2573 [('', 'keep-changes', None,
2575 [('', 'keep-changes', None,
2574 _('tolerate non-conflicting local changes')),
2576 _('tolerate non-conflicting local changes')),
2575 ('f', 'force', None, _('overwrite any local changes')),
2577 ('f', 'force', None, _('overwrite any local changes')),
2576 ('', 'no-backup', None, _('do not save backup copies of files'))],
2578 ('', 'no-backup', None, _('do not save backup copies of files'))],
2577 _('hg qgoto [OPTION]... PATCH'))
2579 _('hg qgoto [OPTION]... PATCH'))
2578 def goto(ui, repo, patch, **opts):
2580 def goto(ui, repo, patch, **opts):
2579 '''push or pop patches until named patch is at top of stack
2581 '''push or pop patches until named patch is at top of stack
2580
2582
2581 Returns 0 on success.'''
2583 Returns 0 on success.'''
2582 opts = fixkeepchangesopts(ui, opts)
2584 opts = fixkeepchangesopts(ui, opts)
2583 q = repo.mq
2585 q = repo.mq
2584 patch = q.lookup(patch)
2586 patch = q.lookup(patch)
2585 nobackup = opts.get('no_backup')
2587 nobackup = opts.get('no_backup')
2586 keepchanges = opts.get('keep_changes')
2588 keepchanges = opts.get('keep_changes')
2587 if q.isapplied(patch):
2589 if q.isapplied(patch):
2588 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2590 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2589 keepchanges=keepchanges)
2591 keepchanges=keepchanges)
2590 else:
2592 else:
2591 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2593 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2592 keepchanges=keepchanges)
2594 keepchanges=keepchanges)
2593 q.savedirty()
2595 q.savedirty()
2594 return ret
2596 return ret
2595
2597
2596 @command("qguard",
2598 @command("qguard",
2597 [('l', 'list', None, _('list all patches and guards')),
2599 [('l', 'list', None, _('list all patches and guards')),
2598 ('n', 'none', None, _('drop all guards'))],
2600 ('n', 'none', None, _('drop all guards'))],
2599 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2601 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2600 def guard(ui, repo, *args, **opts):
2602 def guard(ui, repo, *args, **opts):
2601 '''set or print guards for a patch
2603 '''set or print guards for a patch
2602
2604
2603 Guards control whether a patch can be pushed. A patch with no
2605 Guards control whether a patch can be pushed. A patch with no
2604 guards is always pushed. A patch with a positive guard ("+foo") is
2606 guards is always pushed. A patch with a positive guard ("+foo") is
2605 pushed only if the :hg:`qselect` command has activated it. A patch with
2607 pushed only if the :hg:`qselect` command has activated it. A patch with
2606 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2608 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2607 has activated it.
2609 has activated it.
2608
2610
2609 With no arguments, print the currently active guards.
2611 With no arguments, print the currently active guards.
2610 With arguments, set guards for the named patch.
2612 With arguments, set guards for the named patch.
2611
2613
2612 .. note::
2614 .. note::
2613 Specifying negative guards now requires '--'.
2615 Specifying negative guards now requires '--'.
2614
2616
2615 To set guards on another patch::
2617 To set guards on another patch::
2616
2618
2617 hg qguard other.patch -- +2.6.17 -stable
2619 hg qguard other.patch -- +2.6.17 -stable
2618
2620
2619 Returns 0 on success.
2621 Returns 0 on success.
2620 '''
2622 '''
2621 def status(idx):
2623 def status(idx):
2622 guards = q.seriesguards[idx] or ['unguarded']
2624 guards = q.seriesguards[idx] or ['unguarded']
2623 if q.series[idx] in applied:
2625 if q.series[idx] in applied:
2624 state = 'applied'
2626 state = 'applied'
2625 elif q.pushable(idx)[0]:
2627 elif q.pushable(idx)[0]:
2626 state = 'unapplied'
2628 state = 'unapplied'
2627 else:
2629 else:
2628 state = 'guarded'
2630 state = 'guarded'
2629 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2631 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2630 ui.write('%s: ' % ui.label(q.series[idx], label))
2632 ui.write('%s: ' % ui.label(q.series[idx], label))
2631
2633
2632 for i, guard in enumerate(guards):
2634 for i, guard in enumerate(guards):
2633 if guard.startswith('+'):
2635 if guard.startswith('+'):
2634 ui.write(guard, label='qguard.positive')
2636 ui.write(guard, label='qguard.positive')
2635 elif guard.startswith('-'):
2637 elif guard.startswith('-'):
2636 ui.write(guard, label='qguard.negative')
2638 ui.write(guard, label='qguard.negative')
2637 else:
2639 else:
2638 ui.write(guard, label='qguard.unguarded')
2640 ui.write(guard, label='qguard.unguarded')
2639 if i != len(guards) - 1:
2641 if i != len(guards) - 1:
2640 ui.write(' ')
2642 ui.write(' ')
2641 ui.write('\n')
2643 ui.write('\n')
2642 q = repo.mq
2644 q = repo.mq
2643 applied = set(p.name for p in q.applied)
2645 applied = set(p.name for p in q.applied)
2644 patch = None
2646 patch = None
2645 args = list(args)
2647 args = list(args)
2646 if opts.get('list'):
2648 if opts.get('list'):
2647 if args or opts.get('none'):
2649 if args or opts.get('none'):
2648 raise util.Abort(_('cannot mix -l/--list with options or '
2650 raise util.Abort(_('cannot mix -l/--list with options or '
2649 'arguments'))
2651 'arguments'))
2650 for i in xrange(len(q.series)):
2652 for i in xrange(len(q.series)):
2651 status(i)
2653 status(i)
2652 return
2654 return
2653 if not args or args[0][0:1] in '-+':
2655 if not args or args[0][0:1] in '-+':
2654 if not q.applied:
2656 if not q.applied:
2655 raise util.Abort(_('no patches applied'))
2657 raise util.Abort(_('no patches applied'))
2656 patch = q.applied[-1].name
2658 patch = q.applied[-1].name
2657 if patch is None and args[0][0:1] not in '-+':
2659 if patch is None and args[0][0:1] not in '-+':
2658 patch = args.pop(0)
2660 patch = args.pop(0)
2659 if patch is None:
2661 if patch is None:
2660 raise util.Abort(_('no patch to work with'))
2662 raise util.Abort(_('no patch to work with'))
2661 if args or opts.get('none'):
2663 if args or opts.get('none'):
2662 idx = q.findseries(patch)
2664 idx = q.findseries(patch)
2663 if idx is None:
2665 if idx is None:
2664 raise util.Abort(_('no patch named %s') % patch)
2666 raise util.Abort(_('no patch named %s') % patch)
2665 q.setguards(idx, args)
2667 q.setguards(idx, args)
2666 q.savedirty()
2668 q.savedirty()
2667 else:
2669 else:
2668 status(q.series.index(q.lookup(patch)))
2670 status(q.series.index(q.lookup(patch)))
2669
2671
2670 @command("qheader", [], _('hg qheader [PATCH]'))
2672 @command("qheader", [], _('hg qheader [PATCH]'))
2671 def header(ui, repo, patch=None):
2673 def header(ui, repo, patch=None):
2672 """print the header of the topmost or specified patch
2674 """print the header of the topmost or specified patch
2673
2675
2674 Returns 0 on success."""
2676 Returns 0 on success."""
2675 q = repo.mq
2677 q = repo.mq
2676
2678
2677 if patch:
2679 if patch:
2678 patch = q.lookup(patch)
2680 patch = q.lookup(patch)
2679 else:
2681 else:
2680 if not q.applied:
2682 if not q.applied:
2681 ui.write(_('no patches applied\n'))
2683 ui.write(_('no patches applied\n'))
2682 return 1
2684 return 1
2683 patch = q.lookup('qtip')
2685 patch = q.lookup('qtip')
2684 ph = patchheader(q.join(patch), q.plainmode)
2686 ph = patchheader(q.join(patch), q.plainmode)
2685
2687
2686 ui.write('\n'.join(ph.message) + '\n')
2688 ui.write('\n'.join(ph.message) + '\n')
2687
2689
2688 def lastsavename(path):
2690 def lastsavename(path):
2689 (directory, base) = os.path.split(path)
2691 (directory, base) = os.path.split(path)
2690 names = os.listdir(directory)
2692 names = os.listdir(directory)
2691 namere = re.compile("%s.([0-9]+)" % base)
2693 namere = re.compile("%s.([0-9]+)" % base)
2692 maxindex = None
2694 maxindex = None
2693 maxname = None
2695 maxname = None
2694 for f in names:
2696 for f in names:
2695 m = namere.match(f)
2697 m = namere.match(f)
2696 if m:
2698 if m:
2697 index = int(m.group(1))
2699 index = int(m.group(1))
2698 if maxindex is None or index > maxindex:
2700 if maxindex is None or index > maxindex:
2699 maxindex = index
2701 maxindex = index
2700 maxname = f
2702 maxname = f
2701 if maxname:
2703 if maxname:
2702 return (os.path.join(directory, maxname), maxindex)
2704 return (os.path.join(directory, maxname), maxindex)
2703 return (None, None)
2705 return (None, None)
2704
2706
2705 def savename(path):
2707 def savename(path):
2706 (last, index) = lastsavename(path)
2708 (last, index) = lastsavename(path)
2707 if last is None:
2709 if last is None:
2708 index = 0
2710 index = 0
2709 newpath = path + ".%d" % (index + 1)
2711 newpath = path + ".%d" % (index + 1)
2710 return newpath
2712 return newpath
2711
2713
2712 @command("^qpush",
2714 @command("^qpush",
2713 [('', 'keep-changes', None,
2715 [('', 'keep-changes', None,
2714 _('tolerate non-conflicting local changes')),
2716 _('tolerate non-conflicting local changes')),
2715 ('f', 'force', None, _('apply on top of local changes')),
2717 ('f', 'force', None, _('apply on top of local changes')),
2716 ('e', 'exact', None,
2718 ('e', 'exact', None,
2717 _('apply the target patch to its recorded parent')),
2719 _('apply the target patch to its recorded parent')),
2718 ('l', 'list', None, _('list patch name in commit text')),
2720 ('l', 'list', None, _('list patch name in commit text')),
2719 ('a', 'all', None, _('apply all patches')),
2721 ('a', 'all', None, _('apply all patches')),
2720 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2722 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2721 ('n', 'name', '',
2723 ('n', 'name', '',
2722 _('merge queue name (DEPRECATED)'), _('NAME')),
2724 _('merge queue name (DEPRECATED)'), _('NAME')),
2723 ('', 'move', None,
2725 ('', 'move', None,
2724 _('reorder patch series and apply only the patch')),
2726 _('reorder patch series and apply only the patch')),
2725 ('', 'no-backup', None, _('do not save backup copies of files'))],
2727 ('', 'no-backup', None, _('do not save backup copies of files'))],
2726 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2728 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2727 def push(ui, repo, patch=None, **opts):
2729 def push(ui, repo, patch=None, **opts):
2728 """push the next patch onto the stack
2730 """push the next patch onto the stack
2729
2731
2730 By default, abort if the working directory contains uncommitted
2732 By default, abort if the working directory contains uncommitted
2731 changes. With --keep-changes, abort only if the uncommitted files
2733 changes. With --keep-changes, abort only if the uncommitted files
2732 overlap with patched files. With -f/--force, backup and patch over
2734 overlap with patched files. With -f/--force, backup and patch over
2733 uncommitted changes.
2735 uncommitted changes.
2734
2736
2735 Return 0 on success.
2737 Return 0 on success.
2736 """
2738 """
2737 q = repo.mq
2739 q = repo.mq
2738 mergeq = None
2740 mergeq = None
2739
2741
2740 opts = fixkeepchangesopts(ui, opts)
2742 opts = fixkeepchangesopts(ui, opts)
2741 if opts.get('merge'):
2743 if opts.get('merge'):
2742 if opts.get('name'):
2744 if opts.get('name'):
2743 newpath = repo.join(opts.get('name'))
2745 newpath = repo.join(opts.get('name'))
2744 else:
2746 else:
2745 newpath, i = lastsavename(q.path)
2747 newpath, i = lastsavename(q.path)
2746 if not newpath:
2748 if not newpath:
2747 ui.warn(_("no saved queues found, please use -n\n"))
2749 ui.warn(_("no saved queues found, please use -n\n"))
2748 return 1
2750 return 1
2749 mergeq = queue(ui, repo.path, newpath)
2751 mergeq = queue(ui, repo.path, newpath)
2750 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2752 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2751 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2753 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2752 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2754 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2753 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2755 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2754 keepchanges=opts.get('keep_changes'))
2756 keepchanges=opts.get('keep_changes'))
2755 return ret
2757 return ret
2756
2758
2757 @command("^qpop",
2759 @command("^qpop",
2758 [('a', 'all', None, _('pop all patches')),
2760 [('a', 'all', None, _('pop all patches')),
2759 ('n', 'name', '',
2761 ('n', 'name', '',
2760 _('queue name to pop (DEPRECATED)'), _('NAME')),
2762 _('queue name to pop (DEPRECATED)'), _('NAME')),
2761 ('', 'keep-changes', None,
2763 ('', 'keep-changes', None,
2762 _('tolerate non-conflicting local changes')),
2764 _('tolerate non-conflicting local changes')),
2763 ('f', 'force', None, _('forget any local changes to patched files')),
2765 ('f', 'force', None, _('forget any local changes to patched files')),
2764 ('', 'no-backup', None, _('do not save backup copies of files'))],
2766 ('', 'no-backup', None, _('do not save backup copies of files'))],
2765 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2767 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2766 def pop(ui, repo, patch=None, **opts):
2768 def pop(ui, repo, patch=None, **opts):
2767 """pop the current patch off the stack
2769 """pop the current patch off the stack
2768
2770
2769 Without argument, pops off the top of the patch stack. If given a
2771 Without argument, pops off the top of the patch stack. If given a
2770 patch name, keeps popping off patches until the named patch is at
2772 patch name, keeps popping off patches until the named patch is at
2771 the top of the stack.
2773 the top of the stack.
2772
2774
2773 By default, abort if the working directory contains uncommitted
2775 By default, abort if the working directory contains uncommitted
2774 changes. With --keep-changes, abort only if the uncommitted files
2776 changes. With --keep-changes, abort only if the uncommitted files
2775 overlap with patched files. With -f/--force, backup and discard
2777 overlap with patched files. With -f/--force, backup and discard
2776 changes made to such files.
2778 changes made to such files.
2777
2779
2778 Return 0 on success.
2780 Return 0 on success.
2779 """
2781 """
2780 opts = fixkeepchangesopts(ui, opts)
2782 opts = fixkeepchangesopts(ui, opts)
2781 localupdate = True
2783 localupdate = True
2782 if opts.get('name'):
2784 if opts.get('name'):
2783 q = queue(ui, repo.path, repo.join(opts.get('name')))
2785 q = queue(ui, repo.path, repo.join(opts.get('name')))
2784 ui.warn(_('using patch queue: %s\n') % q.path)
2786 ui.warn(_('using patch queue: %s\n') % q.path)
2785 localupdate = False
2787 localupdate = False
2786 else:
2788 else:
2787 q = repo.mq
2789 q = repo.mq
2788 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2790 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2789 all=opts.get('all'), nobackup=opts.get('no_backup'),
2791 all=opts.get('all'), nobackup=opts.get('no_backup'),
2790 keepchanges=opts.get('keep_changes'))
2792 keepchanges=opts.get('keep_changes'))
2791 q.savedirty()
2793 q.savedirty()
2792 return ret
2794 return ret
2793
2795
2794 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2796 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2795 def rename(ui, repo, patch, name=None, **opts):
2797 def rename(ui, repo, patch, name=None, **opts):
2796 """rename a patch
2798 """rename a patch
2797
2799
2798 With one argument, renames the current patch to PATCH1.
2800 With one argument, renames the current patch to PATCH1.
2799 With two arguments, renames PATCH1 to PATCH2.
2801 With two arguments, renames PATCH1 to PATCH2.
2800
2802
2801 Returns 0 on success."""
2803 Returns 0 on success."""
2802 q = repo.mq
2804 q = repo.mq
2803 if not name:
2805 if not name:
2804 name = patch
2806 name = patch
2805 patch = None
2807 patch = None
2806
2808
2807 if patch:
2809 if patch:
2808 patch = q.lookup(patch)
2810 patch = q.lookup(patch)
2809 else:
2811 else:
2810 if not q.applied:
2812 if not q.applied:
2811 ui.write(_('no patches applied\n'))
2813 ui.write(_('no patches applied\n'))
2812 return
2814 return
2813 patch = q.lookup('qtip')
2815 patch = q.lookup('qtip')
2814 absdest = q.join(name)
2816 absdest = q.join(name)
2815 if os.path.isdir(absdest):
2817 if os.path.isdir(absdest):
2816 name = normname(os.path.join(name, os.path.basename(patch)))
2818 name = normname(os.path.join(name, os.path.basename(patch)))
2817 absdest = q.join(name)
2819 absdest = q.join(name)
2818 q.checkpatchname(name)
2820 q.checkpatchname(name)
2819
2821
2820 ui.note(_('renaming %s to %s\n') % (patch, name))
2822 ui.note(_('renaming %s to %s\n') % (patch, name))
2821 i = q.findseries(patch)
2823 i = q.findseries(patch)
2822 guards = q.guard_re.findall(q.fullseries[i])
2824 guards = q.guard_re.findall(q.fullseries[i])
2823 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2825 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2824 q.parseseries()
2826 q.parseseries()
2825 q.seriesdirty = True
2827 q.seriesdirty = True
2826
2828
2827 info = q.isapplied(patch)
2829 info = q.isapplied(patch)
2828 if info:
2830 if info:
2829 q.applied[info[0]] = statusentry(info[1], name)
2831 q.applied[info[0]] = statusentry(info[1], name)
2830 q.applieddirty = True
2832 q.applieddirty = True
2831
2833
2832 destdir = os.path.dirname(absdest)
2834 destdir = os.path.dirname(absdest)
2833 if not os.path.isdir(destdir):
2835 if not os.path.isdir(destdir):
2834 os.makedirs(destdir)
2836 os.makedirs(destdir)
2835 util.rename(q.join(patch), absdest)
2837 util.rename(q.join(patch), absdest)
2836 r = q.qrepo()
2838 r = q.qrepo()
2837 if r and patch in r.dirstate:
2839 if r and patch in r.dirstate:
2838 wctx = r[None]
2840 wctx = r[None]
2839 wlock = r.wlock()
2841 wlock = r.wlock()
2840 try:
2842 try:
2841 if r.dirstate[patch] == 'a':
2843 if r.dirstate[patch] == 'a':
2842 r.dirstate.drop(patch)
2844 r.dirstate.drop(patch)
2843 r.dirstate.add(name)
2845 r.dirstate.add(name)
2844 else:
2846 else:
2845 wctx.copy(patch, name)
2847 wctx.copy(patch, name)
2846 wctx.forget([patch])
2848 wctx.forget([patch])
2847 finally:
2849 finally:
2848 wlock.release()
2850 wlock.release()
2849
2851
2850 q.savedirty()
2852 q.savedirty()
2851
2853
2852 @command("qrestore",
2854 @command("qrestore",
2853 [('d', 'delete', None, _('delete save entry')),
2855 [('d', 'delete', None, _('delete save entry')),
2854 ('u', 'update', None, _('update queue working directory'))],
2856 ('u', 'update', None, _('update queue working directory'))],
2855 _('hg qrestore [-d] [-u] REV'))
2857 _('hg qrestore [-d] [-u] REV'))
2856 def restore(ui, repo, rev, **opts):
2858 def restore(ui, repo, rev, **opts):
2857 """restore the queue state saved by a revision (DEPRECATED)
2859 """restore the queue state saved by a revision (DEPRECATED)
2858
2860
2859 This command is deprecated, use :hg:`rebase` instead."""
2861 This command is deprecated, use :hg:`rebase` instead."""
2860 rev = repo.lookup(rev)
2862 rev = repo.lookup(rev)
2861 q = repo.mq
2863 q = repo.mq
2862 q.restore(repo, rev, delete=opts.get('delete'),
2864 q.restore(repo, rev, delete=opts.get('delete'),
2863 qupdate=opts.get('update'))
2865 qupdate=opts.get('update'))
2864 q.savedirty()
2866 q.savedirty()
2865 return 0
2867 return 0
2866
2868
2867 @command("qsave",
2869 @command("qsave",
2868 [('c', 'copy', None, _('copy patch directory')),
2870 [('c', 'copy', None, _('copy patch directory')),
2869 ('n', 'name', '',
2871 ('n', 'name', '',
2870 _('copy directory name'), _('NAME')),
2872 _('copy directory name'), _('NAME')),
2871 ('e', 'empty', None, _('clear queue status file')),
2873 ('e', 'empty', None, _('clear queue status file')),
2872 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2874 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2873 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2875 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2874 def save(ui, repo, **opts):
2876 def save(ui, repo, **opts):
2875 """save current queue state (DEPRECATED)
2877 """save current queue state (DEPRECATED)
2876
2878
2877 This command is deprecated, use :hg:`rebase` instead."""
2879 This command is deprecated, use :hg:`rebase` instead."""
2878 q = repo.mq
2880 q = repo.mq
2879 message = cmdutil.logmessage(ui, opts)
2881 message = cmdutil.logmessage(ui, opts)
2880 ret = q.save(repo, msg=message)
2882 ret = q.save(repo, msg=message)
2881 if ret:
2883 if ret:
2882 return ret
2884 return ret
2883 q.savedirty() # save to .hg/patches before copying
2885 q.savedirty() # save to .hg/patches before copying
2884 if opts.get('copy'):
2886 if opts.get('copy'):
2885 path = q.path
2887 path = q.path
2886 if opts.get('name'):
2888 if opts.get('name'):
2887 newpath = os.path.join(q.basepath, opts.get('name'))
2889 newpath = os.path.join(q.basepath, opts.get('name'))
2888 if os.path.exists(newpath):
2890 if os.path.exists(newpath):
2889 if not os.path.isdir(newpath):
2891 if not os.path.isdir(newpath):
2890 raise util.Abort(_('destination %s exists and is not '
2892 raise util.Abort(_('destination %s exists and is not '
2891 'a directory') % newpath)
2893 'a directory') % newpath)
2892 if not opts.get('force'):
2894 if not opts.get('force'):
2893 raise util.Abort(_('destination %s exists, '
2895 raise util.Abort(_('destination %s exists, '
2894 'use -f to force') % newpath)
2896 'use -f to force') % newpath)
2895 else:
2897 else:
2896 newpath = savename(path)
2898 newpath = savename(path)
2897 ui.warn(_("copy %s to %s\n") % (path, newpath))
2899 ui.warn(_("copy %s to %s\n") % (path, newpath))
2898 util.copyfiles(path, newpath)
2900 util.copyfiles(path, newpath)
2899 if opts.get('empty'):
2901 if opts.get('empty'):
2900 del q.applied[:]
2902 del q.applied[:]
2901 q.applieddirty = True
2903 q.applieddirty = True
2902 q.savedirty()
2904 q.savedirty()
2903 return 0
2905 return 0
2904
2906
2905 @command("strip",
2907 @command("strip",
2906 [
2908 [
2907 ('r', 'rev', [], _('strip specified revision (optional, '
2909 ('r', 'rev', [], _('strip specified revision (optional, '
2908 'can specify revisions without this '
2910 'can specify revisions without this '
2909 'option)'), _('REV')),
2911 'option)'), _('REV')),
2910 ('f', 'force', None, _('force removal of changesets, discard '
2912 ('f', 'force', None, _('force removal of changesets, discard '
2911 'uncommitted changes (no backup)')),
2913 'uncommitted changes (no backup)')),
2912 ('b', 'backup', None, _('bundle only changesets with local revision'
2914 ('b', 'backup', None, _('bundle only changesets with local revision'
2913 ' number greater than REV which are not'
2915 ' number greater than REV which are not'
2914 ' descendants of REV (DEPRECATED)')),
2916 ' descendants of REV (DEPRECATED)')),
2915 ('', 'no-backup', None, _('no backups')),
2917 ('', 'no-backup', None, _('no backups')),
2916 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
2918 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
2917 ('n', '', None, _('ignored (DEPRECATED)')),
2919 ('n', '', None, _('ignored (DEPRECATED)')),
2918 ('k', 'keep', None, _("do not modify working copy during strip")),
2920 ('k', 'keep', None, _("do not modify working copy during strip")),
2919 ('B', 'bookmark', '', _("remove revs only reachable from given"
2921 ('B', 'bookmark', '', _("remove revs only reachable from given"
2920 " bookmark"))],
2922 " bookmark"))],
2921 _('hg strip [-k] [-f] [-n] [-B bookmark] [-r] REV...'))
2923 _('hg strip [-k] [-f] [-n] [-B bookmark] [-r] REV...'))
2922 def strip(ui, repo, *revs, **opts):
2924 def strip(ui, repo, *revs, **opts):
2923 """strip changesets and all their descendants from the repository
2925 """strip changesets and all their descendants from the repository
2924
2926
2925 The strip command removes the specified changesets and all their
2927 The strip command removes the specified changesets and all their
2926 descendants. If the working directory has uncommitted changes, the
2928 descendants. If the working directory has uncommitted changes, the
2927 operation is aborted unless the --force flag is supplied, in which
2929 operation is aborted unless the --force flag is supplied, in which
2928 case changes will be discarded.
2930 case changes will be discarded.
2929
2931
2930 If a parent of the working directory is stripped, then the working
2932 If a parent of the working directory is stripped, then the working
2931 directory will automatically be updated to the most recent
2933 directory will automatically be updated to the most recent
2932 available ancestor of the stripped parent after the operation
2934 available ancestor of the stripped parent after the operation
2933 completes.
2935 completes.
2934
2936
2935 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2937 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2936 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2938 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2937 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2939 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2938 where BUNDLE is the bundle file created by the strip. Note that
2940 where BUNDLE is the bundle file created by the strip. Note that
2939 the local revision numbers will in general be different after the
2941 the local revision numbers will in general be different after the
2940 restore.
2942 restore.
2941
2943
2942 Use the --no-backup option to discard the backup bundle once the
2944 Use the --no-backup option to discard the backup bundle once the
2943 operation completes.
2945 operation completes.
2944
2946
2945 Strip is not a history-rewriting operation and can be used on
2947 Strip is not a history-rewriting operation and can be used on
2946 changesets in the public phase. But if the stripped changesets have
2948 changesets in the public phase. But if the stripped changesets have
2947 been pushed to a remote repository you will likely pull them again.
2949 been pushed to a remote repository you will likely pull them again.
2948
2950
2949 Return 0 on success.
2951 Return 0 on success.
2950 """
2952 """
2951 backup = 'all'
2953 backup = 'all'
2952 if opts.get('backup'):
2954 if opts.get('backup'):
2953 backup = 'strip'
2955 backup = 'strip'
2954 elif opts.get('no_backup') or opts.get('nobackup'):
2956 elif opts.get('no_backup') or opts.get('nobackup'):
2955 backup = 'none'
2957 backup = 'none'
2956
2958
2957 cl = repo.changelog
2959 cl = repo.changelog
2958 revs = list(revs) + opts.get('rev')
2960 revs = list(revs) + opts.get('rev')
2959 revs = set(scmutil.revrange(repo, revs))
2961 revs = set(scmutil.revrange(repo, revs))
2960
2962
2961 if opts.get('bookmark'):
2963 if opts.get('bookmark'):
2962 mark = opts.get('bookmark')
2964 mark = opts.get('bookmark')
2963 marks = repo._bookmarks
2965 marks = repo._bookmarks
2964 if mark not in marks:
2966 if mark not in marks:
2965 raise util.Abort(_("bookmark '%s' not found") % mark)
2967 raise util.Abort(_("bookmark '%s' not found") % mark)
2966
2968
2967 # If the requested bookmark is not the only one pointing to a
2969 # If the requested bookmark is not the only one pointing to a
2968 # a revision we have to only delete the bookmark and not strip
2970 # a revision we have to only delete the bookmark and not strip
2969 # anything. revsets cannot detect that case.
2971 # anything. revsets cannot detect that case.
2970 uniquebm = True
2972 uniquebm = True
2971 for m, n in marks.iteritems():
2973 for m, n in marks.iteritems():
2972 if m != mark and n == repo[mark].node():
2974 if m != mark and n == repo[mark].node():
2973 uniquebm = False
2975 uniquebm = False
2974 break
2976 break
2975 if uniquebm:
2977 if uniquebm:
2976 rsrevs = repo.revs("ancestors(bookmark(%s)) - "
2978 rsrevs = repo.revs("ancestors(bookmark(%s)) - "
2977 "ancestors(head() and not bookmark(%s)) - "
2979 "ancestors(head() and not bookmark(%s)) - "
2978 "ancestors(bookmark() and not bookmark(%s))",
2980 "ancestors(bookmark() and not bookmark(%s))",
2979 mark, mark, mark)
2981 mark, mark, mark)
2980 revs.update(set(rsrevs))
2982 revs.update(set(rsrevs))
2981 if not revs:
2983 if not revs:
2982 del marks[mark]
2984 del marks[mark]
2983 repo._writebookmarks(mark)
2985 repo._writebookmarks(mark)
2984 ui.write(_("bookmark '%s' deleted\n") % mark)
2986 ui.write(_("bookmark '%s' deleted\n") % mark)
2985
2987
2986 if not revs:
2988 if not revs:
2987 raise util.Abort(_('empty revision set'))
2989 raise util.Abort(_('empty revision set'))
2988
2990
2989 descendants = set(cl.descendants(revs))
2991 descendants = set(cl.descendants(revs))
2990 strippedrevs = revs.union(descendants)
2992 strippedrevs = revs.union(descendants)
2991 roots = revs.difference(descendants)
2993 roots = revs.difference(descendants)
2992
2994
2993 update = False
2995 update = False
2994 # if one of the wdir parent is stripped we'll need
2996 # if one of the wdir parent is stripped we'll need
2995 # to update away to an earlier revision
2997 # to update away to an earlier revision
2996 for p in repo.dirstate.parents():
2998 for p in repo.dirstate.parents():
2997 if p != nullid and cl.rev(p) in strippedrevs:
2999 if p != nullid and cl.rev(p) in strippedrevs:
2998 update = True
3000 update = True
2999 break
3001 break
3000
3002
3001 rootnodes = set(cl.node(r) for r in roots)
3003 rootnodes = set(cl.node(r) for r in roots)
3002
3004
3003 q = repo.mq
3005 q = repo.mq
3004 if q.applied:
3006 if q.applied:
3005 # refresh queue state if we're about to strip
3007 # refresh queue state if we're about to strip
3006 # applied patches
3008 # applied patches
3007 if cl.rev(repo.lookup('qtip')) in strippedrevs:
3009 if cl.rev(repo.lookup('qtip')) in strippedrevs:
3008 q.applieddirty = True
3010 q.applieddirty = True
3009 start = 0
3011 start = 0
3010 end = len(q.applied)
3012 end = len(q.applied)
3011 for i, statusentry in enumerate(q.applied):
3013 for i, statusentry in enumerate(q.applied):
3012 if statusentry.node in rootnodes:
3014 if statusentry.node in rootnodes:
3013 # if one of the stripped roots is an applied
3015 # if one of the stripped roots is an applied
3014 # patch, only part of the queue is stripped
3016 # patch, only part of the queue is stripped
3015 start = i
3017 start = i
3016 break
3018 break
3017 del q.applied[start:end]
3019 del q.applied[start:end]
3018 q.savedirty()
3020 q.savedirty()
3019
3021
3020 revs = list(rootnodes)
3022 revs = list(rootnodes)
3021 if update and opts.get('keep'):
3023 if update and opts.get('keep'):
3022 wlock = repo.wlock()
3024 wlock = repo.wlock()
3023 try:
3025 try:
3024 urev = repo.mq.qparents(repo, revs[0])
3026 urev = repo.mq.qparents(repo, revs[0])
3025 repo.dirstate.rebuild(urev, repo[urev].manifest())
3027 repo.dirstate.rebuild(urev, repo[urev].manifest())
3026 repo.dirstate.write()
3028 repo.dirstate.write()
3027 update = False
3029 update = False
3028 finally:
3030 finally:
3029 wlock.release()
3031 wlock.release()
3030
3032
3031 if opts.get('bookmark'):
3033 if opts.get('bookmark'):
3032 del marks[mark]
3034 del marks[mark]
3033 repo._writebookmarks(marks)
3035 repo._writebookmarks(marks)
3034 ui.write(_("bookmark '%s' deleted\n") % mark)
3036 ui.write(_("bookmark '%s' deleted\n") % mark)
3035
3037
3036 repo.mq.strip(repo, revs, backup=backup, update=update,
3038 repo.mq.strip(repo, revs, backup=backup, update=update,
3037 force=opts.get('force'))
3039 force=opts.get('force'))
3038
3040
3039 return 0
3041 return 0
3040
3042
3041 @command("qselect",
3043 @command("qselect",
3042 [('n', 'none', None, _('disable all guards')),
3044 [('n', 'none', None, _('disable all guards')),
3043 ('s', 'series', None, _('list all guards in series file')),
3045 ('s', 'series', None, _('list all guards in series file')),
3044 ('', 'pop', None, _('pop to before first guarded applied patch')),
3046 ('', 'pop', None, _('pop to before first guarded applied patch')),
3045 ('', 'reapply', None, _('pop, then reapply patches'))],
3047 ('', 'reapply', None, _('pop, then reapply patches'))],
3046 _('hg qselect [OPTION]... [GUARD]...'))
3048 _('hg qselect [OPTION]... [GUARD]...'))
3047 def select(ui, repo, *args, **opts):
3049 def select(ui, repo, *args, **opts):
3048 '''set or print guarded patches to push
3050 '''set or print guarded patches to push
3049
3051
3050 Use the :hg:`qguard` command to set or print guards on patch, then use
3052 Use the :hg:`qguard` command to set or print guards on patch, then use
3051 qselect to tell mq which guards to use. A patch will be pushed if
3053 qselect to tell mq which guards to use. A patch will be pushed if
3052 it has no guards or any positive guards match the currently
3054 it has no guards or any positive guards match the currently
3053 selected guard, but will not be pushed if any negative guards
3055 selected guard, but will not be pushed if any negative guards
3054 match the current guard. For example::
3056 match the current guard. For example::
3055
3057
3056 qguard foo.patch -- -stable (negative guard)
3058 qguard foo.patch -- -stable (negative guard)
3057 qguard bar.patch +stable (positive guard)
3059 qguard bar.patch +stable (positive guard)
3058 qselect stable
3060 qselect stable
3059
3061
3060 This activates the "stable" guard. mq will skip foo.patch (because
3062 This activates the "stable" guard. mq will skip foo.patch (because
3061 it has a negative match) but push bar.patch (because it has a
3063 it has a negative match) but push bar.patch (because it has a
3062 positive match).
3064 positive match).
3063
3065
3064 With no arguments, prints the currently active guards.
3066 With no arguments, prints the currently active guards.
3065 With one argument, sets the active guard.
3067 With one argument, sets the active guard.
3066
3068
3067 Use -n/--none to deactivate guards (no other arguments needed).
3069 Use -n/--none to deactivate guards (no other arguments needed).
3068 When no guards are active, patches with positive guards are
3070 When no guards are active, patches with positive guards are
3069 skipped and patches with negative guards are pushed.
3071 skipped and patches with negative guards are pushed.
3070
3072
3071 qselect can change the guards on applied patches. It does not pop
3073 qselect can change the guards on applied patches. It does not pop
3072 guarded patches by default. Use --pop to pop back to the last
3074 guarded patches by default. Use --pop to pop back to the last
3073 applied patch that is not guarded. Use --reapply (which implies
3075 applied patch that is not guarded. Use --reapply (which implies
3074 --pop) to push back to the current patch afterwards, but skip
3076 --pop) to push back to the current patch afterwards, but skip
3075 guarded patches.
3077 guarded patches.
3076
3078
3077 Use -s/--series to print a list of all guards in the series file
3079 Use -s/--series to print a list of all guards in the series file
3078 (no other arguments needed). Use -v for more information.
3080 (no other arguments needed). Use -v for more information.
3079
3081
3080 Returns 0 on success.'''
3082 Returns 0 on success.'''
3081
3083
3082 q = repo.mq
3084 q = repo.mq
3083 guards = q.active()
3085 guards = q.active()
3084 if args or opts.get('none'):
3086 if args or opts.get('none'):
3085 old_unapplied = q.unapplied(repo)
3087 old_unapplied = q.unapplied(repo)
3086 old_guarded = [i for i in xrange(len(q.applied)) if
3088 old_guarded = [i for i in xrange(len(q.applied)) if
3087 not q.pushable(i)[0]]
3089 not q.pushable(i)[0]]
3088 q.setactive(args)
3090 q.setactive(args)
3089 q.savedirty()
3091 q.savedirty()
3090 if not args:
3092 if not args:
3091 ui.status(_('guards deactivated\n'))
3093 ui.status(_('guards deactivated\n'))
3092 if not opts.get('pop') and not opts.get('reapply'):
3094 if not opts.get('pop') and not opts.get('reapply'):
3093 unapplied = q.unapplied(repo)
3095 unapplied = q.unapplied(repo)
3094 guarded = [i for i in xrange(len(q.applied))
3096 guarded = [i for i in xrange(len(q.applied))
3095 if not q.pushable(i)[0]]
3097 if not q.pushable(i)[0]]
3096 if len(unapplied) != len(old_unapplied):
3098 if len(unapplied) != len(old_unapplied):
3097 ui.status(_('number of unguarded, unapplied patches has '
3099 ui.status(_('number of unguarded, unapplied patches has '
3098 'changed from %d to %d\n') %
3100 'changed from %d to %d\n') %
3099 (len(old_unapplied), len(unapplied)))
3101 (len(old_unapplied), len(unapplied)))
3100 if len(guarded) != len(old_guarded):
3102 if len(guarded) != len(old_guarded):
3101 ui.status(_('number of guarded, applied patches has changed '
3103 ui.status(_('number of guarded, applied patches has changed '
3102 'from %d to %d\n') %
3104 'from %d to %d\n') %
3103 (len(old_guarded), len(guarded)))
3105 (len(old_guarded), len(guarded)))
3104 elif opts.get('series'):
3106 elif opts.get('series'):
3105 guards = {}
3107 guards = {}
3106 noguards = 0
3108 noguards = 0
3107 for gs in q.seriesguards:
3109 for gs in q.seriesguards:
3108 if not gs:
3110 if not gs:
3109 noguards += 1
3111 noguards += 1
3110 for g in gs:
3112 for g in gs:
3111 guards.setdefault(g, 0)
3113 guards.setdefault(g, 0)
3112 guards[g] += 1
3114 guards[g] += 1
3113 if ui.verbose:
3115 if ui.verbose:
3114 guards['NONE'] = noguards
3116 guards['NONE'] = noguards
3115 guards = guards.items()
3117 guards = guards.items()
3116 guards.sort(key=lambda x: x[0][1:])
3118 guards.sort(key=lambda x: x[0][1:])
3117 if guards:
3119 if guards:
3118 ui.note(_('guards in series file:\n'))
3120 ui.note(_('guards in series file:\n'))
3119 for guard, count in guards:
3121 for guard, count in guards:
3120 ui.note('%2d ' % count)
3122 ui.note('%2d ' % count)
3121 ui.write(guard, '\n')
3123 ui.write(guard, '\n')
3122 else:
3124 else:
3123 ui.note(_('no guards in series file\n'))
3125 ui.note(_('no guards in series file\n'))
3124 else:
3126 else:
3125 if guards:
3127 if guards:
3126 ui.note(_('active guards:\n'))
3128 ui.note(_('active guards:\n'))
3127 for g in guards:
3129 for g in guards:
3128 ui.write(g, '\n')
3130 ui.write(g, '\n')
3129 else:
3131 else:
3130 ui.write(_('no active guards\n'))
3132 ui.write(_('no active guards\n'))
3131 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
3133 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
3132 popped = False
3134 popped = False
3133 if opts.get('pop') or opts.get('reapply'):
3135 if opts.get('pop') or opts.get('reapply'):
3134 for i in xrange(len(q.applied)):
3136 for i in xrange(len(q.applied)):
3135 pushable, reason = q.pushable(i)
3137 pushable, reason = q.pushable(i)
3136 if not pushable:
3138 if not pushable:
3137 ui.status(_('popping guarded patches\n'))
3139 ui.status(_('popping guarded patches\n'))
3138 popped = True
3140 popped = True
3139 if i == 0:
3141 if i == 0:
3140 q.pop(repo, all=True)
3142 q.pop(repo, all=True)
3141 else:
3143 else:
3142 q.pop(repo, str(i - 1))
3144 q.pop(repo, str(i - 1))
3143 break
3145 break
3144 if popped:
3146 if popped:
3145 try:
3147 try:
3146 if reapply:
3148 if reapply:
3147 ui.status(_('reapplying unguarded patches\n'))
3149 ui.status(_('reapplying unguarded patches\n'))
3148 q.push(repo, reapply)
3150 q.push(repo, reapply)
3149 finally:
3151 finally:
3150 q.savedirty()
3152 q.savedirty()
3151
3153
3152 @command("qfinish",
3154 @command("qfinish",
3153 [('a', 'applied', None, _('finish all applied changesets'))],
3155 [('a', 'applied', None, _('finish all applied changesets'))],
3154 _('hg qfinish [-a] [REV]...'))
3156 _('hg qfinish [-a] [REV]...'))
3155 def finish(ui, repo, *revrange, **opts):
3157 def finish(ui, repo, *revrange, **opts):
3156 """move applied patches into repository history
3158 """move applied patches into repository history
3157
3159
3158 Finishes the specified revisions (corresponding to applied
3160 Finishes the specified revisions (corresponding to applied
3159 patches) by moving them out of mq control into regular repository
3161 patches) by moving them out of mq control into regular repository
3160 history.
3162 history.
3161
3163
3162 Accepts a revision range or the -a/--applied option. If --applied
3164 Accepts a revision range or the -a/--applied option. If --applied
3163 is specified, all applied mq revisions are removed from mq
3165 is specified, all applied mq revisions are removed from mq
3164 control. Otherwise, the given revisions must be at the base of the
3166 control. Otherwise, the given revisions must be at the base of the
3165 stack of applied patches.
3167 stack of applied patches.
3166
3168
3167 This can be especially useful if your changes have been applied to
3169 This can be especially useful if your changes have been applied to
3168 an upstream repository, or if you are about to push your changes
3170 an upstream repository, or if you are about to push your changes
3169 to upstream.
3171 to upstream.
3170
3172
3171 Returns 0 on success.
3173 Returns 0 on success.
3172 """
3174 """
3173 if not opts.get('applied') and not revrange:
3175 if not opts.get('applied') and not revrange:
3174 raise util.Abort(_('no revisions specified'))
3176 raise util.Abort(_('no revisions specified'))
3175 elif opts.get('applied'):
3177 elif opts.get('applied'):
3176 revrange = ('qbase::qtip',) + revrange
3178 revrange = ('qbase::qtip',) + revrange
3177
3179
3178 q = repo.mq
3180 q = repo.mq
3179 if not q.applied:
3181 if not q.applied:
3180 ui.status(_('no patches applied\n'))
3182 ui.status(_('no patches applied\n'))
3181 return 0
3183 return 0
3182
3184
3183 revs = scmutil.revrange(repo, revrange)
3185 revs = scmutil.revrange(repo, revrange)
3184 if repo['.'].rev() in revs and repo[None].files():
3186 if repo['.'].rev() in revs and repo[None].files():
3185 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3187 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3186 # queue.finish may changes phases but leave the responsability to lock the
3188 # queue.finish may changes phases but leave the responsability to lock the
3187 # repo to the caller to avoid deadlock with wlock. This command code is
3189 # repo to the caller to avoid deadlock with wlock. This command code is
3188 # responsability for this locking.
3190 # responsability for this locking.
3189 lock = repo.lock()
3191 lock = repo.lock()
3190 try:
3192 try:
3191 q.finish(repo, revs)
3193 q.finish(repo, revs)
3192 q.savedirty()
3194 q.savedirty()
3193 finally:
3195 finally:
3194 lock.release()
3196 lock.release()
3195 return 0
3197 return 0
3196
3198
3197 @command("qqueue",
3199 @command("qqueue",
3198 [('l', 'list', False, _('list all available queues')),
3200 [('l', 'list', False, _('list all available queues')),
3199 ('', 'active', False, _('print name of active queue')),
3201 ('', 'active', False, _('print name of active queue')),
3200 ('c', 'create', False, _('create new queue')),
3202 ('c', 'create', False, _('create new queue')),
3201 ('', 'rename', False, _('rename active queue')),
3203 ('', 'rename', False, _('rename active queue')),
3202 ('', 'delete', False, _('delete reference to queue')),
3204 ('', 'delete', False, _('delete reference to queue')),
3203 ('', 'purge', False, _('delete queue, and remove patch dir')),
3205 ('', 'purge', False, _('delete queue, and remove patch dir')),
3204 ],
3206 ],
3205 _('[OPTION] [QUEUE]'))
3207 _('[OPTION] [QUEUE]'))
3206 def qqueue(ui, repo, name=None, **opts):
3208 def qqueue(ui, repo, name=None, **opts):
3207 '''manage multiple patch queues
3209 '''manage multiple patch queues
3208
3210
3209 Supports switching between different patch queues, as well as creating
3211 Supports switching between different patch queues, as well as creating
3210 new patch queues and deleting existing ones.
3212 new patch queues and deleting existing ones.
3211
3213
3212 Omitting a queue name or specifying -l/--list will show you the registered
3214 Omitting a queue name or specifying -l/--list will show you the registered
3213 queues - by default the "normal" patches queue is registered. The currently
3215 queues - by default the "normal" patches queue is registered. The currently
3214 active queue will be marked with "(active)". Specifying --active will print
3216 active queue will be marked with "(active)". Specifying --active will print
3215 only the name of the active queue.
3217 only the name of the active queue.
3216
3218
3217 To create a new queue, use -c/--create. The queue is automatically made
3219 To create a new queue, use -c/--create. The queue is automatically made
3218 active, except in the case where there are applied patches from the
3220 active, except in the case where there are applied patches from the
3219 currently active queue in the repository. Then the queue will only be
3221 currently active queue in the repository. Then the queue will only be
3220 created and switching will fail.
3222 created and switching will fail.
3221
3223
3222 To delete an existing queue, use --delete. You cannot delete the currently
3224 To delete an existing queue, use --delete. You cannot delete the currently
3223 active queue.
3225 active queue.
3224
3226
3225 Returns 0 on success.
3227 Returns 0 on success.
3226 '''
3228 '''
3227 q = repo.mq
3229 q = repo.mq
3228 _defaultqueue = 'patches'
3230 _defaultqueue = 'patches'
3229 _allqueues = 'patches.queues'
3231 _allqueues = 'patches.queues'
3230 _activequeue = 'patches.queue'
3232 _activequeue = 'patches.queue'
3231
3233
3232 def _getcurrent():
3234 def _getcurrent():
3233 cur = os.path.basename(q.path)
3235 cur = os.path.basename(q.path)
3234 if cur.startswith('patches-'):
3236 if cur.startswith('patches-'):
3235 cur = cur[8:]
3237 cur = cur[8:]
3236 return cur
3238 return cur
3237
3239
3238 def _noqueues():
3240 def _noqueues():
3239 try:
3241 try:
3240 fh = repo.opener(_allqueues, 'r')
3242 fh = repo.opener(_allqueues, 'r')
3241 fh.close()
3243 fh.close()
3242 except IOError:
3244 except IOError:
3243 return True
3245 return True
3244
3246
3245 return False
3247 return False
3246
3248
3247 def _getqueues():
3249 def _getqueues():
3248 current = _getcurrent()
3250 current = _getcurrent()
3249
3251
3250 try:
3252 try:
3251 fh = repo.opener(_allqueues, 'r')
3253 fh = repo.opener(_allqueues, 'r')
3252 queues = [queue.strip() for queue in fh if queue.strip()]
3254 queues = [queue.strip() for queue in fh if queue.strip()]
3253 fh.close()
3255 fh.close()
3254 if current not in queues:
3256 if current not in queues:
3255 queues.append(current)
3257 queues.append(current)
3256 except IOError:
3258 except IOError:
3257 queues = [_defaultqueue]
3259 queues = [_defaultqueue]
3258
3260
3259 return sorted(queues)
3261 return sorted(queues)
3260
3262
3261 def _setactive(name):
3263 def _setactive(name):
3262 if q.applied:
3264 if q.applied:
3263 raise util.Abort(_('patches applied - cannot set new queue active'))
3265 raise util.Abort(_('patches applied - cannot set new queue active'))
3264 _setactivenocheck(name)
3266 _setactivenocheck(name)
3265
3267
3266 def _setactivenocheck(name):
3268 def _setactivenocheck(name):
3267 fh = repo.opener(_activequeue, 'w')
3269 fh = repo.opener(_activequeue, 'w')
3268 if name != 'patches':
3270 if name != 'patches':
3269 fh.write(name)
3271 fh.write(name)
3270 fh.close()
3272 fh.close()
3271
3273
3272 def _addqueue(name):
3274 def _addqueue(name):
3273 fh = repo.opener(_allqueues, 'a')
3275 fh = repo.opener(_allqueues, 'a')
3274 fh.write('%s\n' % (name,))
3276 fh.write('%s\n' % (name,))
3275 fh.close()
3277 fh.close()
3276
3278
3277 def _queuedir(name):
3279 def _queuedir(name):
3278 if name == 'patches':
3280 if name == 'patches':
3279 return repo.join('patches')
3281 return repo.join('patches')
3280 else:
3282 else:
3281 return repo.join('patches-' + name)
3283 return repo.join('patches-' + name)
3282
3284
3283 def _validname(name):
3285 def _validname(name):
3284 for n in name:
3286 for n in name:
3285 if n in ':\\/.':
3287 if n in ':\\/.':
3286 return False
3288 return False
3287 return True
3289 return True
3288
3290
3289 def _delete(name):
3291 def _delete(name):
3290 if name not in existing:
3292 if name not in existing:
3291 raise util.Abort(_('cannot delete queue that does not exist'))
3293 raise util.Abort(_('cannot delete queue that does not exist'))
3292
3294
3293 current = _getcurrent()
3295 current = _getcurrent()
3294
3296
3295 if name == current:
3297 if name == current:
3296 raise util.Abort(_('cannot delete currently active queue'))
3298 raise util.Abort(_('cannot delete currently active queue'))
3297
3299
3298 fh = repo.opener('patches.queues.new', 'w')
3300 fh = repo.opener('patches.queues.new', 'w')
3299 for queue in existing:
3301 for queue in existing:
3300 if queue == name:
3302 if queue == name:
3301 continue
3303 continue
3302 fh.write('%s\n' % (queue,))
3304 fh.write('%s\n' % (queue,))
3303 fh.close()
3305 fh.close()
3304 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3306 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3305
3307
3306 if not name or opts.get('list') or opts.get('active'):
3308 if not name or opts.get('list') or opts.get('active'):
3307 current = _getcurrent()
3309 current = _getcurrent()
3308 if opts.get('active'):
3310 if opts.get('active'):
3309 ui.write('%s\n' % (current,))
3311 ui.write('%s\n' % (current,))
3310 return
3312 return
3311 for queue in _getqueues():
3313 for queue in _getqueues():
3312 ui.write('%s' % (queue,))
3314 ui.write('%s' % (queue,))
3313 if queue == current and not ui.quiet:
3315 if queue == current and not ui.quiet:
3314 ui.write(_(' (active)\n'))
3316 ui.write(_(' (active)\n'))
3315 else:
3317 else:
3316 ui.write('\n')
3318 ui.write('\n')
3317 return
3319 return
3318
3320
3319 if not _validname(name):
3321 if not _validname(name):
3320 raise util.Abort(
3322 raise util.Abort(
3321 _('invalid queue name, may not contain the characters ":\\/."'))
3323 _('invalid queue name, may not contain the characters ":\\/."'))
3322
3324
3323 existing = _getqueues()
3325 existing = _getqueues()
3324
3326
3325 if opts.get('create'):
3327 if opts.get('create'):
3326 if name in existing:
3328 if name in existing:
3327 raise util.Abort(_('queue "%s" already exists') % name)
3329 raise util.Abort(_('queue "%s" already exists') % name)
3328 if _noqueues():
3330 if _noqueues():
3329 _addqueue(_defaultqueue)
3331 _addqueue(_defaultqueue)
3330 _addqueue(name)
3332 _addqueue(name)
3331 _setactive(name)
3333 _setactive(name)
3332 elif opts.get('rename'):
3334 elif opts.get('rename'):
3333 current = _getcurrent()
3335 current = _getcurrent()
3334 if name == current:
3336 if name == current:
3335 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3337 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3336 if name in existing:
3338 if name in existing:
3337 raise util.Abort(_('queue "%s" already exists') % name)
3339 raise util.Abort(_('queue "%s" already exists') % name)
3338
3340
3339 olddir = _queuedir(current)
3341 olddir = _queuedir(current)
3340 newdir = _queuedir(name)
3342 newdir = _queuedir(name)
3341
3343
3342 if os.path.exists(newdir):
3344 if os.path.exists(newdir):
3343 raise util.Abort(_('non-queue directory "%s" already exists') %
3345 raise util.Abort(_('non-queue directory "%s" already exists') %
3344 newdir)
3346 newdir)
3345
3347
3346 fh = repo.opener('patches.queues.new', 'w')
3348 fh = repo.opener('patches.queues.new', 'w')
3347 for queue in existing:
3349 for queue in existing:
3348 if queue == current:
3350 if queue == current:
3349 fh.write('%s\n' % (name,))
3351 fh.write('%s\n' % (name,))
3350 if os.path.exists(olddir):
3352 if os.path.exists(olddir):
3351 util.rename(olddir, newdir)
3353 util.rename(olddir, newdir)
3352 else:
3354 else:
3353 fh.write('%s\n' % (queue,))
3355 fh.write('%s\n' % (queue,))
3354 fh.close()
3356 fh.close()
3355 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3357 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3356 _setactivenocheck(name)
3358 _setactivenocheck(name)
3357 elif opts.get('delete'):
3359 elif opts.get('delete'):
3358 _delete(name)
3360 _delete(name)
3359 elif opts.get('purge'):
3361 elif opts.get('purge'):
3360 if name in existing:
3362 if name in existing:
3361 _delete(name)
3363 _delete(name)
3362 qdir = _queuedir(name)
3364 qdir = _queuedir(name)
3363 if os.path.exists(qdir):
3365 if os.path.exists(qdir):
3364 shutil.rmtree(qdir)
3366 shutil.rmtree(qdir)
3365 else:
3367 else:
3366 if name not in existing:
3368 if name not in existing:
3367 raise util.Abort(_('use --create to create a new queue'))
3369 raise util.Abort(_('use --create to create a new queue'))
3368 _setactive(name)
3370 _setactive(name)
3369
3371
3370 def mqphasedefaults(repo, roots):
3372 def mqphasedefaults(repo, roots):
3371 """callback used to set mq changeset as secret when no phase data exists"""
3373 """callback used to set mq changeset as secret when no phase data exists"""
3372 if repo.mq.applied:
3374 if repo.mq.applied:
3373 if repo.ui.configbool('mq', 'secret', False):
3375 if repo.ui.configbool('mq', 'secret', False):
3374 mqphase = phases.secret
3376 mqphase = phases.secret
3375 else:
3377 else:
3376 mqphase = phases.draft
3378 mqphase = phases.draft
3377 qbase = repo[repo.mq.applied[0].node]
3379 qbase = repo[repo.mq.applied[0].node]
3378 roots[mqphase].add(qbase.node())
3380 roots[mqphase].add(qbase.node())
3379 return roots
3381 return roots
3380
3382
3381 def reposetup(ui, repo):
3383 def reposetup(ui, repo):
3382 class mqrepo(repo.__class__):
3384 class mqrepo(repo.__class__):
3383 @util.propertycache
3385 @util.propertycache
3384 def mq(self):
3386 def mq(self):
3385 return queue(self.ui, self.path)
3387 return queue(self.ui, self.path)
3386
3388
3387 def abortifwdirpatched(self, errmsg, force=False):
3389 def abortifwdirpatched(self, errmsg, force=False):
3388 if self.mq.applied and not force:
3390 if self.mq.applied and not force:
3389 parents = self.dirstate.parents()
3391 parents = self.dirstate.parents()
3390 patches = [s.node for s in self.mq.applied]
3392 patches = [s.node for s in self.mq.applied]
3391 if parents[0] in patches or parents[1] in patches:
3393 if parents[0] in patches or parents[1] in patches:
3392 raise util.Abort(errmsg)
3394 raise util.Abort(errmsg)
3393
3395
3394 def commit(self, text="", user=None, date=None, match=None,
3396 def commit(self, text="", user=None, date=None, match=None,
3395 force=False, editor=False, extra={}):
3397 force=False, editor=False, extra={}):
3396 self.abortifwdirpatched(
3398 self.abortifwdirpatched(
3397 _('cannot commit over an applied mq patch'),
3399 _('cannot commit over an applied mq patch'),
3398 force)
3400 force)
3399
3401
3400 return super(mqrepo, self).commit(text, user, date, match, force,
3402 return super(mqrepo, self).commit(text, user, date, match, force,
3401 editor, extra)
3403 editor, extra)
3402
3404
3403 def checkpush(self, force, revs):
3405 def checkpush(self, force, revs):
3404 if self.mq.applied and not force:
3406 if self.mq.applied and not force:
3405 outapplied = [e.node for e in self.mq.applied]
3407 outapplied = [e.node for e in self.mq.applied]
3406 if revs:
3408 if revs:
3407 # Assume applied patches have no non-patch descendants and
3409 # Assume applied patches have no non-patch descendants and
3408 # are not on remote already. Filtering any changeset not
3410 # are not on remote already. Filtering any changeset not
3409 # pushed.
3411 # pushed.
3410 heads = set(revs)
3412 heads = set(revs)
3411 for node in reversed(outapplied):
3413 for node in reversed(outapplied):
3412 if node in heads:
3414 if node in heads:
3413 break
3415 break
3414 else:
3416 else:
3415 outapplied.pop()
3417 outapplied.pop()
3416 # looking for pushed and shared changeset
3418 # looking for pushed and shared changeset
3417 for node in outapplied:
3419 for node in outapplied:
3418 if repo[node].phase() < phases.secret:
3420 if repo[node].phase() < phases.secret:
3419 raise util.Abort(_('source has mq patches applied'))
3421 raise util.Abort(_('source has mq patches applied'))
3420 # no non-secret patches pushed
3422 # no non-secret patches pushed
3421 super(mqrepo, self).checkpush(force, revs)
3423 super(mqrepo, self).checkpush(force, revs)
3422
3424
3423 def _findtags(self):
3425 def _findtags(self):
3424 '''augment tags from base class with patch tags'''
3426 '''augment tags from base class with patch tags'''
3425 result = super(mqrepo, self)._findtags()
3427 result = super(mqrepo, self)._findtags()
3426
3428
3427 q = self.mq
3429 q = self.mq
3428 if not q.applied:
3430 if not q.applied:
3429 return result
3431 return result
3430
3432
3431 mqtags = [(patch.node, patch.name) for patch in q.applied]
3433 mqtags = [(patch.node, patch.name) for patch in q.applied]
3432
3434
3433 try:
3435 try:
3434 self.changelog.rev(mqtags[-1][0])
3436 self.changelog.rev(mqtags[-1][0])
3435 except error.LookupError:
3437 except error.LookupError:
3436 self.ui.warn(_('mq status file refers to unknown node %s\n')
3438 self.ui.warn(_('mq status file refers to unknown node %s\n')
3437 % short(mqtags[-1][0]))
3439 % short(mqtags[-1][0]))
3438 return result
3440 return result
3439
3441
3440 mqtags.append((mqtags[-1][0], 'qtip'))
3442 mqtags.append((mqtags[-1][0], 'qtip'))
3441 mqtags.append((mqtags[0][0], 'qbase'))
3443 mqtags.append((mqtags[0][0], 'qbase'))
3442 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3444 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3443 tags = result[0]
3445 tags = result[0]
3444 for patch in mqtags:
3446 for patch in mqtags:
3445 if patch[1] in tags:
3447 if patch[1] in tags:
3446 self.ui.warn(_('tag %s overrides mq patch of the same '
3448 self.ui.warn(_('tag %s overrides mq patch of the same '
3447 'name\n') % patch[1])
3449 'name\n') % patch[1])
3448 else:
3450 else:
3449 tags[patch[1]] = patch[0]
3451 tags[patch[1]] = patch[0]
3450
3452
3451 return result
3453 return result
3452
3454
3453 def _branchtags(self, partial, lrev):
3455 def _branchtags(self, partial, lrev):
3454 q = self.mq
3456 q = self.mq
3455 cl = self.changelog
3457 cl = self.changelog
3456 qbase = None
3458 qbase = None
3457 if not q.applied:
3459 if not q.applied:
3458 if getattr(self, '_committingpatch', False):
3460 if getattr(self, '_committingpatch', False):
3459 # Committing a new patch, must be tip
3461 # Committing a new patch, must be tip
3460 qbase = len(cl) - 1
3462 qbase = len(cl) - 1
3461 else:
3463 else:
3462 qbasenode = q.applied[0].node
3464 qbasenode = q.applied[0].node
3463 try:
3465 try:
3464 qbase = cl.rev(qbasenode)
3466 qbase = cl.rev(qbasenode)
3465 except error.LookupError:
3467 except error.LookupError:
3466 self.ui.warn(_('mq status file refers to unknown node %s\n')
3468 self.ui.warn(_('mq status file refers to unknown node %s\n')
3467 % short(qbasenode))
3469 % short(qbasenode))
3468 if qbase is None:
3470 if qbase is None:
3469 return super(mqrepo, self)._branchtags(partial, lrev)
3471 return super(mqrepo, self)._branchtags(partial, lrev)
3470
3472
3471 start = lrev + 1
3473 start = lrev + 1
3472 if start < qbase:
3474 if start < qbase:
3473 # update the cache (excluding the patches) and save it
3475 # update the cache (excluding the patches) and save it
3474 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
3476 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
3475 self._updatebranchcache(partial, ctxgen)
3477 self._updatebranchcache(partial, ctxgen)
3476 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
3478 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
3477 start = qbase
3479 start = qbase
3478 # if start = qbase, the cache is as updated as it should be.
3480 # if start = qbase, the cache is as updated as it should be.
3479 # if start > qbase, the cache includes (part of) the patches.
3481 # if start > qbase, the cache includes (part of) the patches.
3480 # we might as well use it, but we won't save it.
3482 # we might as well use it, but we won't save it.
3481
3483
3482 # update the cache up to the tip
3484 # update the cache up to the tip
3483 ctxgen = (self[r] for r in xrange(start, len(cl)))
3485 ctxgen = (self[r] for r in xrange(start, len(cl)))
3484 self._updatebranchcache(partial, ctxgen)
3486 self._updatebranchcache(partial, ctxgen)
3485
3487
3486 return partial
3488 return partial
3487
3489
3488 if repo.local():
3490 if repo.local():
3489 repo.__class__ = mqrepo
3491 repo.__class__ = mqrepo
3490
3492
3491 repo._phasedefaults.append(mqphasedefaults)
3493 repo._phasedefaults.append(mqphasedefaults)
3492
3494
3493 def mqimport(orig, ui, repo, *args, **kwargs):
3495 def mqimport(orig, ui, repo, *args, **kwargs):
3494 if (util.safehasattr(repo, 'abortifwdirpatched')
3496 if (util.safehasattr(repo, 'abortifwdirpatched')
3495 and not kwargs.get('no_commit', False)):
3497 and not kwargs.get('no_commit', False)):
3496 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3498 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3497 kwargs.get('force'))
3499 kwargs.get('force'))
3498 return orig(ui, repo, *args, **kwargs)
3500 return orig(ui, repo, *args, **kwargs)
3499
3501
3500 def mqinit(orig, ui, *args, **kwargs):
3502 def mqinit(orig, ui, *args, **kwargs):
3501 mq = kwargs.pop('mq', None)
3503 mq = kwargs.pop('mq', None)
3502
3504
3503 if not mq:
3505 if not mq:
3504 return orig(ui, *args, **kwargs)
3506 return orig(ui, *args, **kwargs)
3505
3507
3506 if args:
3508 if args:
3507 repopath = args[0]
3509 repopath = args[0]
3508 if not hg.islocal(repopath):
3510 if not hg.islocal(repopath):
3509 raise util.Abort(_('only a local queue repository '
3511 raise util.Abort(_('only a local queue repository '
3510 'may be initialized'))
3512 'may be initialized'))
3511 else:
3513 else:
3512 repopath = cmdutil.findrepo(os.getcwd())
3514 repopath = cmdutil.findrepo(os.getcwd())
3513 if not repopath:
3515 if not repopath:
3514 raise util.Abort(_('there is no Mercurial repository here '
3516 raise util.Abort(_('there is no Mercurial repository here '
3515 '(.hg not found)'))
3517 '(.hg not found)'))
3516 repo = hg.repository(ui, repopath)
3518 repo = hg.repository(ui, repopath)
3517 return qinit(ui, repo, True)
3519 return qinit(ui, repo, True)
3518
3520
3519 def mqcommand(orig, ui, repo, *args, **kwargs):
3521 def mqcommand(orig, ui, repo, *args, **kwargs):
3520 """Add --mq option to operate on patch repository instead of main"""
3522 """Add --mq option to operate on patch repository instead of main"""
3521
3523
3522 # some commands do not like getting unknown options
3524 # some commands do not like getting unknown options
3523 mq = kwargs.pop('mq', None)
3525 mq = kwargs.pop('mq', None)
3524
3526
3525 if not mq:
3527 if not mq:
3526 return orig(ui, repo, *args, **kwargs)
3528 return orig(ui, repo, *args, **kwargs)
3527
3529
3528 q = repo.mq
3530 q = repo.mq
3529 r = q.qrepo()
3531 r = q.qrepo()
3530 if not r:
3532 if not r:
3531 raise util.Abort(_('no queue repository'))
3533 raise util.Abort(_('no queue repository'))
3532 return orig(r.ui, r, *args, **kwargs)
3534 return orig(r.ui, r, *args, **kwargs)
3533
3535
3534 def summary(orig, ui, repo, *args, **kwargs):
3536 def summary(orig, ui, repo, *args, **kwargs):
3535 r = orig(ui, repo, *args, **kwargs)
3537 r = orig(ui, repo, *args, **kwargs)
3536 q = repo.mq
3538 q = repo.mq
3537 m = []
3539 m = []
3538 a, u = len(q.applied), len(q.unapplied(repo))
3540 a, u = len(q.applied), len(q.unapplied(repo))
3539 if a:
3541 if a:
3540 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3542 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3541 if u:
3543 if u:
3542 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3544 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3543 if m:
3545 if m:
3544 ui.write("mq: %s\n" % ', '.join(m))
3546 ui.write("mq: %s\n" % ', '.join(m))
3545 else:
3547 else:
3546 ui.note(_("mq: (empty queue)\n"))
3548 ui.note(_("mq: (empty queue)\n"))
3547 return r
3549 return r
3548
3550
3549 def revsetmq(repo, subset, x):
3551 def revsetmq(repo, subset, x):
3550 """``mq()``
3552 """``mq()``
3551 Changesets managed by MQ.
3553 Changesets managed by MQ.
3552 """
3554 """
3553 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3555 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3554 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3556 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3555 return [r for r in subset if r in applied]
3557 return [r for r in subset if r in applied]
3556
3558
3557 # tell hggettext to extract docstrings from these functions:
3559 # tell hggettext to extract docstrings from these functions:
3558 i18nfunctions = [revsetmq]
3560 i18nfunctions = [revsetmq]
3559
3561
3560 def extsetup(ui):
3562 def extsetup(ui):
3561 # Ensure mq wrappers are called first, regardless of extension load order by
3563 # Ensure mq wrappers are called first, regardless of extension load order by
3562 # NOT wrapping in uisetup() and instead deferring to init stage two here.
3564 # NOT wrapping in uisetup() and instead deferring to init stage two here.
3563 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3565 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3564
3566
3565 extensions.wrapcommand(commands.table, 'import', mqimport)
3567 extensions.wrapcommand(commands.table, 'import', mqimport)
3566 extensions.wrapcommand(commands.table, 'summary', summary)
3568 extensions.wrapcommand(commands.table, 'summary', summary)
3567
3569
3568 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3570 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3569 entry[1].extend(mqopt)
3571 entry[1].extend(mqopt)
3570
3572
3571 nowrap = set(commands.norepo.split(" "))
3573 nowrap = set(commands.norepo.split(" "))
3572
3574
3573 def dotable(cmdtable):
3575 def dotable(cmdtable):
3574 for cmd in cmdtable.keys():
3576 for cmd in cmdtable.keys():
3575 cmd = cmdutil.parsealiases(cmd)[0]
3577 cmd = cmdutil.parsealiases(cmd)[0]
3576 if cmd in nowrap:
3578 if cmd in nowrap:
3577 continue
3579 continue
3578 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3580 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3579 entry[1].extend(mqopt)
3581 entry[1].extend(mqopt)
3580
3582
3581 dotable(commands.table)
3583 dotable(commands.table)
3582
3584
3583 for extname, extmodule in extensions.extensions():
3585 for extname, extmodule in extensions.extensions():
3584 if extmodule.__file__ != __file__:
3586 if extmodule.__file__ != __file__:
3585 dotable(getattr(extmodule, 'cmdtable', {}))
3587 dotable(getattr(extmodule, 'cmdtable', {}))
3586
3588
3587 revset.symbols['mq'] = revsetmq
3589 revset.symbols['mq'] = revsetmq
3588
3590
3589 colortable = {'qguard.negative': 'red',
3591 colortable = {'qguard.negative': 'red',
3590 'qguard.positive': 'yellow',
3592 'qguard.positive': 'yellow',
3591 'qguard.unguarded': 'green',
3593 'qguard.unguarded': 'green',
3592 'qseries.applied': 'blue bold underline',
3594 'qseries.applied': 'blue bold underline',
3593 'qseries.guarded': 'black bold',
3595 'qseries.guarded': 'black bold',
3594 'qseries.missing': 'red bold',
3596 'qseries.missing': 'red bold',
3595 'qseries.unapplied': 'black bold'}
3597 'qseries.unapplied': 'black bold'}
@@ -1,186 +1,184 b''
1 # Mercurial extension to provide 'hg relink' command
1 # Mercurial extension to provide 'hg relink' command
2 #
2 #
3 # Copyright (C) 2007 Brendan Cully <brendan@kublai.com>
3 # Copyright (C) 2007 Brendan Cully <brendan@kublai.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """recreates hardlinks between repository clones"""
8 """recreates hardlinks between repository clones"""
9
9
10 from mercurial import hg, util
10 from mercurial import hg, util
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12 import os, stat
12 import os, stat
13
13
14 testedwith = 'internal'
14 testedwith = 'internal'
15
15
16 def relink(ui, repo, origin=None, **opts):
16 def relink(ui, repo, origin=None, **opts):
17 """recreate hardlinks between two repositories
17 """recreate hardlinks between two repositories
18
18
19 When repositories are cloned locally, their data files will be
19 When repositories are cloned locally, their data files will be
20 hardlinked so that they only use the space of a single repository.
20 hardlinked so that they only use the space of a single repository.
21
21
22 Unfortunately, subsequent pulls into either repository will break
22 Unfortunately, subsequent pulls into either repository will break
23 hardlinks for any files touched by the new changesets, even if
23 hardlinks for any files touched by the new changesets, even if
24 both repositories end up pulling the same changes.
24 both repositories end up pulling the same changes.
25
25
26 Similarly, passing --rev to "hg clone" will fail to use any
26 Similarly, passing --rev to "hg clone" will fail to use any
27 hardlinks, falling back to a complete copy of the source
27 hardlinks, falling back to a complete copy of the source
28 repository.
28 repository.
29
29
30 This command lets you recreate those hardlinks and reclaim that
30 This command lets you recreate those hardlinks and reclaim that
31 wasted space.
31 wasted space.
32
32
33 This repository will be relinked to share space with ORIGIN, which
33 This repository will be relinked to share space with ORIGIN, which
34 must be on the same local disk. If ORIGIN is omitted, looks for
34 must be on the same local disk. If ORIGIN is omitted, looks for
35 "default-relink", then "default", in [paths].
35 "default-relink", then "default", in [paths].
36
36
37 Do not attempt any read operations on this repository while the
37 Do not attempt any read operations on this repository while the
38 command is running. (Both repositories will be locked against
38 command is running. (Both repositories will be locked against
39 writes.)
39 writes.)
40 """
40 """
41 if (not util.safehasattr(util, 'samefile') or
41 if (not util.safehasattr(util, 'samefile') or
42 not util.safehasattr(util, 'samedevice')):
42 not util.safehasattr(util, 'samedevice')):
43 raise util.Abort(_('hardlinks are not supported on this system'))
43 raise util.Abort(_('hardlinks are not supported on this system'))
44 src = hg.repository(ui, ui.expandpath(origin or 'default-relink',
44 src = hg.repository(ui, ui.expandpath(origin or 'default-relink',
45 origin or 'default'))
45 origin or 'default'))
46 if not src.local():
47 raise util.Abort(_('must specify local origin repository'))
48 ui.status(_('relinking %s to %s\n') % (src.store.path, repo.store.path))
46 ui.status(_('relinking %s to %s\n') % (src.store.path, repo.store.path))
49 if repo.root == src.root:
47 if repo.root == src.root:
50 ui.status(_('there is nothing to relink\n'))
48 ui.status(_('there is nothing to relink\n'))
51 return
49 return
52
50
53 locallock = repo.lock()
51 locallock = repo.lock()
54 try:
52 try:
55 remotelock = src.lock()
53 remotelock = src.lock()
56 try:
54 try:
57 candidates = sorted(collect(src, ui))
55 candidates = sorted(collect(src, ui))
58 targets = prune(candidates, src.store.path, repo.store.path, ui)
56 targets = prune(candidates, src.store.path, repo.store.path, ui)
59 do_relink(src.store.path, repo.store.path, targets, ui)
57 do_relink(src.store.path, repo.store.path, targets, ui)
60 finally:
58 finally:
61 remotelock.release()
59 remotelock.release()
62 finally:
60 finally:
63 locallock.release()
61 locallock.release()
64
62
65 def collect(src, ui):
63 def collect(src, ui):
66 seplen = len(os.path.sep)
64 seplen = len(os.path.sep)
67 candidates = []
65 candidates = []
68 live = len(src['tip'].manifest())
66 live = len(src['tip'].manifest())
69 # Your average repository has some files which were deleted before
67 # Your average repository has some files which were deleted before
70 # the tip revision. We account for that by assuming that there are
68 # the tip revision. We account for that by assuming that there are
71 # 3 tracked files for every 2 live files as of the tip version of
69 # 3 tracked files for every 2 live files as of the tip version of
72 # the repository.
70 # the repository.
73 #
71 #
74 # mozilla-central as of 2010-06-10 had a ratio of just over 7:5.
72 # mozilla-central as of 2010-06-10 had a ratio of just over 7:5.
75 total = live * 3 // 2
73 total = live * 3 // 2
76 src = src.store.path
74 src = src.store.path
77 pos = 0
75 pos = 0
78 ui.status(_("tip has %d files, estimated total number of files: %s\n")
76 ui.status(_("tip has %d files, estimated total number of files: %s\n")
79 % (live, total))
77 % (live, total))
80 for dirpath, dirnames, filenames in os.walk(src):
78 for dirpath, dirnames, filenames in os.walk(src):
81 dirnames.sort()
79 dirnames.sort()
82 relpath = dirpath[len(src) + seplen:]
80 relpath = dirpath[len(src) + seplen:]
83 for filename in sorted(filenames):
81 for filename in sorted(filenames):
84 if filename[-2:] not in ('.d', '.i'):
82 if filename[-2:] not in ('.d', '.i'):
85 continue
83 continue
86 st = os.stat(os.path.join(dirpath, filename))
84 st = os.stat(os.path.join(dirpath, filename))
87 if not stat.S_ISREG(st.st_mode):
85 if not stat.S_ISREG(st.st_mode):
88 continue
86 continue
89 pos += 1
87 pos += 1
90 candidates.append((os.path.join(relpath, filename), st))
88 candidates.append((os.path.join(relpath, filename), st))
91 ui.progress(_('collecting'), pos, filename, _('files'), total)
89 ui.progress(_('collecting'), pos, filename, _('files'), total)
92
90
93 ui.progress(_('collecting'), None)
91 ui.progress(_('collecting'), None)
94 ui.status(_('collected %d candidate storage files\n') % len(candidates))
92 ui.status(_('collected %d candidate storage files\n') % len(candidates))
95 return candidates
93 return candidates
96
94
97 def prune(candidates, src, dst, ui):
95 def prune(candidates, src, dst, ui):
98 def linkfilter(src, dst, st):
96 def linkfilter(src, dst, st):
99 try:
97 try:
100 ts = os.stat(dst)
98 ts = os.stat(dst)
101 except OSError:
99 except OSError:
102 # Destination doesn't have this file?
100 # Destination doesn't have this file?
103 return False
101 return False
104 if util.samefile(src, dst):
102 if util.samefile(src, dst):
105 return False
103 return False
106 if not util.samedevice(src, dst):
104 if not util.samedevice(src, dst):
107 # No point in continuing
105 # No point in continuing
108 raise util.Abort(
106 raise util.Abort(
109 _('source and destination are on different devices'))
107 _('source and destination are on different devices'))
110 if st.st_size != ts.st_size:
108 if st.st_size != ts.st_size:
111 return False
109 return False
112 return st
110 return st
113
111
114 targets = []
112 targets = []
115 total = len(candidates)
113 total = len(candidates)
116 pos = 0
114 pos = 0
117 for fn, st in candidates:
115 for fn, st in candidates:
118 pos += 1
116 pos += 1
119 srcpath = os.path.join(src, fn)
117 srcpath = os.path.join(src, fn)
120 tgt = os.path.join(dst, fn)
118 tgt = os.path.join(dst, fn)
121 ts = linkfilter(srcpath, tgt, st)
119 ts = linkfilter(srcpath, tgt, st)
122 if not ts:
120 if not ts:
123 ui.debug('not linkable: %s\n' % fn)
121 ui.debug('not linkable: %s\n' % fn)
124 continue
122 continue
125 targets.append((fn, ts.st_size))
123 targets.append((fn, ts.st_size))
126 ui.progress(_('pruning'), pos, fn, _('files'), total)
124 ui.progress(_('pruning'), pos, fn, _('files'), total)
127
125
128 ui.progress(_('pruning'), None)
126 ui.progress(_('pruning'), None)
129 ui.status(_('pruned down to %d probably relinkable files\n') % len(targets))
127 ui.status(_('pruned down to %d probably relinkable files\n') % len(targets))
130 return targets
128 return targets
131
129
132 def do_relink(src, dst, files, ui):
130 def do_relink(src, dst, files, ui):
133 def relinkfile(src, dst):
131 def relinkfile(src, dst):
134 bak = dst + '.bak'
132 bak = dst + '.bak'
135 os.rename(dst, bak)
133 os.rename(dst, bak)
136 try:
134 try:
137 util.oslink(src, dst)
135 util.oslink(src, dst)
138 except OSError:
136 except OSError:
139 os.rename(bak, dst)
137 os.rename(bak, dst)
140 raise
138 raise
141 os.remove(bak)
139 os.remove(bak)
142
140
143 CHUNKLEN = 65536
141 CHUNKLEN = 65536
144 relinked = 0
142 relinked = 0
145 savedbytes = 0
143 savedbytes = 0
146
144
147 pos = 0
145 pos = 0
148 total = len(files)
146 total = len(files)
149 for f, sz in files:
147 for f, sz in files:
150 pos += 1
148 pos += 1
151 source = os.path.join(src, f)
149 source = os.path.join(src, f)
152 tgt = os.path.join(dst, f)
150 tgt = os.path.join(dst, f)
153 # Binary mode, so that read() works correctly, especially on Windows
151 # Binary mode, so that read() works correctly, especially on Windows
154 sfp = file(source, 'rb')
152 sfp = file(source, 'rb')
155 dfp = file(tgt, 'rb')
153 dfp = file(tgt, 'rb')
156 sin = sfp.read(CHUNKLEN)
154 sin = sfp.read(CHUNKLEN)
157 while sin:
155 while sin:
158 din = dfp.read(CHUNKLEN)
156 din = dfp.read(CHUNKLEN)
159 if sin != din:
157 if sin != din:
160 break
158 break
161 sin = sfp.read(CHUNKLEN)
159 sin = sfp.read(CHUNKLEN)
162 sfp.close()
160 sfp.close()
163 dfp.close()
161 dfp.close()
164 if sin:
162 if sin:
165 ui.debug('not linkable: %s\n' % f)
163 ui.debug('not linkable: %s\n' % f)
166 continue
164 continue
167 try:
165 try:
168 relinkfile(source, tgt)
166 relinkfile(source, tgt)
169 ui.progress(_('relinking'), pos, f, _('files'), total)
167 ui.progress(_('relinking'), pos, f, _('files'), total)
170 relinked += 1
168 relinked += 1
171 savedbytes += sz
169 savedbytes += sz
172 except OSError, inst:
170 except OSError, inst:
173 ui.warn('%s: %s\n' % (tgt, str(inst)))
171 ui.warn('%s: %s\n' % (tgt, str(inst)))
174
172
175 ui.progress(_('relinking'), None)
173 ui.progress(_('relinking'), None)
176
174
177 ui.status(_('relinked %d files (%s reclaimed)\n') %
175 ui.status(_('relinked %d files (%s reclaimed)\n') %
178 (relinked, util.bytecount(savedbytes)))
176 (relinked, util.bytecount(savedbytes)))
179
177
180 cmdtable = {
178 cmdtable = {
181 'relink': (
179 'relink': (
182 relink,
180 relink,
183 [],
181 [],
184 _('[ORIGIN]')
182 _('[ORIGIN]')
185 )
183 )
186 }
184 }
@@ -1,685 +1,685 b''
1 # Patch transplanting extension for Mercurial
1 # Patch transplanting extension for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to transplant changesets from another branch
8 '''command to transplant changesets from another branch
9
9
10 This extension allows you to transplant patches from another branch.
10 This extension allows you to transplant patches from another branch.
11
11
12 Transplanted patches are recorded in .hg/transplant/transplants, as a
12 Transplanted patches are recorded in .hg/transplant/transplants, as a
13 map from a changeset hash to its hash in the source repository.
13 map from a changeset hash to its hash in the source repository.
14 '''
14 '''
15
15
16 from mercurial.i18n import _
16 from mercurial.i18n import _
17 import os, tempfile
17 import os, tempfile
18 from mercurial.node import short
18 from mercurial.node import short
19 from mercurial import bundlerepo, hg, merge, match
19 from mercurial import bundlerepo, hg, merge, match
20 from mercurial import patch, revlog, scmutil, util, error, cmdutil
20 from mercurial import patch, revlog, scmutil, util, error, cmdutil
21 from mercurial import revset, templatekw
21 from mercurial import revset, templatekw
22
22
23 class TransplantError(error.Abort):
23 class TransplantError(error.Abort):
24 pass
24 pass
25
25
26 cmdtable = {}
26 cmdtable = {}
27 command = cmdutil.command(cmdtable)
27 command = cmdutil.command(cmdtable)
28 testedwith = 'internal'
28 testedwith = 'internal'
29
29
30 class transplantentry(object):
30 class transplantentry(object):
31 def __init__(self, lnode, rnode):
31 def __init__(self, lnode, rnode):
32 self.lnode = lnode
32 self.lnode = lnode
33 self.rnode = rnode
33 self.rnode = rnode
34
34
35 class transplants(object):
35 class transplants(object):
36 def __init__(self, path=None, transplantfile=None, opener=None):
36 def __init__(self, path=None, transplantfile=None, opener=None):
37 self.path = path
37 self.path = path
38 self.transplantfile = transplantfile
38 self.transplantfile = transplantfile
39 self.opener = opener
39 self.opener = opener
40
40
41 if not opener:
41 if not opener:
42 self.opener = scmutil.opener(self.path)
42 self.opener = scmutil.opener(self.path)
43 self.transplants = {}
43 self.transplants = {}
44 self.dirty = False
44 self.dirty = False
45 self.read()
45 self.read()
46
46
47 def read(self):
47 def read(self):
48 abspath = os.path.join(self.path, self.transplantfile)
48 abspath = os.path.join(self.path, self.transplantfile)
49 if self.transplantfile and os.path.exists(abspath):
49 if self.transplantfile and os.path.exists(abspath):
50 for line in self.opener.read(self.transplantfile).splitlines():
50 for line in self.opener.read(self.transplantfile).splitlines():
51 lnode, rnode = map(revlog.bin, line.split(':'))
51 lnode, rnode = map(revlog.bin, line.split(':'))
52 list = self.transplants.setdefault(rnode, [])
52 list = self.transplants.setdefault(rnode, [])
53 list.append(transplantentry(lnode, rnode))
53 list.append(transplantentry(lnode, rnode))
54
54
55 def write(self):
55 def write(self):
56 if self.dirty and self.transplantfile:
56 if self.dirty and self.transplantfile:
57 if not os.path.isdir(self.path):
57 if not os.path.isdir(self.path):
58 os.mkdir(self.path)
58 os.mkdir(self.path)
59 fp = self.opener(self.transplantfile, 'w')
59 fp = self.opener(self.transplantfile, 'w')
60 for list in self.transplants.itervalues():
60 for list in self.transplants.itervalues():
61 for t in list:
61 for t in list:
62 l, r = map(revlog.hex, (t.lnode, t.rnode))
62 l, r = map(revlog.hex, (t.lnode, t.rnode))
63 fp.write(l + ':' + r + '\n')
63 fp.write(l + ':' + r + '\n')
64 fp.close()
64 fp.close()
65 self.dirty = False
65 self.dirty = False
66
66
67 def get(self, rnode):
67 def get(self, rnode):
68 return self.transplants.get(rnode) or []
68 return self.transplants.get(rnode) or []
69
69
70 def set(self, lnode, rnode):
70 def set(self, lnode, rnode):
71 list = self.transplants.setdefault(rnode, [])
71 list = self.transplants.setdefault(rnode, [])
72 list.append(transplantentry(lnode, rnode))
72 list.append(transplantentry(lnode, rnode))
73 self.dirty = True
73 self.dirty = True
74
74
75 def remove(self, transplant):
75 def remove(self, transplant):
76 list = self.transplants.get(transplant.rnode)
76 list = self.transplants.get(transplant.rnode)
77 if list:
77 if list:
78 del list[list.index(transplant)]
78 del list[list.index(transplant)]
79 self.dirty = True
79 self.dirty = True
80
80
81 class transplanter(object):
81 class transplanter(object):
82 def __init__(self, ui, repo):
82 def __init__(self, ui, repo):
83 self.ui = ui
83 self.ui = ui
84 self.path = repo.join('transplant')
84 self.path = repo.join('transplant')
85 self.opener = scmutil.opener(self.path)
85 self.opener = scmutil.opener(self.path)
86 self.transplants = transplants(self.path, 'transplants',
86 self.transplants = transplants(self.path, 'transplants',
87 opener=self.opener)
87 opener=self.opener)
88 self.editor = None
88 self.editor = None
89
89
90 def applied(self, repo, node, parent):
90 def applied(self, repo, node, parent):
91 '''returns True if a node is already an ancestor of parent
91 '''returns True if a node is already an ancestor of parent
92 or is parent or has already been transplanted'''
92 or is parent or has already been transplanted'''
93 if hasnode(repo, parent):
93 if hasnode(repo, parent):
94 parentrev = repo.changelog.rev(parent)
94 parentrev = repo.changelog.rev(parent)
95 if hasnode(repo, node):
95 if hasnode(repo, node):
96 rev = repo.changelog.rev(node)
96 rev = repo.changelog.rev(node)
97 reachable = repo.changelog.incancestors([parentrev], rev)
97 reachable = repo.changelog.incancestors([parentrev], rev)
98 if rev in reachable:
98 if rev in reachable:
99 return True
99 return True
100 for t in self.transplants.get(node):
100 for t in self.transplants.get(node):
101 # it might have been stripped
101 # it might have been stripped
102 if not hasnode(repo, t.lnode):
102 if not hasnode(repo, t.lnode):
103 self.transplants.remove(t)
103 self.transplants.remove(t)
104 return False
104 return False
105 lnoderev = repo.changelog.rev(t.lnode)
105 lnoderev = repo.changelog.rev(t.lnode)
106 if lnoderev in repo.changelog.incancestors([parentrev], lnoderev):
106 if lnoderev in repo.changelog.incancestors([parentrev], lnoderev):
107 return True
107 return True
108 return False
108 return False
109
109
110 def apply(self, repo, source, revmap, merges, opts={}):
110 def apply(self, repo, source, revmap, merges, opts={}):
111 '''apply the revisions in revmap one by one in revision order'''
111 '''apply the revisions in revmap one by one in revision order'''
112 revs = sorted(revmap)
112 revs = sorted(revmap)
113 p1, p2 = repo.dirstate.parents()
113 p1, p2 = repo.dirstate.parents()
114 pulls = []
114 pulls = []
115 diffopts = patch.diffopts(self.ui, opts)
115 diffopts = patch.diffopts(self.ui, opts)
116 diffopts.git = True
116 diffopts.git = True
117
117
118 lock = wlock = tr = None
118 lock = wlock = tr = None
119 try:
119 try:
120 wlock = repo.wlock()
120 wlock = repo.wlock()
121 lock = repo.lock()
121 lock = repo.lock()
122 tr = repo.transaction('transplant')
122 tr = repo.transaction('transplant')
123 for rev in revs:
123 for rev in revs:
124 node = revmap[rev]
124 node = revmap[rev]
125 revstr = '%s:%s' % (rev, short(node))
125 revstr = '%s:%s' % (rev, short(node))
126
126
127 if self.applied(repo, node, p1):
127 if self.applied(repo, node, p1):
128 self.ui.warn(_('skipping already applied revision %s\n') %
128 self.ui.warn(_('skipping already applied revision %s\n') %
129 revstr)
129 revstr)
130 continue
130 continue
131
131
132 parents = source.changelog.parents(node)
132 parents = source.changelog.parents(node)
133 if not (opts.get('filter') or opts.get('log')):
133 if not (opts.get('filter') or opts.get('log')):
134 # If the changeset parent is the same as the
134 # If the changeset parent is the same as the
135 # wdir's parent, just pull it.
135 # wdir's parent, just pull it.
136 if parents[0] == p1:
136 if parents[0] == p1:
137 pulls.append(node)
137 pulls.append(node)
138 p1 = node
138 p1 = node
139 continue
139 continue
140 if pulls:
140 if pulls:
141 if source != repo:
141 if source != repo:
142 repo.pull(source, heads=pulls)
142 repo.pull(source.peer(), heads=pulls)
143 merge.update(repo, pulls[-1], False, False, None)
143 merge.update(repo, pulls[-1], False, False, None)
144 p1, p2 = repo.dirstate.parents()
144 p1, p2 = repo.dirstate.parents()
145 pulls = []
145 pulls = []
146
146
147 domerge = False
147 domerge = False
148 if node in merges:
148 if node in merges:
149 # pulling all the merge revs at once would mean we
149 # pulling all the merge revs at once would mean we
150 # couldn't transplant after the latest even if
150 # couldn't transplant after the latest even if
151 # transplants before them fail.
151 # transplants before them fail.
152 domerge = True
152 domerge = True
153 if not hasnode(repo, node):
153 if not hasnode(repo, node):
154 repo.pull(source, heads=[node])
154 repo.pull(source, heads=[node])
155
155
156 skipmerge = False
156 skipmerge = False
157 if parents[1] != revlog.nullid:
157 if parents[1] != revlog.nullid:
158 if not opts.get('parent'):
158 if not opts.get('parent'):
159 self.ui.note(_('skipping merge changeset %s:%s\n')
159 self.ui.note(_('skipping merge changeset %s:%s\n')
160 % (rev, short(node)))
160 % (rev, short(node)))
161 skipmerge = True
161 skipmerge = True
162 else:
162 else:
163 parent = source.lookup(opts['parent'])
163 parent = source.lookup(opts['parent'])
164 if parent not in parents:
164 if parent not in parents:
165 raise util.Abort(_('%s is not a parent of %s') %
165 raise util.Abort(_('%s is not a parent of %s') %
166 (short(parent), short(node)))
166 (short(parent), short(node)))
167 else:
167 else:
168 parent = parents[0]
168 parent = parents[0]
169
169
170 if skipmerge:
170 if skipmerge:
171 patchfile = None
171 patchfile = None
172 else:
172 else:
173 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
173 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
174 fp = os.fdopen(fd, 'w')
174 fp = os.fdopen(fd, 'w')
175 gen = patch.diff(source, parent, node, opts=diffopts)
175 gen = patch.diff(source, parent, node, opts=diffopts)
176 for chunk in gen:
176 for chunk in gen:
177 fp.write(chunk)
177 fp.write(chunk)
178 fp.close()
178 fp.close()
179
179
180 del revmap[rev]
180 del revmap[rev]
181 if patchfile or domerge:
181 if patchfile or domerge:
182 try:
182 try:
183 try:
183 try:
184 n = self.applyone(repo, node,
184 n = self.applyone(repo, node,
185 source.changelog.read(node),
185 source.changelog.read(node),
186 patchfile, merge=domerge,
186 patchfile, merge=domerge,
187 log=opts.get('log'),
187 log=opts.get('log'),
188 filter=opts.get('filter'))
188 filter=opts.get('filter'))
189 except TransplantError:
189 except TransplantError:
190 # Do not rollback, it is up to the user to
190 # Do not rollback, it is up to the user to
191 # fix the merge or cancel everything
191 # fix the merge or cancel everything
192 tr.close()
192 tr.close()
193 raise
193 raise
194 if n and domerge:
194 if n and domerge:
195 self.ui.status(_('%s merged at %s\n') % (revstr,
195 self.ui.status(_('%s merged at %s\n') % (revstr,
196 short(n)))
196 short(n)))
197 elif n:
197 elif n:
198 self.ui.status(_('%s transplanted to %s\n')
198 self.ui.status(_('%s transplanted to %s\n')
199 % (short(node),
199 % (short(node),
200 short(n)))
200 short(n)))
201 finally:
201 finally:
202 if patchfile:
202 if patchfile:
203 os.unlink(patchfile)
203 os.unlink(patchfile)
204 tr.close()
204 tr.close()
205 if pulls:
205 if pulls:
206 repo.pull(source, heads=pulls)
206 repo.pull(source.peer(), heads=pulls)
207 merge.update(repo, pulls[-1], False, False, None)
207 merge.update(repo, pulls[-1], False, False, None)
208 finally:
208 finally:
209 self.saveseries(revmap, merges)
209 self.saveseries(revmap, merges)
210 self.transplants.write()
210 self.transplants.write()
211 if tr:
211 if tr:
212 tr.release()
212 tr.release()
213 lock.release()
213 lock.release()
214 wlock.release()
214 wlock.release()
215
215
216 def filter(self, filter, node, changelog, patchfile):
216 def filter(self, filter, node, changelog, patchfile):
217 '''arbitrarily rewrite changeset before applying it'''
217 '''arbitrarily rewrite changeset before applying it'''
218
218
219 self.ui.status(_('filtering %s\n') % patchfile)
219 self.ui.status(_('filtering %s\n') % patchfile)
220 user, date, msg = (changelog[1], changelog[2], changelog[4])
220 user, date, msg = (changelog[1], changelog[2], changelog[4])
221 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
221 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
222 fp = os.fdopen(fd, 'w')
222 fp = os.fdopen(fd, 'w')
223 fp.write("# HG changeset patch\n")
223 fp.write("# HG changeset patch\n")
224 fp.write("# User %s\n" % user)
224 fp.write("# User %s\n" % user)
225 fp.write("# Date %d %d\n" % date)
225 fp.write("# Date %d %d\n" % date)
226 fp.write(msg + '\n')
226 fp.write(msg + '\n')
227 fp.close()
227 fp.close()
228
228
229 try:
229 try:
230 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
230 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
231 util.shellquote(patchfile)),
231 util.shellquote(patchfile)),
232 environ={'HGUSER': changelog[1],
232 environ={'HGUSER': changelog[1],
233 'HGREVISION': revlog.hex(node),
233 'HGREVISION': revlog.hex(node),
234 },
234 },
235 onerr=util.Abort, errprefix=_('filter failed'),
235 onerr=util.Abort, errprefix=_('filter failed'),
236 out=self.ui.fout)
236 out=self.ui.fout)
237 user, date, msg = self.parselog(file(headerfile))[1:4]
237 user, date, msg = self.parselog(file(headerfile))[1:4]
238 finally:
238 finally:
239 os.unlink(headerfile)
239 os.unlink(headerfile)
240
240
241 return (user, date, msg)
241 return (user, date, msg)
242
242
243 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
243 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
244 filter=None):
244 filter=None):
245 '''apply the patch in patchfile to the repository as a transplant'''
245 '''apply the patch in patchfile to the repository as a transplant'''
246 (manifest, user, (time, timezone), files, message) = cl[:5]
246 (manifest, user, (time, timezone), files, message) = cl[:5]
247 date = "%d %d" % (time, timezone)
247 date = "%d %d" % (time, timezone)
248 extra = {'transplant_source': node}
248 extra = {'transplant_source': node}
249 if filter:
249 if filter:
250 (user, date, message) = self.filter(filter, node, cl, patchfile)
250 (user, date, message) = self.filter(filter, node, cl, patchfile)
251
251
252 if log:
252 if log:
253 # we don't translate messages inserted into commits
253 # we don't translate messages inserted into commits
254 message += '\n(transplanted from %s)' % revlog.hex(node)
254 message += '\n(transplanted from %s)' % revlog.hex(node)
255
255
256 self.ui.status(_('applying %s\n') % short(node))
256 self.ui.status(_('applying %s\n') % short(node))
257 self.ui.note('%s %s\n%s\n' % (user, date, message))
257 self.ui.note('%s %s\n%s\n' % (user, date, message))
258
258
259 if not patchfile and not merge:
259 if not patchfile and not merge:
260 raise util.Abort(_('can only omit patchfile if merging'))
260 raise util.Abort(_('can only omit patchfile if merging'))
261 if patchfile:
261 if patchfile:
262 try:
262 try:
263 files = set()
263 files = set()
264 patch.patch(self.ui, repo, patchfile, files=files, eolmode=None)
264 patch.patch(self.ui, repo, patchfile, files=files, eolmode=None)
265 files = list(files)
265 files = list(files)
266 if not files:
266 if not files:
267 self.ui.warn(_('%s: empty changeset') % revlog.hex(node))
267 self.ui.warn(_('%s: empty changeset') % revlog.hex(node))
268 return None
268 return None
269 except Exception, inst:
269 except Exception, inst:
270 seriespath = os.path.join(self.path, 'series')
270 seriespath = os.path.join(self.path, 'series')
271 if os.path.exists(seriespath):
271 if os.path.exists(seriespath):
272 os.unlink(seriespath)
272 os.unlink(seriespath)
273 p1 = repo.dirstate.p1()
273 p1 = repo.dirstate.p1()
274 p2 = node
274 p2 = node
275 self.log(user, date, message, p1, p2, merge=merge)
275 self.log(user, date, message, p1, p2, merge=merge)
276 self.ui.write(str(inst) + '\n')
276 self.ui.write(str(inst) + '\n')
277 raise TransplantError(_('fix up the merge and run '
277 raise TransplantError(_('fix up the merge and run '
278 'hg transplant --continue'))
278 'hg transplant --continue'))
279 else:
279 else:
280 files = None
280 files = None
281 if merge:
281 if merge:
282 p1, p2 = repo.dirstate.parents()
282 p1, p2 = repo.dirstate.parents()
283 repo.setparents(p1, node)
283 repo.setparents(p1, node)
284 m = match.always(repo.root, '')
284 m = match.always(repo.root, '')
285 else:
285 else:
286 m = match.exact(repo.root, '', files)
286 m = match.exact(repo.root, '', files)
287
287
288 n = repo.commit(message, user, date, extra=extra, match=m,
288 n = repo.commit(message, user, date, extra=extra, match=m,
289 editor=self.editor)
289 editor=self.editor)
290 if not n:
290 if not n:
291 # Crash here to prevent an unclear crash later, in
291 # Crash here to prevent an unclear crash later, in
292 # transplants.write(). This can happen if patch.patch()
292 # transplants.write(). This can happen if patch.patch()
293 # does nothing but claims success or if repo.status() fails
293 # does nothing but claims success or if repo.status() fails
294 # to report changes done by patch.patch(). These both
294 # to report changes done by patch.patch(). These both
295 # appear to be bugs in other parts of Mercurial, but dying
295 # appear to be bugs in other parts of Mercurial, but dying
296 # here, as soon as we can detect the problem, is preferable
296 # here, as soon as we can detect the problem, is preferable
297 # to silently dropping changesets on the floor.
297 # to silently dropping changesets on the floor.
298 raise RuntimeError('nothing committed after transplant')
298 raise RuntimeError('nothing committed after transplant')
299 if not merge:
299 if not merge:
300 self.transplants.set(n, node)
300 self.transplants.set(n, node)
301
301
302 return n
302 return n
303
303
304 def resume(self, repo, source, opts=None):
304 def resume(self, repo, source, opts=None):
305 '''recover last transaction and apply remaining changesets'''
305 '''recover last transaction and apply remaining changesets'''
306 if os.path.exists(os.path.join(self.path, 'journal')):
306 if os.path.exists(os.path.join(self.path, 'journal')):
307 n, node = self.recover(repo)
307 n, node = self.recover(repo)
308 self.ui.status(_('%s transplanted as %s\n') % (short(node),
308 self.ui.status(_('%s transplanted as %s\n') % (short(node),
309 short(n)))
309 short(n)))
310 seriespath = os.path.join(self.path, 'series')
310 seriespath = os.path.join(self.path, 'series')
311 if not os.path.exists(seriespath):
311 if not os.path.exists(seriespath):
312 self.transplants.write()
312 self.transplants.write()
313 return
313 return
314 nodes, merges = self.readseries()
314 nodes, merges = self.readseries()
315 revmap = {}
315 revmap = {}
316 for n in nodes:
316 for n in nodes:
317 revmap[source.changelog.rev(n)] = n
317 revmap[source.changelog.rev(n)] = n
318 os.unlink(seriespath)
318 os.unlink(seriespath)
319
319
320 self.apply(repo, source, revmap, merges, opts)
320 self.apply(repo, source, revmap, merges, opts)
321
321
322 def recover(self, repo):
322 def recover(self, repo):
323 '''commit working directory using journal metadata'''
323 '''commit working directory using journal metadata'''
324 node, user, date, message, parents = self.readlog()
324 node, user, date, message, parents = self.readlog()
325 merge = False
325 merge = False
326
326
327 if not user or not date or not message or not parents[0]:
327 if not user or not date or not message or not parents[0]:
328 raise util.Abort(_('transplant log file is corrupt'))
328 raise util.Abort(_('transplant log file is corrupt'))
329
329
330 parent = parents[0]
330 parent = parents[0]
331 if len(parents) > 1:
331 if len(parents) > 1:
332 if opts.get('parent'):
332 if opts.get('parent'):
333 parent = source.lookup(opts['parent'])
333 parent = source.lookup(opts['parent'])
334 if parent not in parents:
334 if parent not in parents:
335 raise util.Abort(_('%s is not a parent of %s') %
335 raise util.Abort(_('%s is not a parent of %s') %
336 (short(parent), short(node)))
336 (short(parent), short(node)))
337 else:
337 else:
338 merge = True
338 merge = True
339
339
340 extra = {'transplant_source': node}
340 extra = {'transplant_source': node}
341 wlock = repo.wlock()
341 wlock = repo.wlock()
342 try:
342 try:
343 p1, p2 = repo.dirstate.parents()
343 p1, p2 = repo.dirstate.parents()
344 if p1 != parent:
344 if p1 != parent:
345 raise util.Abort(
345 raise util.Abort(
346 _('working dir not at transplant parent %s') %
346 _('working dir not at transplant parent %s') %
347 revlog.hex(parent))
347 revlog.hex(parent))
348 if merge:
348 if merge:
349 repo.setparents(p1, parents[1])
349 repo.setparents(p1, parents[1])
350 n = repo.commit(message, user, date, extra=extra,
350 n = repo.commit(message, user, date, extra=extra,
351 editor=self.editor)
351 editor=self.editor)
352 if not n:
352 if not n:
353 raise util.Abort(_('commit failed'))
353 raise util.Abort(_('commit failed'))
354 if not merge:
354 if not merge:
355 self.transplants.set(n, node)
355 self.transplants.set(n, node)
356 self.unlog()
356 self.unlog()
357
357
358 return n, node
358 return n, node
359 finally:
359 finally:
360 wlock.release()
360 wlock.release()
361
361
362 def readseries(self):
362 def readseries(self):
363 nodes = []
363 nodes = []
364 merges = []
364 merges = []
365 cur = nodes
365 cur = nodes
366 for line in self.opener.read('series').splitlines():
366 for line in self.opener.read('series').splitlines():
367 if line.startswith('# Merges'):
367 if line.startswith('# Merges'):
368 cur = merges
368 cur = merges
369 continue
369 continue
370 cur.append(revlog.bin(line))
370 cur.append(revlog.bin(line))
371
371
372 return (nodes, merges)
372 return (nodes, merges)
373
373
374 def saveseries(self, revmap, merges):
374 def saveseries(self, revmap, merges):
375 if not revmap:
375 if not revmap:
376 return
376 return
377
377
378 if not os.path.isdir(self.path):
378 if not os.path.isdir(self.path):
379 os.mkdir(self.path)
379 os.mkdir(self.path)
380 series = self.opener('series', 'w')
380 series = self.opener('series', 'w')
381 for rev in sorted(revmap):
381 for rev in sorted(revmap):
382 series.write(revlog.hex(revmap[rev]) + '\n')
382 series.write(revlog.hex(revmap[rev]) + '\n')
383 if merges:
383 if merges:
384 series.write('# Merges\n')
384 series.write('# Merges\n')
385 for m in merges:
385 for m in merges:
386 series.write(revlog.hex(m) + '\n')
386 series.write(revlog.hex(m) + '\n')
387 series.close()
387 series.close()
388
388
389 def parselog(self, fp):
389 def parselog(self, fp):
390 parents = []
390 parents = []
391 message = []
391 message = []
392 node = revlog.nullid
392 node = revlog.nullid
393 inmsg = False
393 inmsg = False
394 user = None
394 user = None
395 date = None
395 date = None
396 for line in fp.read().splitlines():
396 for line in fp.read().splitlines():
397 if inmsg:
397 if inmsg:
398 message.append(line)
398 message.append(line)
399 elif line.startswith('# User '):
399 elif line.startswith('# User '):
400 user = line[7:]
400 user = line[7:]
401 elif line.startswith('# Date '):
401 elif line.startswith('# Date '):
402 date = line[7:]
402 date = line[7:]
403 elif line.startswith('# Node ID '):
403 elif line.startswith('# Node ID '):
404 node = revlog.bin(line[10:])
404 node = revlog.bin(line[10:])
405 elif line.startswith('# Parent '):
405 elif line.startswith('# Parent '):
406 parents.append(revlog.bin(line[9:]))
406 parents.append(revlog.bin(line[9:]))
407 elif not line.startswith('# '):
407 elif not line.startswith('# '):
408 inmsg = True
408 inmsg = True
409 message.append(line)
409 message.append(line)
410 if None in (user, date):
410 if None in (user, date):
411 raise util.Abort(_("filter corrupted changeset (no user or date)"))
411 raise util.Abort(_("filter corrupted changeset (no user or date)"))
412 return (node, user, date, '\n'.join(message), parents)
412 return (node, user, date, '\n'.join(message), parents)
413
413
414 def log(self, user, date, message, p1, p2, merge=False):
414 def log(self, user, date, message, p1, p2, merge=False):
415 '''journal changelog metadata for later recover'''
415 '''journal changelog metadata for later recover'''
416
416
417 if not os.path.isdir(self.path):
417 if not os.path.isdir(self.path):
418 os.mkdir(self.path)
418 os.mkdir(self.path)
419 fp = self.opener('journal', 'w')
419 fp = self.opener('journal', 'w')
420 fp.write('# User %s\n' % user)
420 fp.write('# User %s\n' % user)
421 fp.write('# Date %s\n' % date)
421 fp.write('# Date %s\n' % date)
422 fp.write('# Node ID %s\n' % revlog.hex(p2))
422 fp.write('# Node ID %s\n' % revlog.hex(p2))
423 fp.write('# Parent ' + revlog.hex(p1) + '\n')
423 fp.write('# Parent ' + revlog.hex(p1) + '\n')
424 if merge:
424 if merge:
425 fp.write('# Parent ' + revlog.hex(p2) + '\n')
425 fp.write('# Parent ' + revlog.hex(p2) + '\n')
426 fp.write(message.rstrip() + '\n')
426 fp.write(message.rstrip() + '\n')
427 fp.close()
427 fp.close()
428
428
429 def readlog(self):
429 def readlog(self):
430 return self.parselog(self.opener('journal'))
430 return self.parselog(self.opener('journal'))
431
431
432 def unlog(self):
432 def unlog(self):
433 '''remove changelog journal'''
433 '''remove changelog journal'''
434 absdst = os.path.join(self.path, 'journal')
434 absdst = os.path.join(self.path, 'journal')
435 if os.path.exists(absdst):
435 if os.path.exists(absdst):
436 os.unlink(absdst)
436 os.unlink(absdst)
437
437
438 def transplantfilter(self, repo, source, root):
438 def transplantfilter(self, repo, source, root):
439 def matchfn(node):
439 def matchfn(node):
440 if self.applied(repo, node, root):
440 if self.applied(repo, node, root):
441 return False
441 return False
442 if source.changelog.parents(node)[1] != revlog.nullid:
442 if source.changelog.parents(node)[1] != revlog.nullid:
443 return False
443 return False
444 extra = source.changelog.read(node)[5]
444 extra = source.changelog.read(node)[5]
445 cnode = extra.get('transplant_source')
445 cnode = extra.get('transplant_source')
446 if cnode and self.applied(repo, cnode, root):
446 if cnode and self.applied(repo, cnode, root):
447 return False
447 return False
448 return True
448 return True
449
449
450 return matchfn
450 return matchfn
451
451
452 def hasnode(repo, node):
452 def hasnode(repo, node):
453 try:
453 try:
454 return repo.changelog.rev(node) is not None
454 return repo.changelog.rev(node) is not None
455 except error.RevlogError:
455 except error.RevlogError:
456 return False
456 return False
457
457
458 def browserevs(ui, repo, nodes, opts):
458 def browserevs(ui, repo, nodes, opts):
459 '''interactively transplant changesets'''
459 '''interactively transplant changesets'''
460 def browsehelp(ui):
460 def browsehelp(ui):
461 ui.write(_('y: transplant this changeset\n'
461 ui.write(_('y: transplant this changeset\n'
462 'n: skip this changeset\n'
462 'n: skip this changeset\n'
463 'm: merge at this changeset\n'
463 'm: merge at this changeset\n'
464 'p: show patch\n'
464 'p: show patch\n'
465 'c: commit selected changesets\n'
465 'c: commit selected changesets\n'
466 'q: cancel transplant\n'
466 'q: cancel transplant\n'
467 '?: show this help\n'))
467 '?: show this help\n'))
468
468
469 displayer = cmdutil.show_changeset(ui, repo, opts)
469 displayer = cmdutil.show_changeset(ui, repo, opts)
470 transplants = []
470 transplants = []
471 merges = []
471 merges = []
472 for node in nodes:
472 for node in nodes:
473 displayer.show(repo[node])
473 displayer.show(repo[node])
474 action = None
474 action = None
475 while not action:
475 while not action:
476 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
476 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
477 if action == '?':
477 if action == '?':
478 browsehelp(ui)
478 browsehelp(ui)
479 action = None
479 action = None
480 elif action == 'p':
480 elif action == 'p':
481 parent = repo.changelog.parents(node)[0]
481 parent = repo.changelog.parents(node)[0]
482 for chunk in patch.diff(repo, parent, node):
482 for chunk in patch.diff(repo, parent, node):
483 ui.write(chunk)
483 ui.write(chunk)
484 action = None
484 action = None
485 elif action not in ('y', 'n', 'm', 'c', 'q'):
485 elif action not in ('y', 'n', 'm', 'c', 'q'):
486 ui.write(_('no such option\n'))
486 ui.write(_('no such option\n'))
487 action = None
487 action = None
488 if action == 'y':
488 if action == 'y':
489 transplants.append(node)
489 transplants.append(node)
490 elif action == 'm':
490 elif action == 'm':
491 merges.append(node)
491 merges.append(node)
492 elif action == 'c':
492 elif action == 'c':
493 break
493 break
494 elif action == 'q':
494 elif action == 'q':
495 transplants = ()
495 transplants = ()
496 merges = ()
496 merges = ()
497 break
497 break
498 displayer.close()
498 displayer.close()
499 return (transplants, merges)
499 return (transplants, merges)
500
500
501 @command('transplant',
501 @command('transplant',
502 [('s', 'source', '', _('pull patches from REPO'), _('REPO')),
502 [('s', 'source', '', _('pull patches from REPO'), _('REPO')),
503 ('b', 'branch', [],
503 ('b', 'branch', [],
504 _('pull patches from branch BRANCH'), _('BRANCH')),
504 _('pull patches from branch BRANCH'), _('BRANCH')),
505 ('a', 'all', None, _('pull all changesets up to BRANCH')),
505 ('a', 'all', None, _('pull all changesets up to BRANCH')),
506 ('p', 'prune', [], _('skip over REV'), _('REV')),
506 ('p', 'prune', [], _('skip over REV'), _('REV')),
507 ('m', 'merge', [], _('merge at REV'), _('REV')),
507 ('m', 'merge', [], _('merge at REV'), _('REV')),
508 ('', 'parent', '',
508 ('', 'parent', '',
509 _('parent to choose when transplanting merge'), _('REV')),
509 _('parent to choose when transplanting merge'), _('REV')),
510 ('e', 'edit', False, _('invoke editor on commit messages')),
510 ('e', 'edit', False, _('invoke editor on commit messages')),
511 ('', 'log', None, _('append transplant info to log message')),
511 ('', 'log', None, _('append transplant info to log message')),
512 ('c', 'continue', None, _('continue last transplant session '
512 ('c', 'continue', None, _('continue last transplant session '
513 'after repair')),
513 'after repair')),
514 ('', 'filter', '',
514 ('', 'filter', '',
515 _('filter changesets through command'), _('CMD'))],
515 _('filter changesets through command'), _('CMD'))],
516 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
516 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
517 '[-m REV] [REV]...'))
517 '[-m REV] [REV]...'))
518 def transplant(ui, repo, *revs, **opts):
518 def transplant(ui, repo, *revs, **opts):
519 '''transplant changesets from another branch
519 '''transplant changesets from another branch
520
520
521 Selected changesets will be applied on top of the current working
521 Selected changesets will be applied on top of the current working
522 directory with the log of the original changeset. The changesets
522 directory with the log of the original changeset. The changesets
523 are copied and will thus appear twice in the history. Use the
523 are copied and will thus appear twice in the history. Use the
524 rebase extension instead if you want to move a whole branch of
524 rebase extension instead if you want to move a whole branch of
525 unpublished changesets.
525 unpublished changesets.
526
526
527 If --log is specified, log messages will have a comment appended
527 If --log is specified, log messages will have a comment appended
528 of the form::
528 of the form::
529
529
530 (transplanted from CHANGESETHASH)
530 (transplanted from CHANGESETHASH)
531
531
532 You can rewrite the changelog message with the --filter option.
532 You can rewrite the changelog message with the --filter option.
533 Its argument will be invoked with the current changelog message as
533 Its argument will be invoked with the current changelog message as
534 $1 and the patch as $2.
534 $1 and the patch as $2.
535
535
536 If --source/-s is specified, selects changesets from the named
536 If --source/-s is specified, selects changesets from the named
537 repository. If --branch/-b is specified, selects changesets from
537 repository. If --branch/-b is specified, selects changesets from
538 the branch holding the named revision, up to that revision. If
538 the branch holding the named revision, up to that revision. If
539 --all/-a is specified, all changesets on the branch will be
539 --all/-a is specified, all changesets on the branch will be
540 transplanted, otherwise you will be prompted to select the
540 transplanted, otherwise you will be prompted to select the
541 changesets you want.
541 changesets you want.
542
542
543 :hg:`transplant --branch REV --all` will transplant the
543 :hg:`transplant --branch REV --all` will transplant the
544 selected branch (up to the named revision) onto your current
544 selected branch (up to the named revision) onto your current
545 working directory.
545 working directory.
546
546
547 You can optionally mark selected transplanted changesets as merge
547 You can optionally mark selected transplanted changesets as merge
548 changesets. You will not be prompted to transplant any ancestors
548 changesets. You will not be prompted to transplant any ancestors
549 of a merged transplant, and you can merge descendants of them
549 of a merged transplant, and you can merge descendants of them
550 normally instead of transplanting them.
550 normally instead of transplanting them.
551
551
552 Merge changesets may be transplanted directly by specifying the
552 Merge changesets may be transplanted directly by specifying the
553 proper parent changeset by calling :hg:`transplant --parent`.
553 proper parent changeset by calling :hg:`transplant --parent`.
554
554
555 If no merges or revisions are provided, :hg:`transplant` will
555 If no merges or revisions are provided, :hg:`transplant` will
556 start an interactive changeset browser.
556 start an interactive changeset browser.
557
557
558 If a changeset application fails, you can fix the merge by hand
558 If a changeset application fails, you can fix the merge by hand
559 and then resume where you left off by calling :hg:`transplant
559 and then resume where you left off by calling :hg:`transplant
560 --continue/-c`.
560 --continue/-c`.
561 '''
561 '''
562 def incwalk(repo, csets, match=util.always):
562 def incwalk(repo, csets, match=util.always):
563 for node in csets:
563 for node in csets:
564 if match(node):
564 if match(node):
565 yield node
565 yield node
566
566
567 def transplantwalk(repo, root, branches, match=util.always):
567 def transplantwalk(repo, root, branches, match=util.always):
568 if not branches:
568 if not branches:
569 branches = repo.heads()
569 branches = repo.heads()
570 ancestors = []
570 ancestors = []
571 for branch in branches:
571 for branch in branches:
572 ancestors.append(repo.changelog.ancestor(root, branch))
572 ancestors.append(repo.changelog.ancestor(root, branch))
573 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
573 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
574 if match(node):
574 if match(node):
575 yield node
575 yield node
576
576
577 def checkopts(opts, revs):
577 def checkopts(opts, revs):
578 if opts.get('continue'):
578 if opts.get('continue'):
579 if opts.get('branch') or opts.get('all') or opts.get('merge'):
579 if opts.get('branch') or opts.get('all') or opts.get('merge'):
580 raise util.Abort(_('--continue is incompatible with '
580 raise util.Abort(_('--continue is incompatible with '
581 'branch, all or merge'))
581 'branch, all or merge'))
582 return
582 return
583 if not (opts.get('source') or revs or
583 if not (opts.get('source') or revs or
584 opts.get('merge') or opts.get('branch')):
584 opts.get('merge') or opts.get('branch')):
585 raise util.Abort(_('no source URL, branch tag or revision '
585 raise util.Abort(_('no source URL, branch tag or revision '
586 'list provided'))
586 'list provided'))
587 if opts.get('all'):
587 if opts.get('all'):
588 if not opts.get('branch'):
588 if not opts.get('branch'):
589 raise util.Abort(_('--all requires a branch revision'))
589 raise util.Abort(_('--all requires a branch revision'))
590 if revs:
590 if revs:
591 raise util.Abort(_('--all is incompatible with a '
591 raise util.Abort(_('--all is incompatible with a '
592 'revision list'))
592 'revision list'))
593
593
594 checkopts(opts, revs)
594 checkopts(opts, revs)
595
595
596 if not opts.get('log'):
596 if not opts.get('log'):
597 opts['log'] = ui.config('transplant', 'log')
597 opts['log'] = ui.config('transplant', 'log')
598 if not opts.get('filter'):
598 if not opts.get('filter'):
599 opts['filter'] = ui.config('transplant', 'filter')
599 opts['filter'] = ui.config('transplant', 'filter')
600
600
601 tp = transplanter(ui, repo)
601 tp = transplanter(ui, repo)
602 if opts.get('edit'):
602 if opts.get('edit'):
603 tp.editor = cmdutil.commitforceeditor
603 tp.editor = cmdutil.commitforceeditor
604
604
605 p1, p2 = repo.dirstate.parents()
605 p1, p2 = repo.dirstate.parents()
606 if len(repo) > 0 and p1 == revlog.nullid:
606 if len(repo) > 0 and p1 == revlog.nullid:
607 raise util.Abort(_('no revision checked out'))
607 raise util.Abort(_('no revision checked out'))
608 if not opts.get('continue'):
608 if not opts.get('continue'):
609 if p2 != revlog.nullid:
609 if p2 != revlog.nullid:
610 raise util.Abort(_('outstanding uncommitted merges'))
610 raise util.Abort(_('outstanding uncommitted merges'))
611 m, a, r, d = repo.status()[:4]
611 m, a, r, d = repo.status()[:4]
612 if m or a or r or d:
612 if m or a or r or d:
613 raise util.Abort(_('outstanding local changes'))
613 raise util.Abort(_('outstanding local changes'))
614
614
615 sourcerepo = opts.get('source')
615 sourcerepo = opts.get('source')
616 if sourcerepo:
616 if sourcerepo:
617 source = hg.peer(ui, opts, ui.expandpath(sourcerepo))
617 peer = hg.peer(ui, opts, ui.expandpath(sourcerepo))
618 branches = map(source.lookup, opts.get('branch', ()))
618 branches = map(peer.lookup, opts.get('branch', ()))
619 source, csets, cleanupfn = bundlerepo.getremotechanges(ui, repo, source,
619 source, csets, cleanupfn = bundlerepo.getremotechanges(ui, repo, peer,
620 onlyheads=branches, force=True)
620 onlyheads=branches, force=True)
621 else:
621 else:
622 source = repo
622 source = repo
623 branches = map(source.lookup, opts.get('branch', ()))
623 branches = map(source.lookup, opts.get('branch', ()))
624 cleanupfn = None
624 cleanupfn = None
625
625
626 try:
626 try:
627 if opts.get('continue'):
627 if opts.get('continue'):
628 tp.resume(repo, source, opts)
628 tp.resume(repo, source, opts)
629 return
629 return
630
630
631 tf = tp.transplantfilter(repo, source, p1)
631 tf = tp.transplantfilter(repo, source, p1)
632 if opts.get('prune'):
632 if opts.get('prune'):
633 prune = [source.lookup(r)
633 prune = [source.lookup(r)
634 for r in scmutil.revrange(source, opts.get('prune'))]
634 for r in scmutil.revrange(source, opts.get('prune'))]
635 matchfn = lambda x: tf(x) and x not in prune
635 matchfn = lambda x: tf(x) and x not in prune
636 else:
636 else:
637 matchfn = tf
637 matchfn = tf
638 merges = map(source.lookup, opts.get('merge', ()))
638 merges = map(source.lookup, opts.get('merge', ()))
639 revmap = {}
639 revmap = {}
640 if revs:
640 if revs:
641 for r in scmutil.revrange(source, revs):
641 for r in scmutil.revrange(source, revs):
642 revmap[int(r)] = source.lookup(r)
642 revmap[int(r)] = source.lookup(r)
643 elif opts.get('all') or not merges:
643 elif opts.get('all') or not merges:
644 if source != repo:
644 if source != repo:
645 alltransplants = incwalk(source, csets, match=matchfn)
645 alltransplants = incwalk(source, csets, match=matchfn)
646 else:
646 else:
647 alltransplants = transplantwalk(source, p1, branches,
647 alltransplants = transplantwalk(source, p1, branches,
648 match=matchfn)
648 match=matchfn)
649 if opts.get('all'):
649 if opts.get('all'):
650 revs = alltransplants
650 revs = alltransplants
651 else:
651 else:
652 revs, newmerges = browserevs(ui, source, alltransplants, opts)
652 revs, newmerges = browserevs(ui, source, alltransplants, opts)
653 merges.extend(newmerges)
653 merges.extend(newmerges)
654 for r in revs:
654 for r in revs:
655 revmap[source.changelog.rev(r)] = r
655 revmap[source.changelog.rev(r)] = r
656 for r in merges:
656 for r in merges:
657 revmap[source.changelog.rev(r)] = r
657 revmap[source.changelog.rev(r)] = r
658
658
659 tp.apply(repo, source, revmap, merges, opts)
659 tp.apply(repo, source, revmap, merges, opts)
660 finally:
660 finally:
661 if cleanupfn:
661 if cleanupfn:
662 cleanupfn()
662 cleanupfn()
663
663
664 def revsettransplanted(repo, subset, x):
664 def revsettransplanted(repo, subset, x):
665 """``transplanted([set])``
665 """``transplanted([set])``
666 Transplanted changesets in set, or all transplanted changesets.
666 Transplanted changesets in set, or all transplanted changesets.
667 """
667 """
668 if x:
668 if x:
669 s = revset.getset(repo, subset, x)
669 s = revset.getset(repo, subset, x)
670 else:
670 else:
671 s = subset
671 s = subset
672 return [r for r in s if repo[r].extra().get('transplant_source')]
672 return [r for r in s if repo[r].extra().get('transplant_source')]
673
673
674 def kwtransplanted(repo, ctx, **args):
674 def kwtransplanted(repo, ctx, **args):
675 """:transplanted: String. The node identifier of the transplanted
675 """:transplanted: String. The node identifier of the transplanted
676 changeset if any."""
676 changeset if any."""
677 n = ctx.extra().get('transplant_source')
677 n = ctx.extra().get('transplant_source')
678 return n and revlog.hex(n) or ''
678 return n and revlog.hex(n) or ''
679
679
680 def extsetup(ui):
680 def extsetup(ui):
681 revset.symbols['transplanted'] = revsettransplanted
681 revset.symbols['transplanted'] = revsettransplanted
682 templatekw.keywords['transplanted'] = kwtransplanted
682 templatekw.keywords['transplanted'] = kwtransplanted
683
683
684 # tell hggettext to extract docstrings from these functions:
684 # tell hggettext to extract docstrings from these functions:
685 i18nfunctions = [revsettransplanted, kwtransplanted]
685 i18nfunctions = [revsettransplanted, kwtransplanted]
@@ -1,380 +1,380 b''
1 # bundlerepo.py - repository class for viewing uncompressed bundles
1 # bundlerepo.py - repository class for viewing uncompressed bundles
2 #
2 #
3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.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 """Repository class for viewing uncompressed bundles.
8 """Repository class for viewing uncompressed bundles.
9
9
10 This provides a read-only repository interface to bundles as if they
10 This provides a read-only repository interface to bundles as if they
11 were part of the actual repository.
11 were part of the actual repository.
12 """
12 """
13
13
14 from node import nullid
14 from node import nullid
15 from i18n import _
15 from i18n import _
16 import os, tempfile, shutil
16 import os, tempfile, shutil
17 import changegroup, util, mdiff, discovery, cmdutil
17 import changegroup, util, mdiff, discovery, cmdutil
18 import localrepo, changelog, manifest, filelog, revlog, error
18 import localrepo, changelog, manifest, filelog, revlog, error
19
19
20 class bundlerevlog(revlog.revlog):
20 class bundlerevlog(revlog.revlog):
21 def __init__(self, opener, indexfile, bundle, linkmapper):
21 def __init__(self, opener, indexfile, bundle, linkmapper):
22 # How it works:
22 # How it works:
23 # to retrieve a revision, we need to know the offset of
23 # to retrieve a revision, we need to know the offset of
24 # the revision in the bundle (an unbundle object).
24 # the revision in the bundle (an unbundle object).
25 #
25 #
26 # We store this offset in the index (start), to differentiate a
26 # We store this offset in the index (start), to differentiate a
27 # rev in the bundle and from a rev in the revlog, we check
27 # rev in the bundle and from a rev in the revlog, we check
28 # len(index[r]). If the tuple is bigger than 7, it is a bundle
28 # len(index[r]). If the tuple is bigger than 7, it is a bundle
29 # (it is bigger since we store the node to which the delta is)
29 # (it is bigger since we store the node to which the delta is)
30 #
30 #
31 revlog.revlog.__init__(self, opener, indexfile)
31 revlog.revlog.__init__(self, opener, indexfile)
32 self.bundle = bundle
32 self.bundle = bundle
33 self.basemap = {}
33 self.basemap = {}
34 n = len(self)
34 n = len(self)
35 chain = None
35 chain = None
36 while True:
36 while True:
37 chunkdata = bundle.deltachunk(chain)
37 chunkdata = bundle.deltachunk(chain)
38 if not chunkdata:
38 if not chunkdata:
39 break
39 break
40 node = chunkdata['node']
40 node = chunkdata['node']
41 p1 = chunkdata['p1']
41 p1 = chunkdata['p1']
42 p2 = chunkdata['p2']
42 p2 = chunkdata['p2']
43 cs = chunkdata['cs']
43 cs = chunkdata['cs']
44 deltabase = chunkdata['deltabase']
44 deltabase = chunkdata['deltabase']
45 delta = chunkdata['delta']
45 delta = chunkdata['delta']
46
46
47 size = len(delta)
47 size = len(delta)
48 start = bundle.tell() - size
48 start = bundle.tell() - size
49
49
50 link = linkmapper(cs)
50 link = linkmapper(cs)
51 if node in self.nodemap:
51 if node in self.nodemap:
52 # this can happen if two branches make the same change
52 # this can happen if two branches make the same change
53 chain = node
53 chain = node
54 continue
54 continue
55
55
56 for p in (p1, p2):
56 for p in (p1, p2):
57 if p not in self.nodemap:
57 if p not in self.nodemap:
58 raise error.LookupError(p, self.indexfile,
58 raise error.LookupError(p, self.indexfile,
59 _("unknown parent"))
59 _("unknown parent"))
60 # start, size, full unc. size, base (unused), link, p1, p2, node
60 # start, size, full unc. size, base (unused), link, p1, p2, node
61 e = (revlog.offset_type(start, 0), size, -1, -1, link,
61 e = (revlog.offset_type(start, 0), size, -1, -1, link,
62 self.rev(p1), self.rev(p2), node)
62 self.rev(p1), self.rev(p2), node)
63 self.basemap[n] = deltabase
63 self.basemap[n] = deltabase
64 self.index.insert(-1, e)
64 self.index.insert(-1, e)
65 self.nodemap[node] = n
65 self.nodemap[node] = n
66 chain = node
66 chain = node
67 n += 1
67 n += 1
68
68
69 def inbundle(self, rev):
69 def inbundle(self, rev):
70 """is rev from the bundle"""
70 """is rev from the bundle"""
71 if rev < 0:
71 if rev < 0:
72 return False
72 return False
73 return rev in self.basemap
73 return rev in self.basemap
74 def bundlebase(self, rev):
74 def bundlebase(self, rev):
75 return self.basemap[rev]
75 return self.basemap[rev]
76 def _chunk(self, rev):
76 def _chunk(self, rev):
77 # Warning: in case of bundle, the diff is against bundlebase,
77 # Warning: in case of bundle, the diff is against bundlebase,
78 # not against rev - 1
78 # not against rev - 1
79 # XXX: could use some caching
79 # XXX: could use some caching
80 if not self.inbundle(rev):
80 if not self.inbundle(rev):
81 return revlog.revlog._chunk(self, rev)
81 return revlog.revlog._chunk(self, rev)
82 self.bundle.seek(self.start(rev))
82 self.bundle.seek(self.start(rev))
83 return self.bundle.read(self.length(rev))
83 return self.bundle.read(self.length(rev))
84
84
85 def revdiff(self, rev1, rev2):
85 def revdiff(self, rev1, rev2):
86 """return or calculate a delta between two revisions"""
86 """return or calculate a delta between two revisions"""
87 if self.inbundle(rev1) and self.inbundle(rev2):
87 if self.inbundle(rev1) and self.inbundle(rev2):
88 # hot path for bundle
88 # hot path for bundle
89 revb = self.rev(self.bundlebase(rev2))
89 revb = self.rev(self.bundlebase(rev2))
90 if revb == rev1:
90 if revb == rev1:
91 return self._chunk(rev2)
91 return self._chunk(rev2)
92 elif not self.inbundle(rev1) and not self.inbundle(rev2):
92 elif not self.inbundle(rev1) and not self.inbundle(rev2):
93 return revlog.revlog.revdiff(self, rev1, rev2)
93 return revlog.revlog.revdiff(self, rev1, rev2)
94
94
95 return mdiff.textdiff(self.revision(self.node(rev1)),
95 return mdiff.textdiff(self.revision(self.node(rev1)),
96 self.revision(self.node(rev2)))
96 self.revision(self.node(rev2)))
97
97
98 def revision(self, nodeorrev):
98 def revision(self, nodeorrev):
99 """return an uncompressed revision of a given node or revision
99 """return an uncompressed revision of a given node or revision
100 number.
100 number.
101 """
101 """
102 if isinstance(nodeorrev, int):
102 if isinstance(nodeorrev, int):
103 rev = nodeorrev
103 rev = nodeorrev
104 node = self.node(rev)
104 node = self.node(rev)
105 else:
105 else:
106 node = nodeorrev
106 node = nodeorrev
107 rev = self.rev(node)
107 rev = self.rev(node)
108
108
109 if node == nullid:
109 if node == nullid:
110 return ""
110 return ""
111
111
112 text = None
112 text = None
113 chain = []
113 chain = []
114 iter_node = node
114 iter_node = node
115 # reconstruct the revision if it is from a changegroup
115 # reconstruct the revision if it is from a changegroup
116 while self.inbundle(rev):
116 while self.inbundle(rev):
117 if self._cache and self._cache[0] == iter_node:
117 if self._cache and self._cache[0] == iter_node:
118 text = self._cache[2]
118 text = self._cache[2]
119 break
119 break
120 chain.append(rev)
120 chain.append(rev)
121 iter_node = self.bundlebase(rev)
121 iter_node = self.bundlebase(rev)
122 rev = self.rev(iter_node)
122 rev = self.rev(iter_node)
123 if text is None:
123 if text is None:
124 text = revlog.revlog.revision(self, iter_node)
124 text = revlog.revlog.revision(self, iter_node)
125
125
126 while chain:
126 while chain:
127 delta = self._chunk(chain.pop())
127 delta = self._chunk(chain.pop())
128 text = mdiff.patches(text, [delta])
128 text = mdiff.patches(text, [delta])
129
129
130 p1, p2 = self.parents(node)
130 p1, p2 = self.parents(node)
131 if node != revlog.hash(text, p1, p2):
131 if node != revlog.hash(text, p1, p2):
132 raise error.RevlogError(_("integrity check failed on %s:%d")
132 raise error.RevlogError(_("integrity check failed on %s:%d")
133 % (self.datafile, self.rev(node)))
133 % (self.datafile, self.rev(node)))
134
134
135 self._cache = (node, self.rev(node), text)
135 self._cache = (node, self.rev(node), text)
136 return text
136 return text
137
137
138 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
138 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
139 raise NotImplementedError
139 raise NotImplementedError
140 def addgroup(self, revs, linkmapper, transaction):
140 def addgroup(self, revs, linkmapper, transaction):
141 raise NotImplementedError
141 raise NotImplementedError
142 def strip(self, rev, minlink):
142 def strip(self, rev, minlink):
143 raise NotImplementedError
143 raise NotImplementedError
144 def checksize(self):
144 def checksize(self):
145 raise NotImplementedError
145 raise NotImplementedError
146
146
147 class bundlechangelog(bundlerevlog, changelog.changelog):
147 class bundlechangelog(bundlerevlog, changelog.changelog):
148 def __init__(self, opener, bundle):
148 def __init__(self, opener, bundle):
149 changelog.changelog.__init__(self, opener)
149 changelog.changelog.__init__(self, opener)
150 linkmapper = lambda x: x
150 linkmapper = lambda x: x
151 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
151 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
152 linkmapper)
152 linkmapper)
153
153
154 class bundlemanifest(bundlerevlog, manifest.manifest):
154 class bundlemanifest(bundlerevlog, manifest.manifest):
155 def __init__(self, opener, bundle, linkmapper):
155 def __init__(self, opener, bundle, linkmapper):
156 manifest.manifest.__init__(self, opener)
156 manifest.manifest.__init__(self, opener)
157 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
157 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
158 linkmapper)
158 linkmapper)
159
159
160 class bundlefilelog(bundlerevlog, filelog.filelog):
160 class bundlefilelog(bundlerevlog, filelog.filelog):
161 def __init__(self, opener, path, bundle, linkmapper, repo):
161 def __init__(self, opener, path, bundle, linkmapper, repo):
162 filelog.filelog.__init__(self, opener, path)
162 filelog.filelog.__init__(self, opener, path)
163 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
163 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
164 linkmapper)
164 linkmapper)
165 self._repo = repo
165 self._repo = repo
166
166
167 def _file(self, f):
167 def _file(self, f):
168 self._repo.file(f)
168 self._repo.file(f)
169
169
170 class bundlerepository(localrepo.localrepository):
170 class bundlerepository(localrepo.localrepository):
171 def __init__(self, ui, path, bundlename):
171 def __init__(self, ui, path, bundlename):
172 self._tempparent = None
172 self._tempparent = None
173 try:
173 try:
174 localrepo.localrepository.__init__(self, ui, path)
174 localrepo.localrepository.__init__(self, ui, path)
175 except error.RepoError:
175 except error.RepoError:
176 self._tempparent = tempfile.mkdtemp()
176 self._tempparent = tempfile.mkdtemp()
177 localrepo.instance(ui, self._tempparent, 1)
177 localrepo.instance(ui, self._tempparent, 1)
178 localrepo.localrepository.__init__(self, ui, self._tempparent)
178 localrepo.localrepository.__init__(self, ui, self._tempparent)
179 self.ui.setconfig('phases', 'publish', False)
179 self.ui.setconfig('phases', 'publish', False)
180
180
181 if path:
181 if path:
182 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
182 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
183 else:
183 else:
184 self._url = 'bundle:' + bundlename
184 self._url = 'bundle:' + bundlename
185
185
186 self.tempfile = None
186 self.tempfile = None
187 f = util.posixfile(bundlename, "rb")
187 f = util.posixfile(bundlename, "rb")
188 self.bundle = changegroup.readbundle(f, bundlename)
188 self.bundle = changegroup.readbundle(f, bundlename)
189 if self.bundle.compressed():
189 if self.bundle.compressed():
190 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
190 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
191 suffix=".hg10un", dir=self.path)
191 suffix=".hg10un", dir=self.path)
192 self.tempfile = temp
192 self.tempfile = temp
193 fptemp = os.fdopen(fdtemp, 'wb')
193 fptemp = os.fdopen(fdtemp, 'wb')
194
194
195 try:
195 try:
196 fptemp.write("HG10UN")
196 fptemp.write("HG10UN")
197 while True:
197 while True:
198 chunk = self.bundle.read(2**18)
198 chunk = self.bundle.read(2**18)
199 if not chunk:
199 if not chunk:
200 break
200 break
201 fptemp.write(chunk)
201 fptemp.write(chunk)
202 finally:
202 finally:
203 fptemp.close()
203 fptemp.close()
204
204
205 f = util.posixfile(self.tempfile, "rb")
205 f = util.posixfile(self.tempfile, "rb")
206 self.bundle = changegroup.readbundle(f, bundlename)
206 self.bundle = changegroup.readbundle(f, bundlename)
207
207
208 # dict with the mapping 'filename' -> position in the bundle
208 # dict with the mapping 'filename' -> position in the bundle
209 self.bundlefilespos = {}
209 self.bundlefilespos = {}
210
210
211 @util.propertycache
211 @util.propertycache
212 def changelog(self):
212 def changelog(self):
213 # consume the header if it exists
213 # consume the header if it exists
214 self.bundle.changelogheader()
214 self.bundle.changelogheader()
215 c = bundlechangelog(self.sopener, self.bundle)
215 c = bundlechangelog(self.sopener, self.bundle)
216 self.manstart = self.bundle.tell()
216 self.manstart = self.bundle.tell()
217 return c
217 return c
218
218
219 @util.propertycache
219 @util.propertycache
220 def manifest(self):
220 def manifest(self):
221 self.bundle.seek(self.manstart)
221 self.bundle.seek(self.manstart)
222 # consume the header if it exists
222 # consume the header if it exists
223 self.bundle.manifestheader()
223 self.bundle.manifestheader()
224 m = bundlemanifest(self.sopener, self.bundle, self.changelog.rev)
224 m = bundlemanifest(self.sopener, self.bundle, self.changelog.rev)
225 self.filestart = self.bundle.tell()
225 self.filestart = self.bundle.tell()
226 return m
226 return m
227
227
228 @util.propertycache
228 @util.propertycache
229 def manstart(self):
229 def manstart(self):
230 self.changelog
230 self.changelog
231 return self.manstart
231 return self.manstart
232
232
233 @util.propertycache
233 @util.propertycache
234 def filestart(self):
234 def filestart(self):
235 self.manifest
235 self.manifest
236 return self.filestart
236 return self.filestart
237
237
238 def url(self):
238 def url(self):
239 return self._url
239 return self._url
240
240
241 def file(self, f):
241 def file(self, f):
242 if not self.bundlefilespos:
242 if not self.bundlefilespos:
243 self.bundle.seek(self.filestart)
243 self.bundle.seek(self.filestart)
244 while True:
244 while True:
245 chunkdata = self.bundle.filelogheader()
245 chunkdata = self.bundle.filelogheader()
246 if not chunkdata:
246 if not chunkdata:
247 break
247 break
248 fname = chunkdata['filename']
248 fname = chunkdata['filename']
249 self.bundlefilespos[fname] = self.bundle.tell()
249 self.bundlefilespos[fname] = self.bundle.tell()
250 while True:
250 while True:
251 c = self.bundle.deltachunk(None)
251 c = self.bundle.deltachunk(None)
252 if not c:
252 if not c:
253 break
253 break
254
254
255 if f[0] == '/':
255 if f[0] == '/':
256 f = f[1:]
256 f = f[1:]
257 if f in self.bundlefilespos:
257 if f in self.bundlefilespos:
258 self.bundle.seek(self.bundlefilespos[f])
258 self.bundle.seek(self.bundlefilespos[f])
259 return bundlefilelog(self.sopener, f, self.bundle,
259 return bundlefilelog(self.sopener, f, self.bundle,
260 self.changelog.rev, self)
260 self.changelog.rev, self)
261 else:
261 else:
262 return filelog.filelog(self.sopener, f)
262 return filelog.filelog(self.sopener, f)
263
263
264 def close(self):
264 def close(self):
265 """Close assigned bundle file immediately."""
265 """Close assigned bundle file immediately."""
266 self.bundle.close()
266 self.bundle.close()
267 if self.tempfile is not None:
267 if self.tempfile is not None:
268 os.unlink(self.tempfile)
268 os.unlink(self.tempfile)
269 if self._tempparent:
269 if self._tempparent:
270 shutil.rmtree(self._tempparent, True)
270 shutil.rmtree(self._tempparent, True)
271
271
272 def cancopy(self):
272 def cancopy(self):
273 return False
273 return False
274
274
275 def getcwd(self):
275 def getcwd(self):
276 return os.getcwd() # always outside the repo
276 return os.getcwd() # always outside the repo
277
277
278 def _writebranchcache(self, branches, tip, tiprev):
278 def _writebranchcache(self, branches, tip, tiprev):
279 # don't overwrite the disk cache with bundle-augmented data
279 # don't overwrite the disk cache with bundle-augmented data
280 pass
280 pass
281
281
282 def instance(ui, path, create):
282 def instance(ui, path, create):
283 if create:
283 if create:
284 raise util.Abort(_('cannot create new bundle repository'))
284 raise util.Abort(_('cannot create new bundle repository'))
285 parentpath = ui.config("bundle", "mainreporoot", "")
285 parentpath = ui.config("bundle", "mainreporoot", "")
286 if not parentpath:
286 if not parentpath:
287 # try to find the correct path to the working directory repo
287 # try to find the correct path to the working directory repo
288 parentpath = cmdutil.findrepo(os.getcwd())
288 parentpath = cmdutil.findrepo(os.getcwd())
289 if parentpath is None:
289 if parentpath is None:
290 parentpath = ''
290 parentpath = ''
291 if parentpath:
291 if parentpath:
292 # Try to make the full path relative so we get a nice, short URL.
292 # Try to make the full path relative so we get a nice, short URL.
293 # In particular, we don't want temp dir names in test outputs.
293 # In particular, we don't want temp dir names in test outputs.
294 cwd = os.getcwd()
294 cwd = os.getcwd()
295 if parentpath == cwd:
295 if parentpath == cwd:
296 parentpath = ''
296 parentpath = ''
297 else:
297 else:
298 cwd = os.path.join(cwd,'')
298 cwd = os.path.join(cwd,'')
299 if parentpath.startswith(cwd):
299 if parentpath.startswith(cwd):
300 parentpath = parentpath[len(cwd):]
300 parentpath = parentpath[len(cwd):]
301 u = util.url(path)
301 u = util.url(path)
302 path = u.localpath()
302 path = u.localpath()
303 if u.scheme == 'bundle':
303 if u.scheme == 'bundle':
304 s = path.split("+", 1)
304 s = path.split("+", 1)
305 if len(s) == 1:
305 if len(s) == 1:
306 repopath, bundlename = parentpath, s[0]
306 repopath, bundlename = parentpath, s[0]
307 else:
307 else:
308 repopath, bundlename = s
308 repopath, bundlename = s
309 else:
309 else:
310 repopath, bundlename = parentpath, path
310 repopath, bundlename = parentpath, path
311 return bundlerepository(ui, repopath, bundlename)
311 return bundlerepository(ui, repopath, bundlename)
312
312
313 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
313 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
314 force=False):
314 force=False):
315 '''obtains a bundle of changes incoming from other
315 '''obtains a bundle of changes incoming from other
316
316
317 "onlyheads" restricts the returned changes to those reachable from the
317 "onlyheads" restricts the returned changes to those reachable from the
318 specified heads.
318 specified heads.
319 "bundlename", if given, stores the bundle to this file path permanently;
319 "bundlename", if given, stores the bundle to this file path permanently;
320 otherwise it's stored to a temp file and gets deleted again when you call
320 otherwise it's stored to a temp file and gets deleted again when you call
321 the returned "cleanupfn".
321 the returned "cleanupfn".
322 "force" indicates whether to proceed on unrelated repos.
322 "force" indicates whether to proceed on unrelated repos.
323
323
324 Returns a tuple (local, csets, cleanupfn):
324 Returns a tuple (local, csets, cleanupfn):
325
325
326 "local" is a local repo from which to obtain the actual incoming
326 "local" is a local repo from which to obtain the actual incoming
327 changesets; it is a bundlerepo for the obtained bundle when the
327 changesets; it is a bundlerepo for the obtained bundle when the
328 original "other" is remote.
328 original "other" is remote.
329 "csets" lists the incoming changeset node ids.
329 "csets" lists the incoming changeset node ids.
330 "cleanupfn" must be called without arguments when you're done processing
330 "cleanupfn" must be called without arguments when you're done processing
331 the changes; it closes both the original "other" and the one returned
331 the changes; it closes both the original "other" and the one returned
332 here.
332 here.
333 '''
333 '''
334 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
334 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
335 force=force)
335 force=force)
336 common, incoming, rheads = tmp
336 common, incoming, rheads = tmp
337 if not incoming:
337 if not incoming:
338 try:
338 try:
339 if bundlename:
339 if bundlename:
340 os.unlink(bundlename)
340 os.unlink(bundlename)
341 except OSError:
341 except OSError:
342 pass
342 pass
343 return other, [], other.close
343 return other, [], other.close
344
344
345 bundle = None
345 bundle = None
346 bundlerepo = None
346 bundlerepo = None
347 localrepo = other
347 localrepo = other.local()
348 if bundlename or not other.local():
348 if bundlename or not localrepo:
349 # create a bundle (uncompressed if other repo is not local)
349 # create a bundle (uncompressed if other repo is not local)
350
350
351 if other.capable('getbundle'):
351 if other.capable('getbundle'):
352 cg = other.getbundle('incoming', common=common, heads=rheads)
352 cg = other.getbundle('incoming', common=common, heads=rheads)
353 elif onlyheads is None and not other.capable('changegroupsubset'):
353 elif onlyheads is None and not other.capable('changegroupsubset'):
354 # compat with older servers when pulling all remote heads
354 # compat with older servers when pulling all remote heads
355 cg = other.changegroup(incoming, "incoming")
355 cg = other.changegroup(incoming, "incoming")
356 rheads = None
356 rheads = None
357 else:
357 else:
358 cg = other.changegroupsubset(incoming, rheads, 'incoming')
358 cg = other.changegroupsubset(incoming, rheads, 'incoming')
359 bundletype = other.local() and "HG10BZ" or "HG10UN"
359 bundletype = localrepo and "HG10BZ" or "HG10UN"
360 fname = bundle = changegroup.writebundle(cg, bundlename, bundletype)
360 fname = bundle = changegroup.writebundle(cg, bundlename, bundletype)
361 # keep written bundle?
361 # keep written bundle?
362 if bundlename:
362 if bundlename:
363 bundle = None
363 bundle = None
364 if not other.local():
364 if not localrepo:
365 # use the created uncompressed bundlerepo
365 # use the created uncompressed bundlerepo
366 localrepo = bundlerepo = bundlerepository(ui, repo.root, fname)
366 localrepo = bundlerepo = bundlerepository(ui, repo.root, fname)
367 # this repo contains local and other now, so filter out local again
367 # this repo contains local and other now, so filter out local again
368 common = repo.heads()
368 common = repo.heads()
369
369
370 csets = localrepo.changelog.findmissing(common, rheads)
370 csets = localrepo.changelog.findmissing(common, rheads)
371
371
372 def cleanup():
372 def cleanup():
373 if bundlerepo:
373 if bundlerepo:
374 bundlerepo.close()
374 bundlerepo.close()
375 if bundle:
375 if bundle:
376 os.unlink(bundle)
376 os.unlink(bundle)
377 other.close()
377 other.close()
378
378
379 return (localrepo, csets, cleanup)
379 return (localrepo, csets, cleanup)
380
380
@@ -1,5858 +1,5859 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 _, gettext
10 from i18n import _, gettext
11 import os, re, difflib, time, tempfile, errno
11 import os, re, difflib, time, tempfile, errno
12 import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
12 import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
13 import patch, help, url, encoding, templatekw, discovery
13 import patch, help, url, encoding, templatekw, discovery
14 import archival, changegroup, cmdutil, hbisect
14 import archival, changegroup, cmdutil, hbisect
15 import sshserver, hgweb, hgweb.server, commandserver
15 import sshserver, hgweb, hgweb.server, commandserver
16 import merge as mergemod
16 import merge as mergemod
17 import minirst, revset, fileset
17 import minirst, revset, fileset
18 import dagparser, context, simplemerge, graphmod
18 import dagparser, context, simplemerge, graphmod
19 import random, setdiscovery, treediscovery, dagutil, pvec
19 import random, setdiscovery, treediscovery, dagutil, pvec
20 import phases, obsolete
20 import phases, obsolete
21
21
22 table = {}
22 table = {}
23
23
24 command = cmdutil.command(table)
24 command = cmdutil.command(table)
25
25
26 # common command options
26 # common command options
27
27
28 globalopts = [
28 globalopts = [
29 ('R', 'repository', '',
29 ('R', 'repository', '',
30 _('repository root directory or name of overlay bundle file'),
30 _('repository root directory or name of overlay bundle file'),
31 _('REPO')),
31 _('REPO')),
32 ('', 'cwd', '',
32 ('', 'cwd', '',
33 _('change working directory'), _('DIR')),
33 _('change working directory'), _('DIR')),
34 ('y', 'noninteractive', None,
34 ('y', 'noninteractive', None,
35 _('do not prompt, automatically pick the first choice for all prompts')),
35 _('do not prompt, automatically pick the first choice for all prompts')),
36 ('q', 'quiet', None, _('suppress output')),
36 ('q', 'quiet', None, _('suppress output')),
37 ('v', 'verbose', None, _('enable additional output')),
37 ('v', 'verbose', None, _('enable additional output')),
38 ('', 'config', [],
38 ('', 'config', [],
39 _('set/override config option (use \'section.name=value\')'),
39 _('set/override config option (use \'section.name=value\')'),
40 _('CONFIG')),
40 _('CONFIG')),
41 ('', 'debug', None, _('enable debugging output')),
41 ('', 'debug', None, _('enable debugging output')),
42 ('', 'debugger', None, _('start debugger')),
42 ('', 'debugger', None, _('start debugger')),
43 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
43 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
44 _('ENCODE')),
44 _('ENCODE')),
45 ('', 'encodingmode', encoding.encodingmode,
45 ('', 'encodingmode', encoding.encodingmode,
46 _('set the charset encoding mode'), _('MODE')),
46 _('set the charset encoding mode'), _('MODE')),
47 ('', 'traceback', None, _('always print a traceback on exception')),
47 ('', 'traceback', None, _('always print a traceback on exception')),
48 ('', 'time', None, _('time how long the command takes')),
48 ('', 'time', None, _('time how long the command takes')),
49 ('', 'profile', None, _('print command execution profile')),
49 ('', 'profile', None, _('print command execution profile')),
50 ('', 'version', None, _('output version information and exit')),
50 ('', 'version', None, _('output version information and exit')),
51 ('h', 'help', None, _('display help and exit')),
51 ('h', 'help', None, _('display help and exit')),
52 ]
52 ]
53
53
54 dryrunopts = [('n', 'dry-run', None,
54 dryrunopts = [('n', 'dry-run', None,
55 _('do not perform actions, just print output'))]
55 _('do not perform actions, just print output'))]
56
56
57 remoteopts = [
57 remoteopts = [
58 ('e', 'ssh', '',
58 ('e', 'ssh', '',
59 _('specify ssh command to use'), _('CMD')),
59 _('specify ssh command to use'), _('CMD')),
60 ('', 'remotecmd', '',
60 ('', 'remotecmd', '',
61 _('specify hg command to run on the remote side'), _('CMD')),
61 _('specify hg command to run on the remote side'), _('CMD')),
62 ('', 'insecure', None,
62 ('', 'insecure', None,
63 _('do not verify server certificate (ignoring web.cacerts config)')),
63 _('do not verify server certificate (ignoring web.cacerts config)')),
64 ]
64 ]
65
65
66 walkopts = [
66 walkopts = [
67 ('I', 'include', [],
67 ('I', 'include', [],
68 _('include names matching the given patterns'), _('PATTERN')),
68 _('include names matching the given patterns'), _('PATTERN')),
69 ('X', 'exclude', [],
69 ('X', 'exclude', [],
70 _('exclude names matching the given patterns'), _('PATTERN')),
70 _('exclude names matching the given patterns'), _('PATTERN')),
71 ]
71 ]
72
72
73 commitopts = [
73 commitopts = [
74 ('m', 'message', '',
74 ('m', 'message', '',
75 _('use text as commit message'), _('TEXT')),
75 _('use text as commit message'), _('TEXT')),
76 ('l', 'logfile', '',
76 ('l', 'logfile', '',
77 _('read commit message from file'), _('FILE')),
77 _('read commit message from file'), _('FILE')),
78 ]
78 ]
79
79
80 commitopts2 = [
80 commitopts2 = [
81 ('d', 'date', '',
81 ('d', 'date', '',
82 _('record the specified date as commit date'), _('DATE')),
82 _('record the specified date as commit date'), _('DATE')),
83 ('u', 'user', '',
83 ('u', 'user', '',
84 _('record the specified user as committer'), _('USER')),
84 _('record the specified user as committer'), _('USER')),
85 ]
85 ]
86
86
87 templateopts = [
87 templateopts = [
88 ('', 'style', '',
88 ('', 'style', '',
89 _('display using template map file'), _('STYLE')),
89 _('display using template map file'), _('STYLE')),
90 ('', 'template', '',
90 ('', 'template', '',
91 _('display with template'), _('TEMPLATE')),
91 _('display with template'), _('TEMPLATE')),
92 ]
92 ]
93
93
94 logopts = [
94 logopts = [
95 ('p', 'patch', None, _('show patch')),
95 ('p', 'patch', None, _('show patch')),
96 ('g', 'git', None, _('use git extended diff format')),
96 ('g', 'git', None, _('use git extended diff format')),
97 ('l', 'limit', '',
97 ('l', 'limit', '',
98 _('limit number of changes displayed'), _('NUM')),
98 _('limit number of changes displayed'), _('NUM')),
99 ('M', 'no-merges', None, _('do not show merges')),
99 ('M', 'no-merges', None, _('do not show merges')),
100 ('', 'stat', None, _('output diffstat-style summary of changes')),
100 ('', 'stat', None, _('output diffstat-style summary of changes')),
101 ('G', 'graph', None, _("show the revision DAG")),
101 ('G', 'graph', None, _("show the revision DAG")),
102 ] + templateopts
102 ] + templateopts
103
103
104 diffopts = [
104 diffopts = [
105 ('a', 'text', None, _('treat all files as text')),
105 ('a', 'text', None, _('treat all files as text')),
106 ('g', 'git', None, _('use git extended diff format')),
106 ('g', 'git', None, _('use git extended diff format')),
107 ('', 'nodates', None, _('omit dates from diff headers'))
107 ('', 'nodates', None, _('omit dates from diff headers'))
108 ]
108 ]
109
109
110 diffwsopts = [
110 diffwsopts = [
111 ('w', 'ignore-all-space', None,
111 ('w', 'ignore-all-space', None,
112 _('ignore white space when comparing lines')),
112 _('ignore white space when comparing lines')),
113 ('b', 'ignore-space-change', None,
113 ('b', 'ignore-space-change', None,
114 _('ignore changes in the amount of white space')),
114 _('ignore changes in the amount of white space')),
115 ('B', 'ignore-blank-lines', None,
115 ('B', 'ignore-blank-lines', None,
116 _('ignore changes whose lines are all blank')),
116 _('ignore changes whose lines are all blank')),
117 ]
117 ]
118
118
119 diffopts2 = [
119 diffopts2 = [
120 ('p', 'show-function', None, _('show which function each change is in')),
120 ('p', 'show-function', None, _('show which function each change is in')),
121 ('', 'reverse', None, _('produce a diff that undoes the changes')),
121 ('', 'reverse', None, _('produce a diff that undoes the changes')),
122 ] + diffwsopts + [
122 ] + diffwsopts + [
123 ('U', 'unified', '',
123 ('U', 'unified', '',
124 _('number of lines of context to show'), _('NUM')),
124 _('number of lines of context to show'), _('NUM')),
125 ('', 'stat', None, _('output diffstat-style summary of changes')),
125 ('', 'stat', None, _('output diffstat-style summary of changes')),
126 ]
126 ]
127
127
128 mergetoolopts = [
128 mergetoolopts = [
129 ('t', 'tool', '', _('specify merge tool')),
129 ('t', 'tool', '', _('specify merge tool')),
130 ]
130 ]
131
131
132 similarityopts = [
132 similarityopts = [
133 ('s', 'similarity', '',
133 ('s', 'similarity', '',
134 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
134 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
135 ]
135 ]
136
136
137 subrepoopts = [
137 subrepoopts = [
138 ('S', 'subrepos', None,
138 ('S', 'subrepos', None,
139 _('recurse into subrepositories'))
139 _('recurse into subrepositories'))
140 ]
140 ]
141
141
142 # Commands start here, listed alphabetically
142 # Commands start here, listed alphabetically
143
143
144 @command('^add',
144 @command('^add',
145 walkopts + subrepoopts + dryrunopts,
145 walkopts + subrepoopts + dryrunopts,
146 _('[OPTION]... [FILE]...'))
146 _('[OPTION]... [FILE]...'))
147 def add(ui, repo, *pats, **opts):
147 def add(ui, repo, *pats, **opts):
148 """add the specified files on the next commit
148 """add the specified files on the next commit
149
149
150 Schedule files to be version controlled and added to the
150 Schedule files to be version controlled and added to the
151 repository.
151 repository.
152
152
153 The files will be added to the repository at the next commit. To
153 The files will be added to the repository at the next commit. To
154 undo an add before that, see :hg:`forget`.
154 undo an add before that, see :hg:`forget`.
155
155
156 If no names are given, add all files to the repository.
156 If no names are given, add all files to the repository.
157
157
158 .. container:: verbose
158 .. container:: verbose
159
159
160 An example showing how new (unknown) files are added
160 An example showing how new (unknown) files are added
161 automatically by :hg:`add`::
161 automatically by :hg:`add`::
162
162
163 $ ls
163 $ ls
164 foo.c
164 foo.c
165 $ hg status
165 $ hg status
166 ? foo.c
166 ? foo.c
167 $ hg add
167 $ hg add
168 adding foo.c
168 adding foo.c
169 $ hg status
169 $ hg status
170 A foo.c
170 A foo.c
171
171
172 Returns 0 if all files are successfully added.
172 Returns 0 if all files are successfully added.
173 """
173 """
174
174
175 m = scmutil.match(repo[None], pats, opts)
175 m = scmutil.match(repo[None], pats, opts)
176 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
176 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
177 opts.get('subrepos'), prefix="", explicitonly=False)
177 opts.get('subrepos'), prefix="", explicitonly=False)
178 return rejected and 1 or 0
178 return rejected and 1 or 0
179
179
180 @command('addremove',
180 @command('addremove',
181 similarityopts + walkopts + dryrunopts,
181 similarityopts + walkopts + dryrunopts,
182 _('[OPTION]... [FILE]...'))
182 _('[OPTION]... [FILE]...'))
183 def addremove(ui, repo, *pats, **opts):
183 def addremove(ui, repo, *pats, **opts):
184 """add all new files, delete all missing files
184 """add all new files, delete all missing files
185
185
186 Add all new files and remove all missing files from the
186 Add all new files and remove all missing files from the
187 repository.
187 repository.
188
188
189 New files are ignored if they match any of the patterns in
189 New files are ignored if they match any of the patterns in
190 ``.hgignore``. As with add, these changes take effect at the next
190 ``.hgignore``. As with add, these changes take effect at the next
191 commit.
191 commit.
192
192
193 Use the -s/--similarity option to detect renamed files. With a
193 Use the -s/--similarity option to detect renamed files. With a
194 parameter greater than 0, this compares every removed file with
194 parameter greater than 0, this compares every removed file with
195 every added file and records those similar enough as renames. This
195 every added file and records those similar enough as renames. This
196 option takes a percentage between 0 (disabled) and 100 (files must
196 option takes a percentage between 0 (disabled) and 100 (files must
197 be identical) as its parameter. Detecting renamed files this way
197 be identical) as its parameter. Detecting renamed files this way
198 can be expensive. After using this option, :hg:`status -C` can be
198 can be expensive. After using this option, :hg:`status -C` can be
199 used to check which files were identified as moved or renamed.
199 used to check which files were identified as moved or renamed.
200 If this option is not specified, only renames of identical files
200 If this option is not specified, only renames of identical files
201 are detected.
201 are detected.
202
202
203 Returns 0 if all files are successfully added.
203 Returns 0 if all files are successfully added.
204 """
204 """
205 try:
205 try:
206 sim = float(opts.get('similarity') or 100)
206 sim = float(opts.get('similarity') or 100)
207 except ValueError:
207 except ValueError:
208 raise util.Abort(_('similarity must be a number'))
208 raise util.Abort(_('similarity must be a number'))
209 if sim < 0 or sim > 100:
209 if sim < 0 or sim > 100:
210 raise util.Abort(_('similarity must be between 0 and 100'))
210 raise util.Abort(_('similarity must be between 0 and 100'))
211 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
211 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
212
212
213 @command('^annotate|blame',
213 @command('^annotate|blame',
214 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
214 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
215 ('', 'follow', None,
215 ('', 'follow', None,
216 _('follow copies/renames and list the filename (DEPRECATED)')),
216 _('follow copies/renames and list the filename (DEPRECATED)')),
217 ('', 'no-follow', None, _("don't follow copies and renames")),
217 ('', 'no-follow', None, _("don't follow copies and renames")),
218 ('a', 'text', None, _('treat all files as text')),
218 ('a', 'text', None, _('treat all files as text')),
219 ('u', 'user', None, _('list the author (long with -v)')),
219 ('u', 'user', None, _('list the author (long with -v)')),
220 ('f', 'file', None, _('list the filename')),
220 ('f', 'file', None, _('list the filename')),
221 ('d', 'date', None, _('list the date (short with -q)')),
221 ('d', 'date', None, _('list the date (short with -q)')),
222 ('n', 'number', None, _('list the revision number (default)')),
222 ('n', 'number', None, _('list the revision number (default)')),
223 ('c', 'changeset', None, _('list the changeset')),
223 ('c', 'changeset', None, _('list the changeset')),
224 ('l', 'line-number', None, _('show line number at the first appearance'))
224 ('l', 'line-number', None, _('show line number at the first appearance'))
225 ] + diffwsopts + walkopts,
225 ] + diffwsopts + walkopts,
226 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
226 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
227 def annotate(ui, repo, *pats, **opts):
227 def annotate(ui, repo, *pats, **opts):
228 """show changeset information by line for each file
228 """show changeset information by line for each file
229
229
230 List changes in files, showing the revision id responsible for
230 List changes in files, showing the revision id responsible for
231 each line
231 each line
232
232
233 This command is useful for discovering when a change was made and
233 This command is useful for discovering when a change was made and
234 by whom.
234 by whom.
235
235
236 Without the -a/--text option, annotate will avoid processing files
236 Without the -a/--text option, annotate will avoid processing files
237 it detects as binary. With -a, annotate will annotate the file
237 it detects as binary. With -a, annotate will annotate the file
238 anyway, although the results will probably be neither useful
238 anyway, although the results will probably be neither useful
239 nor desirable.
239 nor desirable.
240
240
241 Returns 0 on success.
241 Returns 0 on success.
242 """
242 """
243 if opts.get('follow'):
243 if opts.get('follow'):
244 # --follow is deprecated and now just an alias for -f/--file
244 # --follow is deprecated and now just an alias for -f/--file
245 # to mimic the behavior of Mercurial before version 1.5
245 # to mimic the behavior of Mercurial before version 1.5
246 opts['file'] = True
246 opts['file'] = True
247
247
248 datefunc = ui.quiet and util.shortdate or util.datestr
248 datefunc = ui.quiet and util.shortdate or util.datestr
249 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
249 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
250
250
251 if not pats:
251 if not pats:
252 raise util.Abort(_('at least one filename or pattern is required'))
252 raise util.Abort(_('at least one filename or pattern is required'))
253
253
254 hexfn = ui.debugflag and hex or short
254 hexfn = ui.debugflag and hex or short
255
255
256 opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
256 opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
257 ('number', ' ', lambda x: str(x[0].rev())),
257 ('number', ' ', lambda x: str(x[0].rev())),
258 ('changeset', ' ', lambda x: hexfn(x[0].node())),
258 ('changeset', ' ', lambda x: hexfn(x[0].node())),
259 ('date', ' ', getdate),
259 ('date', ' ', getdate),
260 ('file', ' ', lambda x: x[0].path()),
260 ('file', ' ', lambda x: x[0].path()),
261 ('line_number', ':', lambda x: str(x[1])),
261 ('line_number', ':', lambda x: str(x[1])),
262 ]
262 ]
263
263
264 if (not opts.get('user') and not opts.get('changeset')
264 if (not opts.get('user') and not opts.get('changeset')
265 and not opts.get('date') and not opts.get('file')):
265 and not opts.get('date') and not opts.get('file')):
266 opts['number'] = True
266 opts['number'] = True
267
267
268 linenumber = opts.get('line_number') is not None
268 linenumber = opts.get('line_number') is not None
269 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
269 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
270 raise util.Abort(_('at least one of -n/-c is required for -l'))
270 raise util.Abort(_('at least one of -n/-c is required for -l'))
271
271
272 funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
272 funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
273 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
273 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
274
274
275 def bad(x, y):
275 def bad(x, y):
276 raise util.Abort("%s: %s" % (x, y))
276 raise util.Abort("%s: %s" % (x, y))
277
277
278 ctx = scmutil.revsingle(repo, opts.get('rev'))
278 ctx = scmutil.revsingle(repo, opts.get('rev'))
279 m = scmutil.match(ctx, pats, opts)
279 m = scmutil.match(ctx, pats, opts)
280 m.bad = bad
280 m.bad = bad
281 follow = not opts.get('no_follow')
281 follow = not opts.get('no_follow')
282 diffopts = patch.diffopts(ui, opts, section='annotate')
282 diffopts = patch.diffopts(ui, opts, section='annotate')
283 for abs in ctx.walk(m):
283 for abs in ctx.walk(m):
284 fctx = ctx[abs]
284 fctx = ctx[abs]
285 if not opts.get('text') and util.binary(fctx.data()):
285 if not opts.get('text') and util.binary(fctx.data()):
286 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
286 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
287 continue
287 continue
288
288
289 lines = fctx.annotate(follow=follow, linenumber=linenumber,
289 lines = fctx.annotate(follow=follow, linenumber=linenumber,
290 diffopts=diffopts)
290 diffopts=diffopts)
291 pieces = []
291 pieces = []
292
292
293 for f, sep in funcmap:
293 for f, sep in funcmap:
294 l = [f(n) for n, dummy in lines]
294 l = [f(n) for n, dummy in lines]
295 if l:
295 if l:
296 sized = [(x, encoding.colwidth(x)) for x in l]
296 sized = [(x, encoding.colwidth(x)) for x in l]
297 ml = max([w for x, w in sized])
297 ml = max([w for x, w in sized])
298 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
298 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
299 for x, w in sized])
299 for x, w in sized])
300
300
301 if pieces:
301 if pieces:
302 for p, l in zip(zip(*pieces), lines):
302 for p, l in zip(zip(*pieces), lines):
303 ui.write("%s: %s" % ("".join(p), l[1]))
303 ui.write("%s: %s" % ("".join(p), l[1]))
304
304
305 if lines and not lines[-1][1].endswith('\n'):
305 if lines and not lines[-1][1].endswith('\n'):
306 ui.write('\n')
306 ui.write('\n')
307
307
308 @command('archive',
308 @command('archive',
309 [('', 'no-decode', None, _('do not pass files through decoders')),
309 [('', 'no-decode', None, _('do not pass files through decoders')),
310 ('p', 'prefix', '', _('directory prefix for files in archive'),
310 ('p', 'prefix', '', _('directory prefix for files in archive'),
311 _('PREFIX')),
311 _('PREFIX')),
312 ('r', 'rev', '', _('revision to distribute'), _('REV')),
312 ('r', 'rev', '', _('revision to distribute'), _('REV')),
313 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
313 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
314 ] + subrepoopts + walkopts,
314 ] + subrepoopts + walkopts,
315 _('[OPTION]... DEST'))
315 _('[OPTION]... DEST'))
316 def archive(ui, repo, dest, **opts):
316 def archive(ui, repo, dest, **opts):
317 '''create an unversioned archive of a repository revision
317 '''create an unversioned archive of a repository revision
318
318
319 By default, the revision used is the parent of the working
319 By default, the revision used is the parent of the working
320 directory; use -r/--rev to specify a different revision.
320 directory; use -r/--rev to specify a different revision.
321
321
322 The archive type is automatically detected based on file
322 The archive type is automatically detected based on file
323 extension (or override using -t/--type).
323 extension (or override using -t/--type).
324
324
325 .. container:: verbose
325 .. container:: verbose
326
326
327 Examples:
327 Examples:
328
328
329 - create a zip file containing the 1.0 release::
329 - create a zip file containing the 1.0 release::
330
330
331 hg archive -r 1.0 project-1.0.zip
331 hg archive -r 1.0 project-1.0.zip
332
332
333 - create a tarball excluding .hg files::
333 - create a tarball excluding .hg files::
334
334
335 hg archive project.tar.gz -X ".hg*"
335 hg archive project.tar.gz -X ".hg*"
336
336
337 Valid types are:
337 Valid types are:
338
338
339 :``files``: a directory full of files (default)
339 :``files``: a directory full of files (default)
340 :``tar``: tar archive, uncompressed
340 :``tar``: tar archive, uncompressed
341 :``tbz2``: tar archive, compressed using bzip2
341 :``tbz2``: tar archive, compressed using bzip2
342 :``tgz``: tar archive, compressed using gzip
342 :``tgz``: tar archive, compressed using gzip
343 :``uzip``: zip archive, uncompressed
343 :``uzip``: zip archive, uncompressed
344 :``zip``: zip archive, compressed using deflate
344 :``zip``: zip archive, compressed using deflate
345
345
346 The exact name of the destination archive or directory is given
346 The exact name of the destination archive or directory is given
347 using a format string; see :hg:`help export` for details.
347 using a format string; see :hg:`help export` for details.
348
348
349 Each member added to an archive file has a directory prefix
349 Each member added to an archive file has a directory prefix
350 prepended. Use -p/--prefix to specify a format string for the
350 prepended. Use -p/--prefix to specify a format string for the
351 prefix. The default is the basename of the archive, with suffixes
351 prefix. The default is the basename of the archive, with suffixes
352 removed.
352 removed.
353
353
354 Returns 0 on success.
354 Returns 0 on success.
355 '''
355 '''
356
356
357 ctx = scmutil.revsingle(repo, opts.get('rev'))
357 ctx = scmutil.revsingle(repo, opts.get('rev'))
358 if not ctx:
358 if not ctx:
359 raise util.Abort(_('no working directory: please specify a revision'))
359 raise util.Abort(_('no working directory: please specify a revision'))
360 node = ctx.node()
360 node = ctx.node()
361 dest = cmdutil.makefilename(repo, dest, node)
361 dest = cmdutil.makefilename(repo, dest, node)
362 if os.path.realpath(dest) == repo.root:
362 if os.path.realpath(dest) == repo.root:
363 raise util.Abort(_('repository root cannot be destination'))
363 raise util.Abort(_('repository root cannot be destination'))
364
364
365 kind = opts.get('type') or archival.guesskind(dest) or 'files'
365 kind = opts.get('type') or archival.guesskind(dest) or 'files'
366 prefix = opts.get('prefix')
366 prefix = opts.get('prefix')
367
367
368 if dest == '-':
368 if dest == '-':
369 if kind == 'files':
369 if kind == 'files':
370 raise util.Abort(_('cannot archive plain files to stdout'))
370 raise util.Abort(_('cannot archive plain files to stdout'))
371 dest = cmdutil.makefileobj(repo, dest)
371 dest = cmdutil.makefileobj(repo, dest)
372 if not prefix:
372 if not prefix:
373 prefix = os.path.basename(repo.root) + '-%h'
373 prefix = os.path.basename(repo.root) + '-%h'
374
374
375 prefix = cmdutil.makefilename(repo, prefix, node)
375 prefix = cmdutil.makefilename(repo, prefix, node)
376 matchfn = scmutil.match(ctx, [], opts)
376 matchfn = scmutil.match(ctx, [], opts)
377 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
377 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
378 matchfn, prefix, subrepos=opts.get('subrepos'))
378 matchfn, prefix, subrepos=opts.get('subrepos'))
379
379
380 @command('backout',
380 @command('backout',
381 [('', 'merge', None, _('merge with old dirstate parent after backout')),
381 [('', 'merge', None, _('merge with old dirstate parent after backout')),
382 ('', 'parent', '',
382 ('', 'parent', '',
383 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
383 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
384 ('r', 'rev', '', _('revision to backout'), _('REV')),
384 ('r', 'rev', '', _('revision to backout'), _('REV')),
385 ] + mergetoolopts + walkopts + commitopts + commitopts2,
385 ] + mergetoolopts + walkopts + commitopts + commitopts2,
386 _('[OPTION]... [-r] REV'))
386 _('[OPTION]... [-r] REV'))
387 def backout(ui, repo, node=None, rev=None, **opts):
387 def backout(ui, repo, node=None, rev=None, **opts):
388 '''reverse effect of earlier changeset
388 '''reverse effect of earlier changeset
389
389
390 Prepare a new changeset with the effect of REV undone in the
390 Prepare a new changeset with the effect of REV undone in the
391 current working directory.
391 current working directory.
392
392
393 If REV is the parent of the working directory, then this new changeset
393 If REV is the parent of the working directory, then this new changeset
394 is committed automatically. Otherwise, hg needs to merge the
394 is committed automatically. Otherwise, hg needs to merge the
395 changes and the merged result is left uncommitted.
395 changes and the merged result is left uncommitted.
396
396
397 .. note::
397 .. note::
398 backout cannot be used to fix either an unwanted or
398 backout cannot be used to fix either an unwanted or
399 incorrect merge.
399 incorrect merge.
400
400
401 .. container:: verbose
401 .. container:: verbose
402
402
403 By default, the pending changeset will have one parent,
403 By default, the pending changeset will have one parent,
404 maintaining a linear history. With --merge, the pending
404 maintaining a linear history. With --merge, the pending
405 changeset will instead have two parents: the old parent of the
405 changeset will instead have two parents: the old parent of the
406 working directory and a new child of REV that simply undoes REV.
406 working directory and a new child of REV that simply undoes REV.
407
407
408 Before version 1.7, the behavior without --merge was equivalent
408 Before version 1.7, the behavior without --merge was equivalent
409 to specifying --merge followed by :hg:`update --clean .` to
409 to specifying --merge followed by :hg:`update --clean .` to
410 cancel the merge and leave the child of REV as a head to be
410 cancel the merge and leave the child of REV as a head to be
411 merged separately.
411 merged separately.
412
412
413 See :hg:`help dates` for a list of formats valid for -d/--date.
413 See :hg:`help dates` for a list of formats valid for -d/--date.
414
414
415 Returns 0 on success.
415 Returns 0 on success.
416 '''
416 '''
417 if rev and node:
417 if rev and node:
418 raise util.Abort(_("please specify just one revision"))
418 raise util.Abort(_("please specify just one revision"))
419
419
420 if not rev:
420 if not rev:
421 rev = node
421 rev = node
422
422
423 if not rev:
423 if not rev:
424 raise util.Abort(_("please specify a revision to backout"))
424 raise util.Abort(_("please specify a revision to backout"))
425
425
426 date = opts.get('date')
426 date = opts.get('date')
427 if date:
427 if date:
428 opts['date'] = util.parsedate(date)
428 opts['date'] = util.parsedate(date)
429
429
430 cmdutil.bailifchanged(repo)
430 cmdutil.bailifchanged(repo)
431 node = scmutil.revsingle(repo, rev).node()
431 node = scmutil.revsingle(repo, rev).node()
432
432
433 op1, op2 = repo.dirstate.parents()
433 op1, op2 = repo.dirstate.parents()
434 a = repo.changelog.ancestor(op1, node)
434 a = repo.changelog.ancestor(op1, node)
435 if a != node:
435 if a != node:
436 raise util.Abort(_('cannot backout change on a different branch'))
436 raise util.Abort(_('cannot backout change on a different branch'))
437
437
438 p1, p2 = repo.changelog.parents(node)
438 p1, p2 = repo.changelog.parents(node)
439 if p1 == nullid:
439 if p1 == nullid:
440 raise util.Abort(_('cannot backout a change with no parents'))
440 raise util.Abort(_('cannot backout a change with no parents'))
441 if p2 != nullid:
441 if p2 != nullid:
442 if not opts.get('parent'):
442 if not opts.get('parent'):
443 raise util.Abort(_('cannot backout a merge changeset'))
443 raise util.Abort(_('cannot backout a merge changeset'))
444 p = repo.lookup(opts['parent'])
444 p = repo.lookup(opts['parent'])
445 if p not in (p1, p2):
445 if p not in (p1, p2):
446 raise util.Abort(_('%s is not a parent of %s') %
446 raise util.Abort(_('%s is not a parent of %s') %
447 (short(p), short(node)))
447 (short(p), short(node)))
448 parent = p
448 parent = p
449 else:
449 else:
450 if opts.get('parent'):
450 if opts.get('parent'):
451 raise util.Abort(_('cannot use --parent on non-merge changeset'))
451 raise util.Abort(_('cannot use --parent on non-merge changeset'))
452 parent = p1
452 parent = p1
453
453
454 # the backout should appear on the same branch
454 # the backout should appear on the same branch
455 wlock = repo.wlock()
455 wlock = repo.wlock()
456 try:
456 try:
457 branch = repo.dirstate.branch()
457 branch = repo.dirstate.branch()
458 hg.clean(repo, node, show_stats=False)
458 hg.clean(repo, node, show_stats=False)
459 repo.dirstate.setbranch(branch)
459 repo.dirstate.setbranch(branch)
460 revert_opts = opts.copy()
460 revert_opts = opts.copy()
461 revert_opts['date'] = None
461 revert_opts['date'] = None
462 revert_opts['all'] = True
462 revert_opts['all'] = True
463 revert_opts['rev'] = hex(parent)
463 revert_opts['rev'] = hex(parent)
464 revert_opts['no_backup'] = None
464 revert_opts['no_backup'] = None
465 revert(ui, repo, **revert_opts)
465 revert(ui, repo, **revert_opts)
466 if not opts.get('merge') and op1 != node:
466 if not opts.get('merge') and op1 != node:
467 try:
467 try:
468 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
468 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
469 return hg.update(repo, op1)
469 return hg.update(repo, op1)
470 finally:
470 finally:
471 ui.setconfig('ui', 'forcemerge', '')
471 ui.setconfig('ui', 'forcemerge', '')
472
472
473 commit_opts = opts.copy()
473 commit_opts = opts.copy()
474 commit_opts['addremove'] = False
474 commit_opts['addremove'] = False
475 if not commit_opts['message'] and not commit_opts['logfile']:
475 if not commit_opts['message'] and not commit_opts['logfile']:
476 # we don't translate commit messages
476 # we don't translate commit messages
477 commit_opts['message'] = "Backed out changeset %s" % short(node)
477 commit_opts['message'] = "Backed out changeset %s" % short(node)
478 commit_opts['force_editor'] = True
478 commit_opts['force_editor'] = True
479 commit(ui, repo, **commit_opts)
479 commit(ui, repo, **commit_opts)
480 def nice(node):
480 def nice(node):
481 return '%d:%s' % (repo.changelog.rev(node), short(node))
481 return '%d:%s' % (repo.changelog.rev(node), short(node))
482 ui.status(_('changeset %s backs out changeset %s\n') %
482 ui.status(_('changeset %s backs out changeset %s\n') %
483 (nice(repo.changelog.tip()), nice(node)))
483 (nice(repo.changelog.tip()), nice(node)))
484 if opts.get('merge') and op1 != node:
484 if opts.get('merge') and op1 != node:
485 hg.clean(repo, op1, show_stats=False)
485 hg.clean(repo, op1, show_stats=False)
486 ui.status(_('merging with changeset %s\n')
486 ui.status(_('merging with changeset %s\n')
487 % nice(repo.changelog.tip()))
487 % nice(repo.changelog.tip()))
488 try:
488 try:
489 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
489 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
490 return hg.merge(repo, hex(repo.changelog.tip()))
490 return hg.merge(repo, hex(repo.changelog.tip()))
491 finally:
491 finally:
492 ui.setconfig('ui', 'forcemerge', '')
492 ui.setconfig('ui', 'forcemerge', '')
493 finally:
493 finally:
494 wlock.release()
494 wlock.release()
495 return 0
495 return 0
496
496
497 @command('bisect',
497 @command('bisect',
498 [('r', 'reset', False, _('reset bisect state')),
498 [('r', 'reset', False, _('reset bisect state')),
499 ('g', 'good', False, _('mark changeset good')),
499 ('g', 'good', False, _('mark changeset good')),
500 ('b', 'bad', False, _('mark changeset bad')),
500 ('b', 'bad', False, _('mark changeset bad')),
501 ('s', 'skip', False, _('skip testing changeset')),
501 ('s', 'skip', False, _('skip testing changeset')),
502 ('e', 'extend', False, _('extend the bisect range')),
502 ('e', 'extend', False, _('extend the bisect range')),
503 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
503 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
504 ('U', 'noupdate', False, _('do not update to target'))],
504 ('U', 'noupdate', False, _('do not update to target'))],
505 _("[-gbsr] [-U] [-c CMD] [REV]"))
505 _("[-gbsr] [-U] [-c CMD] [REV]"))
506 def bisect(ui, repo, rev=None, extra=None, command=None,
506 def bisect(ui, repo, rev=None, extra=None, command=None,
507 reset=None, good=None, bad=None, skip=None, extend=None,
507 reset=None, good=None, bad=None, skip=None, extend=None,
508 noupdate=None):
508 noupdate=None):
509 """subdivision search of changesets
509 """subdivision search of changesets
510
510
511 This command helps to find changesets which introduce problems. To
511 This command helps to find changesets which introduce problems. To
512 use, mark the earliest changeset you know exhibits the problem as
512 use, mark the earliest changeset you know exhibits the problem as
513 bad, then mark the latest changeset which is free from the problem
513 bad, then mark the latest changeset which is free from the problem
514 as good. Bisect will update your working directory to a revision
514 as good. Bisect will update your working directory to a revision
515 for testing (unless the -U/--noupdate option is specified). Once
515 for testing (unless the -U/--noupdate option is specified). Once
516 you have performed tests, mark the working directory as good or
516 you have performed tests, mark the working directory as good or
517 bad, and bisect will either update to another candidate changeset
517 bad, and bisect will either update to another candidate changeset
518 or announce that it has found the bad revision.
518 or announce that it has found the bad revision.
519
519
520 As a shortcut, you can also use the revision argument to mark a
520 As a shortcut, you can also use the revision argument to mark a
521 revision as good or bad without checking it out first.
521 revision as good or bad without checking it out first.
522
522
523 If you supply a command, it will be used for automatic bisection.
523 If you supply a command, it will be used for automatic bisection.
524 The environment variable HG_NODE will contain the ID of the
524 The environment variable HG_NODE will contain the ID of the
525 changeset being tested. The exit status of the command will be
525 changeset being tested. The exit status of the command will be
526 used to mark revisions as good or bad: status 0 means good, 125
526 used to mark revisions as good or bad: status 0 means good, 125
527 means to skip the revision, 127 (command not found) will abort the
527 means to skip the revision, 127 (command not found) will abort the
528 bisection, and any other non-zero exit status means the revision
528 bisection, and any other non-zero exit status means the revision
529 is bad.
529 is bad.
530
530
531 .. container:: verbose
531 .. container:: verbose
532
532
533 Some examples:
533 Some examples:
534
534
535 - start a bisection with known bad revision 12, and good revision 34::
535 - start a bisection with known bad revision 12, and good revision 34::
536
536
537 hg bisect --bad 34
537 hg bisect --bad 34
538 hg bisect --good 12
538 hg bisect --good 12
539
539
540 - advance the current bisection by marking current revision as good or
540 - advance the current bisection by marking current revision as good or
541 bad::
541 bad::
542
542
543 hg bisect --good
543 hg bisect --good
544 hg bisect --bad
544 hg bisect --bad
545
545
546 - mark the current revision, or a known revision, to be skipped (eg. if
546 - mark the current revision, or a known revision, to be skipped (eg. if
547 that revision is not usable because of another issue)::
547 that revision is not usable because of another issue)::
548
548
549 hg bisect --skip
549 hg bisect --skip
550 hg bisect --skip 23
550 hg bisect --skip 23
551
551
552 - forget the current bisection::
552 - forget the current bisection::
553
553
554 hg bisect --reset
554 hg bisect --reset
555
555
556 - use 'make && make tests' to automatically find the first broken
556 - use 'make && make tests' to automatically find the first broken
557 revision::
557 revision::
558
558
559 hg bisect --reset
559 hg bisect --reset
560 hg bisect --bad 34
560 hg bisect --bad 34
561 hg bisect --good 12
561 hg bisect --good 12
562 hg bisect --command 'make && make tests'
562 hg bisect --command 'make && make tests'
563
563
564 - see all changesets whose states are already known in the current
564 - see all changesets whose states are already known in the current
565 bisection::
565 bisection::
566
566
567 hg log -r "bisect(pruned)"
567 hg log -r "bisect(pruned)"
568
568
569 - see the changeset currently being bisected (especially useful
569 - see the changeset currently being bisected (especially useful
570 if running with -U/--noupdate)::
570 if running with -U/--noupdate)::
571
571
572 hg log -r "bisect(current)"
572 hg log -r "bisect(current)"
573
573
574 - see all changesets that took part in the current bisection::
574 - see all changesets that took part in the current bisection::
575
575
576 hg log -r "bisect(range)"
576 hg log -r "bisect(range)"
577
577
578 - with the graphlog extension, you can even get a nice graph::
578 - with the graphlog extension, you can even get a nice graph::
579
579
580 hg log --graph -r "bisect(range)"
580 hg log --graph -r "bisect(range)"
581
581
582 See :hg:`help revsets` for more about the `bisect()` keyword.
582 See :hg:`help revsets` for more about the `bisect()` keyword.
583
583
584 Returns 0 on success.
584 Returns 0 on success.
585 """
585 """
586 def extendbisectrange(nodes, good):
586 def extendbisectrange(nodes, good):
587 # bisect is incomplete when it ends on a merge node and
587 # bisect is incomplete when it ends on a merge node and
588 # one of the parent was not checked.
588 # one of the parent was not checked.
589 parents = repo[nodes[0]].parents()
589 parents = repo[nodes[0]].parents()
590 if len(parents) > 1:
590 if len(parents) > 1:
591 side = good and state['bad'] or state['good']
591 side = good and state['bad'] or state['good']
592 num = len(set(i.node() for i in parents) & set(side))
592 num = len(set(i.node() for i in parents) & set(side))
593 if num == 1:
593 if num == 1:
594 return parents[0].ancestor(parents[1])
594 return parents[0].ancestor(parents[1])
595 return None
595 return None
596
596
597 def print_result(nodes, good):
597 def print_result(nodes, good):
598 displayer = cmdutil.show_changeset(ui, repo, {})
598 displayer = cmdutil.show_changeset(ui, repo, {})
599 if len(nodes) == 1:
599 if len(nodes) == 1:
600 # narrowed it down to a single revision
600 # narrowed it down to a single revision
601 if good:
601 if good:
602 ui.write(_("The first good revision is:\n"))
602 ui.write(_("The first good revision is:\n"))
603 else:
603 else:
604 ui.write(_("The first bad revision is:\n"))
604 ui.write(_("The first bad revision is:\n"))
605 displayer.show(repo[nodes[0]])
605 displayer.show(repo[nodes[0]])
606 extendnode = extendbisectrange(nodes, good)
606 extendnode = extendbisectrange(nodes, good)
607 if extendnode is not None:
607 if extendnode is not None:
608 ui.write(_('Not all ancestors of this changeset have been'
608 ui.write(_('Not all ancestors of this changeset have been'
609 ' checked.\nUse bisect --extend to continue the '
609 ' checked.\nUse bisect --extend to continue the '
610 'bisection from\nthe common ancestor, %s.\n')
610 'bisection from\nthe common ancestor, %s.\n')
611 % extendnode)
611 % extendnode)
612 else:
612 else:
613 # multiple possible revisions
613 # multiple possible revisions
614 if good:
614 if good:
615 ui.write(_("Due to skipped revisions, the first "
615 ui.write(_("Due to skipped revisions, the first "
616 "good revision could be any of:\n"))
616 "good revision could be any of:\n"))
617 else:
617 else:
618 ui.write(_("Due to skipped revisions, the first "
618 ui.write(_("Due to skipped revisions, the first "
619 "bad revision could be any of:\n"))
619 "bad revision could be any of:\n"))
620 for n in nodes:
620 for n in nodes:
621 displayer.show(repo[n])
621 displayer.show(repo[n])
622 displayer.close()
622 displayer.close()
623
623
624 def check_state(state, interactive=True):
624 def check_state(state, interactive=True):
625 if not state['good'] or not state['bad']:
625 if not state['good'] or not state['bad']:
626 if (good or bad or skip or reset) and interactive:
626 if (good or bad or skip or reset) and interactive:
627 return
627 return
628 if not state['good']:
628 if not state['good']:
629 raise util.Abort(_('cannot bisect (no known good revisions)'))
629 raise util.Abort(_('cannot bisect (no known good revisions)'))
630 else:
630 else:
631 raise util.Abort(_('cannot bisect (no known bad revisions)'))
631 raise util.Abort(_('cannot bisect (no known bad revisions)'))
632 return True
632 return True
633
633
634 # backward compatibility
634 # backward compatibility
635 if rev in "good bad reset init".split():
635 if rev in "good bad reset init".split():
636 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
636 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
637 cmd, rev, extra = rev, extra, None
637 cmd, rev, extra = rev, extra, None
638 if cmd == "good":
638 if cmd == "good":
639 good = True
639 good = True
640 elif cmd == "bad":
640 elif cmd == "bad":
641 bad = True
641 bad = True
642 else:
642 else:
643 reset = True
643 reset = True
644 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
644 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
645 raise util.Abort(_('incompatible arguments'))
645 raise util.Abort(_('incompatible arguments'))
646
646
647 if reset:
647 if reset:
648 p = repo.join("bisect.state")
648 p = repo.join("bisect.state")
649 if os.path.exists(p):
649 if os.path.exists(p):
650 os.unlink(p)
650 os.unlink(p)
651 return
651 return
652
652
653 state = hbisect.load_state(repo)
653 state = hbisect.load_state(repo)
654
654
655 if command:
655 if command:
656 changesets = 1
656 changesets = 1
657 try:
657 try:
658 node = state['current'][0]
658 node = state['current'][0]
659 except LookupError:
659 except LookupError:
660 if noupdate:
660 if noupdate:
661 raise util.Abort(_('current bisect revision is unknown - '
661 raise util.Abort(_('current bisect revision is unknown - '
662 'start a new bisect to fix'))
662 'start a new bisect to fix'))
663 node, p2 = repo.dirstate.parents()
663 node, p2 = repo.dirstate.parents()
664 if p2 != nullid:
664 if p2 != nullid:
665 raise util.Abort(_('current bisect revision is a merge'))
665 raise util.Abort(_('current bisect revision is a merge'))
666 try:
666 try:
667 while changesets:
667 while changesets:
668 # update state
668 # update state
669 state['current'] = [node]
669 state['current'] = [node]
670 hbisect.save_state(repo, state)
670 hbisect.save_state(repo, state)
671 status = util.system(command,
671 status = util.system(command,
672 environ={'HG_NODE': hex(node)},
672 environ={'HG_NODE': hex(node)},
673 out=ui.fout)
673 out=ui.fout)
674 if status == 125:
674 if status == 125:
675 transition = "skip"
675 transition = "skip"
676 elif status == 0:
676 elif status == 0:
677 transition = "good"
677 transition = "good"
678 # status < 0 means process was killed
678 # status < 0 means process was killed
679 elif status == 127:
679 elif status == 127:
680 raise util.Abort(_("failed to execute %s") % command)
680 raise util.Abort(_("failed to execute %s") % command)
681 elif status < 0:
681 elif status < 0:
682 raise util.Abort(_("%s killed") % command)
682 raise util.Abort(_("%s killed") % command)
683 else:
683 else:
684 transition = "bad"
684 transition = "bad"
685 ctx = scmutil.revsingle(repo, rev, node)
685 ctx = scmutil.revsingle(repo, rev, node)
686 rev = None # clear for future iterations
686 rev = None # clear for future iterations
687 state[transition].append(ctx.node())
687 state[transition].append(ctx.node())
688 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
688 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
689 check_state(state, interactive=False)
689 check_state(state, interactive=False)
690 # bisect
690 # bisect
691 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
691 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
692 # update to next check
692 # update to next check
693 node = nodes[0]
693 node = nodes[0]
694 if not noupdate:
694 if not noupdate:
695 cmdutil.bailifchanged(repo)
695 cmdutil.bailifchanged(repo)
696 hg.clean(repo, node, show_stats=False)
696 hg.clean(repo, node, show_stats=False)
697 finally:
697 finally:
698 state['current'] = [node]
698 state['current'] = [node]
699 hbisect.save_state(repo, state)
699 hbisect.save_state(repo, state)
700 print_result(nodes, good)
700 print_result(nodes, good)
701 return
701 return
702
702
703 # update state
703 # update state
704
704
705 if rev:
705 if rev:
706 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
706 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
707 else:
707 else:
708 nodes = [repo.lookup('.')]
708 nodes = [repo.lookup('.')]
709
709
710 if good or bad or skip:
710 if good or bad or skip:
711 if good:
711 if good:
712 state['good'] += nodes
712 state['good'] += nodes
713 elif bad:
713 elif bad:
714 state['bad'] += nodes
714 state['bad'] += nodes
715 elif skip:
715 elif skip:
716 state['skip'] += nodes
716 state['skip'] += nodes
717 hbisect.save_state(repo, state)
717 hbisect.save_state(repo, state)
718
718
719 if not check_state(state):
719 if not check_state(state):
720 return
720 return
721
721
722 # actually bisect
722 # actually bisect
723 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
723 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
724 if extend:
724 if extend:
725 if not changesets:
725 if not changesets:
726 extendnode = extendbisectrange(nodes, good)
726 extendnode = extendbisectrange(nodes, good)
727 if extendnode is not None:
727 if extendnode is not None:
728 ui.write(_("Extending search to changeset %d:%s\n"
728 ui.write(_("Extending search to changeset %d:%s\n"
729 % (extendnode.rev(), extendnode)))
729 % (extendnode.rev(), extendnode)))
730 state['current'] = [extendnode.node()]
730 state['current'] = [extendnode.node()]
731 hbisect.save_state(repo, state)
731 hbisect.save_state(repo, state)
732 if noupdate:
732 if noupdate:
733 return
733 return
734 cmdutil.bailifchanged(repo)
734 cmdutil.bailifchanged(repo)
735 return hg.clean(repo, extendnode.node())
735 return hg.clean(repo, extendnode.node())
736 raise util.Abort(_("nothing to extend"))
736 raise util.Abort(_("nothing to extend"))
737
737
738 if changesets == 0:
738 if changesets == 0:
739 print_result(nodes, good)
739 print_result(nodes, good)
740 else:
740 else:
741 assert len(nodes) == 1 # only a single node can be tested next
741 assert len(nodes) == 1 # only a single node can be tested next
742 node = nodes[0]
742 node = nodes[0]
743 # compute the approximate number of remaining tests
743 # compute the approximate number of remaining tests
744 tests, size = 0, 2
744 tests, size = 0, 2
745 while size <= changesets:
745 while size <= changesets:
746 tests, size = tests + 1, size * 2
746 tests, size = tests + 1, size * 2
747 rev = repo.changelog.rev(node)
747 rev = repo.changelog.rev(node)
748 ui.write(_("Testing changeset %d:%s "
748 ui.write(_("Testing changeset %d:%s "
749 "(%d changesets remaining, ~%d tests)\n")
749 "(%d changesets remaining, ~%d tests)\n")
750 % (rev, short(node), changesets, tests))
750 % (rev, short(node), changesets, tests))
751 state['current'] = [node]
751 state['current'] = [node]
752 hbisect.save_state(repo, state)
752 hbisect.save_state(repo, state)
753 if not noupdate:
753 if not noupdate:
754 cmdutil.bailifchanged(repo)
754 cmdutil.bailifchanged(repo)
755 return hg.clean(repo, node)
755 return hg.clean(repo, node)
756
756
757 @command('bookmarks',
757 @command('bookmarks',
758 [('f', 'force', False, _('force')),
758 [('f', 'force', False, _('force')),
759 ('r', 'rev', '', _('revision'), _('REV')),
759 ('r', 'rev', '', _('revision'), _('REV')),
760 ('d', 'delete', False, _('delete a given bookmark')),
760 ('d', 'delete', False, _('delete a given bookmark')),
761 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
761 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
762 ('i', 'inactive', False, _('mark a bookmark inactive'))],
762 ('i', 'inactive', False, _('mark a bookmark inactive'))],
763 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
763 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
764 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
764 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
765 rename=None, inactive=False):
765 rename=None, inactive=False):
766 '''track a line of development with movable markers
766 '''track a line of development with movable markers
767
767
768 Bookmarks are pointers to certain commits that move when committing.
768 Bookmarks are pointers to certain commits that move when committing.
769 Bookmarks are local. They can be renamed, copied and deleted. It is
769 Bookmarks are local. They can be renamed, copied and deleted. It is
770 possible to use :hg:`merge NAME` to merge from a given bookmark, and
770 possible to use :hg:`merge NAME` to merge from a given bookmark, and
771 :hg:`update NAME` to update to a given bookmark.
771 :hg:`update NAME` to update to a given bookmark.
772
772
773 You can use :hg:`bookmark NAME` to set a bookmark on the working
773 You can use :hg:`bookmark NAME` to set a bookmark on the working
774 directory's parent revision with the given name. If you specify
774 directory's parent revision with the given name. If you specify
775 a revision using -r REV (where REV may be an existing bookmark),
775 a revision using -r REV (where REV may be an existing bookmark),
776 the bookmark is assigned to that revision.
776 the bookmark is assigned to that revision.
777
777
778 Bookmarks can be pushed and pulled between repositories (see :hg:`help
778 Bookmarks can be pushed and pulled between repositories (see :hg:`help
779 push` and :hg:`help pull`). This requires both the local and remote
779 push` and :hg:`help pull`). This requires both the local and remote
780 repositories to support bookmarks. For versions prior to 1.8, this means
780 repositories to support bookmarks. For versions prior to 1.8, this means
781 the bookmarks extension must be enabled.
781 the bookmarks extension must be enabled.
782
782
783 With -i/--inactive, the new bookmark will not be made the active
783 With -i/--inactive, the new bookmark will not be made the active
784 bookmark. If -r/--rev is given, the new bookmark will not be made
784 bookmark. If -r/--rev is given, the new bookmark will not be made
785 active even if -i/--inactive is not given. If no NAME is given, the
785 active even if -i/--inactive is not given. If no NAME is given, the
786 current active bookmark will be marked inactive.
786 current active bookmark will be marked inactive.
787 '''
787 '''
788 hexfn = ui.debugflag and hex or short
788 hexfn = ui.debugflag and hex or short
789 marks = repo._bookmarks
789 marks = repo._bookmarks
790 cur = repo.changectx('.').node()
790 cur = repo.changectx('.').node()
791
791
792 if delete:
792 if delete:
793 if mark is None:
793 if mark is None:
794 raise util.Abort(_("bookmark name required"))
794 raise util.Abort(_("bookmark name required"))
795 if mark not in marks:
795 if mark not in marks:
796 raise util.Abort(_("bookmark '%s' does not exist") % mark)
796 raise util.Abort(_("bookmark '%s' does not exist") % mark)
797 if mark == repo._bookmarkcurrent:
797 if mark == repo._bookmarkcurrent:
798 bookmarks.setcurrent(repo, None)
798 bookmarks.setcurrent(repo, None)
799 del marks[mark]
799 del marks[mark]
800 bookmarks.write(repo)
800 bookmarks.write(repo)
801 return
801 return
802
802
803 if rename:
803 if rename:
804 if rename not in marks:
804 if rename not in marks:
805 raise util.Abort(_("bookmark '%s' does not exist") % rename)
805 raise util.Abort(_("bookmark '%s' does not exist") % rename)
806 if mark in marks and not force:
806 if mark in marks and not force:
807 raise util.Abort(_("bookmark '%s' already exists "
807 raise util.Abort(_("bookmark '%s' already exists "
808 "(use -f to force)") % mark)
808 "(use -f to force)") % mark)
809 if mark is None:
809 if mark is None:
810 raise util.Abort(_("new bookmark name required"))
810 raise util.Abort(_("new bookmark name required"))
811 marks[mark] = marks[rename]
811 marks[mark] = marks[rename]
812 if repo._bookmarkcurrent == rename and not inactive:
812 if repo._bookmarkcurrent == rename and not inactive:
813 bookmarks.setcurrent(repo, mark)
813 bookmarks.setcurrent(repo, mark)
814 del marks[rename]
814 del marks[rename]
815 bookmarks.write(repo)
815 bookmarks.write(repo)
816 return
816 return
817
817
818 if mark is not None:
818 if mark is not None:
819 if "\n" in mark:
819 if "\n" in mark:
820 raise util.Abort(_("bookmark name cannot contain newlines"))
820 raise util.Abort(_("bookmark name cannot contain newlines"))
821 mark = mark.strip()
821 mark = mark.strip()
822 if not mark:
822 if not mark:
823 raise util.Abort(_("bookmark names cannot consist entirely of "
823 raise util.Abort(_("bookmark names cannot consist entirely of "
824 "whitespace"))
824 "whitespace"))
825 if inactive and mark == repo._bookmarkcurrent:
825 if inactive and mark == repo._bookmarkcurrent:
826 bookmarks.setcurrent(repo, None)
826 bookmarks.setcurrent(repo, None)
827 return
827 return
828 if mark in marks and not force:
828 if mark in marks and not force:
829 raise util.Abort(_("bookmark '%s' already exists "
829 raise util.Abort(_("bookmark '%s' already exists "
830 "(use -f to force)") % mark)
830 "(use -f to force)") % mark)
831 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
831 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
832 and not force):
832 and not force):
833 raise util.Abort(
833 raise util.Abort(
834 _("a bookmark cannot have the name of an existing branch"))
834 _("a bookmark cannot have the name of an existing branch"))
835 if rev:
835 if rev:
836 marks[mark] = repo.lookup(rev)
836 marks[mark] = repo.lookup(rev)
837 else:
837 else:
838 marks[mark] = cur
838 marks[mark] = cur
839 if not inactive and cur == marks[mark]:
839 if not inactive and cur == marks[mark]:
840 bookmarks.setcurrent(repo, mark)
840 bookmarks.setcurrent(repo, mark)
841 bookmarks.write(repo)
841 bookmarks.write(repo)
842 return
842 return
843
843
844 if mark is None:
844 if mark is None:
845 if rev:
845 if rev:
846 raise util.Abort(_("bookmark name required"))
846 raise util.Abort(_("bookmark name required"))
847 if len(marks) == 0:
847 if len(marks) == 0:
848 ui.status(_("no bookmarks set\n"))
848 ui.status(_("no bookmarks set\n"))
849 else:
849 else:
850 for bmark, n in sorted(marks.iteritems()):
850 for bmark, n in sorted(marks.iteritems()):
851 current = repo._bookmarkcurrent
851 current = repo._bookmarkcurrent
852 if bmark == current and n == cur:
852 if bmark == current and n == cur:
853 prefix, label = '*', 'bookmarks.current'
853 prefix, label = '*', 'bookmarks.current'
854 else:
854 else:
855 prefix, label = ' ', ''
855 prefix, label = ' ', ''
856
856
857 if ui.quiet:
857 if ui.quiet:
858 ui.write("%s\n" % bmark, label=label)
858 ui.write("%s\n" % bmark, label=label)
859 else:
859 else:
860 ui.write(" %s %-25s %d:%s\n" % (
860 ui.write(" %s %-25s %d:%s\n" % (
861 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
861 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
862 label=label)
862 label=label)
863 return
863 return
864
864
865 @command('branch',
865 @command('branch',
866 [('f', 'force', None,
866 [('f', 'force', None,
867 _('set branch name even if it shadows an existing branch')),
867 _('set branch name even if it shadows an existing branch')),
868 ('C', 'clean', None, _('reset branch name to parent branch name'))],
868 ('C', 'clean', None, _('reset branch name to parent branch name'))],
869 _('[-fC] [NAME]'))
869 _('[-fC] [NAME]'))
870 def branch(ui, repo, label=None, **opts):
870 def branch(ui, repo, label=None, **opts):
871 """set or show the current branch name
871 """set or show the current branch name
872
872
873 .. note::
873 .. note::
874 Branch names are permanent and global. Use :hg:`bookmark` to create a
874 Branch names are permanent and global. Use :hg:`bookmark` to create a
875 light-weight bookmark instead. See :hg:`help glossary` for more
875 light-weight bookmark instead. See :hg:`help glossary` for more
876 information about named branches and bookmarks.
876 information about named branches and bookmarks.
877
877
878 With no argument, show the current branch name. With one argument,
878 With no argument, show the current branch name. With one argument,
879 set the working directory branch name (the branch will not exist
879 set the working directory branch name (the branch will not exist
880 in the repository until the next commit). Standard practice
880 in the repository until the next commit). Standard practice
881 recommends that primary development take place on the 'default'
881 recommends that primary development take place on the 'default'
882 branch.
882 branch.
883
883
884 Unless -f/--force is specified, branch will not let you set a
884 Unless -f/--force is specified, branch will not let you set a
885 branch name that already exists, even if it's inactive.
885 branch name that already exists, even if it's inactive.
886
886
887 Use -C/--clean to reset the working directory branch to that of
887 Use -C/--clean to reset the working directory branch to that of
888 the parent of the working directory, negating a previous branch
888 the parent of the working directory, negating a previous branch
889 change.
889 change.
890
890
891 Use the command :hg:`update` to switch to an existing branch. Use
891 Use the command :hg:`update` to switch to an existing branch. Use
892 :hg:`commit --close-branch` to mark this branch as closed.
892 :hg:`commit --close-branch` to mark this branch as closed.
893
893
894 Returns 0 on success.
894 Returns 0 on success.
895 """
895 """
896 if not opts.get('clean') and not label:
896 if not opts.get('clean') and not label:
897 ui.write("%s\n" % repo.dirstate.branch())
897 ui.write("%s\n" % repo.dirstate.branch())
898 return
898 return
899
899
900 wlock = repo.wlock()
900 wlock = repo.wlock()
901 try:
901 try:
902 if opts.get('clean'):
902 if opts.get('clean'):
903 label = repo[None].p1().branch()
903 label = repo[None].p1().branch()
904 repo.dirstate.setbranch(label)
904 repo.dirstate.setbranch(label)
905 ui.status(_('reset working directory to branch %s\n') % label)
905 ui.status(_('reset working directory to branch %s\n') % label)
906 elif label:
906 elif label:
907 if not opts.get('force') and label in repo.branchmap():
907 if not opts.get('force') and label in repo.branchmap():
908 if label not in [p.branch() for p in repo.parents()]:
908 if label not in [p.branch() for p in repo.parents()]:
909 raise util.Abort(_('a branch of the same name already'
909 raise util.Abort(_('a branch of the same name already'
910 ' exists'),
910 ' exists'),
911 # i18n: "it" refers to an existing branch
911 # i18n: "it" refers to an existing branch
912 hint=_("use 'hg update' to switch to it"))
912 hint=_("use 'hg update' to switch to it"))
913 repo.dirstate.setbranch(label)
913 repo.dirstate.setbranch(label)
914 ui.status(_('marked working directory as branch %s\n') % label)
914 ui.status(_('marked working directory as branch %s\n') % label)
915 ui.status(_('(branches are permanent and global, '
915 ui.status(_('(branches are permanent and global, '
916 'did you want a bookmark?)\n'))
916 'did you want a bookmark?)\n'))
917 finally:
917 finally:
918 wlock.release()
918 wlock.release()
919
919
920 @command('branches',
920 @command('branches',
921 [('a', 'active', False, _('show only branches that have unmerged heads')),
921 [('a', 'active', False, _('show only branches that have unmerged heads')),
922 ('c', 'closed', False, _('show normal and closed branches'))],
922 ('c', 'closed', False, _('show normal and closed branches'))],
923 _('[-ac]'))
923 _('[-ac]'))
924 def branches(ui, repo, active=False, closed=False):
924 def branches(ui, repo, active=False, closed=False):
925 """list repository named branches
925 """list repository named branches
926
926
927 List the repository's named branches, indicating which ones are
927 List the repository's named branches, indicating which ones are
928 inactive. If -c/--closed is specified, also list branches which have
928 inactive. If -c/--closed is specified, also list branches which have
929 been marked closed (see :hg:`commit --close-branch`).
929 been marked closed (see :hg:`commit --close-branch`).
930
930
931 If -a/--active is specified, only show active branches. A branch
931 If -a/--active is specified, only show active branches. A branch
932 is considered active if it contains repository heads.
932 is considered active if it contains repository heads.
933
933
934 Use the command :hg:`update` to switch to an existing branch.
934 Use the command :hg:`update` to switch to an existing branch.
935
935
936 Returns 0.
936 Returns 0.
937 """
937 """
938
938
939 hexfunc = ui.debugflag and hex or short
939 hexfunc = ui.debugflag and hex or short
940
940
941 activebranches = set([repo[n].branch() for n in repo.heads()])
941 activebranches = set([repo[n].branch() for n in repo.heads()])
942 branches = []
942 branches = []
943 for tag, heads in repo.branchmap().iteritems():
943 for tag, heads in repo.branchmap().iteritems():
944 for h in reversed(heads):
944 for h in reversed(heads):
945 ctx = repo[h]
945 ctx = repo[h]
946 isopen = not ctx.closesbranch()
946 isopen = not ctx.closesbranch()
947 if isopen:
947 if isopen:
948 tip = ctx
948 tip = ctx
949 break
949 break
950 else:
950 else:
951 tip = repo[heads[-1]]
951 tip = repo[heads[-1]]
952 isactive = tag in activebranches and isopen
952 isactive = tag in activebranches and isopen
953 branches.append((tip, isactive, isopen))
953 branches.append((tip, isactive, isopen))
954 branches.sort(key=lambda i: (i[1], i[0].rev(), i[0].branch(), i[2]),
954 branches.sort(key=lambda i: (i[1], i[0].rev(), i[0].branch(), i[2]),
955 reverse=True)
955 reverse=True)
956
956
957 for ctx, isactive, isopen in branches:
957 for ctx, isactive, isopen in branches:
958 if (not active) or isactive:
958 if (not active) or isactive:
959 if isactive:
959 if isactive:
960 label = 'branches.active'
960 label = 'branches.active'
961 notice = ''
961 notice = ''
962 elif not isopen:
962 elif not isopen:
963 if not closed:
963 if not closed:
964 continue
964 continue
965 label = 'branches.closed'
965 label = 'branches.closed'
966 notice = _(' (closed)')
966 notice = _(' (closed)')
967 else:
967 else:
968 label = 'branches.inactive'
968 label = 'branches.inactive'
969 notice = _(' (inactive)')
969 notice = _(' (inactive)')
970 if ctx.branch() == repo.dirstate.branch():
970 if ctx.branch() == repo.dirstate.branch():
971 label = 'branches.current'
971 label = 'branches.current'
972 rev = str(ctx.rev()).rjust(31 - encoding.colwidth(ctx.branch()))
972 rev = str(ctx.rev()).rjust(31 - encoding.colwidth(ctx.branch()))
973 rev = ui.label('%s:%s' % (rev, hexfunc(ctx.node())),
973 rev = ui.label('%s:%s' % (rev, hexfunc(ctx.node())),
974 'log.changeset')
974 'log.changeset')
975 tag = ui.label(ctx.branch(), label)
975 tag = ui.label(ctx.branch(), label)
976 if ui.quiet:
976 if ui.quiet:
977 ui.write("%s\n" % tag)
977 ui.write("%s\n" % tag)
978 else:
978 else:
979 ui.write("%s %s%s\n" % (tag, rev, notice))
979 ui.write("%s %s%s\n" % (tag, rev, notice))
980
980
981 @command('bundle',
981 @command('bundle',
982 [('f', 'force', None, _('run even when the destination is unrelated')),
982 [('f', 'force', None, _('run even when the destination is unrelated')),
983 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
983 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
984 _('REV')),
984 _('REV')),
985 ('b', 'branch', [], _('a specific branch you would like to bundle'),
985 ('b', 'branch', [], _('a specific branch you would like to bundle'),
986 _('BRANCH')),
986 _('BRANCH')),
987 ('', 'base', [],
987 ('', 'base', [],
988 _('a base changeset assumed to be available at the destination'),
988 _('a base changeset assumed to be available at the destination'),
989 _('REV')),
989 _('REV')),
990 ('a', 'all', None, _('bundle all changesets in the repository')),
990 ('a', 'all', None, _('bundle all changesets in the repository')),
991 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
991 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
992 ] + remoteopts,
992 ] + remoteopts,
993 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
993 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
994 def bundle(ui, repo, fname, dest=None, **opts):
994 def bundle(ui, repo, fname, dest=None, **opts):
995 """create a changegroup file
995 """create a changegroup file
996
996
997 Generate a compressed changegroup file collecting changesets not
997 Generate a compressed changegroup file collecting changesets not
998 known to be in another repository.
998 known to be in another repository.
999
999
1000 If you omit the destination repository, then hg assumes the
1000 If you omit the destination repository, then hg assumes the
1001 destination will have all the nodes you specify with --base
1001 destination will have all the nodes you specify with --base
1002 parameters. To create a bundle containing all changesets, use
1002 parameters. To create a bundle containing all changesets, use
1003 -a/--all (or --base null).
1003 -a/--all (or --base null).
1004
1004
1005 You can change compression method with the -t/--type option.
1005 You can change compression method with the -t/--type option.
1006 The available compression methods are: none, bzip2, and
1006 The available compression methods are: none, bzip2, and
1007 gzip (by default, bundles are compressed using bzip2).
1007 gzip (by default, bundles are compressed using bzip2).
1008
1008
1009 The bundle file can then be transferred using conventional means
1009 The bundle file can then be transferred using conventional means
1010 and applied to another repository with the unbundle or pull
1010 and applied to another repository with the unbundle or pull
1011 command. This is useful when direct push and pull are not
1011 command. This is useful when direct push and pull are not
1012 available or when exporting an entire repository is undesirable.
1012 available or when exporting an entire repository is undesirable.
1013
1013
1014 Applying bundles preserves all changeset contents including
1014 Applying bundles preserves all changeset contents including
1015 permissions, copy/rename information, and revision history.
1015 permissions, copy/rename information, and revision history.
1016
1016
1017 Returns 0 on success, 1 if no changes found.
1017 Returns 0 on success, 1 if no changes found.
1018 """
1018 """
1019 revs = None
1019 revs = None
1020 if 'rev' in opts:
1020 if 'rev' in opts:
1021 revs = scmutil.revrange(repo, opts['rev'])
1021 revs = scmutil.revrange(repo, opts['rev'])
1022
1022
1023 bundletype = opts.get('type', 'bzip2').lower()
1023 bundletype = opts.get('type', 'bzip2').lower()
1024 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1024 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1025 bundletype = btypes.get(bundletype)
1025 bundletype = btypes.get(bundletype)
1026 if bundletype not in changegroup.bundletypes:
1026 if bundletype not in changegroup.bundletypes:
1027 raise util.Abort(_('unknown bundle type specified with --type'))
1027 raise util.Abort(_('unknown bundle type specified with --type'))
1028
1028
1029 if opts.get('all'):
1029 if opts.get('all'):
1030 base = ['null']
1030 base = ['null']
1031 else:
1031 else:
1032 base = scmutil.revrange(repo, opts.get('base'))
1032 base = scmutil.revrange(repo, opts.get('base'))
1033 if base:
1033 if base:
1034 if dest:
1034 if dest:
1035 raise util.Abort(_("--base is incompatible with specifying "
1035 raise util.Abort(_("--base is incompatible with specifying "
1036 "a destination"))
1036 "a destination"))
1037 common = [repo.lookup(rev) for rev in base]
1037 common = [repo.lookup(rev) for rev in base]
1038 heads = revs and map(repo.lookup, revs) or revs
1038 heads = revs and map(repo.lookup, revs) or revs
1039 cg = repo.getbundle('bundle', heads=heads, common=common)
1039 cg = repo.getbundle('bundle', heads=heads, common=common)
1040 outgoing = None
1040 outgoing = None
1041 else:
1041 else:
1042 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1042 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1043 dest, branches = hg.parseurl(dest, opts.get('branch'))
1043 dest, branches = hg.parseurl(dest, opts.get('branch'))
1044 other = hg.peer(repo, opts, dest)
1044 other = hg.peer(repo, opts, dest)
1045 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
1045 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
1046 heads = revs and map(repo.lookup, revs) or revs
1046 heads = revs and map(repo.lookup, revs) or revs
1047 outgoing = discovery.findcommonoutgoing(repo, other,
1047 outgoing = discovery.findcommonoutgoing(repo, other,
1048 onlyheads=heads,
1048 onlyheads=heads,
1049 force=opts.get('force'),
1049 force=opts.get('force'),
1050 portable=True)
1050 portable=True)
1051 cg = repo.getlocalbundle('bundle', outgoing)
1051 cg = repo.getlocalbundle('bundle', outgoing)
1052 if not cg:
1052 if not cg:
1053 scmutil.nochangesfound(ui, outgoing and outgoing.excluded)
1053 scmutil.nochangesfound(ui, outgoing and outgoing.excluded)
1054 return 1
1054 return 1
1055
1055
1056 changegroup.writebundle(cg, fname, bundletype)
1056 changegroup.writebundle(cg, fname, bundletype)
1057
1057
1058 @command('cat',
1058 @command('cat',
1059 [('o', 'output', '',
1059 [('o', 'output', '',
1060 _('print output to file with formatted name'), _('FORMAT')),
1060 _('print output to file with formatted name'), _('FORMAT')),
1061 ('r', 'rev', '', _('print the given revision'), _('REV')),
1061 ('r', 'rev', '', _('print the given revision'), _('REV')),
1062 ('', 'decode', None, _('apply any matching decode filter')),
1062 ('', 'decode', None, _('apply any matching decode filter')),
1063 ] + walkopts,
1063 ] + walkopts,
1064 _('[OPTION]... FILE...'))
1064 _('[OPTION]... FILE...'))
1065 def cat(ui, repo, file1, *pats, **opts):
1065 def cat(ui, repo, file1, *pats, **opts):
1066 """output the current or given revision of files
1066 """output the current or given revision of files
1067
1067
1068 Print the specified files as they were at the given revision. If
1068 Print the specified files as they were at the given revision. If
1069 no revision is given, the parent of the working directory is used,
1069 no revision is given, the parent of the working directory is used,
1070 or tip if no revision is checked out.
1070 or tip if no revision is checked out.
1071
1071
1072 Output may be to a file, in which case the name of the file is
1072 Output may be to a file, in which case the name of the file is
1073 given using a format string. The formatting rules are the same as
1073 given using a format string. The formatting rules are the same as
1074 for the export command, with the following additions:
1074 for the export command, with the following additions:
1075
1075
1076 :``%s``: basename of file being printed
1076 :``%s``: basename of file being printed
1077 :``%d``: dirname of file being printed, or '.' if in repository root
1077 :``%d``: dirname of file being printed, or '.' if in repository root
1078 :``%p``: root-relative path name of file being printed
1078 :``%p``: root-relative path name of file being printed
1079
1079
1080 Returns 0 on success.
1080 Returns 0 on success.
1081 """
1081 """
1082 ctx = scmutil.revsingle(repo, opts.get('rev'))
1082 ctx = scmutil.revsingle(repo, opts.get('rev'))
1083 err = 1
1083 err = 1
1084 m = scmutil.match(ctx, (file1,) + pats, opts)
1084 m = scmutil.match(ctx, (file1,) + pats, opts)
1085 for abs in ctx.walk(m):
1085 for abs in ctx.walk(m):
1086 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1086 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1087 pathname=abs)
1087 pathname=abs)
1088 data = ctx[abs].data()
1088 data = ctx[abs].data()
1089 if opts.get('decode'):
1089 if opts.get('decode'):
1090 data = repo.wwritedata(abs, data)
1090 data = repo.wwritedata(abs, data)
1091 fp.write(data)
1091 fp.write(data)
1092 fp.close()
1092 fp.close()
1093 err = 0
1093 err = 0
1094 return err
1094 return err
1095
1095
1096 @command('^clone',
1096 @command('^clone',
1097 [('U', 'noupdate', None,
1097 [('U', 'noupdate', None,
1098 _('the clone will include an empty working copy (only a repository)')),
1098 _('the clone will include an empty working copy (only a repository)')),
1099 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1099 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1100 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1100 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1101 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1101 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1102 ('', 'pull', None, _('use pull protocol to copy metadata')),
1102 ('', 'pull', None, _('use pull protocol to copy metadata')),
1103 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1103 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1104 ] + remoteopts,
1104 ] + remoteopts,
1105 _('[OPTION]... SOURCE [DEST]'))
1105 _('[OPTION]... SOURCE [DEST]'))
1106 def clone(ui, source, dest=None, **opts):
1106 def clone(ui, source, dest=None, **opts):
1107 """make a copy of an existing repository
1107 """make a copy of an existing repository
1108
1108
1109 Create a copy of an existing repository in a new directory.
1109 Create a copy of an existing repository in a new directory.
1110
1110
1111 If no destination directory name is specified, it defaults to the
1111 If no destination directory name is specified, it defaults to the
1112 basename of the source.
1112 basename of the source.
1113
1113
1114 The location of the source is added to the new repository's
1114 The location of the source is added to the new repository's
1115 ``.hg/hgrc`` file, as the default to be used for future pulls.
1115 ``.hg/hgrc`` file, as the default to be used for future pulls.
1116
1116
1117 Only local paths and ``ssh://`` URLs are supported as
1117 Only local paths and ``ssh://`` URLs are supported as
1118 destinations. For ``ssh://`` destinations, no working directory or
1118 destinations. For ``ssh://`` destinations, no working directory or
1119 ``.hg/hgrc`` will be created on the remote side.
1119 ``.hg/hgrc`` will be created on the remote side.
1120
1120
1121 To pull only a subset of changesets, specify one or more revisions
1121 To pull only a subset of changesets, specify one or more revisions
1122 identifiers with -r/--rev or branches with -b/--branch. The
1122 identifiers with -r/--rev or branches with -b/--branch. The
1123 resulting clone will contain only the specified changesets and
1123 resulting clone will contain only the specified changesets and
1124 their ancestors. These options (or 'clone src#rev dest') imply
1124 their ancestors. These options (or 'clone src#rev dest') imply
1125 --pull, even for local source repositories. Note that specifying a
1125 --pull, even for local source repositories. Note that specifying a
1126 tag will include the tagged changeset but not the changeset
1126 tag will include the tagged changeset but not the changeset
1127 containing the tag.
1127 containing the tag.
1128
1128
1129 To check out a particular version, use -u/--update, or
1129 To check out a particular version, use -u/--update, or
1130 -U/--noupdate to create a clone with no working directory.
1130 -U/--noupdate to create a clone with no working directory.
1131
1131
1132 .. container:: verbose
1132 .. container:: verbose
1133
1133
1134 For efficiency, hardlinks are used for cloning whenever the
1134 For efficiency, hardlinks are used for cloning whenever the
1135 source and destination are on the same filesystem (note this
1135 source and destination are on the same filesystem (note this
1136 applies only to the repository data, not to the working
1136 applies only to the repository data, not to the working
1137 directory). Some filesystems, such as AFS, implement hardlinking
1137 directory). Some filesystems, such as AFS, implement hardlinking
1138 incorrectly, but do not report errors. In these cases, use the
1138 incorrectly, but do not report errors. In these cases, use the
1139 --pull option to avoid hardlinking.
1139 --pull option to avoid hardlinking.
1140
1140
1141 In some cases, you can clone repositories and the working
1141 In some cases, you can clone repositories and the working
1142 directory using full hardlinks with ::
1142 directory using full hardlinks with ::
1143
1143
1144 $ cp -al REPO REPOCLONE
1144 $ cp -al REPO REPOCLONE
1145
1145
1146 This is the fastest way to clone, but it is not always safe. The
1146 This is the fastest way to clone, but it is not always safe. The
1147 operation is not atomic (making sure REPO is not modified during
1147 operation is not atomic (making sure REPO is not modified during
1148 the operation is up to you) and you have to make sure your
1148 the operation is up to you) and you have to make sure your
1149 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1149 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1150 so). Also, this is not compatible with certain extensions that
1150 so). Also, this is not compatible with certain extensions that
1151 place their metadata under the .hg directory, such as mq.
1151 place their metadata under the .hg directory, such as mq.
1152
1152
1153 Mercurial will update the working directory to the first applicable
1153 Mercurial will update the working directory to the first applicable
1154 revision from this list:
1154 revision from this list:
1155
1155
1156 a) null if -U or the source repository has no changesets
1156 a) null if -U or the source repository has no changesets
1157 b) if -u . and the source repository is local, the first parent of
1157 b) if -u . and the source repository is local, the first parent of
1158 the source repository's working directory
1158 the source repository's working directory
1159 c) the changeset specified with -u (if a branch name, this means the
1159 c) the changeset specified with -u (if a branch name, this means the
1160 latest head of that branch)
1160 latest head of that branch)
1161 d) the changeset specified with -r
1161 d) the changeset specified with -r
1162 e) the tipmost head specified with -b
1162 e) the tipmost head specified with -b
1163 f) the tipmost head specified with the url#branch source syntax
1163 f) the tipmost head specified with the url#branch source syntax
1164 g) the tipmost head of the default branch
1164 g) the tipmost head of the default branch
1165 h) tip
1165 h) tip
1166
1166
1167 Examples:
1167 Examples:
1168
1168
1169 - clone a remote repository to a new directory named hg/::
1169 - clone a remote repository to a new directory named hg/::
1170
1170
1171 hg clone http://selenic.com/hg
1171 hg clone http://selenic.com/hg
1172
1172
1173 - create a lightweight local clone::
1173 - create a lightweight local clone::
1174
1174
1175 hg clone project/ project-feature/
1175 hg clone project/ project-feature/
1176
1176
1177 - clone from an absolute path on an ssh server (note double-slash)::
1177 - clone from an absolute path on an ssh server (note double-slash)::
1178
1178
1179 hg clone ssh://user@server//home/projects/alpha/
1179 hg clone ssh://user@server//home/projects/alpha/
1180
1180
1181 - do a high-speed clone over a LAN while checking out a
1181 - do a high-speed clone over a LAN while checking out a
1182 specified version::
1182 specified version::
1183
1183
1184 hg clone --uncompressed http://server/repo -u 1.5
1184 hg clone --uncompressed http://server/repo -u 1.5
1185
1185
1186 - create a repository without changesets after a particular revision::
1186 - create a repository without changesets after a particular revision::
1187
1187
1188 hg clone -r 04e544 experimental/ good/
1188 hg clone -r 04e544 experimental/ good/
1189
1189
1190 - clone (and track) a particular named branch::
1190 - clone (and track) a particular named branch::
1191
1191
1192 hg clone http://selenic.com/hg#stable
1192 hg clone http://selenic.com/hg#stable
1193
1193
1194 See :hg:`help urls` for details on specifying URLs.
1194 See :hg:`help urls` for details on specifying URLs.
1195
1195
1196 Returns 0 on success.
1196 Returns 0 on success.
1197 """
1197 """
1198 if opts.get('noupdate') and opts.get('updaterev'):
1198 if opts.get('noupdate') and opts.get('updaterev'):
1199 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1199 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1200
1200
1201 r = hg.clone(ui, opts, source, dest,
1201 r = hg.clone(ui, opts, source, dest,
1202 pull=opts.get('pull'),
1202 pull=opts.get('pull'),
1203 stream=opts.get('uncompressed'),
1203 stream=opts.get('uncompressed'),
1204 rev=opts.get('rev'),
1204 rev=opts.get('rev'),
1205 update=opts.get('updaterev') or not opts.get('noupdate'),
1205 update=opts.get('updaterev') or not opts.get('noupdate'),
1206 branch=opts.get('branch'))
1206 branch=opts.get('branch'))
1207
1207
1208 return r is None
1208 return r is None
1209
1209
1210 @command('^commit|ci',
1210 @command('^commit|ci',
1211 [('A', 'addremove', None,
1211 [('A', 'addremove', None,
1212 _('mark new/missing files as added/removed before committing')),
1212 _('mark new/missing files as added/removed before committing')),
1213 ('', 'close-branch', None,
1213 ('', 'close-branch', None,
1214 _('mark a branch as closed, hiding it from the branch list')),
1214 _('mark a branch as closed, hiding it from the branch list')),
1215 ('', 'amend', None, _('amend the parent of the working dir')),
1215 ('', 'amend', None, _('amend the parent of the working dir')),
1216 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1216 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1217 _('[OPTION]... [FILE]...'))
1217 _('[OPTION]... [FILE]...'))
1218 def commit(ui, repo, *pats, **opts):
1218 def commit(ui, repo, *pats, **opts):
1219 """commit the specified files or all outstanding changes
1219 """commit the specified files or all outstanding changes
1220
1220
1221 Commit changes to the given files into the repository. Unlike a
1221 Commit changes to the given files into the repository. Unlike a
1222 centralized SCM, this operation is a local operation. See
1222 centralized SCM, this operation is a local operation. See
1223 :hg:`push` for a way to actively distribute your changes.
1223 :hg:`push` for a way to actively distribute your changes.
1224
1224
1225 If a list of files is omitted, all changes reported by :hg:`status`
1225 If a list of files is omitted, all changes reported by :hg:`status`
1226 will be committed.
1226 will be committed.
1227
1227
1228 If you are committing the result of a merge, do not provide any
1228 If you are committing the result of a merge, do not provide any
1229 filenames or -I/-X filters.
1229 filenames or -I/-X filters.
1230
1230
1231 If no commit message is specified, Mercurial starts your
1231 If no commit message is specified, Mercurial starts your
1232 configured editor where you can enter a message. In case your
1232 configured editor where you can enter a message. In case your
1233 commit fails, you will find a backup of your message in
1233 commit fails, you will find a backup of your message in
1234 ``.hg/last-message.txt``.
1234 ``.hg/last-message.txt``.
1235
1235
1236 The --amend flag can be used to amend the parent of the
1236 The --amend flag can be used to amend the parent of the
1237 working directory with a new commit that contains the changes
1237 working directory with a new commit that contains the changes
1238 in the parent in addition to those currently reported by :hg:`status`,
1238 in the parent in addition to those currently reported by :hg:`status`,
1239 if there are any. The old commit is stored in a backup bundle in
1239 if there are any. The old commit is stored in a backup bundle in
1240 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1240 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1241 on how to restore it).
1241 on how to restore it).
1242
1242
1243 Message, user and date are taken from the amended commit unless
1243 Message, user and date are taken from the amended commit unless
1244 specified. When a message isn't specified on the command line,
1244 specified. When a message isn't specified on the command line,
1245 the editor will open with the message of the amended commit.
1245 the editor will open with the message of the amended commit.
1246
1246
1247 It is not possible to amend public changesets (see :hg:`help phases`)
1247 It is not possible to amend public changesets (see :hg:`help phases`)
1248 or changesets that have children.
1248 or changesets that have children.
1249
1249
1250 See :hg:`help dates` for a list of formats valid for -d/--date.
1250 See :hg:`help dates` for a list of formats valid for -d/--date.
1251
1251
1252 Returns 0 on success, 1 if nothing changed.
1252 Returns 0 on success, 1 if nothing changed.
1253 """
1253 """
1254 if opts.get('subrepos'):
1254 if opts.get('subrepos'):
1255 # Let --subrepos on the command line overide config setting.
1255 # Let --subrepos on the command line overide config setting.
1256 ui.setconfig('ui', 'commitsubrepos', True)
1256 ui.setconfig('ui', 'commitsubrepos', True)
1257
1257
1258 extra = {}
1258 extra = {}
1259 if opts.get('close_branch'):
1259 if opts.get('close_branch'):
1260 if repo['.'].node() not in repo.branchheads():
1260 if repo['.'].node() not in repo.branchheads():
1261 # The topo heads set is included in the branch heads set of the
1261 # The topo heads set is included in the branch heads set of the
1262 # current branch, so it's sufficient to test branchheads
1262 # current branch, so it's sufficient to test branchheads
1263 raise util.Abort(_('can only close branch heads'))
1263 raise util.Abort(_('can only close branch heads'))
1264 extra['close'] = 1
1264 extra['close'] = 1
1265
1265
1266 branch = repo[None].branch()
1266 branch = repo[None].branch()
1267 bheads = repo.branchheads(branch)
1267 bheads = repo.branchheads(branch)
1268
1268
1269 if opts.get('amend'):
1269 if opts.get('amend'):
1270 if ui.configbool('ui', 'commitsubrepos'):
1270 if ui.configbool('ui', 'commitsubrepos'):
1271 raise util.Abort(_('cannot amend recursively'))
1271 raise util.Abort(_('cannot amend recursively'))
1272
1272
1273 old = repo['.']
1273 old = repo['.']
1274 if old.phase() == phases.public:
1274 if old.phase() == phases.public:
1275 raise util.Abort(_('cannot amend public changesets'))
1275 raise util.Abort(_('cannot amend public changesets'))
1276 if len(old.parents()) > 1:
1276 if len(old.parents()) > 1:
1277 raise util.Abort(_('cannot amend merge changesets'))
1277 raise util.Abort(_('cannot amend merge changesets'))
1278 if len(repo[None].parents()) > 1:
1278 if len(repo[None].parents()) > 1:
1279 raise util.Abort(_('cannot amend while merging'))
1279 raise util.Abort(_('cannot amend while merging'))
1280 if old.children():
1280 if old.children():
1281 raise util.Abort(_('cannot amend changeset with children'))
1281 raise util.Abort(_('cannot amend changeset with children'))
1282
1282
1283 e = cmdutil.commiteditor
1283 e = cmdutil.commiteditor
1284 if opts.get('force_editor'):
1284 if opts.get('force_editor'):
1285 e = cmdutil.commitforceeditor
1285 e = cmdutil.commitforceeditor
1286
1286
1287 def commitfunc(ui, repo, message, match, opts):
1287 def commitfunc(ui, repo, message, match, opts):
1288 editor = e
1288 editor = e
1289 # message contains text from -m or -l, if it's empty,
1289 # message contains text from -m or -l, if it's empty,
1290 # open the editor with the old message
1290 # open the editor with the old message
1291 if not message:
1291 if not message:
1292 message = old.description()
1292 message = old.description()
1293 editor = cmdutil.commitforceeditor
1293 editor = cmdutil.commitforceeditor
1294 return repo.commit(message,
1294 return repo.commit(message,
1295 opts.get('user') or old.user(),
1295 opts.get('user') or old.user(),
1296 opts.get('date') or old.date(),
1296 opts.get('date') or old.date(),
1297 match,
1297 match,
1298 editor=editor,
1298 editor=editor,
1299 extra=extra)
1299 extra=extra)
1300
1300
1301 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1301 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1302 if node == old.node():
1302 if node == old.node():
1303 ui.status(_("nothing changed\n"))
1303 ui.status(_("nothing changed\n"))
1304 return 1
1304 return 1
1305 else:
1305 else:
1306 e = cmdutil.commiteditor
1306 e = cmdutil.commiteditor
1307 if opts.get('force_editor'):
1307 if opts.get('force_editor'):
1308 e = cmdutil.commitforceeditor
1308 e = cmdutil.commitforceeditor
1309
1309
1310 def commitfunc(ui, repo, message, match, opts):
1310 def commitfunc(ui, repo, message, match, opts):
1311 return repo.commit(message, opts.get('user'), opts.get('date'),
1311 return repo.commit(message, opts.get('user'), opts.get('date'),
1312 match, editor=e, extra=extra)
1312 match, editor=e, extra=extra)
1313
1313
1314 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1314 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1315
1315
1316 if not node:
1316 if not node:
1317 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1317 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1318 if stat[3]:
1318 if stat[3]:
1319 ui.status(_("nothing changed (%d missing files, see "
1319 ui.status(_("nothing changed (%d missing files, see "
1320 "'hg status')\n") % len(stat[3]))
1320 "'hg status')\n") % len(stat[3]))
1321 else:
1321 else:
1322 ui.status(_("nothing changed\n"))
1322 ui.status(_("nothing changed\n"))
1323 return 1
1323 return 1
1324
1324
1325 ctx = repo[node]
1325 ctx = repo[node]
1326 parents = ctx.parents()
1326 parents = ctx.parents()
1327
1327
1328 if (not opts.get('amend') and bheads and node not in bheads and not
1328 if (not opts.get('amend') and bheads and node not in bheads and not
1329 [x for x in parents if x.node() in bheads and x.branch() == branch]):
1329 [x for x in parents if x.node() in bheads and x.branch() == branch]):
1330 ui.status(_('created new head\n'))
1330 ui.status(_('created new head\n'))
1331 # The message is not printed for initial roots. For the other
1331 # The message is not printed for initial roots. For the other
1332 # changesets, it is printed in the following situations:
1332 # changesets, it is printed in the following situations:
1333 #
1333 #
1334 # Par column: for the 2 parents with ...
1334 # Par column: for the 2 parents with ...
1335 # N: null or no parent
1335 # N: null or no parent
1336 # B: parent is on another named branch
1336 # B: parent is on another named branch
1337 # C: parent is a regular non head changeset
1337 # C: parent is a regular non head changeset
1338 # H: parent was a branch head of the current branch
1338 # H: parent was a branch head of the current branch
1339 # Msg column: whether we print "created new head" message
1339 # Msg column: whether we print "created new head" message
1340 # In the following, it is assumed that there already exists some
1340 # In the following, it is assumed that there already exists some
1341 # initial branch heads of the current branch, otherwise nothing is
1341 # initial branch heads of the current branch, otherwise nothing is
1342 # printed anyway.
1342 # printed anyway.
1343 #
1343 #
1344 # Par Msg Comment
1344 # Par Msg Comment
1345 # NN y additional topo root
1345 # NN y additional topo root
1346 #
1346 #
1347 # BN y additional branch root
1347 # BN y additional branch root
1348 # CN y additional topo head
1348 # CN y additional topo head
1349 # HN n usual case
1349 # HN n usual case
1350 #
1350 #
1351 # BB y weird additional branch root
1351 # BB y weird additional branch root
1352 # CB y branch merge
1352 # CB y branch merge
1353 # HB n merge with named branch
1353 # HB n merge with named branch
1354 #
1354 #
1355 # CC y additional head from merge
1355 # CC y additional head from merge
1356 # CH n merge with a head
1356 # CH n merge with a head
1357 #
1357 #
1358 # HH n head merge: head count decreases
1358 # HH n head merge: head count decreases
1359
1359
1360 if not opts.get('close_branch'):
1360 if not opts.get('close_branch'):
1361 for r in parents:
1361 for r in parents:
1362 if r.closesbranch() and r.branch() == branch:
1362 if r.closesbranch() and r.branch() == branch:
1363 ui.status(_('reopening closed branch head %d\n') % r)
1363 ui.status(_('reopening closed branch head %d\n') % r)
1364
1364
1365 if ui.debugflag:
1365 if ui.debugflag:
1366 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1366 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1367 elif ui.verbose:
1367 elif ui.verbose:
1368 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1368 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1369
1369
1370 @command('copy|cp',
1370 @command('copy|cp',
1371 [('A', 'after', None, _('record a copy that has already occurred')),
1371 [('A', 'after', None, _('record a copy that has already occurred')),
1372 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1372 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1373 ] + walkopts + dryrunopts,
1373 ] + walkopts + dryrunopts,
1374 _('[OPTION]... [SOURCE]... DEST'))
1374 _('[OPTION]... [SOURCE]... DEST'))
1375 def copy(ui, repo, *pats, **opts):
1375 def copy(ui, repo, *pats, **opts):
1376 """mark files as copied for the next commit
1376 """mark files as copied for the next commit
1377
1377
1378 Mark dest as having copies of source files. If dest is a
1378 Mark dest as having copies of source files. If dest is a
1379 directory, copies are put in that directory. If dest is a file,
1379 directory, copies are put in that directory. If dest is a file,
1380 the source must be a single file.
1380 the source must be a single file.
1381
1381
1382 By default, this command copies the contents of files as they
1382 By default, this command copies the contents of files as they
1383 exist in the working directory. If invoked with -A/--after, the
1383 exist in the working directory. If invoked with -A/--after, the
1384 operation is recorded, but no copying is performed.
1384 operation is recorded, but no copying is performed.
1385
1385
1386 This command takes effect with the next commit. To undo a copy
1386 This command takes effect with the next commit. To undo a copy
1387 before that, see :hg:`revert`.
1387 before that, see :hg:`revert`.
1388
1388
1389 Returns 0 on success, 1 if errors are encountered.
1389 Returns 0 on success, 1 if errors are encountered.
1390 """
1390 """
1391 wlock = repo.wlock(False)
1391 wlock = repo.wlock(False)
1392 try:
1392 try:
1393 return cmdutil.copy(ui, repo, pats, opts)
1393 return cmdutil.copy(ui, repo, pats, opts)
1394 finally:
1394 finally:
1395 wlock.release()
1395 wlock.release()
1396
1396
1397 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1397 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1398 def debugancestor(ui, repo, *args):
1398 def debugancestor(ui, repo, *args):
1399 """find the ancestor revision of two revisions in a given index"""
1399 """find the ancestor revision of two revisions in a given index"""
1400 if len(args) == 3:
1400 if len(args) == 3:
1401 index, rev1, rev2 = args
1401 index, rev1, rev2 = args
1402 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1402 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1403 lookup = r.lookup
1403 lookup = r.lookup
1404 elif len(args) == 2:
1404 elif len(args) == 2:
1405 if not repo:
1405 if not repo:
1406 raise util.Abort(_("there is no Mercurial repository here "
1406 raise util.Abort(_("there is no Mercurial repository here "
1407 "(.hg not found)"))
1407 "(.hg not found)"))
1408 rev1, rev2 = args
1408 rev1, rev2 = args
1409 r = repo.changelog
1409 r = repo.changelog
1410 lookup = repo.lookup
1410 lookup = repo.lookup
1411 else:
1411 else:
1412 raise util.Abort(_('either two or three arguments required'))
1412 raise util.Abort(_('either two or three arguments required'))
1413 a = r.ancestor(lookup(rev1), lookup(rev2))
1413 a = r.ancestor(lookup(rev1), lookup(rev2))
1414 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1414 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1415
1415
1416 @command('debugbuilddag',
1416 @command('debugbuilddag',
1417 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1417 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1418 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1418 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1419 ('n', 'new-file', None, _('add new file at each rev'))],
1419 ('n', 'new-file', None, _('add new file at each rev'))],
1420 _('[OPTION]... [TEXT]'))
1420 _('[OPTION]... [TEXT]'))
1421 def debugbuilddag(ui, repo, text=None,
1421 def debugbuilddag(ui, repo, text=None,
1422 mergeable_file=False,
1422 mergeable_file=False,
1423 overwritten_file=False,
1423 overwritten_file=False,
1424 new_file=False):
1424 new_file=False):
1425 """builds a repo with a given DAG from scratch in the current empty repo
1425 """builds a repo with a given DAG from scratch in the current empty repo
1426
1426
1427 The description of the DAG is read from stdin if not given on the
1427 The description of the DAG is read from stdin if not given on the
1428 command line.
1428 command line.
1429
1429
1430 Elements:
1430 Elements:
1431
1431
1432 - "+n" is a linear run of n nodes based on the current default parent
1432 - "+n" is a linear run of n nodes based on the current default parent
1433 - "." is a single node based on the current default parent
1433 - "." is a single node based on the current default parent
1434 - "$" resets the default parent to null (implied at the start);
1434 - "$" resets the default parent to null (implied at the start);
1435 otherwise the default parent is always the last node created
1435 otherwise the default parent is always the last node created
1436 - "<p" sets the default parent to the backref p
1436 - "<p" sets the default parent to the backref p
1437 - "*p" is a fork at parent p, which is a backref
1437 - "*p" is a fork at parent p, which is a backref
1438 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1438 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1439 - "/p2" is a merge of the preceding node and p2
1439 - "/p2" is a merge of the preceding node and p2
1440 - ":tag" defines a local tag for the preceding node
1440 - ":tag" defines a local tag for the preceding node
1441 - "@branch" sets the named branch for subsequent nodes
1441 - "@branch" sets the named branch for subsequent nodes
1442 - "#...\\n" is a comment up to the end of the line
1442 - "#...\\n" is a comment up to the end of the line
1443
1443
1444 Whitespace between the above elements is ignored.
1444 Whitespace between the above elements is ignored.
1445
1445
1446 A backref is either
1446 A backref is either
1447
1447
1448 - a number n, which references the node curr-n, where curr is the current
1448 - a number n, which references the node curr-n, where curr is the current
1449 node, or
1449 node, or
1450 - the name of a local tag you placed earlier using ":tag", or
1450 - the name of a local tag you placed earlier using ":tag", or
1451 - empty to denote the default parent.
1451 - empty to denote the default parent.
1452
1452
1453 All string valued-elements are either strictly alphanumeric, or must
1453 All string valued-elements are either strictly alphanumeric, or must
1454 be enclosed in double quotes ("..."), with "\\" as escape character.
1454 be enclosed in double quotes ("..."), with "\\" as escape character.
1455 """
1455 """
1456
1456
1457 if text is None:
1457 if text is None:
1458 ui.status(_("reading DAG from stdin\n"))
1458 ui.status(_("reading DAG from stdin\n"))
1459 text = ui.fin.read()
1459 text = ui.fin.read()
1460
1460
1461 cl = repo.changelog
1461 cl = repo.changelog
1462 if len(cl) > 0:
1462 if len(cl) > 0:
1463 raise util.Abort(_('repository is not empty'))
1463 raise util.Abort(_('repository is not empty'))
1464
1464
1465 # determine number of revs in DAG
1465 # determine number of revs in DAG
1466 total = 0
1466 total = 0
1467 for type, data in dagparser.parsedag(text):
1467 for type, data in dagparser.parsedag(text):
1468 if type == 'n':
1468 if type == 'n':
1469 total += 1
1469 total += 1
1470
1470
1471 if mergeable_file:
1471 if mergeable_file:
1472 linesperrev = 2
1472 linesperrev = 2
1473 # make a file with k lines per rev
1473 # make a file with k lines per rev
1474 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1474 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1475 initialmergedlines.append("")
1475 initialmergedlines.append("")
1476
1476
1477 tags = []
1477 tags = []
1478
1478
1479 lock = tr = None
1479 lock = tr = None
1480 try:
1480 try:
1481 lock = repo.lock()
1481 lock = repo.lock()
1482 tr = repo.transaction("builddag")
1482 tr = repo.transaction("builddag")
1483
1483
1484 at = -1
1484 at = -1
1485 atbranch = 'default'
1485 atbranch = 'default'
1486 nodeids = []
1486 nodeids = []
1487 id = 0
1487 id = 0
1488 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1488 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1489 for type, data in dagparser.parsedag(text):
1489 for type, data in dagparser.parsedag(text):
1490 if type == 'n':
1490 if type == 'n':
1491 ui.note('node %s\n' % str(data))
1491 ui.note('node %s\n' % str(data))
1492 id, ps = data
1492 id, ps = data
1493
1493
1494 files = []
1494 files = []
1495 fctxs = {}
1495 fctxs = {}
1496
1496
1497 p2 = None
1497 p2 = None
1498 if mergeable_file:
1498 if mergeable_file:
1499 fn = "mf"
1499 fn = "mf"
1500 p1 = repo[ps[0]]
1500 p1 = repo[ps[0]]
1501 if len(ps) > 1:
1501 if len(ps) > 1:
1502 p2 = repo[ps[1]]
1502 p2 = repo[ps[1]]
1503 pa = p1.ancestor(p2)
1503 pa = p1.ancestor(p2)
1504 base, local, other = [x[fn].data() for x in pa, p1, p2]
1504 base, local, other = [x[fn].data() for x in pa, p1, p2]
1505 m3 = simplemerge.Merge3Text(base, local, other)
1505 m3 = simplemerge.Merge3Text(base, local, other)
1506 ml = [l.strip() for l in m3.merge_lines()]
1506 ml = [l.strip() for l in m3.merge_lines()]
1507 ml.append("")
1507 ml.append("")
1508 elif at > 0:
1508 elif at > 0:
1509 ml = p1[fn].data().split("\n")
1509 ml = p1[fn].data().split("\n")
1510 else:
1510 else:
1511 ml = initialmergedlines
1511 ml = initialmergedlines
1512 ml[id * linesperrev] += " r%i" % id
1512 ml[id * linesperrev] += " r%i" % id
1513 mergedtext = "\n".join(ml)
1513 mergedtext = "\n".join(ml)
1514 files.append(fn)
1514 files.append(fn)
1515 fctxs[fn] = context.memfilectx(fn, mergedtext)
1515 fctxs[fn] = context.memfilectx(fn, mergedtext)
1516
1516
1517 if overwritten_file:
1517 if overwritten_file:
1518 fn = "of"
1518 fn = "of"
1519 files.append(fn)
1519 files.append(fn)
1520 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1520 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1521
1521
1522 if new_file:
1522 if new_file:
1523 fn = "nf%i" % id
1523 fn = "nf%i" % id
1524 files.append(fn)
1524 files.append(fn)
1525 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1525 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1526 if len(ps) > 1:
1526 if len(ps) > 1:
1527 if not p2:
1527 if not p2:
1528 p2 = repo[ps[1]]
1528 p2 = repo[ps[1]]
1529 for fn in p2:
1529 for fn in p2:
1530 if fn.startswith("nf"):
1530 if fn.startswith("nf"):
1531 files.append(fn)
1531 files.append(fn)
1532 fctxs[fn] = p2[fn]
1532 fctxs[fn] = p2[fn]
1533
1533
1534 def fctxfn(repo, cx, path):
1534 def fctxfn(repo, cx, path):
1535 return fctxs.get(path)
1535 return fctxs.get(path)
1536
1536
1537 if len(ps) == 0 or ps[0] < 0:
1537 if len(ps) == 0 or ps[0] < 0:
1538 pars = [None, None]
1538 pars = [None, None]
1539 elif len(ps) == 1:
1539 elif len(ps) == 1:
1540 pars = [nodeids[ps[0]], None]
1540 pars = [nodeids[ps[0]], None]
1541 else:
1541 else:
1542 pars = [nodeids[p] for p in ps]
1542 pars = [nodeids[p] for p in ps]
1543 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1543 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1544 date=(id, 0),
1544 date=(id, 0),
1545 user="debugbuilddag",
1545 user="debugbuilddag",
1546 extra={'branch': atbranch})
1546 extra={'branch': atbranch})
1547 nodeid = repo.commitctx(cx)
1547 nodeid = repo.commitctx(cx)
1548 nodeids.append(nodeid)
1548 nodeids.append(nodeid)
1549 at = id
1549 at = id
1550 elif type == 'l':
1550 elif type == 'l':
1551 id, name = data
1551 id, name = data
1552 ui.note('tag %s\n' % name)
1552 ui.note('tag %s\n' % name)
1553 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1553 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1554 elif type == 'a':
1554 elif type == 'a':
1555 ui.note('branch %s\n' % data)
1555 ui.note('branch %s\n' % data)
1556 atbranch = data
1556 atbranch = data
1557 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1557 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1558 tr.close()
1558 tr.close()
1559
1559
1560 if tags:
1560 if tags:
1561 repo.opener.write("localtags", "".join(tags))
1561 repo.opener.write("localtags", "".join(tags))
1562 finally:
1562 finally:
1563 ui.progress(_('building'), None)
1563 ui.progress(_('building'), None)
1564 release(tr, lock)
1564 release(tr, lock)
1565
1565
1566 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1566 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1567 def debugbundle(ui, bundlepath, all=None, **opts):
1567 def debugbundle(ui, bundlepath, all=None, **opts):
1568 """lists the contents of a bundle"""
1568 """lists the contents of a bundle"""
1569 f = url.open(ui, bundlepath)
1569 f = url.open(ui, bundlepath)
1570 try:
1570 try:
1571 gen = changegroup.readbundle(f, bundlepath)
1571 gen = changegroup.readbundle(f, bundlepath)
1572 if all:
1572 if all:
1573 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1573 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1574
1574
1575 def showchunks(named):
1575 def showchunks(named):
1576 ui.write("\n%s\n" % named)
1576 ui.write("\n%s\n" % named)
1577 chain = None
1577 chain = None
1578 while True:
1578 while True:
1579 chunkdata = gen.deltachunk(chain)
1579 chunkdata = gen.deltachunk(chain)
1580 if not chunkdata:
1580 if not chunkdata:
1581 break
1581 break
1582 node = chunkdata['node']
1582 node = chunkdata['node']
1583 p1 = chunkdata['p1']
1583 p1 = chunkdata['p1']
1584 p2 = chunkdata['p2']
1584 p2 = chunkdata['p2']
1585 cs = chunkdata['cs']
1585 cs = chunkdata['cs']
1586 deltabase = chunkdata['deltabase']
1586 deltabase = chunkdata['deltabase']
1587 delta = chunkdata['delta']
1587 delta = chunkdata['delta']
1588 ui.write("%s %s %s %s %s %s\n" %
1588 ui.write("%s %s %s %s %s %s\n" %
1589 (hex(node), hex(p1), hex(p2),
1589 (hex(node), hex(p1), hex(p2),
1590 hex(cs), hex(deltabase), len(delta)))
1590 hex(cs), hex(deltabase), len(delta)))
1591 chain = node
1591 chain = node
1592
1592
1593 chunkdata = gen.changelogheader()
1593 chunkdata = gen.changelogheader()
1594 showchunks("changelog")
1594 showchunks("changelog")
1595 chunkdata = gen.manifestheader()
1595 chunkdata = gen.manifestheader()
1596 showchunks("manifest")
1596 showchunks("manifest")
1597 while True:
1597 while True:
1598 chunkdata = gen.filelogheader()
1598 chunkdata = gen.filelogheader()
1599 if not chunkdata:
1599 if not chunkdata:
1600 break
1600 break
1601 fname = chunkdata['filename']
1601 fname = chunkdata['filename']
1602 showchunks(fname)
1602 showchunks(fname)
1603 else:
1603 else:
1604 chunkdata = gen.changelogheader()
1604 chunkdata = gen.changelogheader()
1605 chain = None
1605 chain = None
1606 while True:
1606 while True:
1607 chunkdata = gen.deltachunk(chain)
1607 chunkdata = gen.deltachunk(chain)
1608 if not chunkdata:
1608 if not chunkdata:
1609 break
1609 break
1610 node = chunkdata['node']
1610 node = chunkdata['node']
1611 ui.write("%s\n" % hex(node))
1611 ui.write("%s\n" % hex(node))
1612 chain = node
1612 chain = node
1613 finally:
1613 finally:
1614 f.close()
1614 f.close()
1615
1615
1616 @command('debugcheckstate', [], '')
1616 @command('debugcheckstate', [], '')
1617 def debugcheckstate(ui, repo):
1617 def debugcheckstate(ui, repo):
1618 """validate the correctness of the current dirstate"""
1618 """validate the correctness of the current dirstate"""
1619 parent1, parent2 = repo.dirstate.parents()
1619 parent1, parent2 = repo.dirstate.parents()
1620 m1 = repo[parent1].manifest()
1620 m1 = repo[parent1].manifest()
1621 m2 = repo[parent2].manifest()
1621 m2 = repo[parent2].manifest()
1622 errors = 0
1622 errors = 0
1623 for f in repo.dirstate:
1623 for f in repo.dirstate:
1624 state = repo.dirstate[f]
1624 state = repo.dirstate[f]
1625 if state in "nr" and f not in m1:
1625 if state in "nr" and f not in m1:
1626 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1626 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1627 errors += 1
1627 errors += 1
1628 if state in "a" and f in m1:
1628 if state in "a" and f in m1:
1629 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1629 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1630 errors += 1
1630 errors += 1
1631 if state in "m" and f not in m1 and f not in m2:
1631 if state in "m" and f not in m1 and f not in m2:
1632 ui.warn(_("%s in state %s, but not in either manifest\n") %
1632 ui.warn(_("%s in state %s, but not in either manifest\n") %
1633 (f, state))
1633 (f, state))
1634 errors += 1
1634 errors += 1
1635 for f in m1:
1635 for f in m1:
1636 state = repo.dirstate[f]
1636 state = repo.dirstate[f]
1637 if state not in "nrm":
1637 if state not in "nrm":
1638 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1638 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1639 errors += 1
1639 errors += 1
1640 if errors:
1640 if errors:
1641 error = _(".hg/dirstate inconsistent with current parent's manifest")
1641 error = _(".hg/dirstate inconsistent with current parent's manifest")
1642 raise util.Abort(error)
1642 raise util.Abort(error)
1643
1643
1644 @command('debugcommands', [], _('[COMMAND]'))
1644 @command('debugcommands', [], _('[COMMAND]'))
1645 def debugcommands(ui, cmd='', *args):
1645 def debugcommands(ui, cmd='', *args):
1646 """list all available commands and options"""
1646 """list all available commands and options"""
1647 for cmd, vals in sorted(table.iteritems()):
1647 for cmd, vals in sorted(table.iteritems()):
1648 cmd = cmd.split('|')[0].strip('^')
1648 cmd = cmd.split('|')[0].strip('^')
1649 opts = ', '.join([i[1] for i in vals[1]])
1649 opts = ', '.join([i[1] for i in vals[1]])
1650 ui.write('%s: %s\n' % (cmd, opts))
1650 ui.write('%s: %s\n' % (cmd, opts))
1651
1651
1652 @command('debugcomplete',
1652 @command('debugcomplete',
1653 [('o', 'options', None, _('show the command options'))],
1653 [('o', 'options', None, _('show the command options'))],
1654 _('[-o] CMD'))
1654 _('[-o] CMD'))
1655 def debugcomplete(ui, cmd='', **opts):
1655 def debugcomplete(ui, cmd='', **opts):
1656 """returns the completion list associated with the given command"""
1656 """returns the completion list associated with the given command"""
1657
1657
1658 if opts.get('options'):
1658 if opts.get('options'):
1659 options = []
1659 options = []
1660 otables = [globalopts]
1660 otables = [globalopts]
1661 if cmd:
1661 if cmd:
1662 aliases, entry = cmdutil.findcmd(cmd, table, False)
1662 aliases, entry = cmdutil.findcmd(cmd, table, False)
1663 otables.append(entry[1])
1663 otables.append(entry[1])
1664 for t in otables:
1664 for t in otables:
1665 for o in t:
1665 for o in t:
1666 if "(DEPRECATED)" in o[3]:
1666 if "(DEPRECATED)" in o[3]:
1667 continue
1667 continue
1668 if o[0]:
1668 if o[0]:
1669 options.append('-%s' % o[0])
1669 options.append('-%s' % o[0])
1670 options.append('--%s' % o[1])
1670 options.append('--%s' % o[1])
1671 ui.write("%s\n" % "\n".join(options))
1671 ui.write("%s\n" % "\n".join(options))
1672 return
1672 return
1673
1673
1674 cmdlist = cmdutil.findpossible(cmd, table)
1674 cmdlist = cmdutil.findpossible(cmd, table)
1675 if ui.verbose:
1675 if ui.verbose:
1676 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1676 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1677 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1677 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1678
1678
1679 @command('debugdag',
1679 @command('debugdag',
1680 [('t', 'tags', None, _('use tags as labels')),
1680 [('t', 'tags', None, _('use tags as labels')),
1681 ('b', 'branches', None, _('annotate with branch names')),
1681 ('b', 'branches', None, _('annotate with branch names')),
1682 ('', 'dots', None, _('use dots for runs')),
1682 ('', 'dots', None, _('use dots for runs')),
1683 ('s', 'spaces', None, _('separate elements by spaces'))],
1683 ('s', 'spaces', None, _('separate elements by spaces'))],
1684 _('[OPTION]... [FILE [REV]...]'))
1684 _('[OPTION]... [FILE [REV]...]'))
1685 def debugdag(ui, repo, file_=None, *revs, **opts):
1685 def debugdag(ui, repo, file_=None, *revs, **opts):
1686 """format the changelog or an index DAG as a concise textual description
1686 """format the changelog or an index DAG as a concise textual description
1687
1687
1688 If you pass a revlog index, the revlog's DAG is emitted. If you list
1688 If you pass a revlog index, the revlog's DAG is emitted. If you list
1689 revision numbers, they get labelled in the output as rN.
1689 revision numbers, they get labelled in the output as rN.
1690
1690
1691 Otherwise, the changelog DAG of the current repo is emitted.
1691 Otherwise, the changelog DAG of the current repo is emitted.
1692 """
1692 """
1693 spaces = opts.get('spaces')
1693 spaces = opts.get('spaces')
1694 dots = opts.get('dots')
1694 dots = opts.get('dots')
1695 if file_:
1695 if file_:
1696 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1696 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1697 revs = set((int(r) for r in revs))
1697 revs = set((int(r) for r in revs))
1698 def events():
1698 def events():
1699 for r in rlog:
1699 for r in rlog:
1700 yield 'n', (r, list(set(p for p in rlog.parentrevs(r)
1700 yield 'n', (r, list(set(p for p in rlog.parentrevs(r)
1701 if p != -1)))
1701 if p != -1)))
1702 if r in revs:
1702 if r in revs:
1703 yield 'l', (r, "r%i" % r)
1703 yield 'l', (r, "r%i" % r)
1704 elif repo:
1704 elif repo:
1705 cl = repo.changelog
1705 cl = repo.changelog
1706 tags = opts.get('tags')
1706 tags = opts.get('tags')
1707 branches = opts.get('branches')
1707 branches = opts.get('branches')
1708 if tags:
1708 if tags:
1709 labels = {}
1709 labels = {}
1710 for l, n in repo.tags().items():
1710 for l, n in repo.tags().items():
1711 labels.setdefault(cl.rev(n), []).append(l)
1711 labels.setdefault(cl.rev(n), []).append(l)
1712 def events():
1712 def events():
1713 b = "default"
1713 b = "default"
1714 for r in cl:
1714 for r in cl:
1715 if branches:
1715 if branches:
1716 newb = cl.read(cl.node(r))[5]['branch']
1716 newb = cl.read(cl.node(r))[5]['branch']
1717 if newb != b:
1717 if newb != b:
1718 yield 'a', newb
1718 yield 'a', newb
1719 b = newb
1719 b = newb
1720 yield 'n', (r, list(set(p for p in cl.parentrevs(r)
1720 yield 'n', (r, list(set(p for p in cl.parentrevs(r)
1721 if p != -1)))
1721 if p != -1)))
1722 if tags:
1722 if tags:
1723 ls = labels.get(r)
1723 ls = labels.get(r)
1724 if ls:
1724 if ls:
1725 for l in ls:
1725 for l in ls:
1726 yield 'l', (r, l)
1726 yield 'l', (r, l)
1727 else:
1727 else:
1728 raise util.Abort(_('need repo for changelog dag'))
1728 raise util.Abort(_('need repo for changelog dag'))
1729
1729
1730 for line in dagparser.dagtextlines(events(),
1730 for line in dagparser.dagtextlines(events(),
1731 addspaces=spaces,
1731 addspaces=spaces,
1732 wraplabels=True,
1732 wraplabels=True,
1733 wrapannotations=True,
1733 wrapannotations=True,
1734 wrapnonlinear=dots,
1734 wrapnonlinear=dots,
1735 usedots=dots,
1735 usedots=dots,
1736 maxlinewidth=70):
1736 maxlinewidth=70):
1737 ui.write(line)
1737 ui.write(line)
1738 ui.write("\n")
1738 ui.write("\n")
1739
1739
1740 @command('debugdata',
1740 @command('debugdata',
1741 [('c', 'changelog', False, _('open changelog')),
1741 [('c', 'changelog', False, _('open changelog')),
1742 ('m', 'manifest', False, _('open manifest'))],
1742 ('m', 'manifest', False, _('open manifest'))],
1743 _('-c|-m|FILE REV'))
1743 _('-c|-m|FILE REV'))
1744 def debugdata(ui, repo, file_, rev = None, **opts):
1744 def debugdata(ui, repo, file_, rev = None, **opts):
1745 """dump the contents of a data file revision"""
1745 """dump the contents of a data file revision"""
1746 if opts.get('changelog') or opts.get('manifest'):
1746 if opts.get('changelog') or opts.get('manifest'):
1747 file_, rev = None, file_
1747 file_, rev = None, file_
1748 elif rev is None:
1748 elif rev is None:
1749 raise error.CommandError('debugdata', _('invalid arguments'))
1749 raise error.CommandError('debugdata', _('invalid arguments'))
1750 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1750 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1751 try:
1751 try:
1752 ui.write(r.revision(r.lookup(rev)))
1752 ui.write(r.revision(r.lookup(rev)))
1753 except KeyError:
1753 except KeyError:
1754 raise util.Abort(_('invalid revision identifier %s') % rev)
1754 raise util.Abort(_('invalid revision identifier %s') % rev)
1755
1755
1756 @command('debugdate',
1756 @command('debugdate',
1757 [('e', 'extended', None, _('try extended date formats'))],
1757 [('e', 'extended', None, _('try extended date formats'))],
1758 _('[-e] DATE [RANGE]'))
1758 _('[-e] DATE [RANGE]'))
1759 def debugdate(ui, date, range=None, **opts):
1759 def debugdate(ui, date, range=None, **opts):
1760 """parse and display a date"""
1760 """parse and display a date"""
1761 if opts["extended"]:
1761 if opts["extended"]:
1762 d = util.parsedate(date, util.extendeddateformats)
1762 d = util.parsedate(date, util.extendeddateformats)
1763 else:
1763 else:
1764 d = util.parsedate(date)
1764 d = util.parsedate(date)
1765 ui.write("internal: %s %s\n" % d)
1765 ui.write("internal: %s %s\n" % d)
1766 ui.write("standard: %s\n" % util.datestr(d))
1766 ui.write("standard: %s\n" % util.datestr(d))
1767 if range:
1767 if range:
1768 m = util.matchdate(range)
1768 m = util.matchdate(range)
1769 ui.write("match: %s\n" % m(d[0]))
1769 ui.write("match: %s\n" % m(d[0]))
1770
1770
1771 @command('debugdiscovery',
1771 @command('debugdiscovery',
1772 [('', 'old', None, _('use old-style discovery')),
1772 [('', 'old', None, _('use old-style discovery')),
1773 ('', 'nonheads', None,
1773 ('', 'nonheads', None,
1774 _('use old-style discovery with non-heads included')),
1774 _('use old-style discovery with non-heads included')),
1775 ] + remoteopts,
1775 ] + remoteopts,
1776 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1776 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1777 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1777 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1778 """runs the changeset discovery protocol in isolation"""
1778 """runs the changeset discovery protocol in isolation"""
1779 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
1779 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
1780 opts.get('branch'))
1780 opts.get('branch'))
1781 remote = hg.peer(repo, opts, remoteurl)
1781 remote = hg.peer(repo, opts, remoteurl)
1782 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1782 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1783
1783
1784 # make sure tests are repeatable
1784 # make sure tests are repeatable
1785 random.seed(12323)
1785 random.seed(12323)
1786
1786
1787 def doit(localheads, remoteheads):
1787 def doit(localheads, remoteheads, remote=remote):
1788 if opts.get('old'):
1788 if opts.get('old'):
1789 if localheads:
1789 if localheads:
1790 raise util.Abort('cannot use localheads with old style '
1790 raise util.Abort('cannot use localheads with old style '
1791 'discovery')
1791 'discovery')
1792 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1792 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1793 force=True)
1793 force=True)
1794 common = set(common)
1794 common = set(common)
1795 if not opts.get('nonheads'):
1795 if not opts.get('nonheads'):
1796 ui.write("unpruned common: %s\n" % " ".join([short(n)
1796 ui.write("unpruned common: %s\n" % " ".join([short(n)
1797 for n in common]))
1797 for n in common]))
1798 dag = dagutil.revlogdag(repo.changelog)
1798 dag = dagutil.revlogdag(repo.changelog)
1799 all = dag.ancestorset(dag.internalizeall(common))
1799 all = dag.ancestorset(dag.internalizeall(common))
1800 common = dag.externalizeall(dag.headsetofconnecteds(all))
1800 common = dag.externalizeall(dag.headsetofconnecteds(all))
1801 else:
1801 else:
1802 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1802 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1803 common = set(common)
1803 common = set(common)
1804 rheads = set(hds)
1804 rheads = set(hds)
1805 lheads = set(repo.heads())
1805 lheads = set(repo.heads())
1806 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1806 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1807 if lheads <= common:
1807 if lheads <= common:
1808 ui.write("local is subset\n")
1808 ui.write("local is subset\n")
1809 elif rheads <= common:
1809 elif rheads <= common:
1810 ui.write("remote is subset\n")
1810 ui.write("remote is subset\n")
1811
1811
1812 serverlogs = opts.get('serverlog')
1812 serverlogs = opts.get('serverlog')
1813 if serverlogs:
1813 if serverlogs:
1814 for filename in serverlogs:
1814 for filename in serverlogs:
1815 logfile = open(filename, 'r')
1815 logfile = open(filename, 'r')
1816 try:
1816 try:
1817 line = logfile.readline()
1817 line = logfile.readline()
1818 while line:
1818 while line:
1819 parts = line.strip().split(';')
1819 parts = line.strip().split(';')
1820 op = parts[1]
1820 op = parts[1]
1821 if op == 'cg':
1821 if op == 'cg':
1822 pass
1822 pass
1823 elif op == 'cgss':
1823 elif op == 'cgss':
1824 doit(parts[2].split(' '), parts[3].split(' '))
1824 doit(parts[2].split(' '), parts[3].split(' '))
1825 elif op == 'unb':
1825 elif op == 'unb':
1826 doit(parts[3].split(' '), parts[2].split(' '))
1826 doit(parts[3].split(' '), parts[2].split(' '))
1827 line = logfile.readline()
1827 line = logfile.readline()
1828 finally:
1828 finally:
1829 logfile.close()
1829 logfile.close()
1830
1830
1831 else:
1831 else:
1832 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1832 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1833 opts.get('remote_head'))
1833 opts.get('remote_head'))
1834 localrevs = opts.get('local_head')
1834 localrevs = opts.get('local_head')
1835 doit(localrevs, remoterevs)
1835 doit(localrevs, remoterevs)
1836
1836
1837 @command('debugfileset', [], ('REVSPEC'))
1837 @command('debugfileset', [], ('REVSPEC'))
1838 def debugfileset(ui, repo, expr):
1838 def debugfileset(ui, repo, expr):
1839 '''parse and apply a fileset specification'''
1839 '''parse and apply a fileset specification'''
1840 if ui.verbose:
1840 if ui.verbose:
1841 tree = fileset.parse(expr)[0]
1841 tree = fileset.parse(expr)[0]
1842 ui.note(tree, "\n")
1842 ui.note(tree, "\n")
1843
1843
1844 for f in fileset.getfileset(repo[None], expr):
1844 for f in fileset.getfileset(repo[None], expr):
1845 ui.write("%s\n" % f)
1845 ui.write("%s\n" % f)
1846
1846
1847 @command('debugfsinfo', [], _('[PATH]'))
1847 @command('debugfsinfo', [], _('[PATH]'))
1848 def debugfsinfo(ui, path = "."):
1848 def debugfsinfo(ui, path = "."):
1849 """show information detected about current filesystem"""
1849 """show information detected about current filesystem"""
1850 util.writefile('.debugfsinfo', '')
1850 util.writefile('.debugfsinfo', '')
1851 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1851 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1852 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1852 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1853 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1853 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1854 and 'yes' or 'no'))
1854 and 'yes' or 'no'))
1855 os.unlink('.debugfsinfo')
1855 os.unlink('.debugfsinfo')
1856
1856
1857 @command('debuggetbundle',
1857 @command('debuggetbundle',
1858 [('H', 'head', [], _('id of head node'), _('ID')),
1858 [('H', 'head', [], _('id of head node'), _('ID')),
1859 ('C', 'common', [], _('id of common node'), _('ID')),
1859 ('C', 'common', [], _('id of common node'), _('ID')),
1860 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1860 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1861 _('REPO FILE [-H|-C ID]...'))
1861 _('REPO FILE [-H|-C ID]...'))
1862 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1862 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1863 """retrieves a bundle from a repo
1863 """retrieves a bundle from a repo
1864
1864
1865 Every ID must be a full-length hex node id string. Saves the bundle to the
1865 Every ID must be a full-length hex node id string. Saves the bundle to the
1866 given file.
1866 given file.
1867 """
1867 """
1868 repo = hg.peer(ui, opts, repopath)
1868 repo = hg.peer(ui, opts, repopath)
1869 if not repo.capable('getbundle'):
1869 if not repo.capable('getbundle'):
1870 raise util.Abort("getbundle() not supported by target repository")
1870 raise util.Abort("getbundle() not supported by target repository")
1871 args = {}
1871 args = {}
1872 if common:
1872 if common:
1873 args['common'] = [bin(s) for s in common]
1873 args['common'] = [bin(s) for s in common]
1874 if head:
1874 if head:
1875 args['heads'] = [bin(s) for s in head]
1875 args['heads'] = [bin(s) for s in head]
1876 bundle = repo.getbundle('debug', **args)
1876 bundle = repo.getbundle('debug', **args)
1877
1877
1878 bundletype = opts.get('type', 'bzip2').lower()
1878 bundletype = opts.get('type', 'bzip2').lower()
1879 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1879 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1880 bundletype = btypes.get(bundletype)
1880 bundletype = btypes.get(bundletype)
1881 if bundletype not in changegroup.bundletypes:
1881 if bundletype not in changegroup.bundletypes:
1882 raise util.Abort(_('unknown bundle type specified with --type'))
1882 raise util.Abort(_('unknown bundle type specified with --type'))
1883 changegroup.writebundle(bundle, bundlepath, bundletype)
1883 changegroup.writebundle(bundle, bundlepath, bundletype)
1884
1884
1885 @command('debugignore', [], '')
1885 @command('debugignore', [], '')
1886 def debugignore(ui, repo, *values, **opts):
1886 def debugignore(ui, repo, *values, **opts):
1887 """display the combined ignore pattern"""
1887 """display the combined ignore pattern"""
1888 ignore = repo.dirstate._ignore
1888 ignore = repo.dirstate._ignore
1889 includepat = getattr(ignore, 'includepat', None)
1889 includepat = getattr(ignore, 'includepat', None)
1890 if includepat is not None:
1890 if includepat is not None:
1891 ui.write("%s\n" % includepat)
1891 ui.write("%s\n" % includepat)
1892 else:
1892 else:
1893 raise util.Abort(_("no ignore patterns found"))
1893 raise util.Abort(_("no ignore patterns found"))
1894
1894
1895 @command('debugindex',
1895 @command('debugindex',
1896 [('c', 'changelog', False, _('open changelog')),
1896 [('c', 'changelog', False, _('open changelog')),
1897 ('m', 'manifest', False, _('open manifest')),
1897 ('m', 'manifest', False, _('open manifest')),
1898 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1898 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1899 _('[-f FORMAT] -c|-m|FILE'))
1899 _('[-f FORMAT] -c|-m|FILE'))
1900 def debugindex(ui, repo, file_ = None, **opts):
1900 def debugindex(ui, repo, file_ = None, **opts):
1901 """dump the contents of an index file"""
1901 """dump the contents of an index file"""
1902 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1902 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1903 format = opts.get('format', 0)
1903 format = opts.get('format', 0)
1904 if format not in (0, 1):
1904 if format not in (0, 1):
1905 raise util.Abort(_("unknown format %d") % format)
1905 raise util.Abort(_("unknown format %d") % format)
1906
1906
1907 generaldelta = r.version & revlog.REVLOGGENERALDELTA
1907 generaldelta = r.version & revlog.REVLOGGENERALDELTA
1908 if generaldelta:
1908 if generaldelta:
1909 basehdr = ' delta'
1909 basehdr = ' delta'
1910 else:
1910 else:
1911 basehdr = ' base'
1911 basehdr = ' base'
1912
1912
1913 if format == 0:
1913 if format == 0:
1914 ui.write(" rev offset length " + basehdr + " linkrev"
1914 ui.write(" rev offset length " + basehdr + " linkrev"
1915 " nodeid p1 p2\n")
1915 " nodeid p1 p2\n")
1916 elif format == 1:
1916 elif format == 1:
1917 ui.write(" rev flag offset length"
1917 ui.write(" rev flag offset length"
1918 " size " + basehdr + " link p1 p2"
1918 " size " + basehdr + " link p1 p2"
1919 " nodeid\n")
1919 " nodeid\n")
1920
1920
1921 for i in r:
1921 for i in r:
1922 node = r.node(i)
1922 node = r.node(i)
1923 if generaldelta:
1923 if generaldelta:
1924 base = r.deltaparent(i)
1924 base = r.deltaparent(i)
1925 else:
1925 else:
1926 base = r.chainbase(i)
1926 base = r.chainbase(i)
1927 if format == 0:
1927 if format == 0:
1928 try:
1928 try:
1929 pp = r.parents(node)
1929 pp = r.parents(node)
1930 except Exception:
1930 except Exception:
1931 pp = [nullid, nullid]
1931 pp = [nullid, nullid]
1932 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1932 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1933 i, r.start(i), r.length(i), base, r.linkrev(i),
1933 i, r.start(i), r.length(i), base, r.linkrev(i),
1934 short(node), short(pp[0]), short(pp[1])))
1934 short(node), short(pp[0]), short(pp[1])))
1935 elif format == 1:
1935 elif format == 1:
1936 pr = r.parentrevs(i)
1936 pr = r.parentrevs(i)
1937 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1937 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1938 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1938 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1939 base, r.linkrev(i), pr[0], pr[1], short(node)))
1939 base, r.linkrev(i), pr[0], pr[1], short(node)))
1940
1940
1941 @command('debugindexdot', [], _('FILE'))
1941 @command('debugindexdot', [], _('FILE'))
1942 def debugindexdot(ui, repo, file_):
1942 def debugindexdot(ui, repo, file_):
1943 """dump an index DAG as a graphviz dot file"""
1943 """dump an index DAG as a graphviz dot file"""
1944 r = None
1944 r = None
1945 if repo:
1945 if repo:
1946 filelog = repo.file(file_)
1946 filelog = repo.file(file_)
1947 if len(filelog):
1947 if len(filelog):
1948 r = filelog
1948 r = filelog
1949 if not r:
1949 if not r:
1950 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1950 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1951 ui.write("digraph G {\n")
1951 ui.write("digraph G {\n")
1952 for i in r:
1952 for i in r:
1953 node = r.node(i)
1953 node = r.node(i)
1954 pp = r.parents(node)
1954 pp = r.parents(node)
1955 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1955 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1956 if pp[1] != nullid:
1956 if pp[1] != nullid:
1957 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1957 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1958 ui.write("}\n")
1958 ui.write("}\n")
1959
1959
1960 @command('debuginstall', [], '')
1960 @command('debuginstall', [], '')
1961 def debuginstall(ui):
1961 def debuginstall(ui):
1962 '''test Mercurial installation
1962 '''test Mercurial installation
1963
1963
1964 Returns 0 on success.
1964 Returns 0 on success.
1965 '''
1965 '''
1966
1966
1967 def writetemp(contents):
1967 def writetemp(contents):
1968 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1968 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1969 f = os.fdopen(fd, "wb")
1969 f = os.fdopen(fd, "wb")
1970 f.write(contents)
1970 f.write(contents)
1971 f.close()
1971 f.close()
1972 return name
1972 return name
1973
1973
1974 problems = 0
1974 problems = 0
1975
1975
1976 # encoding
1976 # encoding
1977 ui.status(_("checking encoding (%s)...\n") % encoding.encoding)
1977 ui.status(_("checking encoding (%s)...\n") % encoding.encoding)
1978 try:
1978 try:
1979 encoding.fromlocal("test")
1979 encoding.fromlocal("test")
1980 except util.Abort, inst:
1980 except util.Abort, inst:
1981 ui.write(" %s\n" % inst)
1981 ui.write(" %s\n" % inst)
1982 ui.write(_(" (check that your locale is properly set)\n"))
1982 ui.write(_(" (check that your locale is properly set)\n"))
1983 problems += 1
1983 problems += 1
1984
1984
1985 # compiled modules
1985 # compiled modules
1986 ui.status(_("checking installed modules (%s)...\n")
1986 ui.status(_("checking installed modules (%s)...\n")
1987 % os.path.dirname(__file__))
1987 % os.path.dirname(__file__))
1988 try:
1988 try:
1989 import bdiff, mpatch, base85, osutil
1989 import bdiff, mpatch, base85, osutil
1990 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1990 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1991 except Exception, inst:
1991 except Exception, inst:
1992 ui.write(" %s\n" % inst)
1992 ui.write(" %s\n" % inst)
1993 ui.write(_(" One or more extensions could not be found"))
1993 ui.write(_(" One or more extensions could not be found"))
1994 ui.write(_(" (check that you compiled the extensions)\n"))
1994 ui.write(_(" (check that you compiled the extensions)\n"))
1995 problems += 1
1995 problems += 1
1996
1996
1997 # templates
1997 # templates
1998 import templater
1998 import templater
1999 p = templater.templatepath()
1999 p = templater.templatepath()
2000 ui.status(_("checking templates (%s)...\n") % ' '.join(p))
2000 ui.status(_("checking templates (%s)...\n") % ' '.join(p))
2001 try:
2001 try:
2002 templater.templater(templater.templatepath("map-cmdline.default"))
2002 templater.templater(templater.templatepath("map-cmdline.default"))
2003 except Exception, inst:
2003 except Exception, inst:
2004 ui.write(" %s\n" % inst)
2004 ui.write(" %s\n" % inst)
2005 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
2005 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
2006 problems += 1
2006 problems += 1
2007
2007
2008 # editor
2008 # editor
2009 ui.status(_("checking commit editor...\n"))
2009 ui.status(_("checking commit editor...\n"))
2010 editor = ui.geteditor()
2010 editor = ui.geteditor()
2011 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
2011 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
2012 if not cmdpath:
2012 if not cmdpath:
2013 if editor == 'vi':
2013 if editor == 'vi':
2014 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
2014 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
2015 ui.write(_(" (specify a commit editor in your configuration"
2015 ui.write(_(" (specify a commit editor in your configuration"
2016 " file)\n"))
2016 " file)\n"))
2017 else:
2017 else:
2018 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
2018 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
2019 ui.write(_(" (specify a commit editor in your configuration"
2019 ui.write(_(" (specify a commit editor in your configuration"
2020 " file)\n"))
2020 " file)\n"))
2021 problems += 1
2021 problems += 1
2022
2022
2023 # check username
2023 # check username
2024 ui.status(_("checking username...\n"))
2024 ui.status(_("checking username...\n"))
2025 try:
2025 try:
2026 ui.username()
2026 ui.username()
2027 except util.Abort, e:
2027 except util.Abort, e:
2028 ui.write(" %s\n" % e)
2028 ui.write(" %s\n" % e)
2029 ui.write(_(" (specify a username in your configuration file)\n"))
2029 ui.write(_(" (specify a username in your configuration file)\n"))
2030 problems += 1
2030 problems += 1
2031
2031
2032 if not problems:
2032 if not problems:
2033 ui.status(_("no problems detected\n"))
2033 ui.status(_("no problems detected\n"))
2034 else:
2034 else:
2035 ui.write(_("%s problems detected,"
2035 ui.write(_("%s problems detected,"
2036 " please check your install!\n") % problems)
2036 " please check your install!\n") % problems)
2037
2037
2038 return problems
2038 return problems
2039
2039
2040 @command('debugknown', [], _('REPO ID...'))
2040 @command('debugknown', [], _('REPO ID...'))
2041 def debugknown(ui, repopath, *ids, **opts):
2041 def debugknown(ui, repopath, *ids, **opts):
2042 """test whether node ids are known to a repo
2042 """test whether node ids are known to a repo
2043
2043
2044 Every ID must be a full-length hex node id string. Returns a list of 0s
2044 Every ID must be a full-length hex node id string. Returns a list of 0s
2045 and 1s indicating unknown/known.
2045 and 1s indicating unknown/known.
2046 """
2046 """
2047 repo = hg.peer(ui, opts, repopath)
2047 repo = hg.peer(ui, opts, repopath)
2048 if not repo.capable('known'):
2048 if not repo.capable('known'):
2049 raise util.Abort("known() not supported by target repository")
2049 raise util.Abort("known() not supported by target repository")
2050 flags = repo.known([bin(s) for s in ids])
2050 flags = repo.known([bin(s) for s in ids])
2051 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2051 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2052
2052
2053 @command('debugobsolete', [] + commitopts2,
2053 @command('debugobsolete', [] + commitopts2,
2054 _('[OBSOLETED [REPLACEMENT] [REPL... ]'))
2054 _('[OBSOLETED [REPLACEMENT] [REPL... ]'))
2055 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2055 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2056 """create arbitrary obsolete marker"""
2056 """create arbitrary obsolete marker"""
2057 if precursor is not None:
2057 if precursor is not None:
2058 metadata = {}
2058 metadata = {}
2059 if 'date' in opts:
2059 if 'date' in opts:
2060 metadata['date'] = opts['date']
2060 metadata['date'] = opts['date']
2061 metadata['user'] = opts['user'] or ui.username()
2061 metadata['user'] = opts['user'] or ui.username()
2062 succs = tuple(bin(succ) for succ in successors)
2062 succs = tuple(bin(succ) for succ in successors)
2063 l = repo.lock()
2063 l = repo.lock()
2064 try:
2064 try:
2065 tr = repo.transaction('debugobsolete')
2065 tr = repo.transaction('debugobsolete')
2066 try:
2066 try:
2067 repo.obsstore.create(tr, bin(precursor), succs, 0, metadata)
2067 repo.obsstore.create(tr, bin(precursor), succs, 0, metadata)
2068 tr.close()
2068 tr.close()
2069 finally:
2069 finally:
2070 tr.release()
2070 tr.release()
2071 finally:
2071 finally:
2072 l.release()
2072 l.release()
2073 else:
2073 else:
2074 for m in obsolete.allmarkers(repo):
2074 for m in obsolete.allmarkers(repo):
2075 ui.write(hex(m.precnode()))
2075 ui.write(hex(m.precnode()))
2076 for repl in m.succnodes():
2076 for repl in m.succnodes():
2077 ui.write(' ')
2077 ui.write(' ')
2078 ui.write(hex(repl))
2078 ui.write(hex(repl))
2079 ui.write(' %X ' % m._data[2])
2079 ui.write(' %X ' % m._data[2])
2080 ui.write(m.metadata())
2080 ui.write(m.metadata())
2081 ui.write('\n')
2081 ui.write('\n')
2082
2082
2083 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
2083 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
2084 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2084 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2085 '''access the pushkey key/value protocol
2085 '''access the pushkey key/value protocol
2086
2086
2087 With two args, list the keys in the given namespace.
2087 With two args, list the keys in the given namespace.
2088
2088
2089 With five args, set a key to new if it currently is set to old.
2089 With five args, set a key to new if it currently is set to old.
2090 Reports success or failure.
2090 Reports success or failure.
2091 '''
2091 '''
2092
2092
2093 target = hg.peer(ui, {}, repopath)
2093 target = hg.peer(ui, {}, repopath)
2094 if keyinfo:
2094 if keyinfo:
2095 key, old, new = keyinfo
2095 key, old, new = keyinfo
2096 r = target.pushkey(namespace, key, old, new)
2096 r = target.pushkey(namespace, key, old, new)
2097 ui.status(str(r) + '\n')
2097 ui.status(str(r) + '\n')
2098 return not r
2098 return not r
2099 else:
2099 else:
2100 for k, v in target.listkeys(namespace).iteritems():
2100 for k, v in target.listkeys(namespace).iteritems():
2101 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2101 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2102 v.encode('string-escape')))
2102 v.encode('string-escape')))
2103
2103
2104 @command('debugpvec', [], _('A B'))
2104 @command('debugpvec', [], _('A B'))
2105 def debugpvec(ui, repo, a, b=None):
2105 def debugpvec(ui, repo, a, b=None):
2106 ca = scmutil.revsingle(repo, a)
2106 ca = scmutil.revsingle(repo, a)
2107 cb = scmutil.revsingle(repo, b)
2107 cb = scmutil.revsingle(repo, b)
2108 pa = pvec.ctxpvec(ca)
2108 pa = pvec.ctxpvec(ca)
2109 pb = pvec.ctxpvec(cb)
2109 pb = pvec.ctxpvec(cb)
2110 if pa == pb:
2110 if pa == pb:
2111 rel = "="
2111 rel = "="
2112 elif pa > pb:
2112 elif pa > pb:
2113 rel = ">"
2113 rel = ">"
2114 elif pa < pb:
2114 elif pa < pb:
2115 rel = "<"
2115 rel = "<"
2116 elif pa | pb:
2116 elif pa | pb:
2117 rel = "|"
2117 rel = "|"
2118 ui.write(_("a: %s\n") % pa)
2118 ui.write(_("a: %s\n") % pa)
2119 ui.write(_("b: %s\n") % pb)
2119 ui.write(_("b: %s\n") % pb)
2120 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2120 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2121 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2121 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2122 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2122 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2123 pa.distance(pb), rel))
2123 pa.distance(pb), rel))
2124
2124
2125 @command('debugrebuildstate',
2125 @command('debugrebuildstate',
2126 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
2126 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
2127 _('[-r REV] [REV]'))
2127 _('[-r REV] [REV]'))
2128 def debugrebuildstate(ui, repo, rev="tip"):
2128 def debugrebuildstate(ui, repo, rev="tip"):
2129 """rebuild the dirstate as it would look like for the given revision"""
2129 """rebuild the dirstate as it would look like for the given revision"""
2130 ctx = scmutil.revsingle(repo, rev)
2130 ctx = scmutil.revsingle(repo, rev)
2131 wlock = repo.wlock()
2131 wlock = repo.wlock()
2132 try:
2132 try:
2133 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2133 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2134 finally:
2134 finally:
2135 wlock.release()
2135 wlock.release()
2136
2136
2137 @command('debugrename',
2137 @command('debugrename',
2138 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2138 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2139 _('[-r REV] FILE'))
2139 _('[-r REV] FILE'))
2140 def debugrename(ui, repo, file1, *pats, **opts):
2140 def debugrename(ui, repo, file1, *pats, **opts):
2141 """dump rename information"""
2141 """dump rename information"""
2142
2142
2143 ctx = scmutil.revsingle(repo, opts.get('rev'))
2143 ctx = scmutil.revsingle(repo, opts.get('rev'))
2144 m = scmutil.match(ctx, (file1,) + pats, opts)
2144 m = scmutil.match(ctx, (file1,) + pats, opts)
2145 for abs in ctx.walk(m):
2145 for abs in ctx.walk(m):
2146 fctx = ctx[abs]
2146 fctx = ctx[abs]
2147 o = fctx.filelog().renamed(fctx.filenode())
2147 o = fctx.filelog().renamed(fctx.filenode())
2148 rel = m.rel(abs)
2148 rel = m.rel(abs)
2149 if o:
2149 if o:
2150 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2150 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2151 else:
2151 else:
2152 ui.write(_("%s not renamed\n") % rel)
2152 ui.write(_("%s not renamed\n") % rel)
2153
2153
2154 @command('debugrevlog',
2154 @command('debugrevlog',
2155 [('c', 'changelog', False, _('open changelog')),
2155 [('c', 'changelog', False, _('open changelog')),
2156 ('m', 'manifest', False, _('open manifest')),
2156 ('m', 'manifest', False, _('open manifest')),
2157 ('d', 'dump', False, _('dump index data'))],
2157 ('d', 'dump', False, _('dump index data'))],
2158 _('-c|-m|FILE'))
2158 _('-c|-m|FILE'))
2159 def debugrevlog(ui, repo, file_ = None, **opts):
2159 def debugrevlog(ui, repo, file_ = None, **opts):
2160 """show data and statistics about a revlog"""
2160 """show data and statistics about a revlog"""
2161 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2161 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2162
2162
2163 if opts.get("dump"):
2163 if opts.get("dump"):
2164 numrevs = len(r)
2164 numrevs = len(r)
2165 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
2165 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
2166 " rawsize totalsize compression heads\n")
2166 " rawsize totalsize compression heads\n")
2167 ts = 0
2167 ts = 0
2168 heads = set()
2168 heads = set()
2169 for rev in xrange(numrevs):
2169 for rev in xrange(numrevs):
2170 dbase = r.deltaparent(rev)
2170 dbase = r.deltaparent(rev)
2171 if dbase == -1:
2171 if dbase == -1:
2172 dbase = rev
2172 dbase = rev
2173 cbase = r.chainbase(rev)
2173 cbase = r.chainbase(rev)
2174 p1, p2 = r.parentrevs(rev)
2174 p1, p2 = r.parentrevs(rev)
2175 rs = r.rawsize(rev)
2175 rs = r.rawsize(rev)
2176 ts = ts + rs
2176 ts = ts + rs
2177 heads -= set(r.parentrevs(rev))
2177 heads -= set(r.parentrevs(rev))
2178 heads.add(rev)
2178 heads.add(rev)
2179 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
2179 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
2180 (rev, p1, p2, r.start(rev), r.end(rev),
2180 (rev, p1, p2, r.start(rev), r.end(rev),
2181 r.start(dbase), r.start(cbase),
2181 r.start(dbase), r.start(cbase),
2182 r.start(p1), r.start(p2),
2182 r.start(p1), r.start(p2),
2183 rs, ts, ts / r.end(rev), len(heads)))
2183 rs, ts, ts / r.end(rev), len(heads)))
2184 return 0
2184 return 0
2185
2185
2186 v = r.version
2186 v = r.version
2187 format = v & 0xFFFF
2187 format = v & 0xFFFF
2188 flags = []
2188 flags = []
2189 gdelta = False
2189 gdelta = False
2190 if v & revlog.REVLOGNGINLINEDATA:
2190 if v & revlog.REVLOGNGINLINEDATA:
2191 flags.append('inline')
2191 flags.append('inline')
2192 if v & revlog.REVLOGGENERALDELTA:
2192 if v & revlog.REVLOGGENERALDELTA:
2193 gdelta = True
2193 gdelta = True
2194 flags.append('generaldelta')
2194 flags.append('generaldelta')
2195 if not flags:
2195 if not flags:
2196 flags = ['(none)']
2196 flags = ['(none)']
2197
2197
2198 nummerges = 0
2198 nummerges = 0
2199 numfull = 0
2199 numfull = 0
2200 numprev = 0
2200 numprev = 0
2201 nump1 = 0
2201 nump1 = 0
2202 nump2 = 0
2202 nump2 = 0
2203 numother = 0
2203 numother = 0
2204 nump1prev = 0
2204 nump1prev = 0
2205 nump2prev = 0
2205 nump2prev = 0
2206 chainlengths = []
2206 chainlengths = []
2207
2207
2208 datasize = [None, 0, 0L]
2208 datasize = [None, 0, 0L]
2209 fullsize = [None, 0, 0L]
2209 fullsize = [None, 0, 0L]
2210 deltasize = [None, 0, 0L]
2210 deltasize = [None, 0, 0L]
2211
2211
2212 def addsize(size, l):
2212 def addsize(size, l):
2213 if l[0] is None or size < l[0]:
2213 if l[0] is None or size < l[0]:
2214 l[0] = size
2214 l[0] = size
2215 if size > l[1]:
2215 if size > l[1]:
2216 l[1] = size
2216 l[1] = size
2217 l[2] += size
2217 l[2] += size
2218
2218
2219 numrevs = len(r)
2219 numrevs = len(r)
2220 for rev in xrange(numrevs):
2220 for rev in xrange(numrevs):
2221 p1, p2 = r.parentrevs(rev)
2221 p1, p2 = r.parentrevs(rev)
2222 delta = r.deltaparent(rev)
2222 delta = r.deltaparent(rev)
2223 if format > 0:
2223 if format > 0:
2224 addsize(r.rawsize(rev), datasize)
2224 addsize(r.rawsize(rev), datasize)
2225 if p2 != nullrev:
2225 if p2 != nullrev:
2226 nummerges += 1
2226 nummerges += 1
2227 size = r.length(rev)
2227 size = r.length(rev)
2228 if delta == nullrev:
2228 if delta == nullrev:
2229 chainlengths.append(0)
2229 chainlengths.append(0)
2230 numfull += 1
2230 numfull += 1
2231 addsize(size, fullsize)
2231 addsize(size, fullsize)
2232 else:
2232 else:
2233 chainlengths.append(chainlengths[delta] + 1)
2233 chainlengths.append(chainlengths[delta] + 1)
2234 addsize(size, deltasize)
2234 addsize(size, deltasize)
2235 if delta == rev - 1:
2235 if delta == rev - 1:
2236 numprev += 1
2236 numprev += 1
2237 if delta == p1:
2237 if delta == p1:
2238 nump1prev += 1
2238 nump1prev += 1
2239 elif delta == p2:
2239 elif delta == p2:
2240 nump2prev += 1
2240 nump2prev += 1
2241 elif delta == p1:
2241 elif delta == p1:
2242 nump1 += 1
2242 nump1 += 1
2243 elif delta == p2:
2243 elif delta == p2:
2244 nump2 += 1
2244 nump2 += 1
2245 elif delta != nullrev:
2245 elif delta != nullrev:
2246 numother += 1
2246 numother += 1
2247
2247
2248 # Adjust size min value for empty cases
2248 # Adjust size min value for empty cases
2249 for size in (datasize, fullsize, deltasize):
2249 for size in (datasize, fullsize, deltasize):
2250 if size[0] is None:
2250 if size[0] is None:
2251 size[0] = 0
2251 size[0] = 0
2252
2252
2253 numdeltas = numrevs - numfull
2253 numdeltas = numrevs - numfull
2254 numoprev = numprev - nump1prev - nump2prev
2254 numoprev = numprev - nump1prev - nump2prev
2255 totalrawsize = datasize[2]
2255 totalrawsize = datasize[2]
2256 datasize[2] /= numrevs
2256 datasize[2] /= numrevs
2257 fulltotal = fullsize[2]
2257 fulltotal = fullsize[2]
2258 fullsize[2] /= numfull
2258 fullsize[2] /= numfull
2259 deltatotal = deltasize[2]
2259 deltatotal = deltasize[2]
2260 if numrevs - numfull > 0:
2260 if numrevs - numfull > 0:
2261 deltasize[2] /= numrevs - numfull
2261 deltasize[2] /= numrevs - numfull
2262 totalsize = fulltotal + deltatotal
2262 totalsize = fulltotal + deltatotal
2263 avgchainlen = sum(chainlengths) / numrevs
2263 avgchainlen = sum(chainlengths) / numrevs
2264 compratio = totalrawsize / totalsize
2264 compratio = totalrawsize / totalsize
2265
2265
2266 basedfmtstr = '%%%dd\n'
2266 basedfmtstr = '%%%dd\n'
2267 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2267 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2268
2268
2269 def dfmtstr(max):
2269 def dfmtstr(max):
2270 return basedfmtstr % len(str(max))
2270 return basedfmtstr % len(str(max))
2271 def pcfmtstr(max, padding=0):
2271 def pcfmtstr(max, padding=0):
2272 return basepcfmtstr % (len(str(max)), ' ' * padding)
2272 return basepcfmtstr % (len(str(max)), ' ' * padding)
2273
2273
2274 def pcfmt(value, total):
2274 def pcfmt(value, total):
2275 return (value, 100 * float(value) / total)
2275 return (value, 100 * float(value) / total)
2276
2276
2277 ui.write('format : %d\n' % format)
2277 ui.write('format : %d\n' % format)
2278 ui.write('flags : %s\n' % ', '.join(flags))
2278 ui.write('flags : %s\n' % ', '.join(flags))
2279
2279
2280 ui.write('\n')
2280 ui.write('\n')
2281 fmt = pcfmtstr(totalsize)
2281 fmt = pcfmtstr(totalsize)
2282 fmt2 = dfmtstr(totalsize)
2282 fmt2 = dfmtstr(totalsize)
2283 ui.write('revisions : ' + fmt2 % numrevs)
2283 ui.write('revisions : ' + fmt2 % numrevs)
2284 ui.write(' merges : ' + fmt % pcfmt(nummerges, numrevs))
2284 ui.write(' merges : ' + fmt % pcfmt(nummerges, numrevs))
2285 ui.write(' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs))
2285 ui.write(' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs))
2286 ui.write('revisions : ' + fmt2 % numrevs)
2286 ui.write('revisions : ' + fmt2 % numrevs)
2287 ui.write(' full : ' + fmt % pcfmt(numfull, numrevs))
2287 ui.write(' full : ' + fmt % pcfmt(numfull, numrevs))
2288 ui.write(' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
2288 ui.write(' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
2289 ui.write('revision size : ' + fmt2 % totalsize)
2289 ui.write('revision size : ' + fmt2 % totalsize)
2290 ui.write(' full : ' + fmt % pcfmt(fulltotal, totalsize))
2290 ui.write(' full : ' + fmt % pcfmt(fulltotal, totalsize))
2291 ui.write(' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
2291 ui.write(' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
2292
2292
2293 ui.write('\n')
2293 ui.write('\n')
2294 fmt = dfmtstr(max(avgchainlen, compratio))
2294 fmt = dfmtstr(max(avgchainlen, compratio))
2295 ui.write('avg chain length : ' + fmt % avgchainlen)
2295 ui.write('avg chain length : ' + fmt % avgchainlen)
2296 ui.write('compression ratio : ' + fmt % compratio)
2296 ui.write('compression ratio : ' + fmt % compratio)
2297
2297
2298 if format > 0:
2298 if format > 0:
2299 ui.write('\n')
2299 ui.write('\n')
2300 ui.write('uncompressed data size (min/max/avg) : %d / %d / %d\n'
2300 ui.write('uncompressed data size (min/max/avg) : %d / %d / %d\n'
2301 % tuple(datasize))
2301 % tuple(datasize))
2302 ui.write('full revision size (min/max/avg) : %d / %d / %d\n'
2302 ui.write('full revision size (min/max/avg) : %d / %d / %d\n'
2303 % tuple(fullsize))
2303 % tuple(fullsize))
2304 ui.write('delta size (min/max/avg) : %d / %d / %d\n'
2304 ui.write('delta size (min/max/avg) : %d / %d / %d\n'
2305 % tuple(deltasize))
2305 % tuple(deltasize))
2306
2306
2307 if numdeltas > 0:
2307 if numdeltas > 0:
2308 ui.write('\n')
2308 ui.write('\n')
2309 fmt = pcfmtstr(numdeltas)
2309 fmt = pcfmtstr(numdeltas)
2310 fmt2 = pcfmtstr(numdeltas, 4)
2310 fmt2 = pcfmtstr(numdeltas, 4)
2311 ui.write('deltas against prev : ' + fmt % pcfmt(numprev, numdeltas))
2311 ui.write('deltas against prev : ' + fmt % pcfmt(numprev, numdeltas))
2312 if numprev > 0:
2312 if numprev > 0:
2313 ui.write(' where prev = p1 : ' + fmt2 % pcfmt(nump1prev,
2313 ui.write(' where prev = p1 : ' + fmt2 % pcfmt(nump1prev,
2314 numprev))
2314 numprev))
2315 ui.write(' where prev = p2 : ' + fmt2 % pcfmt(nump2prev,
2315 ui.write(' where prev = p2 : ' + fmt2 % pcfmt(nump2prev,
2316 numprev))
2316 numprev))
2317 ui.write(' other : ' + fmt2 % pcfmt(numoprev,
2317 ui.write(' other : ' + fmt2 % pcfmt(numoprev,
2318 numprev))
2318 numprev))
2319 if gdelta:
2319 if gdelta:
2320 ui.write('deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas))
2320 ui.write('deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas))
2321 ui.write('deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas))
2321 ui.write('deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas))
2322 ui.write('deltas against other : ' + fmt % pcfmt(numother,
2322 ui.write('deltas against other : ' + fmt % pcfmt(numother,
2323 numdeltas))
2323 numdeltas))
2324
2324
2325 @command('debugrevspec', [], ('REVSPEC'))
2325 @command('debugrevspec', [], ('REVSPEC'))
2326 def debugrevspec(ui, repo, expr):
2326 def debugrevspec(ui, repo, expr):
2327 """parse and apply a revision specification
2327 """parse and apply a revision specification
2328
2328
2329 Use --verbose to print the parsed tree before and after aliases
2329 Use --verbose to print the parsed tree before and after aliases
2330 expansion.
2330 expansion.
2331 """
2331 """
2332 if ui.verbose:
2332 if ui.verbose:
2333 tree = revset.parse(expr)[0]
2333 tree = revset.parse(expr)[0]
2334 ui.note(revset.prettyformat(tree), "\n")
2334 ui.note(revset.prettyformat(tree), "\n")
2335 newtree = revset.findaliases(ui, tree)
2335 newtree = revset.findaliases(ui, tree)
2336 if newtree != tree:
2336 if newtree != tree:
2337 ui.note(revset.prettyformat(newtree), "\n")
2337 ui.note(revset.prettyformat(newtree), "\n")
2338 func = revset.match(ui, expr)
2338 func = revset.match(ui, expr)
2339 for c in func(repo, range(len(repo))):
2339 for c in func(repo, range(len(repo))):
2340 ui.write("%s\n" % c)
2340 ui.write("%s\n" % c)
2341
2341
2342 @command('debugsetparents', [], _('REV1 [REV2]'))
2342 @command('debugsetparents', [], _('REV1 [REV2]'))
2343 def debugsetparents(ui, repo, rev1, rev2=None):
2343 def debugsetparents(ui, repo, rev1, rev2=None):
2344 """manually set the parents of the current working directory
2344 """manually set the parents of the current working directory
2345
2345
2346 This is useful for writing repository conversion tools, but should
2346 This is useful for writing repository conversion tools, but should
2347 be used with care.
2347 be used with care.
2348
2348
2349 Returns 0 on success.
2349 Returns 0 on success.
2350 """
2350 """
2351
2351
2352 r1 = scmutil.revsingle(repo, rev1).node()
2352 r1 = scmutil.revsingle(repo, rev1).node()
2353 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2353 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2354
2354
2355 wlock = repo.wlock()
2355 wlock = repo.wlock()
2356 try:
2356 try:
2357 repo.setparents(r1, r2)
2357 repo.setparents(r1, r2)
2358 finally:
2358 finally:
2359 wlock.release()
2359 wlock.release()
2360
2360
2361 @command('debugstate',
2361 @command('debugstate',
2362 [('', 'nodates', None, _('do not display the saved mtime')),
2362 [('', 'nodates', None, _('do not display the saved mtime')),
2363 ('', 'datesort', None, _('sort by saved mtime'))],
2363 ('', 'datesort', None, _('sort by saved mtime'))],
2364 _('[OPTION]...'))
2364 _('[OPTION]...'))
2365 def debugstate(ui, repo, nodates=None, datesort=None):
2365 def debugstate(ui, repo, nodates=None, datesort=None):
2366 """show the contents of the current dirstate"""
2366 """show the contents of the current dirstate"""
2367 timestr = ""
2367 timestr = ""
2368 showdate = not nodates
2368 showdate = not nodates
2369 if datesort:
2369 if datesort:
2370 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2370 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2371 else:
2371 else:
2372 keyfunc = None # sort by filename
2372 keyfunc = None # sort by filename
2373 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2373 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2374 if showdate:
2374 if showdate:
2375 if ent[3] == -1:
2375 if ent[3] == -1:
2376 # Pad or slice to locale representation
2376 # Pad or slice to locale representation
2377 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2377 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2378 time.localtime(0)))
2378 time.localtime(0)))
2379 timestr = 'unset'
2379 timestr = 'unset'
2380 timestr = (timestr[:locale_len] +
2380 timestr = (timestr[:locale_len] +
2381 ' ' * (locale_len - len(timestr)))
2381 ' ' * (locale_len - len(timestr)))
2382 else:
2382 else:
2383 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2383 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2384 time.localtime(ent[3]))
2384 time.localtime(ent[3]))
2385 if ent[1] & 020000:
2385 if ent[1] & 020000:
2386 mode = 'lnk'
2386 mode = 'lnk'
2387 else:
2387 else:
2388 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2388 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2389 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2389 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2390 for f in repo.dirstate.copies():
2390 for f in repo.dirstate.copies():
2391 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2391 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2392
2392
2393 @command('debugsub',
2393 @command('debugsub',
2394 [('r', 'rev', '',
2394 [('r', 'rev', '',
2395 _('revision to check'), _('REV'))],
2395 _('revision to check'), _('REV'))],
2396 _('[-r REV] [REV]'))
2396 _('[-r REV] [REV]'))
2397 def debugsub(ui, repo, rev=None):
2397 def debugsub(ui, repo, rev=None):
2398 ctx = scmutil.revsingle(repo, rev, None)
2398 ctx = scmutil.revsingle(repo, rev, None)
2399 for k, v in sorted(ctx.substate.items()):
2399 for k, v in sorted(ctx.substate.items()):
2400 ui.write('path %s\n' % k)
2400 ui.write('path %s\n' % k)
2401 ui.write(' source %s\n' % v[0])
2401 ui.write(' source %s\n' % v[0])
2402 ui.write(' revision %s\n' % v[1])
2402 ui.write(' revision %s\n' % v[1])
2403
2403
2404 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2404 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2405 def debugwalk(ui, repo, *pats, **opts):
2405 def debugwalk(ui, repo, *pats, **opts):
2406 """show how files match on given patterns"""
2406 """show how files match on given patterns"""
2407 m = scmutil.match(repo[None], pats, opts)
2407 m = scmutil.match(repo[None], pats, opts)
2408 items = list(repo.walk(m))
2408 items = list(repo.walk(m))
2409 if not items:
2409 if not items:
2410 return
2410 return
2411 f = lambda fn: fn
2411 f = lambda fn: fn
2412 if ui.configbool('ui', 'slash') and os.sep != '/':
2412 if ui.configbool('ui', 'slash') and os.sep != '/':
2413 f = lambda fn: util.normpath(fn)
2413 f = lambda fn: util.normpath(fn)
2414 fmt = 'f %%-%ds %%-%ds %%s' % (
2414 fmt = 'f %%-%ds %%-%ds %%s' % (
2415 max([len(abs) for abs in items]),
2415 max([len(abs) for abs in items]),
2416 max([len(m.rel(abs)) for abs in items]))
2416 max([len(m.rel(abs)) for abs in items]))
2417 for abs in items:
2417 for abs in items:
2418 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2418 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2419 ui.write("%s\n" % line.rstrip())
2419 ui.write("%s\n" % line.rstrip())
2420
2420
2421 @command('debugwireargs',
2421 @command('debugwireargs',
2422 [('', 'three', '', 'three'),
2422 [('', 'three', '', 'three'),
2423 ('', 'four', '', 'four'),
2423 ('', 'four', '', 'four'),
2424 ('', 'five', '', 'five'),
2424 ('', 'five', '', 'five'),
2425 ] + remoteopts,
2425 ] + remoteopts,
2426 _('REPO [OPTIONS]... [ONE [TWO]]'))
2426 _('REPO [OPTIONS]... [ONE [TWO]]'))
2427 def debugwireargs(ui, repopath, *vals, **opts):
2427 def debugwireargs(ui, repopath, *vals, **opts):
2428 repo = hg.peer(ui, opts, repopath)
2428 repo = hg.peer(ui, opts, repopath)
2429 for opt in remoteopts:
2429 for opt in remoteopts:
2430 del opts[opt[1]]
2430 del opts[opt[1]]
2431 args = {}
2431 args = {}
2432 for k, v in opts.iteritems():
2432 for k, v in opts.iteritems():
2433 if v:
2433 if v:
2434 args[k] = v
2434 args[k] = v
2435 # run twice to check that we don't mess up the stream for the next command
2435 # run twice to check that we don't mess up the stream for the next command
2436 res1 = repo.debugwireargs(*vals, **args)
2436 res1 = repo.debugwireargs(*vals, **args)
2437 res2 = repo.debugwireargs(*vals, **args)
2437 res2 = repo.debugwireargs(*vals, **args)
2438 ui.write("%s\n" % res1)
2438 ui.write("%s\n" % res1)
2439 if res1 != res2:
2439 if res1 != res2:
2440 ui.warn("%s\n" % res2)
2440 ui.warn("%s\n" % res2)
2441
2441
2442 @command('^diff',
2442 @command('^diff',
2443 [('r', 'rev', [], _('revision'), _('REV')),
2443 [('r', 'rev', [], _('revision'), _('REV')),
2444 ('c', 'change', '', _('change made by revision'), _('REV'))
2444 ('c', 'change', '', _('change made by revision'), _('REV'))
2445 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2445 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2446 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2446 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2447 def diff(ui, repo, *pats, **opts):
2447 def diff(ui, repo, *pats, **opts):
2448 """diff repository (or selected files)
2448 """diff repository (or selected files)
2449
2449
2450 Show differences between revisions for the specified files.
2450 Show differences between revisions for the specified files.
2451
2451
2452 Differences between files are shown using the unified diff format.
2452 Differences between files are shown using the unified diff format.
2453
2453
2454 .. note::
2454 .. note::
2455 diff may generate unexpected results for merges, as it will
2455 diff may generate unexpected results for merges, as it will
2456 default to comparing against the working directory's first
2456 default to comparing against the working directory's first
2457 parent changeset if no revisions are specified.
2457 parent changeset if no revisions are specified.
2458
2458
2459 When two revision arguments are given, then changes are shown
2459 When two revision arguments are given, then changes are shown
2460 between those revisions. If only one revision is specified then
2460 between those revisions. If only one revision is specified then
2461 that revision is compared to the working directory, and, when no
2461 that revision is compared to the working directory, and, when no
2462 revisions are specified, the working directory files are compared
2462 revisions are specified, the working directory files are compared
2463 to its parent.
2463 to its parent.
2464
2464
2465 Alternatively you can specify -c/--change with a revision to see
2465 Alternatively you can specify -c/--change with a revision to see
2466 the changes in that changeset relative to its first parent.
2466 the changes in that changeset relative to its first parent.
2467
2467
2468 Without the -a/--text option, diff will avoid generating diffs of
2468 Without the -a/--text option, diff will avoid generating diffs of
2469 files it detects as binary. With -a, diff will generate a diff
2469 files it detects as binary. With -a, diff will generate a diff
2470 anyway, probably with undesirable results.
2470 anyway, probably with undesirable results.
2471
2471
2472 Use the -g/--git option to generate diffs in the git extended diff
2472 Use the -g/--git option to generate diffs in the git extended diff
2473 format. For more information, read :hg:`help diffs`.
2473 format. For more information, read :hg:`help diffs`.
2474
2474
2475 .. container:: verbose
2475 .. container:: verbose
2476
2476
2477 Examples:
2477 Examples:
2478
2478
2479 - compare a file in the current working directory to its parent::
2479 - compare a file in the current working directory to its parent::
2480
2480
2481 hg diff foo.c
2481 hg diff foo.c
2482
2482
2483 - compare two historical versions of a directory, with rename info::
2483 - compare two historical versions of a directory, with rename info::
2484
2484
2485 hg diff --git -r 1.0:1.2 lib/
2485 hg diff --git -r 1.0:1.2 lib/
2486
2486
2487 - get change stats relative to the last change on some date::
2487 - get change stats relative to the last change on some date::
2488
2488
2489 hg diff --stat -r "date('may 2')"
2489 hg diff --stat -r "date('may 2')"
2490
2490
2491 - diff all newly-added files that contain a keyword::
2491 - diff all newly-added files that contain a keyword::
2492
2492
2493 hg diff "set:added() and grep(GNU)"
2493 hg diff "set:added() and grep(GNU)"
2494
2494
2495 - compare a revision and its parents::
2495 - compare a revision and its parents::
2496
2496
2497 hg diff -c 9353 # compare against first parent
2497 hg diff -c 9353 # compare against first parent
2498 hg diff -r 9353^:9353 # same using revset syntax
2498 hg diff -r 9353^:9353 # same using revset syntax
2499 hg diff -r 9353^2:9353 # compare against the second parent
2499 hg diff -r 9353^2:9353 # compare against the second parent
2500
2500
2501 Returns 0 on success.
2501 Returns 0 on success.
2502 """
2502 """
2503
2503
2504 revs = opts.get('rev')
2504 revs = opts.get('rev')
2505 change = opts.get('change')
2505 change = opts.get('change')
2506 stat = opts.get('stat')
2506 stat = opts.get('stat')
2507 reverse = opts.get('reverse')
2507 reverse = opts.get('reverse')
2508
2508
2509 if revs and change:
2509 if revs and change:
2510 msg = _('cannot specify --rev and --change at the same time')
2510 msg = _('cannot specify --rev and --change at the same time')
2511 raise util.Abort(msg)
2511 raise util.Abort(msg)
2512 elif change:
2512 elif change:
2513 node2 = scmutil.revsingle(repo, change, None).node()
2513 node2 = scmutil.revsingle(repo, change, None).node()
2514 node1 = repo[node2].p1().node()
2514 node1 = repo[node2].p1().node()
2515 else:
2515 else:
2516 node1, node2 = scmutil.revpair(repo, revs)
2516 node1, node2 = scmutil.revpair(repo, revs)
2517
2517
2518 if reverse:
2518 if reverse:
2519 node1, node2 = node2, node1
2519 node1, node2 = node2, node1
2520
2520
2521 diffopts = patch.diffopts(ui, opts)
2521 diffopts = patch.diffopts(ui, opts)
2522 m = scmutil.match(repo[node2], pats, opts)
2522 m = scmutil.match(repo[node2], pats, opts)
2523 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2523 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2524 listsubrepos=opts.get('subrepos'))
2524 listsubrepos=opts.get('subrepos'))
2525
2525
2526 @command('^export',
2526 @command('^export',
2527 [('o', 'output', '',
2527 [('o', 'output', '',
2528 _('print output to file with formatted name'), _('FORMAT')),
2528 _('print output to file with formatted name'), _('FORMAT')),
2529 ('', 'switch-parent', None, _('diff against the second parent')),
2529 ('', 'switch-parent', None, _('diff against the second parent')),
2530 ('r', 'rev', [], _('revisions to export'), _('REV')),
2530 ('r', 'rev', [], _('revisions to export'), _('REV')),
2531 ] + diffopts,
2531 ] + diffopts,
2532 _('[OPTION]... [-o OUTFILESPEC] [-r] REV...'))
2532 _('[OPTION]... [-o OUTFILESPEC] [-r] REV...'))
2533 def export(ui, repo, *changesets, **opts):
2533 def export(ui, repo, *changesets, **opts):
2534 """dump the header and diffs for one or more changesets
2534 """dump the header and diffs for one or more changesets
2535
2535
2536 Print the changeset header and diffs for one or more revisions.
2536 Print the changeset header and diffs for one or more revisions.
2537
2537
2538 The information shown in the changeset header is: author, date,
2538 The information shown in the changeset header is: author, date,
2539 branch name (if non-default), changeset hash, parent(s) and commit
2539 branch name (if non-default), changeset hash, parent(s) and commit
2540 comment.
2540 comment.
2541
2541
2542 .. note::
2542 .. note::
2543 export may generate unexpected diff output for merge
2543 export may generate unexpected diff output for merge
2544 changesets, as it will compare the merge changeset against its
2544 changesets, as it will compare the merge changeset against its
2545 first parent only.
2545 first parent only.
2546
2546
2547 Output may be to a file, in which case the name of the file is
2547 Output may be to a file, in which case the name of the file is
2548 given using a format string. The formatting rules are as follows:
2548 given using a format string. The formatting rules are as follows:
2549
2549
2550 :``%%``: literal "%" character
2550 :``%%``: literal "%" character
2551 :``%H``: changeset hash (40 hexadecimal digits)
2551 :``%H``: changeset hash (40 hexadecimal digits)
2552 :``%N``: number of patches being generated
2552 :``%N``: number of patches being generated
2553 :``%R``: changeset revision number
2553 :``%R``: changeset revision number
2554 :``%b``: basename of the exporting repository
2554 :``%b``: basename of the exporting repository
2555 :``%h``: short-form changeset hash (12 hexadecimal digits)
2555 :``%h``: short-form changeset hash (12 hexadecimal digits)
2556 :``%m``: first line of the commit message (only alphanumeric characters)
2556 :``%m``: first line of the commit message (only alphanumeric characters)
2557 :``%n``: zero-padded sequence number, starting at 1
2557 :``%n``: zero-padded sequence number, starting at 1
2558 :``%r``: zero-padded changeset revision number
2558 :``%r``: zero-padded changeset revision number
2559
2559
2560 Without the -a/--text option, export will avoid generating diffs
2560 Without the -a/--text option, export will avoid generating diffs
2561 of files it detects as binary. With -a, export will generate a
2561 of files it detects as binary. With -a, export will generate a
2562 diff anyway, probably with undesirable results.
2562 diff anyway, probably with undesirable results.
2563
2563
2564 Use the -g/--git option to generate diffs in the git extended diff
2564 Use the -g/--git option to generate diffs in the git extended diff
2565 format. See :hg:`help diffs` for more information.
2565 format. See :hg:`help diffs` for more information.
2566
2566
2567 With the --switch-parent option, the diff will be against the
2567 With the --switch-parent option, the diff will be against the
2568 second parent. It can be useful to review a merge.
2568 second parent. It can be useful to review a merge.
2569
2569
2570 .. container:: verbose
2570 .. container:: verbose
2571
2571
2572 Examples:
2572 Examples:
2573
2573
2574 - use export and import to transplant a bugfix to the current
2574 - use export and import to transplant a bugfix to the current
2575 branch::
2575 branch::
2576
2576
2577 hg export -r 9353 | hg import -
2577 hg export -r 9353 | hg import -
2578
2578
2579 - export all the changesets between two revisions to a file with
2579 - export all the changesets between two revisions to a file with
2580 rename information::
2580 rename information::
2581
2581
2582 hg export --git -r 123:150 > changes.txt
2582 hg export --git -r 123:150 > changes.txt
2583
2583
2584 - split outgoing changes into a series of patches with
2584 - split outgoing changes into a series of patches with
2585 descriptive names::
2585 descriptive names::
2586
2586
2587 hg export -r "outgoing()" -o "%n-%m.patch"
2587 hg export -r "outgoing()" -o "%n-%m.patch"
2588
2588
2589 Returns 0 on success.
2589 Returns 0 on success.
2590 """
2590 """
2591 changesets += tuple(opts.get('rev', []))
2591 changesets += tuple(opts.get('rev', []))
2592 revs = scmutil.revrange(repo, changesets)
2592 revs = scmutil.revrange(repo, changesets)
2593 if not revs:
2593 if not revs:
2594 raise util.Abort(_("export requires at least one changeset"))
2594 raise util.Abort(_("export requires at least one changeset"))
2595 if len(revs) > 1:
2595 if len(revs) > 1:
2596 ui.note(_('exporting patches:\n'))
2596 ui.note(_('exporting patches:\n'))
2597 else:
2597 else:
2598 ui.note(_('exporting patch:\n'))
2598 ui.note(_('exporting patch:\n'))
2599 cmdutil.export(repo, revs, template=opts.get('output'),
2599 cmdutil.export(repo, revs, template=opts.get('output'),
2600 switch_parent=opts.get('switch_parent'),
2600 switch_parent=opts.get('switch_parent'),
2601 opts=patch.diffopts(ui, opts))
2601 opts=patch.diffopts(ui, opts))
2602
2602
2603 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2603 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2604 def forget(ui, repo, *pats, **opts):
2604 def forget(ui, repo, *pats, **opts):
2605 """forget the specified files on the next commit
2605 """forget the specified files on the next commit
2606
2606
2607 Mark the specified files so they will no longer be tracked
2607 Mark the specified files so they will no longer be tracked
2608 after the next commit.
2608 after the next commit.
2609
2609
2610 This only removes files from the current branch, not from the
2610 This only removes files from the current branch, not from the
2611 entire project history, and it does not delete them from the
2611 entire project history, and it does not delete them from the
2612 working directory.
2612 working directory.
2613
2613
2614 To undo a forget before the next commit, see :hg:`add`.
2614 To undo a forget before the next commit, see :hg:`add`.
2615
2615
2616 .. container:: verbose
2616 .. container:: verbose
2617
2617
2618 Examples:
2618 Examples:
2619
2619
2620 - forget newly-added binary files::
2620 - forget newly-added binary files::
2621
2621
2622 hg forget "set:added() and binary()"
2622 hg forget "set:added() and binary()"
2623
2623
2624 - forget files that would be excluded by .hgignore::
2624 - forget files that would be excluded by .hgignore::
2625
2625
2626 hg forget "set:hgignore()"
2626 hg forget "set:hgignore()"
2627
2627
2628 Returns 0 on success.
2628 Returns 0 on success.
2629 """
2629 """
2630
2630
2631 if not pats:
2631 if not pats:
2632 raise util.Abort(_('no files specified'))
2632 raise util.Abort(_('no files specified'))
2633
2633
2634 m = scmutil.match(repo[None], pats, opts)
2634 m = scmutil.match(repo[None], pats, opts)
2635 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2635 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2636 return rejected and 1 or 0
2636 return rejected and 1 or 0
2637
2637
2638 @command(
2638 @command(
2639 'graft',
2639 'graft',
2640 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2640 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2641 ('c', 'continue', False, _('resume interrupted graft')),
2641 ('c', 'continue', False, _('resume interrupted graft')),
2642 ('e', 'edit', False, _('invoke editor on commit messages')),
2642 ('e', 'edit', False, _('invoke editor on commit messages')),
2643 ('', 'log', None, _('append graft info to log message')),
2643 ('', 'log', None, _('append graft info to log message')),
2644 ('D', 'currentdate', False,
2644 ('D', 'currentdate', False,
2645 _('record the current date as commit date')),
2645 _('record the current date as commit date')),
2646 ('U', 'currentuser', False,
2646 ('U', 'currentuser', False,
2647 _('record the current user as committer'), _('DATE'))]
2647 _('record the current user as committer'), _('DATE'))]
2648 + commitopts2 + mergetoolopts + dryrunopts,
2648 + commitopts2 + mergetoolopts + dryrunopts,
2649 _('[OPTION]... [-r] REV...'))
2649 _('[OPTION]... [-r] REV...'))
2650 def graft(ui, repo, *revs, **opts):
2650 def graft(ui, repo, *revs, **opts):
2651 '''copy changes from other branches onto the current branch
2651 '''copy changes from other branches onto the current branch
2652
2652
2653 This command uses Mercurial's merge logic to copy individual
2653 This command uses Mercurial's merge logic to copy individual
2654 changes from other branches without merging branches in the
2654 changes from other branches without merging branches in the
2655 history graph. This is sometimes known as 'backporting' or
2655 history graph. This is sometimes known as 'backporting' or
2656 'cherry-picking'. By default, graft will copy user, date, and
2656 'cherry-picking'. By default, graft will copy user, date, and
2657 description from the source changesets.
2657 description from the source changesets.
2658
2658
2659 Changesets that are ancestors of the current revision, that have
2659 Changesets that are ancestors of the current revision, that have
2660 already been grafted, or that are merges will be skipped.
2660 already been grafted, or that are merges will be skipped.
2661
2661
2662 If --log is specified, log messages will have a comment appended
2662 If --log is specified, log messages will have a comment appended
2663 of the form::
2663 of the form::
2664
2664
2665 (grafted from CHANGESETHASH)
2665 (grafted from CHANGESETHASH)
2666
2666
2667 If a graft merge results in conflicts, the graft process is
2667 If a graft merge results in conflicts, the graft process is
2668 interrupted so that the current merge can be manually resolved.
2668 interrupted so that the current merge can be manually resolved.
2669 Once all conflicts are addressed, the graft process can be
2669 Once all conflicts are addressed, the graft process can be
2670 continued with the -c/--continue option.
2670 continued with the -c/--continue option.
2671
2671
2672 .. note::
2672 .. note::
2673 The -c/--continue option does not reapply earlier options.
2673 The -c/--continue option does not reapply earlier options.
2674
2674
2675 .. container:: verbose
2675 .. container:: verbose
2676
2676
2677 Examples:
2677 Examples:
2678
2678
2679 - copy a single change to the stable branch and edit its description::
2679 - copy a single change to the stable branch and edit its description::
2680
2680
2681 hg update stable
2681 hg update stable
2682 hg graft --edit 9393
2682 hg graft --edit 9393
2683
2683
2684 - graft a range of changesets with one exception, updating dates::
2684 - graft a range of changesets with one exception, updating dates::
2685
2685
2686 hg graft -D "2085::2093 and not 2091"
2686 hg graft -D "2085::2093 and not 2091"
2687
2687
2688 - continue a graft after resolving conflicts::
2688 - continue a graft after resolving conflicts::
2689
2689
2690 hg graft -c
2690 hg graft -c
2691
2691
2692 - show the source of a grafted changeset::
2692 - show the source of a grafted changeset::
2693
2693
2694 hg log --debug -r tip
2694 hg log --debug -r tip
2695
2695
2696 Returns 0 on successful completion.
2696 Returns 0 on successful completion.
2697 '''
2697 '''
2698
2698
2699 revs = list(revs)
2699 revs = list(revs)
2700 revs.extend(opts['rev'])
2700 revs.extend(opts['rev'])
2701
2701
2702 if not opts.get('user') and opts.get('currentuser'):
2702 if not opts.get('user') and opts.get('currentuser'):
2703 opts['user'] = ui.username()
2703 opts['user'] = ui.username()
2704 if not opts.get('date') and opts.get('currentdate'):
2704 if not opts.get('date') and opts.get('currentdate'):
2705 opts['date'] = "%d %d" % util.makedate()
2705 opts['date'] = "%d %d" % util.makedate()
2706
2706
2707 editor = None
2707 editor = None
2708 if opts.get('edit'):
2708 if opts.get('edit'):
2709 editor = cmdutil.commitforceeditor
2709 editor = cmdutil.commitforceeditor
2710
2710
2711 cont = False
2711 cont = False
2712 if opts['continue']:
2712 if opts['continue']:
2713 cont = True
2713 cont = True
2714 if revs:
2714 if revs:
2715 raise util.Abort(_("can't specify --continue and revisions"))
2715 raise util.Abort(_("can't specify --continue and revisions"))
2716 # read in unfinished revisions
2716 # read in unfinished revisions
2717 try:
2717 try:
2718 nodes = repo.opener.read('graftstate').splitlines()
2718 nodes = repo.opener.read('graftstate').splitlines()
2719 revs = [repo[node].rev() for node in nodes]
2719 revs = [repo[node].rev() for node in nodes]
2720 except IOError, inst:
2720 except IOError, inst:
2721 if inst.errno != errno.ENOENT:
2721 if inst.errno != errno.ENOENT:
2722 raise
2722 raise
2723 raise util.Abort(_("no graft state found, can't continue"))
2723 raise util.Abort(_("no graft state found, can't continue"))
2724 else:
2724 else:
2725 cmdutil.bailifchanged(repo)
2725 cmdutil.bailifchanged(repo)
2726 if not revs:
2726 if not revs:
2727 raise util.Abort(_('no revisions specified'))
2727 raise util.Abort(_('no revisions specified'))
2728 revs = scmutil.revrange(repo, revs)
2728 revs = scmutil.revrange(repo, revs)
2729
2729
2730 # check for merges
2730 # check for merges
2731 for rev in repo.revs('%ld and merge()', revs):
2731 for rev in repo.revs('%ld and merge()', revs):
2732 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2732 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2733 revs.remove(rev)
2733 revs.remove(rev)
2734 if not revs:
2734 if not revs:
2735 return -1
2735 return -1
2736
2736
2737 # check for ancestors of dest branch
2737 # check for ancestors of dest branch
2738 for rev in repo.revs('::. and %ld', revs):
2738 for rev in repo.revs('::. and %ld', revs):
2739 ui.warn(_('skipping ancestor revision %s\n') % rev)
2739 ui.warn(_('skipping ancestor revision %s\n') % rev)
2740 revs.remove(rev)
2740 revs.remove(rev)
2741 if not revs:
2741 if not revs:
2742 return -1
2742 return -1
2743
2743
2744 # analyze revs for earlier grafts
2744 # analyze revs for earlier grafts
2745 ids = {}
2745 ids = {}
2746 for ctx in repo.set("%ld", revs):
2746 for ctx in repo.set("%ld", revs):
2747 ids[ctx.hex()] = ctx.rev()
2747 ids[ctx.hex()] = ctx.rev()
2748 n = ctx.extra().get('source')
2748 n = ctx.extra().get('source')
2749 if n:
2749 if n:
2750 ids[n] = ctx.rev()
2750 ids[n] = ctx.rev()
2751
2751
2752 # check ancestors for earlier grafts
2752 # check ancestors for earlier grafts
2753 ui.debug('scanning for duplicate grafts\n')
2753 ui.debug('scanning for duplicate grafts\n')
2754 for ctx in repo.set("::. - ::%ld", revs):
2754 for ctx in repo.set("::. - ::%ld", revs):
2755 n = ctx.extra().get('source')
2755 n = ctx.extra().get('source')
2756 if n in ids:
2756 if n in ids:
2757 r = repo[n].rev()
2757 r = repo[n].rev()
2758 if r in revs:
2758 if r in revs:
2759 ui.warn(_('skipping already grafted revision %s\n') % r)
2759 ui.warn(_('skipping already grafted revision %s\n') % r)
2760 revs.remove(r)
2760 revs.remove(r)
2761 elif ids[n] in revs:
2761 elif ids[n] in revs:
2762 ui.warn(_('skipping already grafted revision %s '
2762 ui.warn(_('skipping already grafted revision %s '
2763 '(same origin %d)\n') % (ids[n], r))
2763 '(same origin %d)\n') % (ids[n], r))
2764 revs.remove(ids[n])
2764 revs.remove(ids[n])
2765 elif ctx.hex() in ids:
2765 elif ctx.hex() in ids:
2766 r = ids[ctx.hex()]
2766 r = ids[ctx.hex()]
2767 ui.warn(_('skipping already grafted revision %s '
2767 ui.warn(_('skipping already grafted revision %s '
2768 '(was grafted from %d)\n') % (r, ctx.rev()))
2768 '(was grafted from %d)\n') % (r, ctx.rev()))
2769 revs.remove(r)
2769 revs.remove(r)
2770 if not revs:
2770 if not revs:
2771 return -1
2771 return -1
2772
2772
2773 wlock = repo.wlock()
2773 wlock = repo.wlock()
2774 try:
2774 try:
2775 for pos, ctx in enumerate(repo.set("%ld", revs)):
2775 for pos, ctx in enumerate(repo.set("%ld", revs)):
2776 current = repo['.']
2776 current = repo['.']
2777
2777
2778 ui.status(_('grafting revision %s\n') % ctx.rev())
2778 ui.status(_('grafting revision %s\n') % ctx.rev())
2779 if opts.get('dry_run'):
2779 if opts.get('dry_run'):
2780 continue
2780 continue
2781
2781
2782 # we don't merge the first commit when continuing
2782 # we don't merge the first commit when continuing
2783 if not cont:
2783 if not cont:
2784 # perform the graft merge with p1(rev) as 'ancestor'
2784 # perform the graft merge with p1(rev) as 'ancestor'
2785 try:
2785 try:
2786 # ui.forcemerge is an internal variable, do not document
2786 # ui.forcemerge is an internal variable, do not document
2787 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2787 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2788 stats = mergemod.update(repo, ctx.node(), True, True, False,
2788 stats = mergemod.update(repo, ctx.node(), True, True, False,
2789 ctx.p1().node())
2789 ctx.p1().node())
2790 finally:
2790 finally:
2791 repo.ui.setconfig('ui', 'forcemerge', '')
2791 repo.ui.setconfig('ui', 'forcemerge', '')
2792 # report any conflicts
2792 # report any conflicts
2793 if stats and stats[3] > 0:
2793 if stats and stats[3] > 0:
2794 # write out state for --continue
2794 # write out state for --continue
2795 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2795 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2796 repo.opener.write('graftstate', ''.join(nodelines))
2796 repo.opener.write('graftstate', ''.join(nodelines))
2797 raise util.Abort(
2797 raise util.Abort(
2798 _("unresolved conflicts, can't continue"),
2798 _("unresolved conflicts, can't continue"),
2799 hint=_('use hg resolve and hg graft --continue'))
2799 hint=_('use hg resolve and hg graft --continue'))
2800 else:
2800 else:
2801 cont = False
2801 cont = False
2802
2802
2803 # drop the second merge parent
2803 # drop the second merge parent
2804 repo.setparents(current.node(), nullid)
2804 repo.setparents(current.node(), nullid)
2805 repo.dirstate.write()
2805 repo.dirstate.write()
2806 # fix up dirstate for copies and renames
2806 # fix up dirstate for copies and renames
2807 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
2807 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
2808
2808
2809 # commit
2809 # commit
2810 source = ctx.extra().get('source')
2810 source = ctx.extra().get('source')
2811 if not source:
2811 if not source:
2812 source = ctx.hex()
2812 source = ctx.hex()
2813 extra = {'source': source}
2813 extra = {'source': source}
2814 user = ctx.user()
2814 user = ctx.user()
2815 if opts.get('user'):
2815 if opts.get('user'):
2816 user = opts['user']
2816 user = opts['user']
2817 date = ctx.date()
2817 date = ctx.date()
2818 if opts.get('date'):
2818 if opts.get('date'):
2819 date = opts['date']
2819 date = opts['date']
2820 message = ctx.description()
2820 message = ctx.description()
2821 if opts.get('log'):
2821 if opts.get('log'):
2822 message += '\n(grafted from %s)' % ctx.hex()
2822 message += '\n(grafted from %s)' % ctx.hex()
2823 node = repo.commit(text=message, user=user,
2823 node = repo.commit(text=message, user=user,
2824 date=date, extra=extra, editor=editor)
2824 date=date, extra=extra, editor=editor)
2825 if node is None:
2825 if node is None:
2826 ui.status(_('graft for revision %s is empty\n') % ctx.rev())
2826 ui.status(_('graft for revision %s is empty\n') % ctx.rev())
2827 finally:
2827 finally:
2828 wlock.release()
2828 wlock.release()
2829
2829
2830 # remove state when we complete successfully
2830 # remove state when we complete successfully
2831 if not opts.get('dry_run') and os.path.exists(repo.join('graftstate')):
2831 if not opts.get('dry_run') and os.path.exists(repo.join('graftstate')):
2832 util.unlinkpath(repo.join('graftstate'))
2832 util.unlinkpath(repo.join('graftstate'))
2833
2833
2834 return 0
2834 return 0
2835
2835
2836 @command('grep',
2836 @command('grep',
2837 [('0', 'print0', None, _('end fields with NUL')),
2837 [('0', 'print0', None, _('end fields with NUL')),
2838 ('', 'all', None, _('print all revisions that match')),
2838 ('', 'all', None, _('print all revisions that match')),
2839 ('a', 'text', None, _('treat all files as text')),
2839 ('a', 'text', None, _('treat all files as text')),
2840 ('f', 'follow', None,
2840 ('f', 'follow', None,
2841 _('follow changeset history,'
2841 _('follow changeset history,'
2842 ' or file history across copies and renames')),
2842 ' or file history across copies and renames')),
2843 ('i', 'ignore-case', None, _('ignore case when matching')),
2843 ('i', 'ignore-case', None, _('ignore case when matching')),
2844 ('l', 'files-with-matches', None,
2844 ('l', 'files-with-matches', None,
2845 _('print only filenames and revisions that match')),
2845 _('print only filenames and revisions that match')),
2846 ('n', 'line-number', None, _('print matching line numbers')),
2846 ('n', 'line-number', None, _('print matching line numbers')),
2847 ('r', 'rev', [],
2847 ('r', 'rev', [],
2848 _('only search files changed within revision range'), _('REV')),
2848 _('only search files changed within revision range'), _('REV')),
2849 ('u', 'user', None, _('list the author (long with -v)')),
2849 ('u', 'user', None, _('list the author (long with -v)')),
2850 ('d', 'date', None, _('list the date (short with -q)')),
2850 ('d', 'date', None, _('list the date (short with -q)')),
2851 ] + walkopts,
2851 ] + walkopts,
2852 _('[OPTION]... PATTERN [FILE]...'))
2852 _('[OPTION]... PATTERN [FILE]...'))
2853 def grep(ui, repo, pattern, *pats, **opts):
2853 def grep(ui, repo, pattern, *pats, **opts):
2854 """search for a pattern in specified files and revisions
2854 """search for a pattern in specified files and revisions
2855
2855
2856 Search revisions of files for a regular expression.
2856 Search revisions of files for a regular expression.
2857
2857
2858 This command behaves differently than Unix grep. It only accepts
2858 This command behaves differently than Unix grep. It only accepts
2859 Python/Perl regexps. It searches repository history, not the
2859 Python/Perl regexps. It searches repository history, not the
2860 working directory. It always prints the revision number in which a
2860 working directory. It always prints the revision number in which a
2861 match appears.
2861 match appears.
2862
2862
2863 By default, grep only prints output for the first revision of a
2863 By default, grep only prints output for the first revision of a
2864 file in which it finds a match. To get it to print every revision
2864 file in which it finds a match. To get it to print every revision
2865 that contains a change in match status ("-" for a match that
2865 that contains a change in match status ("-" for a match that
2866 becomes a non-match, or "+" for a non-match that becomes a match),
2866 becomes a non-match, or "+" for a non-match that becomes a match),
2867 use the --all flag.
2867 use the --all flag.
2868
2868
2869 Returns 0 if a match is found, 1 otherwise.
2869 Returns 0 if a match is found, 1 otherwise.
2870 """
2870 """
2871 reflags = re.M
2871 reflags = re.M
2872 if opts.get('ignore_case'):
2872 if opts.get('ignore_case'):
2873 reflags |= re.I
2873 reflags |= re.I
2874 try:
2874 try:
2875 regexp = re.compile(pattern, reflags)
2875 regexp = re.compile(pattern, reflags)
2876 except re.error, inst:
2876 except re.error, inst:
2877 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2877 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2878 return 1
2878 return 1
2879 sep, eol = ':', '\n'
2879 sep, eol = ':', '\n'
2880 if opts.get('print0'):
2880 if opts.get('print0'):
2881 sep = eol = '\0'
2881 sep = eol = '\0'
2882
2882
2883 getfile = util.lrucachefunc(repo.file)
2883 getfile = util.lrucachefunc(repo.file)
2884
2884
2885 def matchlines(body):
2885 def matchlines(body):
2886 begin = 0
2886 begin = 0
2887 linenum = 0
2887 linenum = 0
2888 while True:
2888 while True:
2889 match = regexp.search(body, begin)
2889 match = regexp.search(body, begin)
2890 if not match:
2890 if not match:
2891 break
2891 break
2892 mstart, mend = match.span()
2892 mstart, mend = match.span()
2893 linenum += body.count('\n', begin, mstart) + 1
2893 linenum += body.count('\n', begin, mstart) + 1
2894 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2894 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2895 begin = body.find('\n', mend) + 1 or len(body) + 1
2895 begin = body.find('\n', mend) + 1 or len(body) + 1
2896 lend = begin - 1
2896 lend = begin - 1
2897 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2897 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2898
2898
2899 class linestate(object):
2899 class linestate(object):
2900 def __init__(self, line, linenum, colstart, colend):
2900 def __init__(self, line, linenum, colstart, colend):
2901 self.line = line
2901 self.line = line
2902 self.linenum = linenum
2902 self.linenum = linenum
2903 self.colstart = colstart
2903 self.colstart = colstart
2904 self.colend = colend
2904 self.colend = colend
2905
2905
2906 def __hash__(self):
2906 def __hash__(self):
2907 return hash((self.linenum, self.line))
2907 return hash((self.linenum, self.line))
2908
2908
2909 def __eq__(self, other):
2909 def __eq__(self, other):
2910 return self.line == other.line
2910 return self.line == other.line
2911
2911
2912 matches = {}
2912 matches = {}
2913 copies = {}
2913 copies = {}
2914 def grepbody(fn, rev, body):
2914 def grepbody(fn, rev, body):
2915 matches[rev].setdefault(fn, [])
2915 matches[rev].setdefault(fn, [])
2916 m = matches[rev][fn]
2916 m = matches[rev][fn]
2917 for lnum, cstart, cend, line in matchlines(body):
2917 for lnum, cstart, cend, line in matchlines(body):
2918 s = linestate(line, lnum, cstart, cend)
2918 s = linestate(line, lnum, cstart, cend)
2919 m.append(s)
2919 m.append(s)
2920
2920
2921 def difflinestates(a, b):
2921 def difflinestates(a, b):
2922 sm = difflib.SequenceMatcher(None, a, b)
2922 sm = difflib.SequenceMatcher(None, a, b)
2923 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2923 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2924 if tag == 'insert':
2924 if tag == 'insert':
2925 for i in xrange(blo, bhi):
2925 for i in xrange(blo, bhi):
2926 yield ('+', b[i])
2926 yield ('+', b[i])
2927 elif tag == 'delete':
2927 elif tag == 'delete':
2928 for i in xrange(alo, ahi):
2928 for i in xrange(alo, ahi):
2929 yield ('-', a[i])
2929 yield ('-', a[i])
2930 elif tag == 'replace':
2930 elif tag == 'replace':
2931 for i in xrange(alo, ahi):
2931 for i in xrange(alo, ahi):
2932 yield ('-', a[i])
2932 yield ('-', a[i])
2933 for i in xrange(blo, bhi):
2933 for i in xrange(blo, bhi):
2934 yield ('+', b[i])
2934 yield ('+', b[i])
2935
2935
2936 def display(fn, ctx, pstates, states):
2936 def display(fn, ctx, pstates, states):
2937 rev = ctx.rev()
2937 rev = ctx.rev()
2938 datefunc = ui.quiet and util.shortdate or util.datestr
2938 datefunc = ui.quiet and util.shortdate or util.datestr
2939 found = False
2939 found = False
2940 filerevmatches = {}
2940 filerevmatches = {}
2941 def binary():
2941 def binary():
2942 flog = getfile(fn)
2942 flog = getfile(fn)
2943 return util.binary(flog.read(ctx.filenode(fn)))
2943 return util.binary(flog.read(ctx.filenode(fn)))
2944
2944
2945 if opts.get('all'):
2945 if opts.get('all'):
2946 iter = difflinestates(pstates, states)
2946 iter = difflinestates(pstates, states)
2947 else:
2947 else:
2948 iter = [('', l) for l in states]
2948 iter = [('', l) for l in states]
2949 for change, l in iter:
2949 for change, l in iter:
2950 cols = [fn, str(rev)]
2950 cols = [fn, str(rev)]
2951 before, match, after = None, None, None
2951 before, match, after = None, None, None
2952 if opts.get('line_number'):
2952 if opts.get('line_number'):
2953 cols.append(str(l.linenum))
2953 cols.append(str(l.linenum))
2954 if opts.get('all'):
2954 if opts.get('all'):
2955 cols.append(change)
2955 cols.append(change)
2956 if opts.get('user'):
2956 if opts.get('user'):
2957 cols.append(ui.shortuser(ctx.user()))
2957 cols.append(ui.shortuser(ctx.user()))
2958 if opts.get('date'):
2958 if opts.get('date'):
2959 cols.append(datefunc(ctx.date()))
2959 cols.append(datefunc(ctx.date()))
2960 if opts.get('files_with_matches'):
2960 if opts.get('files_with_matches'):
2961 c = (fn, rev)
2961 c = (fn, rev)
2962 if c in filerevmatches:
2962 if c in filerevmatches:
2963 continue
2963 continue
2964 filerevmatches[c] = 1
2964 filerevmatches[c] = 1
2965 else:
2965 else:
2966 before = l.line[:l.colstart]
2966 before = l.line[:l.colstart]
2967 match = l.line[l.colstart:l.colend]
2967 match = l.line[l.colstart:l.colend]
2968 after = l.line[l.colend:]
2968 after = l.line[l.colend:]
2969 ui.write(sep.join(cols))
2969 ui.write(sep.join(cols))
2970 if before is not None:
2970 if before is not None:
2971 if not opts.get('text') and binary():
2971 if not opts.get('text') and binary():
2972 ui.write(sep + " Binary file matches")
2972 ui.write(sep + " Binary file matches")
2973 else:
2973 else:
2974 ui.write(sep + before)
2974 ui.write(sep + before)
2975 ui.write(match, label='grep.match')
2975 ui.write(match, label='grep.match')
2976 ui.write(after)
2976 ui.write(after)
2977 ui.write(eol)
2977 ui.write(eol)
2978 found = True
2978 found = True
2979 return found
2979 return found
2980
2980
2981 skip = {}
2981 skip = {}
2982 revfiles = {}
2982 revfiles = {}
2983 matchfn = scmutil.match(repo[None], pats, opts)
2983 matchfn = scmutil.match(repo[None], pats, opts)
2984 found = False
2984 found = False
2985 follow = opts.get('follow')
2985 follow = opts.get('follow')
2986
2986
2987 def prep(ctx, fns):
2987 def prep(ctx, fns):
2988 rev = ctx.rev()
2988 rev = ctx.rev()
2989 pctx = ctx.p1()
2989 pctx = ctx.p1()
2990 parent = pctx.rev()
2990 parent = pctx.rev()
2991 matches.setdefault(rev, {})
2991 matches.setdefault(rev, {})
2992 matches.setdefault(parent, {})
2992 matches.setdefault(parent, {})
2993 files = revfiles.setdefault(rev, [])
2993 files = revfiles.setdefault(rev, [])
2994 for fn in fns:
2994 for fn in fns:
2995 flog = getfile(fn)
2995 flog = getfile(fn)
2996 try:
2996 try:
2997 fnode = ctx.filenode(fn)
2997 fnode = ctx.filenode(fn)
2998 except error.LookupError:
2998 except error.LookupError:
2999 continue
2999 continue
3000
3000
3001 copied = flog.renamed(fnode)
3001 copied = flog.renamed(fnode)
3002 copy = follow and copied and copied[0]
3002 copy = follow and copied and copied[0]
3003 if copy:
3003 if copy:
3004 copies.setdefault(rev, {})[fn] = copy
3004 copies.setdefault(rev, {})[fn] = copy
3005 if fn in skip:
3005 if fn in skip:
3006 if copy:
3006 if copy:
3007 skip[copy] = True
3007 skip[copy] = True
3008 continue
3008 continue
3009 files.append(fn)
3009 files.append(fn)
3010
3010
3011 if fn not in matches[rev]:
3011 if fn not in matches[rev]:
3012 grepbody(fn, rev, flog.read(fnode))
3012 grepbody(fn, rev, flog.read(fnode))
3013
3013
3014 pfn = copy or fn
3014 pfn = copy or fn
3015 if pfn not in matches[parent]:
3015 if pfn not in matches[parent]:
3016 try:
3016 try:
3017 fnode = pctx.filenode(pfn)
3017 fnode = pctx.filenode(pfn)
3018 grepbody(pfn, parent, flog.read(fnode))
3018 grepbody(pfn, parent, flog.read(fnode))
3019 except error.LookupError:
3019 except error.LookupError:
3020 pass
3020 pass
3021
3021
3022 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3022 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3023 rev = ctx.rev()
3023 rev = ctx.rev()
3024 parent = ctx.p1().rev()
3024 parent = ctx.p1().rev()
3025 for fn in sorted(revfiles.get(rev, [])):
3025 for fn in sorted(revfiles.get(rev, [])):
3026 states = matches[rev][fn]
3026 states = matches[rev][fn]
3027 copy = copies.get(rev, {}).get(fn)
3027 copy = copies.get(rev, {}).get(fn)
3028 if fn in skip:
3028 if fn in skip:
3029 if copy:
3029 if copy:
3030 skip[copy] = True
3030 skip[copy] = True
3031 continue
3031 continue
3032 pstates = matches.get(parent, {}).get(copy or fn, [])
3032 pstates = matches.get(parent, {}).get(copy or fn, [])
3033 if pstates or states:
3033 if pstates or states:
3034 r = display(fn, ctx, pstates, states)
3034 r = display(fn, ctx, pstates, states)
3035 found = found or r
3035 found = found or r
3036 if r and not opts.get('all'):
3036 if r and not opts.get('all'):
3037 skip[fn] = True
3037 skip[fn] = True
3038 if copy:
3038 if copy:
3039 skip[copy] = True
3039 skip[copy] = True
3040 del matches[rev]
3040 del matches[rev]
3041 del revfiles[rev]
3041 del revfiles[rev]
3042
3042
3043 return not found
3043 return not found
3044
3044
3045 @command('heads',
3045 @command('heads',
3046 [('r', 'rev', '',
3046 [('r', 'rev', '',
3047 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3047 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3048 ('t', 'topo', False, _('show topological heads only')),
3048 ('t', 'topo', False, _('show topological heads only')),
3049 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3049 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3050 ('c', 'closed', False, _('show normal and closed branch heads')),
3050 ('c', 'closed', False, _('show normal and closed branch heads')),
3051 ] + templateopts,
3051 ] + templateopts,
3052 _('[-ct] [-r STARTREV] [REV]...'))
3052 _('[-ct] [-r STARTREV] [REV]...'))
3053 def heads(ui, repo, *branchrevs, **opts):
3053 def heads(ui, repo, *branchrevs, **opts):
3054 """show current repository heads or show branch heads
3054 """show current repository heads or show branch heads
3055
3055
3056 With no arguments, show all repository branch heads.
3056 With no arguments, show all repository branch heads.
3057
3057
3058 Repository "heads" are changesets with no child changesets. They are
3058 Repository "heads" are changesets with no child changesets. They are
3059 where development generally takes place and are the usual targets
3059 where development generally takes place and are the usual targets
3060 for update and merge operations. Branch heads are changesets that have
3060 for update and merge operations. Branch heads are changesets that have
3061 no child changeset on the same branch.
3061 no child changeset on the same branch.
3062
3062
3063 If one or more REVs are given, only branch heads on the branches
3063 If one or more REVs are given, only branch heads on the branches
3064 associated with the specified changesets are shown. This means
3064 associated with the specified changesets are shown. This means
3065 that you can use :hg:`heads foo` to see the heads on a branch
3065 that you can use :hg:`heads foo` to see the heads on a branch
3066 named ``foo``.
3066 named ``foo``.
3067
3067
3068 If -c/--closed is specified, also show branch heads marked closed
3068 If -c/--closed is specified, also show branch heads marked closed
3069 (see :hg:`commit --close-branch`).
3069 (see :hg:`commit --close-branch`).
3070
3070
3071 If STARTREV is specified, only those heads that are descendants of
3071 If STARTREV is specified, only those heads that are descendants of
3072 STARTREV will be displayed.
3072 STARTREV will be displayed.
3073
3073
3074 If -t/--topo is specified, named branch mechanics will be ignored and only
3074 If -t/--topo is specified, named branch mechanics will be ignored and only
3075 changesets without children will be shown.
3075 changesets without children will be shown.
3076
3076
3077 Returns 0 if matching heads are found, 1 if not.
3077 Returns 0 if matching heads are found, 1 if not.
3078 """
3078 """
3079
3079
3080 start = None
3080 start = None
3081 if 'rev' in opts:
3081 if 'rev' in opts:
3082 start = scmutil.revsingle(repo, opts['rev'], None).node()
3082 start = scmutil.revsingle(repo, opts['rev'], None).node()
3083
3083
3084 if opts.get('topo'):
3084 if opts.get('topo'):
3085 heads = [repo[h] for h in repo.heads(start)]
3085 heads = [repo[h] for h in repo.heads(start)]
3086 else:
3086 else:
3087 heads = []
3087 heads = []
3088 for branch in repo.branchmap():
3088 for branch in repo.branchmap():
3089 heads += repo.branchheads(branch, start, opts.get('closed'))
3089 heads += repo.branchheads(branch, start, opts.get('closed'))
3090 heads = [repo[h] for h in heads]
3090 heads = [repo[h] for h in heads]
3091
3091
3092 if branchrevs:
3092 if branchrevs:
3093 branches = set(repo[br].branch() for br in branchrevs)
3093 branches = set(repo[br].branch() for br in branchrevs)
3094 heads = [h for h in heads if h.branch() in branches]
3094 heads = [h for h in heads if h.branch() in branches]
3095
3095
3096 if opts.get('active') and branchrevs:
3096 if opts.get('active') and branchrevs:
3097 dagheads = repo.heads(start)
3097 dagheads = repo.heads(start)
3098 heads = [h for h in heads if h.node() in dagheads]
3098 heads = [h for h in heads if h.node() in dagheads]
3099
3099
3100 if branchrevs:
3100 if branchrevs:
3101 haveheads = set(h.branch() for h in heads)
3101 haveheads = set(h.branch() for h in heads)
3102 if branches - haveheads:
3102 if branches - haveheads:
3103 headless = ', '.join(b for b in branches - haveheads)
3103 headless = ', '.join(b for b in branches - haveheads)
3104 msg = _('no open branch heads found on branches %s')
3104 msg = _('no open branch heads found on branches %s')
3105 if opts.get('rev'):
3105 if opts.get('rev'):
3106 msg += _(' (started at %s)') % opts['rev']
3106 msg += _(' (started at %s)') % opts['rev']
3107 ui.warn((msg + '\n') % headless)
3107 ui.warn((msg + '\n') % headless)
3108
3108
3109 if not heads:
3109 if not heads:
3110 return 1
3110 return 1
3111
3111
3112 heads = sorted(heads, key=lambda x: -x.rev())
3112 heads = sorted(heads, key=lambda x: -x.rev())
3113 displayer = cmdutil.show_changeset(ui, repo, opts)
3113 displayer = cmdutil.show_changeset(ui, repo, opts)
3114 for ctx in heads:
3114 for ctx in heads:
3115 displayer.show(ctx)
3115 displayer.show(ctx)
3116 displayer.close()
3116 displayer.close()
3117
3117
3118 @command('help',
3118 @command('help',
3119 [('e', 'extension', None, _('show only help for extensions')),
3119 [('e', 'extension', None, _('show only help for extensions')),
3120 ('c', 'command', None, _('show only help for commands')),
3120 ('c', 'command', None, _('show only help for commands')),
3121 ('k', 'keyword', '', _('show topics matching keyword')),
3121 ('k', 'keyword', '', _('show topics matching keyword')),
3122 ],
3122 ],
3123 _('[-ec] [TOPIC]'))
3123 _('[-ec] [TOPIC]'))
3124 def help_(ui, name=None, unknowncmd=False, full=True, **opts):
3124 def help_(ui, name=None, unknowncmd=False, full=True, **opts):
3125 """show help for a given topic or a help overview
3125 """show help for a given topic or a help overview
3126
3126
3127 With no arguments, print a list of commands with short help messages.
3127 With no arguments, print a list of commands with short help messages.
3128
3128
3129 Given a topic, extension, or command name, print help for that
3129 Given a topic, extension, or command name, print help for that
3130 topic.
3130 topic.
3131
3131
3132 Returns 0 if successful.
3132 Returns 0 if successful.
3133 """
3133 """
3134
3134
3135 textwidth = min(ui.termwidth(), 80) - 2
3135 textwidth = min(ui.termwidth(), 80) - 2
3136
3136
3137 def helpcmd(name):
3137 def helpcmd(name):
3138 try:
3138 try:
3139 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
3139 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
3140 except error.AmbiguousCommand, inst:
3140 except error.AmbiguousCommand, inst:
3141 # py3k fix: except vars can't be used outside the scope of the
3141 # py3k fix: except vars can't be used outside the scope of the
3142 # except block, nor can be used inside a lambda. python issue4617
3142 # except block, nor can be used inside a lambda. python issue4617
3143 prefix = inst.args[0]
3143 prefix = inst.args[0]
3144 select = lambda c: c.lstrip('^').startswith(prefix)
3144 select = lambda c: c.lstrip('^').startswith(prefix)
3145 rst = helplist(select)
3145 rst = helplist(select)
3146 return rst
3146 return rst
3147
3147
3148 rst = []
3148 rst = []
3149
3149
3150 # check if it's an invalid alias and display its error if it is
3150 # check if it's an invalid alias and display its error if it is
3151 if getattr(entry[0], 'badalias', False):
3151 if getattr(entry[0], 'badalias', False):
3152 if not unknowncmd:
3152 if not unknowncmd:
3153 ui.pushbuffer()
3153 ui.pushbuffer()
3154 entry[0](ui)
3154 entry[0](ui)
3155 rst.append(ui.popbuffer())
3155 rst.append(ui.popbuffer())
3156 return rst
3156 return rst
3157
3157
3158 # synopsis
3158 # synopsis
3159 if len(entry) > 2:
3159 if len(entry) > 2:
3160 if entry[2].startswith('hg'):
3160 if entry[2].startswith('hg'):
3161 rst.append("%s\n" % entry[2])
3161 rst.append("%s\n" % entry[2])
3162 else:
3162 else:
3163 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
3163 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
3164 else:
3164 else:
3165 rst.append('hg %s\n' % aliases[0])
3165 rst.append('hg %s\n' % aliases[0])
3166 # aliases
3166 # aliases
3167 if full and not ui.quiet and len(aliases) > 1:
3167 if full and not ui.quiet and len(aliases) > 1:
3168 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
3168 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
3169 rst.append('\n')
3169 rst.append('\n')
3170
3170
3171 # description
3171 # description
3172 doc = gettext(entry[0].__doc__)
3172 doc = gettext(entry[0].__doc__)
3173 if not doc:
3173 if not doc:
3174 doc = _("(no help text available)")
3174 doc = _("(no help text available)")
3175 if util.safehasattr(entry[0], 'definition'): # aliased command
3175 if util.safehasattr(entry[0], 'definition'): # aliased command
3176 if entry[0].definition.startswith('!'): # shell alias
3176 if entry[0].definition.startswith('!'): # shell alias
3177 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
3177 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
3178 else:
3178 else:
3179 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
3179 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
3180 doc = doc.splitlines(True)
3180 doc = doc.splitlines(True)
3181 if ui.quiet or not full:
3181 if ui.quiet or not full:
3182 rst.append(doc[0])
3182 rst.append(doc[0])
3183 else:
3183 else:
3184 rst.extend(doc)
3184 rst.extend(doc)
3185 rst.append('\n')
3185 rst.append('\n')
3186
3186
3187 # check if this command shadows a non-trivial (multi-line)
3187 # check if this command shadows a non-trivial (multi-line)
3188 # extension help text
3188 # extension help text
3189 try:
3189 try:
3190 mod = extensions.find(name)
3190 mod = extensions.find(name)
3191 doc = gettext(mod.__doc__) or ''
3191 doc = gettext(mod.__doc__) or ''
3192 if '\n' in doc.strip():
3192 if '\n' in doc.strip():
3193 msg = _('use "hg help -e %s" to show help for '
3193 msg = _('use "hg help -e %s" to show help for '
3194 'the %s extension') % (name, name)
3194 'the %s extension') % (name, name)
3195 rst.append('\n%s\n' % msg)
3195 rst.append('\n%s\n' % msg)
3196 except KeyError:
3196 except KeyError:
3197 pass
3197 pass
3198
3198
3199 # options
3199 # options
3200 if not ui.quiet and entry[1]:
3200 if not ui.quiet and entry[1]:
3201 rst.append('\n%s\n\n' % _("options:"))
3201 rst.append('\n%s\n\n' % _("options:"))
3202 rst.append(help.optrst(entry[1], ui.verbose))
3202 rst.append(help.optrst(entry[1], ui.verbose))
3203
3203
3204 if ui.verbose:
3204 if ui.verbose:
3205 rst.append('\n%s\n\n' % _("global options:"))
3205 rst.append('\n%s\n\n' % _("global options:"))
3206 rst.append(help.optrst(globalopts, ui.verbose))
3206 rst.append(help.optrst(globalopts, ui.verbose))
3207
3207
3208 if not ui.verbose:
3208 if not ui.verbose:
3209 if not full:
3209 if not full:
3210 rst.append(_('\nuse "hg help %s" to show the full help text\n')
3210 rst.append(_('\nuse "hg help %s" to show the full help text\n')
3211 % name)
3211 % name)
3212 elif not ui.quiet:
3212 elif not ui.quiet:
3213 rst.append(_('\nuse "hg -v help %s" to show more info\n')
3213 rst.append(_('\nuse "hg -v help %s" to show more info\n')
3214 % name)
3214 % name)
3215 return rst
3215 return rst
3216
3216
3217
3217
3218 def helplist(select=None):
3218 def helplist(select=None):
3219 # list of commands
3219 # list of commands
3220 if name == "shortlist":
3220 if name == "shortlist":
3221 header = _('basic commands:\n\n')
3221 header = _('basic commands:\n\n')
3222 else:
3222 else:
3223 header = _('list of commands:\n\n')
3223 header = _('list of commands:\n\n')
3224
3224
3225 h = {}
3225 h = {}
3226 cmds = {}
3226 cmds = {}
3227 for c, e in table.iteritems():
3227 for c, e in table.iteritems():
3228 f = c.split("|", 1)[0]
3228 f = c.split("|", 1)[0]
3229 if select and not select(f):
3229 if select and not select(f):
3230 continue
3230 continue
3231 if (not select and name != 'shortlist' and
3231 if (not select and name != 'shortlist' and
3232 e[0].__module__ != __name__):
3232 e[0].__module__ != __name__):
3233 continue
3233 continue
3234 if name == "shortlist" and not f.startswith("^"):
3234 if name == "shortlist" and not f.startswith("^"):
3235 continue
3235 continue
3236 f = f.lstrip("^")
3236 f = f.lstrip("^")
3237 if not ui.debugflag and f.startswith("debug"):
3237 if not ui.debugflag and f.startswith("debug"):
3238 continue
3238 continue
3239 doc = e[0].__doc__
3239 doc = e[0].__doc__
3240 if doc and 'DEPRECATED' in doc and not ui.verbose:
3240 if doc and 'DEPRECATED' in doc and not ui.verbose:
3241 continue
3241 continue
3242 doc = gettext(doc)
3242 doc = gettext(doc)
3243 if not doc:
3243 if not doc:
3244 doc = _("(no help text available)")
3244 doc = _("(no help text available)")
3245 h[f] = doc.splitlines()[0].rstrip()
3245 h[f] = doc.splitlines()[0].rstrip()
3246 cmds[f] = c.lstrip("^")
3246 cmds[f] = c.lstrip("^")
3247
3247
3248 rst = []
3248 rst = []
3249 if not h:
3249 if not h:
3250 if not ui.quiet:
3250 if not ui.quiet:
3251 rst.append(_('no commands defined\n'))
3251 rst.append(_('no commands defined\n'))
3252 return rst
3252 return rst
3253
3253
3254 if not ui.quiet:
3254 if not ui.quiet:
3255 rst.append(header)
3255 rst.append(header)
3256 fns = sorted(h)
3256 fns = sorted(h)
3257 for f in fns:
3257 for f in fns:
3258 if ui.verbose:
3258 if ui.verbose:
3259 commands = cmds[f].replace("|",", ")
3259 commands = cmds[f].replace("|",", ")
3260 rst.append(" :%s: %s\n" % (commands, h[f]))
3260 rst.append(" :%s: %s\n" % (commands, h[f]))
3261 else:
3261 else:
3262 rst.append(' :%s: %s\n' % (f, h[f]))
3262 rst.append(' :%s: %s\n' % (f, h[f]))
3263
3263
3264 if not name:
3264 if not name:
3265 exts = help.listexts(_('enabled extensions:'), extensions.enabled())
3265 exts = help.listexts(_('enabled extensions:'), extensions.enabled())
3266 if exts:
3266 if exts:
3267 rst.append('\n')
3267 rst.append('\n')
3268 rst.extend(exts)
3268 rst.extend(exts)
3269
3269
3270 rst.append(_("\nadditional help topics:\n\n"))
3270 rst.append(_("\nadditional help topics:\n\n"))
3271 topics = []
3271 topics = []
3272 for names, header, doc in help.helptable:
3272 for names, header, doc in help.helptable:
3273 topics.append((sorted(names, key=len, reverse=True)[0], header))
3273 topics.append((sorted(names, key=len, reverse=True)[0], header))
3274 for t, desc in topics:
3274 for t, desc in topics:
3275 rst.append(" :%s: %s\n" % (t, desc))
3275 rst.append(" :%s: %s\n" % (t, desc))
3276
3276
3277 optlist = []
3277 optlist = []
3278 if not ui.quiet:
3278 if not ui.quiet:
3279 if ui.verbose:
3279 if ui.verbose:
3280 optlist.append((_("global options:"), globalopts))
3280 optlist.append((_("global options:"), globalopts))
3281 if name == 'shortlist':
3281 if name == 'shortlist':
3282 optlist.append((_('use "hg help" for the full list '
3282 optlist.append((_('use "hg help" for the full list '
3283 'of commands'), ()))
3283 'of commands'), ()))
3284 else:
3284 else:
3285 if name == 'shortlist':
3285 if name == 'shortlist':
3286 msg = _('use "hg help" for the full list of commands '
3286 msg = _('use "hg help" for the full list of commands '
3287 'or "hg -v" for details')
3287 'or "hg -v" for details')
3288 elif name and not full:
3288 elif name and not full:
3289 msg = _('use "hg help %s" to show the full help '
3289 msg = _('use "hg help %s" to show the full help '
3290 'text') % name
3290 'text') % name
3291 else:
3291 else:
3292 msg = _('use "hg -v help%s" to show builtin aliases and '
3292 msg = _('use "hg -v help%s" to show builtin aliases and '
3293 'global options') % (name and " " + name or "")
3293 'global options') % (name and " " + name or "")
3294 optlist.append((msg, ()))
3294 optlist.append((msg, ()))
3295
3295
3296 if optlist:
3296 if optlist:
3297 for title, options in optlist:
3297 for title, options in optlist:
3298 rst.append('\n%s\n' % title)
3298 rst.append('\n%s\n' % title)
3299 if options:
3299 if options:
3300 rst.append('\n%s\n' % help.optrst(options, ui.verbose))
3300 rst.append('\n%s\n' % help.optrst(options, ui.verbose))
3301 return rst
3301 return rst
3302
3302
3303 def helptopic(name):
3303 def helptopic(name):
3304 for names, header, doc in help.helptable:
3304 for names, header, doc in help.helptable:
3305 if name in names:
3305 if name in names:
3306 break
3306 break
3307 else:
3307 else:
3308 raise error.UnknownCommand(name)
3308 raise error.UnknownCommand(name)
3309
3309
3310 rst = ["%s\n\n" % header]
3310 rst = ["%s\n\n" % header]
3311 # description
3311 # description
3312 if not doc:
3312 if not doc:
3313 rst.append(" %s\n" % _("(no help text available)"))
3313 rst.append(" %s\n" % _("(no help text available)"))
3314 if util.safehasattr(doc, '__call__'):
3314 if util.safehasattr(doc, '__call__'):
3315 rst += [" %s\n" % l for l in doc().splitlines()]
3315 rst += [" %s\n" % l for l in doc().splitlines()]
3316
3316
3317 try:
3317 try:
3318 cmdutil.findcmd(name, table)
3318 cmdutil.findcmd(name, table)
3319 rst.append(_('\nuse "hg help -c %s" to see help for '
3319 rst.append(_('\nuse "hg help -c %s" to see help for '
3320 'the %s command\n') % (name, name))
3320 'the %s command\n') % (name, name))
3321 except error.UnknownCommand:
3321 except error.UnknownCommand:
3322 pass
3322 pass
3323 return rst
3323 return rst
3324
3324
3325 def helpext(name):
3325 def helpext(name):
3326 try:
3326 try:
3327 mod = extensions.find(name)
3327 mod = extensions.find(name)
3328 doc = gettext(mod.__doc__) or _('no help text available')
3328 doc = gettext(mod.__doc__) or _('no help text available')
3329 except KeyError:
3329 except KeyError:
3330 mod = None
3330 mod = None
3331 doc = extensions.disabledext(name)
3331 doc = extensions.disabledext(name)
3332 if not doc:
3332 if not doc:
3333 raise error.UnknownCommand(name)
3333 raise error.UnknownCommand(name)
3334
3334
3335 if '\n' not in doc:
3335 if '\n' not in doc:
3336 head, tail = doc, ""
3336 head, tail = doc, ""
3337 else:
3337 else:
3338 head, tail = doc.split('\n', 1)
3338 head, tail = doc.split('\n', 1)
3339 rst = [_('%s extension - %s\n\n') % (name.split('.')[-1], head)]
3339 rst = [_('%s extension - %s\n\n') % (name.split('.')[-1], head)]
3340 if tail:
3340 if tail:
3341 rst.extend(tail.splitlines(True))
3341 rst.extend(tail.splitlines(True))
3342 rst.append('\n')
3342 rst.append('\n')
3343
3343
3344 if mod:
3344 if mod:
3345 try:
3345 try:
3346 ct = mod.cmdtable
3346 ct = mod.cmdtable
3347 except AttributeError:
3347 except AttributeError:
3348 ct = {}
3348 ct = {}
3349 modcmds = set([c.split('|', 1)[0] for c in ct])
3349 modcmds = set([c.split('|', 1)[0] for c in ct])
3350 rst.extend(helplist(modcmds.__contains__))
3350 rst.extend(helplist(modcmds.__contains__))
3351 else:
3351 else:
3352 rst.append(_('use "hg help extensions" for information on enabling '
3352 rst.append(_('use "hg help extensions" for information on enabling '
3353 'extensions\n'))
3353 'extensions\n'))
3354 return rst
3354 return rst
3355
3355
3356 def helpextcmd(name):
3356 def helpextcmd(name):
3357 cmd, ext, mod = extensions.disabledcmd(ui, name,
3357 cmd, ext, mod = extensions.disabledcmd(ui, name,
3358 ui.configbool('ui', 'strict'))
3358 ui.configbool('ui', 'strict'))
3359 doc = gettext(mod.__doc__).splitlines()[0]
3359 doc = gettext(mod.__doc__).splitlines()[0]
3360
3360
3361 rst = help.listexts(_("'%s' is provided by the following "
3361 rst = help.listexts(_("'%s' is provided by the following "
3362 "extension:") % cmd, {ext: doc}, indent=4)
3362 "extension:") % cmd, {ext: doc}, indent=4)
3363 rst.append('\n')
3363 rst.append('\n')
3364 rst.append(_('use "hg help extensions" for information on enabling '
3364 rst.append(_('use "hg help extensions" for information on enabling '
3365 'extensions\n'))
3365 'extensions\n'))
3366 return rst
3366 return rst
3367
3367
3368
3368
3369 rst = []
3369 rst = []
3370 kw = opts.get('keyword')
3370 kw = opts.get('keyword')
3371 if kw:
3371 if kw:
3372 matches = help.topicmatch(kw)
3372 matches = help.topicmatch(kw)
3373 for t, title in (('topics', _('Topics')),
3373 for t, title in (('topics', _('Topics')),
3374 ('commands', _('Commands')),
3374 ('commands', _('Commands')),
3375 ('extensions', _('Extensions')),
3375 ('extensions', _('Extensions')),
3376 ('extensioncommands', _('Extension Commands'))):
3376 ('extensioncommands', _('Extension Commands'))):
3377 if matches[t]:
3377 if matches[t]:
3378 rst.append('%s:\n\n' % title)
3378 rst.append('%s:\n\n' % title)
3379 rst.extend(minirst.maketable(sorted(matches[t]), 1))
3379 rst.extend(minirst.maketable(sorted(matches[t]), 1))
3380 rst.append('\n')
3380 rst.append('\n')
3381 elif name and name != 'shortlist':
3381 elif name and name != 'shortlist':
3382 i = None
3382 i = None
3383 if unknowncmd:
3383 if unknowncmd:
3384 queries = (helpextcmd,)
3384 queries = (helpextcmd,)
3385 elif opts.get('extension'):
3385 elif opts.get('extension'):
3386 queries = (helpext,)
3386 queries = (helpext,)
3387 elif opts.get('command'):
3387 elif opts.get('command'):
3388 queries = (helpcmd,)
3388 queries = (helpcmd,)
3389 else:
3389 else:
3390 queries = (helptopic, helpcmd, helpext, helpextcmd)
3390 queries = (helptopic, helpcmd, helpext, helpextcmd)
3391 for f in queries:
3391 for f in queries:
3392 try:
3392 try:
3393 rst = f(name)
3393 rst = f(name)
3394 i = None
3394 i = None
3395 break
3395 break
3396 except error.UnknownCommand, inst:
3396 except error.UnknownCommand, inst:
3397 i = inst
3397 i = inst
3398 if i:
3398 if i:
3399 raise i
3399 raise i
3400 else:
3400 else:
3401 # program name
3401 # program name
3402 if not ui.quiet:
3402 if not ui.quiet:
3403 rst = [_("Mercurial Distributed SCM\n"), '\n']
3403 rst = [_("Mercurial Distributed SCM\n"), '\n']
3404 rst.extend(helplist())
3404 rst.extend(helplist())
3405
3405
3406 keep = ui.verbose and ['verbose'] or []
3406 keep = ui.verbose and ['verbose'] or []
3407 formatted, pruned = minirst.format(''.join(rst), textwidth, keep=keep)
3407 formatted, pruned = minirst.format(''.join(rst), textwidth, keep=keep)
3408 ui.write(formatted)
3408 ui.write(formatted)
3409
3409
3410
3410
3411 @command('identify|id',
3411 @command('identify|id',
3412 [('r', 'rev', '',
3412 [('r', 'rev', '',
3413 _('identify the specified revision'), _('REV')),
3413 _('identify the specified revision'), _('REV')),
3414 ('n', 'num', None, _('show local revision number')),
3414 ('n', 'num', None, _('show local revision number')),
3415 ('i', 'id', None, _('show global revision id')),
3415 ('i', 'id', None, _('show global revision id')),
3416 ('b', 'branch', None, _('show branch')),
3416 ('b', 'branch', None, _('show branch')),
3417 ('t', 'tags', None, _('show tags')),
3417 ('t', 'tags', None, _('show tags')),
3418 ('B', 'bookmarks', None, _('show bookmarks')),
3418 ('B', 'bookmarks', None, _('show bookmarks')),
3419 ] + remoteopts,
3419 ] + remoteopts,
3420 _('[-nibtB] [-r REV] [SOURCE]'))
3420 _('[-nibtB] [-r REV] [SOURCE]'))
3421 def identify(ui, repo, source=None, rev=None,
3421 def identify(ui, repo, source=None, rev=None,
3422 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3422 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3423 """identify the working copy or specified revision
3423 """identify the working copy or specified revision
3424
3424
3425 Print a summary identifying the repository state at REV using one or
3425 Print a summary identifying the repository state at REV using one or
3426 two parent hash identifiers, followed by a "+" if the working
3426 two parent hash identifiers, followed by a "+" if the working
3427 directory has uncommitted changes, the branch name (if not default),
3427 directory has uncommitted changes, the branch name (if not default),
3428 a list of tags, and a list of bookmarks.
3428 a list of tags, and a list of bookmarks.
3429
3429
3430 When REV is not given, print a summary of the current state of the
3430 When REV is not given, print a summary of the current state of the
3431 repository.
3431 repository.
3432
3432
3433 Specifying a path to a repository root or Mercurial bundle will
3433 Specifying a path to a repository root or Mercurial bundle will
3434 cause lookup to operate on that repository/bundle.
3434 cause lookup to operate on that repository/bundle.
3435
3435
3436 .. container:: verbose
3436 .. container:: verbose
3437
3437
3438 Examples:
3438 Examples:
3439
3439
3440 - generate a build identifier for the working directory::
3440 - generate a build identifier for the working directory::
3441
3441
3442 hg id --id > build-id.dat
3442 hg id --id > build-id.dat
3443
3443
3444 - find the revision corresponding to a tag::
3444 - find the revision corresponding to a tag::
3445
3445
3446 hg id -n -r 1.3
3446 hg id -n -r 1.3
3447
3447
3448 - check the most recent revision of a remote repository::
3448 - check the most recent revision of a remote repository::
3449
3449
3450 hg id -r tip http://selenic.com/hg/
3450 hg id -r tip http://selenic.com/hg/
3451
3451
3452 Returns 0 if successful.
3452 Returns 0 if successful.
3453 """
3453 """
3454
3454
3455 if not repo and not source:
3455 if not repo and not source:
3456 raise util.Abort(_("there is no Mercurial repository here "
3456 raise util.Abort(_("there is no Mercurial repository here "
3457 "(.hg not found)"))
3457 "(.hg not found)"))
3458
3458
3459 hexfunc = ui.debugflag and hex or short
3459 hexfunc = ui.debugflag and hex or short
3460 default = not (num or id or branch or tags or bookmarks)
3460 default = not (num or id or branch or tags or bookmarks)
3461 output = []
3461 output = []
3462 revs = []
3462 revs = []
3463
3463
3464 if source:
3464 if source:
3465 source, branches = hg.parseurl(ui.expandpath(source))
3465 source, branches = hg.parseurl(ui.expandpath(source))
3466 repo = hg.peer(ui, opts, source)
3466 peer = hg.peer(ui, opts, source)
3467 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3467 repo = peer.local()
3468
3468 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3469 if not repo.local():
3469
3470 if not repo:
3470 if num or branch or tags:
3471 if num or branch or tags:
3471 raise util.Abort(
3472 raise util.Abort(
3472 _("can't query remote revision number, branch, or tags"))
3473 _("can't query remote revision number, branch, or tags"))
3473 if not rev and revs:
3474 if not rev and revs:
3474 rev = revs[0]
3475 rev = revs[0]
3475 if not rev:
3476 if not rev:
3476 rev = "tip"
3477 rev = "tip"
3477
3478
3478 remoterev = repo.lookup(rev)
3479 remoterev = peer.lookup(rev)
3479 if default or id:
3480 if default or id:
3480 output = [hexfunc(remoterev)]
3481 output = [hexfunc(remoterev)]
3481
3482
3482 def getbms():
3483 def getbms():
3483 bms = []
3484 bms = []
3484
3485
3485 if 'bookmarks' in repo.listkeys('namespaces'):
3486 if 'bookmarks' in peer.listkeys('namespaces'):
3486 hexremoterev = hex(remoterev)
3487 hexremoterev = hex(remoterev)
3487 bms = [bm for bm, bmr in repo.listkeys('bookmarks').iteritems()
3488 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3488 if bmr == hexremoterev]
3489 if bmr == hexremoterev]
3489
3490
3490 return bms
3491 return bms
3491
3492
3492 if bookmarks:
3493 if bookmarks:
3493 output.extend(getbms())
3494 output.extend(getbms())
3494 elif default and not ui.quiet:
3495 elif default and not ui.quiet:
3495 # multiple bookmarks for a single parent separated by '/'
3496 # multiple bookmarks for a single parent separated by '/'
3496 bm = '/'.join(getbms())
3497 bm = '/'.join(getbms())
3497 if bm:
3498 if bm:
3498 output.append(bm)
3499 output.append(bm)
3499 else:
3500 else:
3500 if not rev:
3501 if not rev:
3501 ctx = repo[None]
3502 ctx = repo[None]
3502 parents = ctx.parents()
3503 parents = ctx.parents()
3503 changed = ""
3504 changed = ""
3504 if default or id or num:
3505 if default or id or num:
3505 changed = util.any(repo.status()) and "+" or ""
3506 changed = util.any(repo.status()) and "+" or ""
3506 if default or id:
3507 if default or id:
3507 output = ["%s%s" %
3508 output = ["%s%s" %
3508 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3509 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3509 if num:
3510 if num:
3510 output.append("%s%s" %
3511 output.append("%s%s" %
3511 ('+'.join([str(p.rev()) for p in parents]), changed))
3512 ('+'.join([str(p.rev()) for p in parents]), changed))
3512 else:
3513 else:
3513 ctx = scmutil.revsingle(repo, rev)
3514 ctx = scmutil.revsingle(repo, rev)
3514 if default or id:
3515 if default or id:
3515 output = [hexfunc(ctx.node())]
3516 output = [hexfunc(ctx.node())]
3516 if num:
3517 if num:
3517 output.append(str(ctx.rev()))
3518 output.append(str(ctx.rev()))
3518
3519
3519 if default and not ui.quiet:
3520 if default and not ui.quiet:
3520 b = ctx.branch()
3521 b = ctx.branch()
3521 if b != 'default':
3522 if b != 'default':
3522 output.append("(%s)" % b)
3523 output.append("(%s)" % b)
3523
3524
3524 # multiple tags for a single parent separated by '/'
3525 # multiple tags for a single parent separated by '/'
3525 t = '/'.join(ctx.tags())
3526 t = '/'.join(ctx.tags())
3526 if t:
3527 if t:
3527 output.append(t)
3528 output.append(t)
3528
3529
3529 # multiple bookmarks for a single parent separated by '/'
3530 # multiple bookmarks for a single parent separated by '/'
3530 bm = '/'.join(ctx.bookmarks())
3531 bm = '/'.join(ctx.bookmarks())
3531 if bm:
3532 if bm:
3532 output.append(bm)
3533 output.append(bm)
3533 else:
3534 else:
3534 if branch:
3535 if branch:
3535 output.append(ctx.branch())
3536 output.append(ctx.branch())
3536
3537
3537 if tags:
3538 if tags:
3538 output.extend(ctx.tags())
3539 output.extend(ctx.tags())
3539
3540
3540 if bookmarks:
3541 if bookmarks:
3541 output.extend(ctx.bookmarks())
3542 output.extend(ctx.bookmarks())
3542
3543
3543 ui.write("%s\n" % ' '.join(output))
3544 ui.write("%s\n" % ' '.join(output))
3544
3545
3545 @command('import|patch',
3546 @command('import|patch',
3546 [('p', 'strip', 1,
3547 [('p', 'strip', 1,
3547 _('directory strip option for patch. This has the same '
3548 _('directory strip option for patch. This has the same '
3548 'meaning as the corresponding patch option'), _('NUM')),
3549 'meaning as the corresponding patch option'), _('NUM')),
3549 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3550 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3550 ('e', 'edit', False, _('invoke editor on commit messages')),
3551 ('e', 'edit', False, _('invoke editor on commit messages')),
3551 ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
3552 ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
3552 ('', 'no-commit', None,
3553 ('', 'no-commit', None,
3553 _("don't commit, just update the working directory")),
3554 _("don't commit, just update the working directory")),
3554 ('', 'bypass', None,
3555 ('', 'bypass', None,
3555 _("apply patch without touching the working directory")),
3556 _("apply patch without touching the working directory")),
3556 ('', 'exact', None,
3557 ('', 'exact', None,
3557 _('apply patch to the nodes from which it was generated')),
3558 _('apply patch to the nodes from which it was generated')),
3558 ('', 'import-branch', None,
3559 ('', 'import-branch', None,
3559 _('use any branch information in patch (implied by --exact)'))] +
3560 _('use any branch information in patch (implied by --exact)'))] +
3560 commitopts + commitopts2 + similarityopts,
3561 commitopts + commitopts2 + similarityopts,
3561 _('[OPTION]... PATCH...'))
3562 _('[OPTION]... PATCH...'))
3562 def import_(ui, repo, patch1=None, *patches, **opts):
3563 def import_(ui, repo, patch1=None, *patches, **opts):
3563 """import an ordered set of patches
3564 """import an ordered set of patches
3564
3565
3565 Import a list of patches and commit them individually (unless
3566 Import a list of patches and commit them individually (unless
3566 --no-commit is specified).
3567 --no-commit is specified).
3567
3568
3568 If there are outstanding changes in the working directory, import
3569 If there are outstanding changes in the working directory, import
3569 will abort unless given the -f/--force flag.
3570 will abort unless given the -f/--force flag.
3570
3571
3571 You can import a patch straight from a mail message. Even patches
3572 You can import a patch straight from a mail message. Even patches
3572 as attachments work (to use the body part, it must have type
3573 as attachments work (to use the body part, it must have type
3573 text/plain or text/x-patch). From and Subject headers of email
3574 text/plain or text/x-patch). From and Subject headers of email
3574 message are used as default committer and commit message. All
3575 message are used as default committer and commit message. All
3575 text/plain body parts before first diff are added to commit
3576 text/plain body parts before first diff are added to commit
3576 message.
3577 message.
3577
3578
3578 If the imported patch was generated by :hg:`export`, user and
3579 If the imported patch was generated by :hg:`export`, user and
3579 description from patch override values from message headers and
3580 description from patch override values from message headers and
3580 body. Values given on command line with -m/--message and -u/--user
3581 body. Values given on command line with -m/--message and -u/--user
3581 override these.
3582 override these.
3582
3583
3583 If --exact is specified, import will set the working directory to
3584 If --exact is specified, import will set the working directory to
3584 the parent of each patch before applying it, and will abort if the
3585 the parent of each patch before applying it, and will abort if the
3585 resulting changeset has a different ID than the one recorded in
3586 resulting changeset has a different ID than the one recorded in
3586 the patch. This may happen due to character set problems or other
3587 the patch. This may happen due to character set problems or other
3587 deficiencies in the text patch format.
3588 deficiencies in the text patch format.
3588
3589
3589 Use --bypass to apply and commit patches directly to the
3590 Use --bypass to apply and commit patches directly to the
3590 repository, not touching the working directory. Without --exact,
3591 repository, not touching the working directory. Without --exact,
3591 patches will be applied on top of the working directory parent
3592 patches will be applied on top of the working directory parent
3592 revision.
3593 revision.
3593
3594
3594 With -s/--similarity, hg will attempt to discover renames and
3595 With -s/--similarity, hg will attempt to discover renames and
3595 copies in the patch in the same way as :hg:`addremove`.
3596 copies in the patch in the same way as :hg:`addremove`.
3596
3597
3597 To read a patch from standard input, use "-" as the patch name. If
3598 To read a patch from standard input, use "-" as the patch name. If
3598 a URL is specified, the patch will be downloaded from it.
3599 a URL is specified, the patch will be downloaded from it.
3599 See :hg:`help dates` for a list of formats valid for -d/--date.
3600 See :hg:`help dates` for a list of formats valid for -d/--date.
3600
3601
3601 .. container:: verbose
3602 .. container:: verbose
3602
3603
3603 Examples:
3604 Examples:
3604
3605
3605 - import a traditional patch from a website and detect renames::
3606 - import a traditional patch from a website and detect renames::
3606
3607
3607 hg import -s 80 http://example.com/bugfix.patch
3608 hg import -s 80 http://example.com/bugfix.patch
3608
3609
3609 - import a changeset from an hgweb server::
3610 - import a changeset from an hgweb server::
3610
3611
3611 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
3612 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
3612
3613
3613 - import all the patches in an Unix-style mbox::
3614 - import all the patches in an Unix-style mbox::
3614
3615
3615 hg import incoming-patches.mbox
3616 hg import incoming-patches.mbox
3616
3617
3617 - attempt to exactly restore an exported changeset (not always
3618 - attempt to exactly restore an exported changeset (not always
3618 possible)::
3619 possible)::
3619
3620
3620 hg import --exact proposed-fix.patch
3621 hg import --exact proposed-fix.patch
3621
3622
3622 Returns 0 on success.
3623 Returns 0 on success.
3623 """
3624 """
3624
3625
3625 if not patch1:
3626 if not patch1:
3626 raise util.Abort(_('need at least one patch to import'))
3627 raise util.Abort(_('need at least one patch to import'))
3627
3628
3628 patches = (patch1,) + patches
3629 patches = (patch1,) + patches
3629
3630
3630 date = opts.get('date')
3631 date = opts.get('date')
3631 if date:
3632 if date:
3632 opts['date'] = util.parsedate(date)
3633 opts['date'] = util.parsedate(date)
3633
3634
3634 editor = cmdutil.commiteditor
3635 editor = cmdutil.commiteditor
3635 if opts.get('edit'):
3636 if opts.get('edit'):
3636 editor = cmdutil.commitforceeditor
3637 editor = cmdutil.commitforceeditor
3637
3638
3638 update = not opts.get('bypass')
3639 update = not opts.get('bypass')
3639 if not update and opts.get('no_commit'):
3640 if not update and opts.get('no_commit'):
3640 raise util.Abort(_('cannot use --no-commit with --bypass'))
3641 raise util.Abort(_('cannot use --no-commit with --bypass'))
3641 try:
3642 try:
3642 sim = float(opts.get('similarity') or 0)
3643 sim = float(opts.get('similarity') or 0)
3643 except ValueError:
3644 except ValueError:
3644 raise util.Abort(_('similarity must be a number'))
3645 raise util.Abort(_('similarity must be a number'))
3645 if sim < 0 or sim > 100:
3646 if sim < 0 or sim > 100:
3646 raise util.Abort(_('similarity must be between 0 and 100'))
3647 raise util.Abort(_('similarity must be between 0 and 100'))
3647 if sim and not update:
3648 if sim and not update:
3648 raise util.Abort(_('cannot use --similarity with --bypass'))
3649 raise util.Abort(_('cannot use --similarity with --bypass'))
3649
3650
3650 if (opts.get('exact') or not opts.get('force')) and update:
3651 if (opts.get('exact') or not opts.get('force')) and update:
3651 cmdutil.bailifchanged(repo)
3652 cmdutil.bailifchanged(repo)
3652
3653
3653 base = opts["base"]
3654 base = opts["base"]
3654 strip = opts["strip"]
3655 strip = opts["strip"]
3655 wlock = lock = tr = None
3656 wlock = lock = tr = None
3656 msgs = []
3657 msgs = []
3657
3658
3658 def checkexact(repo, n, nodeid):
3659 def checkexact(repo, n, nodeid):
3659 if opts.get('exact') and hex(n) != nodeid:
3660 if opts.get('exact') and hex(n) != nodeid:
3660 repo.rollback()
3661 repo.rollback()
3661 raise util.Abort(_('patch is damaged or loses information'))
3662 raise util.Abort(_('patch is damaged or loses information'))
3662
3663
3663 def tryone(ui, hunk, parents):
3664 def tryone(ui, hunk, parents):
3664 tmpname, message, user, date, branch, nodeid, p1, p2 = \
3665 tmpname, message, user, date, branch, nodeid, p1, p2 = \
3665 patch.extract(ui, hunk)
3666 patch.extract(ui, hunk)
3666
3667
3667 if not tmpname:
3668 if not tmpname:
3668 return (None, None)
3669 return (None, None)
3669 msg = _('applied to working directory')
3670 msg = _('applied to working directory')
3670
3671
3671 try:
3672 try:
3672 cmdline_message = cmdutil.logmessage(ui, opts)
3673 cmdline_message = cmdutil.logmessage(ui, opts)
3673 if cmdline_message:
3674 if cmdline_message:
3674 # pickup the cmdline msg
3675 # pickup the cmdline msg
3675 message = cmdline_message
3676 message = cmdline_message
3676 elif message:
3677 elif message:
3677 # pickup the patch msg
3678 # pickup the patch msg
3678 message = message.strip()
3679 message = message.strip()
3679 else:
3680 else:
3680 # launch the editor
3681 # launch the editor
3681 message = None
3682 message = None
3682 ui.debug('message:\n%s\n' % message)
3683 ui.debug('message:\n%s\n' % message)
3683
3684
3684 if len(parents) == 1:
3685 if len(parents) == 1:
3685 parents.append(repo[nullid])
3686 parents.append(repo[nullid])
3686 if opts.get('exact'):
3687 if opts.get('exact'):
3687 if not nodeid or not p1:
3688 if not nodeid or not p1:
3688 raise util.Abort(_('not a Mercurial patch'))
3689 raise util.Abort(_('not a Mercurial patch'))
3689 p1 = repo[p1]
3690 p1 = repo[p1]
3690 p2 = repo[p2 or nullid]
3691 p2 = repo[p2 or nullid]
3691 elif p2:
3692 elif p2:
3692 try:
3693 try:
3693 p1 = repo[p1]
3694 p1 = repo[p1]
3694 p2 = repo[p2]
3695 p2 = repo[p2]
3695 # Without any options, consider p2 only if the
3696 # Without any options, consider p2 only if the
3696 # patch is being applied on top of the recorded
3697 # patch is being applied on top of the recorded
3697 # first parent.
3698 # first parent.
3698 if p1 != parents[0]:
3699 if p1 != parents[0]:
3699 p1 = parents[0]
3700 p1 = parents[0]
3700 p2 = repo[nullid]
3701 p2 = repo[nullid]
3701 except error.RepoError:
3702 except error.RepoError:
3702 p1, p2 = parents
3703 p1, p2 = parents
3703 else:
3704 else:
3704 p1, p2 = parents
3705 p1, p2 = parents
3705
3706
3706 n = None
3707 n = None
3707 if update:
3708 if update:
3708 if p1 != parents[0]:
3709 if p1 != parents[0]:
3709 hg.clean(repo, p1.node())
3710 hg.clean(repo, p1.node())
3710 if p2 != parents[1]:
3711 if p2 != parents[1]:
3711 repo.setparents(p1.node(), p2.node())
3712 repo.setparents(p1.node(), p2.node())
3712
3713
3713 if opts.get('exact') or opts.get('import_branch'):
3714 if opts.get('exact') or opts.get('import_branch'):
3714 repo.dirstate.setbranch(branch or 'default')
3715 repo.dirstate.setbranch(branch or 'default')
3715
3716
3716 files = set()
3717 files = set()
3717 patch.patch(ui, repo, tmpname, strip=strip, files=files,
3718 patch.patch(ui, repo, tmpname, strip=strip, files=files,
3718 eolmode=None, similarity=sim / 100.0)
3719 eolmode=None, similarity=sim / 100.0)
3719 files = list(files)
3720 files = list(files)
3720 if opts.get('no_commit'):
3721 if opts.get('no_commit'):
3721 if message:
3722 if message:
3722 msgs.append(message)
3723 msgs.append(message)
3723 else:
3724 else:
3724 if opts.get('exact') or p2:
3725 if opts.get('exact') or p2:
3725 # If you got here, you either use --force and know what
3726 # If you got here, you either use --force and know what
3726 # you are doing or used --exact or a merge patch while
3727 # you are doing or used --exact or a merge patch while
3727 # being updated to its first parent.
3728 # being updated to its first parent.
3728 m = None
3729 m = None
3729 else:
3730 else:
3730 m = scmutil.matchfiles(repo, files or [])
3731 m = scmutil.matchfiles(repo, files or [])
3731 n = repo.commit(message, opts.get('user') or user,
3732 n = repo.commit(message, opts.get('user') or user,
3732 opts.get('date') or date, match=m,
3733 opts.get('date') or date, match=m,
3733 editor=editor)
3734 editor=editor)
3734 checkexact(repo, n, nodeid)
3735 checkexact(repo, n, nodeid)
3735 else:
3736 else:
3736 if opts.get('exact') or opts.get('import_branch'):
3737 if opts.get('exact') or opts.get('import_branch'):
3737 branch = branch or 'default'
3738 branch = branch or 'default'
3738 else:
3739 else:
3739 branch = p1.branch()
3740 branch = p1.branch()
3740 store = patch.filestore()
3741 store = patch.filestore()
3741 try:
3742 try:
3742 files = set()
3743 files = set()
3743 try:
3744 try:
3744 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
3745 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
3745 files, eolmode=None)
3746 files, eolmode=None)
3746 except patch.PatchError, e:
3747 except patch.PatchError, e:
3747 raise util.Abort(str(e))
3748 raise util.Abort(str(e))
3748 memctx = patch.makememctx(repo, (p1.node(), p2.node()),
3749 memctx = patch.makememctx(repo, (p1.node(), p2.node()),
3749 message,
3750 message,
3750 opts.get('user') or user,
3751 opts.get('user') or user,
3751 opts.get('date') or date,
3752 opts.get('date') or date,
3752 branch, files, store,
3753 branch, files, store,
3753 editor=cmdutil.commiteditor)
3754 editor=cmdutil.commiteditor)
3754 repo.savecommitmessage(memctx.description())
3755 repo.savecommitmessage(memctx.description())
3755 n = memctx.commit()
3756 n = memctx.commit()
3756 checkexact(repo, n, nodeid)
3757 checkexact(repo, n, nodeid)
3757 finally:
3758 finally:
3758 store.close()
3759 store.close()
3759 if n:
3760 if n:
3760 # i18n: refers to a short changeset id
3761 # i18n: refers to a short changeset id
3761 msg = _('created %s') % short(n)
3762 msg = _('created %s') % short(n)
3762 return (msg, n)
3763 return (msg, n)
3763 finally:
3764 finally:
3764 os.unlink(tmpname)
3765 os.unlink(tmpname)
3765
3766
3766 try:
3767 try:
3767 try:
3768 try:
3768 wlock = repo.wlock()
3769 wlock = repo.wlock()
3769 if not opts.get('no_commit'):
3770 if not opts.get('no_commit'):
3770 lock = repo.lock()
3771 lock = repo.lock()
3771 tr = repo.transaction('import')
3772 tr = repo.transaction('import')
3772 parents = repo.parents()
3773 parents = repo.parents()
3773 for patchurl in patches:
3774 for patchurl in patches:
3774 if patchurl == '-':
3775 if patchurl == '-':
3775 ui.status(_('applying patch from stdin\n'))
3776 ui.status(_('applying patch from stdin\n'))
3776 patchfile = ui.fin
3777 patchfile = ui.fin
3777 patchurl = 'stdin' # for error message
3778 patchurl = 'stdin' # for error message
3778 else:
3779 else:
3779 patchurl = os.path.join(base, patchurl)
3780 patchurl = os.path.join(base, patchurl)
3780 ui.status(_('applying %s\n') % patchurl)
3781 ui.status(_('applying %s\n') % patchurl)
3781 patchfile = url.open(ui, patchurl)
3782 patchfile = url.open(ui, patchurl)
3782
3783
3783 haspatch = False
3784 haspatch = False
3784 for hunk in patch.split(patchfile):
3785 for hunk in patch.split(patchfile):
3785 (msg, node) = tryone(ui, hunk, parents)
3786 (msg, node) = tryone(ui, hunk, parents)
3786 if msg:
3787 if msg:
3787 haspatch = True
3788 haspatch = True
3788 ui.note(msg + '\n')
3789 ui.note(msg + '\n')
3789 if update or opts.get('exact'):
3790 if update or opts.get('exact'):
3790 parents = repo.parents()
3791 parents = repo.parents()
3791 else:
3792 else:
3792 parents = [repo[node]]
3793 parents = [repo[node]]
3793
3794
3794 if not haspatch:
3795 if not haspatch:
3795 raise util.Abort(_('%s: no diffs found') % patchurl)
3796 raise util.Abort(_('%s: no diffs found') % patchurl)
3796
3797
3797 if tr:
3798 if tr:
3798 tr.close()
3799 tr.close()
3799 if msgs:
3800 if msgs:
3800 repo.savecommitmessage('\n* * *\n'.join(msgs))
3801 repo.savecommitmessage('\n* * *\n'.join(msgs))
3801 except: # re-raises
3802 except: # re-raises
3802 # wlock.release() indirectly calls dirstate.write(): since
3803 # wlock.release() indirectly calls dirstate.write(): since
3803 # we're crashing, we do not want to change the working dir
3804 # we're crashing, we do not want to change the working dir
3804 # parent after all, so make sure it writes nothing
3805 # parent after all, so make sure it writes nothing
3805 repo.dirstate.invalidate()
3806 repo.dirstate.invalidate()
3806 raise
3807 raise
3807 finally:
3808 finally:
3808 if tr:
3809 if tr:
3809 tr.release()
3810 tr.release()
3810 release(lock, wlock)
3811 release(lock, wlock)
3811
3812
3812 @command('incoming|in',
3813 @command('incoming|in',
3813 [('f', 'force', None,
3814 [('f', 'force', None,
3814 _('run even if remote repository is unrelated')),
3815 _('run even if remote repository is unrelated')),
3815 ('n', 'newest-first', None, _('show newest record first')),
3816 ('n', 'newest-first', None, _('show newest record first')),
3816 ('', 'bundle', '',
3817 ('', 'bundle', '',
3817 _('file to store the bundles into'), _('FILE')),
3818 _('file to store the bundles into'), _('FILE')),
3818 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3819 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3819 ('B', 'bookmarks', False, _("compare bookmarks")),
3820 ('B', 'bookmarks', False, _("compare bookmarks")),
3820 ('b', 'branch', [],
3821 ('b', 'branch', [],
3821 _('a specific branch you would like to pull'), _('BRANCH')),
3822 _('a specific branch you would like to pull'), _('BRANCH')),
3822 ] + logopts + remoteopts + subrepoopts,
3823 ] + logopts + remoteopts + subrepoopts,
3823 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3824 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3824 def incoming(ui, repo, source="default", **opts):
3825 def incoming(ui, repo, source="default", **opts):
3825 """show new changesets found in source
3826 """show new changesets found in source
3826
3827
3827 Show new changesets found in the specified path/URL or the default
3828 Show new changesets found in the specified path/URL or the default
3828 pull location. These are the changesets that would have been pulled
3829 pull location. These are the changesets that would have been pulled
3829 if a pull at the time you issued this command.
3830 if a pull at the time you issued this command.
3830
3831
3831 For remote repository, using --bundle avoids downloading the
3832 For remote repository, using --bundle avoids downloading the
3832 changesets twice if the incoming is followed by a pull.
3833 changesets twice if the incoming is followed by a pull.
3833
3834
3834 See pull for valid source format details.
3835 See pull for valid source format details.
3835
3836
3836 Returns 0 if there are incoming changes, 1 otherwise.
3837 Returns 0 if there are incoming changes, 1 otherwise.
3837 """
3838 """
3838 if opts.get('graph'):
3839 if opts.get('graph'):
3839 cmdutil.checkunsupportedgraphflags([], opts)
3840 cmdutil.checkunsupportedgraphflags([], opts)
3840 def display(other, chlist, displayer):
3841 def display(other, chlist, displayer):
3841 revdag = cmdutil.graphrevs(other, chlist, opts)
3842 revdag = cmdutil.graphrevs(other, chlist, opts)
3842 showparents = [ctx.node() for ctx in repo[None].parents()]
3843 showparents = [ctx.node() for ctx in repo[None].parents()]
3843 cmdutil.displaygraph(ui, revdag, displayer, showparents,
3844 cmdutil.displaygraph(ui, revdag, displayer, showparents,
3844 graphmod.asciiedges)
3845 graphmod.asciiedges)
3845
3846
3846 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3847 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3847 return 0
3848 return 0
3848
3849
3849 if opts.get('bundle') and opts.get('subrepos'):
3850 if opts.get('bundle') and opts.get('subrepos'):
3850 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3851 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3851
3852
3852 if opts.get('bookmarks'):
3853 if opts.get('bookmarks'):
3853 source, branches = hg.parseurl(ui.expandpath(source),
3854 source, branches = hg.parseurl(ui.expandpath(source),
3854 opts.get('branch'))
3855 opts.get('branch'))
3855 other = hg.peer(repo, opts, source)
3856 other = hg.peer(repo, opts, source)
3856 if 'bookmarks' not in other.listkeys('namespaces'):
3857 if 'bookmarks' not in other.listkeys('namespaces'):
3857 ui.warn(_("remote doesn't support bookmarks\n"))
3858 ui.warn(_("remote doesn't support bookmarks\n"))
3858 return 0
3859 return 0
3859 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3860 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3860 return bookmarks.diff(ui, repo, other)
3861 return bookmarks.diff(ui, repo, other)
3861
3862
3862 repo._subtoppath = ui.expandpath(source)
3863 repo._subtoppath = ui.expandpath(source)
3863 try:
3864 try:
3864 return hg.incoming(ui, repo, source, opts)
3865 return hg.incoming(ui, repo, source, opts)
3865 finally:
3866 finally:
3866 del repo._subtoppath
3867 del repo._subtoppath
3867
3868
3868
3869
3869 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
3870 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
3870 def init(ui, dest=".", **opts):
3871 def init(ui, dest=".", **opts):
3871 """create a new repository in the given directory
3872 """create a new repository in the given directory
3872
3873
3873 Initialize a new repository in the given directory. If the given
3874 Initialize a new repository in the given directory. If the given
3874 directory does not exist, it will be created.
3875 directory does not exist, it will be created.
3875
3876
3876 If no directory is given, the current directory is used.
3877 If no directory is given, the current directory is used.
3877
3878
3878 It is possible to specify an ``ssh://`` URL as the destination.
3879 It is possible to specify an ``ssh://`` URL as the destination.
3879 See :hg:`help urls` for more information.
3880 See :hg:`help urls` for more information.
3880
3881
3881 Returns 0 on success.
3882 Returns 0 on success.
3882 """
3883 """
3883 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3884 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3884
3885
3885 @command('locate',
3886 @command('locate',
3886 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3887 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3887 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3888 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3888 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3889 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3889 ] + walkopts,
3890 ] + walkopts,
3890 _('[OPTION]... [PATTERN]...'))
3891 _('[OPTION]... [PATTERN]...'))
3891 def locate(ui, repo, *pats, **opts):
3892 def locate(ui, repo, *pats, **opts):
3892 """locate files matching specific patterns
3893 """locate files matching specific patterns
3893
3894
3894 Print files under Mercurial control in the working directory whose
3895 Print files under Mercurial control in the working directory whose
3895 names match the given patterns.
3896 names match the given patterns.
3896
3897
3897 By default, this command searches all directories in the working
3898 By default, this command searches all directories in the working
3898 directory. To search just the current directory and its
3899 directory. To search just the current directory and its
3899 subdirectories, use "--include .".
3900 subdirectories, use "--include .".
3900
3901
3901 If no patterns are given to match, this command prints the names
3902 If no patterns are given to match, this command prints the names
3902 of all files under Mercurial control in the working directory.
3903 of all files under Mercurial control in the working directory.
3903
3904
3904 If you want to feed the output of this command into the "xargs"
3905 If you want to feed the output of this command into the "xargs"
3905 command, use the -0 option to both this command and "xargs". This
3906 command, use the -0 option to both this command and "xargs". This
3906 will avoid the problem of "xargs" treating single filenames that
3907 will avoid the problem of "xargs" treating single filenames that
3907 contain whitespace as multiple filenames.
3908 contain whitespace as multiple filenames.
3908
3909
3909 Returns 0 if a match is found, 1 otherwise.
3910 Returns 0 if a match is found, 1 otherwise.
3910 """
3911 """
3911 end = opts.get('print0') and '\0' or '\n'
3912 end = opts.get('print0') and '\0' or '\n'
3912 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3913 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3913
3914
3914 ret = 1
3915 ret = 1
3915 m = scmutil.match(repo[rev], pats, opts, default='relglob')
3916 m = scmutil.match(repo[rev], pats, opts, default='relglob')
3916 m.bad = lambda x, y: False
3917 m.bad = lambda x, y: False
3917 for abs in repo[rev].walk(m):
3918 for abs in repo[rev].walk(m):
3918 if not rev and abs not in repo.dirstate:
3919 if not rev and abs not in repo.dirstate:
3919 continue
3920 continue
3920 if opts.get('fullpath'):
3921 if opts.get('fullpath'):
3921 ui.write(repo.wjoin(abs), end)
3922 ui.write(repo.wjoin(abs), end)
3922 else:
3923 else:
3923 ui.write(((pats and m.rel(abs)) or abs), end)
3924 ui.write(((pats and m.rel(abs)) or abs), end)
3924 ret = 0
3925 ret = 0
3925
3926
3926 return ret
3927 return ret
3927
3928
3928 @command('^log|history',
3929 @command('^log|history',
3929 [('f', 'follow', None,
3930 [('f', 'follow', None,
3930 _('follow changeset history, or file history across copies and renames')),
3931 _('follow changeset history, or file history across copies and renames')),
3931 ('', 'follow-first', None,
3932 ('', 'follow-first', None,
3932 _('only follow the first parent of merge changesets (DEPRECATED)')),
3933 _('only follow the first parent of merge changesets (DEPRECATED)')),
3933 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3934 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3934 ('C', 'copies', None, _('show copied files')),
3935 ('C', 'copies', None, _('show copied files')),
3935 ('k', 'keyword', [],
3936 ('k', 'keyword', [],
3936 _('do case-insensitive search for a given text'), _('TEXT')),
3937 _('do case-insensitive search for a given text'), _('TEXT')),
3937 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
3938 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
3938 ('', 'removed', None, _('include revisions where files were removed')),
3939 ('', 'removed', None, _('include revisions where files were removed')),
3939 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3940 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3940 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3941 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3941 ('', 'only-branch', [],
3942 ('', 'only-branch', [],
3942 _('show only changesets within the given named branch (DEPRECATED)'),
3943 _('show only changesets within the given named branch (DEPRECATED)'),
3943 _('BRANCH')),
3944 _('BRANCH')),
3944 ('b', 'branch', [],
3945 ('b', 'branch', [],
3945 _('show changesets within the given named branch'), _('BRANCH')),
3946 _('show changesets within the given named branch'), _('BRANCH')),
3946 ('P', 'prune', [],
3947 ('P', 'prune', [],
3947 _('do not display revision or any of its ancestors'), _('REV')),
3948 _('do not display revision or any of its ancestors'), _('REV')),
3948 ('', 'hidden', False, _('show hidden changesets (DEPRECATED)')),
3949 ('', 'hidden', False, _('show hidden changesets (DEPRECATED)')),
3949 ] + logopts + walkopts,
3950 ] + logopts + walkopts,
3950 _('[OPTION]... [FILE]'))
3951 _('[OPTION]... [FILE]'))
3951 def log(ui, repo, *pats, **opts):
3952 def log(ui, repo, *pats, **opts):
3952 """show revision history of entire repository or files
3953 """show revision history of entire repository or files
3953
3954
3954 Print the revision history of the specified files or the entire
3955 Print the revision history of the specified files or the entire
3955 project.
3956 project.
3956
3957
3957 If no revision range is specified, the default is ``tip:0`` unless
3958 If no revision range is specified, the default is ``tip:0`` unless
3958 --follow is set, in which case the working directory parent is
3959 --follow is set, in which case the working directory parent is
3959 used as the starting revision.
3960 used as the starting revision.
3960
3961
3961 File history is shown without following rename or copy history of
3962 File history is shown without following rename or copy history of
3962 files. Use -f/--follow with a filename to follow history across
3963 files. Use -f/--follow with a filename to follow history across
3963 renames and copies. --follow without a filename will only show
3964 renames and copies. --follow without a filename will only show
3964 ancestors or descendants of the starting revision.
3965 ancestors or descendants of the starting revision.
3965
3966
3966 By default this command prints revision number and changeset id,
3967 By default this command prints revision number and changeset id,
3967 tags, non-trivial parents, user, date and time, and a summary for
3968 tags, non-trivial parents, user, date and time, and a summary for
3968 each commit. When the -v/--verbose switch is used, the list of
3969 each commit. When the -v/--verbose switch is used, the list of
3969 changed files and full commit message are shown.
3970 changed files and full commit message are shown.
3970
3971
3971 .. note::
3972 .. note::
3972 log -p/--patch may generate unexpected diff output for merge
3973 log -p/--patch may generate unexpected diff output for merge
3973 changesets, as it will only compare the merge changeset against
3974 changesets, as it will only compare the merge changeset against
3974 its first parent. Also, only files different from BOTH parents
3975 its first parent. Also, only files different from BOTH parents
3975 will appear in files:.
3976 will appear in files:.
3976
3977
3977 .. note::
3978 .. note::
3978 for performance reasons, log FILE may omit duplicate changes
3979 for performance reasons, log FILE may omit duplicate changes
3979 made on branches and will not show deletions. To see all
3980 made on branches and will not show deletions. To see all
3980 changes including duplicates and deletions, use the --removed
3981 changes including duplicates and deletions, use the --removed
3981 switch.
3982 switch.
3982
3983
3983 .. container:: verbose
3984 .. container:: verbose
3984
3985
3985 Some examples:
3986 Some examples:
3986
3987
3987 - changesets with full descriptions and file lists::
3988 - changesets with full descriptions and file lists::
3988
3989
3989 hg log -v
3990 hg log -v
3990
3991
3991 - changesets ancestral to the working directory::
3992 - changesets ancestral to the working directory::
3992
3993
3993 hg log -f
3994 hg log -f
3994
3995
3995 - last 10 commits on the current branch::
3996 - last 10 commits on the current branch::
3996
3997
3997 hg log -l 10 -b .
3998 hg log -l 10 -b .
3998
3999
3999 - changesets showing all modifications of a file, including removals::
4000 - changesets showing all modifications of a file, including removals::
4000
4001
4001 hg log --removed file.c
4002 hg log --removed file.c
4002
4003
4003 - all changesets that touch a directory, with diffs, excluding merges::
4004 - all changesets that touch a directory, with diffs, excluding merges::
4004
4005
4005 hg log -Mp lib/
4006 hg log -Mp lib/
4006
4007
4007 - all revision numbers that match a keyword::
4008 - all revision numbers that match a keyword::
4008
4009
4009 hg log -k bug --template "{rev}\\n"
4010 hg log -k bug --template "{rev}\\n"
4010
4011
4011 - check if a given changeset is included is a tagged release::
4012 - check if a given changeset is included is a tagged release::
4012
4013
4013 hg log -r "a21ccf and ancestor(1.9)"
4014 hg log -r "a21ccf and ancestor(1.9)"
4014
4015
4015 - find all changesets by some user in a date range::
4016 - find all changesets by some user in a date range::
4016
4017
4017 hg log -k alice -d "may 2008 to jul 2008"
4018 hg log -k alice -d "may 2008 to jul 2008"
4018
4019
4019 - summary of all changesets after the last tag::
4020 - summary of all changesets after the last tag::
4020
4021
4021 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4022 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4022
4023
4023 See :hg:`help dates` for a list of formats valid for -d/--date.
4024 See :hg:`help dates` for a list of formats valid for -d/--date.
4024
4025
4025 See :hg:`help revisions` and :hg:`help revsets` for more about
4026 See :hg:`help revisions` and :hg:`help revsets` for more about
4026 specifying revisions.
4027 specifying revisions.
4027
4028
4028 See :hg:`help templates` for more about pre-packaged styles and
4029 See :hg:`help templates` for more about pre-packaged styles and
4029 specifying custom templates.
4030 specifying custom templates.
4030
4031
4031 Returns 0 on success.
4032 Returns 0 on success.
4032 """
4033 """
4033 if opts.get('graph'):
4034 if opts.get('graph'):
4034 return cmdutil.graphlog(ui, repo, *pats, **opts)
4035 return cmdutil.graphlog(ui, repo, *pats, **opts)
4035
4036
4036 matchfn = scmutil.match(repo[None], pats, opts)
4037 matchfn = scmutil.match(repo[None], pats, opts)
4037 limit = cmdutil.loglimit(opts)
4038 limit = cmdutil.loglimit(opts)
4038 count = 0
4039 count = 0
4039
4040
4040 getrenamed, endrev = None, None
4041 getrenamed, endrev = None, None
4041 if opts.get('copies'):
4042 if opts.get('copies'):
4042 if opts.get('rev'):
4043 if opts.get('rev'):
4043 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
4044 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
4044 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
4045 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
4045
4046
4046 df = False
4047 df = False
4047 if opts["date"]:
4048 if opts["date"]:
4048 df = util.matchdate(opts["date"])
4049 df = util.matchdate(opts["date"])
4049
4050
4050 branches = opts.get('branch', []) + opts.get('only_branch', [])
4051 branches = opts.get('branch', []) + opts.get('only_branch', [])
4051 opts['branch'] = [repo.lookupbranch(b) for b in branches]
4052 opts['branch'] = [repo.lookupbranch(b) for b in branches]
4052
4053
4053 displayer = cmdutil.show_changeset(ui, repo, opts, True)
4054 displayer = cmdutil.show_changeset(ui, repo, opts, True)
4054 def prep(ctx, fns):
4055 def prep(ctx, fns):
4055 rev = ctx.rev()
4056 rev = ctx.rev()
4056 parents = [p for p in repo.changelog.parentrevs(rev)
4057 parents = [p for p in repo.changelog.parentrevs(rev)
4057 if p != nullrev]
4058 if p != nullrev]
4058 if opts.get('no_merges') and len(parents) == 2:
4059 if opts.get('no_merges') and len(parents) == 2:
4059 return
4060 return
4060 if opts.get('only_merges') and len(parents) != 2:
4061 if opts.get('only_merges') and len(parents) != 2:
4061 return
4062 return
4062 if opts.get('branch') and ctx.branch() not in opts['branch']:
4063 if opts.get('branch') and ctx.branch() not in opts['branch']:
4063 return
4064 return
4064 if not opts.get('hidden') and ctx.hidden():
4065 if not opts.get('hidden') and ctx.hidden():
4065 return
4066 return
4066 if df and not df(ctx.date()[0]):
4067 if df and not df(ctx.date()[0]):
4067 return
4068 return
4068
4069
4069 lower = encoding.lower
4070 lower = encoding.lower
4070 if opts.get('user'):
4071 if opts.get('user'):
4071 luser = lower(ctx.user())
4072 luser = lower(ctx.user())
4072 for k in [lower(x) for x in opts['user']]:
4073 for k in [lower(x) for x in opts['user']]:
4073 if (k in luser):
4074 if (k in luser):
4074 break
4075 break
4075 else:
4076 else:
4076 return
4077 return
4077 if opts.get('keyword'):
4078 if opts.get('keyword'):
4078 luser = lower(ctx.user())
4079 luser = lower(ctx.user())
4079 ldesc = lower(ctx.description())
4080 ldesc = lower(ctx.description())
4080 lfiles = lower(" ".join(ctx.files()))
4081 lfiles = lower(" ".join(ctx.files()))
4081 for k in [lower(x) for x in opts['keyword']]:
4082 for k in [lower(x) for x in opts['keyword']]:
4082 if (k in luser or k in ldesc or k in lfiles):
4083 if (k in luser or k in ldesc or k in lfiles):
4083 break
4084 break
4084 else:
4085 else:
4085 return
4086 return
4086
4087
4087 copies = None
4088 copies = None
4088 if getrenamed is not None and rev:
4089 if getrenamed is not None and rev:
4089 copies = []
4090 copies = []
4090 for fn in ctx.files():
4091 for fn in ctx.files():
4091 rename = getrenamed(fn, rev)
4092 rename = getrenamed(fn, rev)
4092 if rename:
4093 if rename:
4093 copies.append((fn, rename[0]))
4094 copies.append((fn, rename[0]))
4094
4095
4095 revmatchfn = None
4096 revmatchfn = None
4096 if opts.get('patch') or opts.get('stat'):
4097 if opts.get('patch') or opts.get('stat'):
4097 if opts.get('follow') or opts.get('follow_first'):
4098 if opts.get('follow') or opts.get('follow_first'):
4098 # note: this might be wrong when following through merges
4099 # note: this might be wrong when following through merges
4099 revmatchfn = scmutil.match(repo[None], fns, default='path')
4100 revmatchfn = scmutil.match(repo[None], fns, default='path')
4100 else:
4101 else:
4101 revmatchfn = matchfn
4102 revmatchfn = matchfn
4102
4103
4103 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
4104 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
4104
4105
4105 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
4106 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
4106 if count == limit:
4107 if count == limit:
4107 break
4108 break
4108 if displayer.flush(ctx.rev()):
4109 if displayer.flush(ctx.rev()):
4109 count += 1
4110 count += 1
4110 displayer.close()
4111 displayer.close()
4111
4112
4112 @command('manifest',
4113 @command('manifest',
4113 [('r', 'rev', '', _('revision to display'), _('REV')),
4114 [('r', 'rev', '', _('revision to display'), _('REV')),
4114 ('', 'all', False, _("list files from all revisions"))],
4115 ('', 'all', False, _("list files from all revisions"))],
4115 _('[-r REV]'))
4116 _('[-r REV]'))
4116 def manifest(ui, repo, node=None, rev=None, **opts):
4117 def manifest(ui, repo, node=None, rev=None, **opts):
4117 """output the current or given revision of the project manifest
4118 """output the current or given revision of the project manifest
4118
4119
4119 Print a list of version controlled files for the given revision.
4120 Print a list of version controlled files for the given revision.
4120 If no revision is given, the first parent of the working directory
4121 If no revision is given, the first parent of the working directory
4121 is used, or the null revision if no revision is checked out.
4122 is used, or the null revision if no revision is checked out.
4122
4123
4123 With -v, print file permissions, symlink and executable bits.
4124 With -v, print file permissions, symlink and executable bits.
4124 With --debug, print file revision hashes.
4125 With --debug, print file revision hashes.
4125
4126
4126 If option --all is specified, the list of all files from all revisions
4127 If option --all is specified, the list of all files from all revisions
4127 is printed. This includes deleted and renamed files.
4128 is printed. This includes deleted and renamed files.
4128
4129
4129 Returns 0 on success.
4130 Returns 0 on success.
4130 """
4131 """
4131 if opts.get('all'):
4132 if opts.get('all'):
4132 if rev or node:
4133 if rev or node:
4133 raise util.Abort(_("can't specify a revision with --all"))
4134 raise util.Abort(_("can't specify a revision with --all"))
4134
4135
4135 res = []
4136 res = []
4136 prefix = "data/"
4137 prefix = "data/"
4137 suffix = ".i"
4138 suffix = ".i"
4138 plen = len(prefix)
4139 plen = len(prefix)
4139 slen = len(suffix)
4140 slen = len(suffix)
4140 lock = repo.lock()
4141 lock = repo.lock()
4141 try:
4142 try:
4142 for fn, b, size in repo.store.datafiles():
4143 for fn, b, size in repo.store.datafiles():
4143 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
4144 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
4144 res.append(fn[plen:-slen])
4145 res.append(fn[plen:-slen])
4145 finally:
4146 finally:
4146 lock.release()
4147 lock.release()
4147 for f in sorted(res):
4148 for f in sorted(res):
4148 ui.write("%s\n" % f)
4149 ui.write("%s\n" % f)
4149 return
4150 return
4150
4151
4151 if rev and node:
4152 if rev and node:
4152 raise util.Abort(_("please specify just one revision"))
4153 raise util.Abort(_("please specify just one revision"))
4153
4154
4154 if not node:
4155 if not node:
4155 node = rev
4156 node = rev
4156
4157
4157 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
4158 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
4158 ctx = scmutil.revsingle(repo, node)
4159 ctx = scmutil.revsingle(repo, node)
4159 for f in ctx:
4160 for f in ctx:
4160 if ui.debugflag:
4161 if ui.debugflag:
4161 ui.write("%40s " % hex(ctx.manifest()[f]))
4162 ui.write("%40s " % hex(ctx.manifest()[f]))
4162 if ui.verbose:
4163 if ui.verbose:
4163 ui.write(decor[ctx.flags(f)])
4164 ui.write(decor[ctx.flags(f)])
4164 ui.write("%s\n" % f)
4165 ui.write("%s\n" % f)
4165
4166
4166 @command('^merge',
4167 @command('^merge',
4167 [('f', 'force', None, _('force a merge with outstanding changes')),
4168 [('f', 'force', None, _('force a merge with outstanding changes')),
4168 ('r', 'rev', '', _('revision to merge'), _('REV')),
4169 ('r', 'rev', '', _('revision to merge'), _('REV')),
4169 ('P', 'preview', None,
4170 ('P', 'preview', None,
4170 _('review revisions to merge (no merge is performed)'))
4171 _('review revisions to merge (no merge is performed)'))
4171 ] + mergetoolopts,
4172 ] + mergetoolopts,
4172 _('[-P] [-f] [[-r] REV]'))
4173 _('[-P] [-f] [[-r] REV]'))
4173 def merge(ui, repo, node=None, **opts):
4174 def merge(ui, repo, node=None, **opts):
4174 """merge working directory with another revision
4175 """merge working directory with another revision
4175
4176
4176 The current working directory is updated with all changes made in
4177 The current working directory is updated with all changes made in
4177 the requested revision since the last common predecessor revision.
4178 the requested revision since the last common predecessor revision.
4178
4179
4179 Files that changed between either parent are marked as changed for
4180 Files that changed between either parent are marked as changed for
4180 the next commit and a commit must be performed before any further
4181 the next commit and a commit must be performed before any further
4181 updates to the repository are allowed. The next commit will have
4182 updates to the repository are allowed. The next commit will have
4182 two parents.
4183 two parents.
4183
4184
4184 ``--tool`` can be used to specify the merge tool used for file
4185 ``--tool`` can be used to specify the merge tool used for file
4185 merges. It overrides the HGMERGE environment variable and your
4186 merges. It overrides the HGMERGE environment variable and your
4186 configuration files. See :hg:`help merge-tools` for options.
4187 configuration files. See :hg:`help merge-tools` for options.
4187
4188
4188 If no revision is specified, the working directory's parent is a
4189 If no revision is specified, the working directory's parent is a
4189 head revision, and the current branch contains exactly one other
4190 head revision, and the current branch contains exactly one other
4190 head, the other head is merged with by default. Otherwise, an
4191 head, the other head is merged with by default. Otherwise, an
4191 explicit revision with which to merge with must be provided.
4192 explicit revision with which to merge with must be provided.
4192
4193
4193 :hg:`resolve` must be used to resolve unresolved files.
4194 :hg:`resolve` must be used to resolve unresolved files.
4194
4195
4195 To undo an uncommitted merge, use :hg:`update --clean .` which
4196 To undo an uncommitted merge, use :hg:`update --clean .` which
4196 will check out a clean copy of the original merge parent, losing
4197 will check out a clean copy of the original merge parent, losing
4197 all changes.
4198 all changes.
4198
4199
4199 Returns 0 on success, 1 if there are unresolved files.
4200 Returns 0 on success, 1 if there are unresolved files.
4200 """
4201 """
4201
4202
4202 if opts.get('rev') and node:
4203 if opts.get('rev') and node:
4203 raise util.Abort(_("please specify just one revision"))
4204 raise util.Abort(_("please specify just one revision"))
4204 if not node:
4205 if not node:
4205 node = opts.get('rev')
4206 node = opts.get('rev')
4206
4207
4207 if node:
4208 if node:
4208 node = scmutil.revsingle(repo, node).node()
4209 node = scmutil.revsingle(repo, node).node()
4209
4210
4210 if not node and repo._bookmarkcurrent:
4211 if not node and repo._bookmarkcurrent:
4211 bmheads = repo.bookmarkheads(repo._bookmarkcurrent)
4212 bmheads = repo.bookmarkheads(repo._bookmarkcurrent)
4212 curhead = repo[repo._bookmarkcurrent]
4213 curhead = repo[repo._bookmarkcurrent]
4213 if len(bmheads) == 2:
4214 if len(bmheads) == 2:
4214 if curhead == bmheads[0]:
4215 if curhead == bmheads[0]:
4215 node = bmheads[1]
4216 node = bmheads[1]
4216 else:
4217 else:
4217 node = bmheads[0]
4218 node = bmheads[0]
4218 elif len(bmheads) > 2:
4219 elif len(bmheads) > 2:
4219 raise util.Abort(_("multiple matching bookmarks to merge - "
4220 raise util.Abort(_("multiple matching bookmarks to merge - "
4220 "please merge with an explicit rev or bookmark"),
4221 "please merge with an explicit rev or bookmark"),
4221 hint=_("run 'hg heads' to see all heads"))
4222 hint=_("run 'hg heads' to see all heads"))
4222 elif len(bmheads) <= 1:
4223 elif len(bmheads) <= 1:
4223 raise util.Abort(_("no matching bookmark to merge - "
4224 raise util.Abort(_("no matching bookmark to merge - "
4224 "please merge with an explicit rev or bookmark"),
4225 "please merge with an explicit rev or bookmark"),
4225 hint=_("run 'hg heads' to see all heads"))
4226 hint=_("run 'hg heads' to see all heads"))
4226
4227
4227 if not node and not repo._bookmarkcurrent:
4228 if not node and not repo._bookmarkcurrent:
4228 branch = repo[None].branch()
4229 branch = repo[None].branch()
4229 bheads = repo.branchheads(branch)
4230 bheads = repo.branchheads(branch)
4230 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
4231 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
4231
4232
4232 if len(nbhs) > 2:
4233 if len(nbhs) > 2:
4233 raise util.Abort(_("branch '%s' has %d heads - "
4234 raise util.Abort(_("branch '%s' has %d heads - "
4234 "please merge with an explicit rev")
4235 "please merge with an explicit rev")
4235 % (branch, len(bheads)),
4236 % (branch, len(bheads)),
4236 hint=_("run 'hg heads .' to see heads"))
4237 hint=_("run 'hg heads .' to see heads"))
4237
4238
4238 parent = repo.dirstate.p1()
4239 parent = repo.dirstate.p1()
4239 if len(nbhs) == 1:
4240 if len(nbhs) == 1:
4240 if len(bheads) > 1:
4241 if len(bheads) > 1:
4241 raise util.Abort(_("heads are bookmarked - "
4242 raise util.Abort(_("heads are bookmarked - "
4242 "please merge with an explicit rev"),
4243 "please merge with an explicit rev"),
4243 hint=_("run 'hg heads' to see all heads"))
4244 hint=_("run 'hg heads' to see all heads"))
4244 if len(repo.heads()) > 1:
4245 if len(repo.heads()) > 1:
4245 raise util.Abort(_("branch '%s' has one head - "
4246 raise util.Abort(_("branch '%s' has one head - "
4246 "please merge with an explicit rev")
4247 "please merge with an explicit rev")
4247 % branch,
4248 % branch,
4248 hint=_("run 'hg heads' to see all heads"))
4249 hint=_("run 'hg heads' to see all heads"))
4249 msg, hint = _('nothing to merge'), None
4250 msg, hint = _('nothing to merge'), None
4250 if parent != repo.lookup(branch):
4251 if parent != repo.lookup(branch):
4251 hint = _("use 'hg update' instead")
4252 hint = _("use 'hg update' instead")
4252 raise util.Abort(msg, hint=hint)
4253 raise util.Abort(msg, hint=hint)
4253
4254
4254 if parent not in bheads:
4255 if parent not in bheads:
4255 raise util.Abort(_('working directory not at a head revision'),
4256 raise util.Abort(_('working directory not at a head revision'),
4256 hint=_("use 'hg update' or merge with an "
4257 hint=_("use 'hg update' or merge with an "
4257 "explicit revision"))
4258 "explicit revision"))
4258 if parent == nbhs[0]:
4259 if parent == nbhs[0]:
4259 node = nbhs[-1]
4260 node = nbhs[-1]
4260 else:
4261 else:
4261 node = nbhs[0]
4262 node = nbhs[0]
4262
4263
4263 if opts.get('preview'):
4264 if opts.get('preview'):
4264 # find nodes that are ancestors of p2 but not of p1
4265 # find nodes that are ancestors of p2 but not of p1
4265 p1 = repo.lookup('.')
4266 p1 = repo.lookup('.')
4266 p2 = repo.lookup(node)
4267 p2 = repo.lookup(node)
4267 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4268 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4268
4269
4269 displayer = cmdutil.show_changeset(ui, repo, opts)
4270 displayer = cmdutil.show_changeset(ui, repo, opts)
4270 for node in nodes:
4271 for node in nodes:
4271 displayer.show(repo[node])
4272 displayer.show(repo[node])
4272 displayer.close()
4273 displayer.close()
4273 return 0
4274 return 0
4274
4275
4275 try:
4276 try:
4276 # ui.forcemerge is an internal variable, do not document
4277 # ui.forcemerge is an internal variable, do not document
4277 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4278 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4278 return hg.merge(repo, node, force=opts.get('force'))
4279 return hg.merge(repo, node, force=opts.get('force'))
4279 finally:
4280 finally:
4280 ui.setconfig('ui', 'forcemerge', '')
4281 ui.setconfig('ui', 'forcemerge', '')
4281
4282
4282 @command('outgoing|out',
4283 @command('outgoing|out',
4283 [('f', 'force', None, _('run even when the destination is unrelated')),
4284 [('f', 'force', None, _('run even when the destination is unrelated')),
4284 ('r', 'rev', [],
4285 ('r', 'rev', [],
4285 _('a changeset intended to be included in the destination'), _('REV')),
4286 _('a changeset intended to be included in the destination'), _('REV')),
4286 ('n', 'newest-first', None, _('show newest record first')),
4287 ('n', 'newest-first', None, _('show newest record first')),
4287 ('B', 'bookmarks', False, _('compare bookmarks')),
4288 ('B', 'bookmarks', False, _('compare bookmarks')),
4288 ('b', 'branch', [], _('a specific branch you would like to push'),
4289 ('b', 'branch', [], _('a specific branch you would like to push'),
4289 _('BRANCH')),
4290 _('BRANCH')),
4290 ] + logopts + remoteopts + subrepoopts,
4291 ] + logopts + remoteopts + subrepoopts,
4291 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4292 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4292 def outgoing(ui, repo, dest=None, **opts):
4293 def outgoing(ui, repo, dest=None, **opts):
4293 """show changesets not found in the destination
4294 """show changesets not found in the destination
4294
4295
4295 Show changesets not found in the specified destination repository
4296 Show changesets not found in the specified destination repository
4296 or the default push location. These are the changesets that would
4297 or the default push location. These are the changesets that would
4297 be pushed if a push was requested.
4298 be pushed if a push was requested.
4298
4299
4299 See pull for details of valid destination formats.
4300 See pull for details of valid destination formats.
4300
4301
4301 Returns 0 if there are outgoing changes, 1 otherwise.
4302 Returns 0 if there are outgoing changes, 1 otherwise.
4302 """
4303 """
4303 if opts.get('graph'):
4304 if opts.get('graph'):
4304 cmdutil.checkunsupportedgraphflags([], opts)
4305 cmdutil.checkunsupportedgraphflags([], opts)
4305 o = hg._outgoing(ui, repo, dest, opts)
4306 o = hg._outgoing(ui, repo, dest, opts)
4306 if o is None:
4307 if o is None:
4307 return
4308 return
4308
4309
4309 revdag = cmdutil.graphrevs(repo, o, opts)
4310 revdag = cmdutil.graphrevs(repo, o, opts)
4310 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4311 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4311 showparents = [ctx.node() for ctx in repo[None].parents()]
4312 showparents = [ctx.node() for ctx in repo[None].parents()]
4312 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4313 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4313 graphmod.asciiedges)
4314 graphmod.asciiedges)
4314 return 0
4315 return 0
4315
4316
4316 if opts.get('bookmarks'):
4317 if opts.get('bookmarks'):
4317 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4318 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4318 dest, branches = hg.parseurl(dest, opts.get('branch'))
4319 dest, branches = hg.parseurl(dest, opts.get('branch'))
4319 other = hg.peer(repo, opts, dest)
4320 other = hg.peer(repo, opts, dest)
4320 if 'bookmarks' not in other.listkeys('namespaces'):
4321 if 'bookmarks' not in other.listkeys('namespaces'):
4321 ui.warn(_("remote doesn't support bookmarks\n"))
4322 ui.warn(_("remote doesn't support bookmarks\n"))
4322 return 0
4323 return 0
4323 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4324 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4324 return bookmarks.diff(ui, other, repo)
4325 return bookmarks.diff(ui, other, repo)
4325
4326
4326 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4327 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4327 try:
4328 try:
4328 return hg.outgoing(ui, repo, dest, opts)
4329 return hg.outgoing(ui, repo, dest, opts)
4329 finally:
4330 finally:
4330 del repo._subtoppath
4331 del repo._subtoppath
4331
4332
4332 @command('parents',
4333 @command('parents',
4333 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4334 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4334 ] + templateopts,
4335 ] + templateopts,
4335 _('[-r REV] [FILE]'))
4336 _('[-r REV] [FILE]'))
4336 def parents(ui, repo, file_=None, **opts):
4337 def parents(ui, repo, file_=None, **opts):
4337 """show the parents of the working directory or revision
4338 """show the parents of the working directory or revision
4338
4339
4339 Print the working directory's parent revisions. If a revision is
4340 Print the working directory's parent revisions. If a revision is
4340 given via -r/--rev, the parent of that revision will be printed.
4341 given via -r/--rev, the parent of that revision will be printed.
4341 If a file argument is given, the revision in which the file was
4342 If a file argument is given, the revision in which the file was
4342 last changed (before the working directory revision or the
4343 last changed (before the working directory revision or the
4343 argument to --rev if given) is printed.
4344 argument to --rev if given) is printed.
4344
4345
4345 Returns 0 on success.
4346 Returns 0 on success.
4346 """
4347 """
4347
4348
4348 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4349 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4349
4350
4350 if file_:
4351 if file_:
4351 m = scmutil.match(ctx, (file_,), opts)
4352 m = scmutil.match(ctx, (file_,), opts)
4352 if m.anypats() or len(m.files()) != 1:
4353 if m.anypats() or len(m.files()) != 1:
4353 raise util.Abort(_('can only specify an explicit filename'))
4354 raise util.Abort(_('can only specify an explicit filename'))
4354 file_ = m.files()[0]
4355 file_ = m.files()[0]
4355 filenodes = []
4356 filenodes = []
4356 for cp in ctx.parents():
4357 for cp in ctx.parents():
4357 if not cp:
4358 if not cp:
4358 continue
4359 continue
4359 try:
4360 try:
4360 filenodes.append(cp.filenode(file_))
4361 filenodes.append(cp.filenode(file_))
4361 except error.LookupError:
4362 except error.LookupError:
4362 pass
4363 pass
4363 if not filenodes:
4364 if not filenodes:
4364 raise util.Abort(_("'%s' not found in manifest!") % file_)
4365 raise util.Abort(_("'%s' not found in manifest!") % file_)
4365 fl = repo.file(file_)
4366 fl = repo.file(file_)
4366 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
4367 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
4367 else:
4368 else:
4368 p = [cp.node() for cp in ctx.parents()]
4369 p = [cp.node() for cp in ctx.parents()]
4369
4370
4370 displayer = cmdutil.show_changeset(ui, repo, opts)
4371 displayer = cmdutil.show_changeset(ui, repo, opts)
4371 for n in p:
4372 for n in p:
4372 if n != nullid:
4373 if n != nullid:
4373 displayer.show(repo[n])
4374 displayer.show(repo[n])
4374 displayer.close()
4375 displayer.close()
4375
4376
4376 @command('paths', [], _('[NAME]'))
4377 @command('paths', [], _('[NAME]'))
4377 def paths(ui, repo, search=None):
4378 def paths(ui, repo, search=None):
4378 """show aliases for remote repositories
4379 """show aliases for remote repositories
4379
4380
4380 Show definition of symbolic path name NAME. If no name is given,
4381 Show definition of symbolic path name NAME. If no name is given,
4381 show definition of all available names.
4382 show definition of all available names.
4382
4383
4383 Option -q/--quiet suppresses all output when searching for NAME
4384 Option -q/--quiet suppresses all output when searching for NAME
4384 and shows only the path names when listing all definitions.
4385 and shows only the path names when listing all definitions.
4385
4386
4386 Path names are defined in the [paths] section of your
4387 Path names are defined in the [paths] section of your
4387 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4388 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4388 repository, ``.hg/hgrc`` is used, too.
4389 repository, ``.hg/hgrc`` is used, too.
4389
4390
4390 The path names ``default`` and ``default-push`` have a special
4391 The path names ``default`` and ``default-push`` have a special
4391 meaning. When performing a push or pull operation, they are used
4392 meaning. When performing a push or pull operation, they are used
4392 as fallbacks if no location is specified on the command-line.
4393 as fallbacks if no location is specified on the command-line.
4393 When ``default-push`` is set, it will be used for push and
4394 When ``default-push`` is set, it will be used for push and
4394 ``default`` will be used for pull; otherwise ``default`` is used
4395 ``default`` will be used for pull; otherwise ``default`` is used
4395 as the fallback for both. When cloning a repository, the clone
4396 as the fallback for both. When cloning a repository, the clone
4396 source is written as ``default`` in ``.hg/hgrc``. Note that
4397 source is written as ``default`` in ``.hg/hgrc``. Note that
4397 ``default`` and ``default-push`` apply to all inbound (e.g.
4398 ``default`` and ``default-push`` apply to all inbound (e.g.
4398 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4399 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4399 :hg:`bundle`) operations.
4400 :hg:`bundle`) operations.
4400
4401
4401 See :hg:`help urls` for more information.
4402 See :hg:`help urls` for more information.
4402
4403
4403 Returns 0 on success.
4404 Returns 0 on success.
4404 """
4405 """
4405 if search:
4406 if search:
4406 for name, path in ui.configitems("paths"):
4407 for name, path in ui.configitems("paths"):
4407 if name == search:
4408 if name == search:
4408 ui.status("%s\n" % util.hidepassword(path))
4409 ui.status("%s\n" % util.hidepassword(path))
4409 return
4410 return
4410 if not ui.quiet:
4411 if not ui.quiet:
4411 ui.warn(_("not found!\n"))
4412 ui.warn(_("not found!\n"))
4412 return 1
4413 return 1
4413 else:
4414 else:
4414 for name, path in ui.configitems("paths"):
4415 for name, path in ui.configitems("paths"):
4415 if ui.quiet:
4416 if ui.quiet:
4416 ui.write("%s\n" % name)
4417 ui.write("%s\n" % name)
4417 else:
4418 else:
4418 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4419 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4419
4420
4420 @command('^phase',
4421 @command('^phase',
4421 [('p', 'public', False, _('set changeset phase to public')),
4422 [('p', 'public', False, _('set changeset phase to public')),
4422 ('d', 'draft', False, _('set changeset phase to draft')),
4423 ('d', 'draft', False, _('set changeset phase to draft')),
4423 ('s', 'secret', False, _('set changeset phase to secret')),
4424 ('s', 'secret', False, _('set changeset phase to secret')),
4424 ('f', 'force', False, _('allow to move boundary backward')),
4425 ('f', 'force', False, _('allow to move boundary backward')),
4425 ('r', 'rev', [], _('target revision'), _('REV')),
4426 ('r', 'rev', [], _('target revision'), _('REV')),
4426 ],
4427 ],
4427 _('[-p|-d|-s] [-f] [-r] REV...'))
4428 _('[-p|-d|-s] [-f] [-r] REV...'))
4428 def phase(ui, repo, *revs, **opts):
4429 def phase(ui, repo, *revs, **opts):
4429 """set or show the current phase name
4430 """set or show the current phase name
4430
4431
4431 With no argument, show the phase name of specified revisions.
4432 With no argument, show the phase name of specified revisions.
4432
4433
4433 With one of -p/--public, -d/--draft or -s/--secret, change the
4434 With one of -p/--public, -d/--draft or -s/--secret, change the
4434 phase value of the specified revisions.
4435 phase value of the specified revisions.
4435
4436
4436 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
4437 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
4437 lower phase to an higher phase. Phases are ordered as follows::
4438 lower phase to an higher phase. Phases are ordered as follows::
4438
4439
4439 public < draft < secret
4440 public < draft < secret
4440
4441
4441 Return 0 on success, 1 if no phases were changed or some could not
4442 Return 0 on success, 1 if no phases were changed or some could not
4442 be changed.
4443 be changed.
4443 """
4444 """
4444 # search for a unique phase argument
4445 # search for a unique phase argument
4445 targetphase = None
4446 targetphase = None
4446 for idx, name in enumerate(phases.phasenames):
4447 for idx, name in enumerate(phases.phasenames):
4447 if opts[name]:
4448 if opts[name]:
4448 if targetphase is not None:
4449 if targetphase is not None:
4449 raise util.Abort(_('only one phase can be specified'))
4450 raise util.Abort(_('only one phase can be specified'))
4450 targetphase = idx
4451 targetphase = idx
4451
4452
4452 # look for specified revision
4453 # look for specified revision
4453 revs = list(revs)
4454 revs = list(revs)
4454 revs.extend(opts['rev'])
4455 revs.extend(opts['rev'])
4455 if not revs:
4456 if not revs:
4456 raise util.Abort(_('no revisions specified'))
4457 raise util.Abort(_('no revisions specified'))
4457
4458
4458 revs = scmutil.revrange(repo, revs)
4459 revs = scmutil.revrange(repo, revs)
4459
4460
4460 lock = None
4461 lock = None
4461 ret = 0
4462 ret = 0
4462 if targetphase is None:
4463 if targetphase is None:
4463 # display
4464 # display
4464 for r in revs:
4465 for r in revs:
4465 ctx = repo[r]
4466 ctx = repo[r]
4466 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4467 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4467 else:
4468 else:
4468 lock = repo.lock()
4469 lock = repo.lock()
4469 try:
4470 try:
4470 # set phase
4471 # set phase
4471 if not revs:
4472 if not revs:
4472 raise util.Abort(_('empty revision set'))
4473 raise util.Abort(_('empty revision set'))
4473 nodes = [repo[r].node() for r in revs]
4474 nodes = [repo[r].node() for r in revs]
4474 olddata = repo._phasecache.getphaserevs(repo)[:]
4475 olddata = repo._phasecache.getphaserevs(repo)[:]
4475 phases.advanceboundary(repo, targetphase, nodes)
4476 phases.advanceboundary(repo, targetphase, nodes)
4476 if opts['force']:
4477 if opts['force']:
4477 phases.retractboundary(repo, targetphase, nodes)
4478 phases.retractboundary(repo, targetphase, nodes)
4478 finally:
4479 finally:
4479 lock.release()
4480 lock.release()
4480 newdata = repo._phasecache.getphaserevs(repo)
4481 newdata = repo._phasecache.getphaserevs(repo)
4481 changes = sum(o != newdata[i] for i, o in enumerate(olddata))
4482 changes = sum(o != newdata[i] for i, o in enumerate(olddata))
4482 rejected = [n for n in nodes
4483 rejected = [n for n in nodes
4483 if newdata[repo[n].rev()] < targetphase]
4484 if newdata[repo[n].rev()] < targetphase]
4484 if rejected:
4485 if rejected:
4485 ui.warn(_('cannot move %i changesets to a more permissive '
4486 ui.warn(_('cannot move %i changesets to a more permissive '
4486 'phase, use --force\n') % len(rejected))
4487 'phase, use --force\n') % len(rejected))
4487 ret = 1
4488 ret = 1
4488 if changes:
4489 if changes:
4489 msg = _('phase changed for %i changesets\n') % changes
4490 msg = _('phase changed for %i changesets\n') % changes
4490 if ret:
4491 if ret:
4491 ui.status(msg)
4492 ui.status(msg)
4492 else:
4493 else:
4493 ui.note(msg)
4494 ui.note(msg)
4494 else:
4495 else:
4495 ui.warn(_('no phases changed\n'))
4496 ui.warn(_('no phases changed\n'))
4496 ret = 1
4497 ret = 1
4497 return ret
4498 return ret
4498
4499
4499 def postincoming(ui, repo, modheads, optupdate, checkout):
4500 def postincoming(ui, repo, modheads, optupdate, checkout):
4500 if modheads == 0:
4501 if modheads == 0:
4501 return
4502 return
4502 if optupdate:
4503 if optupdate:
4503 movemarkfrom = repo['.'].node()
4504 movemarkfrom = repo['.'].node()
4504 try:
4505 try:
4505 ret = hg.update(repo, checkout)
4506 ret = hg.update(repo, checkout)
4506 except util.Abort, inst:
4507 except util.Abort, inst:
4507 ui.warn(_("not updating: %s\n") % str(inst))
4508 ui.warn(_("not updating: %s\n") % str(inst))
4508 return 0
4509 return 0
4509 if not ret and not checkout:
4510 if not ret and not checkout:
4510 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
4511 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
4511 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
4512 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
4512 return ret
4513 return ret
4513 if modheads > 1:
4514 if modheads > 1:
4514 currentbranchheads = len(repo.branchheads())
4515 currentbranchheads = len(repo.branchheads())
4515 if currentbranchheads == modheads:
4516 if currentbranchheads == modheads:
4516 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4517 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4517 elif currentbranchheads > 1:
4518 elif currentbranchheads > 1:
4518 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4519 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4519 "merge)\n"))
4520 "merge)\n"))
4520 else:
4521 else:
4521 ui.status(_("(run 'hg heads' to see heads)\n"))
4522 ui.status(_("(run 'hg heads' to see heads)\n"))
4522 else:
4523 else:
4523 ui.status(_("(run 'hg update' to get a working copy)\n"))
4524 ui.status(_("(run 'hg update' to get a working copy)\n"))
4524
4525
4525 @command('^pull',
4526 @command('^pull',
4526 [('u', 'update', None,
4527 [('u', 'update', None,
4527 _('update to new branch head if changesets were pulled')),
4528 _('update to new branch head if changesets were pulled')),
4528 ('f', 'force', None, _('run even when remote repository is unrelated')),
4529 ('f', 'force', None, _('run even when remote repository is unrelated')),
4529 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4530 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4530 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4531 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4531 ('b', 'branch', [], _('a specific branch you would like to pull'),
4532 ('b', 'branch', [], _('a specific branch you would like to pull'),
4532 _('BRANCH')),
4533 _('BRANCH')),
4533 ] + remoteopts,
4534 ] + remoteopts,
4534 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4535 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4535 def pull(ui, repo, source="default", **opts):
4536 def pull(ui, repo, source="default", **opts):
4536 """pull changes from the specified source
4537 """pull changes from the specified source
4537
4538
4538 Pull changes from a remote repository to a local one.
4539 Pull changes from a remote repository to a local one.
4539
4540
4540 This finds all changes from the repository at the specified path
4541 This finds all changes from the repository at the specified path
4541 or URL and adds them to a local repository (the current one unless
4542 or URL and adds them to a local repository (the current one unless
4542 -R is specified). By default, this does not update the copy of the
4543 -R is specified). By default, this does not update the copy of the
4543 project in the working directory.
4544 project in the working directory.
4544
4545
4545 Use :hg:`incoming` if you want to see what would have been added
4546 Use :hg:`incoming` if you want to see what would have been added
4546 by a pull at the time you issued this command. If you then decide
4547 by a pull at the time you issued this command. If you then decide
4547 to add those changes to the repository, you should use :hg:`pull
4548 to add those changes to the repository, you should use :hg:`pull
4548 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4549 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4549
4550
4550 If SOURCE is omitted, the 'default' path will be used.
4551 If SOURCE is omitted, the 'default' path will be used.
4551 See :hg:`help urls` for more information.
4552 See :hg:`help urls` for more information.
4552
4553
4553 Returns 0 on success, 1 if an update had unresolved files.
4554 Returns 0 on success, 1 if an update had unresolved files.
4554 """
4555 """
4555 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4556 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4556 other = hg.peer(repo, opts, source)
4557 other = hg.peer(repo, opts, source)
4557 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4558 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4558 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4559 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4559
4560
4560 if opts.get('bookmark'):
4561 if opts.get('bookmark'):
4561 if not revs:
4562 if not revs:
4562 revs = []
4563 revs = []
4563 rb = other.listkeys('bookmarks')
4564 rb = other.listkeys('bookmarks')
4564 for b in opts['bookmark']:
4565 for b in opts['bookmark']:
4565 if b not in rb:
4566 if b not in rb:
4566 raise util.Abort(_('remote bookmark %s not found!') % b)
4567 raise util.Abort(_('remote bookmark %s not found!') % b)
4567 revs.append(rb[b])
4568 revs.append(rb[b])
4568
4569
4569 if revs:
4570 if revs:
4570 try:
4571 try:
4571 revs = [other.lookup(rev) for rev in revs]
4572 revs = [other.lookup(rev) for rev in revs]
4572 except error.CapabilityError:
4573 except error.CapabilityError:
4573 err = _("other repository doesn't support revision lookup, "
4574 err = _("other repository doesn't support revision lookup, "
4574 "so a rev cannot be specified.")
4575 "so a rev cannot be specified.")
4575 raise util.Abort(err)
4576 raise util.Abort(err)
4576
4577
4577 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
4578 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
4578 bookmarks.updatefromremote(ui, repo, other, source)
4579 bookmarks.updatefromremote(ui, repo, other, source)
4579 if checkout:
4580 if checkout:
4580 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4581 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4581 repo._subtoppath = source
4582 repo._subtoppath = source
4582 try:
4583 try:
4583 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4584 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4584
4585
4585 finally:
4586 finally:
4586 del repo._subtoppath
4587 del repo._subtoppath
4587
4588
4588 # update specified bookmarks
4589 # update specified bookmarks
4589 if opts.get('bookmark'):
4590 if opts.get('bookmark'):
4590 for b in opts['bookmark']:
4591 for b in opts['bookmark']:
4591 # explicit pull overrides local bookmark if any
4592 # explicit pull overrides local bookmark if any
4592 ui.status(_("importing bookmark %s\n") % b)
4593 ui.status(_("importing bookmark %s\n") % b)
4593 repo._bookmarks[b] = repo[rb[b]].node()
4594 repo._bookmarks[b] = repo[rb[b]].node()
4594 bookmarks.write(repo)
4595 bookmarks.write(repo)
4595
4596
4596 return ret
4597 return ret
4597
4598
4598 @command('^push',
4599 @command('^push',
4599 [('f', 'force', None, _('force push')),
4600 [('f', 'force', None, _('force push')),
4600 ('r', 'rev', [],
4601 ('r', 'rev', [],
4601 _('a changeset intended to be included in the destination'),
4602 _('a changeset intended to be included in the destination'),
4602 _('REV')),
4603 _('REV')),
4603 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4604 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4604 ('b', 'branch', [],
4605 ('b', 'branch', [],
4605 _('a specific branch you would like to push'), _('BRANCH')),
4606 _('a specific branch you would like to push'), _('BRANCH')),
4606 ('', 'new-branch', False, _('allow pushing a new branch')),
4607 ('', 'new-branch', False, _('allow pushing a new branch')),
4607 ] + remoteopts,
4608 ] + remoteopts,
4608 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4609 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4609 def push(ui, repo, dest=None, **opts):
4610 def push(ui, repo, dest=None, **opts):
4610 """push changes to the specified destination
4611 """push changes to the specified destination
4611
4612
4612 Push changesets from the local repository to the specified
4613 Push changesets from the local repository to the specified
4613 destination.
4614 destination.
4614
4615
4615 This operation is symmetrical to pull: it is identical to a pull
4616 This operation is symmetrical to pull: it is identical to a pull
4616 in the destination repository from the current one.
4617 in the destination repository from the current one.
4617
4618
4618 By default, push will not allow creation of new heads at the
4619 By default, push will not allow creation of new heads at the
4619 destination, since multiple heads would make it unclear which head
4620 destination, since multiple heads would make it unclear which head
4620 to use. In this situation, it is recommended to pull and merge
4621 to use. In this situation, it is recommended to pull and merge
4621 before pushing.
4622 before pushing.
4622
4623
4623 Use --new-branch if you want to allow push to create a new named
4624 Use --new-branch if you want to allow push to create a new named
4624 branch that is not present at the destination. This allows you to
4625 branch that is not present at the destination. This allows you to
4625 only create a new branch without forcing other changes.
4626 only create a new branch without forcing other changes.
4626
4627
4627 Use -f/--force to override the default behavior and push all
4628 Use -f/--force to override the default behavior and push all
4628 changesets on all branches.
4629 changesets on all branches.
4629
4630
4630 If -r/--rev is used, the specified revision and all its ancestors
4631 If -r/--rev is used, the specified revision and all its ancestors
4631 will be pushed to the remote repository.
4632 will be pushed to the remote repository.
4632
4633
4633 If -B/--bookmark is used, the specified bookmarked revision, its
4634 If -B/--bookmark is used, the specified bookmarked revision, its
4634 ancestors, and the bookmark will be pushed to the remote
4635 ancestors, and the bookmark will be pushed to the remote
4635 repository.
4636 repository.
4636
4637
4637 Please see :hg:`help urls` for important details about ``ssh://``
4638 Please see :hg:`help urls` for important details about ``ssh://``
4638 URLs. If DESTINATION is omitted, a default path will be used.
4639 URLs. If DESTINATION is omitted, a default path will be used.
4639
4640
4640 Returns 0 if push was successful, 1 if nothing to push.
4641 Returns 0 if push was successful, 1 if nothing to push.
4641 """
4642 """
4642
4643
4643 if opts.get('bookmark'):
4644 if opts.get('bookmark'):
4644 for b in opts['bookmark']:
4645 for b in opts['bookmark']:
4645 # translate -B options to -r so changesets get pushed
4646 # translate -B options to -r so changesets get pushed
4646 if b in repo._bookmarks:
4647 if b in repo._bookmarks:
4647 opts.setdefault('rev', []).append(b)
4648 opts.setdefault('rev', []).append(b)
4648 else:
4649 else:
4649 # if we try to push a deleted bookmark, translate it to null
4650 # if we try to push a deleted bookmark, translate it to null
4650 # this lets simultaneous -r, -b options continue working
4651 # this lets simultaneous -r, -b options continue working
4651 opts.setdefault('rev', []).append("null")
4652 opts.setdefault('rev', []).append("null")
4652
4653
4653 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4654 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4654 dest, branches = hg.parseurl(dest, opts.get('branch'))
4655 dest, branches = hg.parseurl(dest, opts.get('branch'))
4655 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4656 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4656 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4657 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4657 other = hg.peer(repo, opts, dest)
4658 other = hg.peer(repo, opts, dest)
4658 if revs:
4659 if revs:
4659 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4660 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4660
4661
4661 repo._subtoppath = dest
4662 repo._subtoppath = dest
4662 try:
4663 try:
4663 # push subrepos depth-first for coherent ordering
4664 # push subrepos depth-first for coherent ordering
4664 c = repo['']
4665 c = repo['']
4665 subs = c.substate # only repos that are committed
4666 subs = c.substate # only repos that are committed
4666 for s in sorted(subs):
4667 for s in sorted(subs):
4667 if c.sub(s).push(opts) == 0:
4668 if c.sub(s).push(opts) == 0:
4668 return False
4669 return False
4669 finally:
4670 finally:
4670 del repo._subtoppath
4671 del repo._subtoppath
4671 result = repo.push(other, opts.get('force'), revs=revs,
4672 result = repo.push(other, opts.get('force'), revs=revs,
4672 newbranch=opts.get('new_branch'))
4673 newbranch=opts.get('new_branch'))
4673
4674
4674 result = not result
4675 result = not result
4675
4676
4676 if opts.get('bookmark'):
4677 if opts.get('bookmark'):
4677 rb = other.listkeys('bookmarks')
4678 rb = other.listkeys('bookmarks')
4678 for b in opts['bookmark']:
4679 for b in opts['bookmark']:
4679 # explicit push overrides remote bookmark if any
4680 # explicit push overrides remote bookmark if any
4680 if b in repo._bookmarks:
4681 if b in repo._bookmarks:
4681 ui.status(_("exporting bookmark %s\n") % b)
4682 ui.status(_("exporting bookmark %s\n") % b)
4682 new = repo[b].hex()
4683 new = repo[b].hex()
4683 elif b in rb:
4684 elif b in rb:
4684 ui.status(_("deleting remote bookmark %s\n") % b)
4685 ui.status(_("deleting remote bookmark %s\n") % b)
4685 new = '' # delete
4686 new = '' # delete
4686 else:
4687 else:
4687 ui.warn(_('bookmark %s does not exist on the local '
4688 ui.warn(_('bookmark %s does not exist on the local '
4688 'or remote repository!\n') % b)
4689 'or remote repository!\n') % b)
4689 return 2
4690 return 2
4690 old = rb.get(b, '')
4691 old = rb.get(b, '')
4691 r = other.pushkey('bookmarks', b, old, new)
4692 r = other.pushkey('bookmarks', b, old, new)
4692 if not r:
4693 if not r:
4693 ui.warn(_('updating bookmark %s failed!\n') % b)
4694 ui.warn(_('updating bookmark %s failed!\n') % b)
4694 if not result:
4695 if not result:
4695 result = 2
4696 result = 2
4696
4697
4697 return result
4698 return result
4698
4699
4699 @command('recover', [])
4700 @command('recover', [])
4700 def recover(ui, repo):
4701 def recover(ui, repo):
4701 """roll back an interrupted transaction
4702 """roll back an interrupted transaction
4702
4703
4703 Recover from an interrupted commit or pull.
4704 Recover from an interrupted commit or pull.
4704
4705
4705 This command tries to fix the repository status after an
4706 This command tries to fix the repository status after an
4706 interrupted operation. It should only be necessary when Mercurial
4707 interrupted operation. It should only be necessary when Mercurial
4707 suggests it.
4708 suggests it.
4708
4709
4709 Returns 0 if successful, 1 if nothing to recover or verify fails.
4710 Returns 0 if successful, 1 if nothing to recover or verify fails.
4710 """
4711 """
4711 if repo.recover():
4712 if repo.recover():
4712 return hg.verify(repo)
4713 return hg.verify(repo)
4713 return 1
4714 return 1
4714
4715
4715 @command('^remove|rm',
4716 @command('^remove|rm',
4716 [('A', 'after', None, _('record delete for missing files')),
4717 [('A', 'after', None, _('record delete for missing files')),
4717 ('f', 'force', None,
4718 ('f', 'force', None,
4718 _('remove (and delete) file even if added or modified')),
4719 _('remove (and delete) file even if added or modified')),
4719 ] + walkopts,
4720 ] + walkopts,
4720 _('[OPTION]... FILE...'))
4721 _('[OPTION]... FILE...'))
4721 def remove(ui, repo, *pats, **opts):
4722 def remove(ui, repo, *pats, **opts):
4722 """remove the specified files on the next commit
4723 """remove the specified files on the next commit
4723
4724
4724 Schedule the indicated files for removal from the current branch.
4725 Schedule the indicated files for removal from the current branch.
4725
4726
4726 This command schedules the files to be removed at the next commit.
4727 This command schedules the files to be removed at the next commit.
4727 To undo a remove before that, see :hg:`revert`. To undo added
4728 To undo a remove before that, see :hg:`revert`. To undo added
4728 files, see :hg:`forget`.
4729 files, see :hg:`forget`.
4729
4730
4730 .. container:: verbose
4731 .. container:: verbose
4731
4732
4732 -A/--after can be used to remove only files that have already
4733 -A/--after can be used to remove only files that have already
4733 been deleted, -f/--force can be used to force deletion, and -Af
4734 been deleted, -f/--force can be used to force deletion, and -Af
4734 can be used to remove files from the next revision without
4735 can be used to remove files from the next revision without
4735 deleting them from the working directory.
4736 deleting them from the working directory.
4736
4737
4737 The following table details the behavior of remove for different
4738 The following table details the behavior of remove for different
4738 file states (columns) and option combinations (rows). The file
4739 file states (columns) and option combinations (rows). The file
4739 states are Added [A], Clean [C], Modified [M] and Missing [!]
4740 states are Added [A], Clean [C], Modified [M] and Missing [!]
4740 (as reported by :hg:`status`). The actions are Warn, Remove
4741 (as reported by :hg:`status`). The actions are Warn, Remove
4741 (from branch) and Delete (from disk):
4742 (from branch) and Delete (from disk):
4742
4743
4743 ======= == == == ==
4744 ======= == == == ==
4744 A C M !
4745 A C M !
4745 ======= == == == ==
4746 ======= == == == ==
4746 none W RD W R
4747 none W RD W R
4747 -f R RD RD R
4748 -f R RD RD R
4748 -A W W W R
4749 -A W W W R
4749 -Af R R R R
4750 -Af R R R R
4750 ======= == == == ==
4751 ======= == == == ==
4751
4752
4752 Note that remove never deletes files in Added [A] state from the
4753 Note that remove never deletes files in Added [A] state from the
4753 working directory, not even if option --force is specified.
4754 working directory, not even if option --force is specified.
4754
4755
4755 Returns 0 on success, 1 if any warnings encountered.
4756 Returns 0 on success, 1 if any warnings encountered.
4756 """
4757 """
4757
4758
4758 ret = 0
4759 ret = 0
4759 after, force = opts.get('after'), opts.get('force')
4760 after, force = opts.get('after'), opts.get('force')
4760 if not pats and not after:
4761 if not pats and not after:
4761 raise util.Abort(_('no files specified'))
4762 raise util.Abort(_('no files specified'))
4762
4763
4763 m = scmutil.match(repo[None], pats, opts)
4764 m = scmutil.match(repo[None], pats, opts)
4764 s = repo.status(match=m, clean=True)
4765 s = repo.status(match=m, clean=True)
4765 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
4766 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
4766
4767
4767 for f in m.files():
4768 for f in m.files():
4768 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
4769 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
4769 if os.path.exists(m.rel(f)):
4770 if os.path.exists(m.rel(f)):
4770 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
4771 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
4771 ret = 1
4772 ret = 1
4772
4773
4773 if force:
4774 if force:
4774 list = modified + deleted + clean + added
4775 list = modified + deleted + clean + added
4775 elif after:
4776 elif after:
4776 list = deleted
4777 list = deleted
4777 for f in modified + added + clean:
4778 for f in modified + added + clean:
4778 ui.warn(_('not removing %s: file still exists (use -f'
4779 ui.warn(_('not removing %s: file still exists (use -f'
4779 ' to force removal)\n') % m.rel(f))
4780 ' to force removal)\n') % m.rel(f))
4780 ret = 1
4781 ret = 1
4781 else:
4782 else:
4782 list = deleted + clean
4783 list = deleted + clean
4783 for f in modified:
4784 for f in modified:
4784 ui.warn(_('not removing %s: file is modified (use -f'
4785 ui.warn(_('not removing %s: file is modified (use -f'
4785 ' to force removal)\n') % m.rel(f))
4786 ' to force removal)\n') % m.rel(f))
4786 ret = 1
4787 ret = 1
4787 for f in added:
4788 for f in added:
4788 ui.warn(_('not removing %s: file has been marked for add'
4789 ui.warn(_('not removing %s: file has been marked for add'
4789 ' (use forget to undo)\n') % m.rel(f))
4790 ' (use forget to undo)\n') % m.rel(f))
4790 ret = 1
4791 ret = 1
4791
4792
4792 for f in sorted(list):
4793 for f in sorted(list):
4793 if ui.verbose or not m.exact(f):
4794 if ui.verbose or not m.exact(f):
4794 ui.status(_('removing %s\n') % m.rel(f))
4795 ui.status(_('removing %s\n') % m.rel(f))
4795
4796
4796 wlock = repo.wlock()
4797 wlock = repo.wlock()
4797 try:
4798 try:
4798 if not after:
4799 if not after:
4799 for f in list:
4800 for f in list:
4800 if f in added:
4801 if f in added:
4801 continue # we never unlink added files on remove
4802 continue # we never unlink added files on remove
4802 try:
4803 try:
4803 util.unlinkpath(repo.wjoin(f))
4804 util.unlinkpath(repo.wjoin(f))
4804 except OSError, inst:
4805 except OSError, inst:
4805 if inst.errno != errno.ENOENT:
4806 if inst.errno != errno.ENOENT:
4806 raise
4807 raise
4807 repo[None].forget(list)
4808 repo[None].forget(list)
4808 finally:
4809 finally:
4809 wlock.release()
4810 wlock.release()
4810
4811
4811 return ret
4812 return ret
4812
4813
4813 @command('rename|move|mv',
4814 @command('rename|move|mv',
4814 [('A', 'after', None, _('record a rename that has already occurred')),
4815 [('A', 'after', None, _('record a rename that has already occurred')),
4815 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4816 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4816 ] + walkopts + dryrunopts,
4817 ] + walkopts + dryrunopts,
4817 _('[OPTION]... SOURCE... DEST'))
4818 _('[OPTION]... SOURCE... DEST'))
4818 def rename(ui, repo, *pats, **opts):
4819 def rename(ui, repo, *pats, **opts):
4819 """rename files; equivalent of copy + remove
4820 """rename files; equivalent of copy + remove
4820
4821
4821 Mark dest as copies of sources; mark sources for deletion. If dest
4822 Mark dest as copies of sources; mark sources for deletion. If dest
4822 is a directory, copies are put in that directory. If dest is a
4823 is a directory, copies are put in that directory. If dest is a
4823 file, there can only be one source.
4824 file, there can only be one source.
4824
4825
4825 By default, this command copies the contents of files as they
4826 By default, this command copies the contents of files as they
4826 exist in the working directory. If invoked with -A/--after, the
4827 exist in the working directory. If invoked with -A/--after, the
4827 operation is recorded, but no copying is performed.
4828 operation is recorded, but no copying is performed.
4828
4829
4829 This command takes effect at the next commit. To undo a rename
4830 This command takes effect at the next commit. To undo a rename
4830 before that, see :hg:`revert`.
4831 before that, see :hg:`revert`.
4831
4832
4832 Returns 0 on success, 1 if errors are encountered.
4833 Returns 0 on success, 1 if errors are encountered.
4833 """
4834 """
4834 wlock = repo.wlock(False)
4835 wlock = repo.wlock(False)
4835 try:
4836 try:
4836 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4837 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4837 finally:
4838 finally:
4838 wlock.release()
4839 wlock.release()
4839
4840
4840 @command('resolve',
4841 @command('resolve',
4841 [('a', 'all', None, _('select all unresolved files')),
4842 [('a', 'all', None, _('select all unresolved files')),
4842 ('l', 'list', None, _('list state of files needing merge')),
4843 ('l', 'list', None, _('list state of files needing merge')),
4843 ('m', 'mark', None, _('mark files as resolved')),
4844 ('m', 'mark', None, _('mark files as resolved')),
4844 ('u', 'unmark', None, _('mark files as unresolved')),
4845 ('u', 'unmark', None, _('mark files as unresolved')),
4845 ('n', 'no-status', None, _('hide status prefix'))]
4846 ('n', 'no-status', None, _('hide status prefix'))]
4846 + mergetoolopts + walkopts,
4847 + mergetoolopts + walkopts,
4847 _('[OPTION]... [FILE]...'))
4848 _('[OPTION]... [FILE]...'))
4848 def resolve(ui, repo, *pats, **opts):
4849 def resolve(ui, repo, *pats, **opts):
4849 """redo merges or set/view the merge status of files
4850 """redo merges or set/view the merge status of files
4850
4851
4851 Merges with unresolved conflicts are often the result of
4852 Merges with unresolved conflicts are often the result of
4852 non-interactive merging using the ``internal:merge`` configuration
4853 non-interactive merging using the ``internal:merge`` configuration
4853 setting, or a command-line merge tool like ``diff3``. The resolve
4854 setting, or a command-line merge tool like ``diff3``. The resolve
4854 command is used to manage the files involved in a merge, after
4855 command is used to manage the files involved in a merge, after
4855 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4856 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4856 working directory must have two parents). See :hg:`help
4857 working directory must have two parents). See :hg:`help
4857 merge-tools` for information on configuring merge tools.
4858 merge-tools` for information on configuring merge tools.
4858
4859
4859 The resolve command can be used in the following ways:
4860 The resolve command can be used in the following ways:
4860
4861
4861 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4862 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4862 files, discarding any previous merge attempts. Re-merging is not
4863 files, discarding any previous merge attempts. Re-merging is not
4863 performed for files already marked as resolved. Use ``--all/-a``
4864 performed for files already marked as resolved. Use ``--all/-a``
4864 to select all unresolved files. ``--tool`` can be used to specify
4865 to select all unresolved files. ``--tool`` can be used to specify
4865 the merge tool used for the given files. It overrides the HGMERGE
4866 the merge tool used for the given files. It overrides the HGMERGE
4866 environment variable and your configuration files. Previous file
4867 environment variable and your configuration files. Previous file
4867 contents are saved with a ``.orig`` suffix.
4868 contents are saved with a ``.orig`` suffix.
4868
4869
4869 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4870 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4870 (e.g. after having manually fixed-up the files). The default is
4871 (e.g. after having manually fixed-up the files). The default is
4871 to mark all unresolved files.
4872 to mark all unresolved files.
4872
4873
4873 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4874 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4874 default is to mark all resolved files.
4875 default is to mark all resolved files.
4875
4876
4876 - :hg:`resolve -l`: list files which had or still have conflicts.
4877 - :hg:`resolve -l`: list files which had or still have conflicts.
4877 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4878 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4878
4879
4879 Note that Mercurial will not let you commit files with unresolved
4880 Note that Mercurial will not let you commit files with unresolved
4880 merge conflicts. You must use :hg:`resolve -m ...` before you can
4881 merge conflicts. You must use :hg:`resolve -m ...` before you can
4881 commit after a conflicting merge.
4882 commit after a conflicting merge.
4882
4883
4883 Returns 0 on success, 1 if any files fail a resolve attempt.
4884 Returns 0 on success, 1 if any files fail a resolve attempt.
4884 """
4885 """
4885
4886
4886 all, mark, unmark, show, nostatus = \
4887 all, mark, unmark, show, nostatus = \
4887 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
4888 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
4888
4889
4889 if (show and (mark or unmark)) or (mark and unmark):
4890 if (show and (mark or unmark)) or (mark and unmark):
4890 raise util.Abort(_("too many options specified"))
4891 raise util.Abort(_("too many options specified"))
4891 if pats and all:
4892 if pats and all:
4892 raise util.Abort(_("can't specify --all and patterns"))
4893 raise util.Abort(_("can't specify --all and patterns"))
4893 if not (all or pats or show or mark or unmark):
4894 if not (all or pats or show or mark or unmark):
4894 raise util.Abort(_('no files or directories specified; '
4895 raise util.Abort(_('no files or directories specified; '
4895 'use --all to remerge all files'))
4896 'use --all to remerge all files'))
4896
4897
4897 ms = mergemod.mergestate(repo)
4898 ms = mergemod.mergestate(repo)
4898 m = scmutil.match(repo[None], pats, opts)
4899 m = scmutil.match(repo[None], pats, opts)
4899 ret = 0
4900 ret = 0
4900
4901
4901 for f in ms:
4902 for f in ms:
4902 if m(f):
4903 if m(f):
4903 if show:
4904 if show:
4904 if nostatus:
4905 if nostatus:
4905 ui.write("%s\n" % f)
4906 ui.write("%s\n" % f)
4906 else:
4907 else:
4907 ui.write("%s %s\n" % (ms[f].upper(), f),
4908 ui.write("%s %s\n" % (ms[f].upper(), f),
4908 label='resolve.' +
4909 label='resolve.' +
4909 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
4910 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
4910 elif mark:
4911 elif mark:
4911 ms.mark(f, "r")
4912 ms.mark(f, "r")
4912 elif unmark:
4913 elif unmark:
4913 ms.mark(f, "u")
4914 ms.mark(f, "u")
4914 else:
4915 else:
4915 wctx = repo[None]
4916 wctx = repo[None]
4916 mctx = wctx.parents()[-1]
4917 mctx = wctx.parents()[-1]
4917
4918
4918 # backup pre-resolve (merge uses .orig for its own purposes)
4919 # backup pre-resolve (merge uses .orig for its own purposes)
4919 a = repo.wjoin(f)
4920 a = repo.wjoin(f)
4920 util.copyfile(a, a + ".resolve")
4921 util.copyfile(a, a + ".resolve")
4921
4922
4922 try:
4923 try:
4923 # resolve file
4924 # resolve file
4924 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4925 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4925 if ms.resolve(f, wctx, mctx):
4926 if ms.resolve(f, wctx, mctx):
4926 ret = 1
4927 ret = 1
4927 finally:
4928 finally:
4928 ui.setconfig('ui', 'forcemerge', '')
4929 ui.setconfig('ui', 'forcemerge', '')
4929
4930
4930 # replace filemerge's .orig file with our resolve file
4931 # replace filemerge's .orig file with our resolve file
4931 util.rename(a + ".resolve", a + ".orig")
4932 util.rename(a + ".resolve", a + ".orig")
4932
4933
4933 ms.commit()
4934 ms.commit()
4934 return ret
4935 return ret
4935
4936
4936 @command('revert',
4937 @command('revert',
4937 [('a', 'all', None, _('revert all changes when no arguments given')),
4938 [('a', 'all', None, _('revert all changes when no arguments given')),
4938 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4939 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4939 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4940 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4940 ('C', 'no-backup', None, _('do not save backup copies of files')),
4941 ('C', 'no-backup', None, _('do not save backup copies of files')),
4941 ] + walkopts + dryrunopts,
4942 ] + walkopts + dryrunopts,
4942 _('[OPTION]... [-r REV] [NAME]...'))
4943 _('[OPTION]... [-r REV] [NAME]...'))
4943 def revert(ui, repo, *pats, **opts):
4944 def revert(ui, repo, *pats, **opts):
4944 """restore files to their checkout state
4945 """restore files to their checkout state
4945
4946
4946 .. note::
4947 .. note::
4947
4948
4948 To check out earlier revisions, you should use :hg:`update REV`.
4949 To check out earlier revisions, you should use :hg:`update REV`.
4949 To cancel an uncommitted merge (and lose your changes), use
4950 To cancel an uncommitted merge (and lose your changes), use
4950 :hg:`update --clean .`.
4951 :hg:`update --clean .`.
4951
4952
4952 With no revision specified, revert the specified files or directories
4953 With no revision specified, revert the specified files or directories
4953 to the contents they had in the parent of the working directory.
4954 to the contents they had in the parent of the working directory.
4954 This restores the contents of files to an unmodified
4955 This restores the contents of files to an unmodified
4955 state and unschedules adds, removes, copies, and renames. If the
4956 state and unschedules adds, removes, copies, and renames. If the
4956 working directory has two parents, you must explicitly specify a
4957 working directory has two parents, you must explicitly specify a
4957 revision.
4958 revision.
4958
4959
4959 Using the -r/--rev or -d/--date options, revert the given files or
4960 Using the -r/--rev or -d/--date options, revert the given files or
4960 directories to their states as of a specific revision. Because
4961 directories to their states as of a specific revision. Because
4961 revert does not change the working directory parents, this will
4962 revert does not change the working directory parents, this will
4962 cause these files to appear modified. This can be helpful to "back
4963 cause these files to appear modified. This can be helpful to "back
4963 out" some or all of an earlier change. See :hg:`backout` for a
4964 out" some or all of an earlier change. See :hg:`backout` for a
4964 related method.
4965 related method.
4965
4966
4966 Modified files are saved with a .orig suffix before reverting.
4967 Modified files are saved with a .orig suffix before reverting.
4967 To disable these backups, use --no-backup.
4968 To disable these backups, use --no-backup.
4968
4969
4969 See :hg:`help dates` for a list of formats valid for -d/--date.
4970 See :hg:`help dates` for a list of formats valid for -d/--date.
4970
4971
4971 Returns 0 on success.
4972 Returns 0 on success.
4972 """
4973 """
4973
4974
4974 if opts.get("date"):
4975 if opts.get("date"):
4975 if opts.get("rev"):
4976 if opts.get("rev"):
4976 raise util.Abort(_("you can't specify a revision and a date"))
4977 raise util.Abort(_("you can't specify a revision and a date"))
4977 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4978 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4978
4979
4979 parent, p2 = repo.dirstate.parents()
4980 parent, p2 = repo.dirstate.parents()
4980 if not opts.get('rev') and p2 != nullid:
4981 if not opts.get('rev') and p2 != nullid:
4981 # revert after merge is a trap for new users (issue2915)
4982 # revert after merge is a trap for new users (issue2915)
4982 raise util.Abort(_('uncommitted merge with no revision specified'),
4983 raise util.Abort(_('uncommitted merge with no revision specified'),
4983 hint=_('use "hg update" or see "hg help revert"'))
4984 hint=_('use "hg update" or see "hg help revert"'))
4984
4985
4985 ctx = scmutil.revsingle(repo, opts.get('rev'))
4986 ctx = scmutil.revsingle(repo, opts.get('rev'))
4986
4987
4987 if not pats and not opts.get('all'):
4988 if not pats and not opts.get('all'):
4988 msg = _("no files or directories specified")
4989 msg = _("no files or directories specified")
4989 if p2 != nullid:
4990 if p2 != nullid:
4990 hint = _("uncommitted merge, use --all to discard all changes,"
4991 hint = _("uncommitted merge, use --all to discard all changes,"
4991 " or 'hg update -C .' to abort the merge")
4992 " or 'hg update -C .' to abort the merge")
4992 raise util.Abort(msg, hint=hint)
4993 raise util.Abort(msg, hint=hint)
4993 dirty = util.any(repo.status())
4994 dirty = util.any(repo.status())
4994 node = ctx.node()
4995 node = ctx.node()
4995 if node != parent:
4996 if node != parent:
4996 if dirty:
4997 if dirty:
4997 hint = _("uncommitted changes, use --all to discard all"
4998 hint = _("uncommitted changes, use --all to discard all"
4998 " changes, or 'hg update %s' to update") % ctx.rev()
4999 " changes, or 'hg update %s' to update") % ctx.rev()
4999 else:
5000 else:
5000 hint = _("use --all to revert all files,"
5001 hint = _("use --all to revert all files,"
5001 " or 'hg update %s' to update") % ctx.rev()
5002 " or 'hg update %s' to update") % ctx.rev()
5002 elif dirty:
5003 elif dirty:
5003 hint = _("uncommitted changes, use --all to discard all changes")
5004 hint = _("uncommitted changes, use --all to discard all changes")
5004 else:
5005 else:
5005 hint = _("use --all to revert all files")
5006 hint = _("use --all to revert all files")
5006 raise util.Abort(msg, hint=hint)
5007 raise util.Abort(msg, hint=hint)
5007
5008
5008 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
5009 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
5009
5010
5010 @command('rollback', dryrunopts +
5011 @command('rollback', dryrunopts +
5011 [('f', 'force', False, _('ignore safety measures'))])
5012 [('f', 'force', False, _('ignore safety measures'))])
5012 def rollback(ui, repo, **opts):
5013 def rollback(ui, repo, **opts):
5013 """roll back the last transaction (dangerous)
5014 """roll back the last transaction (dangerous)
5014
5015
5015 This command should be used with care. There is only one level of
5016 This command should be used with care. There is only one level of
5016 rollback, and there is no way to undo a rollback. It will also
5017 rollback, and there is no way to undo a rollback. It will also
5017 restore the dirstate at the time of the last transaction, losing
5018 restore the dirstate at the time of the last transaction, losing
5018 any dirstate changes since that time. This command does not alter
5019 any dirstate changes since that time. This command does not alter
5019 the working directory.
5020 the working directory.
5020
5021
5021 Transactions are used to encapsulate the effects of all commands
5022 Transactions are used to encapsulate the effects of all commands
5022 that create new changesets or propagate existing changesets into a
5023 that create new changesets or propagate existing changesets into a
5023 repository.
5024 repository.
5024
5025
5025 .. container:: verbose
5026 .. container:: verbose
5026
5027
5027 For example, the following commands are transactional, and their
5028 For example, the following commands are transactional, and their
5028 effects can be rolled back:
5029 effects can be rolled back:
5029
5030
5030 - commit
5031 - commit
5031 - import
5032 - import
5032 - pull
5033 - pull
5033 - push (with this repository as the destination)
5034 - push (with this repository as the destination)
5034 - unbundle
5035 - unbundle
5035
5036
5036 To avoid permanent data loss, rollback will refuse to rollback a
5037 To avoid permanent data loss, rollback will refuse to rollback a
5037 commit transaction if it isn't checked out. Use --force to
5038 commit transaction if it isn't checked out. Use --force to
5038 override this protection.
5039 override this protection.
5039
5040
5040 This command is not intended for use on public repositories. Once
5041 This command is not intended for use on public repositories. Once
5041 changes are visible for pull by other users, rolling a transaction
5042 changes are visible for pull by other users, rolling a transaction
5042 back locally is ineffective (someone else may already have pulled
5043 back locally is ineffective (someone else may already have pulled
5043 the changes). Furthermore, a race is possible with readers of the
5044 the changes). Furthermore, a race is possible with readers of the
5044 repository; for example an in-progress pull from the repository
5045 repository; for example an in-progress pull from the repository
5045 may fail if a rollback is performed.
5046 may fail if a rollback is performed.
5046
5047
5047 Returns 0 on success, 1 if no rollback data is available.
5048 Returns 0 on success, 1 if no rollback data is available.
5048 """
5049 """
5049 return repo.rollback(dryrun=opts.get('dry_run'),
5050 return repo.rollback(dryrun=opts.get('dry_run'),
5050 force=opts.get('force'))
5051 force=opts.get('force'))
5051
5052
5052 @command('root', [])
5053 @command('root', [])
5053 def root(ui, repo):
5054 def root(ui, repo):
5054 """print the root (top) of the current working directory
5055 """print the root (top) of the current working directory
5055
5056
5056 Print the root directory of the current repository.
5057 Print the root directory of the current repository.
5057
5058
5058 Returns 0 on success.
5059 Returns 0 on success.
5059 """
5060 """
5060 ui.write(repo.root + "\n")
5061 ui.write(repo.root + "\n")
5061
5062
5062 @command('^serve',
5063 @command('^serve',
5063 [('A', 'accesslog', '', _('name of access log file to write to'),
5064 [('A', 'accesslog', '', _('name of access log file to write to'),
5064 _('FILE')),
5065 _('FILE')),
5065 ('d', 'daemon', None, _('run server in background')),
5066 ('d', 'daemon', None, _('run server in background')),
5066 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
5067 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
5067 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5068 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5068 # use string type, then we can check if something was passed
5069 # use string type, then we can check if something was passed
5069 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5070 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5070 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5071 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5071 _('ADDR')),
5072 _('ADDR')),
5072 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5073 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5073 _('PREFIX')),
5074 _('PREFIX')),
5074 ('n', 'name', '',
5075 ('n', 'name', '',
5075 _('name to show in web pages (default: working directory)'), _('NAME')),
5076 _('name to show in web pages (default: working directory)'), _('NAME')),
5076 ('', 'web-conf', '',
5077 ('', 'web-conf', '',
5077 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
5078 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
5078 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5079 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5079 _('FILE')),
5080 _('FILE')),
5080 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5081 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5081 ('', 'stdio', None, _('for remote clients')),
5082 ('', 'stdio', None, _('for remote clients')),
5082 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
5083 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
5083 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5084 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5084 ('', 'style', '', _('template style to use'), _('STYLE')),
5085 ('', 'style', '', _('template style to use'), _('STYLE')),
5085 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5086 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5086 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
5087 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
5087 _('[OPTION]...'))
5088 _('[OPTION]...'))
5088 def serve(ui, repo, **opts):
5089 def serve(ui, repo, **opts):
5089 """start stand-alone webserver
5090 """start stand-alone webserver
5090
5091
5091 Start a local HTTP repository browser and pull server. You can use
5092 Start a local HTTP repository browser and pull server. You can use
5092 this for ad-hoc sharing and browsing of repositories. It is
5093 this for ad-hoc sharing and browsing of repositories. It is
5093 recommended to use a real web server to serve a repository for
5094 recommended to use a real web server to serve a repository for
5094 longer periods of time.
5095 longer periods of time.
5095
5096
5096 Please note that the server does not implement access control.
5097 Please note that the server does not implement access control.
5097 This means that, by default, anybody can read from the server and
5098 This means that, by default, anybody can read from the server and
5098 nobody can write to it by default. Set the ``web.allow_push``
5099 nobody can write to it by default. Set the ``web.allow_push``
5099 option to ``*`` to allow everybody to push to the server. You
5100 option to ``*`` to allow everybody to push to the server. You
5100 should use a real web server if you need to authenticate users.
5101 should use a real web server if you need to authenticate users.
5101
5102
5102 By default, the server logs accesses to stdout and errors to
5103 By default, the server logs accesses to stdout and errors to
5103 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5104 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5104 files.
5105 files.
5105
5106
5106 To have the server choose a free port number to listen on, specify
5107 To have the server choose a free port number to listen on, specify
5107 a port number of 0; in this case, the server will print the port
5108 a port number of 0; in this case, the server will print the port
5108 number it uses.
5109 number it uses.
5109
5110
5110 Returns 0 on success.
5111 Returns 0 on success.
5111 """
5112 """
5112
5113
5113 if opts["stdio"] and opts["cmdserver"]:
5114 if opts["stdio"] and opts["cmdserver"]:
5114 raise util.Abort(_("cannot use --stdio with --cmdserver"))
5115 raise util.Abort(_("cannot use --stdio with --cmdserver"))
5115
5116
5116 def checkrepo():
5117 def checkrepo():
5117 if repo is None:
5118 if repo is None:
5118 raise error.RepoError(_("there is no Mercurial repository here"
5119 raise error.RepoError(_("there is no Mercurial repository here"
5119 " (.hg not found)"))
5120 " (.hg not found)"))
5120
5121
5121 if opts["stdio"]:
5122 if opts["stdio"]:
5122 checkrepo()
5123 checkrepo()
5123 s = sshserver.sshserver(ui, repo)
5124 s = sshserver.sshserver(ui, repo)
5124 s.serve_forever()
5125 s.serve_forever()
5125
5126
5126 if opts["cmdserver"]:
5127 if opts["cmdserver"]:
5127 checkrepo()
5128 checkrepo()
5128 s = commandserver.server(ui, repo, opts["cmdserver"])
5129 s = commandserver.server(ui, repo, opts["cmdserver"])
5129 return s.serve()
5130 return s.serve()
5130
5131
5131 # this way we can check if something was given in the command-line
5132 # this way we can check if something was given in the command-line
5132 if opts.get('port'):
5133 if opts.get('port'):
5133 opts['port'] = util.getport(opts.get('port'))
5134 opts['port'] = util.getport(opts.get('port'))
5134
5135
5135 baseui = repo and repo.baseui or ui
5136 baseui = repo and repo.baseui or ui
5136 optlist = ("name templates style address port prefix ipv6"
5137 optlist = ("name templates style address port prefix ipv6"
5137 " accesslog errorlog certificate encoding")
5138 " accesslog errorlog certificate encoding")
5138 for o in optlist.split():
5139 for o in optlist.split():
5139 val = opts.get(o, '')
5140 val = opts.get(o, '')
5140 if val in (None, ''): # should check against default options instead
5141 if val in (None, ''): # should check against default options instead
5141 continue
5142 continue
5142 baseui.setconfig("web", o, val)
5143 baseui.setconfig("web", o, val)
5143 if repo and repo.ui != baseui:
5144 if repo and repo.ui != baseui:
5144 repo.ui.setconfig("web", o, val)
5145 repo.ui.setconfig("web", o, val)
5145
5146
5146 o = opts.get('web_conf') or opts.get('webdir_conf')
5147 o = opts.get('web_conf') or opts.get('webdir_conf')
5147 if not o:
5148 if not o:
5148 if not repo:
5149 if not repo:
5149 raise error.RepoError(_("there is no Mercurial repository"
5150 raise error.RepoError(_("there is no Mercurial repository"
5150 " here (.hg not found)"))
5151 " here (.hg not found)"))
5151 o = repo.root
5152 o = repo.root
5152
5153
5153 app = hgweb.hgweb(o, baseui=ui)
5154 app = hgweb.hgweb(o, baseui=ui)
5154
5155
5155 class service(object):
5156 class service(object):
5156 def init(self):
5157 def init(self):
5157 util.setsignalhandler()
5158 util.setsignalhandler()
5158 self.httpd = hgweb.server.create_server(ui, app)
5159 self.httpd = hgweb.server.create_server(ui, app)
5159
5160
5160 if opts['port'] and not ui.verbose:
5161 if opts['port'] and not ui.verbose:
5161 return
5162 return
5162
5163
5163 if self.httpd.prefix:
5164 if self.httpd.prefix:
5164 prefix = self.httpd.prefix.strip('/') + '/'
5165 prefix = self.httpd.prefix.strip('/') + '/'
5165 else:
5166 else:
5166 prefix = ''
5167 prefix = ''
5167
5168
5168 port = ':%d' % self.httpd.port
5169 port = ':%d' % self.httpd.port
5169 if port == ':80':
5170 if port == ':80':
5170 port = ''
5171 port = ''
5171
5172
5172 bindaddr = self.httpd.addr
5173 bindaddr = self.httpd.addr
5173 if bindaddr == '0.0.0.0':
5174 if bindaddr == '0.0.0.0':
5174 bindaddr = '*'
5175 bindaddr = '*'
5175 elif ':' in bindaddr: # IPv6
5176 elif ':' in bindaddr: # IPv6
5176 bindaddr = '[%s]' % bindaddr
5177 bindaddr = '[%s]' % bindaddr
5177
5178
5178 fqaddr = self.httpd.fqaddr
5179 fqaddr = self.httpd.fqaddr
5179 if ':' in fqaddr:
5180 if ':' in fqaddr:
5180 fqaddr = '[%s]' % fqaddr
5181 fqaddr = '[%s]' % fqaddr
5181 if opts['port']:
5182 if opts['port']:
5182 write = ui.status
5183 write = ui.status
5183 else:
5184 else:
5184 write = ui.write
5185 write = ui.write
5185 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5186 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5186 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5187 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5187
5188
5188 def run(self):
5189 def run(self):
5189 self.httpd.serve_forever()
5190 self.httpd.serve_forever()
5190
5191
5191 service = service()
5192 service = service()
5192
5193
5193 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5194 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5194
5195
5195 @command('showconfig|debugconfig',
5196 @command('showconfig|debugconfig',
5196 [('u', 'untrusted', None, _('show untrusted configuration options'))],
5197 [('u', 'untrusted', None, _('show untrusted configuration options'))],
5197 _('[-u] [NAME]...'))
5198 _('[-u] [NAME]...'))
5198 def showconfig(ui, repo, *values, **opts):
5199 def showconfig(ui, repo, *values, **opts):
5199 """show combined config settings from all hgrc files
5200 """show combined config settings from all hgrc files
5200
5201
5201 With no arguments, print names and values of all config items.
5202 With no arguments, print names and values of all config items.
5202
5203
5203 With one argument of the form section.name, print just the value
5204 With one argument of the form section.name, print just the value
5204 of that config item.
5205 of that config item.
5205
5206
5206 With multiple arguments, print names and values of all config
5207 With multiple arguments, print names and values of all config
5207 items with matching section names.
5208 items with matching section names.
5208
5209
5209 With --debug, the source (filename and line number) is printed
5210 With --debug, the source (filename and line number) is printed
5210 for each config item.
5211 for each config item.
5211
5212
5212 Returns 0 on success.
5213 Returns 0 on success.
5213 """
5214 """
5214
5215
5215 for f in scmutil.rcpath():
5216 for f in scmutil.rcpath():
5216 ui.debug('read config from: %s\n' % f)
5217 ui.debug('read config from: %s\n' % f)
5217 untrusted = bool(opts.get('untrusted'))
5218 untrusted = bool(opts.get('untrusted'))
5218 if values:
5219 if values:
5219 sections = [v for v in values if '.' not in v]
5220 sections = [v for v in values if '.' not in v]
5220 items = [v for v in values if '.' in v]
5221 items = [v for v in values if '.' in v]
5221 if len(items) > 1 or items and sections:
5222 if len(items) > 1 or items and sections:
5222 raise util.Abort(_('only one config item permitted'))
5223 raise util.Abort(_('only one config item permitted'))
5223 for section, name, value in ui.walkconfig(untrusted=untrusted):
5224 for section, name, value in ui.walkconfig(untrusted=untrusted):
5224 value = str(value).replace('\n', '\\n')
5225 value = str(value).replace('\n', '\\n')
5225 sectname = section + '.' + name
5226 sectname = section + '.' + name
5226 if values:
5227 if values:
5227 for v in values:
5228 for v in values:
5228 if v == section:
5229 if v == section:
5229 ui.debug('%s: ' %
5230 ui.debug('%s: ' %
5230 ui.configsource(section, name, untrusted))
5231 ui.configsource(section, name, untrusted))
5231 ui.write('%s=%s\n' % (sectname, value))
5232 ui.write('%s=%s\n' % (sectname, value))
5232 elif v == sectname:
5233 elif v == sectname:
5233 ui.debug('%s: ' %
5234 ui.debug('%s: ' %
5234 ui.configsource(section, name, untrusted))
5235 ui.configsource(section, name, untrusted))
5235 ui.write(value, '\n')
5236 ui.write(value, '\n')
5236 else:
5237 else:
5237 ui.debug('%s: ' %
5238 ui.debug('%s: ' %
5238 ui.configsource(section, name, untrusted))
5239 ui.configsource(section, name, untrusted))
5239 ui.write('%s=%s\n' % (sectname, value))
5240 ui.write('%s=%s\n' % (sectname, value))
5240
5241
5241 @command('^status|st',
5242 @command('^status|st',
5242 [('A', 'all', None, _('show status of all files')),
5243 [('A', 'all', None, _('show status of all files')),
5243 ('m', 'modified', None, _('show only modified files')),
5244 ('m', 'modified', None, _('show only modified files')),
5244 ('a', 'added', None, _('show only added files')),
5245 ('a', 'added', None, _('show only added files')),
5245 ('r', 'removed', None, _('show only removed files')),
5246 ('r', 'removed', None, _('show only removed files')),
5246 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5247 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5247 ('c', 'clean', None, _('show only files without changes')),
5248 ('c', 'clean', None, _('show only files without changes')),
5248 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5249 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5249 ('i', 'ignored', None, _('show only ignored files')),
5250 ('i', 'ignored', None, _('show only ignored files')),
5250 ('n', 'no-status', None, _('hide status prefix')),
5251 ('n', 'no-status', None, _('hide status prefix')),
5251 ('C', 'copies', None, _('show source of copied files')),
5252 ('C', 'copies', None, _('show source of copied files')),
5252 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5253 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5253 ('', 'rev', [], _('show difference from revision'), _('REV')),
5254 ('', 'rev', [], _('show difference from revision'), _('REV')),
5254 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5255 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5255 ] + walkopts + subrepoopts,
5256 ] + walkopts + subrepoopts,
5256 _('[OPTION]... [FILE]...'))
5257 _('[OPTION]... [FILE]...'))
5257 def status(ui, repo, *pats, **opts):
5258 def status(ui, repo, *pats, **opts):
5258 """show changed files in the working directory
5259 """show changed files in the working directory
5259
5260
5260 Show status of files in the repository. If names are given, only
5261 Show status of files in the repository. If names are given, only
5261 files that match are shown. Files that are clean or ignored or
5262 files that match are shown. Files that are clean or ignored or
5262 the source of a copy/move operation, are not listed unless
5263 the source of a copy/move operation, are not listed unless
5263 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5264 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5264 Unless options described with "show only ..." are given, the
5265 Unless options described with "show only ..." are given, the
5265 options -mardu are used.
5266 options -mardu are used.
5266
5267
5267 Option -q/--quiet hides untracked (unknown and ignored) files
5268 Option -q/--quiet hides untracked (unknown and ignored) files
5268 unless explicitly requested with -u/--unknown or -i/--ignored.
5269 unless explicitly requested with -u/--unknown or -i/--ignored.
5269
5270
5270 .. note::
5271 .. note::
5271 status may appear to disagree with diff if permissions have
5272 status may appear to disagree with diff if permissions have
5272 changed or a merge has occurred. The standard diff format does
5273 changed or a merge has occurred. The standard diff format does
5273 not report permission changes and diff only reports changes
5274 not report permission changes and diff only reports changes
5274 relative to one merge parent.
5275 relative to one merge parent.
5275
5276
5276 If one revision is given, it is used as the base revision.
5277 If one revision is given, it is used as the base revision.
5277 If two revisions are given, the differences between them are
5278 If two revisions are given, the differences between them are
5278 shown. The --change option can also be used as a shortcut to list
5279 shown. The --change option can also be used as a shortcut to list
5279 the changed files of a revision from its first parent.
5280 the changed files of a revision from its first parent.
5280
5281
5281 The codes used to show the status of files are::
5282 The codes used to show the status of files are::
5282
5283
5283 M = modified
5284 M = modified
5284 A = added
5285 A = added
5285 R = removed
5286 R = removed
5286 C = clean
5287 C = clean
5287 ! = missing (deleted by non-hg command, but still tracked)
5288 ! = missing (deleted by non-hg command, but still tracked)
5288 ? = not tracked
5289 ? = not tracked
5289 I = ignored
5290 I = ignored
5290 = origin of the previous file listed as A (added)
5291 = origin of the previous file listed as A (added)
5291
5292
5292 .. container:: verbose
5293 .. container:: verbose
5293
5294
5294 Examples:
5295 Examples:
5295
5296
5296 - show changes in the working directory relative to a
5297 - show changes in the working directory relative to a
5297 changeset::
5298 changeset::
5298
5299
5299 hg status --rev 9353
5300 hg status --rev 9353
5300
5301
5301 - show all changes including copies in an existing changeset::
5302 - show all changes including copies in an existing changeset::
5302
5303
5303 hg status --copies --change 9353
5304 hg status --copies --change 9353
5304
5305
5305 - get a NUL separated list of added files, suitable for xargs::
5306 - get a NUL separated list of added files, suitable for xargs::
5306
5307
5307 hg status -an0
5308 hg status -an0
5308
5309
5309 Returns 0 on success.
5310 Returns 0 on success.
5310 """
5311 """
5311
5312
5312 revs = opts.get('rev')
5313 revs = opts.get('rev')
5313 change = opts.get('change')
5314 change = opts.get('change')
5314
5315
5315 if revs and change:
5316 if revs and change:
5316 msg = _('cannot specify --rev and --change at the same time')
5317 msg = _('cannot specify --rev and --change at the same time')
5317 raise util.Abort(msg)
5318 raise util.Abort(msg)
5318 elif change:
5319 elif change:
5319 node2 = scmutil.revsingle(repo, change, None).node()
5320 node2 = scmutil.revsingle(repo, change, None).node()
5320 node1 = repo[node2].p1().node()
5321 node1 = repo[node2].p1().node()
5321 else:
5322 else:
5322 node1, node2 = scmutil.revpair(repo, revs)
5323 node1, node2 = scmutil.revpair(repo, revs)
5323
5324
5324 cwd = (pats and repo.getcwd()) or ''
5325 cwd = (pats and repo.getcwd()) or ''
5325 end = opts.get('print0') and '\0' or '\n'
5326 end = opts.get('print0') and '\0' or '\n'
5326 copy = {}
5327 copy = {}
5327 states = 'modified added removed deleted unknown ignored clean'.split()
5328 states = 'modified added removed deleted unknown ignored clean'.split()
5328 show = [k for k in states if opts.get(k)]
5329 show = [k for k in states if opts.get(k)]
5329 if opts.get('all'):
5330 if opts.get('all'):
5330 show += ui.quiet and (states[:4] + ['clean']) or states
5331 show += ui.quiet and (states[:4] + ['clean']) or states
5331 if not show:
5332 if not show:
5332 show = ui.quiet and states[:4] or states[:5]
5333 show = ui.quiet and states[:4] or states[:5]
5333
5334
5334 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5335 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5335 'ignored' in show, 'clean' in show, 'unknown' in show,
5336 'ignored' in show, 'clean' in show, 'unknown' in show,
5336 opts.get('subrepos'))
5337 opts.get('subrepos'))
5337 changestates = zip(states, 'MAR!?IC', stat)
5338 changestates = zip(states, 'MAR!?IC', stat)
5338
5339
5339 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5340 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5340 copy = copies.pathcopies(repo[node1], repo[node2])
5341 copy = copies.pathcopies(repo[node1], repo[node2])
5341
5342
5342 fm = ui.formatter('status', opts)
5343 fm = ui.formatter('status', opts)
5343 format = '%s %s' + end
5344 format = '%s %s' + end
5344 if opts.get('no_status'):
5345 if opts.get('no_status'):
5345 format = '%.0s%s' + end
5346 format = '%.0s%s' + end
5346
5347
5347 for state, char, files in changestates:
5348 for state, char, files in changestates:
5348 if state in show:
5349 if state in show:
5349 label = 'status.' + state
5350 label = 'status.' + state
5350 for f in files:
5351 for f in files:
5351 fm.startitem()
5352 fm.startitem()
5352 fm.write("status path", format, char,
5353 fm.write("status path", format, char,
5353 repo.pathto(f, cwd), label=label)
5354 repo.pathto(f, cwd), label=label)
5354 if f in copy:
5355 if f in copy:
5355 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5356 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5356 label='status.copied')
5357 label='status.copied')
5357 fm.end()
5358 fm.end()
5358
5359
5359 @command('^summary|sum',
5360 @command('^summary|sum',
5360 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5361 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5361 def summary(ui, repo, **opts):
5362 def summary(ui, repo, **opts):
5362 """summarize working directory state
5363 """summarize working directory state
5363
5364
5364 This generates a brief summary of the working directory state,
5365 This generates a brief summary of the working directory state,
5365 including parents, branch, commit status, and available updates.
5366 including parents, branch, commit status, and available updates.
5366
5367
5367 With the --remote option, this will check the default paths for
5368 With the --remote option, this will check the default paths for
5368 incoming and outgoing changes. This can be time-consuming.
5369 incoming and outgoing changes. This can be time-consuming.
5369
5370
5370 Returns 0 on success.
5371 Returns 0 on success.
5371 """
5372 """
5372
5373
5373 ctx = repo[None]
5374 ctx = repo[None]
5374 parents = ctx.parents()
5375 parents = ctx.parents()
5375 pnode = parents[0].node()
5376 pnode = parents[0].node()
5376 marks = []
5377 marks = []
5377
5378
5378 for p in parents:
5379 for p in parents:
5379 # label with log.changeset (instead of log.parent) since this
5380 # label with log.changeset (instead of log.parent) since this
5380 # shows a working directory parent *changeset*:
5381 # shows a working directory parent *changeset*:
5381 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5382 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5382 label='log.changeset')
5383 label='log.changeset')
5383 ui.write(' '.join(p.tags()), label='log.tag')
5384 ui.write(' '.join(p.tags()), label='log.tag')
5384 if p.bookmarks():
5385 if p.bookmarks():
5385 marks.extend(p.bookmarks())
5386 marks.extend(p.bookmarks())
5386 if p.rev() == -1:
5387 if p.rev() == -1:
5387 if not len(repo):
5388 if not len(repo):
5388 ui.write(_(' (empty repository)'))
5389 ui.write(_(' (empty repository)'))
5389 else:
5390 else:
5390 ui.write(_(' (no revision checked out)'))
5391 ui.write(_(' (no revision checked out)'))
5391 ui.write('\n')
5392 ui.write('\n')
5392 if p.description():
5393 if p.description():
5393 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5394 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5394 label='log.summary')
5395 label='log.summary')
5395
5396
5396 branch = ctx.branch()
5397 branch = ctx.branch()
5397 bheads = repo.branchheads(branch)
5398 bheads = repo.branchheads(branch)
5398 m = _('branch: %s\n') % branch
5399 m = _('branch: %s\n') % branch
5399 if branch != 'default':
5400 if branch != 'default':
5400 ui.write(m, label='log.branch')
5401 ui.write(m, label='log.branch')
5401 else:
5402 else:
5402 ui.status(m, label='log.branch')
5403 ui.status(m, label='log.branch')
5403
5404
5404 if marks:
5405 if marks:
5405 current = repo._bookmarkcurrent
5406 current = repo._bookmarkcurrent
5406 ui.write(_('bookmarks:'), label='log.bookmark')
5407 ui.write(_('bookmarks:'), label='log.bookmark')
5407 if current is not None:
5408 if current is not None:
5408 try:
5409 try:
5409 marks.remove(current)
5410 marks.remove(current)
5410 ui.write(' *' + current, label='bookmarks.current')
5411 ui.write(' *' + current, label='bookmarks.current')
5411 except ValueError:
5412 except ValueError:
5412 # current bookmark not in parent ctx marks
5413 # current bookmark not in parent ctx marks
5413 pass
5414 pass
5414 for m in marks:
5415 for m in marks:
5415 ui.write(' ' + m, label='log.bookmark')
5416 ui.write(' ' + m, label='log.bookmark')
5416 ui.write('\n', label='log.bookmark')
5417 ui.write('\n', label='log.bookmark')
5417
5418
5418 st = list(repo.status(unknown=True))[:6]
5419 st = list(repo.status(unknown=True))[:6]
5419
5420
5420 c = repo.dirstate.copies()
5421 c = repo.dirstate.copies()
5421 copied, renamed = [], []
5422 copied, renamed = [], []
5422 for d, s in c.iteritems():
5423 for d, s in c.iteritems():
5423 if s in st[2]:
5424 if s in st[2]:
5424 st[2].remove(s)
5425 st[2].remove(s)
5425 renamed.append(d)
5426 renamed.append(d)
5426 else:
5427 else:
5427 copied.append(d)
5428 copied.append(d)
5428 if d in st[1]:
5429 if d in st[1]:
5429 st[1].remove(d)
5430 st[1].remove(d)
5430 st.insert(3, renamed)
5431 st.insert(3, renamed)
5431 st.insert(4, copied)
5432 st.insert(4, copied)
5432
5433
5433 ms = mergemod.mergestate(repo)
5434 ms = mergemod.mergestate(repo)
5434 st.append([f for f in ms if ms[f] == 'u'])
5435 st.append([f for f in ms if ms[f] == 'u'])
5435
5436
5436 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5437 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5437 st.append(subs)
5438 st.append(subs)
5438
5439
5439 labels = [ui.label(_('%d modified'), 'status.modified'),
5440 labels = [ui.label(_('%d modified'), 'status.modified'),
5440 ui.label(_('%d added'), 'status.added'),
5441 ui.label(_('%d added'), 'status.added'),
5441 ui.label(_('%d removed'), 'status.removed'),
5442 ui.label(_('%d removed'), 'status.removed'),
5442 ui.label(_('%d renamed'), 'status.copied'),
5443 ui.label(_('%d renamed'), 'status.copied'),
5443 ui.label(_('%d copied'), 'status.copied'),
5444 ui.label(_('%d copied'), 'status.copied'),
5444 ui.label(_('%d deleted'), 'status.deleted'),
5445 ui.label(_('%d deleted'), 'status.deleted'),
5445 ui.label(_('%d unknown'), 'status.unknown'),
5446 ui.label(_('%d unknown'), 'status.unknown'),
5446 ui.label(_('%d ignored'), 'status.ignored'),
5447 ui.label(_('%d ignored'), 'status.ignored'),
5447 ui.label(_('%d unresolved'), 'resolve.unresolved'),
5448 ui.label(_('%d unresolved'), 'resolve.unresolved'),
5448 ui.label(_('%d subrepos'), 'status.modified')]
5449 ui.label(_('%d subrepos'), 'status.modified')]
5449 t = []
5450 t = []
5450 for s, l in zip(st, labels):
5451 for s, l in zip(st, labels):
5451 if s:
5452 if s:
5452 t.append(l % len(s))
5453 t.append(l % len(s))
5453
5454
5454 t = ', '.join(t)
5455 t = ', '.join(t)
5455 cleanworkdir = False
5456 cleanworkdir = False
5456
5457
5457 if len(parents) > 1:
5458 if len(parents) > 1:
5458 t += _(' (merge)')
5459 t += _(' (merge)')
5459 elif branch != parents[0].branch():
5460 elif branch != parents[0].branch():
5460 t += _(' (new branch)')
5461 t += _(' (new branch)')
5461 elif (parents[0].closesbranch() and
5462 elif (parents[0].closesbranch() and
5462 pnode in repo.branchheads(branch, closed=True)):
5463 pnode in repo.branchheads(branch, closed=True)):
5463 t += _(' (head closed)')
5464 t += _(' (head closed)')
5464 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
5465 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
5465 t += _(' (clean)')
5466 t += _(' (clean)')
5466 cleanworkdir = True
5467 cleanworkdir = True
5467 elif pnode not in bheads:
5468 elif pnode not in bheads:
5468 t += _(' (new branch head)')
5469 t += _(' (new branch head)')
5469
5470
5470 if cleanworkdir:
5471 if cleanworkdir:
5471 ui.status(_('commit: %s\n') % t.strip())
5472 ui.status(_('commit: %s\n') % t.strip())
5472 else:
5473 else:
5473 ui.write(_('commit: %s\n') % t.strip())
5474 ui.write(_('commit: %s\n') % t.strip())
5474
5475
5475 # all ancestors of branch heads - all ancestors of parent = new csets
5476 # all ancestors of branch heads - all ancestors of parent = new csets
5476 new = [0] * len(repo)
5477 new = [0] * len(repo)
5477 cl = repo.changelog
5478 cl = repo.changelog
5478 for a in [cl.rev(n) for n in bheads]:
5479 for a in [cl.rev(n) for n in bheads]:
5479 new[a] = 1
5480 new[a] = 1
5480 for a in cl.ancestors([cl.rev(n) for n in bheads]):
5481 for a in cl.ancestors([cl.rev(n) for n in bheads]):
5481 new[a] = 1
5482 new[a] = 1
5482 for a in [p.rev() for p in parents]:
5483 for a in [p.rev() for p in parents]:
5483 if a >= 0:
5484 if a >= 0:
5484 new[a] = 0
5485 new[a] = 0
5485 for a in cl.ancestors([p.rev() for p in parents]):
5486 for a in cl.ancestors([p.rev() for p in parents]):
5486 new[a] = 0
5487 new[a] = 0
5487 new = sum(new)
5488 new = sum(new)
5488
5489
5489 if new == 0:
5490 if new == 0:
5490 ui.status(_('update: (current)\n'))
5491 ui.status(_('update: (current)\n'))
5491 elif pnode not in bheads:
5492 elif pnode not in bheads:
5492 ui.write(_('update: %d new changesets (update)\n') % new)
5493 ui.write(_('update: %d new changesets (update)\n') % new)
5493 else:
5494 else:
5494 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5495 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5495 (new, len(bheads)))
5496 (new, len(bheads)))
5496
5497
5497 if opts.get('remote'):
5498 if opts.get('remote'):
5498 t = []
5499 t = []
5499 source, branches = hg.parseurl(ui.expandpath('default'))
5500 source, branches = hg.parseurl(ui.expandpath('default'))
5500 other = hg.peer(repo, {}, source)
5501 other = hg.peer(repo, {}, source)
5501 revs, checkout = hg.addbranchrevs(repo, other, branches,
5502 revs, checkout = hg.addbranchrevs(repo, other, branches,
5502 opts.get('rev'))
5503 opts.get('rev'))
5503 ui.debug('comparing with %s\n' % util.hidepassword(source))
5504 ui.debug('comparing with %s\n' % util.hidepassword(source))
5504 repo.ui.pushbuffer()
5505 repo.ui.pushbuffer()
5505 commoninc = discovery.findcommonincoming(repo, other)
5506 commoninc = discovery.findcommonincoming(repo, other)
5506 _common, incoming, _rheads = commoninc
5507 _common, incoming, _rheads = commoninc
5507 repo.ui.popbuffer()
5508 repo.ui.popbuffer()
5508 if incoming:
5509 if incoming:
5509 t.append(_('1 or more incoming'))
5510 t.append(_('1 or more incoming'))
5510
5511
5511 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5512 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5512 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5513 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5513 if source != dest:
5514 if source != dest:
5514 other = hg.peer(repo, {}, dest)
5515 other = hg.peer(repo, {}, dest)
5515 commoninc = None
5516 commoninc = None
5516 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5517 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5517 repo.ui.pushbuffer()
5518 repo.ui.pushbuffer()
5518 outgoing = discovery.findcommonoutgoing(repo, other,
5519 outgoing = discovery.findcommonoutgoing(repo, other,
5519 commoninc=commoninc)
5520 commoninc=commoninc)
5520 repo.ui.popbuffer()
5521 repo.ui.popbuffer()
5521 o = outgoing.missing
5522 o = outgoing.missing
5522 if o:
5523 if o:
5523 t.append(_('%d outgoing') % len(o))
5524 t.append(_('%d outgoing') % len(o))
5524 if 'bookmarks' in other.listkeys('namespaces'):
5525 if 'bookmarks' in other.listkeys('namespaces'):
5525 lmarks = repo.listkeys('bookmarks')
5526 lmarks = repo.listkeys('bookmarks')
5526 rmarks = other.listkeys('bookmarks')
5527 rmarks = other.listkeys('bookmarks')
5527 diff = set(rmarks) - set(lmarks)
5528 diff = set(rmarks) - set(lmarks)
5528 if len(diff) > 0:
5529 if len(diff) > 0:
5529 t.append(_('%d incoming bookmarks') % len(diff))
5530 t.append(_('%d incoming bookmarks') % len(diff))
5530 diff = set(lmarks) - set(rmarks)
5531 diff = set(lmarks) - set(rmarks)
5531 if len(diff) > 0:
5532 if len(diff) > 0:
5532 t.append(_('%d outgoing bookmarks') % len(diff))
5533 t.append(_('%d outgoing bookmarks') % len(diff))
5533
5534
5534 if t:
5535 if t:
5535 ui.write(_('remote: %s\n') % (', '.join(t)))
5536 ui.write(_('remote: %s\n') % (', '.join(t)))
5536 else:
5537 else:
5537 ui.status(_('remote: (synced)\n'))
5538 ui.status(_('remote: (synced)\n'))
5538
5539
5539 @command('tag',
5540 @command('tag',
5540 [('f', 'force', None, _('force tag')),
5541 [('f', 'force', None, _('force tag')),
5541 ('l', 'local', None, _('make the tag local')),
5542 ('l', 'local', None, _('make the tag local')),
5542 ('r', 'rev', '', _('revision to tag'), _('REV')),
5543 ('r', 'rev', '', _('revision to tag'), _('REV')),
5543 ('', 'remove', None, _('remove a tag')),
5544 ('', 'remove', None, _('remove a tag')),
5544 # -l/--local is already there, commitopts cannot be used
5545 # -l/--local is already there, commitopts cannot be used
5545 ('e', 'edit', None, _('edit commit message')),
5546 ('e', 'edit', None, _('edit commit message')),
5546 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
5547 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
5547 ] + commitopts2,
5548 ] + commitopts2,
5548 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5549 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5549 def tag(ui, repo, name1, *names, **opts):
5550 def tag(ui, repo, name1, *names, **opts):
5550 """add one or more tags for the current or given revision
5551 """add one or more tags for the current or given revision
5551
5552
5552 Name a particular revision using <name>.
5553 Name a particular revision using <name>.
5553
5554
5554 Tags are used to name particular revisions of the repository and are
5555 Tags are used to name particular revisions of the repository and are
5555 very useful to compare different revisions, to go back to significant
5556 very useful to compare different revisions, to go back to significant
5556 earlier versions or to mark branch points as releases, etc. Changing
5557 earlier versions or to mark branch points as releases, etc. Changing
5557 an existing tag is normally disallowed; use -f/--force to override.
5558 an existing tag is normally disallowed; use -f/--force to override.
5558
5559
5559 If no revision is given, the parent of the working directory is
5560 If no revision is given, the parent of the working directory is
5560 used, or tip if no revision is checked out.
5561 used, or tip if no revision is checked out.
5561
5562
5562 To facilitate version control, distribution, and merging of tags,
5563 To facilitate version control, distribution, and merging of tags,
5563 they are stored as a file named ".hgtags" which is managed similarly
5564 they are stored as a file named ".hgtags" which is managed similarly
5564 to other project files and can be hand-edited if necessary. This
5565 to other project files and can be hand-edited if necessary. This
5565 also means that tagging creates a new commit. The file
5566 also means that tagging creates a new commit. The file
5566 ".hg/localtags" is used for local tags (not shared among
5567 ".hg/localtags" is used for local tags (not shared among
5567 repositories).
5568 repositories).
5568
5569
5569 Tag commits are usually made at the head of a branch. If the parent
5570 Tag commits are usually made at the head of a branch. If the parent
5570 of the working directory is not a branch head, :hg:`tag` aborts; use
5571 of the working directory is not a branch head, :hg:`tag` aborts; use
5571 -f/--force to force the tag commit to be based on a non-head
5572 -f/--force to force the tag commit to be based on a non-head
5572 changeset.
5573 changeset.
5573
5574
5574 See :hg:`help dates` for a list of formats valid for -d/--date.
5575 See :hg:`help dates` for a list of formats valid for -d/--date.
5575
5576
5576 Since tag names have priority over branch names during revision
5577 Since tag names have priority over branch names during revision
5577 lookup, using an existing branch name as a tag name is discouraged.
5578 lookup, using an existing branch name as a tag name is discouraged.
5578
5579
5579 Returns 0 on success.
5580 Returns 0 on success.
5580 """
5581 """
5581 wlock = lock = None
5582 wlock = lock = None
5582 try:
5583 try:
5583 wlock = repo.wlock()
5584 wlock = repo.wlock()
5584 lock = repo.lock()
5585 lock = repo.lock()
5585 rev_ = "."
5586 rev_ = "."
5586 names = [t.strip() for t in (name1,) + names]
5587 names = [t.strip() for t in (name1,) + names]
5587 if len(names) != len(set(names)):
5588 if len(names) != len(set(names)):
5588 raise util.Abort(_('tag names must be unique'))
5589 raise util.Abort(_('tag names must be unique'))
5589 for n in names:
5590 for n in names:
5590 if n in ['tip', '.', 'null']:
5591 if n in ['tip', '.', 'null']:
5591 raise util.Abort(_("the name '%s' is reserved") % n)
5592 raise util.Abort(_("the name '%s' is reserved") % n)
5592 if not n:
5593 if not n:
5593 raise util.Abort(_('tag names cannot consist entirely of '
5594 raise util.Abort(_('tag names cannot consist entirely of '
5594 'whitespace'))
5595 'whitespace'))
5595 if opts.get('rev') and opts.get('remove'):
5596 if opts.get('rev') and opts.get('remove'):
5596 raise util.Abort(_("--rev and --remove are incompatible"))
5597 raise util.Abort(_("--rev and --remove are incompatible"))
5597 if opts.get('rev'):
5598 if opts.get('rev'):
5598 rev_ = opts['rev']
5599 rev_ = opts['rev']
5599 message = opts.get('message')
5600 message = opts.get('message')
5600 if opts.get('remove'):
5601 if opts.get('remove'):
5601 expectedtype = opts.get('local') and 'local' or 'global'
5602 expectedtype = opts.get('local') and 'local' or 'global'
5602 for n in names:
5603 for n in names:
5603 if not repo.tagtype(n):
5604 if not repo.tagtype(n):
5604 raise util.Abort(_("tag '%s' does not exist") % n)
5605 raise util.Abort(_("tag '%s' does not exist") % n)
5605 if repo.tagtype(n) != expectedtype:
5606 if repo.tagtype(n) != expectedtype:
5606 if expectedtype == 'global':
5607 if expectedtype == 'global':
5607 raise util.Abort(_("tag '%s' is not a global tag") % n)
5608 raise util.Abort(_("tag '%s' is not a global tag") % n)
5608 else:
5609 else:
5609 raise util.Abort(_("tag '%s' is not a local tag") % n)
5610 raise util.Abort(_("tag '%s' is not a local tag") % n)
5610 rev_ = nullid
5611 rev_ = nullid
5611 if not message:
5612 if not message:
5612 # we don't translate commit messages
5613 # we don't translate commit messages
5613 message = 'Removed tag %s' % ', '.join(names)
5614 message = 'Removed tag %s' % ', '.join(names)
5614 elif not opts.get('force'):
5615 elif not opts.get('force'):
5615 for n in names:
5616 for n in names:
5616 if n in repo.tags():
5617 if n in repo.tags():
5617 raise util.Abort(_("tag '%s' already exists "
5618 raise util.Abort(_("tag '%s' already exists "
5618 "(use -f to force)") % n)
5619 "(use -f to force)") % n)
5619 if not opts.get('local'):
5620 if not opts.get('local'):
5620 p1, p2 = repo.dirstate.parents()
5621 p1, p2 = repo.dirstate.parents()
5621 if p2 != nullid:
5622 if p2 != nullid:
5622 raise util.Abort(_('uncommitted merge'))
5623 raise util.Abort(_('uncommitted merge'))
5623 bheads = repo.branchheads()
5624 bheads = repo.branchheads()
5624 if not opts.get('force') and bheads and p1 not in bheads:
5625 if not opts.get('force') and bheads and p1 not in bheads:
5625 raise util.Abort(_('not at a branch head (use -f to force)'))
5626 raise util.Abort(_('not at a branch head (use -f to force)'))
5626 r = scmutil.revsingle(repo, rev_).node()
5627 r = scmutil.revsingle(repo, rev_).node()
5627
5628
5628 if not message:
5629 if not message:
5629 # we don't translate commit messages
5630 # we don't translate commit messages
5630 message = ('Added tag %s for changeset %s' %
5631 message = ('Added tag %s for changeset %s' %
5631 (', '.join(names), short(r)))
5632 (', '.join(names), short(r)))
5632
5633
5633 date = opts.get('date')
5634 date = opts.get('date')
5634 if date:
5635 if date:
5635 date = util.parsedate(date)
5636 date = util.parsedate(date)
5636
5637
5637 if opts.get('edit'):
5638 if opts.get('edit'):
5638 message = ui.edit(message, ui.username())
5639 message = ui.edit(message, ui.username())
5639
5640
5640 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
5641 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
5641 finally:
5642 finally:
5642 release(lock, wlock)
5643 release(lock, wlock)
5643
5644
5644 @command('tags', [], '')
5645 @command('tags', [], '')
5645 def tags(ui, repo):
5646 def tags(ui, repo):
5646 """list repository tags
5647 """list repository tags
5647
5648
5648 This lists both regular and local tags. When the -v/--verbose
5649 This lists both regular and local tags. When the -v/--verbose
5649 switch is used, a third column "local" is printed for local tags.
5650 switch is used, a third column "local" is printed for local tags.
5650
5651
5651 Returns 0 on success.
5652 Returns 0 on success.
5652 """
5653 """
5653
5654
5654 hexfunc = ui.debugflag and hex or short
5655 hexfunc = ui.debugflag and hex or short
5655 tagtype = ""
5656 tagtype = ""
5656
5657
5657 for t, n in reversed(repo.tagslist()):
5658 for t, n in reversed(repo.tagslist()):
5658 if ui.quiet:
5659 if ui.quiet:
5659 ui.write("%s\n" % t, label='tags.normal')
5660 ui.write("%s\n" % t, label='tags.normal')
5660 continue
5661 continue
5661
5662
5662 hn = hexfunc(n)
5663 hn = hexfunc(n)
5663 r = "%5d:%s" % (repo.changelog.rev(n), hn)
5664 r = "%5d:%s" % (repo.changelog.rev(n), hn)
5664 rev = ui.label(r, 'log.changeset')
5665 rev = ui.label(r, 'log.changeset')
5665 spaces = " " * (30 - encoding.colwidth(t))
5666 spaces = " " * (30 - encoding.colwidth(t))
5666
5667
5667 tag = ui.label(t, 'tags.normal')
5668 tag = ui.label(t, 'tags.normal')
5668 if ui.verbose:
5669 if ui.verbose:
5669 if repo.tagtype(t) == 'local':
5670 if repo.tagtype(t) == 'local':
5670 tagtype = " local"
5671 tagtype = " local"
5671 tag = ui.label(t, 'tags.local')
5672 tag = ui.label(t, 'tags.local')
5672 else:
5673 else:
5673 tagtype = ""
5674 tagtype = ""
5674 ui.write("%s%s %s%s\n" % (tag, spaces, rev, tagtype))
5675 ui.write("%s%s %s%s\n" % (tag, spaces, rev, tagtype))
5675
5676
5676 @command('tip',
5677 @command('tip',
5677 [('p', 'patch', None, _('show patch')),
5678 [('p', 'patch', None, _('show patch')),
5678 ('g', 'git', None, _('use git extended diff format')),
5679 ('g', 'git', None, _('use git extended diff format')),
5679 ] + templateopts,
5680 ] + templateopts,
5680 _('[-p] [-g]'))
5681 _('[-p] [-g]'))
5681 def tip(ui, repo, **opts):
5682 def tip(ui, repo, **opts):
5682 """show the tip revision
5683 """show the tip revision
5683
5684
5684 The tip revision (usually just called the tip) is the changeset
5685 The tip revision (usually just called the tip) is the changeset
5685 most recently added to the repository (and therefore the most
5686 most recently added to the repository (and therefore the most
5686 recently changed head).
5687 recently changed head).
5687
5688
5688 If you have just made a commit, that commit will be the tip. If
5689 If you have just made a commit, that commit will be the tip. If
5689 you have just pulled changes from another repository, the tip of
5690 you have just pulled changes from another repository, the tip of
5690 that repository becomes the current tip. The "tip" tag is special
5691 that repository becomes the current tip. The "tip" tag is special
5691 and cannot be renamed or assigned to a different changeset.
5692 and cannot be renamed or assigned to a different changeset.
5692
5693
5693 Returns 0 on success.
5694 Returns 0 on success.
5694 """
5695 """
5695 displayer = cmdutil.show_changeset(ui, repo, opts)
5696 displayer = cmdutil.show_changeset(ui, repo, opts)
5696 displayer.show(repo[len(repo) - 1])
5697 displayer.show(repo[len(repo) - 1])
5697 displayer.close()
5698 displayer.close()
5698
5699
5699 @command('unbundle',
5700 @command('unbundle',
5700 [('u', 'update', None,
5701 [('u', 'update', None,
5701 _('update to new branch head if changesets were unbundled'))],
5702 _('update to new branch head if changesets were unbundled'))],
5702 _('[-u] FILE...'))
5703 _('[-u] FILE...'))
5703 def unbundle(ui, repo, fname1, *fnames, **opts):
5704 def unbundle(ui, repo, fname1, *fnames, **opts):
5704 """apply one or more changegroup files
5705 """apply one or more changegroup files
5705
5706
5706 Apply one or more compressed changegroup files generated by the
5707 Apply one or more compressed changegroup files generated by the
5707 bundle command.
5708 bundle command.
5708
5709
5709 Returns 0 on success, 1 if an update has unresolved files.
5710 Returns 0 on success, 1 if an update has unresolved files.
5710 """
5711 """
5711 fnames = (fname1,) + fnames
5712 fnames = (fname1,) + fnames
5712
5713
5713 lock = repo.lock()
5714 lock = repo.lock()
5714 wc = repo['.']
5715 wc = repo['.']
5715 try:
5716 try:
5716 for fname in fnames:
5717 for fname in fnames:
5717 f = url.open(ui, fname)
5718 f = url.open(ui, fname)
5718 gen = changegroup.readbundle(f, fname)
5719 gen = changegroup.readbundle(f, fname)
5719 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
5720 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
5720 finally:
5721 finally:
5721 lock.release()
5722 lock.release()
5722 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
5723 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
5723 return postincoming(ui, repo, modheads, opts.get('update'), None)
5724 return postincoming(ui, repo, modheads, opts.get('update'), None)
5724
5725
5725 @command('^update|up|checkout|co',
5726 @command('^update|up|checkout|co',
5726 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5727 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5727 ('c', 'check', None,
5728 ('c', 'check', None,
5728 _('update across branches if no uncommitted changes')),
5729 _('update across branches if no uncommitted changes')),
5729 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5730 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5730 ('r', 'rev', '', _('revision'), _('REV'))],
5731 ('r', 'rev', '', _('revision'), _('REV'))],
5731 _('[-c] [-C] [-d DATE] [[-r] REV]'))
5732 _('[-c] [-C] [-d DATE] [[-r] REV]'))
5732 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
5733 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
5733 """update working directory (or switch revisions)
5734 """update working directory (or switch revisions)
5734
5735
5735 Update the repository's working directory to the specified
5736 Update the repository's working directory to the specified
5736 changeset. If no changeset is specified, update to the tip of the
5737 changeset. If no changeset is specified, update to the tip of the
5737 current named branch and move the current bookmark (see :hg:`help
5738 current named branch and move the current bookmark (see :hg:`help
5738 bookmarks`).
5739 bookmarks`).
5739
5740
5740 Update sets the working directory's parent revison to the specified
5741 Update sets the working directory's parent revison to the specified
5741 changeset (see :hg:`help parents`).
5742 changeset (see :hg:`help parents`).
5742
5743
5743 If the changeset is not a descendant or ancestor of the working
5744 If the changeset is not a descendant or ancestor of the working
5744 directory's parent, the update is aborted. With the -c/--check
5745 directory's parent, the update is aborted. With the -c/--check
5745 option, the working directory is checked for uncommitted changes; if
5746 option, the working directory is checked for uncommitted changes; if
5746 none are found, the working directory is updated to the specified
5747 none are found, the working directory is updated to the specified
5747 changeset.
5748 changeset.
5748
5749
5749 The following rules apply when the working directory contains
5750 The following rules apply when the working directory contains
5750 uncommitted changes:
5751 uncommitted changes:
5751
5752
5752 1. If neither -c/--check nor -C/--clean is specified, and if
5753 1. If neither -c/--check nor -C/--clean is specified, and if
5753 the requested changeset is an ancestor or descendant of
5754 the requested changeset is an ancestor or descendant of
5754 the working directory's parent, the uncommitted changes
5755 the working directory's parent, the uncommitted changes
5755 are merged into the requested changeset and the merged
5756 are merged into the requested changeset and the merged
5756 result is left uncommitted. If the requested changeset is
5757 result is left uncommitted. If the requested changeset is
5757 not an ancestor or descendant (that is, it is on another
5758 not an ancestor or descendant (that is, it is on another
5758 branch), the update is aborted and the uncommitted changes
5759 branch), the update is aborted and the uncommitted changes
5759 are preserved.
5760 are preserved.
5760
5761
5761 2. With the -c/--check option, the update is aborted and the
5762 2. With the -c/--check option, the update is aborted and the
5762 uncommitted changes are preserved.
5763 uncommitted changes are preserved.
5763
5764
5764 3. With the -C/--clean option, uncommitted changes are discarded and
5765 3. With the -C/--clean option, uncommitted changes are discarded and
5765 the working directory is updated to the requested changeset.
5766 the working directory is updated to the requested changeset.
5766
5767
5767 To cancel an uncommitted merge (and lose your changes), use
5768 To cancel an uncommitted merge (and lose your changes), use
5768 :hg:`update --clean .`.
5769 :hg:`update --clean .`.
5769
5770
5770 Use null as the changeset to remove the working directory (like
5771 Use null as the changeset to remove the working directory (like
5771 :hg:`clone -U`).
5772 :hg:`clone -U`).
5772
5773
5773 If you want to revert just one file to an older revision, use
5774 If you want to revert just one file to an older revision, use
5774 :hg:`revert [-r REV] NAME`.
5775 :hg:`revert [-r REV] NAME`.
5775
5776
5776 See :hg:`help dates` for a list of formats valid for -d/--date.
5777 See :hg:`help dates` for a list of formats valid for -d/--date.
5777
5778
5778 Returns 0 on success, 1 if there are unresolved files.
5779 Returns 0 on success, 1 if there are unresolved files.
5779 """
5780 """
5780 if rev and node:
5781 if rev and node:
5781 raise util.Abort(_("please specify just one revision"))
5782 raise util.Abort(_("please specify just one revision"))
5782
5783
5783 if rev is None or rev == '':
5784 if rev is None or rev == '':
5784 rev = node
5785 rev = node
5785
5786
5786 # with no argument, we also move the current bookmark, if any
5787 # with no argument, we also move the current bookmark, if any
5787 movemarkfrom = None
5788 movemarkfrom = None
5788 if rev is None or node == '':
5789 if rev is None or node == '':
5789 movemarkfrom = repo['.'].node()
5790 movemarkfrom = repo['.'].node()
5790
5791
5791 # if we defined a bookmark, we have to remember the original bookmark name
5792 # if we defined a bookmark, we have to remember the original bookmark name
5792 brev = rev
5793 brev = rev
5793 rev = scmutil.revsingle(repo, rev, rev).rev()
5794 rev = scmutil.revsingle(repo, rev, rev).rev()
5794
5795
5795 if check and clean:
5796 if check and clean:
5796 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
5797 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
5797
5798
5798 if date:
5799 if date:
5799 if rev is not None:
5800 if rev is not None:
5800 raise util.Abort(_("you can't specify a revision and a date"))
5801 raise util.Abort(_("you can't specify a revision and a date"))
5801 rev = cmdutil.finddate(ui, repo, date)
5802 rev = cmdutil.finddate(ui, repo, date)
5802
5803
5803 if check:
5804 if check:
5804 c = repo[None]
5805 c = repo[None]
5805 if c.dirty(merge=False, branch=False):
5806 if c.dirty(merge=False, branch=False):
5806 raise util.Abort(_("uncommitted local changes"))
5807 raise util.Abort(_("uncommitted local changes"))
5807 if rev is None:
5808 if rev is None:
5808 rev = repo[repo[None].branch()].rev()
5809 rev = repo[repo[None].branch()].rev()
5809 mergemod._checkunknown(repo, repo[None], repo[rev])
5810 mergemod._checkunknown(repo, repo[None], repo[rev])
5810
5811
5811 if clean:
5812 if clean:
5812 ret = hg.clean(repo, rev)
5813 ret = hg.clean(repo, rev)
5813 else:
5814 else:
5814 ret = hg.update(repo, rev)
5815 ret = hg.update(repo, rev)
5815
5816
5816 if not ret and movemarkfrom:
5817 if not ret and movemarkfrom:
5817 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
5818 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
5818 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
5819 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
5819 elif brev in repo._bookmarks:
5820 elif brev in repo._bookmarks:
5820 bookmarks.setcurrent(repo, brev)
5821 bookmarks.setcurrent(repo, brev)
5821 elif brev:
5822 elif brev:
5822 bookmarks.unsetcurrent(repo)
5823 bookmarks.unsetcurrent(repo)
5823
5824
5824 return ret
5825 return ret
5825
5826
5826 @command('verify', [])
5827 @command('verify', [])
5827 def verify(ui, repo):
5828 def verify(ui, repo):
5828 """verify the integrity of the repository
5829 """verify the integrity of the repository
5829
5830
5830 Verify the integrity of the current repository.
5831 Verify the integrity of the current repository.
5831
5832
5832 This will perform an extensive check of the repository's
5833 This will perform an extensive check of the repository's
5833 integrity, validating the hashes and checksums of each entry in
5834 integrity, validating the hashes and checksums of each entry in
5834 the changelog, manifest, and tracked files, as well as the
5835 the changelog, manifest, and tracked files, as well as the
5835 integrity of their crosslinks and indices.
5836 integrity of their crosslinks and indices.
5836
5837
5837 Returns 0 on success, 1 if errors are encountered.
5838 Returns 0 on success, 1 if errors are encountered.
5838 """
5839 """
5839 return hg.verify(repo)
5840 return hg.verify(repo)
5840
5841
5841 @command('version', [])
5842 @command('version', [])
5842 def version_(ui):
5843 def version_(ui):
5843 """output version and copyright information"""
5844 """output version and copyright information"""
5844 ui.write(_("Mercurial Distributed SCM (version %s)\n")
5845 ui.write(_("Mercurial Distributed SCM (version %s)\n")
5845 % util.version())
5846 % util.version())
5846 ui.status(_(
5847 ui.status(_(
5847 "(see http://mercurial.selenic.com for more information)\n"
5848 "(see http://mercurial.selenic.com for more information)\n"
5848 "\nCopyright (C) 2005-2012 Matt Mackall and others\n"
5849 "\nCopyright (C) 2005-2012 Matt Mackall and others\n"
5849 "This is free software; see the source for copying conditions. "
5850 "This is free software; see the source for copying conditions. "
5850 "There is NO\nwarranty; "
5851 "There is NO\nwarranty; "
5851 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5852 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5852 ))
5853 ))
5853
5854
5854 norepo = ("clone init version help debugcommands debugcomplete"
5855 norepo = ("clone init version help debugcommands debugcomplete"
5855 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
5856 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
5856 " debugknown debuggetbundle debugbundle")
5857 " debugknown debuggetbundle debugbundle")
5857 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
5858 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
5858 " debugdata debugindex debugindexdot debugrevlog")
5859 " debugdata debugindex debugindexdot debugrevlog")
@@ -1,266 +1,266 b''
1 # discovery.py - protocol changeset discovery functions
1 # discovery.py - protocol changeset discovery functions
2 #
2 #
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2010 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 nullid, short
8 from node import nullid, short
9 from i18n import _
9 from i18n import _
10 import util, setdiscovery, treediscovery, phases
10 import util, setdiscovery, treediscovery, phases
11
11
12 def findcommonincoming(repo, remote, heads=None, force=False):
12 def findcommonincoming(repo, remote, heads=None, force=False):
13 """Return a tuple (common, anyincoming, heads) used to identify the common
13 """Return a tuple (common, anyincoming, heads) used to identify the common
14 subset of nodes between repo and remote.
14 subset of nodes between repo and remote.
15
15
16 "common" is a list of (at least) the heads of the common subset.
16 "common" is a list of (at least) the heads of the common subset.
17 "anyincoming" is testable as a boolean indicating if any nodes are missing
17 "anyincoming" is testable as a boolean indicating if any nodes are missing
18 locally. If remote does not support getbundle, this actually is a list of
18 locally. If remote does not support getbundle, this actually is a list of
19 roots of the nodes that would be incoming, to be supplied to
19 roots of the nodes that would be incoming, to be supplied to
20 changegroupsubset. No code except for pull should be relying on this fact
20 changegroupsubset. No code except for pull should be relying on this fact
21 any longer.
21 any longer.
22 "heads" is either the supplied heads, or else the remote's heads.
22 "heads" is either the supplied heads, or else the remote's heads.
23
23
24 If you pass heads and they are all known locally, the reponse lists justs
24 If you pass heads and they are all known locally, the reponse lists justs
25 these heads in "common" and in "heads".
25 these heads in "common" and in "heads".
26
26
27 Please use findcommonoutgoing to compute the set of outgoing nodes to give
27 Please use findcommonoutgoing to compute the set of outgoing nodes to give
28 extensions a good hook into outgoing.
28 extensions a good hook into outgoing.
29 """
29 """
30
30
31 if not remote.capable('getbundle'):
31 if not remote.capable('getbundle'):
32 return treediscovery.findcommonincoming(repo, remote, heads, force)
32 return treediscovery.findcommonincoming(repo, remote, heads, force)
33
33
34 if heads:
34 if heads:
35 allknown = True
35 allknown = True
36 nm = repo.changelog.nodemap
36 nm = repo.changelog.nodemap
37 for h in heads:
37 for h in heads:
38 if nm.get(h) is None:
38 if nm.get(h) is None:
39 allknown = False
39 allknown = False
40 break
40 break
41 if allknown:
41 if allknown:
42 return (heads, False, heads)
42 return (heads, False, heads)
43
43
44 res = setdiscovery.findcommonheads(repo.ui, repo, remote,
44 res = setdiscovery.findcommonheads(repo.ui, repo, remote,
45 abortwhenunrelated=not force)
45 abortwhenunrelated=not force)
46 common, anyinc, srvheads = res
46 common, anyinc, srvheads = res
47 return (list(common), anyinc, heads or list(srvheads))
47 return (list(common), anyinc, heads or list(srvheads))
48
48
49 class outgoing(object):
49 class outgoing(object):
50 '''Represents the set of nodes present in a local repo but not in a
50 '''Represents the set of nodes present in a local repo but not in a
51 (possibly) remote one.
51 (possibly) remote one.
52
52
53 Members:
53 Members:
54
54
55 missing is a list of all nodes present in local but not in remote.
55 missing is a list of all nodes present in local but not in remote.
56 common is a list of all nodes shared between the two repos.
56 common is a list of all nodes shared between the two repos.
57 excluded is the list of missing changeset that shouldn't be sent remotely.
57 excluded is the list of missing changeset that shouldn't be sent remotely.
58 missingheads is the list of heads of missing.
58 missingheads is the list of heads of missing.
59 commonheads is the list of heads of common.
59 commonheads is the list of heads of common.
60
60
61 The sets are computed on demand from the heads, unless provided upfront
61 The sets are computed on demand from the heads, unless provided upfront
62 by discovery.'''
62 by discovery.'''
63
63
64 def __init__(self, revlog, commonheads, missingheads):
64 def __init__(self, revlog, commonheads, missingheads):
65 self.commonheads = commonheads
65 self.commonheads = commonheads
66 self.missingheads = missingheads
66 self.missingheads = missingheads
67 self._revlog = revlog
67 self._revlog = revlog
68 self._common = None
68 self._common = None
69 self._missing = None
69 self._missing = None
70 self.excluded = []
70 self.excluded = []
71
71
72 def _computecommonmissing(self):
72 def _computecommonmissing(self):
73 sets = self._revlog.findcommonmissing(self.commonheads,
73 sets = self._revlog.findcommonmissing(self.commonheads,
74 self.missingheads)
74 self.missingheads)
75 self._common, self._missing = sets
75 self._common, self._missing = sets
76
76
77 @util.propertycache
77 @util.propertycache
78 def common(self):
78 def common(self):
79 if self._common is None:
79 if self._common is None:
80 self._computecommonmissing()
80 self._computecommonmissing()
81 return self._common
81 return self._common
82
82
83 @util.propertycache
83 @util.propertycache
84 def missing(self):
84 def missing(self):
85 if self._missing is None:
85 if self._missing is None:
86 self._computecommonmissing()
86 self._computecommonmissing()
87 return self._missing
87 return self._missing
88
88
89 def findcommonoutgoing(repo, other, onlyheads=None, force=False,
89 def findcommonoutgoing(repo, other, onlyheads=None, force=False,
90 commoninc=None, portable=False):
90 commoninc=None, portable=False):
91 '''Return an outgoing instance to identify the nodes present in repo but
91 '''Return an outgoing instance to identify the nodes present in repo but
92 not in other.
92 not in other.
93
93
94 If onlyheads is given, only nodes ancestral to nodes in onlyheads
94 If onlyheads is given, only nodes ancestral to nodes in onlyheads
95 (inclusive) are included. If you already know the local repo's heads,
95 (inclusive) are included. If you already know the local repo's heads,
96 passing them in onlyheads is faster than letting them be recomputed here.
96 passing them in onlyheads is faster than letting them be recomputed here.
97
97
98 If commoninc is given, it must the the result of a prior call to
98 If commoninc is given, it must the the result of a prior call to
99 findcommonincoming(repo, other, force) to avoid recomputing it here.
99 findcommonincoming(repo, other, force) to avoid recomputing it here.
100
100
101 If portable is given, compute more conservative common and missingheads,
101 If portable is given, compute more conservative common and missingheads,
102 to make bundles created from the instance more portable.'''
102 to make bundles created from the instance more portable.'''
103 # declare an empty outgoing object to be filled later
103 # declare an empty outgoing object to be filled later
104 og = outgoing(repo.changelog, None, None)
104 og = outgoing(repo.changelog, None, None)
105
105
106 # get common set if not provided
106 # get common set if not provided
107 if commoninc is None:
107 if commoninc is None:
108 commoninc = findcommonincoming(repo, other, force=force)
108 commoninc = findcommonincoming(repo, other, force=force)
109 og.commonheads, _any, _hds = commoninc
109 og.commonheads, _any, _hds = commoninc
110
110
111 # compute outgoing
111 # compute outgoing
112 if not repo._phasecache.phaseroots[phases.secret]:
112 if not repo._phasecache.phaseroots[phases.secret]:
113 og.missingheads = onlyheads or repo.heads()
113 og.missingheads = onlyheads or repo.heads()
114 elif onlyheads is None:
114 elif onlyheads is None:
115 # use visible heads as it should be cached
115 # use visible heads as it should be cached
116 og.missingheads = phases.visibleheads(repo)
116 og.missingheads = phases.visibleheads(repo)
117 og.excluded = [ctx.node() for ctx in repo.set('secret()')]
117 og.excluded = [ctx.node() for ctx in repo.set('secret()')]
118 else:
118 else:
119 # compute common, missing and exclude secret stuff
119 # compute common, missing and exclude secret stuff
120 sets = repo.changelog.findcommonmissing(og.commonheads, onlyheads)
120 sets = repo.changelog.findcommonmissing(og.commonheads, onlyheads)
121 og._common, allmissing = sets
121 og._common, allmissing = sets
122 og._missing = missing = []
122 og._missing = missing = []
123 og.excluded = excluded = []
123 og.excluded = excluded = []
124 for node in allmissing:
124 for node in allmissing:
125 if repo[node].phase() >= phases.secret:
125 if repo[node].phase() >= phases.secret:
126 excluded.append(node)
126 excluded.append(node)
127 else:
127 else:
128 missing.append(node)
128 missing.append(node)
129 if excluded:
129 if excluded:
130 # update missing heads
130 # update missing heads
131 missingheads = phases.newheads(repo, onlyheads, excluded)
131 missingheads = phases.newheads(repo, onlyheads, excluded)
132 else:
132 else:
133 missingheads = onlyheads
133 missingheads = onlyheads
134 og.missingheads = missingheads
134 og.missingheads = missingheads
135
135
136 if portable:
136 if portable:
137 # recompute common and missingheads as if -r<rev> had been given for
137 # recompute common and missingheads as if -r<rev> had been given for
138 # each head of missing, and --base <rev> for each head of the proper
138 # each head of missing, and --base <rev> for each head of the proper
139 # ancestors of missing
139 # ancestors of missing
140 og._computecommonmissing()
140 og._computecommonmissing()
141 cl = repo.changelog
141 cl = repo.changelog
142 missingrevs = set(cl.rev(n) for n in og._missing)
142 missingrevs = set(cl.rev(n) for n in og._missing)
143 og._common = set(cl.ancestors(missingrevs)) - missingrevs
143 og._common = set(cl.ancestors(missingrevs)) - missingrevs
144 commonheads = set(og.commonheads)
144 commonheads = set(og.commonheads)
145 og.missingheads = [h for h in og.missingheads if h not in commonheads]
145 og.missingheads = [h for h in og.missingheads if h not in commonheads]
146
146
147 return og
147 return og
148
148
149 def checkheads(repo, remote, outgoing, remoteheads, newbranch=False, inc=False):
149 def checkheads(repo, remote, outgoing, remoteheads, newbranch=False, inc=False):
150 """Check that a push won't add any outgoing head
150 """Check that a push won't add any outgoing head
151
151
152 raise Abort error and display ui message as needed.
152 raise Abort error and display ui message as needed.
153 """
153 """
154 if remoteheads == [nullid]:
154 if remoteheads == [nullid]:
155 # remote is empty, nothing to check.
155 # remote is empty, nothing to check.
156 return
156 return
157
157
158 cl = repo.changelog
158 cl = repo.changelog
159 if remote.capable('branchmap'):
159 if remote.capable('branchmap'):
160 # Check for each named branch if we're creating new remote heads.
160 # Check for each named branch if we're creating new remote heads.
161 # To be a remote head after push, node must be either:
161 # To be a remote head after push, node must be either:
162 # - unknown locally
162 # - unknown locally
163 # - a local outgoing head descended from update
163 # - a local outgoing head descended from update
164 # - a remote head that's known locally and not
164 # - a remote head that's known locally and not
165 # ancestral to an outgoing head
165 # ancestral to an outgoing head
166
166
167 # 1. Create set of branches involved in the push.
167 # 1. Create set of branches involved in the push.
168 branches = set(repo[n].branch() for n in outgoing.missing)
168 branches = set(repo[n].branch() for n in outgoing.missing)
169
169
170 # 2. Check for new branches on the remote.
170 # 2. Check for new branches on the remote.
171 if remote.local():
171 if remote.local():
172 remotemap = phases.visiblebranchmap(remote)
172 remotemap = phases.visiblebranchmap(remote.local())
173 else:
173 else:
174 remotemap = remote.branchmap()
174 remotemap = remote.branchmap()
175 newbranches = branches - set(remotemap)
175 newbranches = branches - set(remotemap)
176 if newbranches and not newbranch: # new branch requires --new-branch
176 if newbranches and not newbranch: # new branch requires --new-branch
177 branchnames = ', '.join(sorted(newbranches))
177 branchnames = ', '.join(sorted(newbranches))
178 raise util.Abort(_("push creates new remote branches: %s!")
178 raise util.Abort(_("push creates new remote branches: %s!")
179 % branchnames,
179 % branchnames,
180 hint=_("use 'hg push --new-branch' to create"
180 hint=_("use 'hg push --new-branch' to create"
181 " new remote branches"))
181 " new remote branches"))
182 branches.difference_update(newbranches)
182 branches.difference_update(newbranches)
183
183
184 # 3. Construct the initial oldmap and newmap dicts.
184 # 3. Construct the initial oldmap and newmap dicts.
185 # They contain information about the remote heads before and
185 # They contain information about the remote heads before and
186 # after the push, respectively.
186 # after the push, respectively.
187 # Heads not found locally are not included in either dict,
187 # Heads not found locally are not included in either dict,
188 # since they won't be affected by the push.
188 # since they won't be affected by the push.
189 # unsynced contains all branches with incoming changesets.
189 # unsynced contains all branches with incoming changesets.
190 oldmap = {}
190 oldmap = {}
191 newmap = {}
191 newmap = {}
192 unsynced = set()
192 unsynced = set()
193 for branch in branches:
193 for branch in branches:
194 remotebrheads = remotemap[branch]
194 remotebrheads = remotemap[branch]
195 prunedbrheads = [h for h in remotebrheads if h in cl.nodemap]
195 prunedbrheads = [h for h in remotebrheads if h in cl.nodemap]
196 oldmap[branch] = prunedbrheads
196 oldmap[branch] = prunedbrheads
197 newmap[branch] = list(prunedbrheads)
197 newmap[branch] = list(prunedbrheads)
198 if len(remotebrheads) > len(prunedbrheads):
198 if len(remotebrheads) > len(prunedbrheads):
199 unsynced.add(branch)
199 unsynced.add(branch)
200
200
201 # 4. Update newmap with outgoing changes.
201 # 4. Update newmap with outgoing changes.
202 # This will possibly add new heads and remove existing ones.
202 # This will possibly add new heads and remove existing ones.
203 ctxgen = (repo[n] for n in outgoing.missing)
203 ctxgen = (repo[n] for n in outgoing.missing)
204 repo._updatebranchcache(newmap, ctxgen)
204 repo._updatebranchcache(newmap, ctxgen)
205
205
206 else:
206 else:
207 # 1-4b. old servers: Check for new topological heads.
207 # 1-4b. old servers: Check for new topological heads.
208 # Construct {old,new}map with branch = None (topological branch).
208 # Construct {old,new}map with branch = None (topological branch).
209 # (code based on _updatebranchcache)
209 # (code based on _updatebranchcache)
210 oldheads = set(h for h in remoteheads if h in cl.nodemap)
210 oldheads = set(h for h in remoteheads if h in cl.nodemap)
211 # all nodes in outgoing.missing are children of either:
211 # all nodes in outgoing.missing are children of either:
212 # - an element of oldheads
212 # - an element of oldheads
213 # - another element of outgoing.missing
213 # - another element of outgoing.missing
214 # - nullrev
214 # - nullrev
215 # This explains why the new head are very simple to compute.
215 # This explains why the new head are very simple to compute.
216 r = repo.set('heads(%ln + %ln)', oldheads, outgoing.missing)
216 r = repo.set('heads(%ln + %ln)', oldheads, outgoing.missing)
217 branches = set([None])
217 branches = set([None])
218 newmap = {None: list(c.node() for c in r)}
218 newmap = {None: list(c.node() for c in r)}
219 oldmap = {None: oldheads}
219 oldmap = {None: oldheads}
220 unsynced = inc and branches or set()
220 unsynced = inc and branches or set()
221
221
222 # 5. Check for new heads.
222 # 5. Check for new heads.
223 # If there are more heads after the push than before, a suitable
223 # If there are more heads after the push than before, a suitable
224 # error message, depending on unsynced status, is displayed.
224 # error message, depending on unsynced status, is displayed.
225 error = None
225 error = None
226 localbookmarks = repo._bookmarks
226 localbookmarks = repo._bookmarks
227
227
228 for branch in branches:
228 for branch in branches:
229 newhs = set(newmap[branch])
229 newhs = set(newmap[branch])
230 oldhs = set(oldmap[branch])
230 oldhs = set(oldmap[branch])
231 dhs = None
231 dhs = None
232 if len(newhs) > len(oldhs):
232 if len(newhs) > len(oldhs):
233 # strip updates to existing remote heads from the new heads list
233 # strip updates to existing remote heads from the new heads list
234 remotebookmarks = remote.listkeys('bookmarks')
234 remotebookmarks = remote.listkeys('bookmarks')
235 bookmarkedheads = set()
235 bookmarkedheads = set()
236 for bm in localbookmarks:
236 for bm in localbookmarks:
237 rnode = remotebookmarks.get(bm)
237 rnode = remotebookmarks.get(bm)
238 if rnode and rnode in repo:
238 if rnode and rnode in repo:
239 lctx, rctx = repo[bm], repo[rnode]
239 lctx, rctx = repo[bm], repo[rnode]
240 if rctx == lctx.ancestor(rctx):
240 if rctx == lctx.ancestor(rctx):
241 bookmarkedheads.add(lctx.node())
241 bookmarkedheads.add(lctx.node())
242 dhs = list(newhs - bookmarkedheads - oldhs)
242 dhs = list(newhs - bookmarkedheads - oldhs)
243 if dhs:
243 if dhs:
244 if error is None:
244 if error is None:
245 if branch not in ('default', None):
245 if branch not in ('default', None):
246 error = _("push creates new remote head %s "
246 error = _("push creates new remote head %s "
247 "on branch '%s'!") % (short(dhs[0]), branch)
247 "on branch '%s'!") % (short(dhs[0]), branch)
248 else:
248 else:
249 error = _("push creates new remote head %s!"
249 error = _("push creates new remote head %s!"
250 ) % short(dhs[0])
250 ) % short(dhs[0])
251 if branch in unsynced:
251 if branch in unsynced:
252 hint = _("you should pull and merge or "
252 hint = _("you should pull and merge or "
253 "use push -f to force")
253 "use push -f to force")
254 else:
254 else:
255 hint = _("did you forget to merge? "
255 hint = _("did you forget to merge? "
256 "use push -f to force")
256 "use push -f to force")
257 if branch is not None:
257 if branch is not None:
258 repo.ui.note(_("new remote heads on branch '%s'\n") % branch)
258 repo.ui.note(_("new remote heads on branch '%s'\n") % branch)
259 for h in dhs:
259 for h in dhs:
260 repo.ui.note(_("new remote head %s\n") % short(h))
260 repo.ui.note(_("new remote head %s\n") % short(h))
261 if error:
261 if error:
262 raise util.Abort(error, hint=hint)
262 raise util.Abort(error, hint=hint)
263
263
264 # 6. Check for unsynced changes on involved branches.
264 # 6. Check for unsynced changes on involved branches.
265 if unsynced:
265 if unsynced:
266 repo.ui.warn(_("note: unsynced remote changes!\n"))
266 repo.ui.warn(_("note: unsynced remote changes!\n"))
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now