##// END OF EJS Templates
uncommit: use field names instead of field numbers on scmutil.status...
Augie Fackler -
r44042:d0310f21 default
parent child Browse files
Show More
@@ -1,313 +1,314 b''
1 1 # uncommit - undo the actions of a commit
2 2 #
3 3 # Copyright 2011 Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
4 4 # Logilab SA <contact@logilab.fr>
5 5 # Pierre-Yves David <pierre-yves.david@ens-lyon.org>
6 6 # Patrick Mezard <patrick@mezard.eu>
7 7 # Copyright 2016 Facebook, Inc.
8 8 #
9 9 # This software may be used and distributed according to the terms of the
10 10 # GNU General Public License version 2 or any later version.
11 11
12 12 """uncommit part or all of a local changeset (EXPERIMENTAL)
13 13
14 14 This command undoes the effect of a local commit, returning the affected
15 15 files to their uncommitted state. This means that files modified, added or
16 16 removed in the changeset will be left unchanged, and so will remain modified,
17 17 added and removed in the working directory.
18 18 """
19 19
20 20 from __future__ import absolute_import
21 21
22 22 from mercurial.i18n import _
23 23
24 24 from mercurial import (
25 25 cmdutil,
26 26 commands,
27 27 context,
28 28 copies as copiesmod,
29 29 error,
30 30 node,
31 31 obsutil,
32 32 pathutil,
33 33 pycompat,
34 34 registrar,
35 35 rewriteutil,
36 36 scmutil,
37 37 )
38 38
39 39 cmdtable = {}
40 40 command = registrar.command(cmdtable)
41 41
42 42 configtable = {}
43 43 configitem = registrar.configitem(configtable)
44 44
45 45 configitem(
46 46 b'experimental', b'uncommitondirtywdir', default=False,
47 47 )
48 48 configitem(
49 49 b'experimental', b'uncommit.keep', default=False,
50 50 )
51 51
52 52 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
53 53 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
54 54 # be specifying the version(s) of Mercurial they are tested with, or
55 55 # leave the attribute unspecified.
56 56 testedwith = b'ships-with-hg-core'
57 57
58 58
59 59 def _commitfiltered(
60 60 repo, ctx, match, keepcommit, message=None, user=None, date=None
61 61 ):
62 62 """Recommit ctx with changed files not in match. Return the new
63 63 node identifier, or None if nothing changed.
64 64 """
65 65 base = ctx.p1()
66 66 # ctx
67 67 initialfiles = set(ctx.files())
68 68 exclude = set(f for f in initialfiles if match(f))
69 69
70 70 # No files matched commit, so nothing excluded
71 71 if not exclude:
72 72 return None
73 73
74 74 # return the p1 so that we don't create an obsmarker later
75 75 if not keepcommit:
76 76 return ctx.p1().node()
77 77
78 78 files = initialfiles - exclude
79 79 # Filter copies
80 80 copied = copiesmod.pathcopies(base, ctx)
81 81 copied = dict(
82 82 (dst, src) for dst, src in pycompat.iteritems(copied) if dst in files
83 83 )
84 84
85 85 def filectxfn(repo, memctx, path, contentctx=ctx, redirect=()):
86 86 if path not in contentctx:
87 87 return None
88 88 fctx = contentctx[path]
89 89 mctx = context.memfilectx(
90 90 repo,
91 91 memctx,
92 92 fctx.path(),
93 93 fctx.data(),
94 94 fctx.islink(),
95 95 fctx.isexec(),
96 96 copysource=copied.get(path),
97 97 )
98 98 return mctx
99 99
100 100 if not files:
101 101 repo.ui.status(_(b"note: keeping empty commit\n"))
102 102
103 103 if message is None:
104 104 message = ctx.description()
105 105 if not user:
106 106 user = ctx.user()
107 107 if not date:
108 108 date = ctx.date()
109 109
110 110 new = context.memctx(
111 111 repo,
112 112 parents=[base.node(), node.nullid],
113 113 text=message,
114 114 files=files,
115 115 filectxfn=filectxfn,
116 116 user=user,
117 117 date=date,
118 118 extra=ctx.extra(),
119 119 )
120 120 return repo.commitctx(new)
121 121
122 122
123 123 @command(
124 124 b'uncommit',
125 125 [
126 126 (b'', b'keep', None, _(b'allow an empty commit after uncommitting')),
127 127 (
128 128 b'',
129 129 b'allow-dirty-working-copy',
130 130 False,
131 131 _(b'allow uncommit with outstanding changes'),
132 132 ),
133 133 (b'n', b'note', b'', _(b'store a note on uncommit'), _(b'TEXT')),
134 134 ]
135 135 + commands.walkopts
136 136 + commands.commitopts
137 137 + commands.commitopts2
138 138 + commands.commitopts3,
139 139 _(b'[OPTION]... [FILE]...'),
140 140 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
141 141 )
142 142 def uncommit(ui, repo, *pats, **opts):
143 143 """uncommit part or all of a local changeset
144 144
145 145 This command undoes the effect of a local commit, returning the affected
146 146 files to their uncommitted state. This means that files modified or
147 147 deleted in the changeset will be left unchanged, and so will remain
148 148 modified in the working directory.
149 149
150 150 If no files are specified, the commit will be pruned, unless --keep is
151 151 given.
152 152 """
153 153 opts = pycompat.byteskwargs(opts)
154 154
155 155 cmdutil.checknotesize(ui, opts)
156 156 cmdutil.resolvecommitoptions(ui, opts)
157 157
158 158 with repo.wlock(), repo.lock():
159 159
160 m, a, r, d = repo.status()[:4]
160 st = repo.status()
161 m, a, r, d = st.modified, st.added, st.removed, st.deleted
161 162 isdirtypath = any(set(m + a + r + d) & set(pats))
162 163 allowdirtywcopy = opts[
163 164 b'allow_dirty_working_copy'
164 165 ] or repo.ui.configbool(b'experimental', b'uncommitondirtywdir')
165 166 if not allowdirtywcopy and (not pats or isdirtypath):
166 167 cmdutil.bailifchanged(
167 168 repo,
168 169 hint=_(b'requires --allow-dirty-working-copy to uncommit'),
169 170 )
170 171 old = repo[b'.']
171 172 rewriteutil.precheck(repo, [old.rev()], b'uncommit')
172 173 if len(old.parents()) > 1:
173 174 raise error.Abort(_(b"cannot uncommit merge changeset"))
174 175
175 176 match = scmutil.match(old, pats, opts)
176 177
177 178 # Check all explicitly given files; abort if there's a problem.
178 179 if match.files():
179 180 s = old.status(old.p1(), match, listclean=True)
180 181 eligible = set(s.added) | set(s.modified) | set(s.removed)
181 182
182 183 badfiles = set(match.files()) - eligible
183 184
184 185 # Naming a parent directory of an eligible file is OK, even
185 186 # if not everything tracked in that directory can be
186 187 # uncommitted.
187 188 if badfiles:
188 189 badfiles -= {f for f in pathutil.dirs(eligible)}
189 190
190 191 for f in sorted(badfiles):
191 192 if f in s.clean:
192 193 hint = _(
193 194 b"file was not changed in working directory parent"
194 195 )
195 196 elif repo.wvfs.exists(f):
196 197 hint = _(b"file was untracked in working directory parent")
197 198 else:
198 199 hint = _(b"file does not exist")
199 200
200 201 raise error.Abort(
201 202 _(b'cannot uncommit "%s"') % scmutil.getuipathfn(repo)(f),
202 203 hint=hint,
203 204 )
204 205
205 206 with repo.transaction(b'uncommit'):
206 207 if not (opts[b'message'] or opts[b'logfile']):
207 208 opts[b'message'] = old.description()
208 209 message = cmdutil.logmessage(ui, opts)
209 210
210 211 keepcommit = pats
211 212 if not keepcommit:
212 213 if opts.get(b'keep') is not None:
213 214 keepcommit = opts.get(b'keep')
214 215 else:
215 216 keepcommit = ui.configbool(
216 217 b'experimental', b'uncommit.keep'
217 218 )
218 219 newid = _commitfiltered(
219 220 repo,
220 221 old,
221 222 match,
222 223 keepcommit,
223 224 message=message,
224 225 user=opts.get(b'user'),
225 226 date=opts.get(b'date'),
226 227 )
227 228 if newid is None:
228 229 ui.status(_(b"nothing to uncommit\n"))
229 230 return 1
230 231
231 232 mapping = {}
232 233 if newid != old.p1().node():
233 234 # Move local changes on filtered changeset
234 235 mapping[old.node()] = (newid,)
235 236 else:
236 237 # Fully removed the old commit
237 238 mapping[old.node()] = ()
238 239
239 240 with repo.dirstate.parentchange():
240 241 scmutil.movedirstate(repo, repo[newid], match)
241 242
242 243 scmutil.cleanupnodes(repo, mapping, b'uncommit', fixphase=True)
243 244
244 245
245 246 def predecessormarkers(ctx):
246 247 """yields the obsolete markers marking the given changeset as a successor"""
247 248 for data in ctx.repo().obsstore.predecessors.get(ctx.node(), ()):
248 249 yield obsutil.marker(ctx.repo(), data)
249 250
250 251
251 252 @command(
252 253 b'unamend',
253 254 [],
254 255 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
255 256 helpbasic=True,
256 257 )
257 258 def unamend(ui, repo, **opts):
258 259 """undo the most recent amend operation on a current changeset
259 260
260 261 This command will roll back to the previous version of a changeset,
261 262 leaving working directory in state in which it was before running
262 263 `hg amend` (e.g. files modified as part of an amend will be
263 264 marked as modified `hg status`)
264 265 """
265 266
266 267 unfi = repo.unfiltered()
267 268 with repo.wlock(), repo.lock(), repo.transaction(b'unamend'):
268 269
269 270 # identify the commit from which to unamend
270 271 curctx = repo[b'.']
271 272
272 273 rewriteutil.precheck(repo, [curctx.rev()], b'unamend')
273 274
274 275 # identify the commit to which to unamend
275 276 markers = list(predecessormarkers(curctx))
276 277 if len(markers) != 1:
277 278 e = _(b"changeset must have one predecessor, found %i predecessors")
278 279 raise error.Abort(e % len(markers))
279 280
280 281 prednode = markers[0].prednode()
281 282 predctx = unfi[prednode]
282 283
283 284 # add an extra so that we get a new hash
284 285 # note: allowing unamend to undo an unamend is an intentional feature
285 286 extras = predctx.extra()
286 287 extras[b'unamend_source'] = curctx.hex()
287 288
288 289 def filectxfn(repo, ctx_, path):
289 290 try:
290 291 return predctx.filectx(path)
291 292 except KeyError:
292 293 return None
293 294
294 295 # Make a new commit same as predctx
295 296 newctx = context.memctx(
296 297 repo,
297 298 parents=(predctx.p1(), predctx.p2()),
298 299 text=predctx.description(),
299 300 files=predctx.files(),
300 301 filectxfn=filectxfn,
301 302 user=predctx.user(),
302 303 date=predctx.date(),
303 304 extra=extras,
304 305 )
305 306 newprednode = repo.commitctx(newctx)
306 307 newpredctx = repo[newprednode]
307 308 dirstate = repo.dirstate
308 309
309 310 with dirstate.parentchange():
310 311 scmutil.movedirstate(repo, newpredctx)
311 312
312 313 mapping = {curctx.node(): (newprednode,)}
313 314 scmutil.cleanupnodes(repo, mapping, b'unamend', fixphase=True)
General Comments 0
You need to be logged in to leave comments. Login now