diff --git a/bin/swh-hashtree b/bin/swh-hashtree
new file mode 100755
index 0000000000000000000000000000000000000000..faf258fddb4f8f6d21fc13eaee34c9cc077aa27d
--- /dev/null
+++ b/bin/swh-hashtree
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+
+# Use sample:
+# swh-hashtree --path . --ignore '.svn' --ignore '.git-svn' \
+#    --ignore-empty-folders
+# 38f8d2c3a951f6b94007896d0981077e48bbd702
+
+import click
+import os
+
+from swh.model import from_disk, hashutil
+
+
+def combine_filters(*filters):
+    """Combine several ignore filters"""
+    if len(filters) == 0:
+        return from_disk.accept_all_directories
+    elif len(filters) == 1:
+        return filters[0]
+
+    def combined_filter(*args, **kwargs):
+        return all(filter(*args, **kwargs) for filter in filters)
+
+    return combined_filter
+
+
+@click.command()
+@click.option('--path', default='.',
+              help='Optional path to hash.')
+@click.option('--ignore-empty-folder', is_flag=True, default=False,
+              help='Ignore empty folder.')
+@click.option('--ignore', multiple=True,
+              help='Ignore pattern.')
+def main(path, ignore_empty_folder=False, ignore=None):
+
+    filters = []
+    if ignore_empty_folder:
+        filters.append(from_disk.ignore_empty_directories)
+    if ignore:
+        filters.append(
+            from_disk.ignore_named_directories(
+                [os.fsencode(name) for name in ignore]
+            )
+        )
+
+    try:
+        d = from_disk.Directory.from_disk(path=os.fsencode(path),
+                                          dir_filter=combine_filters(*filters))
+        hash = d.hash
+    except Exception as e:
+        print(e)
+        return
+    else:
+        print(hashutil.hash_to_hex(hash))
+
+
+if __name__ == '__main__':
+    main()