2018-01-03 22:51:20 +03:00
|
|
|
# no-check-code -- see T24862348
|
|
|
|
|
2010-07-08 23:46:54 +04:00
|
|
|
import errno
|
2018-07-06 03:45:27 +03:00
|
|
|
import os
|
|
|
|
import shutil
|
2008-09-30 20:42:52 +04:00
|
|
|
import sys
|
2012-10-06 11:59:55 +04:00
|
|
|
import tempfile
|
2008-09-30 20:42:52 +04:00
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
import svnexternals
|
2010-03-31 19:51:09 +04:00
|
|
|
import svnwrap
|
2009-04-10 21:43:44 +04:00
|
|
|
import util
|
2019-01-30 03:25:33 +03:00
|
|
|
from edenscm.mercurial import match as matchmod, node, revlog, util as hgutil
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2018-12-07 04:50:35 +03:00
|
|
|
from ..extlib import cstore
|
|
|
|
|
2008-10-17 20:03:52 +04:00
|
|
|
|
2012-09-26 23:01:17 +04:00
|
|
|
class EditingError(Exception):
|
|
|
|
pass
|
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2012-10-06 11:59:55 +04:00
|
|
|
class FileStore(object):
|
|
|
|
def __init__(self, maxsize=None):
|
|
|
|
self._tempdir = None
|
|
|
|
self._files = {}
|
|
|
|
self._created = 0
|
|
|
|
self._maxsize = maxsize
|
|
|
|
if self._maxsize is None:
|
2018-07-06 03:45:27 +03:00
|
|
|
self._maxsize = 100 * (2 ** 20)
|
2012-10-06 11:59:55 +04:00
|
|
|
self._size = 0
|
|
|
|
self._data = {}
|
2012-09-26 23:01:17 +04:00
|
|
|
self._popped = set()
|
2012-10-06 11:59:55 +04:00
|
|
|
|
|
|
|
def setfile(self, fname, data):
|
2012-09-26 23:01:17 +04:00
|
|
|
if fname in self._popped:
|
2018-07-06 03:45:27 +03:00
|
|
|
raise EditingError("trying to set a popped file %s" % fname)
|
2012-09-26 23:01:17 +04:00
|
|
|
|
editor: fix edge case with in memory file-store size limit
There are a few cases where we will set a single file into to the
editor's FileStore object more than once. Notably, for copied and
then modified files, we will set it at least twice. Three times if
editing fails (which it can for symlinks).
If we pass the in-memory storage limit in between the first (or second
if editing fails) time we set the file and the last time we set the
file, we will write the data to the in memory store the first time and
the file store the last time. We didn't remove it form the in-memory
store though, and we always prefer reading from the in-memory store.
This means we can sometimes end up with the wrong version of a file.
This is fairly unlikely to happen in normal use since you need to hit
the memory limit between two writes to the store for the same file.
We only write a file multiple times if a) the file (and not one of
it's parent directories) is copied and then modified or b) editing
fails. From what I can tell, it's only common for editing to fail for
symlinks, and they ten to be relatively small data that is unlikely to
push over the limit. Finally, the default limit is 100MB which I
would expect to be most often either well over (source code) or well
under (binaries or automated changes) the size of the changes files in
a single commit.
The easiest way to reproduce this is to set the in-memory cache size
to 0 and then commit a copied and modified symlink. The empty-string
version from the failed editing will be the one that persists. I
happened to stumble upon this while trying (and failing) to test a
bug-fix for a related bug with identical symptoms (empty simlink). I
have seen this in the wild, once, but couldn't reproduce it at the
time. The repo in question is quite large and quite active, so I am
quite confident in my estimation that this is a real, but very rare,
problem.
The test changes attached to this was mneant to test a related bug,
but turned out not to actually cover the bug in question. They did
trigger this bug though, and are worthwhile to test, so I kept them.
2014-04-08 04:51:59 +04:00
|
|
|
if fname in self._data:
|
|
|
|
self._size -= len(self._data[fname])
|
|
|
|
del self._data[fname]
|
|
|
|
|
|
|
|
if fname in self._files:
|
|
|
|
del self._files[fname]
|
|
|
|
|
2012-10-06 11:59:55 +04:00
|
|
|
if self._maxsize < 0 or (len(data) + self._size) <= self._maxsize:
|
|
|
|
self._data[fname] = data
|
|
|
|
self._size += len(data)
|
|
|
|
else:
|
|
|
|
if self._tempdir is None:
|
2018-07-06 03:45:27 +03:00
|
|
|
self._tempdir = tempfile.mkdtemp(prefix="hg-subversion-")
|
2012-10-06 11:59:55 +04:00
|
|
|
# Avoid filename issues with these simple names
|
|
|
|
fn = str(self._created)
|
2018-07-06 03:45:27 +03:00
|
|
|
fp = hgutil.posixfile(os.path.join(self._tempdir, fn), "wb")
|
2012-10-06 11:59:55 +04:00
|
|
|
try:
|
|
|
|
fp.write(data)
|
|
|
|
finally:
|
|
|
|
fp.close()
|
|
|
|
self._created += 1
|
|
|
|
self._files[fname] = fn
|
|
|
|
|
|
|
|
def delfile(self, fname):
|
2012-09-26 23:01:17 +04:00
|
|
|
if fname in self._popped:
|
2018-07-06 03:45:27 +03:00
|
|
|
raise EditingError("trying to delete a popped file %s" % fname)
|
2012-09-26 23:01:17 +04:00
|
|
|
|
2012-10-06 11:59:55 +04:00
|
|
|
if fname in self._data:
|
|
|
|
del self._data[fname]
|
|
|
|
elif fname in self._files:
|
|
|
|
path = os.path.join(self._tempdir, self._files.pop(fname))
|
|
|
|
os.unlink(path)
|
|
|
|
|
|
|
|
def getfile(self, fname):
|
2012-09-26 23:01:17 +04:00
|
|
|
if fname in self._popped:
|
2018-07-06 03:45:27 +03:00
|
|
|
raise EditingError("trying to get a popped file %s" % fname)
|
2012-09-26 23:01:17 +04:00
|
|
|
|
2012-10-06 11:59:55 +04:00
|
|
|
if fname in self._data:
|
|
|
|
return self._data[fname]
|
|
|
|
if self._tempdir is None or fname not in self._files:
|
|
|
|
raise IOError
|
|
|
|
path = os.path.join(self._tempdir, self._files[fname])
|
2018-07-06 03:45:27 +03:00
|
|
|
fp = hgutil.posixfile(path, "rb")
|
2012-10-06 11:59:55 +04:00
|
|
|
try:
|
|
|
|
return fp.read()
|
|
|
|
finally:
|
|
|
|
fp.close()
|
|
|
|
|
2012-09-26 23:01:17 +04:00
|
|
|
def popfile(self, fname):
|
|
|
|
self.delfile(fname)
|
|
|
|
self._popped.add(fname)
|
|
|
|
|
2012-10-06 11:59:55 +04:00
|
|
|
def files(self):
|
|
|
|
return list(self._files) + list(self._data)
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
if self._tempdir is not None:
|
|
|
|
tempdir, self._tempdir = self._tempdir, None
|
|
|
|
shutil.rmtree(tempdir)
|
|
|
|
self._files = None
|
|
|
|
self._data = None
|
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2009-06-11 12:12:16 +04:00
|
|
|
class RevisionData(object):
|
|
|
|
|
|
|
|
__slots__ = [
|
2018-07-06 03:45:27 +03:00
|
|
|
"file",
|
|
|
|
"added",
|
|
|
|
"deleted",
|
|
|
|
"rev",
|
|
|
|
"execfiles",
|
|
|
|
"symlinks",
|
|
|
|
"copies",
|
|
|
|
"emptybranches",
|
|
|
|
"base",
|
|
|
|
"externals",
|
|
|
|
"ui",
|
|
|
|
"exception",
|
|
|
|
"store",
|
2009-06-11 12:12:16 +04:00
|
|
|
]
|
|
|
|
|
2009-06-16 10:41:28 +04:00
|
|
|
def __init__(self, ui):
|
|
|
|
self.ui = ui
|
2009-06-11 12:12:16 +04:00
|
|
|
self.clear()
|
|
|
|
|
|
|
|
def clear(self):
|
2018-07-06 03:45:27 +03:00
|
|
|
oldstore = getattr(self, "store", None)
|
2016-06-27 04:06:50 +03:00
|
|
|
if oldstore is not None:
|
|
|
|
oldstore.close()
|
2012-09-28 23:43:50 +04:00
|
|
|
self.store = FileStore(util.getfilestoresize(self.ui))
|
2012-05-13 17:28:50 +04:00
|
|
|
self.added = set()
|
2009-06-11 12:12:16 +04:00
|
|
|
self.deleted = {}
|
|
|
|
self.rev = None
|
|
|
|
self.execfiles = {}
|
|
|
|
self.symlinks = {}
|
|
|
|
# Map fully qualified destination file paths to module source path
|
|
|
|
self.copies = {}
|
|
|
|
self.emptybranches = {}
|
|
|
|
self.externals = {}
|
2009-06-16 10:42:15 +04:00
|
|
|
self.exception = None
|
2009-06-11 12:12:16 +04:00
|
|
|
|
2012-09-25 23:34:16 +04:00
|
|
|
def set(self, path, data, isexec=False, islink=False, copypath=None):
|
2012-10-06 11:59:55 +04:00
|
|
|
self.store.setfile(path, data)
|
2009-06-16 10:41:28 +04:00
|
|
|
self.execfiles[path] = isexec
|
|
|
|
self.symlinks[path] = islink
|
|
|
|
if path in self.deleted:
|
|
|
|
del self.deleted[path]
|
2012-09-25 23:34:16 +04:00
|
|
|
if copypath is not None:
|
|
|
|
self.copies[path] = copypath
|
2009-06-16 10:41:28 +04:00
|
|
|
|
2012-10-06 11:59:55 +04:00
|
|
|
def get(self, path):
|
|
|
|
if path in self.deleted:
|
2018-07-06 03:45:27 +03:00
|
|
|
raise IOError(errno.ENOENT, "%s is deleted" % path)
|
2012-10-06 11:59:55 +04:00
|
|
|
data = self.store.getfile(path)
|
|
|
|
isexec = self.execfiles.get(path)
|
|
|
|
islink = self.symlinks.get(path)
|
|
|
|
copied = self.copies.get(path)
|
|
|
|
return data, isexec, islink, copied
|
|
|
|
|
2012-09-26 23:01:17 +04:00
|
|
|
def pop(self, path):
|
|
|
|
ret = self.get(path)
|
|
|
|
self.store.popfile(path)
|
|
|
|
return ret
|
|
|
|
|
2009-06-16 10:41:28 +04:00
|
|
|
def delete(self, path):
|
|
|
|
self.deleted[path] = True
|
2012-10-06 11:59:55 +04:00
|
|
|
self.store.delfile(path)
|
2009-06-16 10:41:28 +04:00
|
|
|
self.execfiles[path] = False
|
|
|
|
self.symlinks[path] = False
|
2018-07-06 03:45:27 +03:00
|
|
|
self.ui.note("D %s\n" % path)
|
2009-06-16 10:41:28 +04:00
|
|
|
|
2012-10-06 11:59:55 +04:00
|
|
|
def files(self):
|
|
|
|
"""Return a sorted list of changed files."""
|
|
|
|
files = set(self.store.files())
|
|
|
|
for g in (self.symlinks, self.execfiles, self.deleted):
|
|
|
|
files.update(g)
|
|
|
|
return sorted(files)
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
self.store.close()
|
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2012-09-25 01:44:23 +04:00
|
|
|
class CopiedFile(object):
|
|
|
|
def __init__(self, node, path, copypath):
|
|
|
|
self.node = node
|
|
|
|
self.path = path
|
|
|
|
self.copypath = copypath
|
|
|
|
|
2012-10-06 11:42:07 +04:00
|
|
|
def resolve(self, getctxfn, ctx=None):
|
2012-09-25 01:44:23 +04:00
|
|
|
if ctx is None:
|
2012-10-06 11:42:07 +04:00
|
|
|
ctx = getctxfn(self.node)
|
2012-09-25 01:44:23 +04:00
|
|
|
fctx = ctx[self.path]
|
|
|
|
data = fctx.data()
|
|
|
|
flags = fctx.flags()
|
2018-07-06 03:45:27 +03:00
|
|
|
islink = "l" in flags
|
2012-09-25 01:44:23 +04:00
|
|
|
if islink:
|
2018-07-06 03:45:27 +03:00
|
|
|
data = "link " + data
|
|
|
|
return data, "x" in flags, islink, self.copypath
|
2012-09-25 01:44:23 +04:00
|
|
|
|
2008-10-16 05:47:48 +04:00
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
class HgEditor(svnwrap.Editor):
|
2009-06-11 20:49:52 +04:00
|
|
|
def __init__(self, meta):
|
|
|
|
self.meta = meta
|
|
|
|
self.ui = meta.ui
|
|
|
|
self.repo = meta.repo
|
2009-06-16 10:41:28 +04:00
|
|
|
self.current = RevisionData(meta.ui)
|
editor: implement file batons
The concept of current.file is incorrect, svn_delta.h documents open
file lifetime as:
* 5. When the producer calls @c open_file or @c add_file, either:
*
* (a) The producer must follow with any changes to the file
* (@c change_file_prop and/or @c apply_textdelta, as applicable),
* followed by a @c close_file call, before issuing any other file
* or directory calls, or
*
* (b) The producer must follow with a @c change_file_prop call if
* it is applicable, before issuing any other file or directory
* calls; later, after all directory batons including the root
* have been closed, the producer must issue @c apply_textdelta
* and @c close_file calls.
So, an open file can be kept open until after the root directory is
closed and have deltas applied afterwards. In the meantime, other files
may have been opened and patched, overwriting the current.file variable.
This patch fixes it by introducing file batons bound to file paths, and
using them to deduce the correct target in apply_textdelta(). In theory,
open files could be put in a staging area until they are closed and
moved in the RevisionData. But the current code registers files copied
during a directory copy as open files and these will not receive a
close_file() event. This separation will be enforced later.
2012-09-23 21:52:48 +04:00
|
|
|
self._clear()
|
|
|
|
|
2012-10-21 00:22:02 +04:00
|
|
|
def setsvn(self, svn):
|
|
|
|
self._svn = svn
|
|
|
|
|
editor: implement file batons
The concept of current.file is incorrect, svn_delta.h documents open
file lifetime as:
* 5. When the producer calls @c open_file or @c add_file, either:
*
* (a) The producer must follow with any changes to the file
* (@c change_file_prop and/or @c apply_textdelta, as applicable),
* followed by a @c close_file call, before issuing any other file
* or directory calls, or
*
* (b) The producer must follow with a @c change_file_prop call if
* it is applicable, before issuing any other file or directory
* calls; later, after all directory batons including the root
* have been closed, the producer must issue @c apply_textdelta
* and @c close_file calls.
So, an open file can be kept open until after the root directory is
closed and have deltas applied afterwards. In the meantime, other files
may have been opened and patched, overwriting the current.file variable.
This patch fixes it by introducing file batons bound to file paths, and
using them to deduce the correct target in apply_textdelta(). In theory,
open files could be put in a staging area until they are closed and
moved in the RevisionData. But the current code registers files copied
during a directory copy as open files and these will not receive a
close_file() event. This separation will be enforced later.
2012-09-23 21:52:48 +04:00
|
|
|
def _clear(self):
|
|
|
|
self._filecounter = 0
|
2012-09-25 01:44:23 +04:00
|
|
|
# A mapping of svn paths to CopiedFile entries
|
2012-10-03 23:27:02 +04:00
|
|
|
self._svncopies = {}
|
2012-09-25 01:12:01 +04:00
|
|
|
# A mapping of batons to (path, data, isexec, islink, copypath) tuples
|
2012-09-27 00:18:31 +04:00
|
|
|
# data is a SimpleStringIO if the file was edited, a string
|
|
|
|
# otherwise.
|
2012-09-25 01:12:01 +04:00
|
|
|
self._openfiles = {}
|
|
|
|
# A mapping of file paths to batons
|
|
|
|
self._openpaths = {}
|
2012-10-03 23:04:37 +04:00
|
|
|
self._deleted = set()
|
2013-08-05 22:49:53 +04:00
|
|
|
self._getctx = hgutil.lrucachefunc(self.repo.changectx)
|
2015-09-26 17:49:57 +03:00
|
|
|
# A map from directory baton to path
|
|
|
|
self._opendirs = {}
|
2012-10-21 00:22:02 +04:00
|
|
|
self._missing = set()
|
2018-04-07 00:40:16 +03:00
|
|
|
self._manifestfiles = None
|
2012-09-25 01:12:01 +04:00
|
|
|
|
|
|
|
def _openfile(self, path, data, isexec, islink, copypath, create=False):
|
|
|
|
if path in self._openpaths:
|
2018-07-06 03:45:27 +03:00
|
|
|
raise EditingError("trying to open an already opened file %s" % path)
|
2012-10-03 23:04:37 +04:00
|
|
|
if not create and path in self._deleted:
|
2018-07-06 03:45:27 +03:00
|
|
|
raise EditingError("trying to open a deleted file %s" % path)
|
2012-10-03 23:04:37 +04:00
|
|
|
if path in self._deleted:
|
|
|
|
self._deleted.remove(path)
|
editor: implement file batons
The concept of current.file is incorrect, svn_delta.h documents open
file lifetime as:
* 5. When the producer calls @c open_file or @c add_file, either:
*
* (a) The producer must follow with any changes to the file
* (@c change_file_prop and/or @c apply_textdelta, as applicable),
* followed by a @c close_file call, before issuing any other file
* or directory calls, or
*
* (b) The producer must follow with a @c change_file_prop call if
* it is applicable, before issuing any other file or directory
* calls; later, after all directory batons including the root
* have been closed, the producer must issue @c apply_textdelta
* and @c close_file calls.
So, an open file can be kept open until after the root directory is
closed and have deltas applied afterwards. In the meantime, other files
may have been opened and patched, overwriting the current.file variable.
This patch fixes it by introducing file batons bound to file paths, and
using them to deduce the correct target in apply_textdelta(). In theory,
open files could be put in a staging area until they are closed and
moved in the RevisionData. But the current code registers files copied
during a directory copy as open files and these will not receive a
close_file() event. This separation will be enforced later.
2012-09-23 21:52:48 +04:00
|
|
|
self._filecounter += 1
|
2018-07-06 03:45:27 +03:00
|
|
|
baton = "f%d-%s" % (self._filecounter, path)
|
2012-09-25 01:12:01 +04:00
|
|
|
self._openfiles[baton] = (path, data, isexec, islink, copypath)
|
|
|
|
self._openpaths[path] = baton
|
editor: implement file batons
The concept of current.file is incorrect, svn_delta.h documents open
file lifetime as:
* 5. When the producer calls @c open_file or @c add_file, either:
*
* (a) The producer must follow with any changes to the file
* (@c change_file_prop and/or @c apply_textdelta, as applicable),
* followed by a @c close_file call, before issuing any other file
* or directory calls, or
*
* (b) The producer must follow with a @c change_file_prop call if
* it is applicable, before issuing any other file or directory
* calls; later, after all directory batons including the root
* have been closed, the producer must issue @c apply_textdelta
* and @c close_file calls.
So, an open file can be kept open until after the root directory is
closed and have deltas applied afterwards. In the meantime, other files
may have been opened and patched, overwriting the current.file variable.
This patch fixes it by introducing file batons bound to file paths, and
using them to deduce the correct target in apply_textdelta(). In theory,
open files could be put in a staging area until they are closed and
moved in the RevisionData. But the current code registers files copied
during a directory copy as open files and these will not receive a
close_file() event. This separation will be enforced later.
2012-09-23 21:52:48 +04:00
|
|
|
return baton
|
2009-06-11 10:40:20 +04:00
|
|
|
|
2012-10-14 16:26:53 +04:00
|
|
|
def _opendir(self, path):
|
|
|
|
self._filecounter += 1
|
2018-07-06 03:45:27 +03:00
|
|
|
baton = "f%d-%s" % (self._filecounter, path)
|
2015-09-26 17:49:57 +03:00
|
|
|
self._opendirs[baton] = path
|
2012-10-14 16:26:53 +04:00
|
|
|
return baton
|
|
|
|
|
|
|
|
def _checkparentdir(self, baton):
|
2015-09-26 17:49:57 +03:00
|
|
|
if not self._opendirs or baton not in self._opendirs:
|
2018-07-06 03:45:27 +03:00
|
|
|
raise EditingError(
|
|
|
|
"trying to operate on an already closed " "directory: %s" % baton
|
|
|
|
)
|
2012-10-14 16:26:53 +04:00
|
|
|
|
2012-10-03 23:04:37 +04:00
|
|
|
def _deletefile(self, path):
|
2012-10-16 00:24:29 +04:00
|
|
|
if self.meta.is_path_valid(path):
|
|
|
|
self._deleted.add(path)
|
2012-10-03 23:04:37 +04:00
|
|
|
if path in self._svncopies:
|
|
|
|
del self._svncopies[path]
|
2012-10-21 00:22:02 +04:00
|
|
|
self._missing.discard(path)
|
|
|
|
|
|
|
|
def addmissing(self, path, isdir=False):
|
|
|
|
svn = self._svn
|
2018-07-06 03:45:27 +03:00
|
|
|
root = svn.subdir and svn.subdir[1:] or ""
|
2012-10-21 00:22:02 +04:00
|
|
|
if not isdir:
|
2018-07-06 03:45:27 +03:00
|
|
|
self._missing.add(path[len(root) :])
|
2012-10-21 00:22:02 +04:00
|
|
|
else:
|
|
|
|
# Resolve missing directories content immediately so the
|
|
|
|
# missing files maybe processed by delete actions.
|
2013-11-17 21:57:00 +04:00
|
|
|
# we remove the missing directory entries to deal with the case
|
|
|
|
# where a directory is replaced from e.g. a closed branch
|
|
|
|
# this will show up as a delete and then a copy
|
|
|
|
# we process deletes after missing, so we can handle a directory
|
|
|
|
# copy plus delete of file in that directory. This means that we
|
|
|
|
# need to be sure that only things whose final disposition is
|
|
|
|
# deletion remain in self._deleted at the end of the editing process.
|
2012-10-21 00:22:02 +04:00
|
|
|
rev = self.current.rev.revnum
|
2018-07-06 03:45:27 +03:00
|
|
|
path = path + "/"
|
|
|
|
parentdir = path[len(root) :]
|
2012-10-21 00:22:02 +04:00
|
|
|
for f, k in svn.list_files(parentdir, rev):
|
2018-07-06 03:45:27 +03:00
|
|
|
if k != "f":
|
2012-10-21 00:22:02 +04:00
|
|
|
continue
|
|
|
|
f = parentdir + f
|
|
|
|
if not self.meta.is_path_valid(f, False):
|
|
|
|
continue
|
2013-11-17 21:57:00 +04:00
|
|
|
self._deleted.discard(f)
|
2012-10-21 00:22:02 +04:00
|
|
|
self._missing.add(f)
|
2012-10-03 23:04:37 +04:00
|
|
|
|
2018-04-07 00:40:16 +03:00
|
|
|
def get_files_in_dir(self, ctx, dir):
|
2018-07-06 03:45:27 +03:00
|
|
|
assert dir == "" or dir.endswith("/")
|
2018-11-16 22:03:32 +03:00
|
|
|
mf = ctx.manifest()
|
|
|
|
if isinstance(mf, cstore.treemanifest):
|
2018-12-07 04:50:35 +03:00
|
|
|
matcher = matchmod.match("", "/", patterns=[dir], default="path")
|
2018-11-16 22:03:32 +03:00
|
|
|
for x in mf.walk(matcher):
|
|
|
|
yield x
|
|
|
|
else:
|
|
|
|
if self._manifestfiles is None:
|
|
|
|
self._manifestfiles = mf.text().splitlines()
|
2018-04-07 00:40:16 +03:00
|
|
|
|
2018-11-16 22:03:32 +03:00
|
|
|
files = self._manifestfiles
|
|
|
|
import bisect
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2018-11-16 22:03:32 +03:00
|
|
|
cur = bisect.bisect_left(files, dir)
|
2018-04-07 00:40:16 +03:00
|
|
|
|
2018-11-16 22:03:32 +03:00
|
|
|
while cur < len(files) and files[cur].startswith(dir):
|
|
|
|
yield files[cur].split("\0")[0]
|
|
|
|
cur += 1
|
2018-04-07 00:40:16 +03:00
|
|
|
|
2010-08-11 21:57:35 +04:00
|
|
|
@svnwrap.ieditor
|
2008-09-30 20:42:52 +04:00
|
|
|
def delete_entry(self, path, revision_bogus, parent_baton, pool=None):
|
2012-10-14 16:26:53 +04:00
|
|
|
self._checkparentdir(parent_baton)
|
2010-02-06 19:34:49 +03:00
|
|
|
br_path, branch = self.meta.split_branch_path(path)[:2]
|
2018-07-06 03:45:27 +03:00
|
|
|
if br_path == "":
|
2010-02-02 23:18:20 +03:00
|
|
|
if self.meta.get_path_tag(path):
|
|
|
|
# Tag deletion is not handled as branched deletion
|
|
|
|
return
|
2009-06-11 20:56:35 +04:00
|
|
|
self.meta.closebranches.add(branch)
|
2012-10-03 23:27:02 +04:00
|
|
|
|
|
|
|
# Delete copied entries, no need to check they exist in hg
|
|
|
|
# parent revision.
|
|
|
|
if path in self._svncopies:
|
|
|
|
del self._svncopies[path]
|
2018-07-06 03:45:27 +03:00
|
|
|
prefix = path + "/"
|
2012-10-03 23:27:02 +04:00
|
|
|
for f in list(self._svncopies):
|
|
|
|
if f.startswith(prefix):
|
2012-10-03 23:04:37 +04:00
|
|
|
self._deletefile(f)
|
2012-10-21 00:22:02 +04:00
|
|
|
if path in self._missing:
|
|
|
|
self._missing.remove(path)
|
|
|
|
else:
|
|
|
|
for f in list(self._missing):
|
|
|
|
if f.startswith(prefix):
|
|
|
|
self._missing.remove(f)
|
2012-10-03 23:27:02 +04:00
|
|
|
|
2008-11-29 20:24:31 +03:00
|
|
|
if br_path is not None:
|
2009-06-11 20:56:35 +04:00
|
|
|
ha = self.meta.get_parent_revision(self.current.rev.revnum, branch)
|
2008-09-30 20:42:52 +04:00
|
|
|
if ha == revlog.nullid:
|
|
|
|
return
|
2012-10-06 11:42:07 +04:00
|
|
|
ctx = self._getctx(ha)
|
2008-09-30 20:42:52 +04:00
|
|
|
if br_path not in ctx:
|
2018-07-06 03:45:27 +03:00
|
|
|
br_path2 = ""
|
|
|
|
if br_path != "":
|
|
|
|
br_path2 = br_path + "/"
|
2008-09-30 20:42:52 +04:00
|
|
|
# assuming it is a directory
|
2009-06-11 12:12:16 +04:00
|
|
|
self.current.externals[path] = None
|
2018-04-07 00:40:16 +03:00
|
|
|
|
|
|
|
for f in self.get_files_in_dir(ctx, br_path2):
|
2018-07-06 03:45:27 +03:00
|
|
|
f_p = "%s/%s" % (path, f[len(br_path2) :])
|
2012-10-03 23:04:37 +04:00
|
|
|
self._deletefile(f_p)
|
|
|
|
self._deletefile(path)
|
2008-09-30 20:42:52 +04:00
|
|
|
|
2010-08-11 21:57:35 +04:00
|
|
|
@svnwrap.ieditor
|
2008-09-30 20:42:52 +04:00
|
|
|
def open_file(self, path, parent_baton, base_revision, p=None):
|
2012-10-14 16:26:53 +04:00
|
|
|
self._checkparentdir(parent_baton)
|
2012-10-14 17:51:12 +04:00
|
|
|
if not self.meta.is_path_valid(path):
|
editor: implement file batons
The concept of current.file is incorrect, svn_delta.h documents open
file lifetime as:
* 5. When the producer calls @c open_file or @c add_file, either:
*
* (a) The producer must follow with any changes to the file
* (@c change_file_prop and/or @c apply_textdelta, as applicable),
* followed by a @c close_file call, before issuing any other file
* or directory calls, or
*
* (b) The producer must follow with a @c change_file_prop call if
* it is applicable, before issuing any other file or directory
* calls; later, after all directory batons including the root
* have been closed, the producer must issue @c apply_textdelta
* and @c close_file calls.
So, an open file can be kept open until after the root directory is
closed and have deltas applied afterwards. In the meantime, other files
may have been opened and patched, overwriting the current.file variable.
This patch fixes it by introducing file batons bound to file paths, and
using them to deduce the correct target in apply_textdelta(). In theory,
open files could be put in a staging area until they are closed and
moved in the RevisionData. But the current code registers files copied
during a directory copy as open files and these will not receive a
close_file() event. This separation will be enforced later.
2012-09-23 21:52:48 +04:00
|
|
|
return None
|
2012-10-14 17:51:12 +04:00
|
|
|
fpath, branch = self.meta.split_branch_path(path)[:2]
|
2009-05-30 00:48:27 +04:00
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
self.ui.note("M %s\n" % path)
|
2009-05-30 00:48:27 +04:00
|
|
|
|
2012-10-03 23:27:02 +04:00
|
|
|
if path in self._svncopies:
|
2012-09-25 01:44:23 +04:00
|
|
|
copy = self._svncopies.pop(path)
|
2012-10-06 11:42:07 +04:00
|
|
|
base, isexec, islink, copypath = copy.resolve(self._getctx)
|
2012-09-25 01:12:01 +04:00
|
|
|
return self._openfile(path, base, isexec, islink, copypath)
|
2012-10-03 23:27:02 +04:00
|
|
|
|
2009-05-30 00:48:27 +04:00
|
|
|
baserev = base_revision
|
|
|
|
if baserev is None or baserev == -1:
|
2009-06-11 12:12:16 +04:00
|
|
|
baserev = self.current.rev.revnum - 1
|
2010-03-02 19:06:06 +03:00
|
|
|
# Use exact=True because during replacements ('R' action) we select
|
|
|
|
# replacing branch as parent, but svn delta editor provides delta
|
|
|
|
# agains replaced branch.
|
|
|
|
parent = self.meta.get_parent_revision(baserev + 1, branch, True)
|
2012-10-06 11:42:07 +04:00
|
|
|
ctx = self._getctx(parent)
|
2009-05-30 00:48:27 +04:00
|
|
|
if fpath not in ctx:
|
2012-10-21 00:22:02 +04:00
|
|
|
self.addmissing(path)
|
editor: implement file batons
The concept of current.file is incorrect, svn_delta.h documents open
file lifetime as:
* 5. When the producer calls @c open_file or @c add_file, either:
*
* (a) The producer must follow with any changes to the file
* (@c change_file_prop and/or @c apply_textdelta, as applicable),
* followed by a @c close_file call, before issuing any other file
* or directory calls, or
*
* (b) The producer must follow with a @c change_file_prop call if
* it is applicable, before issuing any other file or directory
* calls; later, after all directory batons including the root
* have been closed, the producer must issue @c apply_textdelta
* and @c close_file calls.
So, an open file can be kept open until after the root directory is
closed and have deltas applied afterwards. In the meantime, other files
may have been opened and patched, overwriting the current.file variable.
This patch fixes it by introducing file batons bound to file paths, and
using them to deduce the correct target in apply_textdelta(). In theory,
open files could be put in a staging area until they are closed and
moved in the RevisionData. But the current code registers files copied
during a directory copy as open files and these will not receive a
close_file() event. This separation will be enforced later.
2012-09-23 21:52:48 +04:00
|
|
|
return None
|
2009-05-30 00:48:27 +04:00
|
|
|
|
|
|
|
fctx = ctx.filectx(fpath)
|
|
|
|
base = fctx.data()
|
2012-09-25 01:12:01 +04:00
|
|
|
flags = fctx.flags()
|
2018-07-06 03:45:27 +03:00
|
|
|
if "l" in flags:
|
|
|
|
base = "link " + base
|
|
|
|
return self._openfile(path, base, "x" in flags, "l" in flags, None)
|
2008-09-30 20:42:52 +04:00
|
|
|
|
2010-08-11 21:57:35 +04:00
|
|
|
@svnwrap.ieditor
|
2018-07-06 03:45:27 +03:00
|
|
|
def add_file(
|
|
|
|
self,
|
|
|
|
path,
|
|
|
|
parent_baton=None,
|
|
|
|
copyfrom_path=None,
|
|
|
|
copyfrom_revision=None,
|
|
|
|
file_pool=None,
|
|
|
|
):
|
2012-10-14 16:26:53 +04:00
|
|
|
self._checkparentdir(parent_baton)
|
2012-10-14 17:51:12 +04:00
|
|
|
# Use existing=False because we use the fact a file is being
|
|
|
|
# added here to populate the branchmap which is used with
|
|
|
|
# existing=True.
|
|
|
|
fpath, branch = self.meta.split_branch_path(path, existing=False)[:2]
|
|
|
|
if not fpath or fpath not in self.meta.filemap:
|
|
|
|
return None
|
2012-10-03 23:27:02 +04:00
|
|
|
if path in self._svncopies:
|
2018-07-06 03:45:27 +03:00
|
|
|
raise EditingError("trying to replace copied file %s" % path)
|
2012-10-03 23:04:37 +04:00
|
|
|
if path in self._deleted:
|
|
|
|
self._deleted.remove(path)
|
2018-07-06 03:45:27 +03:00
|
|
|
if branch not in self.meta.branches and not self.meta.get_path_tag(
|
|
|
|
self.meta.remotename(branch)
|
|
|
|
):
|
2012-10-14 17:51:12 +04:00
|
|
|
# we know this branch will exist now, because it has at
|
|
|
|
# least one file. Rock.
|
2009-06-11 20:56:35 +04:00
|
|
|
self.meta.branches[branch] = None, 0, self.current.rev.revnum
|
2008-11-05 15:37:07 +03:00
|
|
|
if not copyfrom_path:
|
2018-07-06 03:45:27 +03:00
|
|
|
self.ui.note("A %s\n" % path)
|
2012-05-13 17:28:50 +04:00
|
|
|
self.current.added.add(path)
|
2018-07-06 03:45:27 +03:00
|
|
|
return self._openfile(path, "", False, False, None, create=True)
|
|
|
|
self.ui.note("A+ %s\n" % path)
|
|
|
|
(from_file, from_branch) = self.meta.split_branch_path(copyfrom_path)[:2]
|
2008-11-05 15:37:07 +03:00
|
|
|
if not from_file:
|
2012-10-21 00:22:02 +04:00
|
|
|
self.addmissing(path)
|
editor: implement file batons
The concept of current.file is incorrect, svn_delta.h documents open
file lifetime as:
* 5. When the producer calls @c open_file or @c add_file, either:
*
* (a) The producer must follow with any changes to the file
* (@c change_file_prop and/or @c apply_textdelta, as applicable),
* followed by a @c close_file call, before issuing any other file
* or directory calls, or
*
* (b) The producer must follow with a @c change_file_prop call if
* it is applicable, before issuing any other file or directory
* calls; later, after all directory batons including the root
* have been closed, the producer must issue @c apply_textdelta
* and @c close_file calls.
So, an open file can be kept open until after the root directory is
closed and have deltas applied afterwards. In the meantime, other files
may have been opened and patched, overwriting the current.file variable.
This patch fixes it by introducing file batons bound to file paths, and
using them to deduce the correct target in apply_textdelta(). In theory,
open files could be put in a staging area until they are closed and
moved in the RevisionData. But the current code registers files copied
during a directory copy as open files and these will not receive a
close_file() event. This separation will be enforced later.
2012-09-23 21:52:48 +04:00
|
|
|
return None
|
2010-03-02 19:06:06 +03:00
|
|
|
# Use exact=True because during replacements ('R' action) we select
|
|
|
|
# replacing branch as parent, but svn delta editor provides delta
|
|
|
|
# agains replaced branch.
|
2018-07-06 03:45:27 +03:00
|
|
|
ha = self.meta.get_parent_revision(copyfrom_revision + 1, from_branch, True)
|
2012-10-06 11:42:07 +04:00
|
|
|
ctx = self._getctx(ha)
|
2012-09-25 01:12:01 +04:00
|
|
|
if from_file not in ctx:
|
2012-10-21 00:22:02 +04:00
|
|
|
self.addmissing(path)
|
2012-09-25 01:12:01 +04:00
|
|
|
return None
|
|
|
|
|
|
|
|
fctx = ctx.filectx(from_file)
|
|
|
|
flags = fctx.flags()
|
2014-04-08 05:44:46 +04:00
|
|
|
base = fctx.data()
|
2018-07-06 03:45:27 +03:00
|
|
|
if "l" in flags:
|
|
|
|
base = "link " + base
|
|
|
|
self.current.set(path, base, "x" in flags, "l" in flags)
|
2012-09-25 01:12:01 +04:00
|
|
|
copypath = None
|
|
|
|
if from_branch == branch:
|
2018-07-06 03:45:27 +03:00
|
|
|
parentid = self.meta.get_parent_revision(self.current.rev.revnum, branch)
|
2012-09-25 01:12:01 +04:00
|
|
|
if parentid != revlog.nullid:
|
2012-10-06 11:42:07 +04:00
|
|
|
parentctx = self._getctx(parentid)
|
2012-09-25 01:12:01 +04:00
|
|
|
if util.issamefile(parentctx, ctx, from_file):
|
|
|
|
copypath = from_file
|
2018-07-06 03:45:27 +03:00
|
|
|
return self._openfile(
|
|
|
|
path, base, "x" in flags, "l" in flags, copypath, create=True
|
|
|
|
)
|
2012-09-25 01:12:01 +04:00
|
|
|
|
|
|
|
@svnwrap.ieditor
|
|
|
|
def close_file(self, file_baton, checksum, pool=None):
|
|
|
|
if file_baton is None:
|
|
|
|
return
|
|
|
|
if file_baton not in self._openfiles:
|
2018-07-06 03:45:27 +03:00
|
|
|
raise EditingError("trying to close a non-open file %s" % file_baton)
|
2012-09-25 01:12:01 +04:00
|
|
|
path, data, isexec, islink, copypath = self._openfiles.pop(file_baton)
|
|
|
|
del self._openpaths[path]
|
2018-07-06 03:45:27 +03:00
|
|
|
if not isinstance(data, basestring): # noqa: F821
|
2012-09-27 00:18:31 +04:00
|
|
|
# Files can be opened, properties changed and apply_text
|
|
|
|
# never called, in which case data is still a string.
|
|
|
|
data = data.getvalue()
|
2012-09-25 23:34:16 +04:00
|
|
|
self.current.set(path, data, isexec, islink, copypath)
|
2008-09-30 20:42:52 +04:00
|
|
|
|
2010-08-11 21:57:35 +04:00
|
|
|
@svnwrap.ieditor
|
2018-07-06 03:45:27 +03:00
|
|
|
def add_directory(
|
|
|
|
self, path, parent_baton, copyfrom_path, copyfrom_revision, dir_pool=None
|
|
|
|
):
|
2012-10-14 16:26:53 +04:00
|
|
|
self._checkparentdir(parent_baton)
|
|
|
|
baton = self._opendir(path)
|
|
|
|
|
2009-06-11 21:10:05 +04:00
|
|
|
br_path, branch = self.meta.split_branch_path(path)[:2]
|
2008-11-29 20:24:31 +03:00
|
|
|
if br_path is not None:
|
|
|
|
if not copyfrom_path and not br_path:
|
2013-11-17 04:16:59 +04:00
|
|
|
# This handles the case where a branch root is
|
|
|
|
# replaced without copy info. It will show up as a
|
|
|
|
# deletion and then an add.
|
|
|
|
self.meta.closebranches.discard(branch)
|
2009-06-11 12:12:16 +04:00
|
|
|
self.current.emptybranches[branch] = True
|
2008-09-30 20:42:52 +04:00
|
|
|
else:
|
2009-06-11 12:12:16 +04:00
|
|
|
self.current.emptybranches[branch] = False
|
2008-11-29 20:24:31 +03:00
|
|
|
if br_path is None or not copyfrom_path:
|
2012-10-14 16:26:53 +04:00
|
|
|
return baton
|
2010-01-23 03:01:19 +03:00
|
|
|
if self.meta.get_path_tag(path):
|
|
|
|
del self.current.emptybranches[branch]
|
2012-10-14 16:26:53 +04:00
|
|
|
return baton
|
2010-01-18 23:40:28 +03:00
|
|
|
tag = self.meta.get_path_tag(copyfrom_path)
|
|
|
|
if tag not in self.meta.tags:
|
|
|
|
tag = None
|
2012-10-06 12:10:35 +04:00
|
|
|
if not self.meta.is_path_valid(copyfrom_path, existing=False):
|
|
|
|
# The source path only exists at copyfrom_revision, use
|
|
|
|
# existing=False to guess a possible branch location and
|
|
|
|
# test it against the filemap. The actual path and
|
|
|
|
# revision will be resolved below if necessary.
|
2012-10-21 00:22:02 +04:00
|
|
|
self.addmissing(path, isdir=True)
|
2012-10-14 16:26:53 +04:00
|
|
|
return baton
|
2008-11-05 01:38:16 +03:00
|
|
|
if tag:
|
2010-01-30 11:52:24 +03:00
|
|
|
changeid = self.meta.tags[tag]
|
|
|
|
source_rev, source_branch = self.meta.get_source_rev(changeid)[:2]
|
2018-07-06 03:45:27 +03:00
|
|
|
frompath = ""
|
2008-11-05 01:38:16 +03:00
|
|
|
else:
|
|
|
|
source_rev = copyfrom_revision
|
2011-03-10 00:07:05 +03:00
|
|
|
frompath, source_branch = self.meta.split_branch_path(copyfrom_path)[:2]
|
2010-03-02 19:06:06 +03:00
|
|
|
new_hash = self.meta.get_parent_revision(source_rev + 1, source_branch, True)
|
2013-11-17 21:57:00 +04:00
|
|
|
if frompath is None or new_hash == node.nullid:
|
2012-10-21 00:22:02 +04:00
|
|
|
self.addmissing(path, isdir=True)
|
2012-10-14 16:26:53 +04:00
|
|
|
return baton
|
2012-10-06 11:42:07 +04:00
|
|
|
fromctx = self._getctx(new_hash)
|
2018-07-06 03:45:27 +03:00
|
|
|
if frompath != "/" and frompath != "":
|
|
|
|
frompath = "%s/" % frompath
|
2008-09-30 20:42:52 +04:00
|
|
|
else:
|
2018-07-06 03:45:27 +03:00
|
|
|
frompath = ""
|
2012-10-16 10:47:01 +04:00
|
|
|
|
|
|
|
copyfromparent = False
|
2018-07-06 03:45:27 +03:00
|
|
|
if frompath == "" and br_path == "":
|
|
|
|
pnode = self.meta.get_parent_revision(self.current.rev.revnum, branch)
|
2012-10-16 10:47:01 +04:00
|
|
|
if pnode == new_hash:
|
|
|
|
# Data parent is topological parent and relative paths
|
|
|
|
# are the same, not need to do anything but restore
|
|
|
|
# files marked as deleted.
|
|
|
|
copyfromparent = True
|
2012-10-16 23:17:55 +04:00
|
|
|
# Get the parent which would have been used for this branch
|
|
|
|
# without the replace action.
|
|
|
|
oldpnode = self.meta.get_parent_revision(
|
2018-07-06 03:45:27 +03:00
|
|
|
self.current.rev.revnum, branch, exact=True
|
|
|
|
)
|
|
|
|
if oldpnode != revlog.nullid and util.isancestor(
|
|
|
|
self._getctx(oldpnode), fromctx
|
|
|
|
):
|
2012-10-16 23:17:55 +04:00
|
|
|
# Branch-wide replacement, unmark the branch as deleted
|
|
|
|
self.meta.closebranches.discard(branch)
|
2012-10-16 10:47:01 +04:00
|
|
|
|
2012-10-03 23:27:02 +04:00
|
|
|
svncopies = {}
|
2008-11-05 15:37:08 +03:00
|
|
|
copies = {}
|
2011-03-10 00:07:05 +03:00
|
|
|
for f in fromctx:
|
|
|
|
if not f.startswith(frompath):
|
2008-11-05 15:37:08 +03:00
|
|
|
continue
|
2018-07-06 03:45:27 +03:00
|
|
|
dest = path + "/" + f[len(frompath) :]
|
2012-10-14 17:51:12 +04:00
|
|
|
if not self.meta.is_path_valid(dest):
|
|
|
|
continue
|
2012-10-03 23:04:37 +04:00
|
|
|
if dest in self._deleted:
|
|
|
|
self._deleted.remove(dest)
|
2012-10-16 10:47:01 +04:00
|
|
|
if copyfromparent:
|
|
|
|
continue
|
|
|
|
svncopies[dest] = CopiedFile(new_hash, f, None)
|
2008-11-05 15:37:08 +03:00
|
|
|
if branch == source_branch:
|
2011-03-10 00:07:17 +03:00
|
|
|
copies[dest] = f
|
2008-11-05 15:37:08 +03:00
|
|
|
if copies:
|
|
|
|
# Preserve the directory copy records if no file was changed between
|
|
|
|
# the source and destination revisions, or discard it completely.
|
2018-07-06 03:45:27 +03:00
|
|
|
parentid = self.meta.get_parent_revision(self.current.rev.revnum, branch)
|
2008-11-05 15:37:08 +03:00
|
|
|
if parentid != revlog.nullid:
|
2012-10-06 11:42:07 +04:00
|
|
|
parentctx = self._getctx(parentid)
|
2009-10-17 03:09:25 +04:00
|
|
|
for k, v in copies.iteritems():
|
2011-03-10 00:07:05 +03:00
|
|
|
if util.issamefile(parentctx, fromctx, v):
|
2012-09-25 01:44:23 +04:00
|
|
|
svncopies[k].copypath = v
|
2012-10-03 23:27:02 +04:00
|
|
|
self._svncopies.update(svncopies)
|
|
|
|
|
2011-03-10 00:07:26 +03:00
|
|
|
# Copy the externals definitions of copied directories
|
|
|
|
fromext = svnexternals.parse(self.ui, fromctx)
|
|
|
|
for p, v in fromext.iteritems():
|
2018-07-06 03:45:27 +03:00
|
|
|
pp = p and (p + "/") or ""
|
2011-03-10 00:07:26 +03:00
|
|
|
if pp.startswith(frompath):
|
2018-07-06 03:45:27 +03:00
|
|
|
dest = (path + "/" + pp[len(frompath) :]).rstrip("/")
|
2011-03-10 00:07:26 +03:00
|
|
|
self.current.externals[dest] = v
|
2012-10-14 16:26:53 +04:00
|
|
|
return baton
|
2008-09-30 20:42:52 +04:00
|
|
|
|
2010-08-11 21:57:35 +04:00
|
|
|
@svnwrap.ieditor
|
2008-09-30 20:42:52 +04:00
|
|
|
def change_file_prop(self, file_baton, name, value, pool=None):
|
editor: implement file batons
The concept of current.file is incorrect, svn_delta.h documents open
file lifetime as:
* 5. When the producer calls @c open_file or @c add_file, either:
*
* (a) The producer must follow with any changes to the file
* (@c change_file_prop and/or @c apply_textdelta, as applicable),
* followed by a @c close_file call, before issuing any other file
* or directory calls, or
*
* (b) The producer must follow with a @c change_file_prop call if
* it is applicable, before issuing any other file or directory
* calls; later, after all directory batons including the root
* have been closed, the producer must issue @c apply_textdelta
* and @c close_file calls.
So, an open file can be kept open until after the root directory is
closed and have deltas applied afterwards. In the meantime, other files
may have been opened and patched, overwriting the current.file variable.
This patch fixes it by introducing file batons bound to file paths, and
using them to deduce the correct target in apply_textdelta(). In theory,
open files could be put in a staging area until they are closed and
moved in the RevisionData. But the current code registers files copied
during a directory copy as open files and these will not receive a
close_file() event. This separation will be enforced later.
2012-09-23 21:52:48 +04:00
|
|
|
if file_baton is None:
|
|
|
|
return
|
2012-09-25 01:12:01 +04:00
|
|
|
path, data, isexec, islink, copypath = self._openfiles[file_baton]
|
|
|
|
changed = False
|
2018-07-06 03:45:27 +03:00
|
|
|
if name == "svn:executable":
|
2012-09-25 01:12:01 +04:00
|
|
|
changed = True
|
|
|
|
isexec = bool(value is not None)
|
2018-07-06 03:45:27 +03:00
|
|
|
elif name == "svn:special":
|
2012-09-25 01:12:01 +04:00
|
|
|
changed = True
|
|
|
|
islink = bool(value is not None)
|
|
|
|
if changed:
|
|
|
|
self._openfiles[file_baton] = (path, data, isexec, islink, copypath)
|
2008-09-30 20:42:52 +04:00
|
|
|
|
2010-08-11 21:57:35 +04:00
|
|
|
@svnwrap.ieditor
|
2009-01-03 00:54:05 +03:00
|
|
|
def change_dir_prop(self, dir_baton, name, value, pool=None):
|
2012-10-14 16:26:53 +04:00
|
|
|
self._checkparentdir(dir_baton)
|
|
|
|
if len(self._opendirs) == 1:
|
2009-01-03 00:54:05 +03:00
|
|
|
return
|
2015-09-26 17:49:57 +03:00
|
|
|
path = self._opendirs[dir_baton]
|
2018-07-06 03:45:27 +03:00
|
|
|
if name == "svn:externals":
|
2009-06-11 12:12:16 +04:00
|
|
|
self.current.externals[path] = value
|
2009-01-03 00:54:05 +03:00
|
|
|
|
2012-09-23 21:42:34 +04:00
|
|
|
@svnwrap.ieditor
|
|
|
|
def open_root(self, edit_baton, base_revision, dir_pool=None):
|
editor: implement file batons
The concept of current.file is incorrect, svn_delta.h documents open
file lifetime as:
* 5. When the producer calls @c open_file or @c add_file, either:
*
* (a) The producer must follow with any changes to the file
* (@c change_file_prop and/or @c apply_textdelta, as applicable),
* followed by a @c close_file call, before issuing any other file
* or directory calls, or
*
* (b) The producer must follow with a @c change_file_prop call if
* it is applicable, before issuing any other file or directory
* calls; later, after all directory batons including the root
* have been closed, the producer must issue @c apply_textdelta
* and @c close_file calls.
So, an open file can be kept open until after the root directory is
closed and have deltas applied afterwards. In the meantime, other files
may have been opened and patched, overwriting the current.file variable.
This patch fixes it by introducing file batons bound to file paths, and
using them to deduce the correct target in apply_textdelta(). In theory,
open files could be put in a staging area until they are closed and
moved in the RevisionData. But the current code registers files copied
during a directory copy as open files and these will not receive a
close_file() event. This separation will be enforced later.
2012-09-23 21:52:48 +04:00
|
|
|
# We should not have to reset these, unfortunately the editor is
|
|
|
|
# reused for different revisions.
|
|
|
|
self._clear()
|
2018-07-06 03:45:27 +03:00
|
|
|
return self._opendir("")
|
2012-09-23 21:42:34 +04:00
|
|
|
|
2010-08-11 21:57:35 +04:00
|
|
|
@svnwrap.ieditor
|
2008-09-30 20:42:52 +04:00
|
|
|
def open_directory(self, path, parent_baton, base_revision, dir_pool=None):
|
2012-10-14 16:26:53 +04:00
|
|
|
self._checkparentdir(parent_baton)
|
|
|
|
baton = self._opendir(path)
|
2009-06-11 21:10:05 +04:00
|
|
|
p_, branch = self.meta.split_branch_path(path)[:2]
|
2018-07-06 03:45:27 +03:00
|
|
|
if p_ == "" or (self.meta.layout == "single" and p_):
|
2010-02-02 23:18:20 +03:00
|
|
|
if not self.meta.get_path_tag(path):
|
|
|
|
self.current.emptybranches[branch] = False
|
2012-10-14 16:26:53 +04:00
|
|
|
return baton
|
2009-01-03 00:54:05 +03:00
|
|
|
|
2010-08-11 21:57:35 +04:00
|
|
|
@svnwrap.ieditor
|
2009-01-03 00:54:05 +03:00
|
|
|
def close_directory(self, dir_baton, dir_pool=None):
|
2012-10-14 16:26:53 +04:00
|
|
|
self._checkparentdir(dir_baton)
|
2015-09-26 17:49:57 +03:00
|
|
|
del self._opendirs[dir_baton]
|
2008-09-30 20:42:52 +04:00
|
|
|
|
2010-08-11 21:57:35 +04:00
|
|
|
@svnwrap.ieditor
|
2008-09-30 20:42:52 +04:00
|
|
|
def apply_textdelta(self, file_baton, base_checksum, pool=None):
|
2012-09-25 01:12:01 +04:00
|
|
|
if file_baton is None:
|
2008-09-30 20:42:52 +04:00
|
|
|
return lambda x: None
|
2012-09-25 01:12:01 +04:00
|
|
|
if file_baton not in self._openfiles:
|
2018-07-06 03:45:27 +03:00
|
|
|
raise EditingError("trying to patch a closed file %s" % file_baton)
|
2012-09-25 01:12:01 +04:00
|
|
|
path, base, isexec, islink, copypath = self._openfiles[file_baton]
|
2018-07-06 03:45:27 +03:00
|
|
|
if not isinstance(base, basestring): # noqa: F821
|
|
|
|
raise EditingError("trying to edit a file again: %s" % path)
|
2012-09-25 01:12:01 +04:00
|
|
|
if not self.meta.is_path_valid(path):
|
2009-03-17 07:19:00 +03:00
|
|
|
return lambda x: None
|
2012-09-25 01:12:01 +04:00
|
|
|
|
2012-09-27 00:18:31 +04:00
|
|
|
target = svnwrap.SimpleStringIO(closing=False)
|
2008-09-30 20:42:52 +04:00
|
|
|
self.stream = target
|
|
|
|
|
2010-03-31 19:51:09 +04:00
|
|
|
handler = svnwrap.apply_txdelta(base, target)
|
2018-07-06 03:45:27 +03:00
|
|
|
if not callable(handler): # pragma: no cover
|
|
|
|
raise hgutil.Abort("Error in Subversion bindings: " "cannot call handler!")
|
|
|
|
|
2008-09-30 20:42:52 +04:00
|
|
|
def txdelt_window(window):
|
|
|
|
try:
|
editor: implement file batons
The concept of current.file is incorrect, svn_delta.h documents open
file lifetime as:
* 5. When the producer calls @c open_file or @c add_file, either:
*
* (a) The producer must follow with any changes to the file
* (@c change_file_prop and/or @c apply_textdelta, as applicable),
* followed by a @c close_file call, before issuing any other file
* or directory calls, or
*
* (b) The producer must follow with a @c change_file_prop call if
* it is applicable, before issuing any other file or directory
* calls; later, after all directory batons including the root
* have been closed, the producer must issue @c apply_textdelta
* and @c close_file calls.
So, an open file can be kept open until after the root directory is
closed and have deltas applied afterwards. In the meantime, other files
may have been opened and patched, overwriting the current.file variable.
This patch fixes it by introducing file batons bound to file paths, and
using them to deduce the correct target in apply_textdelta(). In theory,
open files could be put in a staging area until they are closed and
moved in the RevisionData. But the current code registers files copied
during a directory copy as open files and these will not receive a
close_file() event. This separation will be enforced later.
2012-09-23 21:52:48 +04:00
|
|
|
if not self.meta.is_path_valid(path):
|
2008-09-30 20:42:52 +04:00
|
|
|
return
|
2014-03-24 20:20:57 +04:00
|
|
|
|
|
|
|
# are we skipping this branch entirely?
|
|
|
|
br_path, branch = self.meta.split_branch_path(path)[:2]
|
|
|
|
if self.meta.skipbranch(branch):
|
|
|
|
return
|
|
|
|
|
2012-09-18 07:18:22 +04:00
|
|
|
try:
|
|
|
|
handler(window)
|
2018-07-06 03:45:27 +03:00
|
|
|
except AssertionError as e: # pragma: no cover
|
2012-09-18 07:18:22 +04:00
|
|
|
# Enhance the exception message
|
|
|
|
msg, others = e.args[0], e.args[1:]
|
|
|
|
|
|
|
|
if msg:
|
2018-07-06 03:45:27 +03:00
|
|
|
msg += "\n"
|
2012-09-18 07:18:22 +04:00
|
|
|
|
|
|
|
msg += _TXDELT_WINDOW_HANDLER_FAILURE_MSG
|
|
|
|
e.args = (msg,) + others
|
2012-11-11 18:31:19 +04:00
|
|
|
|
|
|
|
# re-raising ensures that we show the full stack trace
|
|
|
|
raise
|
2012-09-18 07:18:22 +04:00
|
|
|
|
2008-09-30 20:42:52 +04:00
|
|
|
# window being None means commit this file
|
|
|
|
if not window:
|
2012-09-25 01:12:01 +04:00
|
|
|
self._openfiles[file_baton] = (
|
2018-07-06 03:45:27 +03:00
|
|
|
path,
|
|
|
|
target,
|
|
|
|
isexec,
|
|
|
|
islink,
|
|
|
|
copypath,
|
|
|
|
)
|
|
|
|
except svnwrap.SubversionException as e: # pragma: no cover
|
2012-11-11 18:31:19 +04:00
|
|
|
self.ui.traceback()
|
2010-03-31 19:51:09 +04:00
|
|
|
if e.args[1] == svnwrap.ERR_INCOMPLETE_DATA:
|
2012-10-21 00:22:02 +04:00
|
|
|
self.addmissing(path)
|
2018-07-06 03:45:27 +03:00
|
|
|
else: # pragma: no cover
|
2009-04-10 21:43:44 +04:00
|
|
|
raise hgutil.Abort(*e.args)
|
2018-07-06 03:45:27 +03:00
|
|
|
except: # pragma: no cover
|
2008-09-30 20:42:52 +04:00
|
|
|
self._exception_info = sys.exc_info()
|
|
|
|
raise
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2008-09-30 20:42:52 +04:00
|
|
|
return txdelt_window
|
2012-09-18 07:18:22 +04:00
|
|
|
|
2012-10-03 23:27:02 +04:00
|
|
|
def close(self):
|
2012-09-25 01:12:01 +04:00
|
|
|
if self._openfiles:
|
|
|
|
for e in self._openfiles.itervalues():
|
2018-07-06 03:45:27 +03:00
|
|
|
self.ui.debug("error: %s was not closed\n" % e[0])
|
|
|
|
raise EditingError("%d edited files were not closed" % len(self._openfiles))
|
2012-09-25 01:12:01 +04:00
|
|
|
|
2012-10-14 16:26:53 +04:00
|
|
|
if self._opendirs:
|
2018-07-06 03:45:27 +03:00
|
|
|
raise EditingError(
|
|
|
|
"directory %s was not closed" % self._opendirs.keys()[-1]
|
|
|
|
)
|
2012-10-14 16:26:53 +04:00
|
|
|
|
2012-09-25 01:44:23 +04:00
|
|
|
# Resolve by changelog entries to avoid extra reads
|
|
|
|
nodes = {}
|
|
|
|
for path, copy in self._svncopies.iteritems():
|
|
|
|
nodes.setdefault(copy.node, []).append((path, copy))
|
2018-01-03 22:51:00 +03:00
|
|
|
for nodex, copies in nodes.iteritems():
|
2012-09-25 01:44:23 +04:00
|
|
|
for path, copy in copies:
|
2012-10-06 11:42:07 +04:00
|
|
|
data, isexec, islink, copied = copy.resolve(self._getctx)
|
2012-09-25 23:34:16 +04:00
|
|
|
self.current.set(path, data, isexec, islink, copied)
|
2012-10-03 23:27:02 +04:00
|
|
|
self._svncopies.clear()
|
|
|
|
|
2012-10-21 00:22:02 +04:00
|
|
|
# Resolve missing files
|
|
|
|
if self._missing:
|
|
|
|
missing = sorted(self._missing)
|
2018-07-06 03:45:27 +03:00
|
|
|
self.ui.debug(
|
|
|
|
"fetching %s files that could not use replay.\n" % len(missing)
|
|
|
|
)
|
|
|
|
if self.ui.configbool("hgsubversion", "failonmissing", False):
|
|
|
|
raise EditingError("missing entry: %s" % missing[0])
|
2012-10-21 00:22:02 +04:00
|
|
|
|
|
|
|
svn = self._svn
|
|
|
|
rev = self.current.rev.revnum
|
2018-07-06 03:45:27 +03:00
|
|
|
root = svn.subdir and svn.subdir[1:] or ""
|
2012-10-21 00:22:02 +04:00
|
|
|
i = 1
|
|
|
|
for f in missing:
|
|
|
|
if self.ui.debugflag:
|
2018-07-06 03:45:27 +03:00
|
|
|
self.ui.debug("fetching %s\n" % f)
|
2012-10-21 00:22:02 +04:00
|
|
|
else:
|
2018-07-06 03:45:27 +03:00
|
|
|
self.ui.note(".")
|
2012-10-21 00:22:02 +04:00
|
|
|
self.ui.flush()
|
|
|
|
if i % 50 == 0:
|
|
|
|
svn.init_ra_and_client()
|
|
|
|
i += 1
|
|
|
|
data, mode = svn.get_file(f, rev)
|
2018-07-06 03:45:27 +03:00
|
|
|
self.current.set(root + f, data, "x" in mode, "l" in mode)
|
2012-10-21 00:22:02 +04:00
|
|
|
if not self.ui.debugflag:
|
2018-07-06 03:45:27 +03:00
|
|
|
self.ui.note("\n")
|
2012-10-21 00:22:02 +04:00
|
|
|
|
2012-10-03 23:04:37 +04:00
|
|
|
for f in self._deleted:
|
|
|
|
self.current.delete(f)
|
|
|
|
self._deleted.clear()
|
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2012-09-18 07:18:22 +04:00
|
|
|
_TXDELT_WINDOW_HANDLER_FAILURE_MSG = (
|
|
|
|
"Your SVN repository may not be supplying correct replay deltas."
|
|
|
|
" It is strongly"
|
|
|
|
"\nadvised that you repull the entire SVN repository using"
|
|
|
|
" hg pull --stupid."
|
|
|
|
"\nAlternatively, re-pull just this revision using --stupid and verify"
|
|
|
|
" that the"
|
|
|
|
"\nchangeset is correct."
|
|
|
|
)
|