Commit 3568b211 by Matthias Putz

Portable: symlink, using mklink in Windows

parent 8368c65f
......@@ -16,6 +16,7 @@
from __future__ import print_function
import itertools
import os
import portable
import re
import sys
import xml.dom.minidom
......@@ -148,7 +149,8 @@ class XmlManifest(object):
try:
if os.path.lexists(self.manifestFile):
os.remove(self.manifestFile)
os.symlink('manifests/%s' % name, self.manifestFile)
# os.symlink('manifests/%s' % name, self.manifestFile)
portable.os_symlink('manifests/%s' % name, self.manifestFile)
except OSError as e:
raise ManifestParseError('cannot link manifest %s: %s' % (name, str(e)))
......
import git_config
import os
import pager
import platform
import re
import socket
import sys
import subprocess
......@@ -83,6 +85,139 @@ class socket_reader():
return self.server_socket.fileno()
def os_symlink(src, dst):
if isUnix():
os.symlink(src, dst)
else:
windows_symlink(src, dst)
def windows_symlink(src, dst):
globalConfig = git_config.GitConfig.ForUser()
src = to_windows_path(src)
dst = to_windows_path(dst)
is_dir = True if os.path.isdir(os.path.realpath(os.path.join(os.path.dirname(dst), src))) else False
no_symlinks = globalConfig.GetBoolean("portable.windowsNoSymlinks")
if no_symlinks is None or no_symlinks == False:
symlink_options_dir = '/D'
symlink_options_file = ''
else:
src = os.path.abspath(os.path.join(os.path.dirname(dst), src))
Trace("Using no symlinks for %s from %s to %s", "dir" if is_dir else "file", src, dst)
symlink_options_dir = '/J'
symlink_options_file = '/H'
if is_dir:
cmd = ['cmd', '/c', 'mklink', symlink_options_dir, dst, src]
cmd = filter(len, cmd)
Trace(' '.join(cmd))
try:
subprocess.Popen(cmd, stdout=subprocess.PIPE).wait()
except Exception as e:
Trace("failed to create dir symlink: %s", e.strerror)
pass
else:
cmd = ['cmd', '/c', 'mklink', symlink_options_file, dst, src]
cmd = filter(len, cmd)
Trace(' '.join(cmd))
try:
subprocess.Popen(cmd, stdout=subprocess.PIPE).wait()
except Exception as e:
Trace("failed to create file symlink: %s", e.strerror)
pass
def os_path_islink(path):
if isUnix():
os.path.islink(path)
else:
if get_windows_symlink(path) is not None:
return True
if get_windows_hardlink(path) is not None:
return True
return False
def os_path_realpath(file_path):
if isUnix():
os.path.realpath(file_path)
else:
if not os.path.exists(file_path):
return file_path
return windows_realpath(file_path)
def windows_realpath(file_path):
symlink = file_path
while True:
s = get_windows_symlink(symlink)
if s is None:
break
else:
symlink = s
hardlink = get_windows_hardlink(symlink)
if hardlink is not None:
return hardlink
else:
return symlink
def get_windows_symlink(file_path):
if os.path.isdir(file_path):
root = os.path.abspath(os.path.join(file_path, os.pardir))
file_object = os.path.split(file_path)[1]
if not file_object:
file_object = os.path.split(os.path.split(file_object)[0])[1]
else:
root = os.path.dirname(file_path)
file_object = os.path.split(file_path)[1]
cmd = ['cmd', '/c', 'dir', '/AL', root]
try:
Trace(' '.join(cmd))
out = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
except:
return None
lines = [s.strip() for s in out.split('\n')]
if len(lines) < 6:
return None
pattern = re.compile('.*<(.*)>\\s*(.*) \[(.*)\]$')
for line in lines[5:]:
result = pattern.match(line)
if result:
ftype = result.group(1)
fname = result.group(2)
flink = result.group(3)
if file_object == fname:
if ftype == 'SYMLINK' or ftype == 'SYMLINKD':
new_path = os.path.realpath(os.path.join(os.path.dirname(file_path), flink))
Trace("Relative link found: %s -> %s -> %s", fname, flink, new_path)
else:
new_path = flink
Trace("Absolute link found: %s -> %s", fname, flink)
return new_path
return None
def get_windows_hardlink(file_path):
if os.path.isdir(file_path):
return None
cmd = ['cmd', '/c', 'fsutil', 'hardlink', 'list', file_path]
try:
Trace(' '.join(cmd))
out = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
except:
return None
lines = [s.strip() for s in out.split('\n')]
if len(lines) >= 2 and len(lines[1]) > 0:
hardlink = file_path[0:2] + lines[0]
Trace("Hard link found: %s -> %s", file_path, hardlink)
return hardlink
else:
return None
child_process = None
def RunPager(cmd):
......
......@@ -17,6 +17,7 @@ import errno
import filecmp
import glob
import os
import portable
import random
import re
import shutil
......@@ -246,7 +247,8 @@ class _LinkFile(object):
def __linkIt(self, relSrc, absDest):
# link file if it does not exist or is out of date
if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
# if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc):
if not portable.os_path_islink(absDest) or (portable.os_path_realpath(absDest) != relSrc):
try:
# remove existing file first, since it might be read-only
if os.path.exists(absDest):
......@@ -255,7 +257,8 @@ class _LinkFile(object):
dest_dir = os.path.dirname(absDest)
if not os.path.isdir(dest_dir):
os.makedirs(dest_dir)
os.symlink(relSrc, absDest)
# os.symlink(relSrc, absDest)
portable.os_symlink(relSrc, absDest)
except IOError:
_error('Cannot link file %s to %s', relSrc, absDest)
......@@ -2231,7 +2234,8 @@ class Project(object):
continue
dst = os.path.join(hooks, name)
if os.path.islink(dst):
# if os.path.islink(dst):
if portable.os_path_islink(dst):
continue
if os.path.exists(dst):
if filecmp.cmp(stock_hook, dst, shallow=False):
......@@ -2240,7 +2244,8 @@ class Project(object):
_warn("%s: Not replacing locally modified %s hook", self.relpath, name)
continue
try:
os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
# os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
portable.os_symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
except OSError as e:
if e.errno == errno.EPERM:
raise GitError('filesystem must support symlinks')
......@@ -2293,6 +2298,10 @@ class Project(object):
dst = os.path.realpath(os.path.join(destdir, name))
if os.path.lexists(dst):
src = os.path.realpath(os.path.join(srcdir, name))
src = portable.os_path_realpath(src)
dst = portable.os_path_realpath(dst)
Trace("%s ~= %s", src, dst)
# Fail if the links are pointing to the wrong place
if src != dst:
raise GitError('--force-sync not enabled; cannot overwrite a local '
......@@ -2345,8 +2354,10 @@ class Project(object):
pass
if name in to_symlink:
os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
elif copy_all and not os.path.islink(dst):
# os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
portable.os_symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
# elif copy_all and not os.path.islink(dst):
elif copy_all and not portable.os_path_islink(dst):
if os.path.isdir(src):
shutil.copytree(src, dst)
elif os.path.isfile(src):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment