##// END OF EJS Templates
Merge pull request #3751 from wolever/link-if-possible...
Jonathan Frederic -
r11727:07bbd498 merge
parent child Browse files
Show More
@@ -16,10 +16,10 b' Contains writer for writing nbconvert output to filesystem.'
16
16
17 import io
17 import io
18 import os
18 import os
19 import shutil
20 import glob
19 import glob
21
20
22 from IPython.utils.traitlets import Unicode
21 from IPython.utils.traitlets import Unicode
22 from IPython.utils.path import link_or_copy
23
23
24 from .base import WriterBase
24 from .base import WriterBase
25
25
@@ -90,7 +90,7 b' class FilesWriter(WriterBase):'
90
90
91 # Copy if destination is different.
91 # Copy if destination is different.
92 if not os.path.normpath(dest) == os.path.normpath(matching_filename):
92 if not os.path.normpath(dest) == os.path.normpath(matching_filename):
93 shutil.copyfile(matching_filename, dest)
93 link_or_copy(matching_filename, dest)
94
94
95 # Determine where to write conversion results.
95 # Determine where to write conversion results.
96 dest = notebook_name + '.' + output_extension
96 dest = notebook_name + '.' + output_extension
@@ -16,6 +16,9 b' Utilities for path handling.'
16
16
17 import os
17 import os
18 import sys
18 import sys
19 import errno
20 import shutil
21 import random
19 import tempfile
22 import tempfile
20 import warnings
23 import warnings
21 from hashlib import md5
24 from hashlib import md5
@@ -510,3 +513,52 b" def get_security_file(filename, profile='default'):"
510 raise IOError("Profile %r not found")
513 raise IOError("Profile %r not found")
511 return filefind(filename, ['.', pd.security_dir])
514 return filefind(filename, ['.', pd.security_dir])
512
515
516
517 ENOLINK = 1998
518
519 def link(src, dst):
520 """Hard links ``src`` to ``dst``, returning 0 or errno.
521
522 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
523 supported by the operating system.
524 """
525
526 if not hasattr(os, "link"):
527 return ENOLINK
528 link_errno = 0
529 try:
530 os.link(src, dst)
531 except OSError as e:
532 link_errno = e.errno
533 return link_errno
534
535
536 def link_or_copy(src, dst):
537 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
538
539 Attempts to maintain the semantics of ``shutil.copy``.
540
541 Because ``os.link`` does not overwrite files, a unique temporary file
542 will be used if the target already exists, then that file will be moved
543 into place.
544 """
545
546 if os.path.isdir(dst):
547 dst = os.path.join(dst, os.path.basename(src))
548
549 link_errno = link(src, dst)
550 if link_errno == errno.EEXIST:
551 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
552 try:
553 link_or_copy(src, new_dst)
554 except:
555 try:
556 os.remove(new_dst)
557 except OSError:
558 pass
559 raise
560 os.rename(new_dst, dst)
561 elif link_errno != 0:
562 # Either link isn't supported, or the filesystem doesn't support
563 # linking, or 'src' and 'dst' are on different filesystems.
564 shutil.copy(src, dst)
@@ -559,3 +559,69 b' def test_unescape_glob():'
559 nt.assert_equals(path.unescape_glob(r'\\\*'), r'\*')
559 nt.assert_equals(path.unescape_glob(r'\\\*'), r'\*')
560 nt.assert_equals(path.unescape_glob(r'\\a'), r'\a')
560 nt.assert_equals(path.unescape_glob(r'\\a'), r'\a')
561 nt.assert_equals(path.unescape_glob(r'\a'), r'\a')
561 nt.assert_equals(path.unescape_glob(r'\a'), r'\a')
562
563
564 class TestLinkOrCopy(object):
565 def setUp(self):
566 self.tempdir = TemporaryDirectory()
567 self.src = self.dst("src")
568 with open(self.src, "w") as f:
569 f.write("Hello, world!")
570
571 def tearDown(self):
572 self.tempdir.cleanup()
573
574 def dst(self, *args):
575 return os.path.join(self.tempdir.name, *args)
576
577 def assert_inode_not_equal(self, a, b):
578 nt.assert_not_equals(os.stat(a).st_ino, os.stat(b).st_ino,
579 "%r and %r do reference the same indoes" %(a, b))
580
581 def assert_inode_equal(self, a, b):
582 nt.assert_equals(os.stat(a).st_ino, os.stat(b).st_ino,
583 "%r and %r do not reference the same indoes" %(a, b))
584
585 def assert_content_equal(self, a, b):
586 with open(a) as a_f:
587 with open(b) as b_f:
588 nt.assert_equals(a_f.read(), b_f.read())
589
590 @skip_win32
591 def test_link_successful(self):
592 dst = self.dst("target")
593 path.link_or_copy(self.src, dst)
594 self.assert_inode_equal(self.src, dst)
595
596 @skip_win32
597 def test_link_into_dir(self):
598 dst = self.dst("some_dir")
599 os.mkdir(dst)
600 path.link_or_copy(self.src, dst)
601 expected_dst = self.dst("some_dir", os.path.basename(self.src))
602 self.assert_inode_equal(self.src, expected_dst)
603
604 @skip_win32
605 def test_target_exists(self):
606 dst = self.dst("target")
607 open(dst, "w").close()
608 path.link_or_copy(self.src, dst)
609 self.assert_inode_equal(self.src, dst)
610
611 @skip_win32
612 def test_no_link(self):
613 real_link = os.link
614 try:
615 del os.link
616 dst = self.dst("target")
617 path.link_or_copy(self.src, dst)
618 self.assert_content_equal(self.src, dst)
619 self.assert_inode_not_equal(self.src, dst)
620 finally:
621 os.link = real_link
622
623 @skip_if_not_win32
624 def test_windows(self):
625 dst = self.dst("target")
626 path.link_or_copy(self.src, dst)
627 self.assert_content_equal(self.src, dst)
General Comments 0
You need to be logged in to leave comments. Login now