comparison mercurial/cmdutil.py @ 16458:55982f62651f

commit: add option to amend the working dir parent The --amend flag can be used to amend the parent of the working directory with a new commit that contains the changes in the parent in addition to those currently reported by "hg status", if there are any. The old commit is stored in a backup bundle in ".hg/strip-backup"(see "hg help bundle" and "hg help unbundle" on how to restore it). Message, user and date are taken from the amended commit unless specified. When a message isn't specified on the command line, the editor will open with the message of the amended commit. It is not possible to amend public changesets (see "hg help phases") or changesets that have children. Behind the scenes, first commit the update (if there is one) as a regular child of the current parent. Then create a new commit on the parent's parent with the updated contents. Then change the working copy parent to this new combined changeset. Finally, strip the amended commit and update commit created in the beginning. An alternative (cleaner?) approach of doing this is suggested here: http://selenic.com/pipermail/mercurial-devel/2012-March/038540.html It is currently not possible to amend merge commits or recursively, this can be added at a later time.
author Idan Kamara <idankk86@gmail.com>
date Wed, 18 Apr 2012 01:20:16 +0300
parents 6883c2363f44
children e596a631210e
comparison
equal deleted inserted replaced
16457:91196ebcaeed 16458:55982f62651f
8 from node import hex, nullid, nullrev, short 8 from node import hex, nullid, nullrev, short
9 from i18n import _ 9 from i18n import _
10 import os, sys, errno, re, tempfile 10 import os, sys, errno, re, tempfile
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies 11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 import match as matchmod 12 import match as matchmod
13 import subrepo 13 import subrepo, context, repair, bookmarks
14 14
15 def parsealiases(cmd): 15 def parsealiases(cmd):
16 return cmd.lstrip("^").split("|") 16 return cmd.lstrip("^").split("|")
17 17
18 def findpossible(cmd, table, strict=False): 18 def findpossible(cmd, table, strict=False):
1282 if opts.get('addremove'): 1282 if opts.get('addremove'):
1283 scmutil.addremove(repo, pats, opts) 1283 scmutil.addremove(repo, pats, opts)
1284 1284
1285 return commitfunc(ui, repo, message, 1285 return commitfunc(ui, repo, message,
1286 scmutil.match(repo[None], pats, opts), opts) 1286 scmutil.match(repo[None], pats, opts), opts)
1287
1288 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1289 ui.note(_('amending changeset %s\n') % old)
1290 base = old.p1()
1291
1292 wlock = repo.wlock()
1293 try:
1294 # Fix up dirstate for copies and renames
1295 duplicatecopies(repo, None, base.node())
1296
1297 # First, do a regular commit to record all changes in the working
1298 # directory (if there are any)
1299 node = commit(ui, repo, commitfunc, pats, opts)
1300 ctx = repo[node]
1301
1302 # Participating changesets:
1303 #
1304 # node/ctx o - new (intermediate) commit that contains changes from
1305 # | working dir to go into amending commit (or a workingctx
1306 # | if there were no changes)
1307 # |
1308 # old o - changeset to amend
1309 # |
1310 # base o - parent of amending changeset
1311
1312 files = set(old.files())
1313
1314 # Second, we use either the commit we just did, or if there were no
1315 # changes the parent of the working directory as the version of the
1316 # files in the final amend commit
1317 if node:
1318 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
1319
1320 user = ctx.user()
1321 date = ctx.date()
1322 message = ctx.description()
1323 extra = ctx.extra()
1324
1325 # Prune files which were reverted by the updates: if old introduced
1326 # file X and our intermediate commit, node, renamed that file, then
1327 # those two files are the same and we can discard X from our list
1328 # of files. Likewise if X was deleted, it's no longer relevant
1329 files.update(ctx.files())
1330
1331 def samefile(f):
1332 if f in ctx.manifest():
1333 a = ctx.filectx(f)
1334 if f in base.manifest():
1335 b = base.filectx(f)
1336 return (a.data() == b.data()
1337 and a.flags() == b.flags()
1338 and a.renamed() == b.renamed())
1339 else:
1340 return False
1341 else:
1342 return f not in base.manifest()
1343 files = [f for f in files if not samefile(f)]
1344
1345 def filectxfn(repo, ctx_, path):
1346 try:
1347 return ctx.filectx(path)
1348 except KeyError:
1349 raise IOError()
1350 else:
1351 ui.note(_('copying changeset %s to %s\n') % (old, base))
1352
1353 # Use version of files as in the old cset
1354 def filectxfn(repo, ctx_, path):
1355 try:
1356 return old.filectx(path)
1357 except KeyError:
1358 raise IOError()
1359
1360 # See if we got a message from -m or -l, if not, open the editor
1361 # with the message of the changeset to amend
1362 user = opts.get('user') or old.user()
1363 date = opts.get('date') or old.date()
1364 message = logmessage(ui, opts)
1365 if not message:
1366 cctx = context.workingctx(repo, old.description(), user, date,
1367 extra,
1368 repo.status(base.node(), old.node()))
1369 message = commitforceeditor(repo, cctx, [])
1370
1371 new = context.memctx(repo,
1372 parents=[base.node(), nullid],
1373 text=message,
1374 files=files,
1375 filectxfn=filectxfn,
1376 user=user,
1377 date=date,
1378 extra=extra)
1379 newid = repo.commitctx(new)
1380 if newid != old.node():
1381 # Reroute the working copy parent to the new changeset
1382 repo.dirstate.setparents(newid, nullid)
1383
1384 # Move bookmarks from old parent to amend commit
1385 bms = repo.nodebookmarks(old.node())
1386 if bms:
1387 for bm in bms:
1388 repo._bookmarks[bm] = newid
1389 bookmarks.write(repo)
1390
1391 # Strip the intermediate commit (if there was one) and the amended
1392 # commit
1393 lock = repo.lock()
1394 try:
1395 if node:
1396 ui.note(_('stripping intermediate changeset %s\n') % ctx)
1397 ui.note(_('stripping amended changeset %s\n') % old)
1398 repair.strip(ui, repo, old.node(), topic='amend-backup')
1399 finally:
1400 lock.release()
1401 finally:
1402 wlock.release()
1403 return newid
1287 1404
1288 def commiteditor(repo, ctx, subs): 1405 def commiteditor(repo, ctx, subs):
1289 if ctx.description(): 1406 if ctx.description():
1290 return ctx.description() 1407 return ctx.description()
1291 return commitforceeditor(repo, ctx, subs) 1408 return commitforceeditor(repo, ctx, subs)