Fix symlink handling in content directory.
authorNick Bowler <nbowler@draconx.ca>
Fri, 26 Jun 2020 00:11:28 +0000 (20:11 -0400)
committerNick Bowler <nbowler@draconx.ca>
Fri, 26 Jun 2020 02:51:30 +0000 (22:51 -0400)
In preparation to use git-annex for tracking large binary files, we
need to solve some problems related to its symlinks.  Most importantly
is that the nanoc "filesystem" data source really doesn't like broken
symlinks, so we modify that to avoid major problems.

Next we filter out all missing annex items in a preprocess rule.
Ideally we would replace them with stubs but just dropping them is
acceptable for now.

Finally, for binary items, passthrough rules end up copying the symlink
literally from the content to the output directory.  In the case of symlinks
to the annex directory this is not going to deploy correctly, so we need
a dummy filter to break the link.

Rules
lib/copybin.rb [new file with mode: 0644]
lib/deadlinks.rb [new file with mode: 0644]

diff --git a/Rules b/Rules
index 383feb6d6e06107a05eb1d7bbcbfbdf15ae6d702..5473788e31261cfed8b0bbe865e1ba88cf83545e 100644 (file)
--- a/Rules
+++ b/Rules
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 preprocess do
+    # Remove dead annex keys from processing
+    @items.delete_if do |item|
+      l = File.readlink(item.raw_filename)
+      true if !File.exists?(item.raw_filename) and l =~ %r{/annex/objects/}
+    rescue Errno::EINVAL
+    end
+
     commit = nil
     Open3.popen2("git", "rev-parse", "HEAD") do |stdin, stdout, result|
         stdin.close
@@ -103,6 +110,10 @@ compile '/**/*.scss' do
     write @item.identifier.without_ext + '.css'
 end
 
-passthrough '/**/*'
+compile '/**/*' do
+  filter :copybin if @item.binary?
+  write @item.identifier.to_s
+end
+
 layout '/**/*.xsl', :xsl
 layout '/**/*', :erb
diff --git a/lib/copybin.rb b/lib/copybin.rb
new file mode 100644 (file)
index 0000000..f37ff4c
--- /dev/null
@@ -0,0 +1,28 @@
+# Nick's web site: copybin filter.  No-op filter binary that simply copies the
+# input to the output.  This exists primarily to break symlinks in rules.
+#
+# Copyright © 2020 Nick Bowler
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+class CopyBinFilter < Nanoc::Filter
+    identifier :copybin
+    type :binary
+
+    require 'fileutils'
+
+    def run(filename, params = {})
+        FileUtils.cp(filename, output_filename)
+    end
+end
diff --git a/lib/deadlinks.rb b/lib/deadlinks.rb
new file mode 100644 (file)
index 0000000..adcb25f
--- /dev/null
@@ -0,0 +1,49 @@
+# Nick's web site: Workarounds for Nanoc's busted handling of broken symlinks.
+#
+# Copyright © 2020 Nick Bowler
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# Monkey patch the filesystem data source to adjust some strange behavoiur.
+class Nanoc::DataSources::Filesystem < Nanoc::DataSource
+    # The filesystem mtime_of crashes on broken symlinks.  Let's not do that,
+    # instead we'll fall back to the link mtime for broken links.
+    def mtime_of(*args)
+        mtimes = args.compact.map do |f|
+            File.stat(f).mtime
+        rescue Errno::ENOENT
+            File.lstat(f).mtime
+        end
+        mtimes.max
+    end
+
+    module Tools
+        @dummy_file = Tempfile.new('dummy')
+
+        # The original resolve_symlink helper uses a readlink loop which
+        # crashes on broken links.  I'm also fairly sure symlinks in path
+        # components are not correctly resolved.  Let's just use realpath
+        # instead and hope ruby itself doesn't have these bugs.
+        def resolve_symlink(filename, recursion_limit = nil)
+            File.realpath(filename)
+        rescue Errno::ENOENT
+            # Dead link.  Return a valid filename otherwise the filesystem
+            # source does bizarre things.  The actual items are created with
+            # the original filename so this dummy file does not appear to
+            # leak outside of the filesystem data source implementation.
+            @dummy_file.path
+        end
+        module_function :resolve_symlink
+    end
+end