##// END OF EJS Templates
py3: don't double-convert "opts" to bytes...
Martin von Zweigbergk -
r43222:44be33cf default
parent child Browse files
Show More
@@ -1,269 +1,269 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 pycompat,
33 33 registrar,
34 34 rewriteutil,
35 35 scmutil,
36 36 util,
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('experimental', 'uncommitondirtywdir',
46 46 default=False,
47 47 )
48 48 configitem('experimental', 'uncommit.keep',
49 49 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 = 'ships-with-hg-core'
57 57
58 58 def _commitfiltered(repo, ctx, match, keepcommit, message=None, user=None,
59 59 date=None):
60 60 """Recommit ctx with changed files not in match. Return the new
61 61 node identifier, or None if nothing changed.
62 62 """
63 63 base = ctx.p1()
64 64 # ctx
65 65 initialfiles = set(ctx.files())
66 66 exclude = set(f for f in initialfiles if match(f))
67 67
68 68 # No files matched commit, so nothing excluded
69 69 if not exclude:
70 70 return None
71 71
72 72 # return the p1 so that we don't create an obsmarker later
73 73 if not keepcommit:
74 74 return ctx.p1().node()
75 75
76 76 files = (initialfiles - exclude)
77 77 # Filter copies
78 78 copied = copiesmod.pathcopies(base, ctx)
79 79 copied = dict((dst, src) for dst, src in copied.iteritems()
80 80 if dst in files)
81 81 def filectxfn(repo, memctx, path, contentctx=ctx, redirect=()):
82 82 if path not in contentctx:
83 83 return None
84 84 fctx = contentctx[path]
85 85 mctx = context.memfilectx(repo, memctx, fctx.path(), fctx.data(),
86 86 fctx.islink(),
87 87 fctx.isexec(),
88 88 copysource=copied.get(path))
89 89 return mctx
90 90
91 91 if not files:
92 92 repo.ui.status(_("note: keeping empty commit\n"))
93 93
94 94 if message is None:
95 95 message = ctx.description()
96 96 if not user:
97 97 user = ctx.user()
98 98 if not date:
99 99 date = ctx.date()
100 100
101 101 new = context.memctx(repo,
102 102 parents=[base.node(), node.nullid],
103 103 text=message,
104 104 files=files,
105 105 filectxfn=filectxfn,
106 106 user=user,
107 107 date=date,
108 108 extra=ctx.extra())
109 109 return repo.commitctx(new)
110 110
111 111 @command('uncommit',
112 112 [('', 'keep', None, _('allow an empty commit after uncommiting')),
113 113 ('', 'allow-dirty-working-copy', False,
114 114 _('allow uncommit with outstanding changes')),
115 115 (b'n', b'note', b'', _(b'store a note on uncommit'), _(b'TEXT'))
116 116 ] + commands.walkopts + commands.commitopts + commands.commitopts2
117 117 + commands.commitopts3,
118 118 _('[OPTION]... [FILE]...'),
119 119 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
120 120 def uncommit(ui, repo, *pats, **opts):
121 121 """uncommit part or all of a local changeset
122 122
123 123 This command undoes the effect of a local commit, returning the affected
124 124 files to their uncommitted state. This means that files modified or
125 125 deleted in the changeset will be left unchanged, and so will remain
126 126 modified in the working directory.
127 127
128 128 If no files are specified, the commit will be pruned, unless --keep is
129 129 given.
130 130 """
131 131 opts = pycompat.byteskwargs(opts)
132 132
133 133 cmdutil.checknotesize(ui, opts)
134 134 cmdutil.resolvecommitoptions(ui, opts)
135 135
136 136 with repo.wlock(), repo.lock():
137 137
138 138 m, a, r, d = repo.status()[:4]
139 139 isdirtypath = any(set(m + a + r + d) & set(pats))
140 140 allowdirtywcopy = (opts['allow_dirty_working_copy'] or
141 141 repo.ui.configbool('experimental', 'uncommitondirtywdir'))
142 142 if not allowdirtywcopy and (not pats or isdirtypath):
143 143 cmdutil.bailifchanged(repo, hint=_('requires '
144 144 '--allow-dirty-working-copy to uncommit'))
145 145 old = repo['.']
146 146 rewriteutil.precheck(repo, [old.rev()], 'uncommit')
147 147 if len(old.parents()) > 1:
148 148 raise error.Abort(_("cannot uncommit merge changeset"))
149 149
150 150 match = scmutil.match(old, pats, opts)
151 151
152 152 # Check all explicitly given files; abort if there's a problem.
153 153 if match.files():
154 154 s = old.status(old.p1(), match, listclean=True)
155 155 eligible = set(s.added) | set(s.modified) | set(s.removed)
156 156
157 157 badfiles = set(match.files()) - eligible
158 158
159 159 # Naming a parent directory of an eligible file is OK, even
160 160 # if not everything tracked in that directory can be
161 161 # uncommitted.
162 162 if badfiles:
163 163 badfiles -= {f for f in util.dirs(eligible)}
164 164
165 165 for f in sorted(badfiles):
166 166 if f in s.clean:
167 167 hint = _(b"file was not changed in working directory "
168 168 b"parent")
169 169 elif repo.wvfs.exists(f):
170 170 hint = _(b"file was untracked in working directory parent")
171 171 else:
172 172 hint = _(b"file does not exist")
173 173
174 174 raise error.Abort(_(b'cannot uncommit "%s"')
175 175 % scmutil.getuipathfn(repo)(f), hint=hint)
176 176
177 177 with repo.transaction('uncommit'):
178 178 if not (opts[b'message'] or opts[b'logfile']):
179 179 opts[b'message'] = old.description()
180 message = cmdutil.logmessage(ui, pycompat.byteskwargs(opts))
180 message = cmdutil.logmessage(ui, opts)
181 181
182 182 keepcommit = pats
183 183 if not keepcommit:
184 184 if opts.get('keep') is not None:
185 185 keepcommit = opts.get('keep')
186 186 else:
187 187 keepcommit = ui.configbool('experimental', 'uncommit.keep')
188 188 newid = _commitfiltered(repo, old, match, keepcommit,
189 189 message=message, user=opts.get(b'user'),
190 190 date=opts.get(b'date'))
191 191 if newid is None:
192 192 ui.status(_("nothing to uncommit\n"))
193 193 return 1
194 194
195 195 mapping = {}
196 196 if newid != old.p1().node():
197 197 # Move local changes on filtered changeset
198 198 mapping[old.node()] = (newid,)
199 199 else:
200 200 # Fully removed the old commit
201 201 mapping[old.node()] = ()
202 202
203 203 with repo.dirstate.parentchange():
204 204 scmutil.movedirstate(repo, repo[newid], match)
205 205
206 206 scmutil.cleanupnodes(repo, mapping, 'uncommit', fixphase=True)
207 207
208 208 def predecessormarkers(ctx):
209 209 """yields the obsolete markers marking the given changeset as a successor"""
210 210 for data in ctx.repo().obsstore.predecessors.get(ctx.node(), ()):
211 211 yield obsutil.marker(ctx.repo(), data)
212 212
213 213 @command('unamend', [], helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
214 214 helpbasic=True)
215 215 def unamend(ui, repo, **opts):
216 216 """undo the most recent amend operation on a current changeset
217 217
218 218 This command will roll back to the previous version of a changeset,
219 219 leaving working directory in state in which it was before running
220 220 `hg amend` (e.g. files modified as part of an amend will be
221 221 marked as modified `hg status`)
222 222 """
223 223
224 224 unfi = repo.unfiltered()
225 225 with repo.wlock(), repo.lock(), repo.transaction('unamend'):
226 226
227 227 # identify the commit from which to unamend
228 228 curctx = repo['.']
229 229
230 230 rewriteutil.precheck(repo, [curctx.rev()], 'unamend')
231 231
232 232 # identify the commit to which to unamend
233 233 markers = list(predecessormarkers(curctx))
234 234 if len(markers) != 1:
235 235 e = _("changeset must have one predecessor, found %i predecessors")
236 236 raise error.Abort(e % len(markers))
237 237
238 238 prednode = markers[0].prednode()
239 239 predctx = unfi[prednode]
240 240
241 241 # add an extra so that we get a new hash
242 242 # note: allowing unamend to undo an unamend is an intentional feature
243 243 extras = predctx.extra()
244 244 extras['unamend_source'] = curctx.hex()
245 245
246 246 def filectxfn(repo, ctx_, path):
247 247 try:
248 248 return predctx.filectx(path)
249 249 except KeyError:
250 250 return None
251 251
252 252 # Make a new commit same as predctx
253 253 newctx = context.memctx(repo,
254 254 parents=(predctx.p1(), predctx.p2()),
255 255 text=predctx.description(),
256 256 files=predctx.files(),
257 257 filectxfn=filectxfn,
258 258 user=predctx.user(),
259 259 date=predctx.date(),
260 260 extra=extras)
261 261 newprednode = repo.commitctx(newctx)
262 262 newpredctx = repo[newprednode]
263 263 dirstate = repo.dirstate
264 264
265 265 with dirstate.parentchange():
266 266 scmutil.movedirstate(repo, newpredctx)
267 267
268 268 mapping = {curctx.node(): (newprednode,)}
269 269 scmutil.cleanupnodes(repo, mapping, 'unamend', fixphase=True)
General Comments 0
You need to be logged in to leave comments. Login now