##// END OF EJS Templates
fix: add --all flag to fix non-public non-obsolete revisions...
Danny Hooper -
r37613:e2506748 default
parent child Browse files
Show More
@@ -1,542 +1,548
1 # fix - rewrite file content in changesets and working copy
1 # fix - rewrite file content in changesets and working copy
2 #
2 #
3 # Copyright 2018 Google LLC.
3 # Copyright 2018 Google LLC.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 """rewrite file content in changesets or working copy (EXPERIMENTAL)
7 """rewrite file content in changesets or working copy (EXPERIMENTAL)
8
8
9 Provides a command that runs configured tools on the contents of modified files,
9 Provides a command that runs configured tools on the contents of modified files,
10 writing back any fixes to the working copy or replacing changesets.
10 writing back any fixes to the working copy or replacing changesets.
11
11
12 Here is an example configuration that causes :hg:`fix` to apply automatic
12 Here is an example configuration that causes :hg:`fix` to apply automatic
13 formatting fixes to modified lines in C++ code::
13 formatting fixes to modified lines in C++ code::
14
14
15 [fix]
15 [fix]
16 clang-format:command=clang-format --assume-filename={rootpath}
16 clang-format:command=clang-format --assume-filename={rootpath}
17 clang-format:linerange=--lines={first}:{last}
17 clang-format:linerange=--lines={first}:{last}
18 clang-format:fileset=set:**.cpp or **.hpp
18 clang-format:fileset=set:**.cpp or **.hpp
19
19
20 The :command suboption forms the first part of the shell command that will be
20 The :command suboption forms the first part of the shell command that will be
21 used to fix a file. The content of the file is passed on standard input, and the
21 used to fix a file. The content of the file is passed on standard input, and the
22 fixed file content is expected on standard output. If there is any output on
22 fixed file content is expected on standard output. If there is any output on
23 standard error, the file will not be affected. Some values may be substituted
23 standard error, the file will not be affected. Some values may be substituted
24 into the command::
24 into the command::
25
25
26 {rootpath} The path of the file being fixed, relative to the repo root
26 {rootpath} The path of the file being fixed, relative to the repo root
27 {basename} The name of the file being fixed, without the directory path
27 {basename} The name of the file being fixed, without the directory path
28
28
29 If the :linerange suboption is set, the tool will only be run if there are
29 If the :linerange suboption is set, the tool will only be run if there are
30 changed lines in a file. The value of this suboption is appended to the shell
30 changed lines in a file. The value of this suboption is appended to the shell
31 command once for every range of changed lines in the file. Some values may be
31 command once for every range of changed lines in the file. Some values may be
32 substituted into the command::
32 substituted into the command::
33
33
34 {first} The 1-based line number of the first line in the modified range
34 {first} The 1-based line number of the first line in the modified range
35 {last} The 1-based line number of the last line in the modified range
35 {last} The 1-based line number of the last line in the modified range
36
36
37 The :fileset suboption determines which files will be passed through each
37 The :fileset suboption determines which files will be passed through each
38 configured tool. See :hg:`help fileset` for possible values. If there are file
38 configured tool. See :hg:`help fileset` for possible values. If there are file
39 arguments to :hg:`fix`, the intersection of these filesets is used.
39 arguments to :hg:`fix`, the intersection of these filesets is used.
40
40
41 There is also a configurable limit for the maximum size of file that will be
41 There is also a configurable limit for the maximum size of file that will be
42 processed by :hg:`fix`::
42 processed by :hg:`fix`::
43
43
44 [fix]
44 [fix]
45 maxfilesize=2MB
45 maxfilesize=2MB
46
46
47 """
47 """
48
48
49 from __future__ import absolute_import
49 from __future__ import absolute_import
50
50
51 import collections
51 import collections
52 import itertools
52 import itertools
53 import os
53 import os
54 import re
54 import re
55 import subprocess
55 import subprocess
56
56
57 from mercurial.i18n import _
57 from mercurial.i18n import _
58 from mercurial.node import nullrev
58 from mercurial.node import nullrev
59 from mercurial.node import wdirrev
59 from mercurial.node import wdirrev
60
60
61 from mercurial import (
61 from mercurial import (
62 cmdutil,
62 cmdutil,
63 context,
63 context,
64 copies,
64 copies,
65 error,
65 error,
66 mdiff,
66 mdiff,
67 merge,
67 merge,
68 obsolete,
68 obsolete,
69 registrar,
69 registrar,
70 scmutil,
70 scmutil,
71 util,
71 util,
72 )
72 )
73
73
74 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
74 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
75 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
75 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
76 # be specifying the version(s) of Mercurial they are tested with, or
76 # be specifying the version(s) of Mercurial they are tested with, or
77 # leave the attribute unspecified.
77 # leave the attribute unspecified.
78 testedwith = 'ships-with-hg-core'
78 testedwith = 'ships-with-hg-core'
79
79
80 cmdtable = {}
80 cmdtable = {}
81 command = registrar.command(cmdtable)
81 command = registrar.command(cmdtable)
82
82
83 configtable = {}
83 configtable = {}
84 configitem = registrar.configitem(configtable)
84 configitem = registrar.configitem(configtable)
85
85
86 # Register the suboptions allowed for each configured fixer.
86 # Register the suboptions allowed for each configured fixer.
87 FIXER_ATTRS = ('command', 'linerange', 'fileset')
87 FIXER_ATTRS = ('command', 'linerange', 'fileset')
88
88
89 for key in FIXER_ATTRS:
89 for key in FIXER_ATTRS:
90 configitem('fix', '.*(:%s)?' % key, default=None, generic=True)
90 configitem('fix', '.*(:%s)?' % key, default=None, generic=True)
91
91
92 # A good default size allows most source code files to be fixed, but avoids
92 # A good default size allows most source code files to be fixed, but avoids
93 # letting fixer tools choke on huge inputs, which could be surprising to the
93 # letting fixer tools choke on huge inputs, which could be surprising to the
94 # user.
94 # user.
95 configitem('fix', 'maxfilesize', default='2MB')
95 configitem('fix', 'maxfilesize', default='2MB')
96
96
97 @command('fix',
97 @command('fix',
98 [('', 'base', [], _('revisions to diff against (overrides automatic '
98 [('', 'all', False, _('fix all non-public non-obsolete revisions')),
99 ('', 'base', [], _('revisions to diff against (overrides automatic '
99 'selection, and applies to every revision being '
100 'selection, and applies to every revision being '
100 'fixed)'), _('REV')),
101 'fixed)'), _('REV')),
101 ('r', 'rev', [], _('revisions to fix'), _('REV')),
102 ('r', 'rev', [], _('revisions to fix'), _('REV')),
102 ('w', 'working-dir', False, _('fix the working directory')),
103 ('w', 'working-dir', False, _('fix the working directory')),
103 ('', 'whole', False, _('always fix every line of a file'))],
104 ('', 'whole', False, _('always fix every line of a file'))],
104 _('[OPTION]... [FILE]...'))
105 _('[OPTION]... [FILE]...'))
105 def fix(ui, repo, *pats, **opts):
106 def fix(ui, repo, *pats, **opts):
106 """rewrite file content in changesets or working directory
107 """rewrite file content in changesets or working directory
107
108
108 Runs any configured tools to fix the content of files. Only affects files
109 Runs any configured tools to fix the content of files. Only affects files
109 with changes, unless file arguments are provided. Only affects changed lines
110 with changes, unless file arguments are provided. Only affects changed lines
110 of files, unless the --whole flag is used. Some tools may always affect the
111 of files, unless the --whole flag is used. Some tools may always affect the
111 whole file regardless of --whole.
112 whole file regardless of --whole.
112
113
113 If revisions are specified with --rev, those revisions will be checked, and
114 If revisions are specified with --rev, those revisions will be checked, and
114 they may be replaced with new revisions that have fixed file content. It is
115 they may be replaced with new revisions that have fixed file content. It is
115 desirable to specify all descendants of each specified revision, so that the
116 desirable to specify all descendants of each specified revision, so that the
116 fixes propagate to the descendants. If all descendants are fixed at the same
117 fixes propagate to the descendants. If all descendants are fixed at the same
117 time, no merging, rebasing, or evolution will be required.
118 time, no merging, rebasing, or evolution will be required.
118
119
119 If --working-dir is used, files with uncommitted changes in the working copy
120 If --working-dir is used, files with uncommitted changes in the working copy
120 will be fixed. If the checked-out revision is also fixed, the working
121 will be fixed. If the checked-out revision is also fixed, the working
121 directory will update to the replacement revision.
122 directory will update to the replacement revision.
122
123
123 When determining what lines of each file to fix at each revision, the whole
124 When determining what lines of each file to fix at each revision, the whole
124 set of revisions being fixed is considered, so that fixes to earlier
125 set of revisions being fixed is considered, so that fixes to earlier
125 revisions are not forgotten in later ones. The --base flag can be used to
126 revisions are not forgotten in later ones. The --base flag can be used to
126 override this default behavior, though it is not usually desirable to do so.
127 override this default behavior, though it is not usually desirable to do so.
127 """
128 """
129 if opts['all']:
130 if opts['rev']:
131 raise error.Abort(_('cannot specify both "--rev" and "--all"'))
132 opts['rev'] = ['not public() and not obsolete()']
133 opts['working_dir'] = True
128 with repo.wlock(), repo.lock():
134 with repo.wlock(), repo.lock():
129 revstofix = getrevstofix(ui, repo, opts)
135 revstofix = getrevstofix(ui, repo, opts)
130 basectxs = getbasectxs(repo, opts, revstofix)
136 basectxs = getbasectxs(repo, opts, revstofix)
131 workqueue, numitems = getworkqueue(ui, repo, pats, opts, revstofix,
137 workqueue, numitems = getworkqueue(ui, repo, pats, opts, revstofix,
132 basectxs)
138 basectxs)
133 filedata = collections.defaultdict(dict)
139 filedata = collections.defaultdict(dict)
134 replacements = {}
140 replacements = {}
135 fixers = getfixers(ui)
141 fixers = getfixers(ui)
136 # Some day this loop can become a worker pool, but for now it's easier
142 # Some day this loop can become a worker pool, but for now it's easier
137 # to fix everything serially in topological order.
143 # to fix everything serially in topological order.
138 for rev, path in sorted(workqueue):
144 for rev, path in sorted(workqueue):
139 ctx = repo[rev]
145 ctx = repo[rev]
140 olddata = ctx[path].data()
146 olddata = ctx[path].data()
141 newdata = fixfile(ui, opts, fixers, ctx, path, basectxs[rev])
147 newdata = fixfile(ui, opts, fixers, ctx, path, basectxs[rev])
142 if newdata != olddata:
148 if newdata != olddata:
143 filedata[rev][path] = newdata
149 filedata[rev][path] = newdata
144 numitems[rev] -= 1
150 numitems[rev] -= 1
145 if not numitems[rev]:
151 if not numitems[rev]:
146 if rev == wdirrev:
152 if rev == wdirrev:
147 writeworkingdir(repo, ctx, filedata[rev], replacements)
153 writeworkingdir(repo, ctx, filedata[rev], replacements)
148 else:
154 else:
149 replacerev(ui, repo, ctx, filedata[rev], replacements)
155 replacerev(ui, repo, ctx, filedata[rev], replacements)
150 del filedata[rev]
156 del filedata[rev]
151
157
152 replacements = {prec: [succ] for prec, succ in replacements.iteritems()}
158 replacements = {prec: [succ] for prec, succ in replacements.iteritems()}
153 scmutil.cleanupnodes(repo, replacements, 'fix')
159 scmutil.cleanupnodes(repo, replacements, 'fix')
154
160
155 def getworkqueue(ui, repo, pats, opts, revstofix, basectxs):
161 def getworkqueue(ui, repo, pats, opts, revstofix, basectxs):
156 """"Constructs the list of files to be fixed at specific revisions
162 """"Constructs the list of files to be fixed at specific revisions
157
163
158 It is up to the caller how to consume the work items, and the only
164 It is up to the caller how to consume the work items, and the only
159 dependence between them is that replacement revisions must be committed in
165 dependence between them is that replacement revisions must be committed in
160 topological order. Each work item represents a file in the working copy or
166 topological order. Each work item represents a file in the working copy or
161 in some revision that should be fixed and written back to the working copy
167 in some revision that should be fixed and written back to the working copy
162 or into a replacement revision.
168 or into a replacement revision.
163 """
169 """
164 workqueue = []
170 workqueue = []
165 numitems = collections.defaultdict(int)
171 numitems = collections.defaultdict(int)
166 maxfilesize = ui.configbytes('fix', 'maxfilesize')
172 maxfilesize = ui.configbytes('fix', 'maxfilesize')
167 for rev in revstofix:
173 for rev in revstofix:
168 fixctx = repo[rev]
174 fixctx = repo[rev]
169 match = scmutil.match(fixctx, pats, opts)
175 match = scmutil.match(fixctx, pats, opts)
170 for path in pathstofix(ui, repo, pats, opts, match, basectxs[rev],
176 for path in pathstofix(ui, repo, pats, opts, match, basectxs[rev],
171 fixctx):
177 fixctx):
172 if path not in fixctx:
178 if path not in fixctx:
173 continue
179 continue
174 fctx = fixctx[path]
180 fctx = fixctx[path]
175 if fctx.islink():
181 if fctx.islink():
176 continue
182 continue
177 if fctx.size() > maxfilesize:
183 if fctx.size() > maxfilesize:
178 ui.warn(_('ignoring file larger than %s: %s\n') %
184 ui.warn(_('ignoring file larger than %s: %s\n') %
179 (util.bytecount(maxfilesize), path))
185 (util.bytecount(maxfilesize), path))
180 continue
186 continue
181 workqueue.append((rev, path))
187 workqueue.append((rev, path))
182 numitems[rev] += 1
188 numitems[rev] += 1
183 return workqueue, numitems
189 return workqueue, numitems
184
190
185 def getrevstofix(ui, repo, opts):
191 def getrevstofix(ui, repo, opts):
186 """Returns the set of revision numbers that should be fixed"""
192 """Returns the set of revision numbers that should be fixed"""
187 revs = set(scmutil.revrange(repo, opts['rev']))
193 revs = set(scmutil.revrange(repo, opts['rev']))
188 for rev in revs:
194 for rev in revs:
189 checkfixablectx(ui, repo, repo[rev])
195 checkfixablectx(ui, repo, repo[rev])
190 if revs:
196 if revs:
191 cmdutil.checkunfinished(repo)
197 cmdutil.checkunfinished(repo)
192 checknodescendants(repo, revs)
198 checknodescendants(repo, revs)
193 if opts.get('working_dir'):
199 if opts.get('working_dir'):
194 revs.add(wdirrev)
200 revs.add(wdirrev)
195 if list(merge.mergestate.read(repo).unresolved()):
201 if list(merge.mergestate.read(repo).unresolved()):
196 raise error.Abort('unresolved conflicts', hint="use 'hg resolve'")
202 raise error.Abort('unresolved conflicts', hint="use 'hg resolve'")
197 if not revs:
203 if not revs:
198 raise error.Abort(
204 raise error.Abort(
199 'no changesets specified', hint='use --rev or --working-dir')
205 'no changesets specified', hint='use --rev or --working-dir')
200 return revs
206 return revs
201
207
202 def checknodescendants(repo, revs):
208 def checknodescendants(repo, revs):
203 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
209 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
204 repo.revs('(%ld::) - (%ld)', revs, revs)):
210 repo.revs('(%ld::) - (%ld)', revs, revs)):
205 raise error.Abort(_('can only fix a changeset together '
211 raise error.Abort(_('can only fix a changeset together '
206 'with all its descendants'))
212 'with all its descendants'))
207
213
208 def checkfixablectx(ui, repo, ctx):
214 def checkfixablectx(ui, repo, ctx):
209 """Aborts if the revision shouldn't be replaced with a fixed one."""
215 """Aborts if the revision shouldn't be replaced with a fixed one."""
210 if not ctx.mutable():
216 if not ctx.mutable():
211 raise error.Abort('can\'t fix immutable changeset %s' %
217 raise error.Abort('can\'t fix immutable changeset %s' %
212 (scmutil.formatchangeid(ctx),))
218 (scmutil.formatchangeid(ctx),))
213 if ctx.obsolete():
219 if ctx.obsolete():
214 # It would be better to actually check if the revision has a successor.
220 # It would be better to actually check if the revision has a successor.
215 allowdivergence = ui.configbool('experimental',
221 allowdivergence = ui.configbool('experimental',
216 'evolution.allowdivergence')
222 'evolution.allowdivergence')
217 if not allowdivergence:
223 if not allowdivergence:
218 raise error.Abort('fixing obsolete revision could cause divergence')
224 raise error.Abort('fixing obsolete revision could cause divergence')
219
225
220 def pathstofix(ui, repo, pats, opts, match, basectxs, fixctx):
226 def pathstofix(ui, repo, pats, opts, match, basectxs, fixctx):
221 """Returns the set of files that should be fixed in a context
227 """Returns the set of files that should be fixed in a context
222
228
223 The result depends on the base contexts; we include any file that has
229 The result depends on the base contexts; we include any file that has
224 changed relative to any of the base contexts. Base contexts should be
230 changed relative to any of the base contexts. Base contexts should be
225 ancestors of the context being fixed.
231 ancestors of the context being fixed.
226 """
232 """
227 files = set()
233 files = set()
228 for basectx in basectxs:
234 for basectx in basectxs:
229 stat = repo.status(
235 stat = repo.status(
230 basectx, fixctx, match=match, clean=bool(pats), unknown=bool(pats))
236 basectx, fixctx, match=match, clean=bool(pats), unknown=bool(pats))
231 files.update(
237 files.update(
232 set(itertools.chain(stat.added, stat.modified, stat.clean,
238 set(itertools.chain(stat.added, stat.modified, stat.clean,
233 stat.unknown)))
239 stat.unknown)))
234 return files
240 return files
235
241
236 def lineranges(opts, path, basectxs, fixctx, content2):
242 def lineranges(opts, path, basectxs, fixctx, content2):
237 """Returns the set of line ranges that should be fixed in a file
243 """Returns the set of line ranges that should be fixed in a file
238
244
239 Of the form [(10, 20), (30, 40)].
245 Of the form [(10, 20), (30, 40)].
240
246
241 This depends on the given base contexts; we must consider lines that have
247 This depends on the given base contexts; we must consider lines that have
242 changed versus any of the base contexts, and whether the file has been
248 changed versus any of the base contexts, and whether the file has been
243 renamed versus any of them.
249 renamed versus any of them.
244
250
245 Another way to understand this is that we exclude line ranges that are
251 Another way to understand this is that we exclude line ranges that are
246 common to the file in all base contexts.
252 common to the file in all base contexts.
247 """
253 """
248 if opts.get('whole'):
254 if opts.get('whole'):
249 # Return a range containing all lines. Rely on the diff implementation's
255 # Return a range containing all lines. Rely on the diff implementation's
250 # idea of how many lines are in the file, instead of reimplementing it.
256 # idea of how many lines are in the file, instead of reimplementing it.
251 return difflineranges('', content2)
257 return difflineranges('', content2)
252
258
253 rangeslist = []
259 rangeslist = []
254 for basectx in basectxs:
260 for basectx in basectxs:
255 basepath = copies.pathcopies(basectx, fixctx).get(path, path)
261 basepath = copies.pathcopies(basectx, fixctx).get(path, path)
256 if basepath in basectx:
262 if basepath in basectx:
257 content1 = basectx[basepath].data()
263 content1 = basectx[basepath].data()
258 else:
264 else:
259 content1 = ''
265 content1 = ''
260 rangeslist.extend(difflineranges(content1, content2))
266 rangeslist.extend(difflineranges(content1, content2))
261 return unionranges(rangeslist)
267 return unionranges(rangeslist)
262
268
263 def unionranges(rangeslist):
269 def unionranges(rangeslist):
264 """Return the union of some closed intervals
270 """Return the union of some closed intervals
265
271
266 >>> unionranges([])
272 >>> unionranges([])
267 []
273 []
268 >>> unionranges([(1, 100)])
274 >>> unionranges([(1, 100)])
269 [(1, 100)]
275 [(1, 100)]
270 >>> unionranges([(1, 100), (1, 100)])
276 >>> unionranges([(1, 100), (1, 100)])
271 [(1, 100)]
277 [(1, 100)]
272 >>> unionranges([(1, 100), (2, 100)])
278 >>> unionranges([(1, 100), (2, 100)])
273 [(1, 100)]
279 [(1, 100)]
274 >>> unionranges([(1, 99), (1, 100)])
280 >>> unionranges([(1, 99), (1, 100)])
275 [(1, 100)]
281 [(1, 100)]
276 >>> unionranges([(1, 100), (40, 60)])
282 >>> unionranges([(1, 100), (40, 60)])
277 [(1, 100)]
283 [(1, 100)]
278 >>> unionranges([(1, 49), (50, 100)])
284 >>> unionranges([(1, 49), (50, 100)])
279 [(1, 100)]
285 [(1, 100)]
280 >>> unionranges([(1, 48), (50, 100)])
286 >>> unionranges([(1, 48), (50, 100)])
281 [(1, 48), (50, 100)]
287 [(1, 48), (50, 100)]
282 >>> unionranges([(1, 2), (3, 4), (5, 6)])
288 >>> unionranges([(1, 2), (3, 4), (5, 6)])
283 [(1, 6)]
289 [(1, 6)]
284 """
290 """
285 rangeslist = sorted(set(rangeslist))
291 rangeslist = sorted(set(rangeslist))
286 unioned = []
292 unioned = []
287 if rangeslist:
293 if rangeslist:
288 unioned, rangeslist = [rangeslist[0]], rangeslist[1:]
294 unioned, rangeslist = [rangeslist[0]], rangeslist[1:]
289 for a, b in rangeslist:
295 for a, b in rangeslist:
290 c, d = unioned[-1]
296 c, d = unioned[-1]
291 if a > d + 1:
297 if a > d + 1:
292 unioned.append((a, b))
298 unioned.append((a, b))
293 else:
299 else:
294 unioned[-1] = (c, max(b, d))
300 unioned[-1] = (c, max(b, d))
295 return unioned
301 return unioned
296
302
297 def difflineranges(content1, content2):
303 def difflineranges(content1, content2):
298 """Return list of line number ranges in content2 that differ from content1.
304 """Return list of line number ranges in content2 that differ from content1.
299
305
300 Line numbers are 1-based. The numbers are the first and last line contained
306 Line numbers are 1-based. The numbers are the first and last line contained
301 in the range. Single-line ranges have the same line number for the first and
307 in the range. Single-line ranges have the same line number for the first and
302 last line. Excludes any empty ranges that result from lines that are only
308 last line. Excludes any empty ranges that result from lines that are only
303 present in content1. Relies on mdiff's idea of where the line endings are in
309 present in content1. Relies on mdiff's idea of where the line endings are in
304 the string.
310 the string.
305
311
306 >>> from mercurial import pycompat
312 >>> from mercurial import pycompat
307 >>> lines = lambda s: b'\\n'.join([c for c in pycompat.iterbytestr(s)])
313 >>> lines = lambda s: b'\\n'.join([c for c in pycompat.iterbytestr(s)])
308 >>> difflineranges2 = lambda a, b: difflineranges(lines(a), lines(b))
314 >>> difflineranges2 = lambda a, b: difflineranges(lines(a), lines(b))
309 >>> difflineranges2(b'', b'')
315 >>> difflineranges2(b'', b'')
310 []
316 []
311 >>> difflineranges2(b'a', b'')
317 >>> difflineranges2(b'a', b'')
312 []
318 []
313 >>> difflineranges2(b'', b'A')
319 >>> difflineranges2(b'', b'A')
314 [(1, 1)]
320 [(1, 1)]
315 >>> difflineranges2(b'a', b'a')
321 >>> difflineranges2(b'a', b'a')
316 []
322 []
317 >>> difflineranges2(b'a', b'A')
323 >>> difflineranges2(b'a', b'A')
318 [(1, 1)]
324 [(1, 1)]
319 >>> difflineranges2(b'ab', b'')
325 >>> difflineranges2(b'ab', b'')
320 []
326 []
321 >>> difflineranges2(b'', b'AB')
327 >>> difflineranges2(b'', b'AB')
322 [(1, 2)]
328 [(1, 2)]
323 >>> difflineranges2(b'abc', b'ac')
329 >>> difflineranges2(b'abc', b'ac')
324 []
330 []
325 >>> difflineranges2(b'ab', b'aCb')
331 >>> difflineranges2(b'ab', b'aCb')
326 [(2, 2)]
332 [(2, 2)]
327 >>> difflineranges2(b'abc', b'aBc')
333 >>> difflineranges2(b'abc', b'aBc')
328 [(2, 2)]
334 [(2, 2)]
329 >>> difflineranges2(b'ab', b'AB')
335 >>> difflineranges2(b'ab', b'AB')
330 [(1, 2)]
336 [(1, 2)]
331 >>> difflineranges2(b'abcde', b'aBcDe')
337 >>> difflineranges2(b'abcde', b'aBcDe')
332 [(2, 2), (4, 4)]
338 [(2, 2), (4, 4)]
333 >>> difflineranges2(b'abcde', b'aBCDe')
339 >>> difflineranges2(b'abcde', b'aBCDe')
334 [(2, 4)]
340 [(2, 4)]
335 """
341 """
336 ranges = []
342 ranges = []
337 for lines, kind in mdiff.allblocks(content1, content2):
343 for lines, kind in mdiff.allblocks(content1, content2):
338 firstline, lastline = lines[2:4]
344 firstline, lastline = lines[2:4]
339 if kind == '!' and firstline != lastline:
345 if kind == '!' and firstline != lastline:
340 ranges.append((firstline + 1, lastline))
346 ranges.append((firstline + 1, lastline))
341 return ranges
347 return ranges
342
348
343 def getbasectxs(repo, opts, revstofix):
349 def getbasectxs(repo, opts, revstofix):
344 """Returns a map of the base contexts for each revision
350 """Returns a map of the base contexts for each revision
345
351
346 The base contexts determine which lines are considered modified when we
352 The base contexts determine which lines are considered modified when we
347 attempt to fix just the modified lines in a file.
353 attempt to fix just the modified lines in a file.
348 """
354 """
349 # The --base flag overrides the usual logic, and we give every revision
355 # The --base flag overrides the usual logic, and we give every revision
350 # exactly the set of baserevs that the user specified.
356 # exactly the set of baserevs that the user specified.
351 if opts.get('base'):
357 if opts.get('base'):
352 baserevs = set(scmutil.revrange(repo, opts.get('base')))
358 baserevs = set(scmutil.revrange(repo, opts.get('base')))
353 if not baserevs:
359 if not baserevs:
354 baserevs = {nullrev}
360 baserevs = {nullrev}
355 basectxs = {repo[rev] for rev in baserevs}
361 basectxs = {repo[rev] for rev in baserevs}
356 return {rev: basectxs for rev in revstofix}
362 return {rev: basectxs for rev in revstofix}
357
363
358 # Proceed in topological order so that we can easily determine each
364 # Proceed in topological order so that we can easily determine each
359 # revision's baserevs by looking at its parents and their baserevs.
365 # revision's baserevs by looking at its parents and their baserevs.
360 basectxs = collections.defaultdict(set)
366 basectxs = collections.defaultdict(set)
361 for rev in sorted(revstofix):
367 for rev in sorted(revstofix):
362 ctx = repo[rev]
368 ctx = repo[rev]
363 for pctx in ctx.parents():
369 for pctx in ctx.parents():
364 if pctx.rev() in basectxs:
370 if pctx.rev() in basectxs:
365 basectxs[rev].update(basectxs[pctx.rev()])
371 basectxs[rev].update(basectxs[pctx.rev()])
366 else:
372 else:
367 basectxs[rev].add(pctx)
373 basectxs[rev].add(pctx)
368 return basectxs
374 return basectxs
369
375
370 def fixfile(ui, opts, fixers, fixctx, path, basectxs):
376 def fixfile(ui, opts, fixers, fixctx, path, basectxs):
371 """Run any configured fixers that should affect the file in this context
377 """Run any configured fixers that should affect the file in this context
372
378
373 Returns the file content that results from applying the fixers in some order
379 Returns the file content that results from applying the fixers in some order
374 starting with the file's content in the fixctx. Fixers that support line
380 starting with the file's content in the fixctx. Fixers that support line
375 ranges will affect lines that have changed relative to any of the basectxs
381 ranges will affect lines that have changed relative to any of the basectxs
376 (i.e. they will only avoid lines that are common to all basectxs).
382 (i.e. they will only avoid lines that are common to all basectxs).
377 """
383 """
378 newdata = fixctx[path].data()
384 newdata = fixctx[path].data()
379 for fixername, fixer in fixers.iteritems():
385 for fixername, fixer in fixers.iteritems():
380 if fixer.affects(opts, fixctx, path):
386 if fixer.affects(opts, fixctx, path):
381 ranges = lineranges(opts, path, basectxs, fixctx, newdata)
387 ranges = lineranges(opts, path, basectxs, fixctx, newdata)
382 command = fixer.command(path, ranges)
388 command = fixer.command(path, ranges)
383 if command is None:
389 if command is None:
384 continue
390 continue
385 ui.debug('subprocess: %s\n' % (command,))
391 ui.debug('subprocess: %s\n' % (command,))
386 proc = subprocess.Popen(
392 proc = subprocess.Popen(
387 command,
393 command,
388 shell=True,
394 shell=True,
389 cwd='/',
395 cwd='/',
390 stdin=subprocess.PIPE,
396 stdin=subprocess.PIPE,
391 stdout=subprocess.PIPE,
397 stdout=subprocess.PIPE,
392 stderr=subprocess.PIPE)
398 stderr=subprocess.PIPE)
393 newerdata, stderr = proc.communicate(newdata)
399 newerdata, stderr = proc.communicate(newdata)
394 if stderr:
400 if stderr:
395 showstderr(ui, fixctx.rev(), fixername, stderr)
401 showstderr(ui, fixctx.rev(), fixername, stderr)
396 else:
402 else:
397 newdata = newerdata
403 newdata = newerdata
398 return newdata
404 return newdata
399
405
400 def showstderr(ui, rev, fixername, stderr):
406 def showstderr(ui, rev, fixername, stderr):
401 """Writes the lines of the stderr string as warnings on the ui
407 """Writes the lines of the stderr string as warnings on the ui
402
408
403 Uses the revision number and fixername to give more context to each line of
409 Uses the revision number and fixername to give more context to each line of
404 the error message. Doesn't include file names, since those take up a lot of
410 the error message. Doesn't include file names, since those take up a lot of
405 space and would tend to be included in the error message if they were
411 space and would tend to be included in the error message if they were
406 relevant.
412 relevant.
407 """
413 """
408 for line in re.split('[\r\n]+', stderr):
414 for line in re.split('[\r\n]+', stderr):
409 if line:
415 if line:
410 ui.warn(('['))
416 ui.warn(('['))
411 if rev is None:
417 if rev is None:
412 ui.warn(_('wdir'), label='evolve.rev')
418 ui.warn(_('wdir'), label='evolve.rev')
413 else:
419 else:
414 ui.warn((str(rev)), label='evolve.rev')
420 ui.warn((str(rev)), label='evolve.rev')
415 ui.warn(('] %s: %s\n') % (fixername, line))
421 ui.warn(('] %s: %s\n') % (fixername, line))
416
422
417 def writeworkingdir(repo, ctx, filedata, replacements):
423 def writeworkingdir(repo, ctx, filedata, replacements):
418 """Write new content to the working copy and check out the new p1 if any
424 """Write new content to the working copy and check out the new p1 if any
419
425
420 We check out a new revision if and only if we fixed something in both the
426 We check out a new revision if and only if we fixed something in both the
421 working directory and its parent revision. This avoids the need for a full
427 working directory and its parent revision. This avoids the need for a full
422 update/merge, and means that the working directory simply isn't affected
428 update/merge, and means that the working directory simply isn't affected
423 unless the --working-dir flag is given.
429 unless the --working-dir flag is given.
424
430
425 Directly updates the dirstate for the affected files.
431 Directly updates the dirstate for the affected files.
426 """
432 """
427 for path, data in filedata.iteritems():
433 for path, data in filedata.iteritems():
428 fctx = ctx[path]
434 fctx = ctx[path]
429 fctx.write(data, fctx.flags())
435 fctx.write(data, fctx.flags())
430 if repo.dirstate[path] == 'n':
436 if repo.dirstate[path] == 'n':
431 repo.dirstate.normallookup(path)
437 repo.dirstate.normallookup(path)
432
438
433 oldparentnodes = repo.dirstate.parents()
439 oldparentnodes = repo.dirstate.parents()
434 newparentnodes = [replacements.get(n, n) for n in oldparentnodes]
440 newparentnodes = [replacements.get(n, n) for n in oldparentnodes]
435 if newparentnodes != oldparentnodes:
441 if newparentnodes != oldparentnodes:
436 repo.setparents(*newparentnodes)
442 repo.setparents(*newparentnodes)
437
443
438 def replacerev(ui, repo, ctx, filedata, replacements):
444 def replacerev(ui, repo, ctx, filedata, replacements):
439 """Commit a new revision like the given one, but with file content changes
445 """Commit a new revision like the given one, but with file content changes
440
446
441 "ctx" is the original revision to be replaced by a modified one.
447 "ctx" is the original revision to be replaced by a modified one.
442
448
443 "filedata" is a dict that maps paths to their new file content. All other
449 "filedata" is a dict that maps paths to their new file content. All other
444 paths will be recreated from the original revision without changes.
450 paths will be recreated from the original revision without changes.
445 "filedata" may contain paths that didn't exist in the original revision;
451 "filedata" may contain paths that didn't exist in the original revision;
446 they will be added.
452 they will be added.
447
453
448 "replacements" is a dict that maps a single node to a single node, and it is
454 "replacements" is a dict that maps a single node to a single node, and it is
449 updated to indicate the original revision is replaced by the newly created
455 updated to indicate the original revision is replaced by the newly created
450 one. No entry is added if the replacement's node already exists.
456 one. No entry is added if the replacement's node already exists.
451
457
452 The new revision has the same parents as the old one, unless those parents
458 The new revision has the same parents as the old one, unless those parents
453 have already been replaced, in which case those replacements are the parents
459 have already been replaced, in which case those replacements are the parents
454 of this new revision. Thus, if revisions are replaced in topological order,
460 of this new revision. Thus, if revisions are replaced in topological order,
455 there is no need to rebase them into the original topology later.
461 there is no need to rebase them into the original topology later.
456 """
462 """
457
463
458 p1rev, p2rev = repo.changelog.parentrevs(ctx.rev())
464 p1rev, p2rev = repo.changelog.parentrevs(ctx.rev())
459 p1ctx, p2ctx = repo[p1rev], repo[p2rev]
465 p1ctx, p2ctx = repo[p1rev], repo[p2rev]
460 newp1node = replacements.get(p1ctx.node(), p1ctx.node())
466 newp1node = replacements.get(p1ctx.node(), p1ctx.node())
461 newp2node = replacements.get(p2ctx.node(), p2ctx.node())
467 newp2node = replacements.get(p2ctx.node(), p2ctx.node())
462
468
463 def filectxfn(repo, memctx, path):
469 def filectxfn(repo, memctx, path):
464 if path not in ctx:
470 if path not in ctx:
465 return None
471 return None
466 fctx = ctx[path]
472 fctx = ctx[path]
467 copied = fctx.renamed()
473 copied = fctx.renamed()
468 if copied:
474 if copied:
469 copied = copied[0]
475 copied = copied[0]
470 return context.memfilectx(
476 return context.memfilectx(
471 repo,
477 repo,
472 memctx,
478 memctx,
473 path=fctx.path(),
479 path=fctx.path(),
474 data=filedata.get(path, fctx.data()),
480 data=filedata.get(path, fctx.data()),
475 islink=fctx.islink(),
481 islink=fctx.islink(),
476 isexec=fctx.isexec(),
482 isexec=fctx.isexec(),
477 copied=copied)
483 copied=copied)
478
484
479 overrides = {('phases', 'new-commit'): ctx.phase()}
485 overrides = {('phases', 'new-commit'): ctx.phase()}
480 with ui.configoverride(overrides, source='fix'):
486 with ui.configoverride(overrides, source='fix'):
481 memctx = context.memctx(
487 memctx = context.memctx(
482 repo,
488 repo,
483 parents=(newp1node, newp2node),
489 parents=(newp1node, newp2node),
484 text=ctx.description(),
490 text=ctx.description(),
485 files=set(ctx.files()) | set(filedata.keys()),
491 files=set(ctx.files()) | set(filedata.keys()),
486 filectxfn=filectxfn,
492 filectxfn=filectxfn,
487 user=ctx.user(),
493 user=ctx.user(),
488 date=ctx.date(),
494 date=ctx.date(),
489 extra=ctx.extra(),
495 extra=ctx.extra(),
490 branch=ctx.branch(),
496 branch=ctx.branch(),
491 editor=None)
497 editor=None)
492 sucnode = memctx.commit()
498 sucnode = memctx.commit()
493 prenode = ctx.node()
499 prenode = ctx.node()
494 if prenode == sucnode:
500 if prenode == sucnode:
495 ui.debug('node %s already existed\n' % (ctx.hex()))
501 ui.debug('node %s already existed\n' % (ctx.hex()))
496 else:
502 else:
497 replacements[ctx.node()] = sucnode
503 replacements[ctx.node()] = sucnode
498
504
499 def getfixers(ui):
505 def getfixers(ui):
500 """Returns a map of configured fixer tools indexed by their names
506 """Returns a map of configured fixer tools indexed by their names
501
507
502 Each value is a Fixer object with methods that implement the behavior of the
508 Each value is a Fixer object with methods that implement the behavior of the
503 fixer's config suboptions. Does not validate the config values.
509 fixer's config suboptions. Does not validate the config values.
504 """
510 """
505 result = {}
511 result = {}
506 for name in fixernames(ui):
512 for name in fixernames(ui):
507 result[name] = Fixer()
513 result[name] = Fixer()
508 attrs = ui.configsuboptions('fix', name)[1]
514 attrs = ui.configsuboptions('fix', name)[1]
509 for key in FIXER_ATTRS:
515 for key in FIXER_ATTRS:
510 setattr(result[name], '_' + key, attrs.get(key, ''))
516 setattr(result[name], '_' + key, attrs.get(key, ''))
511 return result
517 return result
512
518
513 def fixernames(ui):
519 def fixernames(ui):
514 """Returns the names of [fix] config options that have suboptions"""
520 """Returns the names of [fix] config options that have suboptions"""
515 names = set()
521 names = set()
516 for k, v in ui.configitems('fix'):
522 for k, v in ui.configitems('fix'):
517 if ':' in k:
523 if ':' in k:
518 names.add(k.split(':', 1)[0])
524 names.add(k.split(':', 1)[0])
519 return names
525 return names
520
526
521 class Fixer(object):
527 class Fixer(object):
522 """Wraps the raw config values for a fixer with methods"""
528 """Wraps the raw config values for a fixer with methods"""
523
529
524 def affects(self, opts, fixctx, path):
530 def affects(self, opts, fixctx, path):
525 """Should this fixer run on the file at the given path and context?"""
531 """Should this fixer run on the file at the given path and context?"""
526 return scmutil.match(fixctx, [self._fileset], opts)(path)
532 return scmutil.match(fixctx, [self._fileset], opts)(path)
527
533
528 def command(self, path, ranges):
534 def command(self, path, ranges):
529 """A shell command to use to invoke this fixer on the given file/lines
535 """A shell command to use to invoke this fixer on the given file/lines
530
536
531 May return None if there is no appropriate command to run for the given
537 May return None if there is no appropriate command to run for the given
532 parameters.
538 parameters.
533 """
539 """
534 parts = [self._command.format(rootpath=path,
540 parts = [self._command.format(rootpath=path,
535 basename=os.path.basename(path))]
541 basename=os.path.basename(path))]
536 if self._linerange:
542 if self._linerange:
537 if not ranges:
543 if not ranges:
538 # No line ranges to fix, so don't run the fixer.
544 # No line ranges to fix, so don't run the fixer.
539 return None
545 return None
540 for first, last in ranges:
546 for first, last in ranges:
541 parts.append(self._linerange.format(first=first, last=last))
547 parts.append(self._linerange.format(first=first, last=last))
542 return ' '.join(parts)
548 return ' '.join(parts)
@@ -1,268 +1,417
1 A script that implements uppercasing all letters in a file.
1 A script that implements uppercasing all letters in a file.
2
2
3 $ UPPERCASEPY="$TESTTMP/uppercase.py"
3 $ UPPERCASEPY="$TESTTMP/uppercase.py"
4 $ cat > $UPPERCASEPY <<EOF
4 $ cat > $UPPERCASEPY <<EOF
5 > import sys
5 > import sys
6 > from mercurial.utils.procutil import setbinary
6 > from mercurial.utils.procutil import setbinary
7 > setbinary(sys.stdin)
7 > setbinary(sys.stdin)
8 > setbinary(sys.stdout)
8 > setbinary(sys.stdout)
9 > sys.stdout.write(sys.stdin.read().upper())
9 > sys.stdout.write(sys.stdin.read().upper())
10 > EOF
10 > EOF
11 $ TESTLINES="foo\nbar\nbaz\n"
11 $ TESTLINES="foo\nbar\nbaz\n"
12 $ printf $TESTLINES | $PYTHON $UPPERCASEPY
12 $ printf $TESTLINES | $PYTHON $UPPERCASEPY
13 FOO
13 FOO
14 BAR
14 BAR
15 BAZ
15 BAZ
16
16
17 Tests for the fix extension's behavior around non-trivial history topologies.
17 Tests for the fix extension's behavior around non-trivial history topologies.
18 Looks for correct incremental fixing and reproduction of parent/child
18 Looks for correct incremental fixing and reproduction of parent/child
19 relationships. We indicate fixed file content by uppercasing it.
19 relationships. We indicate fixed file content by uppercasing it.
20
20
21 $ cat >> $HGRCPATH <<EOF
21 $ cat >> $HGRCPATH <<EOF
22 > [extensions]
22 > [extensions]
23 > fix =
23 > fix =
24 > [fix]
24 > [fix]
25 > uppercase-whole-file:command=$PYTHON $UPPERCASEPY
25 > uppercase-whole-file:command=$PYTHON $UPPERCASEPY
26 > uppercase-whole-file:fileset=set:**
26 > uppercase-whole-file:fileset=set:**
27 > EOF
27 > EOF
28
28
29 This tests the only behavior that should really be affected by obsolescence, so
29 This tests the only behavior that should really be affected by obsolescence, so
30 we'll test it with evolution off and on. This only changes the revision
30 we'll test it with evolution off and on. This only changes the revision
31 numbers, if all is well.
31 numbers, if all is well.
32
32
33 #testcases obsstore-off obsstore-on
33 #testcases obsstore-off obsstore-on
34 #if obsstore-on
34 #if obsstore-on
35 $ cat >> $HGRCPATH <<EOF
35 $ cat >> $HGRCPATH <<EOF
36 > [experimental]
36 > [experimental]
37 > evolution.createmarkers=True
37 > evolution.createmarkers=True
38 > evolution.allowunstable=True
38 > evolution.allowunstable=True
39 > EOF
39 > EOF
40 #endif
40 #endif
41
41
42 Setting up the test topology. Scroll down to see the graph produced. We make it
42 Setting up the test topology. Scroll down to see the graph produced. We make it
43 clear which files were modified in each revision. It's enough to test at the
43 clear which files were modified in each revision. It's enough to test at the
44 file granularity, because that demonstrates which baserevs were diffed against.
44 file granularity, because that demonstrates which baserevs were diffed against.
45 The computation of changed lines is orthogonal and tested separately.
45 The computation of changed lines is orthogonal and tested separately.
46
46
47 $ hg init repo
47 $ hg init repo
48 $ cd repo
48 $ cd repo
49
49
50 $ printf "aaaa\n" > a
50 $ printf "aaaa\n" > a
51 $ hg commit -Am "change A"
51 $ hg commit -Am "change A"
52 adding a
52 adding a
53 $ printf "bbbb\n" > b
53 $ printf "bbbb\n" > b
54 $ hg commit -Am "change B"
54 $ hg commit -Am "change B"
55 adding b
55 adding b
56 $ printf "cccc\n" > c
56 $ printf "cccc\n" > c
57 $ hg commit -Am "change C"
57 $ hg commit -Am "change C"
58 adding c
58 adding c
59 $ hg checkout 0
59 $ hg checkout 0
60 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
60 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
61 $ printf "dddd\n" > d
61 $ printf "dddd\n" > d
62 $ hg commit -Am "change D"
62 $ hg commit -Am "change D"
63 adding d
63 adding d
64 created new head
64 created new head
65 $ hg merge -r 2
65 $ hg merge -r 2
66 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
66 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
67 (branch merge, don't forget to commit)
67 (branch merge, don't forget to commit)
68 $ printf "eeee\n" > e
68 $ printf "eeee\n" > e
69 $ hg commit -Am "change E"
69 $ hg commit -Am "change E"
70 adding e
70 adding e
71 $ hg checkout 0
71 $ hg checkout 0
72 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
72 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
73 $ printf "ffff\n" > f
73 $ printf "ffff\n" > f
74 $ hg commit -Am "change F"
74 $ hg commit -Am "change F"
75 adding f
75 adding f
76 created new head
76 created new head
77 $ hg checkout 0
77 $ hg checkout 0
78 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
78 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
79 $ printf "gggg\n" > g
79 $ printf "gggg\n" > g
80 $ hg commit -Am "change G"
80 $ hg commit -Am "change G"
81 adding g
81 adding g
82 created new head
82 created new head
83 $ hg merge -r 5
83 $ hg merge -r 5
84 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
84 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
85 (branch merge, don't forget to commit)
85 (branch merge, don't forget to commit)
86 $ printf "hhhh\n" > h
86 $ printf "hhhh\n" > h
87 $ hg commit -Am "change H"
87 $ hg commit -Am "change H"
88 adding h
88 adding h
89 $ hg merge -r 4
89 $ hg merge -r 4
90 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
90 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
91 (branch merge, don't forget to commit)
91 (branch merge, don't forget to commit)
92 $ printf "iiii\n" > i
92 $ printf "iiii\n" > i
93 $ hg commit -Am "change I"
93 $ hg commit -Am "change I"
94 adding i
94 adding i
95 $ hg checkout 2
95 $ hg checkout 2
96 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
96 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
97 $ printf "jjjj\n" > j
97 $ printf "jjjj\n" > j
98 $ hg commit -Am "change J"
98 $ hg commit -Am "change J"
99 adding j
99 adding j
100 created new head
100 created new head
101 $ hg checkout 7
101 $ hg checkout 7
102 3 files updated, 0 files merged, 3 files removed, 0 files unresolved
102 3 files updated, 0 files merged, 3 files removed, 0 files unresolved
103 $ printf "kkkk\n" > k
103 $ printf "kkkk\n" > k
104 $ hg add
104 $ hg add
105 adding k
105 adding k
106
106
107 $ hg log --graph --template '{rev} {desc}\n'
107 $ hg log --graph --template '{rev} {desc}\n'
108 o 9 change J
108 o 9 change J
109 |
109 |
110 | o 8 change I
110 | o 8 change I
111 | |\
111 | |\
112 | | @ 7 change H
112 | | @ 7 change H
113 | | |\
113 | | |\
114 | | | o 6 change G
114 | | | o 6 change G
115 | | | |
115 | | | |
116 | | o | 5 change F
116 | | o | 5 change F
117 | | |/
117 | | |/
118 | o | 4 change E
118 | o | 4 change E
119 |/| |
119 |/| |
120 | o | 3 change D
120 | o | 3 change D
121 | |/
121 | |/
122 o | 2 change C
122 o | 2 change C
123 | |
123 | |
124 o | 1 change B
124 o | 1 change B
125 |/
125 |/
126 o 0 change A
126 o 0 change A
127
127
128
128
129 Fix all but the root revision and its four children.
129 Fix all but the root revision and its four children.
130
130
131 #if obsstore-on
131 #if obsstore-on
132 $ hg fix -r '2|4|7|8|9' --working-dir
132 $ hg fix -r '2|4|7|8|9' --working-dir
133 #else
133 #else
134 $ hg fix -r '2|4|7|8|9' --working-dir
134 $ hg fix -r '2|4|7|8|9' --working-dir
135 saved backup bundle to * (glob)
135 saved backup bundle to * (glob)
136 #endif
136 #endif
137
137
138 The five revisions remain, but the other revisions were fixed and replaced. All
138 The five revisions remain, but the other revisions were fixed and replaced. All
139 parent pointers have been accurately set to reproduce the previous topology
139 parent pointers have been accurately set to reproduce the previous topology
140 (though it is rendered in a slightly different order now).
140 (though it is rendered in a slightly different order now).
141
141
142 #if obsstore-on
142 #if obsstore-on
143 $ hg log --graph --template '{rev} {desc}\n'
143 $ hg log --graph --template '{rev} {desc}\n'
144 o 14 change J
144 o 14 change J
145 |
145 |
146 | o 13 change I
146 | o 13 change I
147 | |\
147 | |\
148 | | @ 12 change H
148 | | @ 12 change H
149 | | |\
149 | | |\
150 | o | | 11 change E
150 | o | | 11 change E
151 |/| | |
151 |/| | |
152 o | | | 10 change C
152 o | | | 10 change C
153 | | | |
153 | | | |
154 | | | o 6 change G
154 | | | o 6 change G
155 | | | |
155 | | | |
156 | | o | 5 change F
156 | | o | 5 change F
157 | | |/
157 | | |/
158 | o / 3 change D
158 | o / 3 change D
159 | |/
159 | |/
160 o / 1 change B
160 o / 1 change B
161 |/
161 |/
162 o 0 change A
162 o 0 change A
163
163
164 $ C=10
164 $ C=10
165 $ E=11
165 $ E=11
166 $ H=12
166 $ H=12
167 $ I=13
167 $ I=13
168 $ J=14
168 $ J=14
169 #else
169 #else
170 $ hg log --graph --template '{rev} {desc}\n'
170 $ hg log --graph --template '{rev} {desc}\n'
171 o 9 change J
171 o 9 change J
172 |
172 |
173 | o 8 change I
173 | o 8 change I
174 | |\
174 | |\
175 | | @ 7 change H
175 | | @ 7 change H
176 | | |\
176 | | |\
177 | o | | 6 change E
177 | o | | 6 change E
178 |/| | |
178 |/| | |
179 o | | | 5 change C
179 o | | | 5 change C
180 | | | |
180 | | | |
181 | | | o 4 change G
181 | | | o 4 change G
182 | | | |
182 | | | |
183 | | o | 3 change F
183 | | o | 3 change F
184 | | |/
184 | | |/
185 | o / 2 change D
185 | o / 2 change D
186 | |/
186 | |/
187 o / 1 change B
187 o / 1 change B
188 |/
188 |/
189 o 0 change A
189 o 0 change A
190
190
191 $ C=5
191 $ C=5
192 $ E=6
192 $ E=6
193 $ H=7
193 $ H=7
194 $ I=8
194 $ I=8
195 $ J=9
195 $ J=9
196 #endif
196 #endif
197
197
198 Change C is a root of the set being fixed, so all we fix is what has changed
198 Change C is a root of the set being fixed, so all we fix is what has changed
199 since its parent. That parent, change B, is its baserev.
199 since its parent. That parent, change B, is its baserev.
200
200
201 $ hg cat -r $C 'set:**'
201 $ hg cat -r $C 'set:**'
202 aaaa
202 aaaa
203 bbbb
203 bbbb
204 CCCC
204 CCCC
205
205
206 Change E is a merge with only one parent being fixed. Its baserevs are the
206 Change E is a merge with only one parent being fixed. Its baserevs are the
207 unfixed parent plus the baserevs of the other parent. This evaluates to changes
207 unfixed parent plus the baserevs of the other parent. This evaluates to changes
208 B and D. We now have to decide what it means to incrementally fix a merge
208 B and D. We now have to decide what it means to incrementally fix a merge
209 commit. We choose to fix anything that has changed versus any baserev. Only the
209 commit. We choose to fix anything that has changed versus any baserev. Only the
210 undisturbed content of the common ancestor, change A, is unfixed.
210 undisturbed content of the common ancestor, change A, is unfixed.
211
211
212 $ hg cat -r $E 'set:**'
212 $ hg cat -r $E 'set:**'
213 aaaa
213 aaaa
214 BBBB
214 BBBB
215 CCCC
215 CCCC
216 DDDD
216 DDDD
217 EEEE
217 EEEE
218
218
219 Change H is a merge with neither parent being fixed. This is essentially
219 Change H is a merge with neither parent being fixed. This is essentially
220 equivalent to the previous case because there is still only one baserev for
220 equivalent to the previous case because there is still only one baserev for
221 each parent of the merge.
221 each parent of the merge.
222
222
223 $ hg cat -r $H 'set:**'
223 $ hg cat -r $H 'set:**'
224 aaaa
224 aaaa
225 FFFF
225 FFFF
226 GGGG
226 GGGG
227 HHHH
227 HHHH
228
228
229 Change I is a merge that has four baserevs; two from each parent. We handle
229 Change I is a merge that has four baserevs; two from each parent. We handle
230 multiple baserevs in the same way regardless of how many came from each parent.
230 multiple baserevs in the same way regardless of how many came from each parent.
231 So, fixing change H will fix any files that were not exactly the same in each
231 So, fixing change H will fix any files that were not exactly the same in each
232 baserev.
232 baserev.
233
233
234 $ hg cat -r $I 'set:**'
234 $ hg cat -r $I 'set:**'
235 aaaa
235 aaaa
236 BBBB
236 BBBB
237 CCCC
237 CCCC
238 DDDD
238 DDDD
239 EEEE
239 EEEE
240 FFFF
240 FFFF
241 GGGG
241 GGGG
242 HHHH
242 HHHH
243 IIII
243 IIII
244
244
245 Change J is a simple case with one baserev, but its baserev is not its parent,
245 Change J is a simple case with one baserev, but its baserev is not its parent,
246 change C. Its baserev is its grandparent, change B.
246 change C. Its baserev is its grandparent, change B.
247
247
248 $ hg cat -r $J 'set:**'
248 $ hg cat -r $J 'set:**'
249 aaaa
249 aaaa
250 bbbb
250 bbbb
251 CCCC
251 CCCC
252 JJJJ
252 JJJJ
253
253
254 The working copy was dirty, so it is treated much like a revision. The baserevs
254 The working copy was dirty, so it is treated much like a revision. The baserevs
255 for the working copy are inherited from its parent, change H, because it is
255 for the working copy are inherited from its parent, change H, because it is
256 also being fixed.
256 also being fixed.
257
257
258 $ cat *
258 $ cat *
259 aaaa
259 aaaa
260 FFFF
260 FFFF
261 GGGG
261 GGGG
262 HHHH
262 HHHH
263 KKKK
263 KKKK
264
264
265 Change A was never a baserev because none of its children were to be fixed.
265 Change A was never a baserev because none of its children were to be fixed.
266
266
267 $ cd ..
267 $ cd ..
268
268
269 The --all flag should fix anything that wouldn't cause a problem if you fixed
270 it, including the working copy. Obsolete revisions are not fixed because that
271 could cause divergence. Public revisions would cause an abort because they are
272 immutable. We can fix orphans because their successors are still just orphans
273 of the original obsolete parent. When obsolesence is off, we're just fixing and
274 replacing anything that isn't public.
275
276 $ hg init fixall
277 $ cd fixall
278
279 #if obsstore-on
280 $ printf "one\n" > foo.whole
281 $ hg commit -Aqm "first"
282 $ hg phase --public
283 $ hg tag --local root
284 $ printf "two\n" > foo.whole
285 $ hg commit -m "second"
286 $ printf "three\n" > foo.whole
287 $ hg commit -m "third" --secret
288 $ hg tag --local secret
289 $ hg checkout root
290 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
291 $ printf "four\n" > foo.whole
292 $ hg commit -m "fourth"
293 created new head
294 $ printf "five\n" > foo.whole
295 $ hg commit -m "fifth"
296 $ hg tag --local replaced
297 $ printf "six\n" > foo.whole
298 $ hg commit -m "sixth"
299 $ hg checkout replaced
300 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
301 $ printf "seven\n" > foo.whole
302 $ hg commit --amend
303 1 new orphan changesets
304 $ hg checkout secret
305 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
306 $ printf "uncommitted\n" > foo.whole
307
308 $ hg log --graph --template '{rev} {desc} {phase}\n'
309 o 6 fifth draft
310 |
311 | * 5 sixth draft
312 | |
313 | x 4 fifth draft
314 |/
315 o 3 fourth draft
316 |
317 | @ 2 third secret
318 | |
319 | o 1 second draft
320 |/
321 o 0 first public
322
323
324 $ hg fix --all
325 1 new orphan changesets
326
327 $ hg log --graph --template '{rev} {desc}\n' -r 'sort(all(), topo)' --hidden
328 o 11 fifth
329 |
330 o 9 fourth
331 |
332 | @ 8 third
333 | |
334 | o 7 second
335 |/
336 | * 10 sixth
337 | |
338 | | x 5 sixth
339 | |/
340 | x 4 fifth
341 | |
342 | | x 6 fifth
343 | |/
344 | x 3 fourth
345 |/
346 | x 2 third
347 | |
348 | x 1 second
349 |/
350 o 0 first
351
352
353 $ hg cat -r 7 foo.whole
354 TWO
355 $ hg cat -r 8 foo.whole
356 THREE
357 $ hg cat -r 9 foo.whole
358 FOUR
359 $ hg cat -r 10 foo.whole
360 SIX
361 $ hg cat -r 11 foo.whole
362 SEVEN
363 $ cat foo.whole
364 UNCOMMITTED
365 #else
366 $ printf "one\n" > foo.whole
367 $ hg commit -Aqm "first"
368 $ hg phase --public
369 $ hg tag --local root
370 $ printf "two\n" > foo.whole
371 $ hg commit -m "second"
372 $ printf "three\n" > foo.whole
373 $ hg commit -m "third" --secret
374 $ hg tag --local secret
375 $ hg checkout root
376 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
377 $ printf "four\n" > foo.whole
378 $ hg commit -m "fourth"
379 created new head
380 $ printf "uncommitted\n" > foo.whole
381
382 $ hg log --graph --template '{rev} {desc} {phase}\n'
383 @ 3 fourth draft
384 |
385 | o 2 third secret
386 | |
387 | o 1 second draft
388 |/
389 o 0 first public
390
391
392 $ hg fix --all
393 saved backup bundle to * (glob)
394
395 $ hg log --graph --template '{rev} {desc} {phase}\n'
396 @ 3 fourth draft
397 |
398 | o 2 third secret
399 | |
400 | o 1 second draft
401 |/
402 o 0 first public
403
404 $ hg cat -r 0 foo.whole
405 one
406 $ hg cat -r 1 foo.whole
407 TWO
408 $ hg cat -r 2 foo.whole
409 THREE
410 $ hg cat -r 3 foo.whole
411 FOUR
412 $ cat foo.whole
413 UNCOMMITTED
414 #endif
415
416 $ cd ..
417
@@ -1,1024 +1,1025
1 A script that implements uppercasing of specific lines in a file. This
1 A script that implements uppercasing of specific lines in a file. This
2 approximates the behavior of code formatters well enough for our tests.
2 approximates the behavior of code formatters well enough for our tests.
3
3
4 $ UPPERCASEPY="$TESTTMP/uppercase.py"
4 $ UPPERCASEPY="$TESTTMP/uppercase.py"
5 $ cat > $UPPERCASEPY <<EOF
5 $ cat > $UPPERCASEPY <<EOF
6 > import sys
6 > import sys
7 > from mercurial.utils.procutil import setbinary
7 > from mercurial.utils.procutil import setbinary
8 > setbinary(sys.stdin)
8 > setbinary(sys.stdin)
9 > setbinary(sys.stdout)
9 > setbinary(sys.stdout)
10 > lines = set()
10 > lines = set()
11 > for arg in sys.argv[1:]:
11 > for arg in sys.argv[1:]:
12 > if arg == 'all':
12 > if arg == 'all':
13 > sys.stdout.write(sys.stdin.read().upper())
13 > sys.stdout.write(sys.stdin.read().upper())
14 > sys.exit(0)
14 > sys.exit(0)
15 > else:
15 > else:
16 > first, last = arg.split('-')
16 > first, last = arg.split('-')
17 > lines.update(range(int(first), int(last) + 1))
17 > lines.update(range(int(first), int(last) + 1))
18 > for i, line in enumerate(sys.stdin.readlines()):
18 > for i, line in enumerate(sys.stdin.readlines()):
19 > if i + 1 in lines:
19 > if i + 1 in lines:
20 > sys.stdout.write(line.upper())
20 > sys.stdout.write(line.upper())
21 > else:
21 > else:
22 > sys.stdout.write(line)
22 > sys.stdout.write(line)
23 > EOF
23 > EOF
24 $ TESTLINES="foo\nbar\nbaz\nqux\n"
24 $ TESTLINES="foo\nbar\nbaz\nqux\n"
25 $ printf $TESTLINES | $PYTHON $UPPERCASEPY
25 $ printf $TESTLINES | $PYTHON $UPPERCASEPY
26 foo
26 foo
27 bar
27 bar
28 baz
28 baz
29 qux
29 qux
30 $ printf $TESTLINES | $PYTHON $UPPERCASEPY all
30 $ printf $TESTLINES | $PYTHON $UPPERCASEPY all
31 FOO
31 FOO
32 BAR
32 BAR
33 BAZ
33 BAZ
34 QUX
34 QUX
35 $ printf $TESTLINES | $PYTHON $UPPERCASEPY 1-1
35 $ printf $TESTLINES | $PYTHON $UPPERCASEPY 1-1
36 FOO
36 FOO
37 bar
37 bar
38 baz
38 baz
39 qux
39 qux
40 $ printf $TESTLINES | $PYTHON $UPPERCASEPY 1-2
40 $ printf $TESTLINES | $PYTHON $UPPERCASEPY 1-2
41 FOO
41 FOO
42 BAR
42 BAR
43 baz
43 baz
44 qux
44 qux
45 $ printf $TESTLINES | $PYTHON $UPPERCASEPY 2-3
45 $ printf $TESTLINES | $PYTHON $UPPERCASEPY 2-3
46 foo
46 foo
47 BAR
47 BAR
48 BAZ
48 BAZ
49 qux
49 qux
50 $ printf $TESTLINES | $PYTHON $UPPERCASEPY 2-2 4-4
50 $ printf $TESTLINES | $PYTHON $UPPERCASEPY 2-2 4-4
51 foo
51 foo
52 BAR
52 BAR
53 baz
53 baz
54 QUX
54 QUX
55
55
56 Set up the config with two simple fixers: one that fixes specific line ranges,
56 Set up the config with two simple fixers: one that fixes specific line ranges,
57 and one that always fixes the whole file. They both "fix" files by converting
57 and one that always fixes the whole file. They both "fix" files by converting
58 letters to uppercase. They use different file extensions, so each test case can
58 letters to uppercase. They use different file extensions, so each test case can
59 choose which behavior to use by naming files.
59 choose which behavior to use by naming files.
60
60
61 $ cat >> $HGRCPATH <<EOF
61 $ cat >> $HGRCPATH <<EOF
62 > [extensions]
62 > [extensions]
63 > fix =
63 > fix =
64 > [experimental]
64 > [experimental]
65 > evolution.createmarkers=True
65 > evolution.createmarkers=True
66 > evolution.allowunstable=True
66 > evolution.allowunstable=True
67 > [fix]
67 > [fix]
68 > uppercase-whole-file:command=$PYTHON $UPPERCASEPY all
68 > uppercase-whole-file:command=$PYTHON $UPPERCASEPY all
69 > uppercase-whole-file:fileset=set:**.whole
69 > uppercase-whole-file:fileset=set:**.whole
70 > uppercase-changed-lines:command=$PYTHON $UPPERCASEPY
70 > uppercase-changed-lines:command=$PYTHON $UPPERCASEPY
71 > uppercase-changed-lines:linerange={first}-{last}
71 > uppercase-changed-lines:linerange={first}-{last}
72 > uppercase-changed-lines:fileset=set:**.changed
72 > uppercase-changed-lines:fileset=set:**.changed
73 > EOF
73 > EOF
74
74
75 Help text for fix.
75 Help text for fix.
76
76
77 $ hg help fix
77 $ hg help fix
78 hg fix [OPTION]... [FILE]...
78 hg fix [OPTION]... [FILE]...
79
79
80 rewrite file content in changesets or working directory
80 rewrite file content in changesets or working directory
81
81
82 Runs any configured tools to fix the content of files. Only affects files
82 Runs any configured tools to fix the content of files. Only affects files
83 with changes, unless file arguments are provided. Only affects changed
83 with changes, unless file arguments are provided. Only affects changed
84 lines of files, unless the --whole flag is used. Some tools may always
84 lines of files, unless the --whole flag is used. Some tools may always
85 affect the whole file regardless of --whole.
85 affect the whole file regardless of --whole.
86
86
87 If revisions are specified with --rev, those revisions will be checked,
87 If revisions are specified with --rev, those revisions will be checked,
88 and they may be replaced with new revisions that have fixed file content.
88 and they may be replaced with new revisions that have fixed file content.
89 It is desirable to specify all descendants of each specified revision, so
89 It is desirable to specify all descendants of each specified revision, so
90 that the fixes propagate to the descendants. If all descendants are fixed
90 that the fixes propagate to the descendants. If all descendants are fixed
91 at the same time, no merging, rebasing, or evolution will be required.
91 at the same time, no merging, rebasing, or evolution will be required.
92
92
93 If --working-dir is used, files with uncommitted changes in the working
93 If --working-dir is used, files with uncommitted changes in the working
94 copy will be fixed. If the checked-out revision is also fixed, the working
94 copy will be fixed. If the checked-out revision is also fixed, the working
95 directory will update to the replacement revision.
95 directory will update to the replacement revision.
96
96
97 When determining what lines of each file to fix at each revision, the
97 When determining what lines of each file to fix at each revision, the
98 whole set of revisions being fixed is considered, so that fixes to earlier
98 whole set of revisions being fixed is considered, so that fixes to earlier
99 revisions are not forgotten in later ones. The --base flag can be used to
99 revisions are not forgotten in later ones. The --base flag can be used to
100 override this default behavior, though it is not usually desirable to do
100 override this default behavior, though it is not usually desirable to do
101 so.
101 so.
102
102
103 (use 'hg help -e fix' to show help for the fix extension)
103 (use 'hg help -e fix' to show help for the fix extension)
104
104
105 options ([+] can be repeated):
105 options ([+] can be repeated):
106
106
107 --all fix all non-public non-obsolete revisions
107 --base REV [+] revisions to diff against (overrides automatic selection,
108 --base REV [+] revisions to diff against (overrides automatic selection,
108 and applies to every revision being fixed)
109 and applies to every revision being fixed)
109 -r --rev REV [+] revisions to fix
110 -r --rev REV [+] revisions to fix
110 -w --working-dir fix the working directory
111 -w --working-dir fix the working directory
111 --whole always fix every line of a file
112 --whole always fix every line of a file
112
113
113 (some details hidden, use --verbose to show complete help)
114 (some details hidden, use --verbose to show complete help)
114
115
115 $ hg help -e fix
116 $ hg help -e fix
116 fix extension - rewrite file content in changesets or working copy
117 fix extension - rewrite file content in changesets or working copy
117 (EXPERIMENTAL)
118 (EXPERIMENTAL)
118
119
119 Provides a command that runs configured tools on the contents of modified
120 Provides a command that runs configured tools on the contents of modified
120 files, writing back any fixes to the working copy or replacing changesets.
121 files, writing back any fixes to the working copy or replacing changesets.
121
122
122 Here is an example configuration that causes 'hg fix' to apply automatic
123 Here is an example configuration that causes 'hg fix' to apply automatic
123 formatting fixes to modified lines in C++ code:
124 formatting fixes to modified lines in C++ code:
124
125
125 [fix]
126 [fix]
126 clang-format:command=clang-format --assume-filename={rootpath}
127 clang-format:command=clang-format --assume-filename={rootpath}
127 clang-format:linerange=--lines={first}:{last}
128 clang-format:linerange=--lines={first}:{last}
128 clang-format:fileset=set:**.cpp or **.hpp
129 clang-format:fileset=set:**.cpp or **.hpp
129
130
130 The :command suboption forms the first part of the shell command that will be
131 The :command suboption forms the first part of the shell command that will be
131 used to fix a file. The content of the file is passed on standard input, and
132 used to fix a file. The content of the file is passed on standard input, and
132 the fixed file content is expected on standard output. If there is any output
133 the fixed file content is expected on standard output. If there is any output
133 on standard error, the file will not be affected. Some values may be
134 on standard error, the file will not be affected. Some values may be
134 substituted into the command:
135 substituted into the command:
135
136
136 {rootpath} The path of the file being fixed, relative to the repo root
137 {rootpath} The path of the file being fixed, relative to the repo root
137 {basename} The name of the file being fixed, without the directory path
138 {basename} The name of the file being fixed, without the directory path
138
139
139 If the :linerange suboption is set, the tool will only be run if there are
140 If the :linerange suboption is set, the tool will only be run if there are
140 changed lines in a file. The value of this suboption is appended to the shell
141 changed lines in a file. The value of this suboption is appended to the shell
141 command once for every range of changed lines in the file. Some values may be
142 command once for every range of changed lines in the file. Some values may be
142 substituted into the command:
143 substituted into the command:
143
144
144 {first} The 1-based line number of the first line in the modified range
145 {first} The 1-based line number of the first line in the modified range
145 {last} The 1-based line number of the last line in the modified range
146 {last} The 1-based line number of the last line in the modified range
146
147
147 The :fileset suboption determines which files will be passed through each
148 The :fileset suboption determines which files will be passed through each
148 configured tool. See 'hg help fileset' for possible values. If there are file
149 configured tool. See 'hg help fileset' for possible values. If there are file
149 arguments to 'hg fix', the intersection of these filesets is used.
150 arguments to 'hg fix', the intersection of these filesets is used.
150
151
151 There is also a configurable limit for the maximum size of file that will be
152 There is also a configurable limit for the maximum size of file that will be
152 processed by 'hg fix':
153 processed by 'hg fix':
153
154
154 [fix]
155 [fix]
155 maxfilesize=2MB
156 maxfilesize=2MB
156
157
157 list of commands:
158 list of commands:
158
159
159 fix rewrite file content in changesets or working directory
160 fix rewrite file content in changesets or working directory
160
161
161 (use 'hg help -v -e fix' to show built-in aliases and global options)
162 (use 'hg help -v -e fix' to show built-in aliases and global options)
162
163
163 There is no default behavior in the absence of --rev and --working-dir.
164 There is no default behavior in the absence of --rev and --working-dir.
164
165
165 $ hg init badusage
166 $ hg init badusage
166 $ cd badusage
167 $ cd badusage
167
168
168 $ hg fix
169 $ hg fix
169 abort: no changesets specified
170 abort: no changesets specified
170 (use --rev or --working-dir)
171 (use --rev or --working-dir)
171 [255]
172 [255]
172 $ hg fix --whole
173 $ hg fix --whole
173 abort: no changesets specified
174 abort: no changesets specified
174 (use --rev or --working-dir)
175 (use --rev or --working-dir)
175 [255]
176 [255]
176 $ hg fix --base 0
177 $ hg fix --base 0
177 abort: no changesets specified
178 abort: no changesets specified
178 (use --rev or --working-dir)
179 (use --rev or --working-dir)
179 [255]
180 [255]
180
181
181 Fixing a public revision isn't allowed. It should abort early enough that
182 Fixing a public revision isn't allowed. It should abort early enough that
182 nothing happens, even to the working directory.
183 nothing happens, even to the working directory.
183
184
184 $ printf "hello\n" > hello.whole
185 $ printf "hello\n" > hello.whole
185 $ hg commit -Aqm "hello"
186 $ hg commit -Aqm "hello"
186 $ hg phase -r 0 --public
187 $ hg phase -r 0 --public
187 $ hg fix -r 0
188 $ hg fix -r 0
188 abort: can't fix immutable changeset 0:6470986d2e7b
189 abort: can't fix immutable changeset 0:6470986d2e7b
189 [255]
190 [255]
190 $ hg fix -r 0 --working-dir
191 $ hg fix -r 0 --working-dir
191 abort: can't fix immutable changeset 0:6470986d2e7b
192 abort: can't fix immutable changeset 0:6470986d2e7b
192 [255]
193 [255]
193 $ hg cat -r tip hello.whole
194 $ hg cat -r tip hello.whole
194 hello
195 hello
195 $ cat hello.whole
196 $ cat hello.whole
196 hello
197 hello
197
198
198 $ cd ..
199 $ cd ..
199
200
200 Fixing a clean working directory should do nothing. Even the --whole flag
201 Fixing a clean working directory should do nothing. Even the --whole flag
201 shouldn't cause any clean files to be fixed. Specifying a clean file explicitly
202 shouldn't cause any clean files to be fixed. Specifying a clean file explicitly
202 should only fix it if the fixer always fixes the whole file. The combination of
203 should only fix it if the fixer always fixes the whole file. The combination of
203 an explicit filename and --whole should format the entire file regardless.
204 an explicit filename and --whole should format the entire file regardless.
204
205
205 $ hg init fixcleanwdir
206 $ hg init fixcleanwdir
206 $ cd fixcleanwdir
207 $ cd fixcleanwdir
207
208
208 $ printf "hello\n" > hello.changed
209 $ printf "hello\n" > hello.changed
209 $ printf "world\n" > hello.whole
210 $ printf "world\n" > hello.whole
210 $ hg commit -Aqm "foo"
211 $ hg commit -Aqm "foo"
211 $ hg fix --working-dir
212 $ hg fix --working-dir
212 $ hg diff
213 $ hg diff
213 $ hg fix --working-dir --whole
214 $ hg fix --working-dir --whole
214 $ hg diff
215 $ hg diff
215 $ hg fix --working-dir *
216 $ hg fix --working-dir *
216 $ cat *
217 $ cat *
217 hello
218 hello
218 WORLD
219 WORLD
219 $ hg revert --all --no-backup
220 $ hg revert --all --no-backup
220 reverting hello.whole
221 reverting hello.whole
221 $ hg fix --working-dir * --whole
222 $ hg fix --working-dir * --whole
222 $ cat *
223 $ cat *
223 HELLO
224 HELLO
224 WORLD
225 WORLD
225
226
226 The same ideas apply to fixing a revision, so we create a revision that doesn't
227 The same ideas apply to fixing a revision, so we create a revision that doesn't
227 modify either of the files in question and try fixing it. This also tests that
228 modify either of the files in question and try fixing it. This also tests that
228 we ignore a file that doesn't match any configured fixer.
229 we ignore a file that doesn't match any configured fixer.
229
230
230 $ hg revert --all --no-backup
231 $ hg revert --all --no-backup
231 reverting hello.changed
232 reverting hello.changed
232 reverting hello.whole
233 reverting hello.whole
233 $ printf "unimportant\n" > some.file
234 $ printf "unimportant\n" > some.file
234 $ hg commit -Aqm "some other file"
235 $ hg commit -Aqm "some other file"
235
236
236 $ hg fix -r .
237 $ hg fix -r .
237 $ hg cat -r tip *
238 $ hg cat -r tip *
238 hello
239 hello
239 world
240 world
240 unimportant
241 unimportant
241 $ hg fix -r . --whole
242 $ hg fix -r . --whole
242 $ hg cat -r tip *
243 $ hg cat -r tip *
243 hello
244 hello
244 world
245 world
245 unimportant
246 unimportant
246 $ hg fix -r . *
247 $ hg fix -r . *
247 $ hg cat -r tip *
248 $ hg cat -r tip *
248 hello
249 hello
249 WORLD
250 WORLD
250 unimportant
251 unimportant
251 $ hg fix -r . * --whole --config experimental.evolution.allowdivergence=true
252 $ hg fix -r . * --whole --config experimental.evolution.allowdivergence=true
252 2 new content-divergent changesets
253 2 new content-divergent changesets
253 $ hg cat -r tip *
254 $ hg cat -r tip *
254 HELLO
255 HELLO
255 WORLD
256 WORLD
256 unimportant
257 unimportant
257
258
258 $ cd ..
259 $ cd ..
259
260
260 Fixing the working directory should still work if there are no revisions.
261 Fixing the working directory should still work if there are no revisions.
261
262
262 $ hg init norevisions
263 $ hg init norevisions
263 $ cd norevisions
264 $ cd norevisions
264
265
265 $ printf "something\n" > something.whole
266 $ printf "something\n" > something.whole
266 $ hg add
267 $ hg add
267 adding something.whole
268 adding something.whole
268 $ hg fix --working-dir
269 $ hg fix --working-dir
269 $ cat something.whole
270 $ cat something.whole
270 SOMETHING
271 SOMETHING
271
272
272 $ cd ..
273 $ cd ..
273
274
274 Test the effect of fixing the working directory for each possible status, with
275 Test the effect of fixing the working directory for each possible status, with
275 and without providing explicit file arguments.
276 and without providing explicit file arguments.
276
277
277 $ hg init implicitlyfixstatus
278 $ hg init implicitlyfixstatus
278 $ cd implicitlyfixstatus
279 $ cd implicitlyfixstatus
279
280
280 $ printf "modified\n" > modified.whole
281 $ printf "modified\n" > modified.whole
281 $ printf "removed\n" > removed.whole
282 $ printf "removed\n" > removed.whole
282 $ printf "deleted\n" > deleted.whole
283 $ printf "deleted\n" > deleted.whole
283 $ printf "clean\n" > clean.whole
284 $ printf "clean\n" > clean.whole
284 $ printf "ignored.whole" > .hgignore
285 $ printf "ignored.whole" > .hgignore
285 $ hg commit -Aqm "stuff"
286 $ hg commit -Aqm "stuff"
286
287
287 $ printf "modified!!!\n" > modified.whole
288 $ printf "modified!!!\n" > modified.whole
288 $ printf "unknown\n" > unknown.whole
289 $ printf "unknown\n" > unknown.whole
289 $ printf "ignored\n" > ignored.whole
290 $ printf "ignored\n" > ignored.whole
290 $ printf "added\n" > added.whole
291 $ printf "added\n" > added.whole
291 $ hg add added.whole
292 $ hg add added.whole
292 $ hg remove removed.whole
293 $ hg remove removed.whole
293 $ rm deleted.whole
294 $ rm deleted.whole
294
295
295 $ hg status --all
296 $ hg status --all
296 M modified.whole
297 M modified.whole
297 A added.whole
298 A added.whole
298 R removed.whole
299 R removed.whole
299 ! deleted.whole
300 ! deleted.whole
300 ? unknown.whole
301 ? unknown.whole
301 I ignored.whole
302 I ignored.whole
302 C .hgignore
303 C .hgignore
303 C clean.whole
304 C clean.whole
304
305
305 $ hg fix --working-dir
306 $ hg fix --working-dir
306
307
307 $ hg status --all
308 $ hg status --all
308 M modified.whole
309 M modified.whole
309 A added.whole
310 A added.whole
310 R removed.whole
311 R removed.whole
311 ! deleted.whole
312 ! deleted.whole
312 ? unknown.whole
313 ? unknown.whole
313 I ignored.whole
314 I ignored.whole
314 C .hgignore
315 C .hgignore
315 C clean.whole
316 C clean.whole
316
317
317 $ cat *.whole
318 $ cat *.whole
318 ADDED
319 ADDED
319 clean
320 clean
320 ignored
321 ignored
321 MODIFIED!!!
322 MODIFIED!!!
322 unknown
323 unknown
323
324
324 $ printf "modified!!!\n" > modified.whole
325 $ printf "modified!!!\n" > modified.whole
325 $ printf "added\n" > added.whole
326 $ printf "added\n" > added.whole
326 $ hg fix --working-dir *.whole
327 $ hg fix --working-dir *.whole
327
328
328 $ hg status --all
329 $ hg status --all
329 M clean.whole
330 M clean.whole
330 M modified.whole
331 M modified.whole
331 A added.whole
332 A added.whole
332 R removed.whole
333 R removed.whole
333 ! deleted.whole
334 ! deleted.whole
334 ? unknown.whole
335 ? unknown.whole
335 I ignored.whole
336 I ignored.whole
336 C .hgignore
337 C .hgignore
337
338
338 It would be better if this also fixed the unknown file.
339 It would be better if this also fixed the unknown file.
339 $ cat *.whole
340 $ cat *.whole
340 ADDED
341 ADDED
341 CLEAN
342 CLEAN
342 ignored
343 ignored
343 MODIFIED!!!
344 MODIFIED!!!
344 unknown
345 unknown
345
346
346 $ cd ..
347 $ cd ..
347
348
348 Test that incremental fixing works on files with additions, deletions, and
349 Test that incremental fixing works on files with additions, deletions, and
349 changes in multiple line ranges. Note that deletions do not generally cause
350 changes in multiple line ranges. Note that deletions do not generally cause
350 neighboring lines to be fixed, so we don't return a line range for purely
351 neighboring lines to be fixed, so we don't return a line range for purely
351 deleted sections. In the future we should support a :deletion config that
352 deleted sections. In the future we should support a :deletion config that
352 allows fixers to know where deletions are located.
353 allows fixers to know where deletions are located.
353
354
354 $ hg init incrementalfixedlines
355 $ hg init incrementalfixedlines
355 $ cd incrementalfixedlines
356 $ cd incrementalfixedlines
356
357
357 $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.txt
358 $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.txt
358 $ hg commit -Aqm "foo"
359 $ hg commit -Aqm "foo"
359 $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.txt
360 $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.txt
360
361
361 $ hg --config "fix.fail:command=echo" \
362 $ hg --config "fix.fail:command=echo" \
362 > --config "fix.fail:linerange={first}:{last}" \
363 > --config "fix.fail:linerange={first}:{last}" \
363 > --config "fix.fail:fileset=foo.txt" \
364 > --config "fix.fail:fileset=foo.txt" \
364 > fix --working-dir
365 > fix --working-dir
365 $ cat foo.txt
366 $ cat foo.txt
366 1:1 4:6 8:8
367 1:1 4:6 8:8
367
368
368 $ cd ..
369 $ cd ..
369
370
370 Test that --whole fixes all lines regardless of the diffs present.
371 Test that --whole fixes all lines regardless of the diffs present.
371
372
372 $ hg init wholeignoresdiffs
373 $ hg init wholeignoresdiffs
373 $ cd wholeignoresdiffs
374 $ cd wholeignoresdiffs
374
375
375 $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.changed
376 $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.changed
376 $ hg commit -Aqm "foo"
377 $ hg commit -Aqm "foo"
377 $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.changed
378 $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.changed
378 $ hg fix --working-dir --whole
379 $ hg fix --working-dir --whole
379 $ cat foo.changed
380 $ cat foo.changed
380 ZZ
381 ZZ
381 A
382 A
382 C
383 C
383 DD
384 DD
384 EE
385 EE
385 FF
386 FF
386 F
387 F
387 GG
388 GG
388
389
389 $ cd ..
390 $ cd ..
390
391
391 We should do nothing with symlinks, and their targets should be unaffected. Any
392 We should do nothing with symlinks, and their targets should be unaffected. Any
392 other behavior would be more complicated to implement and harder to document.
393 other behavior would be more complicated to implement and harder to document.
393
394
394 #if symlink
395 #if symlink
395 $ hg init dontmesswithsymlinks
396 $ hg init dontmesswithsymlinks
396 $ cd dontmesswithsymlinks
397 $ cd dontmesswithsymlinks
397
398
398 $ printf "hello\n" > hello.whole
399 $ printf "hello\n" > hello.whole
399 $ ln -s hello.whole hellolink
400 $ ln -s hello.whole hellolink
400 $ hg add
401 $ hg add
401 adding hello.whole
402 adding hello.whole
402 adding hellolink
403 adding hellolink
403 $ hg fix --working-dir hellolink
404 $ hg fix --working-dir hellolink
404 $ hg status
405 $ hg status
405 A hello.whole
406 A hello.whole
406 A hellolink
407 A hellolink
407
408
408 $ cd ..
409 $ cd ..
409 #endif
410 #endif
410
411
411 We should allow fixers to run on binary files, even though this doesn't sound
412 We should allow fixers to run on binary files, even though this doesn't sound
412 like a common use case. There's not much benefit to disallowing it, and users
413 like a common use case. There's not much benefit to disallowing it, and users
413 can add "and not binary()" to their filesets if needed. The Mercurial
414 can add "and not binary()" to their filesets if needed. The Mercurial
414 philosophy is generally to not handle binary files specially anyway.
415 philosophy is generally to not handle binary files specially anyway.
415
416
416 $ hg init cantouchbinaryfiles
417 $ hg init cantouchbinaryfiles
417 $ cd cantouchbinaryfiles
418 $ cd cantouchbinaryfiles
418
419
419 $ printf "hello\0\n" > hello.whole
420 $ printf "hello\0\n" > hello.whole
420 $ hg add
421 $ hg add
421 adding hello.whole
422 adding hello.whole
422 $ hg fix --working-dir 'set:binary()'
423 $ hg fix --working-dir 'set:binary()'
423 $ cat hello.whole
424 $ cat hello.whole
424 HELLO\x00 (esc)
425 HELLO\x00 (esc)
425
426
426 $ cd ..
427 $ cd ..
427
428
428 We have a config for the maximum size of file we will attempt to fix. This can
429 We have a config for the maximum size of file we will attempt to fix. This can
429 be helpful to avoid running unsuspecting fixer tools on huge inputs, which
430 be helpful to avoid running unsuspecting fixer tools on huge inputs, which
430 could happen by accident without a well considered configuration. A more
431 could happen by accident without a well considered configuration. A more
431 precise configuration could use the size() fileset function if one global limit
432 precise configuration could use the size() fileset function if one global limit
432 is undesired.
433 is undesired.
433
434
434 $ hg init maxfilesize
435 $ hg init maxfilesize
435 $ cd maxfilesize
436 $ cd maxfilesize
436
437
437 $ printf "this file is huge\n" > hello.whole
438 $ printf "this file is huge\n" > hello.whole
438 $ hg add
439 $ hg add
439 adding hello.whole
440 adding hello.whole
440 $ hg --config fix.maxfilesize=10 fix --working-dir
441 $ hg --config fix.maxfilesize=10 fix --working-dir
441 ignoring file larger than 10 bytes: hello.whole
442 ignoring file larger than 10 bytes: hello.whole
442 $ cat hello.whole
443 $ cat hello.whole
443 this file is huge
444 this file is huge
444
445
445 $ cd ..
446 $ cd ..
446
447
447 If we specify a file to fix, other files should be left alone, even if they
448 If we specify a file to fix, other files should be left alone, even if they
448 have changes.
449 have changes.
449
450
450 $ hg init fixonlywhatitellyouto
451 $ hg init fixonlywhatitellyouto
451 $ cd fixonlywhatitellyouto
452 $ cd fixonlywhatitellyouto
452
453
453 $ printf "fix me!\n" > fixme.whole
454 $ printf "fix me!\n" > fixme.whole
454 $ printf "not me.\n" > notme.whole
455 $ printf "not me.\n" > notme.whole
455 $ hg add
456 $ hg add
456 adding fixme.whole
457 adding fixme.whole
457 adding notme.whole
458 adding notme.whole
458 $ hg fix --working-dir fixme.whole
459 $ hg fix --working-dir fixme.whole
459 $ cat *.whole
460 $ cat *.whole
460 FIX ME!
461 FIX ME!
461 not me.
462 not me.
462
463
463 $ cd ..
464 $ cd ..
464
465
465 Specifying a directory name should fix all its files and subdirectories.
466 Specifying a directory name should fix all its files and subdirectories.
466
467
467 $ hg init fixdirectory
468 $ hg init fixdirectory
468 $ cd fixdirectory
469 $ cd fixdirectory
469
470
470 $ mkdir -p dir1/dir2
471 $ mkdir -p dir1/dir2
471 $ printf "foo\n" > foo.whole
472 $ printf "foo\n" > foo.whole
472 $ printf "bar\n" > dir1/bar.whole
473 $ printf "bar\n" > dir1/bar.whole
473 $ printf "baz\n" > dir1/dir2/baz.whole
474 $ printf "baz\n" > dir1/dir2/baz.whole
474 $ hg add
475 $ hg add
475 adding dir1/bar.whole
476 adding dir1/bar.whole
476 adding dir1/dir2/baz.whole
477 adding dir1/dir2/baz.whole
477 adding foo.whole
478 adding foo.whole
478 $ hg fix --working-dir dir1
479 $ hg fix --working-dir dir1
479 $ cat foo.whole dir1/bar.whole dir1/dir2/baz.whole
480 $ cat foo.whole dir1/bar.whole dir1/dir2/baz.whole
480 foo
481 foo
481 BAR
482 BAR
482 BAZ
483 BAZ
483
484
484 $ cd ..
485 $ cd ..
485
486
486 Fixing a file in the working directory that needs no fixes should not actually
487 Fixing a file in the working directory that needs no fixes should not actually
487 write back to the file, so for example the mtime shouldn't change.
488 write back to the file, so for example the mtime shouldn't change.
488
489
489 $ hg init donttouchunfixedfiles
490 $ hg init donttouchunfixedfiles
490 $ cd donttouchunfixedfiles
491 $ cd donttouchunfixedfiles
491
492
492 $ printf "NO FIX NEEDED\n" > foo.whole
493 $ printf "NO FIX NEEDED\n" > foo.whole
493 $ hg add
494 $ hg add
494 adding foo.whole
495 adding foo.whole
495 $ cp foo.whole foo.whole.orig
496 $ cp foo.whole foo.whole.orig
496 $ sleep 2 # mtime has a resolution of one or two seconds.
497 $ sleep 2 # mtime has a resolution of one or two seconds.
497 $ hg fix --working-dir
498 $ hg fix --working-dir
498 $ f foo.whole --newer foo.whole.orig
499 $ f foo.whole --newer foo.whole.orig
499 foo.whole: older than foo.whole.orig
500 foo.whole: older than foo.whole.orig
500
501
501 $ cd ..
502 $ cd ..
502
503
503 When a fixer prints to stderr, we assume that it has failed. We should show the
504 When a fixer prints to stderr, we assume that it has failed. We should show the
504 error messages to the user, and we should not let the failing fixer affect the
505 error messages to the user, and we should not let the failing fixer affect the
505 file it was fixing (many code formatters might emit error messages on stderr
506 file it was fixing (many code formatters might emit error messages on stderr
506 and nothing on stdout, which would cause us the clear the file). We show the
507 and nothing on stdout, which would cause us the clear the file). We show the
507 user which fixer failed and which revision, but we assume that the fixer will
508 user which fixer failed and which revision, but we assume that the fixer will
508 print the filename if it is relevant.
509 print the filename if it is relevant.
509
510
510 $ hg init showstderr
511 $ hg init showstderr
511 $ cd showstderr
512 $ cd showstderr
512
513
513 $ printf "hello\n" > hello.txt
514 $ printf "hello\n" > hello.txt
514 $ hg add
515 $ hg add
515 adding hello.txt
516 adding hello.txt
516 $ hg --config "fix.fail:command=printf 'HELLO\n' ; \
517 $ hg --config "fix.fail:command=printf 'HELLO\n' ; \
517 > printf '{rootpath}: some\nerror' >&2" \
518 > printf '{rootpath}: some\nerror' >&2" \
518 > --config "fix.fail:fileset=hello.txt" \
519 > --config "fix.fail:fileset=hello.txt" \
519 > fix --working-dir
520 > fix --working-dir
520 [wdir] fail: hello.txt: some
521 [wdir] fail: hello.txt: some
521 [wdir] fail: error
522 [wdir] fail: error
522 $ cat hello.txt
523 $ cat hello.txt
523 hello
524 hello
524
525
525 $ cd ..
526 $ cd ..
526
527
527 Fixing the working directory and its parent revision at the same time should
528 Fixing the working directory and its parent revision at the same time should
528 check out the replacement revision for the parent. This prevents any new
529 check out the replacement revision for the parent. This prevents any new
529 uncommitted changes from appearing. We test this for a clean working directory
530 uncommitted changes from appearing. We test this for a clean working directory
530 and a dirty one. In both cases, all lines/files changed since the grandparent
531 and a dirty one. In both cases, all lines/files changed since the grandparent
531 will be fixed. The grandparent is the "baserev" for both the parent and the
532 will be fixed. The grandparent is the "baserev" for both the parent and the
532 working copy.
533 working copy.
533
534
534 $ hg init fixdotandcleanwdir
535 $ hg init fixdotandcleanwdir
535 $ cd fixdotandcleanwdir
536 $ cd fixdotandcleanwdir
536
537
537 $ printf "hello\n" > hello.whole
538 $ printf "hello\n" > hello.whole
538 $ printf "world\n" > world.whole
539 $ printf "world\n" > world.whole
539 $ hg commit -Aqm "the parent commit"
540 $ hg commit -Aqm "the parent commit"
540
541
541 $ hg parents --template '{rev} {desc}\n'
542 $ hg parents --template '{rev} {desc}\n'
542 0 the parent commit
543 0 the parent commit
543 $ hg fix --working-dir -r .
544 $ hg fix --working-dir -r .
544 $ hg parents --template '{rev} {desc}\n'
545 $ hg parents --template '{rev} {desc}\n'
545 1 the parent commit
546 1 the parent commit
546 $ hg cat -r . *.whole
547 $ hg cat -r . *.whole
547 HELLO
548 HELLO
548 WORLD
549 WORLD
549 $ cat *.whole
550 $ cat *.whole
550 HELLO
551 HELLO
551 WORLD
552 WORLD
552 $ hg status
553 $ hg status
553
554
554 $ cd ..
555 $ cd ..
555
556
556 Same test with a dirty working copy.
557 Same test with a dirty working copy.
557
558
558 $ hg init fixdotanddirtywdir
559 $ hg init fixdotanddirtywdir
559 $ cd fixdotanddirtywdir
560 $ cd fixdotanddirtywdir
560
561
561 $ printf "hello\n" > hello.whole
562 $ printf "hello\n" > hello.whole
562 $ printf "world\n" > world.whole
563 $ printf "world\n" > world.whole
563 $ hg commit -Aqm "the parent commit"
564 $ hg commit -Aqm "the parent commit"
564
565
565 $ printf "hello,\n" > hello.whole
566 $ printf "hello,\n" > hello.whole
566 $ printf "world!\n" > world.whole
567 $ printf "world!\n" > world.whole
567
568
568 $ hg parents --template '{rev} {desc}\n'
569 $ hg parents --template '{rev} {desc}\n'
569 0 the parent commit
570 0 the parent commit
570 $ hg fix --working-dir -r .
571 $ hg fix --working-dir -r .
571 $ hg parents --template '{rev} {desc}\n'
572 $ hg parents --template '{rev} {desc}\n'
572 1 the parent commit
573 1 the parent commit
573 $ hg cat -r . *.whole
574 $ hg cat -r . *.whole
574 HELLO
575 HELLO
575 WORLD
576 WORLD
576 $ cat *.whole
577 $ cat *.whole
577 HELLO,
578 HELLO,
578 WORLD!
579 WORLD!
579 $ hg status
580 $ hg status
580 M hello.whole
581 M hello.whole
581 M world.whole
582 M world.whole
582
583
583 $ cd ..
584 $ cd ..
584
585
585 When we have a chain of commits that change mutually exclusive lines of code,
586 When we have a chain of commits that change mutually exclusive lines of code,
586 we should be able to do incremental fixing that causes each commit in the chain
587 we should be able to do incremental fixing that causes each commit in the chain
587 to include fixes made to the previous commits. This prevents children from
588 to include fixes made to the previous commits. This prevents children from
588 backing out the fixes made in their parents. A dirty working directory is
589 backing out the fixes made in their parents. A dirty working directory is
589 conceptually similar to another commit in the chain.
590 conceptually similar to another commit in the chain.
590
591
591 $ hg init incrementallyfixchain
592 $ hg init incrementallyfixchain
592 $ cd incrementallyfixchain
593 $ cd incrementallyfixchain
593
594
594 $ cat > file.changed <<EOF
595 $ cat > file.changed <<EOF
595 > first
596 > first
596 > second
597 > second
597 > third
598 > third
598 > fourth
599 > fourth
599 > fifth
600 > fifth
600 > EOF
601 > EOF
601 $ hg commit -Aqm "the common ancestor (the baserev)"
602 $ hg commit -Aqm "the common ancestor (the baserev)"
602 $ cat > file.changed <<EOF
603 $ cat > file.changed <<EOF
603 > first (changed)
604 > first (changed)
604 > second
605 > second
605 > third
606 > third
606 > fourth
607 > fourth
607 > fifth
608 > fifth
608 > EOF
609 > EOF
609 $ hg commit -Aqm "the first commit to fix"
610 $ hg commit -Aqm "the first commit to fix"
610 $ cat > file.changed <<EOF
611 $ cat > file.changed <<EOF
611 > first (changed)
612 > first (changed)
612 > second
613 > second
613 > third (changed)
614 > third (changed)
614 > fourth
615 > fourth
615 > fifth
616 > fifth
616 > EOF
617 > EOF
617 $ hg commit -Aqm "the second commit to fix"
618 $ hg commit -Aqm "the second commit to fix"
618 $ cat > file.changed <<EOF
619 $ cat > file.changed <<EOF
619 > first (changed)
620 > first (changed)
620 > second
621 > second
621 > third (changed)
622 > third (changed)
622 > fourth
623 > fourth
623 > fifth (changed)
624 > fifth (changed)
624 > EOF
625 > EOF
625
626
626 $ hg fix -r . -r '.^' --working-dir
627 $ hg fix -r . -r '.^' --working-dir
627
628
628 $ hg parents --template '{rev}\n'
629 $ hg parents --template '{rev}\n'
629 4
630 4
630 $ hg cat -r '.^^' file.changed
631 $ hg cat -r '.^^' file.changed
631 first
632 first
632 second
633 second
633 third
634 third
634 fourth
635 fourth
635 fifth
636 fifth
636 $ hg cat -r '.^' file.changed
637 $ hg cat -r '.^' file.changed
637 FIRST (CHANGED)
638 FIRST (CHANGED)
638 second
639 second
639 third
640 third
640 fourth
641 fourth
641 fifth
642 fifth
642 $ hg cat -r . file.changed
643 $ hg cat -r . file.changed
643 FIRST (CHANGED)
644 FIRST (CHANGED)
644 second
645 second
645 THIRD (CHANGED)
646 THIRD (CHANGED)
646 fourth
647 fourth
647 fifth
648 fifth
648 $ cat file.changed
649 $ cat file.changed
649 FIRST (CHANGED)
650 FIRST (CHANGED)
650 second
651 second
651 THIRD (CHANGED)
652 THIRD (CHANGED)
652 fourth
653 fourth
653 FIFTH (CHANGED)
654 FIFTH (CHANGED)
654
655
655 $ cd ..
656 $ cd ..
656
657
657 If we incrementally fix a merge commit, we should fix any lines that changed
658 If we incrementally fix a merge commit, we should fix any lines that changed
658 versus either parent. You could imagine only fixing the intersection or some
659 versus either parent. You could imagine only fixing the intersection or some
659 other subset, but this is necessary if either parent is being fixed. It
660 other subset, but this is necessary if either parent is being fixed. It
660 prevents us from forgetting fixes made in either parent.
661 prevents us from forgetting fixes made in either parent.
661
662
662 $ hg init incrementallyfixmergecommit
663 $ hg init incrementallyfixmergecommit
663 $ cd incrementallyfixmergecommit
664 $ cd incrementallyfixmergecommit
664
665
665 $ printf "a\nb\nc\n" > file.changed
666 $ printf "a\nb\nc\n" > file.changed
666 $ hg commit -Aqm "ancestor"
667 $ hg commit -Aqm "ancestor"
667
668
668 $ printf "aa\nb\nc\n" > file.changed
669 $ printf "aa\nb\nc\n" > file.changed
669 $ hg commit -m "change a"
670 $ hg commit -m "change a"
670
671
671 $ hg checkout '.^'
672 $ hg checkout '.^'
672 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
673 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
673 $ printf "a\nb\ncc\n" > file.changed
674 $ printf "a\nb\ncc\n" > file.changed
674 $ hg commit -m "change c"
675 $ hg commit -m "change c"
675 created new head
676 created new head
676
677
677 $ hg merge
678 $ hg merge
678 merging file.changed
679 merging file.changed
679 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
680 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
680 (branch merge, don't forget to commit)
681 (branch merge, don't forget to commit)
681 $ hg commit -m "merge"
682 $ hg commit -m "merge"
682 $ hg cat -r . file.changed
683 $ hg cat -r . file.changed
683 aa
684 aa
684 b
685 b
685 cc
686 cc
686
687
687 $ hg fix -r . --working-dir
688 $ hg fix -r . --working-dir
688 $ hg cat -r . file.changed
689 $ hg cat -r . file.changed
689 AA
690 AA
690 b
691 b
691 CC
692 CC
692
693
693 $ cd ..
694 $ cd ..
694
695
695 Abort fixing revisions if there is an unfinished operation. We don't want to
696 Abort fixing revisions if there is an unfinished operation. We don't want to
696 make things worse by editing files or stripping/obsoleting things. Also abort
697 make things worse by editing files or stripping/obsoleting things. Also abort
697 fixing the working directory if there are unresolved merge conflicts.
698 fixing the working directory if there are unresolved merge conflicts.
698
699
699 $ hg init abortunresolved
700 $ hg init abortunresolved
700 $ cd abortunresolved
701 $ cd abortunresolved
701
702
702 $ echo "foo1" > foo.whole
703 $ echo "foo1" > foo.whole
703 $ hg commit -Aqm "foo 1"
704 $ hg commit -Aqm "foo 1"
704
705
705 $ hg update null
706 $ hg update null
706 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
707 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
707 $ echo "foo2" > foo.whole
708 $ echo "foo2" > foo.whole
708 $ hg commit -Aqm "foo 2"
709 $ hg commit -Aqm "foo 2"
709
710
710 $ hg --config extensions.rebase= rebase -r 1 -d 0
711 $ hg --config extensions.rebase= rebase -r 1 -d 0
711 rebasing 1:c3b6dc0e177a "foo 2" (tip)
712 rebasing 1:c3b6dc0e177a "foo 2" (tip)
712 merging foo.whole
713 merging foo.whole
713 warning: conflicts while merging foo.whole! (edit, then use 'hg resolve --mark')
714 warning: conflicts while merging foo.whole! (edit, then use 'hg resolve --mark')
714 unresolved conflicts (see hg resolve, then hg rebase --continue)
715 unresolved conflicts (see hg resolve, then hg rebase --continue)
715 [1]
716 [1]
716
717
717 $ hg --config extensions.rebase= fix --working-dir
718 $ hg --config extensions.rebase= fix --working-dir
718 abort: unresolved conflicts
719 abort: unresolved conflicts
719 (use 'hg resolve')
720 (use 'hg resolve')
720 [255]
721 [255]
721
722
722 $ hg --config extensions.rebase= fix -r .
723 $ hg --config extensions.rebase= fix -r .
723 abort: rebase in progress
724 abort: rebase in progress
724 (use 'hg rebase --continue' or 'hg rebase --abort')
725 (use 'hg rebase --continue' or 'hg rebase --abort')
725 [255]
726 [255]
726
727
727 When fixing a file that was renamed, we should diff against the source of the
728 When fixing a file that was renamed, we should diff against the source of the
728 rename for incremental fixing and we should correctly reproduce the rename in
729 rename for incremental fixing and we should correctly reproduce the rename in
729 the replacement revision.
730 the replacement revision.
730
731
731 $ hg init fixrenamecommit
732 $ hg init fixrenamecommit
732 $ cd fixrenamecommit
733 $ cd fixrenamecommit
733
734
734 $ printf "a\nb\nc\n" > source.changed
735 $ printf "a\nb\nc\n" > source.changed
735 $ hg commit -Aqm "source revision"
736 $ hg commit -Aqm "source revision"
736 $ hg move source.changed dest.changed
737 $ hg move source.changed dest.changed
737 $ printf "a\nb\ncc\n" > dest.changed
738 $ printf "a\nb\ncc\n" > dest.changed
738 $ hg commit -m "dest revision"
739 $ hg commit -m "dest revision"
739
740
740 $ hg fix -r .
741 $ hg fix -r .
741 $ hg log -r tip --copies --template "{file_copies}\n"
742 $ hg log -r tip --copies --template "{file_copies}\n"
742 dest.changed (source.changed)
743 dest.changed (source.changed)
743 $ hg cat -r tip dest.changed
744 $ hg cat -r tip dest.changed
744 a
745 a
745 b
746 b
746 CC
747 CC
747
748
748 $ cd ..
749 $ cd ..
749
750
750 When fixing revisions that remove files we must ensure that the replacement
751 When fixing revisions that remove files we must ensure that the replacement
751 actually removes the file, whereas it could accidentally leave it unchanged or
752 actually removes the file, whereas it could accidentally leave it unchanged or
752 write an empty string to it.
753 write an empty string to it.
753
754
754 $ hg init fixremovedfile
755 $ hg init fixremovedfile
755 $ cd fixremovedfile
756 $ cd fixremovedfile
756
757
757 $ printf "foo\n" > foo.whole
758 $ printf "foo\n" > foo.whole
758 $ printf "bar\n" > bar.whole
759 $ printf "bar\n" > bar.whole
759 $ hg commit -Aqm "add files"
760 $ hg commit -Aqm "add files"
760 $ hg remove bar.whole
761 $ hg remove bar.whole
761 $ hg commit -m "remove file"
762 $ hg commit -m "remove file"
762 $ hg status --change .
763 $ hg status --change .
763 R bar.whole
764 R bar.whole
764 $ hg fix -r . foo.whole
765 $ hg fix -r . foo.whole
765 $ hg status --change tip
766 $ hg status --change tip
766 M foo.whole
767 M foo.whole
767 R bar.whole
768 R bar.whole
768
769
769 $ cd ..
770 $ cd ..
770
771
771 If fixing a revision finds no fixes to make, no replacement revision should be
772 If fixing a revision finds no fixes to make, no replacement revision should be
772 created.
773 created.
773
774
774 $ hg init nofixesneeded
775 $ hg init nofixesneeded
775 $ cd nofixesneeded
776 $ cd nofixesneeded
776
777
777 $ printf "FOO\n" > foo.whole
778 $ printf "FOO\n" > foo.whole
778 $ hg commit -Aqm "add file"
779 $ hg commit -Aqm "add file"
779 $ hg log --template '{rev}\n'
780 $ hg log --template '{rev}\n'
780 0
781 0
781 $ hg fix -r .
782 $ hg fix -r .
782 $ hg log --template '{rev}\n'
783 $ hg log --template '{rev}\n'
783 0
784 0
784
785
785 $ cd ..
786 $ cd ..
786
787
787 If fixing a commit reverts all the changes in the commit, we replace it with a
788 If fixing a commit reverts all the changes in the commit, we replace it with a
788 commit that changes no files.
789 commit that changes no files.
789
790
790 $ hg init nochangesleft
791 $ hg init nochangesleft
791 $ cd nochangesleft
792 $ cd nochangesleft
792
793
793 $ printf "FOO\n" > foo.whole
794 $ printf "FOO\n" > foo.whole
794 $ hg commit -Aqm "add file"
795 $ hg commit -Aqm "add file"
795 $ printf "foo\n" > foo.whole
796 $ printf "foo\n" > foo.whole
796 $ hg commit -m "edit file"
797 $ hg commit -m "edit file"
797 $ hg status --change .
798 $ hg status --change .
798 M foo.whole
799 M foo.whole
799 $ hg fix -r .
800 $ hg fix -r .
800 $ hg status --change tip
801 $ hg status --change tip
801
802
802 $ cd ..
803 $ cd ..
803
804
804 If we fix a parent and child revision together, the child revision must be
805 If we fix a parent and child revision together, the child revision must be
805 replaced if the parent is replaced, even if the diffs of the child needed no
806 replaced if the parent is replaced, even if the diffs of the child needed no
806 fixes. However, we're free to not replace revisions that need no fixes and have
807 fixes. However, we're free to not replace revisions that need no fixes and have
807 no ancestors that are replaced.
808 no ancestors that are replaced.
808
809
809 $ hg init mustreplacechild
810 $ hg init mustreplacechild
810 $ cd mustreplacechild
811 $ cd mustreplacechild
811
812
812 $ printf "FOO\n" > foo.whole
813 $ printf "FOO\n" > foo.whole
813 $ hg commit -Aqm "add foo"
814 $ hg commit -Aqm "add foo"
814 $ printf "foo\n" > foo.whole
815 $ printf "foo\n" > foo.whole
815 $ hg commit -m "edit foo"
816 $ hg commit -m "edit foo"
816 $ printf "BAR\n" > bar.whole
817 $ printf "BAR\n" > bar.whole
817 $ hg commit -Aqm "add bar"
818 $ hg commit -Aqm "add bar"
818
819
819 $ hg log --graph --template '{node|shortest} {files}'
820 $ hg log --graph --template '{node|shortest} {files}'
820 @ bc05 bar.whole
821 @ bc05 bar.whole
821 |
822 |
822 o 4fd2 foo.whole
823 o 4fd2 foo.whole
823 |
824 |
824 o f9ac foo.whole
825 o f9ac foo.whole
825
826
826 $ hg fix -r 0:2
827 $ hg fix -r 0:2
827 $ hg log --graph --template '{node|shortest} {files}'
828 $ hg log --graph --template '{node|shortest} {files}'
828 o 3801 bar.whole
829 o 3801 bar.whole
829 |
830 |
830 o 38cc
831 o 38cc
831 |
832 |
832 | @ bc05 bar.whole
833 | @ bc05 bar.whole
833 | |
834 | |
834 | x 4fd2 foo.whole
835 | x 4fd2 foo.whole
835 |/
836 |/
836 o f9ac foo.whole
837 o f9ac foo.whole
837
838
838
839
839 $ cd ..
840 $ cd ..
840
841
841 It's also possible that the child needs absolutely no changes, but we still
842 It's also possible that the child needs absolutely no changes, but we still
842 need to replace it to update its parent. If we skipped replacing the child
843 need to replace it to update its parent. If we skipped replacing the child
843 because it had no file content changes, it would become an orphan for no good
844 because it had no file content changes, it would become an orphan for no good
844 reason.
845 reason.
845
846
846 $ hg init mustreplacechildevenifnop
847 $ hg init mustreplacechildevenifnop
847 $ cd mustreplacechildevenifnop
848 $ cd mustreplacechildevenifnop
848
849
849 $ printf "Foo\n" > foo.whole
850 $ printf "Foo\n" > foo.whole
850 $ hg commit -Aqm "add a bad foo"
851 $ hg commit -Aqm "add a bad foo"
851 $ printf "FOO\n" > foo.whole
852 $ printf "FOO\n" > foo.whole
852 $ hg commit -m "add a good foo"
853 $ hg commit -m "add a good foo"
853 $ hg fix -r . -r '.^'
854 $ hg fix -r . -r '.^'
854 $ hg log --graph --template '{rev} {desc}'
855 $ hg log --graph --template '{rev} {desc}'
855 o 3 add a good foo
856 o 3 add a good foo
856 |
857 |
857 o 2 add a bad foo
858 o 2 add a bad foo
858
859
859 @ 1 add a good foo
860 @ 1 add a good foo
860 |
861 |
861 x 0 add a bad foo
862 x 0 add a bad foo
862
863
863
864
864 $ cd ..
865 $ cd ..
865
866
866 Similar to the case above, the child revision may become empty as a result of
867 Similar to the case above, the child revision may become empty as a result of
867 fixing its parent. We should still create an empty replacement child.
868 fixing its parent. We should still create an empty replacement child.
868 TODO: determine how this should interact with ui.allowemptycommit given that
869 TODO: determine how this should interact with ui.allowemptycommit given that
869 the empty replacement could have children.
870 the empty replacement could have children.
870
871
871 $ hg init mustreplacechildevenifempty
872 $ hg init mustreplacechildevenifempty
872 $ cd mustreplacechildevenifempty
873 $ cd mustreplacechildevenifempty
873
874
874 $ printf "foo\n" > foo.whole
875 $ printf "foo\n" > foo.whole
875 $ hg commit -Aqm "add foo"
876 $ hg commit -Aqm "add foo"
876 $ printf "Foo\n" > foo.whole
877 $ printf "Foo\n" > foo.whole
877 $ hg commit -m "edit foo"
878 $ hg commit -m "edit foo"
878 $ hg fix -r . -r '.^'
879 $ hg fix -r . -r '.^'
879 $ hg log --graph --template '{rev} {desc}\n' --stat
880 $ hg log --graph --template '{rev} {desc}\n' --stat
880 o 3 edit foo
881 o 3 edit foo
881 |
882 |
882 o 2 add foo
883 o 2 add foo
883 foo.whole | 1 +
884 foo.whole | 1 +
884 1 files changed, 1 insertions(+), 0 deletions(-)
885 1 files changed, 1 insertions(+), 0 deletions(-)
885
886
886 @ 1 edit foo
887 @ 1 edit foo
887 | foo.whole | 2 +-
888 | foo.whole | 2 +-
888 | 1 files changed, 1 insertions(+), 1 deletions(-)
889 | 1 files changed, 1 insertions(+), 1 deletions(-)
889 |
890 |
890 x 0 add foo
891 x 0 add foo
891 foo.whole | 1 +
892 foo.whole | 1 +
892 1 files changed, 1 insertions(+), 0 deletions(-)
893 1 files changed, 1 insertions(+), 0 deletions(-)
893
894
894
895
895 $ cd ..
896 $ cd ..
896
897
897 Fixing a secret commit should replace it with another secret commit.
898 Fixing a secret commit should replace it with another secret commit.
898
899
899 $ hg init fixsecretcommit
900 $ hg init fixsecretcommit
900 $ cd fixsecretcommit
901 $ cd fixsecretcommit
901
902
902 $ printf "foo\n" > foo.whole
903 $ printf "foo\n" > foo.whole
903 $ hg commit -Aqm "add foo" --secret
904 $ hg commit -Aqm "add foo" --secret
904 $ hg fix -r .
905 $ hg fix -r .
905 $ hg log --template '{rev} {phase}\n'
906 $ hg log --template '{rev} {phase}\n'
906 1 secret
907 1 secret
907 0 secret
908 0 secret
908
909
909 $ cd ..
910 $ cd ..
910
911
911 We should also preserve phase when fixing a draft commit while the user has
912 We should also preserve phase when fixing a draft commit while the user has
912 their default set to secret.
913 their default set to secret.
913
914
914 $ hg init respectphasesnewcommit
915 $ hg init respectphasesnewcommit
915 $ cd respectphasesnewcommit
916 $ cd respectphasesnewcommit
916
917
917 $ printf "foo\n" > foo.whole
918 $ printf "foo\n" > foo.whole
918 $ hg commit -Aqm "add foo"
919 $ hg commit -Aqm "add foo"
919 $ hg --config phases.newcommit=secret fix -r .
920 $ hg --config phases.newcommit=secret fix -r .
920 $ hg log --template '{rev} {phase}\n'
921 $ hg log --template '{rev} {phase}\n'
921 1 draft
922 1 draft
922 0 draft
923 0 draft
923
924
924 $ cd ..
925 $ cd ..
925
926
926 Debug output should show what fixer commands are being subprocessed, which is
927 Debug output should show what fixer commands are being subprocessed, which is
927 useful for anyone trying to set up a new config.
928 useful for anyone trying to set up a new config.
928
929
929 $ hg init debugoutput
930 $ hg init debugoutput
930 $ cd debugoutput
931 $ cd debugoutput
931
932
932 $ printf "foo\nbar\nbaz\n" > foo.changed
933 $ printf "foo\nbar\nbaz\n" > foo.changed
933 $ hg commit -Aqm "foo"
934 $ hg commit -Aqm "foo"
934 $ printf "Foo\nbar\nBaz\n" > foo.changed
935 $ printf "Foo\nbar\nBaz\n" > foo.changed
935 $ hg --debug fix --working-dir
936 $ hg --debug fix --working-dir
936 subprocess: * $TESTTMP/uppercase.py 1-1 3-3 (glob)
937 subprocess: * $TESTTMP/uppercase.py 1-1 3-3 (glob)
937
938
938 $ cd ..
939 $ cd ..
939
940
940 Fixing an obsolete revision can cause divergence, so we abort unless the user
941 Fixing an obsolete revision can cause divergence, so we abort unless the user
941 configures to allow it. This is not yet smart enough to know whether there is a
942 configures to allow it. This is not yet smart enough to know whether there is a
942 successor, but even then it is not likely intentional or idiomatic to fix an
943 successor, but even then it is not likely intentional or idiomatic to fix an
943 obsolete revision.
944 obsolete revision.
944
945
945 $ hg init abortobsoleterev
946 $ hg init abortobsoleterev
946 $ cd abortobsoleterev
947 $ cd abortobsoleterev
947
948
948 $ printf "foo\n" > foo.changed
949 $ printf "foo\n" > foo.changed
949 $ hg commit -Aqm "foo"
950 $ hg commit -Aqm "foo"
950 $ hg debugobsolete `hg parents --template '{node}'`
951 $ hg debugobsolete `hg parents --template '{node}'`
951 obsoleted 1 changesets
952 obsoleted 1 changesets
952 $ hg --hidden fix -r 0
953 $ hg --hidden fix -r 0
953 abort: fixing obsolete revision could cause divergence
954 abort: fixing obsolete revision could cause divergence
954 [255]
955 [255]
955
956
956 $ hg --hidden fix -r 0 --config experimental.evolution.allowdivergence=true
957 $ hg --hidden fix -r 0 --config experimental.evolution.allowdivergence=true
957 $ hg cat -r tip foo.changed
958 $ hg cat -r tip foo.changed
958 FOO
959 FOO
959
960
960 $ cd ..
961 $ cd ..
961
962
962 Test all of the available substitution values for fixer commands.
963 Test all of the available substitution values for fixer commands.
963
964
964 $ hg init substitution
965 $ hg init substitution
965 $ cd substitution
966 $ cd substitution
966
967
967 $ mkdir foo
968 $ mkdir foo
968 $ printf "hello\ngoodbye\n" > foo/bar
969 $ printf "hello\ngoodbye\n" > foo/bar
969 $ hg add
970 $ hg add
970 adding foo/bar
971 adding foo/bar
971 $ hg --config "fix.fail:command=printf '%s\n' '{rootpath}' '{basename}'" \
972 $ hg --config "fix.fail:command=printf '%s\n' '{rootpath}' '{basename}'" \
972 > --config "fix.fail:linerange='{first}' '{last}'" \
973 > --config "fix.fail:linerange='{first}' '{last}'" \
973 > --config "fix.fail:fileset=foo/bar" \
974 > --config "fix.fail:fileset=foo/bar" \
974 > fix --working-dir
975 > fix --working-dir
975 $ cat foo/bar
976 $ cat foo/bar
976 foo/bar
977 foo/bar
977 bar
978 bar
978 1
979 1
979 2
980 2
980
981
981 $ cd ..
982 $ cd ..
982
983
983 The --base flag should allow picking the revisions to diff against for changed
984 The --base flag should allow picking the revisions to diff against for changed
984 files and incremental line formatting.
985 files and incremental line formatting.
985
986
986 $ hg init baseflag
987 $ hg init baseflag
987 $ cd baseflag
988 $ cd baseflag
988
989
989 $ printf "one\ntwo\n" > foo.changed
990 $ printf "one\ntwo\n" > foo.changed
990 $ printf "bar\n" > bar.changed
991 $ printf "bar\n" > bar.changed
991 $ hg commit -Aqm "first"
992 $ hg commit -Aqm "first"
992 $ printf "one\nTwo\n" > foo.changed
993 $ printf "one\nTwo\n" > foo.changed
993 $ hg commit -m "second"
994 $ hg commit -m "second"
994 $ hg fix -w --base .
995 $ hg fix -w --base .
995 $ hg status
996 $ hg status
996 $ hg fix -w --base null
997 $ hg fix -w --base null
997 $ cat foo.changed
998 $ cat foo.changed
998 ONE
999 ONE
999 TWO
1000 TWO
1000 $ cat bar.changed
1001 $ cat bar.changed
1001 BAR
1002 BAR
1002
1003
1003 $ cd ..
1004 $ cd ..
1004
1005
1005 If the user asks to fix the parent of another commit, they are asking to create
1006 If the user asks to fix the parent of another commit, they are asking to create
1006 an orphan. We must respect experimental.evolution.allowunstable.
1007 an orphan. We must respect experimental.evolution.allowunstable.
1007
1008
1008 $ hg init allowunstable
1009 $ hg init allowunstable
1009 $ cd allowunstable
1010 $ cd allowunstable
1010
1011
1011 $ printf "one\n" > foo.whole
1012 $ printf "one\n" > foo.whole
1012 $ hg commit -Aqm "first"
1013 $ hg commit -Aqm "first"
1013 $ printf "two\n" > foo.whole
1014 $ printf "two\n" > foo.whole
1014 $ hg commit -m "second"
1015 $ hg commit -m "second"
1015 $ hg --config experimental.evolution.allowunstable=False fix -r '.^'
1016 $ hg --config experimental.evolution.allowunstable=False fix -r '.^'
1016 abort: can only fix a changeset together with all its descendants
1017 abort: can only fix a changeset together with all its descendants
1017 [255]
1018 [255]
1018 $ hg fix -r '.^'
1019 $ hg fix -r '.^'
1019 1 new orphan changesets
1020 1 new orphan changesets
1020 $ hg cat -r 2 foo.whole
1021 $ hg cat -r 2 foo.whole
1021 ONE
1022 ONE
1022
1023
1023 $ cd ..
1024 $ cd ..
1024
1025
General Comments 0
You need to be logged in to leave comments. Login now