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