40 command = registrar.command(cmdtable) |
40 command = registrar.command(cmdtable) |
41 |
41 |
42 configtable = {} |
42 configtable = {} |
43 configitem = registrar.configitem(configtable) |
43 configitem = registrar.configitem(configtable) |
44 |
44 |
45 configitem('experimental', 'uncommitondirtywdir', |
45 configitem( |
46 default=False, |
46 'experimental', 'uncommitondirtywdir', default=False, |
47 ) |
47 ) |
48 configitem('experimental', 'uncommit.keep', |
48 configitem( |
49 default=False, |
49 'experimental', 'uncommit.keep', default=False, |
50 ) |
50 ) |
51 |
51 |
52 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for |
52 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for |
53 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should |
53 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should |
54 # be specifying the version(s) of Mercurial they are tested with, or |
54 # be specifying the version(s) of Mercurial they are tested with, or |
55 # leave the attribute unspecified. |
55 # leave the attribute unspecified. |
56 testedwith = 'ships-with-hg-core' |
56 testedwith = 'ships-with-hg-core' |
57 |
57 |
58 def _commitfiltered(repo, ctx, match, keepcommit, message=None, user=None, |
58 |
59 date=None): |
59 def _commitfiltered( |
|
60 repo, ctx, match, keepcommit, message=None, user=None, date=None |
|
61 ): |
60 """Recommit ctx with changed files not in match. Return the new |
62 """Recommit ctx with changed files not in match. Return the new |
61 node identifier, or None if nothing changed. |
63 node identifier, or None if nothing changed. |
62 """ |
64 """ |
63 base = ctx.p1() |
65 base = ctx.p1() |
64 # ctx |
66 # ctx |
71 |
73 |
72 # return the p1 so that we don't create an obsmarker later |
74 # return the p1 so that we don't create an obsmarker later |
73 if not keepcommit: |
75 if not keepcommit: |
74 return ctx.p1().node() |
76 return ctx.p1().node() |
75 |
77 |
76 files = (initialfiles - exclude) |
78 files = initialfiles - exclude |
77 # Filter copies |
79 # Filter copies |
78 copied = copiesmod.pathcopies(base, ctx) |
80 copied = copiesmod.pathcopies(base, ctx) |
79 copied = dict((dst, src) for dst, src in copied.iteritems() |
81 copied = dict((dst, src) for dst, src in copied.iteritems() if dst in files) |
80 if dst in files) |
82 |
81 def filectxfn(repo, memctx, path, contentctx=ctx, redirect=()): |
83 def filectxfn(repo, memctx, path, contentctx=ctx, redirect=()): |
82 if path not in contentctx: |
84 if path not in contentctx: |
83 return None |
85 return None |
84 fctx = contentctx[path] |
86 fctx = contentctx[path] |
85 mctx = context.memfilectx(repo, memctx, fctx.path(), fctx.data(), |
87 mctx = context.memfilectx( |
86 fctx.islink(), |
88 repo, |
87 fctx.isexec(), |
89 memctx, |
88 copysource=copied.get(path)) |
90 fctx.path(), |
|
91 fctx.data(), |
|
92 fctx.islink(), |
|
93 fctx.isexec(), |
|
94 copysource=copied.get(path), |
|
95 ) |
89 return mctx |
96 return mctx |
90 |
97 |
91 if not files: |
98 if not files: |
92 repo.ui.status(_("note: keeping empty commit\n")) |
99 repo.ui.status(_("note: keeping empty commit\n")) |
93 |
100 |
96 if not user: |
103 if not user: |
97 user = ctx.user() |
104 user = ctx.user() |
98 if not date: |
105 if not date: |
99 date = ctx.date() |
106 date = ctx.date() |
100 |
107 |
101 new = context.memctx(repo, |
108 new = context.memctx( |
102 parents=[base.node(), node.nullid], |
109 repo, |
103 text=message, |
110 parents=[base.node(), node.nullid], |
104 files=files, |
111 text=message, |
105 filectxfn=filectxfn, |
112 files=files, |
106 user=user, |
113 filectxfn=filectxfn, |
107 date=date, |
114 user=user, |
108 extra=ctx.extra()) |
115 date=date, |
|
116 extra=ctx.extra(), |
|
117 ) |
109 return repo.commitctx(new) |
118 return repo.commitctx(new) |
110 |
119 |
111 @command('uncommit', |
120 |
112 [('', 'keep', None, _('allow an empty commit after uncommitting')), |
121 @command( |
113 ('', 'allow-dirty-working-copy', False, |
122 'uncommit', |
114 _('allow uncommit with outstanding changes')), |
123 [ |
115 (b'n', b'note', b'', _(b'store a note on uncommit'), _(b'TEXT')) |
124 ('', 'keep', None, _('allow an empty commit after uncommitting')), |
116 ] + commands.walkopts + commands.commitopts + commands.commitopts2 |
125 ( |
|
126 '', |
|
127 'allow-dirty-working-copy', |
|
128 False, |
|
129 _('allow uncommit with outstanding changes'), |
|
130 ), |
|
131 (b'n', b'note', b'', _(b'store a note on uncommit'), _(b'TEXT')), |
|
132 ] |
|
133 + commands.walkopts |
|
134 + commands.commitopts |
|
135 + commands.commitopts2 |
117 + commands.commitopts3, |
136 + commands.commitopts3, |
118 _('[OPTION]... [FILE]...'), |
137 _('[OPTION]... [FILE]...'), |
119 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT) |
138 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, |
|
139 ) |
120 def uncommit(ui, repo, *pats, **opts): |
140 def uncommit(ui, repo, *pats, **opts): |
121 """uncommit part or all of a local changeset |
141 """uncommit part or all of a local changeset |
122 |
142 |
123 This command undoes the effect of a local commit, returning the affected |
143 This command undoes the effect of a local commit, returning the affected |
124 files to their uncommitted state. This means that files modified or |
144 files to their uncommitted state. This means that files modified or |
135 |
155 |
136 with repo.wlock(), repo.lock(): |
156 with repo.wlock(), repo.lock(): |
137 |
157 |
138 m, a, r, d = repo.status()[:4] |
158 m, a, r, d = repo.status()[:4] |
139 isdirtypath = any(set(m + a + r + d) & set(pats)) |
159 isdirtypath = any(set(m + a + r + d) & set(pats)) |
140 allowdirtywcopy = (opts['allow_dirty_working_copy'] or |
160 allowdirtywcopy = opts[ |
141 repo.ui.configbool('experimental', 'uncommitondirtywdir')) |
161 'allow_dirty_working_copy' |
|
162 ] or repo.ui.configbool('experimental', 'uncommitondirtywdir') |
142 if not allowdirtywcopy and (not pats or isdirtypath): |
163 if not allowdirtywcopy and (not pats or isdirtypath): |
143 cmdutil.bailifchanged(repo, hint=_('requires ' |
164 cmdutil.bailifchanged( |
144 '--allow-dirty-working-copy to uncommit')) |
165 repo, |
|
166 hint=_('requires ' '--allow-dirty-working-copy to uncommit'), |
|
167 ) |
145 old = repo['.'] |
168 old = repo['.'] |
146 rewriteutil.precheck(repo, [old.rev()], 'uncommit') |
169 rewriteutil.precheck(repo, [old.rev()], 'uncommit') |
147 if len(old.parents()) > 1: |
170 if len(old.parents()) > 1: |
148 raise error.Abort(_("cannot uncommit merge changeset")) |
171 raise error.Abort(_("cannot uncommit merge changeset")) |
149 |
172 |
162 if badfiles: |
185 if badfiles: |
163 badfiles -= {f for f in util.dirs(eligible)} |
186 badfiles -= {f for f in util.dirs(eligible)} |
164 |
187 |
165 for f in sorted(badfiles): |
188 for f in sorted(badfiles): |
166 if f in s.clean: |
189 if f in s.clean: |
167 hint = _(b"file was not changed in working directory " |
190 hint = _( |
168 b"parent") |
191 b"file was not changed in working directory " b"parent" |
|
192 ) |
169 elif repo.wvfs.exists(f): |
193 elif repo.wvfs.exists(f): |
170 hint = _(b"file was untracked in working directory parent") |
194 hint = _(b"file was untracked in working directory parent") |
171 else: |
195 else: |
172 hint = _(b"file does not exist") |
196 hint = _(b"file does not exist") |
173 |
197 |
174 raise error.Abort(_(b'cannot uncommit "%s"') |
198 raise error.Abort( |
175 % scmutil.getuipathfn(repo)(f), hint=hint) |
199 _(b'cannot uncommit "%s"') % scmutil.getuipathfn(repo)(f), |
|
200 hint=hint, |
|
201 ) |
176 |
202 |
177 with repo.transaction('uncommit'): |
203 with repo.transaction('uncommit'): |
178 if not (opts[b'message'] or opts[b'logfile']): |
204 if not (opts[b'message'] or opts[b'logfile']): |
179 opts[b'message'] = old.description() |
205 opts[b'message'] = old.description() |
180 message = cmdutil.logmessage(ui, opts) |
206 message = cmdutil.logmessage(ui, opts) |
183 if not keepcommit: |
209 if not keepcommit: |
184 if opts.get('keep') is not None: |
210 if opts.get('keep') is not None: |
185 keepcommit = opts.get('keep') |
211 keepcommit = opts.get('keep') |
186 else: |
212 else: |
187 keepcommit = ui.configbool('experimental', 'uncommit.keep') |
213 keepcommit = ui.configbool('experimental', 'uncommit.keep') |
188 newid = _commitfiltered(repo, old, match, keepcommit, |
214 newid = _commitfiltered( |
189 message=message, user=opts.get(b'user'), |
215 repo, |
190 date=opts.get(b'date')) |
216 old, |
|
217 match, |
|
218 keepcommit, |
|
219 message=message, |
|
220 user=opts.get(b'user'), |
|
221 date=opts.get(b'date'), |
|
222 ) |
191 if newid is None: |
223 if newid is None: |
192 ui.status(_("nothing to uncommit\n")) |
224 ui.status(_("nothing to uncommit\n")) |
193 return 1 |
225 return 1 |
194 |
226 |
195 mapping = {} |
227 mapping = {} |
203 with repo.dirstate.parentchange(): |
235 with repo.dirstate.parentchange(): |
204 scmutil.movedirstate(repo, repo[newid], match) |
236 scmutil.movedirstate(repo, repo[newid], match) |
205 |
237 |
206 scmutil.cleanupnodes(repo, mapping, 'uncommit', fixphase=True) |
238 scmutil.cleanupnodes(repo, mapping, 'uncommit', fixphase=True) |
207 |
239 |
|
240 |
208 def predecessormarkers(ctx): |
241 def predecessormarkers(ctx): |
209 """yields the obsolete markers marking the given changeset as a successor""" |
242 """yields the obsolete markers marking the given changeset as a successor""" |
210 for data in ctx.repo().obsstore.predecessors.get(ctx.node(), ()): |
243 for data in ctx.repo().obsstore.predecessors.get(ctx.node(), ()): |
211 yield obsutil.marker(ctx.repo(), data) |
244 yield obsutil.marker(ctx.repo(), data) |
212 |
245 |
213 @command('unamend', [], helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, |
246 |
214 helpbasic=True) |
247 @command( |
|
248 'unamend', |
|
249 [], |
|
250 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, |
|
251 helpbasic=True, |
|
252 ) |
215 def unamend(ui, repo, **opts): |
253 def unamend(ui, repo, **opts): |
216 """undo the most recent amend operation on a current changeset |
254 """undo the most recent amend operation on a current changeset |
217 |
255 |
218 This command will roll back to the previous version of a changeset, |
256 This command will roll back to the previous version of a changeset, |
219 leaving working directory in state in which it was before running |
257 leaving working directory in state in which it was before running |
248 return predctx.filectx(path) |
286 return predctx.filectx(path) |
249 except KeyError: |
287 except KeyError: |
250 return None |
288 return None |
251 |
289 |
252 # Make a new commit same as predctx |
290 # Make a new commit same as predctx |
253 newctx = context.memctx(repo, |
291 newctx = context.memctx( |
254 parents=(predctx.p1(), predctx.p2()), |
292 repo, |
255 text=predctx.description(), |
293 parents=(predctx.p1(), predctx.p2()), |
256 files=predctx.files(), |
294 text=predctx.description(), |
257 filectxfn=filectxfn, |
295 files=predctx.files(), |
258 user=predctx.user(), |
296 filectxfn=filectxfn, |
259 date=predctx.date(), |
297 user=predctx.user(), |
260 extra=extras) |
298 date=predctx.date(), |
|
299 extra=extras, |
|
300 ) |
261 newprednode = repo.commitctx(newctx) |
301 newprednode = repo.commitctx(newctx) |
262 newpredctx = repo[newprednode] |
302 newpredctx = repo[newprednode] |
263 dirstate = repo.dirstate |
303 dirstate = repo.dirstate |
264 |
304 |
265 with dirstate.parentchange(): |
305 with dirstate.parentchange(): |