##// END OF EJS Templates
merge: with stable
Augie Fackler -
r49840:533820f5 merge default
parent child Browse files
Show More

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

@@ -1,315 +1,324 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
21 21 from mercurial.i18n import _
22 22
23 23 from mercurial import (
24 24 cmdutil,
25 25 commands,
26 26 context,
27 27 copies as copiesmod,
28 28 error,
29 29 obsutil,
30 30 pathutil,
31 31 pycompat,
32 32 registrar,
33 33 rewriteutil,
34 34 scmutil,
35 35 )
36 36
37 37 cmdtable = {}
38 38 command = registrar.command(cmdtable)
39 39
40 40 configtable = {}
41 41 configitem = registrar.configitem(configtable)
42 42
43 43 configitem(
44 44 b'experimental',
45 45 b'uncommitondirtywdir',
46 46 default=False,
47 47 )
48 48 configitem(
49 49 b'experimental',
50 50 b'uncommit.keep',
51 51 default=False,
52 52 )
53 53
54 54 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
55 55 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
56 56 # be specifying the version(s) of Mercurial they are tested with, or
57 57 # leave the attribute unspecified.
58 58 testedwith = b'ships-with-hg-core'
59 59
60 60
61 61 def _commitfiltered(
62 62 repo, ctx, match, keepcommit, message=None, user=None, date=None
63 63 ):
64 64 """Recommit ctx with changed files not in match. Return the new
65 65 node identifier, or None if nothing changed.
66 66 """
67 67 base = ctx.p1()
68 68 # ctx
69 69 initialfiles = set(ctx.files())
70 70 exclude = {f for f in initialfiles if match(f)}
71 71
72 72 # No files matched commit, so nothing excluded
73 73 if not exclude:
74 74 return None
75 75
76 76 # return the p1 so that we don't create an obsmarker later
77 77 if not keepcommit:
78 78 return ctx.p1().node()
79 79
80 80 files = initialfiles - exclude
81 81 # Filter copies
82 82 copied = copiesmod.pathcopies(base, ctx)
83 83 copied = {dst: src for dst, src in copied.items() if dst in files}
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(), repo.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 cmdutil.check_note_size(opts)
154 154 cmdutil.resolve_commit_options(ui, opts)
155 155 opts = pycompat.byteskwargs(opts)
156 156
157 157 with repo.wlock(), repo.lock():
158 158
159 159 st = repo.status()
160 160 m, a, r, d = st.modified, st.added, st.removed, st.deleted
161 161 isdirtypath = any(set(m + a + r + d) & set(pats))
162 162 allowdirtywcopy = opts[
163 163 b'allow_dirty_working_copy'
164 164 ] or repo.ui.configbool(b'experimental', b'uncommitondirtywdir')
165 165 if not allowdirtywcopy and (not pats or isdirtypath):
166 166 cmdutil.bailifchanged(
167 167 repo,
168 168 hint=_(b'requires --allow-dirty-working-copy to uncommit'),
169 169 )
170 170 old = repo[b'.']
171 171 rewriteutil.precheck(repo, [old.rev()], b'uncommit')
172 172 if len(old.parents()) > 1:
173 173 raise error.InputError(_(b"cannot uncommit merge changeset"))
174 174
175 175 match = scmutil.match(old, pats, opts)
176 176
177 177 # Check all explicitly given files; abort if there's a problem.
178 178 if match.files():
179 179 s = old.status(old.p1(), match, listclean=True)
180 180 eligible = set(s.added) | set(s.modified) | set(s.removed)
181 181
182 182 badfiles = set(match.files()) - eligible
183 183
184 184 # Naming a parent directory of an eligible file is OK, even
185 185 # if not everything tracked in that directory can be
186 186 # uncommitted.
187 187 if badfiles:
188 188 badfiles -= {f for f in pathutil.dirs(eligible)}
189 189
190 190 for f in sorted(badfiles):
191 191 if f in s.clean:
192 192 hint = _(
193 193 b"file was not changed in working directory parent"
194 194 )
195 195 elif repo.wvfs.exists(f):
196 196 hint = _(b"file was untracked in working directory parent")
197 197 else:
198 198 hint = _(b"file does not exist")
199 199
200 200 raise error.InputError(
201 201 _(b'cannot uncommit "%s"') % scmutil.getuipathfn(repo)(f),
202 202 hint=hint,
203 203 )
204 204
205 205 with repo.transaction(b'uncommit'):
206 206 if not (opts[b'message'] or opts[b'logfile']):
207 207 opts[b'message'] = old.description()
208 208 message = cmdutil.logmessage(ui, opts)
209 209
210 210 keepcommit = pats
211 211 if not keepcommit:
212 212 if opts.get(b'keep') is not None:
213 213 keepcommit = opts.get(b'keep')
214 214 else:
215 215 keepcommit = ui.configbool(
216 216 b'experimental', b'uncommit.keep'
217 217 )
218 218 newid = _commitfiltered(
219 219 repo,
220 220 old,
221 221 match,
222 222 keepcommit,
223 223 message=message,
224 224 user=opts.get(b'user'),
225 225 date=opts.get(b'date'),
226 226 )
227 227 if newid is None:
228 228 ui.status(_(b"nothing to uncommit\n"))
229 229 return 1
230 230
231 231 mapping = {}
232 232 if newid != old.p1().node():
233 233 # Move local changes on filtered changeset
234 234 mapping[old.node()] = (newid,)
235 235 else:
236 236 # Fully removed the old commit
237 237 mapping[old.node()] = ()
238 238
239 239 with repo.dirstate.parentchange():
240 240 scmutil.movedirstate(repo, repo[newid], match)
241 241
242 242 scmutil.cleanupnodes(repo, mapping, b'uncommit', fixphase=True)
243 243
244 244
245 245 def predecessormarkers(ctx):
246 246 """yields the obsolete markers marking the given changeset as a successor"""
247 247 for data in ctx.repo().obsstore.predecessors.get(ctx.node(), ()):
248 248 yield obsutil.marker(ctx.repo(), data)
249 249
250 250
251 251 @command(
252 252 b'unamend',
253 253 [],
254 254 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
255 255 helpbasic=True,
256 256 )
257 257 def unamend(ui, repo, **opts):
258 258 """undo the most recent amend operation on a current changeset
259 259
260 260 This command will roll back to the previous version of a changeset,
261 261 leaving working directory in state in which it was before running
262 262 `hg amend` (e.g. files modified as part of an amend will be
263 263 marked as modified `hg status`)
264 264 """
265 265
266 266 unfi = repo.unfiltered()
267 267 with repo.wlock(), repo.lock(), repo.transaction(b'unamend'):
268 268
269 269 # identify the commit from which to unamend
270 270 curctx = repo[b'.']
271 271
272 272 rewriteutil.precheck(repo, [curctx.rev()], b'unamend')
273 273 if len(curctx.parents()) > 1:
274 274 raise error.InputError(_(b"cannot unamend merge changeset"))
275 275
276 expected_keys = (b'amend_source', b'unamend_source')
277 if not any(key in curctx.extra() for key in expected_keys):
278 raise error.InputError(
279 _(
280 b"working copy parent was not created by 'hg amend' or "
281 b"'hg unamend'"
282 )
283 )
284
276 285 # identify the commit to which to unamend
277 286 markers = list(predecessormarkers(curctx))
278 287 if len(markers) != 1:
279 288 e = _(b"changeset must have one predecessor, found %i predecessors")
280 289 raise error.InputError(e % len(markers))
281 290
282 291 prednode = markers[0].prednode()
283 292 predctx = unfi[prednode]
284 293
285 294 # add an extra so that we get a new hash
286 295 # note: allowing unamend to undo an unamend is an intentional feature
287 296 extras = predctx.extra()
288 297 extras[b'unamend_source'] = curctx.hex()
289 298
290 299 def filectxfn(repo, ctx_, path):
291 300 try:
292 301 return predctx.filectx(path)
293 302 except KeyError:
294 303 return None
295 304
296 305 # Make a new commit same as predctx
297 306 newctx = context.memctx(
298 307 repo,
299 308 parents=(predctx.p1(), predctx.p2()),
300 309 text=predctx.description(),
301 310 files=predctx.files(),
302 311 filectxfn=filectxfn,
303 312 user=predctx.user(),
304 313 date=predctx.date(),
305 314 extra=extras,
306 315 )
307 316 newprednode = repo.commitctx(newctx)
308 317 newpredctx = repo[newprednode]
309 318 dirstate = repo.dirstate
310 319
311 320 with dirstate.parentchange():
312 321 scmutil.movedirstate(repo, newpredctx)
313 322
314 323 mapping = {curctx.node(): (newprednode,)}
315 324 scmutil.cleanupnodes(repo, mapping, b'unamend', fixphase=True)
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 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