##// END OF EJS Templates
commit: add option to amend the working dir parent...
Idan Kamara -
r16458:55982f62 default
parent child Browse files
Show More
@@ -0,0 +1,292 b''
1 $ hg init
2
3 Setup:
4
5 $ echo a >> a
6 $ hg ci -Am 'base'
7 adding a
8
9 Refuse to amend public csets:
10
11 $ hg phase -r . -p
12 $ hg ci --amend
13 abort: cannot amend public changesets
14 [255]
15 $ hg phase -r . -f -d
16
17 $ echo a >> a
18 $ hg ci -Am 'base1'
19
20 Nothing to amend:
21
22 $ hg ci --amend
23 nothing changed
24 [1]
25
26 Amending changeset with changes in working dir:
27
28 $ echo a >> a
29 $ hg ci --amend -m 'amend base1'
30 saved backup bundle to $TESTTMP/.hg/strip-backup/489edb5b847d-amend-backup.hg
31 $ hg diff -c .
32 diff -r ad120869acf0 -r 9cd25b479c51 a
33 --- a/a Thu Jan 01 00:00:00 1970 +0000
34 +++ b/a Thu Jan 01 00:00:00 1970 +0000
35 @@ -1,1 +1,3 @@
36 a
37 +a
38 +a
39 $ hg log
40 changeset: 1:9cd25b479c51
41 tag: tip
42 user: test
43 date: Thu Jan 01 00:00:00 1970 +0000
44 summary: amend base1
45
46 changeset: 0:ad120869acf0
47 user: test
48 date: Thu Jan 01 00:00:00 1970 +0000
49 summary: base
50
51
52 Add new file:
53
54 $ echo b > b
55 $ hg ci --amend -Am 'amend base1 new file'
56 adding b
57 saved backup bundle to $TESTTMP/.hg/strip-backup/9cd25b479c51-amend-backup.hg
58
59 Remove file that was added in amended commit:
60
61 $ hg rm b
62 $ hg ci --amend -m 'amend base1 remove new file'
63 saved backup bundle to $TESTTMP/.hg/strip-backup/e2bb3ecffd2f-amend-backup.hg
64
65 $ hg cat b
66 b: no such file in rev 664a9b2d60cd
67 [1]
68
69 No changes, just a different message:
70
71 $ hg ci -v --amend -m 'no changes, new message'
72 amending changeset 664a9b2d60cd
73 copying changeset 664a9b2d60cd to ad120869acf0
74 a
75 stripping amended changeset 664a9b2d60cd
76 1 changesets found
77 saved backup bundle to $TESTTMP/.hg/strip-backup/664a9b2d60cd-amend-backup.hg
78 1 changesets found
79 adding branch
80 adding changesets
81 adding manifests
82 adding file changes
83 added 1 changesets with 1 changes to 1 files
84 committed changeset 1:ea6e356ff2ad
85 $ hg diff -c .
86 diff -r ad120869acf0 -r ea6e356ff2ad a
87 --- a/a Thu Jan 01 00:00:00 1970 +0000
88 +++ b/a Thu Jan 01 00:00:00 1970 +0000
89 @@ -1,1 +1,3 @@
90 a
91 +a
92 +a
93 $ hg log
94 changeset: 1:ea6e356ff2ad
95 tag: tip
96 user: test
97 date: Thu Jan 01 00:00:00 1970 +0000
98 summary: no changes, new message
99
100 changeset: 0:ad120869acf0
101 user: test
102 date: Thu Jan 01 00:00:00 1970 +0000
103 summary: base
104
105
106 Disable default date on commit so when -d isn't given, the old date is preserved:
107
108 $ echo '[defaults]' >> $HGRCPATH
109 $ echo 'commit=' >> $HGRCPATH
110
111 Test -u/-d:
112
113 $ hg ci --amend -u foo -d '1 0'
114 saved backup bundle to $TESTTMP/.hg/strip-backup/ea6e356ff2ad-amend-backup.hg
115 $ echo a >> a
116 $ hg ci --amend -u foo -d '1 0'
117 saved backup bundle to $TESTTMP/.hg/strip-backup/377b91ce8b56-amend-backup.hg
118 $ hg log -r .
119 changeset: 1:2c94e4a5756f
120 tag: tip
121 user: foo
122 date: Thu Jan 01 00:00:01 1970 +0000
123 summary: no changes, new message
124
125
126 Open editor with old commit message if a message isn't given otherwise:
127
128 $ cat > editor << '__EOF__'
129 > #!/bin/sh
130 > cat $1
131 > echo "another precious commit message" > "$1"
132 > __EOF__
133 $ chmod +x editor
134 $ HGEDITOR="'`pwd`'"/editor hg commit --amend -v
135 amending changeset 2c94e4a5756f
136 copying changeset 2c94e4a5756f to ad120869acf0
137 no changes, new message
138
139
140 HG: Enter commit message. Lines beginning with 'HG:' are removed.
141 HG: Leave message empty to abort commit.
142 HG: --
143 HG: user: foo
144 HG: branch 'default'
145 HG: changed a
146 a
147 stripping amended changeset 2c94e4a5756f
148 1 changesets found
149 saved backup bundle to $TESTTMP/.hg/strip-backup/2c94e4a5756f-amend-backup.hg
150 1 changesets found
151 adding branch
152 adding changesets
153 adding manifests
154 adding file changes
155 added 1 changesets with 1 changes to 1 files
156 committed changeset 1:ffb49186f961
157
158 Same, but with changes in working dir (different code path):
159
160 $ echo a >> a
161 $ HGEDITOR="'`pwd`'"/editor hg commit --amend -v
162 amending changeset ffb49186f961
163 another precious commit message
164
165
166 HG: Enter commit message. Lines beginning with 'HG:' are removed.
167 HG: Leave message empty to abort commit.
168 HG: --
169 HG: user: foo
170 HG: branch 'default'
171 HG: changed a
172 a
173 copying changeset 27f3aacd3011 to ad120869acf0
174 a
175 stripping intermediate changeset 27f3aacd3011
176 stripping amended changeset ffb49186f961
177 2 changesets found
178 saved backup bundle to $TESTTMP/.hg/strip-backup/ffb49186f961-amend-backup.hg
179 1 changesets found
180 adding branch
181 adding changesets
182 adding manifests
183 adding file changes
184 added 1 changesets with 1 changes to 1 files
185 committed changeset 1:fb6cca43446f
186
187 $ rm editor
188 $ hg log -r .
189 changeset: 1:fb6cca43446f
190 tag: tip
191 user: foo
192 date: Thu Jan 01 00:00:01 1970 +0000
193 summary: another precious commit message
194
195
196 Moving bookmarks, preserve active bookmark:
197
198 $ hg book book1
199 $ hg book book2
200 $ hg ci --amend -m 'move bookmarks'
201 saved backup bundle to $TESTTMP/.hg/strip-backup/fb6cca43446f-amend-backup.hg
202 $ hg book
203 book1 1:0cf1c7a51bcf
204 * book2 1:0cf1c7a51bcf
205 $ echo a >> a
206 $ hg ci --amend -m 'move bookmarks'
207 saved backup bundle to $TESTTMP/.hg/strip-backup/0cf1c7a51bcf-amend-backup.hg
208 $ hg book
209 book1 1:7344472bd951
210 * book2 1:7344472bd951
211
212 $ echo '[defaults]' >> $HGRCPATH
213 $ echo "commit=-d '0 0'" >> $HGRCPATH
214
215 Moving branches:
216
217 $ hg branch foo
218 marked working directory as branch foo
219 (branches are permanent and global, did you want a bookmark?)
220 $ echo a >> a
221 $ hg ci -m 'branch foo'
222 $ hg branch default -f
223 marked working directory as branch default
224 (branches are permanent and global, did you want a bookmark?)
225 $ hg ci --amend -m 'back to default'
226 saved backup bundle to $TESTTMP/.hg/strip-backup/1661ca36a2db-amend-backup.hg
227 $ hg branches
228 default 2:f24ee5961967
229
230 Close branch:
231
232 $ hg up -q 0
233 $ echo b >> b
234 $ hg branch foo
235 marked working directory as branch foo
236 (branches are permanent and global, did you want a bookmark?)
237 $ hg ci -Am 'fork'
238 adding b
239 $ echo b >> b
240 $ hg ci -mb
241 $ hg ci --amend --close-branch -m 'closing branch foo'
242 saved backup bundle to $TESTTMP/.hg/strip-backup/c962248fa264-amend-backup.hg
243
244 Same thing, different code path:
245
246 $ echo b >> b
247 $ hg ci -m 'reopen branch'
248 reopening closed branch head 4
249 $ echo b >> b
250 $ hg ci --amend --close-branch
251 saved backup bundle to $TESTTMP/.hg/strip-backup/5e302dcc12b8-amend-backup.hg
252 $ hg branches
253 default 2:f24ee5961967
254
255 Refuse to amend merges:
256
257 $ hg up -q default
258 $ hg merge foo
259 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
260 (branch merge, don't forget to commit)
261 $ hg ci --amend
262 abort: cannot amend while merging
263 [255]
264 $ hg ci -m 'merge'
265 $ hg ci --amend
266 abort: cannot amend merge changesets
267 [255]
268
269 Follow copies/renames:
270
271 $ hg mv b c
272 $ hg ci -m 'b -> c'
273 $ hg mv c d
274 $ hg ci --amend -m 'b -> d'
275 saved backup bundle to $TESTTMP/.hg/strip-backup/9c207120aa98-amend-backup.hg
276 $ hg st --rev .^ --copies d
277 A d
278 b
279 $ hg cp d e
280 $ hg ci -m 'e = d'
281 $ hg cp e f
282 $ hg ci --amend -m 'f = d'
283 saved backup bundle to $TESTTMP/.hg/strip-backup/fda2b3b27b22-amend-backup.hg
284 $ hg st --rev .^ --copies f
285 A f
286 d
287
288 Can't rollback an amend:
289
290 $ hg rollback
291 no rollback information available
292 [1]
@@ -10,7 +10,7 b' from i18n import _'
10 10 import os, sys, errno, re, tempfile
11 11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 12 import match as matchmod
13 import subrepo
13 import subrepo, context, repair, bookmarks
14 14
15 15 def parsealiases(cmd):
16 16 return cmd.lstrip("^").split("|")
@@ -1285,6 +1285,123 b' def commit(ui, repo, commitfunc, pats, o'
1285 1285 return commitfunc(ui, repo, message,
1286 1286 scmutil.match(repo[None], pats, opts), opts)
1287 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
1404
1288 1405 def commiteditor(repo, ctx, subs):
1289 1406 if ctx.description():
1290 1407 return ctx.description()
@@ -1163,6 +1163,7 b' def clone(ui, source, dest=None, **opts)'
1163 1163 _('mark new/missing files as added/removed before committing')),
1164 1164 ('', 'close-branch', None,
1165 1165 _('mark a branch as closed, hiding it from the branch list')),
1166 ('', 'amend', None, _('amend the parent of the working dir')),
1166 1167 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1167 1168 _('[OPTION]... [FILE]...'))
1168 1169 def commit(ui, repo, *pats, **opts):
@@ -1183,6 +1184,20 b' def commit(ui, repo, *pats, **opts):'
1183 1184 commit fails, you will find a backup of your message in
1184 1185 ``.hg/last-message.txt``.
1185 1186
1187 The --amend flag can be used to amend the parent of the
1188 working directory with a new commit that contains the changes
1189 in the parent in addition to those currently reported by :hg:`status`,
1190 if there are any. The old commit is stored in a backup bundle in
1191 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1192 on how to restore it).
1193
1194 Message, user and date are taken from the amended commit unless
1195 specified. When a message isn't specified on the command line,
1196 the editor will open with the message of the amended commit.
1197
1198 It is not possible to amend public changesets (see :hg:`help phases`)
1199 or changesets that have children.
1200
1186 1201 See :hg:`help dates` for a list of formats valid for -d/--date.
1187 1202
1188 1203 Returns 0 on success, 1 if nothing changed.
@@ -1198,31 +1213,70 b' def commit(ui, repo, *pats, **opts):'
1198 1213 # current branch, so it's sufficient to test branchheads
1199 1214 raise util.Abort(_('can only close branch heads'))
1200 1215 extra['close'] = 1
1201 e = cmdutil.commiteditor
1202 if opts.get('force_editor'):
1203 e = cmdutil.commitforceeditor
1204
1205 def commitfunc(ui, repo, message, match, opts):
1206 return repo.commit(message, opts.get('user'), opts.get('date'), match,
1207 editor=e, extra=extra)
1208 1216
1209 1217 branch = repo[None].branch()
1210 1218 bheads = repo.branchheads(branch)
1211 1219
1212 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1213 if not node:
1214 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1215 if stat[3]:
1216 ui.status(_("nothing changed (%d missing files, see 'hg status')\n")
1217 % len(stat[3]))
1218 else:
1220 if opts.get('amend'):
1221 if ui.config('ui', 'commitsubrepos'):
1222 raise util.Abort(_('cannot amend recursively'))
1223
1224 old = repo['.']
1225 if old.phase() == phases.public:
1226 raise util.Abort(_('cannot amend public changesets'))
1227 if len(old.parents()) > 1:
1228 raise util.Abort(_('cannot amend merge changesets'))
1229 if len(repo[None].parents()) > 1:
1230 raise util.Abort(_('cannot amend while merging'))
1231 if old.children():
1232 raise util.Abort(_('cannot amend changeset with children'))
1233
1234 e = cmdutil.commiteditor
1235 if opts.get('force_editor'):
1236 e = cmdutil.commitforceeditor
1237
1238 def commitfunc(ui, repo, message, match, opts):
1239 editor = e
1240 # message contains text from -m or -l, if it's empty,
1241 # open the editor with the old message
1242 if not message:
1243 message = old.description()
1244 editor = cmdutil.commitforceeditor
1245 return repo.commit(message,
1246 opts.get('user') or old.user(),
1247 opts.get('date') or old.date(),
1248 match,
1249 editor=editor,
1250 extra=extra)
1251
1252 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1253 if node == old.node():
1219 1254 ui.status(_("nothing changed\n"))
1220 return 1
1255 return 1
1256 else:
1257 e = cmdutil.commiteditor
1258 if opts.get('force_editor'):
1259 e = cmdutil.commitforceeditor
1260
1261 def commitfunc(ui, repo, message, match, opts):
1262 return repo.commit(message, opts.get('user'), opts.get('date'),
1263 match, editor=e, extra=extra)
1264
1265 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1266
1267 if not node:
1268 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1269 if stat[3]:
1270 ui.status(_("nothing changed (%d missing files, see "
1271 "'hg status')\n") % len(stat[3]))
1272 else:
1273 ui.status(_("nothing changed\n"))
1274 return 1
1221 1275
1222 1276 ctx = repo[node]
1223 1277 parents = ctx.parents()
1224 1278
1225 if (bheads and node not in bheads and not
1279 if (not opts.get('amend') and bheads and node not in bheads and not
1226 1280 [x for x in parents if x.node() in bheads and x.branch() == branch]):
1227 1281 ui.status(_('created new head\n'))
1228 1282 # The message is not printed for initial roots. For the other
@@ -193,7 +193,7 b' Show all commands + options'
193 193 add: include, exclude, subrepos, dry-run
194 194 annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, ignore-all-space, ignore-space-change, ignore-blank-lines, include, exclude
195 195 clone: noupdate, updaterev, rev, branch, pull, uncompressed, ssh, remotecmd, insecure
196 commit: addremove, close-branch, include, exclude, message, logfile, date, user, subrepos
196 commit: addremove, close-branch, amend, include, exclude, message, logfile, date, user, subrepos
197 197 diff: rev, change, text, git, nodates, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, unified, stat, include, exclude, subrepos
198 198 export: output, switch-parent, rev, text, git, nodates
199 199 forget: include, exclude
@@ -59,6 +59,7 b' help record (record)'
59 59 committing
60 60 --close-branch mark a branch as closed, hiding it from the branch
61 61 list
62 --amend amend the parent of the working dir
62 63 -I --include PATTERN [+] include names matching the given patterns
63 64 -X --exclude PATTERN [+] exclude names matching the given patterns
64 65 -m --message TEXT use text as commit message
General Comments 0
You need to be logged in to leave comments. Login now