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