diff --git a/LICENSE b/LICENSE
index 63c11cb..bac4eb2 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2011 Sam Stephenson
+Copyright (c) 2014 Sam Stephenson
 
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the
diff --git a/debian/changelog b/debian/changelog
index c3e0e61..5a39865 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,4 +1,4 @@
-ruby-hike (1.2.1-4) UNRELEASED; urgency=medium
+ruby-hike (2.1.3-1) UNRELEASED; urgency=medium
 
   [ Ondřej Surý ]
   * Remove myself from Uploaders
@@ -26,8 +26,9 @@ ruby-hike (1.2.1-4) UNRELEASED; urgency=medium
   * Update watch file format version to 4.
   * Apply multi-arch hints.
     + ruby-hike: Add :any qualifier for ruby dependency.
+  * New upstream release.
 
- -- Utkarsh Gupta <guptautkarsh2102@gmail.com>  Tue, 13 Aug 2019 05:37:27 +0530
+ -- Utkarsh Gupta <guptautkarsh2102@gmail.com>  Sun, 15 May 2022 14:15:01 -0000
 
 ruby-hike (1.2.1-3) unstable; urgency=medium
 
diff --git a/hike.gemspec b/hike.gemspec
new file mode 100644
index 0000000..f468272
--- /dev/null
+++ b/hike.gemspec
@@ -0,0 +1,35 @@
+#########################################################
+# This file has been automatically generated by gem2tgz #
+#########################################################
+# -*- encoding: utf-8 -*-
+# stub: hike 2.1.3 ruby lib
+
+Gem::Specification.new do |s|
+  s.name = "hike".freeze
+  s.version = "2.1.3"
+
+  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
+  s.require_paths = ["lib".freeze]
+  s.authors = ["Sam Stephenson".freeze]
+  s.date = "2014-04-25"
+  s.description = "A Ruby library for finding files in a set of paths.".freeze
+  s.email = ["sstephenson@gmail.com".freeze]
+  s.files = ["LICENSE".freeze, "README.md".freeze, "lib/hike.rb".freeze, "lib/hike/cached_trail.rb".freeze, "lib/hike/extensions.rb".freeze, "lib/hike/fileutils.rb".freeze, "lib/hike/normalized_array.rb".freeze, "lib/hike/paths.rb".freeze, "lib/hike/trail.rb".freeze]
+  s.homepage = "http://github.com/sstephenson/hike".freeze
+  s.licenses = ["MIT".freeze]
+  s.required_ruby_version = Gem::Requirement.new(">= 1.9.3".freeze)
+  s.rubygems_version = "2.5.2.1".freeze
+  s.summary = "Find files in a set of paths".freeze
+
+  if s.respond_to? :specification_version then
+    s.specification_version = 4
+
+    if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
+      s.add_development_dependency(%q<rake>.freeze, [">= 0"])
+    else
+      s.add_dependency(%q<rake>.freeze, [">= 0"])
+    end
+  else
+    s.add_dependency(%q<rake>.freeze, [">= 0"])
+  end
+end
diff --git a/lib/hike.rb b/lib/hike.rb
index 48d46e8..c5f6d76 100644
--- a/lib/hike.rb
+++ b/lib/hike.rb
@@ -1,8 +1,9 @@
 module Hike
-  VERSION = "1.2.0"
+  VERSION = "2.1.3"
 
+  autoload :CachedTrail,     "hike/cached_trail"
   autoload :Extensions,      "hike/extensions"
-  autoload :Index,           "hike/index"
+  autoload :FileUtils,       "hike/fileutils"
   autoload :NormalizedArray, "hike/normalized_array"
   autoload :Paths,           "hike/paths"
   autoload :Trail,           "hike/trail"
diff --git a/lib/hike/cached_trail.rb b/lib/hike/cached_trail.rb
new file mode 100644
index 0000000..95babea
--- /dev/null
+++ b/lib/hike/cached_trail.rb
@@ -0,0 +1,188 @@
+module Hike
+  # `CachedTrail` is an internal cached variant of `Trail`. It assumes the
+  # file system does not change between `find` calls. All `stat` and
+  # `entries` calls are cached for the lifetime of the `CachedTrail` object.
+  class CachedTrail
+    include FileUtils
+
+    # `CachedTrail#paths` is an immutable `Paths` collection.
+    attr_reader :paths
+
+    # `CachedTrail#extensions` is an immutable `Extensions` collection.
+    attr_reader :extensions
+
+    # `CachedTrail#aliases` is an immutable `Hash` mapping an extension to
+    # an `Array` of aliases.
+    attr_reader :aliases
+
+    # `CachedTrail.new` is an internal method. Instead of constructing it
+    # directly, create a `Trail` and call `Trail#CachedTrail`.
+    def initialize(root, paths, extensions, aliases)
+      @root = root.to_s
+
+      # Freeze is used here so an error is throw if a mutator method
+      # is called on the array. Mutating `@paths`, `@extensions`, or
+      # `@aliases` would have unpredictable results.
+      @paths      = paths.dup.freeze
+      @extensions = extensions.dup.freeze
+
+      # Create a reverse mapping from extension to possible aliases.
+      @aliases = aliases.dup.freeze
+      @reverse_aliases = @aliases.inject({}) { |h, (k, a)|
+        (h[a] ||= []) << k; h
+      }
+
+      @stats    = Hash.new { |h, k| h[k] = FileUtils.stat(k) }
+      @entries  = Hash.new { |h, k| h[k] = FileUtils.entries(k) }
+      @patterns = Hash.new { |h, k| h[k] = pattern_for(k) }
+    end
+
+    # `CachedTrail#root` returns root path as a `String`. This attribute is immutable.
+    attr_reader :root
+
+    # `CachedTrail#cached` returns `self` to be compatable with the `Trail` interface.
+    def cached
+      self
+    end
+
+    # Deprecated alias for `cached`.
+    alias_method :index, :cached
+
+    # The real implementation of `find`. `Trail#find` generates a one
+    # time cache and delegates here.
+    #
+    # See `Trail#find` for usage.
+    def find(*logical_paths)
+      find_all(*logical_paths).first
+    end
+
+    # The real implementation of `find_all`. `Trail#find_all` generates a one
+    # time index and delegates here.
+    #
+    # See `Trail#find_all` for usage.
+    def find_all(*logical_paths, &block)
+      return to_enum(__method__, *logical_paths) unless block_given?
+
+      options = extract_options!(logical_paths)
+      base_path = (options[:base_path] || root).to_s
+
+      logical_paths.each do |logical_path|
+        logical_path = logical_path.sub(/^\//, '')
+
+        if relative?(logical_path)
+          find_in_base_path(logical_path, base_path, &block)
+        else
+          find_in_paths(logical_path, &block)
+        end
+      end
+
+      nil
+    end
+
+    # A cached version of `Dir.entries` that filters out `.` files and
+    # `~` swap files. Returns an empty `Array` if the directory does
+    # not exist.
+    def entries(path)
+      @entries[path]
+    end
+
+    # A cached version of `File.stat`. Returns nil if the file does
+    # not exist.
+    def stat(path)
+      @stats[path]
+    end
+
+    protected
+      def extract_options!(arguments)
+        arguments.last.is_a?(Hash) ? arguments.pop.dup : {}
+      end
+
+      def relative?(path)
+        path =~ /^\.\.?\//
+      end
+
+      # Finds logical path across all `paths`
+      def find_in_paths(logical_path, &block)
+        dirname, basename = File.split(logical_path)
+        @paths.each do |base_path|
+          match(File.expand_path(dirname, base_path), basename, &block)
+        end
+      end
+
+      # Finds relative logical path, `../test/test_trail`. Requires a
+      # `base_path` for reference.
+      def find_in_base_path(logical_path, base_path, &block)
+        candidate = File.expand_path(logical_path, base_path)
+        dirname, basename = File.split(candidate)
+        match(dirname, basename, &block) if paths_contain?(dirname)
+      end
+
+      # Checks if the path is actually on the file system and performs
+      # any syscalls if necessary.
+      def match(dirname, basename)
+        # Potential `entries` syscall
+        matches = @entries[dirname]
+
+        pattern = @patterns[basename]
+        matches = matches.select { |m| m =~ pattern }
+
+        sort_matches(matches, basename).each do |path|
+          filename = File.join(dirname, path)
+
+          # Potential `stat` syscall
+          stat = @stats[filename]
+
+          # Exclude directories
+          if stat && stat.file?
+            yield filename
+          end
+        end
+      end
+
+      # Returns true if `dirname` is a subdirectory of any of the `paths`
+      def paths_contain?(dirname)
+        paths.any? { |path| dirname[0, path.length] == path }
+      end
+
+      # Returns a `Regexp` that matches the allowed extensions.
+      #
+      #     pattern_for("index.html") #=> /^index(.html|.htm)(.builder|.erb)*$/
+      def pattern_for(basename)
+        extname = File.extname(basename)
+        aliases = @reverse_aliases[extname]
+
+        if aliases
+          basename = File.basename(basename, extname)
+          aliases  = [extname] + aliases
+          aliases_pattern = aliases.map { |e| Regexp.escape(e) }.join("|")
+          basename_re = Regexp.escape(basename) + "(?:#{aliases_pattern})"
+        else
+          basename_re = Regexp.escape(basename)
+        end
+
+        extension_pattern = extensions.map { |e| Regexp.escape(e) }.join("|")
+        /^#{basename_re}(?:#{extension_pattern})*$/
+      end
+
+      # Sorts candidate matches by their extension
+      # priority. Extensions in the front of the `extensions` carry
+      # more weight.
+      def sort_matches(matches, basename)
+        extname = File.extname(basename)
+        aliases = @reverse_aliases[extname] || []
+
+        matches.sort_by do |match|
+          extnames = match.sub(basename, '').scan(/\.[^.]+/)
+          extnames.inject(0) do |sum, ext|
+            if i = extensions.index(ext)
+              sum + i + 1
+            elsif i = aliases.index(ext)
+              sum + i + 11
+            else
+              sum
+            end
+          end
+        end
+      end
+  end
+end
diff --git a/lib/hike/fileutils.rb b/lib/hike/fileutils.rb
new file mode 100644
index 0000000..61cb3f0
--- /dev/null
+++ b/lib/hike/fileutils.rb
@@ -0,0 +1,24 @@
+module Hike
+  module FileUtils
+    extend self
+
+    # Like `File.stat`. Returns nil if the file does not exist.
+    def stat(path)
+      if File.exist?(path)
+        File.stat(path.to_s)
+      else
+        nil
+      end
+    end
+
+    # A version of `Dir.entries` that filters out `.` files and `~` swap files.
+    # Returns an empty `Array` if the directory does not exist.
+    def entries(path)
+      if File.directory?(path)
+        Dir.entries(path).reject { |entry| entry =~ /^\.|~$|^\#.*\#$/ }.sort
+      else
+        []
+      end
+    end
+  end
+end
diff --git a/lib/hike/index.rb b/lib/hike/index.rb
deleted file mode 100644
index c5f2e00..0000000
--- a/lib/hike/index.rb
+++ /dev/null
@@ -1,204 +0,0 @@
-require 'pathname'
-
-module Hike
-  # `Index` is an internal cached variant of `Trail`. It assumes the
-  # file system does not change between `find` calls. All `stat` and
-  # `entries` calls are cached for the lifetime of the `Index` object.
-  class Index
-    # `Index#paths` is an immutable `Paths` collection.
-    attr_reader :paths
-
-    # `Index#extensions` is an immutable `Extensions` collection.
-    attr_reader :extensions
-
-    # `Index#aliases` is an immutable `Hash` mapping an extension to
-    # an `Array` of aliases.
-    attr_reader :aliases
-
-    # `Index.new` is an internal method. Instead of constructing it
-    # directly, create a `Trail` and call `Trail#index`.
-    def initialize(root, paths, extensions, aliases)
-      @root = root
-
-      # Freeze is used here so an error is throw if a mutator method
-      # is called on the array. Mutating `@paths`, `@extensions`, or
-      # `@aliases` would have unpredictable results.
-      @paths      = paths.dup.freeze
-      @extensions = extensions.dup.freeze
-      @aliases    = aliases.inject({}) { |h, (k, a)|
-                      h[k] = a.dup.freeze; h
-                   }.freeze
-      @pathnames  = paths.map { |path| Pathname.new(path) }
-
-      @stats    = {}
-      @entries  = {}
-      @patterns = {}
-    end
-
-    # `Index#root` returns root path as a `String`. This attribute is immutable.
-    def root
-      @root.to_s
-    end
-
-    # `Index#index` returns `self` to be compatable with the `Trail` interface.
-    def index
-      self
-    end
-
-    # The real implementation of `find`. `Trail#find` generates a one
-    # time index and delegates here.
-    #
-    # See `Trail#find` for usage.
-    def find(*logical_paths, &block)
-      if block_given?
-        options = extract_options!(logical_paths)
-        base_path = Pathname.new(options[:base_path] || @root)
-
-        logical_paths.each do |logical_path|
-          logical_path = Pathname.new(logical_path.sub(/^\//, ''))
-
-          if relative?(logical_path)
-            find_in_base_path(logical_path, base_path, &block)
-          else
-            find_in_paths(logical_path, &block)
-          end
-        end
-
-        nil
-      else
-        find(*logical_paths) do |path|
-          return path
-        end
-      end
-    end
-
-    # A cached version of `Dir.entries` that filters out `.` files and
-    # `~` swap files. Returns an empty `Array` if the directory does
-    # not exist.
-    def entries(path)
-      key = path.to_s
-      @entries[key] ||= Pathname.new(path).entries.reject { |entry| entry.to_s =~ /^\.|~$|^\#.*\#$/ }.sort
-    rescue Errno::ENOENT
-      @entries[key] = []
-    end
-
-    # A cached version of `File.stat`. Returns nil if the file does
-    # not exist.
-    def stat(path)
-      key = path.to_s
-      if @stats.key?(key)
-        @stats[key]
-      else
-        begin
-          @stats[key] = File.stat(path)
-        rescue Errno::ENOENT
-          @stats[key] = nil
-        end
-      end
-    end
-
-    protected
-      def extract_options!(arguments)
-        arguments.last.is_a?(Hash) ? arguments.pop.dup : {}
-      end
-
-      def relative?(logical_path)
-        logical_path.to_s =~ /^\.\.?\//
-      end
-
-      # Finds logical path across all `paths`
-      def find_in_paths(logical_path, &block)
-        dirname, basename = logical_path.split
-        @pathnames.each do |base_path|
-          match(base_path.join(dirname), basename, &block)
-        end
-      end
-
-      # Finds relative logical path, `../test/test_trail`. Requires a
-      # `base_path` for reference.
-      def find_in_base_path(logical_path, base_path, &block)
-        candidate = base_path.join(logical_path)
-        dirname, basename = candidate.split
-        match(dirname, basename, &block) if paths_contain?(dirname)
-      end
-
-      # Checks if the path is actually on the file system and performs
-      # any syscalls if necessary.
-      def match(dirname, basename)
-        # Potential `entries` syscall
-        matches = entries(dirname)
-
-        pattern = pattern_for(basename)
-        matches = matches.select { |m| m.to_s =~ pattern }
-
-        sort_matches(matches, basename).each do |path|
-          pathname = dirname.join(path)
-
-          # Potential `stat` syscall
-          stat = stat(pathname)
-
-          # Exclude directories
-          if stat && stat.file?
-            yield pathname.to_s
-          end
-        end
-      end
-
-      # Returns true if `dirname` is a subdirectory of any of the `paths`
-      def paths_contain?(dirname)
-        paths.any? { |path| dirname.to_s[0, path.length] == path }
-      end
-
-      # Cache results of `build_pattern_for`
-      def pattern_for(basename)
-        @patterns[basename] ||= build_pattern_for(basename)
-      end
-
-      # Returns a `Regexp` that matches the allowed extensions.
-      #
-      #     pattern_for("index.html") #=> /^index(.html|.htm)(.builder|.erb)*$/
-      def build_pattern_for(basename)
-        extname = basename.extname
-        aliases = find_aliases_for(extname)
-
-        if aliases.any?
-          basename = basename.basename(extname)
-          aliases  = [extname] + aliases
-          aliases_pattern = aliases.map { |e| Regexp.escape(e) }.join("|")
-          basename_re = Regexp.escape(basename.to_s) + "(?:#{aliases_pattern})"
-        else
-          basename_re = Regexp.escape(basename.to_s)
-        end
-
-        extension_pattern = extensions.map { |e| Regexp.escape(e) }.join("|")
-        /^#{basename_re}(?:#{extension_pattern})*$/
-      end
-
-      # Sorts candidate matches by their extension
-      # priority. Extensions in the front of the `extensions` carry
-      # more weight.
-      def sort_matches(matches, basename)
-        aliases = find_aliases_for(basename.extname)
-
-        matches.sort_by do |match|
-          extnames = match.sub(basename.to_s, '').to_s.scan(/\.[^.]+/)
-          extnames.inject(0) do |sum, ext|
-            if i = extensions.index(ext)
-              sum + i + 1
-            elsif i = aliases.index(ext)
-              sum + i + 11
-            else
-              sum
-            end
-          end
-        end
-      end
-
-      def find_aliases_for(extension)
-        @aliases.inject([]) do |aliases, (key, value)|
-          aliases.push(key) if value == extension
-          aliases
-        end
-      end
-  end
-end
diff --git a/lib/hike/trail.rb b/lib/hike/trail.rb
index f0728b4..8006ce9 100644
--- a/lib/hike/trail.rb
+++ b/lib/hike/trail.rb
@@ -1,11 +1,13 @@
 require 'pathname'
+require 'hike/cached_trail'
 require 'hike/extensions'
-require 'hike/index'
 require 'hike/paths'
 
 module Hike
   # `Trail` is the public container class for holding paths and extensions.
   class Trail
+    include FileUtils
+
     # `Trail#paths` is a mutable `Paths` collection.
     #
     #     trail = Hike::Trail.new
@@ -28,7 +30,7 @@ module Hike
     # allows you to require files with specifiying `foo.rb`.
     attr_reader :extensions
 
-    # `Index#aliases` is a mutable `Hash` mapping an extension to
+    # `Trail#aliases` is a mutable `Hash` mapping an extension to
     # an `Array` of aliases.
     #
     #   trail = Hike::Trail.new
@@ -49,7 +51,7 @@ module Hike
       @root       = Pathname.new(root).expand_path
       @paths      = Paths.new(@root)
       @extensions = Extensions.new
-      @aliases    = Hash.new { |h, k| h[k] = Extensions.new }
+      @aliases    = {}
     end
 
     # `Trail#root` returns root path as a `String`. This attribute is immutable.
@@ -123,49 +125,42 @@ module Hike
     #
     #     trail.find("hike") || trail.find("hike/index")
     #
-    # Though `find` always returns the first match, it is possible
-    # to iterate over all shadowed matches and fallbacks by supplying
-    # a block.
+    def find(*args)
+      index.find(*args)
+    end
+
+    # `Trail#find_all` returns all matching paths including fallbacks and
+    #  shadowed matches.
     #
-    #     trail.find("hike", "hike/index") { |path| warn path }
+    #     trail.find_all("hike", "hike/index").each { |path| warn path }
     #
-    # This allows you to filter your matches by any condition.
+    # `find_all` returns an `Enumerator`. This allows you to filter your
+    # matches by any condition.
     #
-    #     trail.find("application") do |path|
-    #       return path if mime_type_for(path) == "text/css"
+    #     trail.find_all("application").find do |path|
+    #       mime_type_for(path) == "text/css"
     #     end
     #
-    def find(*args, &block)
-      index.find(*args, &block)
+    def find_all(*args, &block)
+      cached.find_all(*args, &block)
     end
 
-    # `Trail#index` returns an `Index` object that has the same
-    # interface as `Trail`. An `Index` is a cached `Trail` object that
+    # `Trail#cached` returns an `CachedTrail` object that has the same
+    # interface as `Trail`. An `CachedTrail` is a cached `Trail` object that
     # does not update when the file system changes. If you are
     # confident that you are not making changes the paths you are
-    # searching, `index` will avoid excess system calls.
+    # searching, `cached` will avoid excess system calls.
     #
-    #     index = trail.index
-    #     index.find "hike/trail"
-    #     index.find "test_trail"
+    #     cached = trail.cached
+    #     cached.find "hike/trail"
+    #     cached.find "test_trail"
     #
-    def index
-      Index.new(root, paths, extensions, aliases)
+    def cached
+      CachedTrail.new(root, paths, extensions, aliases)
     end
 
-    # `Trail#entries` is equivalent to `Dir#entries`. It is not
-    # recommend to use this method for general purposes. It exists for
-    # parity with `Index#entries`.
-    def entries(*args)
-      index.entries(*args)
-    end
-
-    # `Trail#stat` is equivalent to `File#stat`. It is not
-    # recommend to use this method for general purposes. It exists for
-    # parity with `Index#stat`.
-    def stat(*args)
-      index.stat(*args)
-    end
+    # Deprecated alias for `cached`.
+    alias_method :index, :cached
 
     private
       def normalize_extension(extension)
diff --git a/metadata.yml b/metadata.yml
deleted file mode 100644
index d25e063..0000000
--- a/metadata.yml
+++ /dev/null
@@ -1,75 +0,0 @@
---- !ruby/object:Gem::Specification 
-name: hike
-version: !ruby/object:Gem::Version 
-  hash: 29
-  prerelease: 
-  segments: 
-  - 1
-  - 2
-  - 1
-  version: 1.2.1
-platform: ruby
-authors: 
-- Sam Stephenson
-autorequire: 
-bindir: bin
-cert_chain: []
-
-date: 2011-08-17 00:00:00 -05:00
-default_executable: 
-dependencies: []
-
-description: A Ruby library for finding files in a set of paths.
-email: 
-- sstephenson@gmail.com
-executables: []
-
-extensions: []
-
-extra_rdoc_files: []
-
-files: 
-- README.md
-- LICENSE
-- lib/hike/extensions.rb
-- lib/hike/index.rb
-- lib/hike/normalized_array.rb
-- lib/hike/paths.rb
-- lib/hike/trail.rb
-- lib/hike.rb
-has_rdoc: true
-homepage: http://github.com/sstephenson/hike
-licenses: []
-
-post_install_message: 
-rdoc_options: []
-
-require_paths: 
-- lib
-required_ruby_version: !ruby/object:Gem::Requirement 
-  none: false
-  requirements: 
-  - - ">="
-    - !ruby/object:Gem::Version 
-      hash: 3
-      segments: 
-      - 0
-      version: "0"
-required_rubygems_version: !ruby/object:Gem::Requirement 
-  none: false
-  requirements: 
-  - - ">="
-    - !ruby/object:Gem::Version 
-      hash: 3
-      segments: 
-      - 0
-      version: "0"
-requirements: []
-
-rubyforge_project: 
-rubygems_version: 1.5.2
-signing_key: 
-specification_version: 3
-summary: Find files in a set of paths
-test_files: []
-