commit - d40c5cfe2c07824595c5d582d3268a1a322027dd
commit + ffb76d3a86982bc529c44ccacb04b4c91b0b331f
blob - 343b6117ebe64d95a55a143ce494976a471fdf46
blob + 3c0638f7ffed3e6fef90dfefef03003ccfb5c9e2
--- NEWS
+++ NEWS
0.21.4 UNRELEASED
+
+ * Support ``core.symlinks=false``. (Jelmer Vernooij, #1169)
* Deprecate ``dulwich.objects.parse_commit``.
blob - 8d15cc8f255770cde66c029122e1e80b37e2bd3f
blob + d63523f094eb699f1907e5def4aedb4e6b5c6482
--- dulwich/porcelain.py
+++ dulwich/porcelain.py
)
-def init(path=".", bare=False):
+def init(path=".", *, bare=False, symlinks: Optional[bool] = None):
"""Create a new git repository.
Args:
path: Path to repository.
bare: Whether to create a bare repository.
+ symlinks: Whether to create actual symlinks (defaults to autodetect)
Returns: A Repo instance
"""
if not os.path.exists(path):
if bare:
return Repo.init_bare(path)
else:
- return Repo.init(path)
+ return Repo.init(path, symlinks=symlinks)
def clone(
blob - 4450dce43695532c0763f0c266909ca0a185f966
blob + d24f733c1d138e2d160301093c4e1c7931d4ae97
--- dulwich/repo.py
+++ dulwich/repo.py
"""
raise NotImplementedError(self._determine_file_mode)
- def _init_files(self, bare: bool) -> None:
+ def _determine_symlinks(self) -> bool:
+ """Probe the filesystem to determine whether symlinks can be created.
+
+ Returns: True if symlinks can be created, False otherwise.
+ """
+ raise NotImplementedError(self._determine_symlinks)
+
+ def _init_files(self, *, bare: bool, symlinks: Optional[bool] = None) -> None:
"""Initialize a default set of named files."""
from .config import ConfigFile
else:
cf.set("core", "filemode", False)
+ if symlinks is None and not bare:
+ symlinks = self._determine_symlinks()
+
+ if symlinks is False:
+ cf.set("core", "symlinks", symlinks)
+
cf.set("core", "bare", bare)
cf.set("core", "logallrefupdates", True)
cf.write_to_file(f)
object_store: Optional[PackBasedObjectStore] = None,
bare: Optional[bool] = None
) -> None:
- self.symlink_fn = None
hidden_path = os.path.join(root, CONTROLDIR)
if bare is None:
if (os.path.isfile(hidden_path)
return mode_differs and st2_has_exec
+ def _determine_symlinks(self):
+ """Probe the filesystem to determine whether symlinks can be created.
+
+ Returns: True if symlinks can be created, False otherwise.
+ """
+ # TODO(jelmer): Actually probe disk / look at filesystem
+ return sys.platform != "win32"
+
def _put_named_file(self, path, contents):
"""Write a file to the control dir with the given name and contents.
def clone(
self,
target_path,
+ *,
mkdir=True,
bare=False,
origin=b"origin",
branch=None,
progress=None,
depth=None,
- ):
+ symlinks=None,
+ ) -> "Repo":
"""Clone this repository.
Args:
instead of this repository's HEAD.
progress: Optional progress function
depth: Depth at which to fetch
+ symlinks: Symlinks setting (default to autodetect)
Returns: Created repository as `Repo`
"""
os.mkdir(target_path)
try:
- target = None
if not bare:
- target = Repo.init(target_path)
+ target = Repo.init(target_path, symlinks=symlinks)
if checkout is None:
checkout = True
else:
raise ValueError("checkout and bare are incompatible")
target = Repo.init_bare(target_path)
- target_config = target.get_config()
- target_config.set((b"remote", origin), b"url", encoded_path)
- target_config.set(
- (b"remote", origin),
- b"fetch",
- b"+refs/heads/*:refs/remotes/" + origin + b"/*",
- )
- target_config.write_to_path()
+ try:
+ target_config = target.get_config()
+ target_config.set((b"remote", origin), b"url", encoded_path)
+ target_config.set(
+ (b"remote", origin),
+ b"fetch",
+ b"+refs/heads/*:refs/remotes/" + origin + b"/*",
+ )
+ target_config.write_to_path()
- ref_message = b"clone: from " + encoded_path
- self.fetch(target, depth=depth)
- target.refs.import_refs(
- b"refs/remotes/" + origin,
- self.refs.as_dict(b"refs/heads"),
- message=ref_message,
- )
- target.refs.import_refs(
- b"refs/tags", self.refs.as_dict(b"refs/tags"), message=ref_message
- )
-
- head_chain, origin_sha = self.refs.follow(b"HEAD")
- origin_head = head_chain[-1] if head_chain else None
- if origin_sha and not origin_head:
- # set detached HEAD
- target.refs[b"HEAD"] = origin_sha
- else:
- _set_origin_head(target.refs, origin, origin_head)
- head_ref = _set_default_branch(
- target.refs, origin, origin_head, branch, ref_message
+ ref_message = b"clone: from " + encoded_path
+ self.fetch(target, depth=depth)
+ target.refs.import_refs(
+ b"refs/remotes/" + origin,
+ self.refs.as_dict(b"refs/heads"),
+ message=ref_message,
)
+ target.refs.import_refs(
+ b"refs/tags", self.refs.as_dict(b"refs/tags"), message=ref_message
+ )
- # Update target head
- if head_ref:
- head = _set_head(target.refs, head_ref, ref_message)
+ head_chain, origin_sha = self.refs.follow(b"HEAD")
+ origin_head = head_chain[-1] if head_chain else None
+ if origin_sha and not origin_head:
+ # set detached HEAD
+ target.refs[b"HEAD"] = origin_sha
else:
- head = None
+ _set_origin_head(target.refs, origin, origin_head)
+ head_ref = _set_default_branch(
+ target.refs, origin, origin_head, branch, ref_message
+ )
- if checkout and head is not None:
- target.reset_index()
- except BaseException:
- if target is not None:
+ # Update target head
+ if head_ref:
+ head = _set_head(target.refs, head_ref, ref_message)
+ else:
+ head = None
+
+ if checkout and head is not None:
+ target.reset_index()
+ except BaseException:
target.close()
+ raise
+ except BaseException:
if mkdir:
import shutil
shutil.rmtree(target_path)
tree: Tree SHA to reset to, None for current HEAD tree.
"""
from .index import (build_index_from_tree,
+ symlink,
validate_path_element_default,
validate_path_element_ntfs)
validate_path_element = validate_path_element_ntfs
else:
validate_path_element = validate_path_element_default
+ if config.get_boolean(b"core", b"symlinks", True):
+ symlink_fn = symlink
+ else:
+ def symlink_fn(source, target): # type: ignore
+ with open(target, 'w' + ('b' if isinstance(source, bytes) else '')) as f:
+ f.write(source)
return build_index_from_tree(
self.path,
self.index_path(),
tree,
honor_filemode=honor_filemode,
validate_path_element=validate_path_element,
- symlink_fn=self.symlink_fn,
+ symlink_fn=symlink_fn
)
def get_worktree_config(self) -> "ConfigFile":
@classmethod
def _init_maybe_bare(
cls, path, controldir, bare, object_store=None, config=None,
- default_branch=None):
+ default_branch=None, symlinks: Optional[bool] = None):
for d in BASE_DIRECTORIES:
os.mkdir(os.path.join(controldir, *d))
if object_store is None:
except KeyError:
default_branch = DEFAULT_BRANCH
ret.refs.set_symbolic_ref(b"HEAD", LOCAL_BRANCH_PREFIX + default_branch)
- ret._init_files(bare)
+ ret._init_files(bare=bare, symlinks=symlinks)
return ret
@classmethod
- def init(cls, path: str, *, mkdir: bool = False, config=None, default_branch=None) -> "Repo":
+ def init(cls, path: str, *, mkdir: bool = False, config=None, default_branch=None, symlinks: Optional[bool] = None) -> "Repo":
"""Create a new repository.
Args:
_set_filesystem_hidden(controldir)
return cls._init_maybe_bare(
path, controldir, False, config=config,
- default_branch=default_branch)
+ default_branch=default_branch,
+ symlinks=symlinks)
@classmethod
def _init_new_working_directory(cls, path, main_repo, identifier=None, mkdir=False):
"""
return sys.platform != "win32"
+ def _determine_symlinks(self):
+ """Probe the file-system to determine whether permissions can be trusted.
+
+ Returns: True if permissions can be trusted, False otherwise.
+ """
+ return sys.platform != "win32"
+
def _put_named_file(self, path, contents):
"""Write a file to the control dir with the given name and contents.
blob - f48fa7642a02e46b16cf61ede8cdce25ffce0b3f
blob + 8d84b605ba169fd9f684cdeaf4c106fdf5e583e6
--- dulwich/tests/test_repository.py
+++ dulwich/tests/test_repository.py
tmp_dir = self.mkdtemp()
self.addCleanup(shutil.rmtree, tmp_dir)
r.clone(tmp_dir, mkdir=False, bare=True)
+
+ def test_reset_index_symlink_enabled(self):
+ if sys.platform == 'win32':
+ self.skipTest("symlinks are not supported on Windows")
+ tmp_dir = self.mkdtemp()
+ self.addCleanup(shutil.rmtree, tmp_dir)
+
+ o = Repo.init(os.path.join(tmp_dir, "s"), mkdir=True)
+ os.symlink("foo", os.path.join(tmp_dir, "s", "bar"))
+ o.stage("bar")
+ o.do_commit(b"add symlink")
+ t = o.clone(os.path.join(tmp_dir, "t"), symlinks=True)
+ o.close()
+ bar_path = os.path.join(tmp_dir, 't', 'bar')
+ if sys.platform == 'win32':
+ with open(bar_path, 'r') as f:
+ self.assertEqual('foo', f.read())
+ else:
+ self.assertEqual('foo', os.readlink(bar_path))
+ t.close()
+
+ def test_reset_index_symlink_disabled(self):
+ tmp_dir = self.mkdtemp()
+ self.addCleanup(shutil.rmtree, tmp_dir)
+
+ o = Repo.init(os.path.join(tmp_dir, "s"), mkdir=True)
+ o.close()
+ os.symlink("foo", os.path.join(tmp_dir, "s", "bar"))
+ o.stage("bar")
+ o.do_commit(b"add symlink")
+
+ t = o.clone(os.path.join(tmp_dir, "t"), symlinks=False)
+ with open(os.path.join(tmp_dir, "t", 'bar'), 'r') as f:
+ self.assertEqual('foo', f.read())
+
+ t.close()
+
def test_clone_bare(self):
r = self.open_repo("a.git")
tmp_dir = self.mkdtemp()