1# filelog.py - file history class for mercurial
2#
3# Copyright 2005-2007 Olivia Mackall <olivia@selenic.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
8from __future__ import absolute_import
9
10from .i18n import _
11from .node import nullrev
12from . import (
13    error,
14    revlog,
15)
16from .interfaces import (
17    repository,
18    util as interfaceutil,
19)
20from .utils import storageutil
21from .revlogutils import (
22    constants as revlog_constants,
23    rewrite,
24)
25
26
27@interfaceutil.implementer(repository.ifilestorage)
28class filelog(object):
29    def __init__(self, opener, path):
30        self._revlog = revlog.revlog(
31            opener,
32            # XXX should use the unencoded path
33            target=(revlog_constants.KIND_FILELOG, path),
34            radix=b'/'.join((b'data', path)),
35            censorable=True,
36        )
37        # Full name of the user visible file, relative to the repository root.
38        # Used by LFS.
39        self._revlog.filename = path
40        self.nullid = self._revlog.nullid
41        opts = opener.options
42        self._fix_issue6528 = opts.get(b'issue6528.fix-incoming', True)
43
44    def __len__(self):
45        return len(self._revlog)
46
47    def __iter__(self):
48        return self._revlog.__iter__()
49
50    def hasnode(self, node):
51        if node in (self.nullid, nullrev):
52            return False
53
54        try:
55            self._revlog.rev(node)
56            return True
57        except (TypeError, ValueError, IndexError, error.LookupError):
58            return False
59
60    def revs(self, start=0, stop=None):
61        return self._revlog.revs(start=start, stop=stop)
62
63    def parents(self, node):
64        return self._revlog.parents(node)
65
66    def parentrevs(self, rev):
67        return self._revlog.parentrevs(rev)
68
69    def rev(self, node):
70        return self._revlog.rev(node)
71
72    def node(self, rev):
73        return self._revlog.node(rev)
74
75    def lookup(self, node):
76        return storageutil.fileidlookup(
77            self._revlog, node, self._revlog.display_id
78        )
79
80    def linkrev(self, rev):
81        return self._revlog.linkrev(rev)
82
83    def commonancestorsheads(self, node1, node2):
84        return self._revlog.commonancestorsheads(node1, node2)
85
86    # Used by dagop.blockdescendants().
87    def descendants(self, revs):
88        return self._revlog.descendants(revs)
89
90    def heads(self, start=None, stop=None):
91        return self._revlog.heads(start, stop)
92
93    # Used by hgweb, children extension.
94    def children(self, node):
95        return self._revlog.children(node)
96
97    def iscensored(self, rev):
98        return self._revlog.iscensored(rev)
99
100    def revision(self, node, _df=None, raw=False):
101        return self._revlog.revision(node, _df=_df, raw=raw)
102
103    def rawdata(self, node, _df=None):
104        return self._revlog.rawdata(node, _df=_df)
105
106    def emitrevisions(
107        self,
108        nodes,
109        nodesorder=None,
110        revisiondata=False,
111        assumehaveparentrevisions=False,
112        deltamode=repository.CG_DELTAMODE_STD,
113        sidedata_helpers=None,
114    ):
115        return self._revlog.emitrevisions(
116            nodes,
117            nodesorder=nodesorder,
118            revisiondata=revisiondata,
119            assumehaveparentrevisions=assumehaveparentrevisions,
120            deltamode=deltamode,
121            sidedata_helpers=sidedata_helpers,
122        )
123
124    def addrevision(
125        self,
126        revisiondata,
127        transaction,
128        linkrev,
129        p1,
130        p2,
131        node=None,
132        flags=revlog.REVIDX_DEFAULT_FLAGS,
133        cachedelta=None,
134    ):
135        return self._revlog.addrevision(
136            revisiondata,
137            transaction,
138            linkrev,
139            p1,
140            p2,
141            node=node,
142            flags=flags,
143            cachedelta=cachedelta,
144        )
145
146    def addgroup(
147        self,
148        deltas,
149        linkmapper,
150        transaction,
151        addrevisioncb=None,
152        duplicaterevisioncb=None,
153        maybemissingparents=False,
154    ):
155        if maybemissingparents:
156            raise error.Abort(
157                _(
158                    b'revlog storage does not support missing '
159                    b'parents write mode'
160                )
161            )
162
163        with self._revlog._writing(transaction):
164
165            if self._fix_issue6528:
166                deltas = rewrite.filter_delta_issue6528(self._revlog, deltas)
167
168            return self._revlog.addgroup(
169                deltas,
170                linkmapper,
171                transaction,
172                addrevisioncb=addrevisioncb,
173                duplicaterevisioncb=duplicaterevisioncb,
174            )
175
176    def getstrippoint(self, minlink):
177        return self._revlog.getstrippoint(minlink)
178
179    def strip(self, minlink, transaction):
180        return self._revlog.strip(minlink, transaction)
181
182    def censorrevision(self, tr, node, tombstone=b''):
183        return self._revlog.censorrevision(tr, node, tombstone=tombstone)
184
185    def files(self):
186        return self._revlog.files()
187
188    def read(self, node):
189        return storageutil.filtermetadata(self.revision(node))
190
191    def add(self, text, meta, transaction, link, p1=None, p2=None):
192        if meta or text.startswith(b'\1\n'):
193            text = storageutil.packmeta(meta, text)
194        rev = self.addrevision(text, transaction, link, p1, p2)
195        return self.node(rev)
196
197    def renamed(self, node):
198        return storageutil.filerevisioncopied(self, node)
199
200    def size(self, rev):
201        """return the size of a given revision"""
202
203        # for revisions with renames, we have to go the slow way
204        node = self.node(rev)
205        if self.renamed(node):
206            return len(self.read(node))
207        if self.iscensored(rev):
208            return 0
209
210        # XXX if self.read(node).startswith("\1\n"), this returns (size+4)
211        return self._revlog.size(rev)
212
213    def cmp(self, node, text):
214        """compare text with a given file revision
215
216        returns True if text is different than what is stored.
217        """
218        return not storageutil.filedataequivalent(self, node, text)
219
220    def verifyintegrity(self, state):
221        return self._revlog.verifyintegrity(state)
222
223    def storageinfo(
224        self,
225        exclusivefiles=False,
226        sharedfiles=False,
227        revisionscount=False,
228        trackedsize=False,
229        storedsize=False,
230    ):
231        return self._revlog.storageinfo(
232            exclusivefiles=exclusivefiles,
233            sharedfiles=sharedfiles,
234            revisionscount=revisionscount,
235            trackedsize=trackedsize,
236            storedsize=storedsize,
237        )
238
239    # Used by repo upgrade.
240    def clone(self, tr, destrevlog, **kwargs):
241        if not isinstance(destrevlog, filelog):
242            raise error.ProgrammingError(b'expected filelog to clone()')
243
244        return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
245
246
247class narrowfilelog(filelog):
248    """Filelog variation to be used with narrow stores."""
249
250    def __init__(self, opener, path, narrowmatch):
251        super(narrowfilelog, self).__init__(opener, path)
252        self._narrowmatch = narrowmatch
253
254    def renamed(self, node):
255        res = super(narrowfilelog, self).renamed(node)
256
257        # Renames that come from outside the narrowspec are problematic
258        # because we may lack the base text for the rename. This can result
259        # in code attempting to walk the ancestry or compute a diff
260        # encountering a missing revision. We address this by silently
261        # removing rename metadata if the source file is outside the
262        # narrow spec.
263        #
264        # A better solution would be to see if the base revision is available,
265        # rather than assuming it isn't.
266        #
267        # An even better solution would be to teach all consumers of rename
268        # metadata that the base revision may not be available.
269        #
270        # TODO consider better ways of doing this.
271        if res and not self._narrowmatch(res[0]):
272            return None
273
274        return res
275
276    def size(self, rev):
277        # Because we have a custom renamed() that may lie, we need to call
278        # the base renamed() to report accurate results.
279        node = self.node(rev)
280        if super(narrowfilelog, self).renamed(node):
281            return len(self.read(node))
282        else:
283            return super(narrowfilelog, self).size(rev)
284
285    def cmp(self, node, text):
286        # We don't call `super` because narrow parents can be buggy in case of a
287        # ambiguous dirstate. Always take the slow path until there is a better
288        # fix, see issue6150.
289
290        # Censored files compare against the empty file.
291        if self.iscensored(self.rev(node)):
292            return text != b''
293
294        return self.read(node) != text
295