##// END OF EJS Templates
unamend: fix command summary line...
Martin von Zweigbergk -
r35827:f9a82b9b stable
parent child Browse files
Show More
@@ -1,257 +1,256 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,
29 29 error,
30 30 node,
31 31 obsutil,
32 32 pycompat,
33 33 registrar,
34 34 rewriteutil,
35 35 scmutil,
36 36 )
37 37
38 38 cmdtable = {}
39 39 command = registrar.command(cmdtable)
40 40
41 41 configtable = {}
42 42 configitem = registrar.configitem(configtable)
43 43
44 44 configitem('experimental', 'uncommitondirtywdir',
45 45 default=False,
46 46 )
47 47
48 48 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
49 49 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
50 50 # be specifying the version(s) of Mercurial they are tested with, or
51 51 # leave the attribute unspecified.
52 52 testedwith = 'ships-with-hg-core'
53 53
54 54 def _commitfiltered(repo, ctx, match, allowempty):
55 55 """Recommit ctx with changed files not in match. Return the new
56 56 node identifier, or None if nothing changed.
57 57 """
58 58 base = ctx.p1()
59 59 # ctx
60 60 initialfiles = set(ctx.files())
61 61 exclude = set(f for f in initialfiles if match(f))
62 62
63 63 # No files matched commit, so nothing excluded
64 64 if not exclude:
65 65 return None
66 66
67 67 files = (initialfiles - exclude)
68 68 # return the p1 so that we don't create an obsmarker later
69 69 if not files and not allowempty:
70 70 return ctx.parents()[0].node()
71 71
72 72 # Filter copies
73 73 copied = copies.pathcopies(base, ctx)
74 74 copied = dict((dst, src) for dst, src in copied.iteritems()
75 75 if dst in files)
76 76 def filectxfn(repo, memctx, path, contentctx=ctx, redirect=()):
77 77 if path not in contentctx:
78 78 return None
79 79 fctx = contentctx[path]
80 80 mctx = context.memfilectx(repo, memctx, fctx.path(), fctx.data(),
81 81 fctx.islink(),
82 82 fctx.isexec(),
83 83 copied=copied.get(path))
84 84 return mctx
85 85
86 86 new = context.memctx(repo,
87 87 parents=[base.node(), node.nullid],
88 88 text=ctx.description(),
89 89 files=files,
90 90 filectxfn=filectxfn,
91 91 user=ctx.user(),
92 92 date=ctx.date(),
93 93 extra=ctx.extra())
94 94 # phase handling
95 95 commitphase = ctx.phase()
96 96 overrides = {('phases', 'new-commit'): commitphase}
97 97 with repo.ui.configoverride(overrides, 'uncommit'):
98 98 newid = repo.commitctx(new)
99 99 return newid
100 100
101 101 def _fixdirstate(repo, oldctx, newctx, status):
102 102 """ fix the dirstate after switching the working directory from oldctx to
103 103 newctx which can be result of either unamend or uncommit.
104 104 """
105 105 ds = repo.dirstate
106 106 copies = dict(ds.copies())
107 107 s = status
108 108 for f in s.modified:
109 109 if ds[f] == 'r':
110 110 # modified + removed -> removed
111 111 continue
112 112 ds.normallookup(f)
113 113
114 114 for f in s.added:
115 115 if ds[f] == 'r':
116 116 # added + removed -> unknown
117 117 ds.drop(f)
118 118 elif ds[f] != 'a':
119 119 ds.add(f)
120 120
121 121 for f in s.removed:
122 122 if ds[f] == 'a':
123 123 # removed + added -> normal
124 124 ds.normallookup(f)
125 125 elif ds[f] != 'r':
126 126 ds.remove(f)
127 127
128 128 # Merge old parent and old working dir copies
129 129 oldcopies = {}
130 130 for f in (s.modified + s.added):
131 131 src = oldctx[f].renamed()
132 132 if src:
133 133 oldcopies[f] = src[0]
134 134 oldcopies.update(copies)
135 135 copies = dict((dst, oldcopies.get(src, src))
136 136 for dst, src in oldcopies.iteritems())
137 137 # Adjust the dirstate copies
138 138 for dst, src in copies.iteritems():
139 139 if (src not in newctx or dst in newctx or ds[dst] != 'a'):
140 140 src = None
141 141 ds.copy(src, dst)
142 142
143 143 @command('uncommit',
144 144 [('', 'keep', False, _('allow an empty commit after uncommiting')),
145 145 ] + commands.walkopts,
146 146 _('[OPTION]... [FILE]...'))
147 147 def uncommit(ui, repo, *pats, **opts):
148 148 """uncommit part or all of a local changeset
149 149
150 150 This command undoes the effect of a local commit, returning the affected
151 151 files to their uncommitted state. This means that files modified or
152 152 deleted in the changeset will be left unchanged, and so will remain
153 153 modified in the working directory.
154 154 """
155 155 opts = pycompat.byteskwargs(opts)
156 156
157 157 with repo.wlock(), repo.lock():
158 158
159 159 if not pats and not repo.ui.configbool('experimental',
160 160 'uncommitondirtywdir'):
161 161 cmdutil.bailifchanged(repo)
162 162 old = repo['.']
163 163 rewriteutil.precheck(repo, [old.rev()], 'uncommit')
164 164 if len(old.parents()) > 1:
165 165 raise error.Abort(_("cannot uncommit merge changeset"))
166 166
167 167 with repo.transaction('uncommit'):
168 168 match = scmutil.match(old, pats, opts)
169 169 newid = _commitfiltered(repo, old, match, opts.get('keep'))
170 170 if newid is None:
171 171 ui.status(_("nothing to uncommit\n"))
172 172 return 1
173 173
174 174 mapping = {}
175 175 if newid != old.p1().node():
176 176 # Move local changes on filtered changeset
177 177 mapping[old.node()] = (newid,)
178 178 else:
179 179 # Fully removed the old commit
180 180 mapping[old.node()] = ()
181 181
182 182 scmutil.cleanupnodes(repo, mapping, 'uncommit')
183 183
184 184 with repo.dirstate.parentchange():
185 185 repo.dirstate.setparents(newid, node.nullid)
186 186 s = repo.status(old.p1(), old, match=match)
187 187 _fixdirstate(repo, old, repo[newid], s)
188 188
189 189 def predecessormarkers(ctx):
190 190 """yields the obsolete markers marking the given changeset as a successor"""
191 191 for data in ctx.repo().obsstore.predecessors.get(ctx.node(), ()):
192 192 yield obsutil.marker(ctx.repo(), data)
193 193
194 194 @command('^unamend', [])
195 195 def unamend(ui, repo, **opts):
196 """
197 undo the most recent amend operation on a current changeset
196 """undo the most recent amend operation on a current changeset
198 197
199 198 This command will roll back to the previous version of a changeset,
200 199 leaving working directory in state in which it was before running
201 200 `hg amend` (e.g. files modified as part of an amend will be
202 201 marked as modified `hg status`)
203 202 """
204 203
205 204 unfi = repo.unfiltered()
206 205 with repo.wlock(), repo.lock(), repo.transaction('unamend'):
207 206
208 207 # identify the commit from which to unamend
209 208 curctx = repo['.']
210 209
211 210 rewriteutil.precheck(repo, [curctx.rev()], 'unamend')
212 211
213 212 # identify the commit to which to unamend
214 213 markers = list(predecessormarkers(curctx))
215 214 if len(markers) != 1:
216 215 e = _("changeset must have one predecessor, found %i predecessors")
217 216 raise error.Abort(e % len(markers))
218 217
219 218 prednode = markers[0].prednode()
220 219 predctx = unfi[prednode]
221 220
222 221 # add an extra so that we get a new hash
223 222 # note: allowing unamend to undo an unamend is an intentional feature
224 223 extras = predctx.extra()
225 224 extras['unamend_source'] = curctx.hex()
226 225
227 226 def filectxfn(repo, ctx_, path):
228 227 try:
229 228 return predctx.filectx(path)
230 229 except KeyError:
231 230 return None
232 231
233 232 # Make a new commit same as predctx
234 233 newctx = context.memctx(repo,
235 234 parents=(predctx.p1(), predctx.p2()),
236 235 text=predctx.description(),
237 236 files=predctx.files(),
238 237 filectxfn=filectxfn,
239 238 user=predctx.user(),
240 239 date=predctx.date(),
241 240 extra=extras)
242 241 # phase handling
243 242 commitphase = curctx.phase()
244 243 overrides = {('phases', 'new-commit'): commitphase}
245 244 with repo.ui.configoverride(overrides, 'uncommit'):
246 245 newprednode = repo.commitctx(newctx)
247 246
248 247 newpredctx = repo[newprednode]
249 248 dirstate = repo.dirstate
250 249
251 250 with dirstate.parentchange():
252 251 dirstate.setparents(newprednode, node.nullid)
253 252 s = repo.status(predctx, curctx)
254 253 _fixdirstate(repo, curctx, newpredctx, s)
255 254
256 255 mapping = {curctx.node(): (newprednode,)}
257 256 scmutil.cleanupnodes(repo, mapping, 'unamend')
General Comments 0
You need to be logged in to leave comments. Login now