##// END OF EJS Templates
graft: get the editor later...
marmoute -
r53232:0b52283d default
parent child Browse files
Show More
@@ -1,323 +1,322
1 # graft.py - implementation of the graft command
1 # graft.py - implementation of the graft command
2
2
3 from ..i18n import _
3 from ..i18n import _
4
4
5 from .. import cmdutil, error, logcmdutil, merge as mergemod, state as statemod
5 from .. import cmdutil, error, logcmdutil, merge as mergemod, state as statemod
6
6
7
7
8 def cmd_graft(ui, repo, *revs, **opts):
8 def cmd_graft(ui, repo, *revs, **opts):
9 """implement the graft command as defined in mercuria/commands.py"""
9 """implement the graft command as defined in mercuria/commands.py"""
10 ret = _process_args(ui, repo, *revs, **opts)
10 ret = _process_args(ui, repo, *revs, **opts)
11 action, graftstate, args = ret
11 action, graftstate, args = ret
12 if action == "ERROR":
12 if action == "ERROR":
13 return -1
13 return -1
14 elif action == "ABORT":
14 elif action == "ABORT":
15 assert args is None
15 assert args is None
16 return cmdutil.abortgraft(ui, repo, graftstate)
16 return cmdutil.abortgraft(ui, repo, graftstate)
17 elif action == "STOP":
17 elif action == "STOP":
18 assert args is None
18 assert args is None
19 return _stopgraft(ui, repo, graftstate)
19 return _stopgraft(ui, repo, graftstate)
20 elif action == "GRAFT":
20 elif action == "GRAFT":
21 return _graft_revisions(ui, repo, graftstate, *args)
21 return _graft_revisions(ui, repo, graftstate, *args)
22 else:
22 else:
23 raise error.ProgrammingError(b'unknown action: %s' % action)
23 raise error.ProgrammingError(b'unknown action: %s' % action)
24
24
25
25
26 def _process_args(ui, repo, *revs, **opts):
26 def _process_args(ui, repo, *revs, **opts):
27 """process the graft command argument to figure out what to do
27 """process the graft command argument to figure out what to do
28
28
29 This also filter the selected revision to skip the one that cannot be graft
29 This also filter the selected revision to skip the one that cannot be graft
30 or were alredy grafted.
30 or were alredy grafted.
31 """
31 """
32 if revs and opts.get('rev'):
32 if revs and opts.get('rev'):
33 ui.warn(
33 ui.warn(
34 _(
34 _(
35 b'warning: inconsistent use of --rev might give unexpected '
35 b'warning: inconsistent use of --rev might give unexpected '
36 b'revision ordering!\n'
36 b'revision ordering!\n'
37 )
37 )
38 )
38 )
39
39
40 revs = list(revs)
40 revs = list(revs)
41 revs.extend(opts.get('rev'))
41 revs.extend(opts.get('rev'))
42 # a dict of data to be stored in state file
42 # a dict of data to be stored in state file
43 statedata = {}
43 statedata = {}
44 # list of new nodes created by ongoing graft
44 # list of new nodes created by ongoing graft
45 statedata[b'newnodes'] = []
45 statedata[b'newnodes'] = []
46
46
47 cmdutil.resolve_commit_options(ui, opts)
47 cmdutil.resolve_commit_options(ui, opts)
48
48
49 editor = cmdutil.getcommiteditor(editform=b'graft', **opts)
50
51 cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue')
49 cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue')
52
50
53 cont = False
51 cont = False
54 if opts.get('no_commit'):
52 if opts.get('no_commit'):
55 cmdutil.check_incompatible_arguments(
53 cmdutil.check_incompatible_arguments(
56 opts,
54 opts,
57 'no_commit',
55 'no_commit',
58 ['edit', 'currentuser', 'currentdate', 'log'],
56 ['edit', 'currentuser', 'currentdate', 'log'],
59 )
57 )
60
58
61 graftstate = statemod.cmdstate(repo, b'graftstate')
59 graftstate = statemod.cmdstate(repo, b'graftstate')
62
60
63 if opts.get('stop'):
61 if opts.get('stop'):
64 cmdutil.check_incompatible_arguments(
62 cmdutil.check_incompatible_arguments(
65 opts,
63 opts,
66 'stop',
64 'stop',
67 [
65 [
68 'edit',
66 'edit',
69 'log',
67 'log',
70 'user',
68 'user',
71 'date',
69 'date',
72 'currentdate',
70 'currentdate',
73 'currentuser',
71 'currentuser',
74 'rev',
72 'rev',
75 ],
73 ],
76 )
74 )
77 return "STOP", graftstate, None
75 return "STOP", graftstate, None
78 elif opts.get('abort'):
76 elif opts.get('abort'):
79 cmdutil.check_incompatible_arguments(
77 cmdutil.check_incompatible_arguments(
80 opts,
78 opts,
81 'abort',
79 'abort',
82 [
80 [
83 'edit',
81 'edit',
84 'log',
82 'log',
85 'user',
83 'user',
86 'date',
84 'date',
87 'currentdate',
85 'currentdate',
88 'currentuser',
86 'currentuser',
89 'rev',
87 'rev',
90 ],
88 ],
91 )
89 )
92 return "ABORT", graftstate, None
90 return "ABORT", graftstate, None
93 elif opts.get('continue'):
91 elif opts.get('continue'):
94 cont = True
92 cont = True
95 if revs:
93 if revs:
96 raise error.InputError(_(b"can't specify --continue and revisions"))
94 raise error.InputError(_(b"can't specify --continue and revisions"))
97 # read in unfinished revisions
95 # read in unfinished revisions
98 if graftstate.exists():
96 if graftstate.exists():
99 statedata = cmdutil.readgraftstate(repo, graftstate)
97 statedata = cmdutil.readgraftstate(repo, graftstate)
100 if statedata.get(b'no_commit'):
98 if statedata.get(b'no_commit'):
101 opts['no_commit'] = statedata.get(b'no_commit')
99 opts['no_commit'] = statedata.get(b'no_commit')
102 if statedata.get(b'base'):
100 if statedata.get(b'base'):
103 opts['base'] = statedata.get(b'base')
101 opts['base'] = statedata.get(b'base')
104 nodes = statedata[b'nodes']
102 nodes = statedata[b'nodes']
105 revs = [repo[node].rev() for node in nodes]
103 revs = [repo[node].rev() for node in nodes]
106 else:
104 else:
107 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
105 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
108 elif not revs:
106 elif not revs:
109 raise error.InputError(_(b'no revisions specified'))
107 raise error.InputError(_(b'no revisions specified'))
110 else:
108 else:
111 cmdutil.checkunfinished(repo)
109 cmdutil.checkunfinished(repo)
112 cmdutil.bailifchanged(repo)
110 cmdutil.bailifchanged(repo)
113 revs = logcmdutil.revrange(repo, revs)
111 revs = logcmdutil.revrange(repo, revs)
114
112
115 for o in (
113 for o in (
116 b'date',
114 b'date',
117 b'user',
115 b'user',
118 b'log',
116 b'log',
119 b'no_commit',
117 b'no_commit',
120 b'dry_run',
118 b'dry_run',
121 ):
119 ):
122 v = opts.get(o.decode('ascii'))
120 v = opts.get(o.decode('ascii'))
123 # if statedata is already set, it comes from --continue and test says
121 # if statedata is already set, it comes from --continue and test says
124 # we should honor them above the options (which seems weird).
122 # we should honor them above the options (which seems weird).
125 if v and o not in statedata:
123 if v and o not in statedata:
126 statedata[o] = v
124 statedata[o] = v
127
125
128 skipped = set()
126 skipped = set()
129 basectx = None
127 basectx = None
130 if opts.get('base'):
128 if opts.get('base'):
131 basectx = logcmdutil.revsingle(repo, opts['base'], None)
129 basectx = logcmdutil.revsingle(repo, opts['base'], None)
132 statedata[b'base'] = basectx.hex()
130 statedata[b'base'] = basectx.hex()
133 if basectx is None:
131 if basectx is None:
134 # check for merges
132 # check for merges
135 for rev in repo.revs(b'%ld and merge()', revs):
133 for rev in repo.revs(b'%ld and merge()', revs):
136 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
134 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
137 skipped.add(rev)
135 skipped.add(rev)
138 revs = [r for r in revs if r not in skipped]
136 revs = [r for r in revs if r not in skipped]
139 if not revs:
137 if not revs:
140 return "ERROR", None, None
138 return "ERROR", None, None
141 if basectx is not None and len(revs) != 1:
139 if basectx is not None and len(revs) != 1:
142 raise error.InputError(_(b'only one revision allowed with --base '))
140 raise error.InputError(_(b'only one revision allowed with --base '))
143
141
144 # Don't check in the --continue case, in effect retaining --force across
142 # Don't check in the --continue case, in effect retaining --force across
145 # --continues. That's because without --force, any revisions we decided to
143 # --continues. That's because without --force, any revisions we decided to
146 # skip would have been filtered out here, so they wouldn't have made their
144 # skip would have been filtered out here, so they wouldn't have made their
147 # way to the graftstate. With --force, any revisions we would have otherwise
145 # way to the graftstate. With --force, any revisions we would have otherwise
148 # skipped would not have been filtered out, and if they hadn't been applied
146 # skipped would not have been filtered out, and if they hadn't been applied
149 # already, they'd have been in the graftstate.
147 # already, they'd have been in the graftstate.
150 if not (cont or opts.get('force')) and basectx is None:
148 if not (cont or opts.get('force')) and basectx is None:
151 # check for ancestors of dest branch
149 # check for ancestors of dest branch
152 ancestors = repo.revs(b'%ld & (::.)', revs)
150 ancestors = repo.revs(b'%ld & (::.)', revs)
153 for rev in ancestors:
151 for rev in ancestors:
154 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
152 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
155
153
156 revs = [r for r in revs if r not in ancestors]
154 revs = [r for r in revs if r not in ancestors]
157
155
158 if not revs:
156 if not revs:
159 return "ERROR", None, None
157 return "ERROR", None, None
160
158
161 # analyze revs for earlier grafts
159 # analyze revs for earlier grafts
162 ids = {}
160 ids = {}
163 for ctx in repo.set(b"%ld", revs):
161 for ctx in repo.set(b"%ld", revs):
164 ids[ctx.hex()] = ctx.rev()
162 ids[ctx.hex()] = ctx.rev()
165 n = ctx.extra().get(b'source')
163 n = ctx.extra().get(b'source')
166 if n:
164 if n:
167 ids[n] = ctx.rev()
165 ids[n] = ctx.rev()
168
166
169 # check ancestors for earlier grafts
167 # check ancestors for earlier grafts
170 ui.debug(b'scanning for duplicate grafts\n')
168 ui.debug(b'scanning for duplicate grafts\n')
171
169
172 # The only changesets we can be sure doesn't contain grafts of any
170 # The only changesets we can be sure doesn't contain grafts of any
173 # revs, are the ones that are common ancestors of *all* revs:
171 # revs, are the ones that are common ancestors of *all* revs:
174 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
172 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
175 ctx = repo[rev]
173 ctx = repo[rev]
176 n = ctx.extra().get(b'source')
174 n = ctx.extra().get(b'source')
177 if n in ids:
175 if n in ids:
178 try:
176 try:
179 r = repo[n].rev()
177 r = repo[n].rev()
180 except error.RepoLookupError:
178 except error.RepoLookupError:
181 r = None
179 r = None
182 if r in revs:
180 if r in revs:
183 ui.warn(
181 ui.warn(
184 _(
182 _(
185 b'skipping revision %d:%s '
183 b'skipping revision %d:%s '
186 b'(already grafted to %d:%s)\n'
184 b'(already grafted to %d:%s)\n'
187 )
185 )
188 % (r, repo[r], rev, ctx)
186 % (r, repo[r], rev, ctx)
189 )
187 )
190 revs.remove(r)
188 revs.remove(r)
191 elif ids[n] in revs:
189 elif ids[n] in revs:
192 if r is None:
190 if r is None:
193 ui.warn(
191 ui.warn(
194 _(
192 _(
195 b'skipping already grafted revision %d:%s '
193 b'skipping already grafted revision %d:%s '
196 b'(%d:%s also has unknown origin %s)\n'
194 b'(%d:%s also has unknown origin %s)\n'
197 )
195 )
198 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
196 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
199 )
197 )
200 else:
198 else:
201 ui.warn(
199 ui.warn(
202 _(
200 _(
203 b'skipping already grafted revision %d:%s '
201 b'skipping already grafted revision %d:%s '
204 b'(%d:%s also has origin %d:%s)\n'
202 b'(%d:%s also has origin %d:%s)\n'
205 )
203 )
206 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
204 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
207 )
205 )
208 revs.remove(ids[n])
206 revs.remove(ids[n])
209 elif ctx.hex() in ids:
207 elif ctx.hex() in ids:
210 r = ids[ctx.hex()]
208 r = ids[ctx.hex()]
211 if r in revs:
209 if r in revs:
212 ui.warn(
210 ui.warn(
213 _(
211 _(
214 b'skipping already grafted revision %d:%s '
212 b'skipping already grafted revision %d:%s '
215 b'(was grafted from %d:%s)\n'
213 b'(was grafted from %d:%s)\n'
216 )
214 )
217 % (r, repo[r], rev, ctx)
215 % (r, repo[r], rev, ctx)
218 )
216 )
219 revs.remove(r)
217 revs.remove(r)
220 if not revs:
218 if not revs:
221 return "ERROR", None, None
219 return "ERROR", None, None
222
220
221 editor = cmdutil.getcommiteditor(editform=b'graft', **opts)
223 dry_run = bool(opts.get("dry_run"))
222 dry_run = bool(opts.get("dry_run"))
224 tool = opts.get('tool', b'')
223 tool = opts.get('tool', b'')
225 return "GRAFT", graftstate, (statedata, revs, editor, cont, dry_run, tool)
224 return "GRAFT", graftstate, (statedata, revs, editor, cont, dry_run, tool)
226
225
227
226
228 def _graft_revisions(
227 def _graft_revisions(
229 ui,
228 ui,
230 repo,
229 repo,
231 graftstate,
230 graftstate,
232 statedata,
231 statedata,
233 revs,
232 revs,
234 editor,
233 editor,
235 cont=False,
234 cont=False,
236 dry_run=False,
235 dry_run=False,
237 tool=b'',
236 tool=b'',
238 ):
237 ):
239 """actually graft some revisions"""
238 """actually graft some revisions"""
240 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
239 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
241 desc = b'%d:%s "%s"' % (
240 desc = b'%d:%s "%s"' % (
242 ctx.rev(),
241 ctx.rev(),
243 ctx,
242 ctx,
244 ctx.description().split(b'\n', 1)[0],
243 ctx.description().split(b'\n', 1)[0],
245 )
244 )
246 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
245 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
247 if names:
246 if names:
248 desc += b' (%s)' % b' '.join(names)
247 desc += b' (%s)' % b' '.join(names)
249 ui.status(_(b'grafting %s\n') % desc)
248 ui.status(_(b'grafting %s\n') % desc)
250 if dry_run:
249 if dry_run:
251 continue
250 continue
252
251
253 source = ctx.extra().get(b'source')
252 source = ctx.extra().get(b'source')
254 extra = {}
253 extra = {}
255 if source:
254 if source:
256 extra[b'source'] = source
255 extra[b'source'] = source
257 extra[b'intermediate-source'] = ctx.hex()
256 extra[b'intermediate-source'] = ctx.hex()
258 else:
257 else:
259 extra[b'source'] = ctx.hex()
258 extra[b'source'] = ctx.hex()
260 user = statedata.get(b'user', ctx.user())
259 user = statedata.get(b'user', ctx.user())
261 date = statedata.get(b'date', ctx.date())
260 date = statedata.get(b'date', ctx.date())
262 message = ctx.description()
261 message = ctx.description()
263 if statedata.get(b'log'):
262 if statedata.get(b'log'):
264 message += b'\n(grafted from %s)' % ctx.hex()
263 message += b'\n(grafted from %s)' % ctx.hex()
265
264
266 # we don't merge the first commit when continuing
265 # we don't merge the first commit when continuing
267 if not cont:
266 if not cont:
268 # perform the graft merge with p1(rev) as 'ancestor'
267 # perform the graft merge with p1(rev) as 'ancestor'
269 overrides = {(b'ui', b'forcemerge'): tool}
268 overrides = {(b'ui', b'forcemerge'): tool}
270 if b'base' in statedata:
269 if b'base' in statedata:
271 base = repo[statedata[b'base']]
270 base = repo[statedata[b'base']]
272 else:
271 else:
273 base = ctx.p1()
272 base = ctx.p1()
274 with ui.configoverride(overrides, b'graft'):
273 with ui.configoverride(overrides, b'graft'):
275 stats = mergemod.graft(
274 stats = mergemod.graft(
276 repo, ctx, base, [b'local', b'graft', b'parent of graft']
275 repo, ctx, base, [b'local', b'graft', b'parent of graft']
277 )
276 )
278 # report any conflicts
277 # report any conflicts
279 if stats.unresolvedcount > 0:
278 if stats.unresolvedcount > 0:
280 # write out state for --continue
279 # write out state for --continue
281 nodes = [repo[rev].hex() for rev in revs[pos:]]
280 nodes = [repo[rev].hex() for rev in revs[pos:]]
282 statedata[b'nodes'] = nodes
281 statedata[b'nodes'] = nodes
283 stateversion = 1
282 stateversion = 1
284 graftstate.save(stateversion, statedata)
283 graftstate.save(stateversion, statedata)
285 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
284 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
286 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
285 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
287 return 1
286 return 1
288 else:
287 else:
289 cont = False
288 cont = False
290
289
291 # commit if --no-commit is false
290 # commit if --no-commit is false
292 if not statedata.get(b'no_commit'):
291 if not statedata.get(b'no_commit'):
293 node = repo.commit(
292 node = repo.commit(
294 text=message, user=user, date=date, extra=extra, editor=editor
293 text=message, user=user, date=date, extra=extra, editor=editor
295 )
294 )
296 if node is None:
295 if node is None:
297 ui.warn(
296 ui.warn(
298 _(b'note: graft of %d:%s created no changes to commit\n')
297 _(b'note: graft of %d:%s created no changes to commit\n')
299 % (ctx.rev(), ctx)
298 % (ctx.rev(), ctx)
300 )
299 )
301 # checking that newnodes exist because old state files won't have it
300 # checking that newnodes exist because old state files won't have it
302 elif statedata.get(b'newnodes') is not None:
301 elif statedata.get(b'newnodes') is not None:
303 nn = statedata[b'newnodes']
302 nn = statedata[b'newnodes']
304 assert isinstance(nn, list) # list of bytes
303 assert isinstance(nn, list) # list of bytes
305 nn.append(node)
304 nn.append(node)
306
305
307 # remove state when we complete successfully
306 # remove state when we complete successfully
308 if not dry_run:
307 if not dry_run:
309 graftstate.delete()
308 graftstate.delete()
310
309
311 return 0
310 return 0
312
311
313
312
314 def _stopgraft(ui, repo, graftstate):
313 def _stopgraft(ui, repo, graftstate):
315 """stop the interrupted graft"""
314 """stop the interrupted graft"""
316 if not graftstate.exists():
315 if not graftstate.exists():
317 raise error.StateError(_(b"no interrupted graft found"))
316 raise error.StateError(_(b"no interrupted graft found"))
318 pctx = repo[b'.']
317 pctx = repo[b'.']
319 mergemod.clean_update(pctx)
318 mergemod.clean_update(pctx)
320 graftstate.delete()
319 graftstate.delete()
321 ui.status(_(b"stopped the interrupted graft\n"))
320 ui.status(_(b"stopped the interrupted graft\n"))
322 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
321 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
323 return 0
322 return 0
General Comments 0
You need to be logged in to leave comments. Login now