##// END OF EJS Templates
uncommit: inform user if the commit is empty after uncommit...
Martin von Zweigbergk -
r41895:c10652d1 default draft
parent child Browse files
Show More
@@ -1,450 +1,453 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 obsolete,
32 32 obsutil,
33 33 patch,
34 34 pycompat,
35 35 registrar,
36 36 rewriteutil,
37 37 scmutil,
38 38 util,
39 39 )
40 40
41 41 cmdtable = {}
42 42 command = registrar.command(cmdtable)
43 43
44 44 configtable = {}
45 45 configitem = registrar.configitem(configtable)
46 46
47 47 configitem('experimental', 'uncommitondirtywdir',
48 48 default=False,
49 49 )
50 50
51 51 stringio = util.stringio
52 52
53 53 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
54 54 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
55 55 # be specifying the version(s) of Mercurial they are tested with, or
56 56 # leave the attribute unspecified.
57 57 testedwith = 'ships-with-hg-core'
58 58
59 59 def _commitfiltered(repo, ctx, match, keepcommit):
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 files = (initialfiles - exclude)
73 72 # return the p1 so that we don't create an obsmarker later
74 73 if not keepcommit:
75 74 return ctx.p1().node()
76 75
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 copied=copied.get(path))
89 89 return mctx
90 90
91 if not files:
92 repo.ui.status(_("note: keeping empty commit\n"))
93
91 94 new = context.memctx(repo,
92 95 parents=[base.node(), node.nullid],
93 96 text=ctx.description(),
94 97 files=files,
95 98 filectxfn=filectxfn,
96 99 user=ctx.user(),
97 100 date=ctx.date(),
98 101 extra=ctx.extra())
99 102 return repo.commitctx(new)
100 103
101 104 def _fixdirstate(repo, oldctx, newctx, match=None):
102 105 """ fix the dirstate after switching the working directory from oldctx to
103 106 newctx which can be result of either unamend or uncommit.
104 107 """
105 108 ds = repo.dirstate
106 109 ds.setparents(newctx.node(), node.nullid)
107 110 copies = dict(ds.copies())
108 111 s = newctx.status(oldctx, match=match)
109 112 for f in s.modified:
110 113 if ds[f] == 'r':
111 114 # modified + removed -> removed
112 115 continue
113 116 ds.normallookup(f)
114 117
115 118 for f in s.added:
116 119 if ds[f] == 'r':
117 120 # added + removed -> unknown
118 121 ds.drop(f)
119 122 elif ds[f] != 'a':
120 123 ds.add(f)
121 124
122 125 for f in s.removed:
123 126 if ds[f] == 'a':
124 127 # removed + added -> normal
125 128 ds.normallookup(f)
126 129 elif ds[f] != 'r':
127 130 ds.remove(f)
128 131
129 132 # Merge old parent and old working dir copies
130 133 oldcopies = copiesmod.pathcopies(newctx, oldctx, match)
131 134 oldcopies.update(copies)
132 135 copies = dict((dst, oldcopies.get(src, src))
133 136 for dst, src in oldcopies.iteritems())
134 137 # Adjust the dirstate copies
135 138 for dst, src in copies.iteritems():
136 139 if (src not in newctx or dst in newctx or ds[dst] != 'a'):
137 140 src = None
138 141 ds.copy(src, dst)
139 142
140 143
141 144 def _uncommitdirstate(repo, oldctx, match, interactive):
142 145 """Fix the dirstate after switching the working directory from
143 146 oldctx to a copy of oldctx not containing changed files matched by
144 147 match.
145 148 """
146 149 ctx = repo['.']
147 150 ds = repo.dirstate
148 151 copies = dict(ds.copies())
149 152 if interactive:
150 153 # In interactive cases, we will find the status between oldctx and ctx
151 154 # and considering only the files which are changed between oldctx and
152 155 # ctx, and the status of what changed between oldctx and ctx will help
153 156 # us in defining the exact behavior
154 157 m, a, r = repo.status(oldctx, ctx, match=match)[:3]
155 158 for f in m:
156 159 # These are files which are modified between oldctx and ctx which
157 160 # contains two cases: 1) Were modified in oldctx and some
158 161 # modifications are uncommitted
159 162 # 2) Were added in oldctx but some part is uncommitted (this cannot
160 163 # contain the case when added files are uncommitted completely as
161 164 # that will result in status as removed not modified.)
162 165 # Also any modifications to a removed file will result the status as
163 166 # added, so we have only two cases. So in either of the cases, the
164 167 # resulting status can be modified or clean.
165 168 if ds[f] == 'r':
166 169 # But the file is removed in the working directory, leaving that
167 170 # as removed
168 171 continue
169 172 ds.normallookup(f)
170 173
171 174 for f in a:
172 175 # These are the files which are added between oldctx and ctx(new
173 176 # one), which means the files which were removed in oldctx
174 177 # but uncommitted completely while making the ctx
175 178 # This file should be marked as removed if the working directory
176 179 # does not adds it back. If it's adds it back, we do a normallookup.
177 180 # The file can't be removed in working directory, because it was
178 181 # removed in oldctx
179 182 if ds[f] == 'a':
180 183 ds.normallookup(f)
181 184 continue
182 185 ds.remove(f)
183 186
184 187 for f in r:
185 188 # These are files which are removed between oldctx and ctx, which
186 189 # means the files which were added in oldctx and were completely
187 190 # uncommitted in ctx. If a added file is partially uncommitted, that
188 191 # would have resulted in modified status, not removed.
189 192 # So a file added in a commit, and uncommitting that addition must
190 193 # result in file being stated as unknown.
191 194 if ds[f] == 'r':
192 195 # The working directory say it's removed, so lets make the file
193 196 # unknown
194 197 ds.drop(f)
195 198 continue
196 199 ds.add(f)
197 200 else:
198 201 m, a, r = repo.status(oldctx.p1(), oldctx, match=match)[:3]
199 202 for f in m:
200 203 if ds[f] == 'r':
201 204 # modified + removed -> removed
202 205 continue
203 206 ds.normallookup(f)
204 207
205 208 for f in a:
206 209 if ds[f] == 'r':
207 210 # added + removed -> unknown
208 211 ds.drop(f)
209 212 elif ds[f] != 'a':
210 213 ds.add(f)
211 214
212 215 for f in r:
213 216 if ds[f] == 'a':
214 217 # removed + added -> normal
215 218 ds.normallookup(f)
216 219 elif ds[f] != 'r':
217 220 ds.remove(f)
218 221
219 222 # Merge old parent and old working dir copies
220 223 oldcopies = {}
221 224 if interactive:
222 225 # Interactive had different meaning of the variables so restoring the
223 226 # original meaning to use them
224 227 m, a, r = repo.status(oldctx.p1(), oldctx, match=match)[:3]
225 228 for f in (m + a):
226 229 src = oldctx[f].renamed()
227 230 if src:
228 231 oldcopies[f] = src[0]
229 232 oldcopies.update(copies)
230 233 copies = dict((dst, oldcopies.get(src, src))
231 234 for dst, src in oldcopies.iteritems())
232 235 # Adjust the dirstate copies
233 236 for dst, src in copies.iteritems():
234 237 if (src not in ctx or dst in ctx or ds[dst] != 'a'):
235 238 src = None
236 239 ds.copy(src, dst)
237 240
238 241 @command('uncommit',
239 242 [('i', 'interactive', False, _('interactive mode to uncommit')),
240 243 ('', 'keep', False, _('allow an empty commit after uncommiting')),
241 244 ] + commands.walkopts,
242 245 _('[OPTION]... [FILE]...'),
243 246 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
244 247 def uncommit(ui, repo, *pats, **opts):
245 248 """uncommit part or all of a local changeset
246 249
247 250 This command undoes the effect of a local commit, returning the affected
248 251 files to their uncommitted state. This means that files modified or
249 252 deleted in the changeset will be left unchanged, and so will remain
250 253 modified in the working directory.
251 254
252 255 If no files are specified, the commit will be pruned, unless --keep is
253 256 given.
254 257 """
255 258 opts = pycompat.byteskwargs(opts)
256 259 interactive = opts.get('interactive')
257 260
258 261 with repo.wlock(), repo.lock():
259 262
260 263 if not pats and not repo.ui.configbool('experimental',
261 264 'uncommitondirtywdir'):
262 265 cmdutil.bailifchanged(repo)
263 266 old = repo['.']
264 267 rewriteutil.precheck(repo, [old.rev()], 'uncommit')
265 268 if len(old.parents()) > 1:
266 269 raise error.Abort(_("cannot uncommit merge changeset"))
267 270
268 271 with repo.transaction('uncommit'):
269 272 match = scmutil.match(old, pats, opts)
270 273 keepcommit = opts.get('keep') or pats
271 274 newid = _commitfiltered(repo, old, match, keepcommit)
272 275 if interactive:
273 276 match = scmutil.match(old, pats, opts)
274 277 newid = _interactiveuncommit(ui, repo, old, match)
275 278
276 279 if newid is None:
277 280 ui.status(_("nothing to uncommit\n"))
278 281 return 1
279 282
280 283 mapping = {}
281 284 if newid != old.p1().node():
282 285 # Move local changes on filtered changeset
283 286 mapping[old.node()] = (newid,)
284 287 else:
285 288 # Fully removed the old commit
286 289 mapping[old.node()] = ()
287 290
288 291 scmutil.cleanupnodes(repo, mapping, 'uncommit', fixphase=True)
289 292
290 293 with repo.dirstate.parentchange():
291 294 repo.dirstate.setparents(newid, node.nullid)
292 295 _uncommitdirstate(repo, old, match, interactive)
293 296
294 297 def _interactiveuncommit(ui, repo, old, match):
295 298 """ The function which contains all the logic for interactively uncommiting
296 299 a commit. This function makes a temporary commit with the chunks which user
297 300 selected to uncommit. After that the diff of the parent and that commit is
298 301 applied to the working directory and committed again which results in the
299 302 new commit which should be one after uncommitted.
300 303 """
301 304
302 305 # create a temporary commit with hunks user selected
303 306 tempnode = _createtempcommit(ui, repo, old, match)
304 307
305 308 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
306 309 diffopts.nodates = True
307 310 diffopts.git = True
308 311 fp = stringio()
309 312 for chunk, label in patch.diffui(repo, tempnode, old.node(), None,
310 313 opts=diffopts):
311 314 fp.write(chunk)
312 315
313 316 fp.seek(0)
314 317 newnode = _patchtocommit(ui, repo, old, fp)
315 318 # creating obs marker temp -> ()
316 319 obsolete.createmarkers(repo, [(repo[tempnode], ())], operation="uncommit")
317 320 return newnode
318 321
319 322 def _createtempcommit(ui, repo, old, match):
320 323 """ Creates a temporary commit for `uncommit --interative` which contains
321 324 the hunks which were selected by the user to uncommit.
322 325 """
323 326
324 327 pold = old.p1()
325 328 # The logic to interactively selecting something copied from
326 329 # cmdutil.revert()
327 330 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
328 331 diffopts.nodates = True
329 332 diffopts.git = True
330 333 diff = patch.diff(repo, pold.node(), old.node(), match, opts=diffopts)
331 334 originalchunks = patch.parsepatch(diff)
332 335 # XXX: The interactive selection is buggy and does not let you
333 336 # uncommit a removed file partially.
334 337 # TODO: wrap the operations in mercurial/patch.py and mercurial/crecord.py
335 338 # to add uncommit as an operation taking care of BC.
336 339 chunks, opts = cmdutil.recordfilter(repo.ui, originalchunks,
337 340 operation='discard')
338 341 if not chunks:
339 342 raise error.Abort(_("nothing selected to uncommit"))
340 343 fp = stringio()
341 344 for c in chunks:
342 345 c.write(fp)
343 346
344 347 fp.seek(0)
345 348 oldnode = node.hex(old.node())[:12]
346 349 message = 'temporary commit for uncommiting %s' % oldnode
347 350 tempnode = _patchtocommit(ui, repo, old, fp, message, oldnode)
348 351 return tempnode
349 352
350 353 def _patchtocommit(ui, repo, old, fp, message=None, extras=None):
351 354 """ A function which will apply the patch to the working directory and
352 355 make a commit whose parents are same as that of old argument. The message
353 356 argument tells us whether to use the message of the old commit or a
354 357 different message which is passed. Returns the node of new commit made.
355 358 """
356 359 pold = old.p1()
357 360 parents = (old.p1().node(), old.p2().node())
358 361 date = old.date()
359 362 branch = old.branch()
360 363 user = old.user()
361 364 extra = old.extra()
362 365 if extras:
363 366 extra['uncommit_source'] = extras
364 367 if not message:
365 368 message = old.description()
366 369 store = patch.filestore()
367 370 try:
368 371 files = set()
369 372 try:
370 373 patch.patchrepo(ui, repo, pold, store, fp, 1, '',
371 374 files=files, eolmode=None)
372 375 except patch.PatchError as err:
373 376 raise error.Abort(str(err))
374 377
375 378 finally:
376 379 del fp
377 380
378 381 memctx = context.memctx(repo, parents, message, files=files,
379 382 filectxfn=store,
380 383 user=user,
381 384 date=date,
382 385 branch=branch,
383 386 extra=extra)
384 387 newcm = memctx.commit()
385 388 finally:
386 389 store.close()
387 390 return newcm
388 391
389 392 def predecessormarkers(ctx):
390 393 """yields the obsolete markers marking the given changeset as a successor"""
391 394 for data in ctx.repo().obsstore.predecessors.get(ctx.node(), ()):
392 395 yield obsutil.marker(ctx.repo(), data)
393 396
394 397 @command('unamend', [], helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
395 398 helpbasic=True)
396 399 def unamend(ui, repo, **opts):
397 400 """undo the most recent amend operation on a current changeset
398 401
399 402 This command will roll back to the previous version of a changeset,
400 403 leaving working directory in state in which it was before running
401 404 `hg amend` (e.g. files modified as part of an amend will be
402 405 marked as modified `hg status`)
403 406 """
404 407
405 408 unfi = repo.unfiltered()
406 409 with repo.wlock(), repo.lock(), repo.transaction('unamend'):
407 410
408 411 # identify the commit from which to unamend
409 412 curctx = repo['.']
410 413
411 414 rewriteutil.precheck(repo, [curctx.rev()], 'unamend')
412 415
413 416 # identify the commit to which to unamend
414 417 markers = list(predecessormarkers(curctx))
415 418 if len(markers) != 1:
416 419 e = _("changeset must have one predecessor, found %i predecessors")
417 420 raise error.Abort(e % len(markers))
418 421
419 422 prednode = markers[0].prednode()
420 423 predctx = unfi[prednode]
421 424
422 425 # add an extra so that we get a new hash
423 426 # note: allowing unamend to undo an unamend is an intentional feature
424 427 extras = predctx.extra()
425 428 extras['unamend_source'] = curctx.hex()
426 429
427 430 def filectxfn(repo, ctx_, path):
428 431 try:
429 432 return predctx.filectx(path)
430 433 except KeyError:
431 434 return None
432 435
433 436 # Make a new commit same as predctx
434 437 newctx = context.memctx(repo,
435 438 parents=(predctx.p1(), predctx.p2()),
436 439 text=predctx.description(),
437 440 files=predctx.files(),
438 441 filectxfn=filectxfn,
439 442 user=predctx.user(),
440 443 date=predctx.date(),
441 444 extra=extras)
442 445 newprednode = repo.commitctx(newctx)
443 446 newpredctx = repo[newprednode]
444 447 dirstate = repo.dirstate
445 448
446 449 with dirstate.parentchange():
447 450 _fixdirstate(repo, curctx, newpredctx)
448 451
449 452 mapping = {curctx.node(): (newprednode,)}
450 453 scmutil.cleanupnodes(repo, mapping, 'unamend', fixphase=True)
@@ -1,441 +1,444 b''
1 1 Test uncommit - set up the config
2 2
3 3 $ cat >> $HGRCPATH <<EOF
4 4 > [experimental]
5 5 > evolution.createmarkers=True
6 6 > evolution.allowunstable=True
7 7 > [extensions]
8 8 > uncommit =
9 9 > drawdag=$TESTDIR/drawdag.py
10 10 > EOF
11 11
12 12 Build up a repo
13 13
14 14 $ hg init repo
15 15 $ cd repo
16 16 $ hg bookmark foo
17 17
18 18 Help for uncommit
19 19
20 20 $ hg help uncommit
21 21 hg uncommit [OPTION]... [FILE]...
22 22
23 23 uncommit part or all of a local changeset
24 24
25 25 This command undoes the effect of a local commit, returning the affected
26 26 files to their uncommitted state. This means that files modified or
27 27 deleted in the changeset will be left unchanged, and so will remain
28 28 modified in the working directory.
29 29
30 30 If no files are specified, the commit will be pruned, unless --keep is
31 31 given.
32 32
33 33 (use 'hg help -e uncommit' to show help for the uncommit extension)
34 34
35 35 options ([+] can be repeated):
36 36
37 37 -i --interactive interactive mode to uncommit
38 38 --keep allow an empty commit after uncommiting
39 39 -I --include PATTERN [+] include names matching the given patterns
40 40 -X --exclude PATTERN [+] exclude names matching the given patterns
41 41
42 42 (some details hidden, use --verbose to show complete help)
43 43
44 44 Uncommit with no commits should fail
45 45
46 46 $ hg uncommit
47 47 abort: cannot uncommit null changeset
48 48 (no changeset checked out)
49 49 [255]
50 50
51 51 Create some commits
52 52
53 53 $ touch files
54 54 $ hg add files
55 55 $ for i in a ab abc abcd abcde; do echo $i > files; echo $i > file-$i; hg add file-$i; hg commit -m "added file-$i"; done
56 56 $ ls
57 57 file-a
58 58 file-ab
59 59 file-abc
60 60 file-abcd
61 61 file-abcde
62 62 files
63 63
64 64 $ hg log -G -T '{rev}:{node} {desc}' --hidden
65 65 @ 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
66 66 |
67 67 o 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
68 68 |
69 69 o 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
70 70 |
71 71 o 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
72 72 |
73 73 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
74 74
75 75 Simple uncommit off the top, also moves bookmark
76 76
77 77 $ hg bookmark
78 78 * foo 4:6c4fd43ed714
79 79 $ hg uncommit
80 80 $ hg status
81 81 M files
82 82 A file-abcde
83 83 $ hg bookmark
84 84 * foo 3:6db330d65db4
85 85
86 86 $ hg log -G -T '{rev}:{node} {desc}' --hidden
87 87 x 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
88 88 |
89 89 @ 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
90 90 |
91 91 o 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
92 92 |
93 93 o 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
94 94 |
95 95 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
96 96
97 97
98 98 Recommit
99 99
100 100 $ hg commit -m 'new change abcde'
101 101 $ hg status
102 102 $ hg heads -T '{rev}:{node} {desc}'
103 103 5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde (no-eol)
104 104
105 105 Uncommit of non-existent and unchanged files has no effect
106 106 $ hg uncommit nothinghere
107 107 nothing to uncommit
108 108 [1]
109 109 $ hg status
110 110 $ hg uncommit file-abc
111 111 nothing to uncommit
112 112 [1]
113 113 $ hg status
114 114
115 115 Try partial uncommit, also moves bookmark
116 116
117 117 $ hg bookmark
118 118 * foo 5:0c07a3ccda77
119 119 $ hg uncommit files
120 120 $ hg status
121 121 M files
122 122 $ hg bookmark
123 123 * foo 6:3727deee06f7
124 124 $ hg heads -T '{rev}:{node} {desc}'
125 125 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde (no-eol)
126 126 $ hg log -r . -p -T '{rev}:{node} {desc}'
127 127 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcdediff -r 6db330d65db4 -r 3727deee06f7 file-abcde
128 128 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
129 129 +++ b/file-abcde Thu Jan 01 00:00:00 1970 +0000
130 130 @@ -0,0 +1,1 @@
131 131 +abcde
132 132
133 133 $ hg log -G -T '{rev}:{node} {desc}' --hidden
134 134 @ 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde
135 135 |
136 136 | x 5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde
137 137 |/
138 138 | x 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
139 139 |/
140 140 o 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
141 141 |
142 142 o 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
143 143 |
144 144 o 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
145 145 |
146 146 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
147 147
148 148 $ hg commit -m 'update files for abcde'
149 149
150 150 Uncommit with dirty state
151 151
152 152 $ echo "foo" >> files
153 153 $ cat files
154 154 abcde
155 155 foo
156 156 $ hg status
157 157 M files
158 158 $ hg uncommit
159 159 abort: uncommitted changes
160 160 [255]
161 161 $ hg uncommit files
162 note: keeping empty commit
162 163 $ cat files
163 164 abcde
164 165 foo
165 166 $ hg commit --amend -m "files abcde + foo"
166 167
167 168 Testing the 'experimental.uncommitondirtywdir' config
168 169
169 170 $ echo "bar" >> files
170 171 $ hg uncommit
171 172 abort: uncommitted changes
172 173 [255]
173 174 $ hg uncommit --config experimental.uncommitondirtywdir=True
174 175 $ hg commit -m "files abcde + foo"
175 176
176 177 Uncommit in the middle of a stack, does not move bookmark
177 178
178 179 $ hg checkout '.^^^'
179 180 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
180 181 (leaving bookmark foo)
181 182 $ hg log -r . -p -T '{rev}:{node} {desc}'
182 183 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abcdiff -r 69a232e754b0 -r abf2df566fc1 file-abc
183 184 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
184 185 +++ b/file-abc Thu Jan 01 00:00:00 1970 +0000
185 186 @@ -0,0 +1,1 @@
186 187 +abc
187 188 diff -r 69a232e754b0 -r abf2df566fc1 files
188 189 --- a/files Thu Jan 01 00:00:00 1970 +0000
189 190 +++ b/files Thu Jan 01 00:00:00 1970 +0000
190 191 @@ -1,1 +1,1 @@
191 192 -ab
192 193 +abc
193 194
194 195 $ hg bookmark
195 196 foo 10:48e5bd7cd583
196 197 $ hg uncommit
197 198 3 new orphan changesets
198 199 $ hg status
199 200 M files
200 201 A file-abc
201 202 $ hg heads -T '{rev}:{node} {desc}'
202 203 10:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo (no-eol)
203 204 $ hg bookmark
204 205 foo 10:48e5bd7cd583
205 206 $ hg commit -m 'new abc'
206 207 created new head
207 208
208 209 Partial uncommit in the middle, does not move bookmark
209 210
210 211 $ hg checkout '.^'
211 212 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
212 213 $ hg log -r . -p -T '{rev}:{node} {desc}'
213 214 1:69a232e754b08d568c4899475faf2eb44b857802 added file-abdiff -r 3004d2d9b508 -r 69a232e754b0 file-ab
214 215 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
215 216 +++ b/file-ab Thu Jan 01 00:00:00 1970 +0000
216 217 @@ -0,0 +1,1 @@
217 218 +ab
218 219 diff -r 3004d2d9b508 -r 69a232e754b0 files
219 220 --- a/files Thu Jan 01 00:00:00 1970 +0000
220 221 +++ b/files Thu Jan 01 00:00:00 1970 +0000
221 222 @@ -1,1 +1,1 @@
222 223 -a
223 224 +ab
224 225
225 226 $ hg bookmark
226 227 foo 10:48e5bd7cd583
227 228 $ hg uncommit file-ab
228 229 1 new orphan changesets
229 230 $ hg status
230 231 A file-ab
231 232
232 233 $ hg heads -T '{rev}:{node} {desc}\n'
233 234 12:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
234 235 11:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
235 236 10:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
236 237
237 238 $ hg bookmark
238 239 foo 10:48e5bd7cd583
239 240 $ hg commit -m 'update ab'
240 241 $ hg status
241 242 $ hg heads -T '{rev}:{node} {desc}\n'
242 243 13:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
243 244 11:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
244 245 10:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
245 246
246 247 $ hg log -G -T '{rev}:{node} {desc}' --hidden
247 248 @ 13:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
248 249 |
249 250 o 12:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
250 251 |
251 252 | * 11:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
252 253 | |
253 254 | | * 10:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
254 255 | | |
255 256 | | | x 9:8a6b58c173ca6a2e3745d8bd86698718d664bc6c files abcde + foo
256 257 | | |/
257 258 | | | x 8:39ad452c7f684a55d161c574340c5766c4569278 update files for abcde
258 259 | | |/
259 260 | | | x 7:0977fa602c2fd7d8427ed4e7ee15ea13b84c9173 update files for abcde
260 261 | | |/
261 262 | | * 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde
262 263 | | |
263 264 | | | x 5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde
264 265 | | |/
265 266 | | | x 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
266 267 | | |/
267 268 | | * 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
268 269 | | |
269 270 | | x 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
270 271 | |/
271 272 | x 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
272 273 |/
273 274 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
274 275
275 276 Uncommit with draft parent
276 277
277 278 $ hg uncommit
278 279 $ hg phase -r .
279 280 12: draft
280 281 $ hg commit -m 'update ab again'
281 282
282 283 Phase is preserved
283 284
284 285 $ hg uncommit --keep --config phases.new-commit=secret
286 note: keeping empty commit
285 287 $ hg phase -r .
286 288 15: draft
287 289 $ hg commit --amend -m 'update ab again'
288 290
289 291 Uncommit with public parent
290 292
291 293 $ hg phase -p "::.^"
292 294 $ hg uncommit
293 295 $ hg phase -r .
294 296 12: public
295 297
296 298 Partial uncommit with public parent
297 299
298 300 $ echo xyz > xyz
299 301 $ hg add xyz
300 302 $ hg commit -m "update ab and add xyz"
301 303 $ hg uncommit xyz
302 304 $ hg status
303 305 A xyz
304 306 $ hg phase -r .
305 307 18: draft
306 308 $ hg phase -r ".^"
307 309 12: public
308 310
309 311 Uncommit leaving an empty changeset
310 312
311 313 $ cd $TESTTMP
312 314 $ hg init repo1
313 315 $ cd repo1
314 316 $ hg debugdrawdag <<'EOS'
315 317 > Q
316 318 > |
317 319 > P
318 320 > EOS
319 321 $ hg up Q -q
320 322 $ hg uncommit --keep
323 note: keeping empty commit
321 324 $ hg log -G -T '{desc} FILES: {files}'
322 325 @ Q FILES:
323 326 |
324 327 | x Q FILES: Q
325 328 |/
326 329 o P FILES: P
327 330
328 331 $ hg status
329 332 A Q
330 333
331 334 $ cd ..
332 335 $ rm -rf repo1
333 336
334 337 Testing uncommit while merge
335 338
336 339 $ hg init repo2
337 340 $ cd repo2
338 341
339 342 Create some history
340 343
341 344 $ touch a
342 345 $ hg add a
343 346 $ for i in 1 2 3; do echo $i > a; hg commit -m "a $i"; done
344 347 $ hg checkout 0
345 348 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
346 349 $ touch b
347 350 $ hg add b
348 351 $ for i in 1 2 3; do echo $i > b; hg commit -m "b $i"; done
349 352 created new head
350 353 $ hg log -G -T '{rev}:{node} {desc}' --hidden
351 354 @ 5:2cd56cdde163ded2fbb16ba2f918c96046ab0bf2 b 3
352 355 |
353 356 o 4:c3a0d5bb3b15834ffd2ef9ef603e93ec65cf2037 b 2
354 357 |
355 358 o 3:49bb009ca26078726b8870f1edb29fae8f7618f5 b 1
356 359 |
357 360 | o 2:990982b7384266e691f1bc08ca36177adcd1c8a9 a 3
358 361 | |
359 362 | o 1:24d38e3cf160c7b6f5ffe82179332229886a6d34 a 2
360 363 |/
361 364 o 0:ea4e33293d4d274a2ba73150733c2612231f398c a 1
362 365
363 366
364 367 Add and expect uncommit to fail on both merge working dir and merge changeset
365 368
366 369 $ hg merge 2
367 370 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
368 371 (branch merge, don't forget to commit)
369 372
370 373 $ hg uncommit
371 374 abort: outstanding uncommitted merge
372 375 [255]
373 376
374 377 $ hg uncommit --config experimental.uncommitondirtywdir=True
375 378 abort: cannot uncommit while merging
376 379 [255]
377 380
378 381 $ hg status
379 382 M a
380 383 $ hg commit -m 'merge a and b'
381 384
382 385 $ hg uncommit
383 386 abort: cannot uncommit merge changeset
384 387 [255]
385 388
386 389 $ hg status
387 390 $ hg log -G -T '{rev}:{node} {desc}' --hidden
388 391 @ 6:c03b9c37bc67bf504d4912061cfb527b47a63c6e merge a and b
389 392 |\
390 393 | o 5:2cd56cdde163ded2fbb16ba2f918c96046ab0bf2 b 3
391 394 | |
392 395 | o 4:c3a0d5bb3b15834ffd2ef9ef603e93ec65cf2037 b 2
393 396 | |
394 397 | o 3:49bb009ca26078726b8870f1edb29fae8f7618f5 b 1
395 398 | |
396 399 o | 2:990982b7384266e691f1bc08ca36177adcd1c8a9 a 3
397 400 | |
398 401 o | 1:24d38e3cf160c7b6f5ffe82179332229886a6d34 a 2
399 402 |/
400 403 o 0:ea4e33293d4d274a2ba73150733c2612231f398c a 1
401 404
402 405
403 406 Rename a->b, then remove b in working copy. Result should remove a.
404 407
405 408 $ hg co -q 0
406 409 $ hg mv a b
407 410 $ hg ci -qm 'move a to b'
408 411 $ hg rm b
409 412 $ hg uncommit --config experimental.uncommitondirtywdir=True
410 413 $ hg st --copies
411 414 R a
412 415 $ hg revert a
413 416
414 417 Rename a->b, then rename b->c in working copy. Result should rename a->c.
415 418
416 419 $ hg co -q 0
417 420 $ hg mv a b
418 421 $ hg ci -qm 'move a to b'
419 422 $ hg mv b c
420 423 $ hg uncommit --config experimental.uncommitondirtywdir=True
421 424 $ hg st --copies
422 425 A c
423 426 a
424 427 R a
425 428 $ hg revert a
426 429 $ hg forget c
427 430 $ rm c
428 431
429 432 Copy a->b1 and a->b2, then rename b1->c in working copy. Result should copy a->b2 and a->c.
430 433
431 434 $ hg co -q 0
432 435 $ hg cp a b1
433 436 $ hg cp a b2
434 437 $ hg ci -qm 'move a to b1 and b2'
435 438 $ hg mv b1 c
436 439 $ hg uncommit --config experimental.uncommitondirtywdir=True
437 440 $ hg st --copies
438 441 A b2
439 442 a
440 443 A c
441 444 a
General Comments 0
You need to be logged in to leave comments. Login now