diff --git a/Makefile b/Makefile
index 8d18be961dac06c2c13fceaa13ff672ed4ca8b5e..d0b417fb8a3ce1386d6495f36054196f6423402f 100644
--- a/Makefile
+++ b/Makefile
@@ -2,5 +2,8 @@
 
 PREFIX := /usr
 
-install:
+git-ls:
+	ln -s $(PWD)/git-ls.scm git-ls
+
+install: git-ls
 	cp git-* $(DESTDIR)$(PREFIX)/bin/
diff --git a/git-ls b/git-ls
deleted file mode 100755
index 935fd95f36b82828ed8d761ac018aea6dfcd7dd7..0000000000000000000000000000000000000000
--- a/git-ls
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/bash
-
-Cyan='\e[0;36m'
-Normal="\e[m"
-
-for f in `ls`; do
-	if [ -f "$f/.git" ] || [ -d "$f/.git" ]; then
-		echo -e "$Cyan$f$Normal"
-	else
-		ls --color -d $f
-	fi
-done | column
diff --git a/git-ls.scm b/git-ls.scm
new file mode 100755
index 0000000000000000000000000000000000000000..92b0bb724ba10dd64d0792c12a6d3c51a4e285c4
--- /dev/null
+++ b/git-ls.scm
@@ -0,0 +1,90 @@
+#!/usr/bin/guile \
+-e main -s
+!#
+
+(use-modules (srfi srfi-1)
+             (srfi srfi-71)
+             (ice-9 ftw)
+             (ice-9 regex)
+             (ice-9 popen))
+
+
+(define cyan "0;36")
+(define normal "")
+(define error "1;31")
+
+(define* (ansi-escape color #:optional (port #f))
+  (format port "\x1b[~am" color))
+
+(define dir-color
+  (cond ((find (lambda (s) (string-match "^di=" s))
+                (string-split (or (getenv "LS_COLORS") "") #\:))
+         => (lambda (s) (string-drop s 3)))
+        (else "01;34")))
+
+
+(define-public (group list width)
+  (unless (zero? (modulo (length list) width))
+    (scm-error 'misc-error "group" "~a∤~a (~s)" `(,width ,(length list) ,list) '()))
+  (let inner ((list list))
+    (if (null? list)
+        '()
+        (let* ((row rest (split-at list width)))
+          (cons row (inner rest))))))
+
+
+(define (main args)
+
+  (define cols  (read (open-input-pipe "tput cols")))
+  (define dir
+    (if (< 1 (length args))
+        (cadr args)
+        "."))
+
+  (define items
+    (map
+     (lambda (pair)
+       (case (car pair)
+         ((module) (cons cyan (cdr pair)))
+         ((regular)
+          (catch 'system-error
+            (lambda ()
+              (case (stat:type (stat (string-append dir "/" (cdr pair))))
+                ((regular symlink block-special char-special fifo socket
+                          unknown)
+                 (cons normal (cdr pair)))
+                ((directory)
+                 (cons dir-color (cdr pair)))))
+            (const (cons error (cdr pair)))))))
+     (map (lambda (file)
+            (if (file-exists? (string-append dir "/" file "/.git"))
+                (cons 'module file)
+                (cons 'regular file)))
+          (remove (lambda (filename)
+                    (char=? #\. (string-ref filename 0)))
+           (scandir dir)))))
+
+  (let ()
+    (define longest-string (apply max (map string-length (map cdr items))))
+    (define items-per-line (floor (/ cols (+ 1 longest-string))))
+
+    (let ((head tail-line (split-at items
+                                    (- (length items)
+                                       (modulo (length items) items-per-line)))))
+      (for-each (lambda (group)
+                  (for-each (lambda (item)
+                              (format #t "~a~a~a"
+                                      (ansi-escape (car item))
+                                      (string-pad-right (cdr item) (1+ longest-string))
+                                      (ansi-escape normal)))
+                            group)
+                  (newline))
+                (group head items-per-line))
+      (for-each (lambda (item)
+                  (format #t "~a~a~a"
+                          (ansi-escape (car item))
+                          (string-pad-right (cdr item) (1+ longest-string))
+                          (ansi-escape normal)))
+                tail-line)
+      (unless (null? tail-line)
+        (newline)))))