Changeset View
Changeset View
Standalone View
Standalone View
mercurial/patch.py
Show All 9 Lines | |||||
import collections | import collections | ||||
import contextlib | import contextlib | ||||
import copy | import copy | ||||
import email | import email | ||||
import errno | import errno | ||||
import hashlib | import hashlib | ||||
import os | import os | ||||
import posixpath | |||||
import re | import re | ||||
import shutil | import shutil | ||||
import zlib | import zlib | ||||
from .i18n import _ | from .i18n import _ | ||||
from .node import ( | from .node import ( | ||||
hex, | hex, | ||||
short, | short, | ||||
▲ Show 20 Lines • Show All 2207 Lines • ▼ Show 20 Line(s) | |||||
class GitDiffRequired(Exception): | class GitDiffRequired(Exception): | ||||
pass | pass | ||||
diffopts = diffutil.diffallopts | diffopts = diffutil.diffallopts | ||||
diffallopts = diffutil.diffallopts | diffallopts = diffutil.diffallopts | ||||
difffeatureopts = diffutil.difffeatureopts | difffeatureopts = diffutil.difffeatureopts | ||||
def diff(repo, node1=None, node2=None, match=None, changes=None, | def diff(repo, node1=None, node2=None, match=None, changes=None, | ||||
opts=None, losedatafn=None, prefix='', relroot='', copy=None, | opts=None, losedatafn=None, pathfn=None, copy=None, | ||||
copysourcematch=None, hunksfilterfn=None): | copysourcematch=None, hunksfilterfn=None): | ||||
'''yields diff of changes to files between two nodes, or node and | '''yields diff of changes to files between two nodes, or node and | ||||
working directory. | working directory. | ||||
if node1 is None, use first dirstate parent instead. | if node1 is None, use first dirstate parent instead. | ||||
if node2 is None, compare node1 with working directory. | if node2 is None, compare node1 with working directory. | ||||
losedatafn(**kwarg) is a callable run when opts.upgrade=True and | losedatafn(**kwarg) is a callable run when opts.upgrade=True and | ||||
Show All 21 Lines | def diff(repo, node1=None, node2=None, match=None, changes=None, | ||||
''' | ''' | ||||
if not node1 and not node2: | if not node1 and not node2: | ||||
node1 = repo.dirstate.p1() | node1 = repo.dirstate.p1() | ||||
ctx1 = repo[node1] | ctx1 = repo[node1] | ||||
ctx2 = repo[node2] | ctx2 = repo[node2] | ||||
for fctx1, fctx2, hdr, hunks in diffhunks( | for fctx1, fctx2, hdr, hunks in diffhunks( | ||||
repo, ctx1=ctx1, ctx2=ctx2, | repo, ctx1=ctx1, ctx2=ctx2, match=match, changes=changes, opts=opts, | ||||
match=match, changes=changes, opts=opts, | losedatafn=losedatafn, pathfn=pathfn, copy=copy, | ||||
losedatafn=losedatafn, prefix=prefix, relroot=relroot, copy=copy, | |||||
copysourcematch=copysourcematch): | copysourcematch=copysourcematch): | ||||
if hunksfilterfn is not None: | if hunksfilterfn is not None: | ||||
# If the file has been removed, fctx2 is None; but this should | # If the file has been removed, fctx2 is None; but this should | ||||
# not occur here since we catch removed files early in | # not occur here since we catch removed files early in | ||||
# logcmdutil.getlinerangerevs() for 'hg log -L'. | # logcmdutil.getlinerangerevs() for 'hg log -L'. | ||||
assert fctx2 is not None, \ | assert fctx2 is not None, \ | ||||
'fctx2 unexpectly None in diff hunks filtering' | 'fctx2 unexpectly None in diff hunks filtering' | ||||
hunks = hunksfilterfn(fctx2, hunks) | hunks = hunksfilterfn(fctx2, hunks) | ||||
text = ''.join(sum((list(hlines) for hrange, hlines in hunks), [])) | text = ''.join(sum((list(hlines) for hrange, hlines in hunks), [])) | ||||
if hdr and (text or len(hdr) > 1): | if hdr and (text or len(hdr) > 1): | ||||
yield '\n'.join(hdr) + '\n' | yield '\n'.join(hdr) + '\n' | ||||
if text: | if text: | ||||
yield text | yield text | ||||
def diffhunks(repo, ctx1, ctx2, match=None, changes=None, | def diffhunks(repo, ctx1, ctx2, match=None, changes=None, opts=None, | ||||
opts=None, losedatafn=None, prefix='', relroot='', copy=None, | losedatafn=None, pathfn=None, copy=None, copysourcematch=None): | ||||
copysourcematch=None): | |||||
"""Yield diff of changes to files in the form of (`header`, `hunks`) tuples | """Yield diff of changes to files in the form of (`header`, `hunks`) tuples | ||||
where `header` is a list of diff headers and `hunks` is an iterable of | where `header` is a list of diff headers and `hunks` is an iterable of | ||||
(`hunkrange`, `hunklines`) tuples. | (`hunkrange`, `hunklines`) tuples. | ||||
See diff() for the meaning of parameters. | See diff() for the meaning of parameters. | ||||
""" | """ | ||||
if opts is None: | if opts is None: | ||||
▲ Show 20 Lines • Show All 63 Lines • ▼ Show 20 Line(s) | for dst, src in list(copy.items()): | ||||
del copy[dst] | del copy[dst] | ||||
prefetchmatch = scmutil.matchfiles( | prefetchmatch = scmutil.matchfiles( | ||||
repo, list(modifiedset | addedset | removedset)) | repo, list(modifiedset | addedset | removedset)) | ||||
scmutil.prefetchfiles(repo, [ctx1.rev(), ctx2.rev()], prefetchmatch) | scmutil.prefetchfiles(repo, [ctx1.rev(), ctx2.rev()], prefetchmatch) | ||||
def difffn(opts, losedata): | def difffn(opts, losedata): | ||||
return trydiff(repo, revs, ctx1, ctx2, modified, added, removed, | return trydiff(repo, revs, ctx1, ctx2, modified, added, removed, | ||||
copy, getfilectx, opts, losedata, prefix, relroot) | copy, getfilectx, opts, losedata, pathfn) | ||||
if opts.upgrade and not opts.git: | if opts.upgrade and not opts.git: | ||||
try: | try: | ||||
def losedata(fn): | def losedata(fn): | ||||
if not losedatafn or not losedatafn(fn=fn): | if not losedatafn or not losedatafn(fn=fn): | ||||
raise GitDiffRequired | raise GitDiffRequired | ||||
# Buffer the whole output until we are sure it can be generated | # Buffer the whole output until we are sure it can be generated | ||||
return list(difffn(opts.copy(git=False), losedata)) | return list(difffn(opts.copy(git=False), losedata)) | ||||
except GitDiffRequired: | except GitDiffRequired: | ||||
▲ Show 20 Lines • Show All 198 Lines • ▼ Show 20 Line(s) | for f in sorted(modified + added + removed): | ||||
if opts.git: | if opts.git: | ||||
# have we already reported a copy above? | # have we already reported a copy above? | ||||
if (f in copyto and copyto[f] in addedset | if (f in copyto and copyto[f] in addedset | ||||
and copy[copyto[f]] == f): | and copy[copyto[f]] == f): | ||||
continue | continue | ||||
yield f1, f2, copyop | yield f1, f2, copyop | ||||
def trydiff(repo, revs, ctx1, ctx2, modified, added, removed, | def trydiff(repo, revs, ctx1, ctx2, modified, added, removed, | ||||
copy, getfilectx, opts, losedatafn, prefix, relroot): | copy, getfilectx, opts, losedatafn, pathfn): | ||||
'''given input data, generate a diff and yield it in blocks | '''given input data, generate a diff and yield it in blocks | ||||
If generating a diff would lose data like flags or binary data and | If generating a diff would lose data like flags or binary data and | ||||
losedatafn is not None, it will be called. | losedatafn is not None, it will be called. | ||||
relroot is removed and prefix is added to every path in the diff output. | pathfn is applied to every path in the diff output. | ||||
''' | |||||
If relroot is not empty, this function expects every path in modified, | |||||
added, removed and copy to start with it.''' | |||||
def gitindex(text): | def gitindex(text): | ||||
if not text: | if not text: | ||||
text = "" | text = "" | ||||
l = len(text) | l = len(text) | ||||
s = hashlib.sha1('blob %d\0' % l) | s = hashlib.sha1('blob %d\0' % l) | ||||
s.update(text) | s.update(text) | ||||
return hex(s.digest()) | return hex(s.digest()) | ||||
Show All 11 Lines | def trydiff(repo, revs, ctx1, ctx2, modified, added, removed, | ||||
def isempty(fctx): | def isempty(fctx): | ||||
return fctx is None or fctx.size() == 0 | return fctx is None or fctx.size() == 0 | ||||
date1 = dateutil.datestr(ctx1.date()) | date1 = dateutil.datestr(ctx1.date()) | ||||
date2 = dateutil.datestr(ctx2.date()) | date2 = dateutil.datestr(ctx2.date()) | ||||
gitmode = {'l': '120000', 'x': '100755', '': '100644'} | gitmode = {'l': '120000', 'x': '100755', '': '100644'} | ||||
if relroot != '' and (repo.ui.configbool('devel', 'all-warnings') | if not pathfn: | ||||
or repo.ui.configbool('devel', 'check-relroot')): | pathfn = lambda f: f | ||||
for f in modified + added + removed + list(copy) + list(copy.values()): | |||||
if f is not None and not f.startswith(relroot): | |||||
raise AssertionError( | |||||
"file %s doesn't start with relroot %s" % (f, relroot)) | |||||
for f1, f2, copyop in _filepairs(modified, added, removed, copy, opts): | for f1, f2, copyop in _filepairs(modified, added, removed, copy, opts): | ||||
content1 = None | content1 = None | ||||
content2 = None | content2 = None | ||||
fctx1 = None | fctx1 = None | ||||
fctx2 = None | fctx2 = None | ||||
flag1 = None | flag1 = None | ||||
flag2 = None | flag2 = None | ||||
Show All 20 Lines | for f1, f2, copyop in _filepairs(modified, added, removed, copy, opts): | ||||
# empty file deletion | # empty file deletion | ||||
(isempty(fctx1) and not f2) or | (isempty(fctx1) and not f2) or | ||||
# create with flags | # create with flags | ||||
(not f1 and flag2) or | (not f1 and flag2) or | ||||
# change flags | # change flags | ||||
(f1 and f2 and flag1 != flag2)): | (f1 and f2 and flag1 != flag2)): | ||||
losedatafn(f2 or f1) | losedatafn(f2 or f1) | ||||
path1 = f1 or f2 | path1 = pathfn(f1 or f2) | ||||
path2 = f2 or f1 | path2 = pathfn(f2 or f1) | ||||
path1 = posixpath.join(prefix, path1[len(relroot):]) | |||||
path2 = posixpath.join(prefix, path2[len(relroot):]) | |||||
header = [] | header = [] | ||||
if opts.git: | if opts.git: | ||||
header.append('diff --git %s%s %s%s' % | header.append('diff --git %s%s %s%s' % | ||||
(aprefix, path1, bprefix, path2)) | (aprefix, path1, bprefix, path2)) | ||||
if not f1: # added | if not f1: # added | ||||
header.append('new file mode %s' % gitmode[flag2]) | header.append('new file mode %s' % gitmode[flag2]) | ||||
elif not f2: # removed | elif not f2: # removed | ||||
header.append('deleted file mode %s' % gitmode[flag1]) | header.append('deleted file mode %s' % gitmode[flag1]) | ||||
▲ Show 20 Lines • Show All 177 Lines • Show Last 20 Lines |