Show More
@@ -0,0 +1,162 b'' | |||||
|
1 | # wdutil.py - working dir utilities | |||
|
2 | # | |||
|
3 | # Copyright 2011 Patrick Mezard <pmezard@gmail.com> | |||
|
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 | ||||
|
8 | import glob, os | |||
|
9 | import util, similar, scmutil | |||
|
10 | import match as matchmod | |||
|
11 | from i18n import _ | |||
|
12 | ||||
|
13 | def expandpats(pats): | |||
|
14 | if not util.expandglobs: | |||
|
15 | return list(pats) | |||
|
16 | ret = [] | |||
|
17 | for p in pats: | |||
|
18 | kind, name = matchmod._patsplit(p, None) | |||
|
19 | if kind is None: | |||
|
20 | try: | |||
|
21 | globbed = glob.glob(name) | |||
|
22 | except re.error: | |||
|
23 | globbed = [name] | |||
|
24 | if globbed: | |||
|
25 | ret.extend(globbed) | |||
|
26 | continue | |||
|
27 | ret.append(p) | |||
|
28 | return ret | |||
|
29 | ||||
|
30 | def match(repo, pats=[], opts={}, globbed=False, default='relpath'): | |||
|
31 | if pats == ("",): | |||
|
32 | pats = [] | |||
|
33 | if not globbed and default == 'relpath': | |||
|
34 | pats = expandpats(pats or []) | |||
|
35 | m = matchmod.match(repo.root, repo.getcwd(), pats, | |||
|
36 | opts.get('include'), opts.get('exclude'), default, | |||
|
37 | auditor=repo.auditor) | |||
|
38 | def badfn(f, msg): | |||
|
39 | repo.ui.warn("%s: %s\n" % (m.rel(f), msg)) | |||
|
40 | m.bad = badfn | |||
|
41 | return m | |||
|
42 | ||||
|
43 | def matchall(repo): | |||
|
44 | return matchmod.always(repo.root, repo.getcwd()) | |||
|
45 | ||||
|
46 | def matchfiles(repo, files): | |||
|
47 | return matchmod.exact(repo.root, repo.getcwd(), files) | |||
|
48 | ||||
|
49 | def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None): | |||
|
50 | if dry_run is None: | |||
|
51 | dry_run = opts.get('dry_run') | |||
|
52 | if similarity is None: | |||
|
53 | similarity = float(opts.get('similarity') or 0) | |||
|
54 | # we'd use status here, except handling of symlinks and ignore is tricky | |||
|
55 | added, unknown, deleted, removed = [], [], [], [] | |||
|
56 | audit_path = scmutil.pathauditor(repo.root) | |||
|
57 | m = match(repo, pats, opts) | |||
|
58 | for abs in repo.walk(m): | |||
|
59 | target = repo.wjoin(abs) | |||
|
60 | good = True | |||
|
61 | try: | |||
|
62 | audit_path(abs) | |||
|
63 | except (OSError, util.Abort): | |||
|
64 | good = False | |||
|
65 | rel = m.rel(abs) | |||
|
66 | exact = m.exact(abs) | |||
|
67 | if good and abs not in repo.dirstate: | |||
|
68 | unknown.append(abs) | |||
|
69 | if repo.ui.verbose or not exact: | |||
|
70 | repo.ui.status(_('adding %s\n') % ((pats and rel) or abs)) | |||
|
71 | elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target) | |||
|
72 | or (os.path.isdir(target) and not os.path.islink(target))): | |||
|
73 | deleted.append(abs) | |||
|
74 | if repo.ui.verbose or not exact: | |||
|
75 | repo.ui.status(_('removing %s\n') % ((pats and rel) or abs)) | |||
|
76 | # for finding renames | |||
|
77 | elif repo.dirstate[abs] == 'r': | |||
|
78 | removed.append(abs) | |||
|
79 | elif repo.dirstate[abs] == 'a': | |||
|
80 | added.append(abs) | |||
|
81 | copies = {} | |||
|
82 | if similarity > 0: | |||
|
83 | for old, new, score in similar.findrenames(repo, | |||
|
84 | added + unknown, removed + deleted, similarity): | |||
|
85 | if repo.ui.verbose or not m.exact(old) or not m.exact(new): | |||
|
86 | repo.ui.status(_('recording removal of %s as rename to %s ' | |||
|
87 | '(%d%% similar)\n') % | |||
|
88 | (m.rel(old), m.rel(new), score * 100)) | |||
|
89 | copies[new] = old | |||
|
90 | ||||
|
91 | if not dry_run: | |||
|
92 | wctx = repo[None] | |||
|
93 | wlock = repo.wlock() | |||
|
94 | try: | |||
|
95 | wctx.remove(deleted) | |||
|
96 | wctx.add(unknown) | |||
|
97 | for new, old in copies.iteritems(): | |||
|
98 | wctx.copy(old, new) | |||
|
99 | finally: | |||
|
100 | wlock.release() | |||
|
101 | ||||
|
102 | def updatedir(ui, repo, patches, similarity=0): | |||
|
103 | '''Update dirstate after patch application according to metadata''' | |||
|
104 | if not patches: | |||
|
105 | return [] | |||
|
106 | copies = [] | |||
|
107 | removes = set() | |||
|
108 | cfiles = patches.keys() | |||
|
109 | cwd = repo.getcwd() | |||
|
110 | if cwd: | |||
|
111 | cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()] | |||
|
112 | for f in patches: | |||
|
113 | gp = patches[f] | |||
|
114 | if not gp: | |||
|
115 | continue | |||
|
116 | if gp.op == 'RENAME': | |||
|
117 | copies.append((gp.oldpath, gp.path)) | |||
|
118 | removes.add(gp.oldpath) | |||
|
119 | elif gp.op == 'COPY': | |||
|
120 | copies.append((gp.oldpath, gp.path)) | |||
|
121 | elif gp.op == 'DELETE': | |||
|
122 | removes.add(gp.path) | |||
|
123 | ||||
|
124 | wctx = repo[None] | |||
|
125 | for src, dst in copies: | |||
|
126 | dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd) | |||
|
127 | if (not similarity) and removes: | |||
|
128 | wctx.remove(sorted(removes), True) | |||
|
129 | ||||
|
130 | for f in patches: | |||
|
131 | gp = patches[f] | |||
|
132 | if gp and gp.mode: | |||
|
133 | islink, isexec = gp.mode | |||
|
134 | dst = repo.wjoin(gp.path) | |||
|
135 | # patch won't create empty files | |||
|
136 | if gp.op == 'ADD' and not os.path.lexists(dst): | |||
|
137 | flags = (isexec and 'x' or '') + (islink and 'l' or '') | |||
|
138 | repo.wwrite(gp.path, '', flags) | |||
|
139 | util.setflags(dst, islink, isexec) | |||
|
140 | addremove(repo, cfiles, similarity=similarity) | |||
|
141 | files = patches.keys() | |||
|
142 | files.extend([r for r in removes if r not in files]) | |||
|
143 | return sorted(files) | |||
|
144 | ||||
|
145 | def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None): | |||
|
146 | """Update the dirstate to reflect the intent of copying src to dst. For | |||
|
147 | different reasons it might not end with dst being marked as copied from src. | |||
|
148 | """ | |||
|
149 | origsrc = repo.dirstate.copied(src) or src | |||
|
150 | if dst == origsrc: # copying back a copy? | |||
|
151 | if repo.dirstate[dst] not in 'mn' and not dryrun: | |||
|
152 | repo.dirstate.normallookup(dst) | |||
|
153 | else: | |||
|
154 | if repo.dirstate[origsrc] == 'a' and origsrc == src: | |||
|
155 | if not ui.quiet: | |||
|
156 | ui.warn(_("%s has not been committed yet, so no copy " | |||
|
157 | "data will be stored for %s.\n") | |||
|
158 | % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd))) | |||
|
159 | if repo.dirstate[dst] in '?r' and not dryrun: | |||
|
160 | wctx.add([dst]) | |||
|
161 | elif not dryrun: | |||
|
162 | wctx.copy(origsrc, dst) |
@@ -618,7 +618,7 b' class queue(object):' | |||||
618 | fuzz = patchmod.patch(patchfile, self.ui, strip=1, |
|
618 | fuzz = patchmod.patch(patchfile, self.ui, strip=1, | |
619 | cwd=repo.root, files=files, eolmode=None) |
|
619 | cwd=repo.root, files=files, eolmode=None) | |
620 | finally: |
|
620 | finally: | |
621 |
files = cmd |
|
621 | files = patchmod.updatedir(self.ui, repo, files) | |
622 | return (True, files, fuzz) |
|
622 | return (True, files, fuzz) | |
623 | except Exception, inst: |
|
623 | except Exception, inst: | |
624 | self.ui.note(str(inst) + '\n') |
|
624 | self.ui.note(str(inst) + '\n') |
@@ -482,7 +482,7 b' def dorecord(ui, repo, commitfunc, *pats' | |||||
482 | patch.internalpatch(fp, ui, 1, repo.root, files=pfiles, |
|
482 | patch.internalpatch(fp, ui, 1, repo.root, files=pfiles, | |
483 | eolmode=None) |
|
483 | eolmode=None) | |
484 | finally: |
|
484 | finally: | |
485 |
|
|
485 | patch.updatedir(ui, repo, pfiles) | |
486 | except patch.PatchError, err: |
|
486 | except patch.PatchError, err: | |
487 | raise util.Abort(str(err)) |
|
487 | raise util.Abort(str(err)) | |
488 | del fp |
|
488 | del fp |
@@ -232,7 +232,7 b' class transplanter(object):' | |||||
232 | % revlog.hex(node)) |
|
232 | % revlog.hex(node)) | |
233 | return None |
|
233 | return None | |
234 | finally: |
|
234 | finally: | |
235 |
files = |
|
235 | files = patch.updatedir(self.ui, repo, files) | |
236 | except Exception, inst: |
|
236 | except Exception, inst: | |
237 | seriespath = os.path.join(self.path, 'series') |
|
237 | seriespath = os.path.join(self.path, 'series') | |
238 | if os.path.exists(seriespath): |
|
238 | if os.path.exists(seriespath): |
@@ -8,10 +8,17 b'' | |||||
8 | from node import hex, nullid, nullrev, short |
|
8 | from node import hex, nullid, nullrev, short | |
9 | from i18n import _ |
|
9 | from i18n import _ | |
10 | import os, sys, errno, re, glob, tempfile |
|
10 | import os, sys, errno, re, glob, tempfile | |
11 | import util, scmutil, templater, patch, error, templatekw |
|
11 | import util, scmutil, templater, patch, error, templatekw, wdutil | |
12 | import match as matchmod |
|
12 | import match as matchmod | |
13 | import similar, revset, subrepo |
|
13 | import similar, revset, subrepo | |
14 |
|
14 | |||
|
15 | expandpats = wdutil.expandpats | |||
|
16 | match = wdutil.match | |||
|
17 | matchall = wdutil.matchall | |||
|
18 | matchfiles = wdutil.matchfiles | |||
|
19 | addremove = wdutil.addremove | |||
|
20 | dirstatecopy = wdutil.dirstatecopy | |||
|
21 | ||||
15 | revrangesep = ':' |
|
22 | revrangesep = ':' | |
16 |
|
23 | |||
17 | def parsealiases(cmd): |
|
24 | def parsealiases(cmd): | |
@@ -243,157 +250,6 b' def make_file(repo, pat, node=None,' | |||||
243 | pathname), |
|
250 | pathname), | |
244 | mode) |
|
251 | mode) | |
245 |
|
252 | |||
246 | def expandpats(pats): |
|
|||
247 | if not util.expandglobs: |
|
|||
248 | return list(pats) |
|
|||
249 | ret = [] |
|
|||
250 | for p in pats: |
|
|||
251 | kind, name = matchmod._patsplit(p, None) |
|
|||
252 | if kind is None: |
|
|||
253 | try: |
|
|||
254 | globbed = glob.glob(name) |
|
|||
255 | except re.error: |
|
|||
256 | globbed = [name] |
|
|||
257 | if globbed: |
|
|||
258 | ret.extend(globbed) |
|
|||
259 | continue |
|
|||
260 | ret.append(p) |
|
|||
261 | return ret |
|
|||
262 |
|
||||
263 | def match(repo, pats=[], opts={}, globbed=False, default='relpath'): |
|
|||
264 | if pats == ("",): |
|
|||
265 | pats = [] |
|
|||
266 | if not globbed and default == 'relpath': |
|
|||
267 | pats = expandpats(pats or []) |
|
|||
268 | m = matchmod.match(repo.root, repo.getcwd(), pats, |
|
|||
269 | opts.get('include'), opts.get('exclude'), default, |
|
|||
270 | auditor=repo.auditor) |
|
|||
271 | def badfn(f, msg): |
|
|||
272 | repo.ui.warn("%s: %s\n" % (m.rel(f), msg)) |
|
|||
273 | m.bad = badfn |
|
|||
274 | return m |
|
|||
275 |
|
||||
276 | def matchall(repo): |
|
|||
277 | return matchmod.always(repo.root, repo.getcwd()) |
|
|||
278 |
|
||||
279 | def matchfiles(repo, files): |
|
|||
280 | return matchmod.exact(repo.root, repo.getcwd(), files) |
|
|||
281 |
|
||||
282 | def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None): |
|
|||
283 | if dry_run is None: |
|
|||
284 | dry_run = opts.get('dry_run') |
|
|||
285 | if similarity is None: |
|
|||
286 | similarity = float(opts.get('similarity') or 0) |
|
|||
287 | # we'd use status here, except handling of symlinks and ignore is tricky |
|
|||
288 | added, unknown, deleted, removed = [], [], [], [] |
|
|||
289 | audit_path = scmutil.pathauditor(repo.root) |
|
|||
290 | m = match(repo, pats, opts) |
|
|||
291 | for abs in repo.walk(m): |
|
|||
292 | target = repo.wjoin(abs) |
|
|||
293 | good = True |
|
|||
294 | try: |
|
|||
295 | audit_path(abs) |
|
|||
296 | except (OSError, util.Abort): |
|
|||
297 | good = False |
|
|||
298 | rel = m.rel(abs) |
|
|||
299 | exact = m.exact(abs) |
|
|||
300 | if good and abs not in repo.dirstate: |
|
|||
301 | unknown.append(abs) |
|
|||
302 | if repo.ui.verbose or not exact: |
|
|||
303 | repo.ui.status(_('adding %s\n') % ((pats and rel) or abs)) |
|
|||
304 | elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target) |
|
|||
305 | or (os.path.isdir(target) and not os.path.islink(target))): |
|
|||
306 | deleted.append(abs) |
|
|||
307 | if repo.ui.verbose or not exact: |
|
|||
308 | repo.ui.status(_('removing %s\n') % ((pats and rel) or abs)) |
|
|||
309 | # for finding renames |
|
|||
310 | elif repo.dirstate[abs] == 'r': |
|
|||
311 | removed.append(abs) |
|
|||
312 | elif repo.dirstate[abs] == 'a': |
|
|||
313 | added.append(abs) |
|
|||
314 | copies = {} |
|
|||
315 | if similarity > 0: |
|
|||
316 | for old, new, score in similar.findrenames(repo, |
|
|||
317 | added + unknown, removed + deleted, similarity): |
|
|||
318 | if repo.ui.verbose or not m.exact(old) or not m.exact(new): |
|
|||
319 | repo.ui.status(_('recording removal of %s as rename to %s ' |
|
|||
320 | '(%d%% similar)\n') % |
|
|||
321 | (m.rel(old), m.rel(new), score * 100)) |
|
|||
322 | copies[new] = old |
|
|||
323 |
|
||||
324 | if not dry_run: |
|
|||
325 | wctx = repo[None] |
|
|||
326 | wlock = repo.wlock() |
|
|||
327 | try: |
|
|||
328 | wctx.remove(deleted) |
|
|||
329 | wctx.add(unknown) |
|
|||
330 | for new, old in copies.iteritems(): |
|
|||
331 | wctx.copy(old, new) |
|
|||
332 | finally: |
|
|||
333 | wlock.release() |
|
|||
334 |
|
||||
335 | def updatedir(ui, repo, patches, similarity=0): |
|
|||
336 | '''Update dirstate after patch application according to metadata''' |
|
|||
337 | if not patches: |
|
|||
338 | return [] |
|
|||
339 | copies = [] |
|
|||
340 | removes = set() |
|
|||
341 | cfiles = patches.keys() |
|
|||
342 | cwd = repo.getcwd() |
|
|||
343 | if cwd: |
|
|||
344 | cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()] |
|
|||
345 | for f in patches: |
|
|||
346 | gp = patches[f] |
|
|||
347 | if not gp: |
|
|||
348 | continue |
|
|||
349 | if gp.op == 'RENAME': |
|
|||
350 | copies.append((gp.oldpath, gp.path)) |
|
|||
351 | removes.add(gp.oldpath) |
|
|||
352 | elif gp.op == 'COPY': |
|
|||
353 | copies.append((gp.oldpath, gp.path)) |
|
|||
354 | elif gp.op == 'DELETE': |
|
|||
355 | removes.add(gp.path) |
|
|||
356 |
|
||||
357 | wctx = repo[None] |
|
|||
358 | for src, dst in copies: |
|
|||
359 | dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd) |
|
|||
360 | if (not similarity) and removes: |
|
|||
361 | wctx.remove(sorted(removes), True) |
|
|||
362 |
|
||||
363 | for f in patches: |
|
|||
364 | gp = patches[f] |
|
|||
365 | if gp and gp.mode: |
|
|||
366 | islink, isexec = gp.mode |
|
|||
367 | dst = repo.wjoin(gp.path) |
|
|||
368 | # patch won't create empty files |
|
|||
369 | if gp.op == 'ADD' and not os.path.lexists(dst): |
|
|||
370 | flags = (isexec and 'x' or '') + (islink and 'l' or '') |
|
|||
371 | repo.wwrite(gp.path, '', flags) |
|
|||
372 | util.setflags(dst, islink, isexec) |
|
|||
373 | addremove(repo, cfiles, similarity=similarity) |
|
|||
374 | files = patches.keys() |
|
|||
375 | files.extend([r for r in removes if r not in files]) |
|
|||
376 | return sorted(files) |
|
|||
377 |
|
||||
378 | def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None): |
|
|||
379 | """Update the dirstate to reflect the intent of copying src to dst. For |
|
|||
380 | different reasons it might not end with dst being marked as copied from src. |
|
|||
381 | """ |
|
|||
382 | origsrc = repo.dirstate.copied(src) or src |
|
|||
383 | if dst == origsrc: # copying back a copy? |
|
|||
384 | if repo.dirstate[dst] not in 'mn' and not dryrun: |
|
|||
385 | repo.dirstate.normallookup(dst) |
|
|||
386 | else: |
|
|||
387 | if repo.dirstate[origsrc] == 'a' and origsrc == src: |
|
|||
388 | if not ui.quiet: |
|
|||
389 | ui.warn(_("%s has not been committed yet, so no copy " |
|
|||
390 | "data will be stored for %s.\n") |
|
|||
391 | % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd))) |
|
|||
392 | if repo.dirstate[dst] in '?r' and not dryrun: |
|
|||
393 | wctx.add([dst]) |
|
|||
394 | elif not dryrun: |
|
|||
395 | wctx.copy(origsrc, dst) |
|
|||
396 |
|
||||
397 | def copy(ui, repo, pats, opts, rename=False): |
|
253 | def copy(ui, repo, pats, opts, rename=False): | |
398 | # called with the repo lock held |
|
254 | # called with the repo lock held | |
399 | # |
|
255 | # |
@@ -2627,8 +2627,8 b' def import_(ui, repo, patch1, *patches, ' | |||||
2627 | patch.patch(tmpname, ui, strip=strip, cwd=repo.root, |
|
2627 | patch.patch(tmpname, ui, strip=strip, cwd=repo.root, | |
2628 | files=files, eolmode=None) |
|
2628 | files=files, eolmode=None) | |
2629 | finally: |
|
2629 | finally: | |
2630 |
files = |
|
2630 | files = patch.updatedir(ui, repo, files, | |
2631 |
|
|
2631 | similarity=sim / 100.0) | |
2632 | if opts.get('no_commit'): |
|
2632 | if opts.get('no_commit'): | |
2633 | if message: |
|
2633 | if message: | |
2634 | msgs.append(message) |
|
2634 | msgs.append(message) |
@@ -11,7 +11,7 b' import tempfile, zlib' | |||||
11 |
|
11 | |||
12 | from i18n import _ |
|
12 | from i18n import _ | |
13 | from node import hex, nullid, short |
|
13 | from node import hex, nullid, short | |
14 | import base85, mdiff, scmutil, util, diffhelpers, copies, encoding |
|
14 | import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, wdutil | |
15 |
|
15 | |||
16 | gitre = re.compile('diff --git a/(.*) b/(.*)') |
|
16 | gitre = re.compile('diff --git a/(.*) b/(.*)') | |
17 |
|
17 | |||
@@ -1157,6 +1157,49 b' def _applydiff(ui, fp, patcher, copyfn, ' | |||||
1157 | return -1 |
|
1157 | return -1 | |
1158 | return err |
|
1158 | return err | |
1159 |
|
1159 | |||
|
1160 | def updatedir(ui, repo, patches, similarity=0): | |||
|
1161 | '''Update dirstate after patch application according to metadata''' | |||
|
1162 | if not patches: | |||
|
1163 | return [] | |||
|
1164 | copies = [] | |||
|
1165 | removes = set() | |||
|
1166 | cfiles = patches.keys() | |||
|
1167 | cwd = repo.getcwd() | |||
|
1168 | if cwd: | |||
|
1169 | cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()] | |||
|
1170 | for f in patches: | |||
|
1171 | gp = patches[f] | |||
|
1172 | if not gp: | |||
|
1173 | continue | |||
|
1174 | if gp.op == 'RENAME': | |||
|
1175 | copies.append((gp.oldpath, gp.path)) | |||
|
1176 | removes.add(gp.oldpath) | |||
|
1177 | elif gp.op == 'COPY': | |||
|
1178 | copies.append((gp.oldpath, gp.path)) | |||
|
1179 | elif gp.op == 'DELETE': | |||
|
1180 | removes.add(gp.path) | |||
|
1181 | ||||
|
1182 | wctx = repo[None] | |||
|
1183 | for src, dst in copies: | |||
|
1184 | wdutil.dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd) | |||
|
1185 | if (not similarity) and removes: | |||
|
1186 | wctx.remove(sorted(removes), True) | |||
|
1187 | ||||
|
1188 | for f in patches: | |||
|
1189 | gp = patches[f] | |||
|
1190 | if gp and gp.mode: | |||
|
1191 | islink, isexec = gp.mode | |||
|
1192 | dst = repo.wjoin(gp.path) | |||
|
1193 | # patch won't create empty files | |||
|
1194 | if gp.op == 'ADD' and not os.path.lexists(dst): | |||
|
1195 | flags = (isexec and 'x' or '') + (islink and 'l' or '') | |||
|
1196 | repo.wwrite(gp.path, '', flags) | |||
|
1197 | util.setflags(dst, islink, isexec) | |||
|
1198 | wdutil.addremove(repo, cfiles, similarity=similarity) | |||
|
1199 | files = patches.keys() | |||
|
1200 | files.extend([r for r in removes if r not in files]) | |||
|
1201 | return sorted(files) | |||
|
1202 | ||||
1160 | def _externalpatch(patcher, patchname, ui, strip, cwd, files): |
|
1203 | def _externalpatch(patcher, patchname, ui, strip, cwd, files): | |
1161 | """use <patcher> to apply <patchname> to the working directory. |
|
1204 | """use <patcher> to apply <patchname> to the working directory. | |
1162 | returns whether patch was applied with fuzz factor.""" |
|
1205 | returns whether patch was applied with fuzz factor.""" |
General Comments 0
You need to be logged in to leave comments.
Login now