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