login

<     >

2023-07-19 16:53:15 (UTC-03:00)

Marcel Rodrigues <marcelgmr@gmail.com>

organize file structure

diff --git a/cogit.lua b/cogit.lua
deleted file mode 100644
index 5457bf9..0000000
--- a/cogit.lua
+++ /dev/null
@@ -1,275 +0,0 @@
-local git = require "git"
-local scan = require "scan"
-local hash = require "hash"
-
-local lud = require "ludweb"
-
-local function time_fmt(sig)
-    local s = os.date("%Y-%m-%d %H:%M:%S", sig.time_)
-    local offset = sig.offset
-    local sign
-    if offset < 0 then
-        offset = -offset
-        sign = "-"
-    else
-        sign = "+"
-    end
-    local hours = math.floor(offset / 60)
-    local mins = offset % 60
-    s = s .. (" (UTC%s%02d:%02d)"):format(sign, hours, mins)
-    return s
-end
-
-local function diff_cb(line_type, line)
-    line = lud.template.escape(line:sub(1, #line-1))
-    if line_type == " " then
-        line = ' <span class="diff_ctx">' .. line .. '</span>'
-    elseif line_type == "+" then
-        line = '+<span class="diff_add">' .. line .. '</span>'
-    elseif line_type == "-" then
-        line = '-<span class="diff_del">' .. line .. '</span>'
-    elseif line_type == "=" then
-        line = ' <span class="diff_nonl diff_ctx_nonl">' .. line .. '</span>'
-    elseif line_type == ">" then
-        line = ' <span class="diff_nonl diff_old_nonl">' .. line .. '</span>'
-    elseif line_type == "<" then
-        line = ' <span class="diff_nonl diff_new_nonl">' .. line .. '</span>'
-    elseif line_type == "F" then
-        line = '\n<span class="diff_file_hdr">' .. line .. '</span>'
-    elseif line_type == "H" then
-        line = '<span class="diff_hunk_hdr">' .. line .. '</span>'
-    elseif line_type == "B" then
-        line = '<span class="diff_bin">' .. line .. '</span>'
-    else
-        line = line_type .. line
-    end
-    return line .. "\n"
-end
-
-local function allowed(user, gname)
-    if gname == "public" then
-        return true
-    elseif user == nil then
-        return false
-    end
-    for _, ok in ipairs(user.groups) do
-        if gname == ok then
-            return true
-        end
-    end
-    return false
-end
-
-local LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG = 0, 1, 2, 3
-
-local Cogit = {}
-Cogit.__index = Cogit
-
-function Cogit:init()
-    if not self.initialized then
-        git.init()
-        self.initialized = true
-    end
-end
-
-function Cogit:finish()
-    git.shutdown()
-    self.initialized = false
-end
-
-local function get_user(cookies)
-    local session_id = cookies["sid"]
-    if session_id == nil then
-        return nil
-    end
-    local uname = sessions[session_id]
-    if uname == nil then
-        return nil
-    end
-    return conf.users[uname]
-end
-
-function Cogit:run()
-    self:init()
-    self.conf.log(LOG_INFO, "server running on port "..self.conf.port)
-    self.app:run(self.conf.port)
-end
-
-function Cogit:scan()
-    self.conf.log(LOG_INFO, "scanning repos in "..self.path)
-    self.groups = scan.scanrepos(self.path)
-end
-
-function Cogit:log(level, msg)
-    self.conf.log(level, msg)
-end
-
-function Cogit:routes()
-    conf = self.conf
-    title = conf.title
-    sessions = self.sessions
-    return {
-    {"GET", "/?",
-    function (req)
-        local user = get_user(req.cookies)
-        local gnames = {}
-        self:scan()
-        for gname in pairs(self.groups) do
-            if allowed(user, gname) then
-                table.insert(gnames, gname)
-            end
-        end
-        local env = {title=title, user=user, gnames=gnames}
-        return lud.template.render_file("view/home.html", env)
-    end},
-    {"GET", "/login",
-    function (req)
-        local user = get_user(req.cookies)
-        if user ~= nil then  -- already logged in
-            return "/", 303
-        else
-            return lud.template.render_file("view/login.html", {title=title})
-        end
-    end},
-    {"POST", "/login",
-    function (req)
-        local uname = req.form.username
-        local pass = req.form.password
-        local user = conf.users[uname]
-        local salt, h
-        if user == nil then
-            -- hash something as if we're trying to login anyway
-            salt = hash.get_salt()
-            h = hash.hash_pass(pass, salt)
-            conf.log(LOG_INFO, "invalid username")
-        else
-            salt = lud.crypt.b64_dec(user.salt)
-            h = hash.hash_pass(pass, salt)
-            if h == lud.crypt.b64_dec(user.hash) then
-                local session_id = lud.crypt.b64_enc(lud.crypt.uuid4())
-                sessions[session_id] = uname
-                conf.log(LOG_INFO, "logged in as "..uname)
-                local age = conf.session_age
-                local cookie = {key="sid", val=session_id, path="/", age=age}
-                return "/", 303, "See Other", {cookie}
-            else
-                conf.log(LOG_INFO, "invalid password")
-            end
-        end
-        return "/login", 303
-    end},
-    {"GET", "/logout",
-    function (req)
-        local session_id = req.cookies["sid"]
-        if session_id ~= nil then
-            conf.log(LOG_INFO, "logged out as "..sessions[session_id])
-            sessions[session_id] = nil
-        end
-        return "/", 303
-    end},
-    {"GET", "/group/([%w_-]+)",
-    function (req, gname)
-        local user = get_user(req.cookies)
-        if not allowed(user, gname) then
-            return "/login", 303
-        end
-        local rnames = {}
-        for rname in pairs(self.groups[gname]) do
-            table.insert(rnames, rname)
-        end
-        local env = {title=title, user=user, gname=gname, rnames=rnames}
-        return lud.template.render_file("view/group.html", env)
-    end},
-    {"GET", "/group/([%w_-]+)/repo/([%w_-]+)",
-    function (req, gname, rname)
-        local user = get_user(req.cookies)
-        if not allowed(user, gname) then
-            return "/login", 303
-        end
-        local repo = self.groups[gname][rname]
-        local bnames = repo:branches()
-        local tnames = repo:tags()
-        local env = {
-            title=title, user=user, repo=repo, gname=gname,
-            rname=rname, bnames=bnames, tnames=tnames,
-        }
-        return lud.template.render_file("view/repo.html", env)
-    end},
-    {"GET", "/group/([%w_-]+)/repo/([%w_-]+)/history/([%w_-]+)",
-    function (req, gname, rname, first)
-        local user = get_user(req.cookies)
-        if not allowed(user, gname) then
-            return "/login", 303
-        end
-        local repo = self.groups[gname][rname]
-        local commit = repo:commit(first)
-        local prev = repo:find_prev(commit:id(), conf.limit)
-        local env = {
-            title=title, user=user, gname=gname, rname=rname, bname=bname,
-            commit=commit, limit=conf.limit, prev=prev, first=first,
-        }
-        return lud.template.render_file("view/history.html", env)
-    end},
-    {"GET", "/group/([%w_-]+)/repo/([%w_-]+)/commit/([%w_-]+)",
-    function (req, gname, rname, cid)
-        local user = get_user(req.cookies)
-        if not allowed(user, gname) then
-            return "/login", 303
-        end
-        local repo = self.groups[gname][rname]
-        local commit = repo:commit(cid)
-        local prev = repo:find_prev(commit:id(), 1)
-        local sig = commit:signature()
-        local time_str = time_fmt(sig)
-        local diff = repo:diff(commit, diff_cb)
-        local env = {
-            title=title, user=user, gname=gname, rname=rname, bname=bname,
-            commit=commit, time_str=time_str, sig=sig, cid=cid, prev=prev, diff=diff,
-        }
-        return lud.template.render_file("view/commit.html", env)
-    end},
-    {"GET", "/group/([%w_-]+)/repo/([%w_-]+)/commit/([%w_-]+)/tree/(.*)",
-    function (req, gname, rname, cid, path)
-        local user = get_user(req.cookies)
-        if not allowed(user, gname) then
-            return "/login", 303
-        end
-        local repo = self.groups[gname][rname]
-        local commit = repo:commit(cid)
-        local node = commit:tree_entry(path)
-        if node == nil then
-            return "File not found", 404, "Not found"
-        end
-        local parts = {}
-        for part in path:gmatch("[^/]+") do
-            table.insert(parts, part)
-        end
-        local base = req.path
-        if base:sub(#base) ~= "/" then
-            base = base .. "/"
-        end
-        local env = {
-            title=title, user=user, gname=gname, rname=rname, cid=cid,
-            path=path, base=base, parts=parts, node=node,
-        }
-        if node.type_ == "dir" then
-            return lud.template.render_file("view/dir.html", env)
-        elseif node.type_ == "file" then
-            return lud.template.render_file("view/file.html", env)
-        end
-    end},
-} end
-
-local function new_cogit(path)
-    local self = setmetatable({}, Cogit)
-    self.path = path
-    self.sessions = {}
-    self.initialized = false
-    self.conf = dofile(path.."/conf.lua")
-    self:init()
-    self:scan()
-    self.app = lud.app.new_app(self:routes())
-    return self
-end
-
-return {new_cogit=new_cogit}

diff --git a/cogit/cogit.lua b/cogit/cogit.lua
new file mode 100644
index 0000000..5de1996
--- /dev/null
+++ b/cogit/cogit.lua
@@ -0,0 +1,275 @@
+local git = require "cogit.git"
+local scan = require "cogit.scan"
+local hash = require "cogit.hash"
+
+local lud = require "ludweb"
+
+local function time_fmt(sig)
+    local s = os.date("%Y-%m-%d %H:%M:%S", sig.time_)
+    local offset = sig.offset
+    local sign
+    if offset < 0 then
+        offset = -offset
+        sign = "-"
+    else
+        sign = "+"
+    end
+    local hours = math.floor(offset / 60)
+    local mins = offset % 60
+    s = s .. (" (UTC%s%02d:%02d)"):format(sign, hours, mins)
+    return s
+end
+
+local function diff_cb(line_type, line)
+    line = lud.template.escape(line:sub(1, #line-1))
+    if line_type == " " then
+        line = ' <span class="diff_ctx">' .. line .. '</span>'
+    elseif line_type == "+" then
+        line = '+<span class="diff_add">' .. line .. '</span>'
+    elseif line_type == "-" then
+        line = '-<span class="diff_del">' .. line .. '</span>'
+    elseif line_type == "=" then
+        line = ' <span class="diff_nonl diff_ctx_nonl">' .. line .. '</span>'
+    elseif line_type == ">" then
+        line = ' <span class="diff_nonl diff_old_nonl">' .. line .. '</span>'
+    elseif line_type == "<" then
+        line = ' <span class="diff_nonl diff_new_nonl">' .. line .. '</span>'
+    elseif line_type == "F" then
+        line = '\n<span class="diff_file_hdr">' .. line .. '</span>'
+    elseif line_type == "H" then
+        line = '<span class="diff_hunk_hdr">' .. line .. '</span>'
+    elseif line_type == "B" then
+        line = '<span class="diff_bin">' .. line .. '</span>'
+    else
+        line = line_type .. line
+    end
+    return line .. "\n"
+end
+
+local function allowed(user, gname)
+    if gname == "public" then
+        return true
+    elseif user == nil then
+        return false
+    end
+    for _, ok in ipairs(user.groups) do
+        if gname == ok then
+            return true
+        end
+    end
+    return false
+end
+
+local LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG = 0, 1, 2, 3
+
+local Cogit = {}
+Cogit.__index = Cogit
+
+function Cogit:init()
+    if not self.initialized then
+        git.init()
+        self.initialized = true
+    end
+end
+
+function Cogit:finish()
+    git.shutdown()
+    self.initialized = false
+end
+
+local function get_user(cookies)
+    local session_id = cookies["sid"]
+    if session_id == nil then
+        return nil
+    end
+    local uname = sessions[session_id]
+    if uname == nil then
+        return nil
+    end
+    return conf.users[uname]
+end
+
+function Cogit:run()
+    self:init()
+    self.conf.log(LOG_INFO, "server running on port "..self.conf.port)
+    self.app:run(self.conf.port)
+end
+
+function Cogit:scan()
+    self.conf.log(LOG_INFO, "scanning repos in "..self.path)
+    self.groups = scan.scanrepos(self.path)
+end
+
+function Cogit:log(level, msg)
+    self.conf.log(level, msg)
+end
+
+function Cogit:routes()
+    conf = self.conf
+    title = conf.title
+    sessions = self.sessions
+    return {
+    {"GET", "/?",
+    function (req)
+        local user = get_user(req.cookies)
+        local gnames = {}
+        self:scan()
+        for gname in pairs(self.groups) do
+            if allowed(user, gname) then
+                table.insert(gnames, gname)
+            end
+        end
+        local env = {title=title, user=user, gnames=gnames}
+        return lud.template.render_file("view/home.html", env)
+    end},
+    {"GET", "/login",
+    function (req)
+        local user = get_user(req.cookies)
+        if user ~= nil then  -- already logged in
+            return "/", 303
+        else
+            return lud.template.render_file("view/login.html", {title=title})
+        end
+    end},
+    {"POST", "/login",
+    function (req)
+        local uname = req.form.username
+        local pass = req.form.password
+        local user = conf.users[uname]
+        local salt, h
+        if user == nil then
+            -- hash something as if we're trying to login anyway
+            salt = hash.get_salt()
+            h = hash.hash_pass(pass, salt)
+            conf.log(LOG_INFO, "invalid username")
+        else
+            salt = lud.crypt.b64_dec(user.salt)
+            h = hash.hash_pass(pass, salt)
+            if h == lud.crypt.b64_dec(user.hash) then
+                local session_id = lud.crypt.b64_enc(lud.crypt.uuid4())
+                sessions[session_id] = uname
+                conf.log(LOG_INFO, "logged in as "..uname)
+                local age = conf.session_age
+                local cookie = {key="sid", val=session_id, path="/", age=age}
+                return "/", 303, "See Other", {cookie}
+            else
+                conf.log(LOG_INFO, "invalid password")
+            end
+        end
+        return "/login", 303
+    end},
+    {"GET", "/logout",
+    function (req)
+        local session_id = req.cookies["sid"]
+        if session_id ~= nil then
+            conf.log(LOG_INFO, "logged out as "..sessions[session_id])
+            sessions[session_id] = nil
+        end
+        return "/", 303
+    end},
+    {"GET", "/group/([%w_-]+)",
+    function (req, gname)
+        local user = get_user(req.cookies)
+        if not allowed(user, gname) then
+            return "/login", 303
+        end
+        local rnames = {}
+        for rname in pairs(self.groups[gname]) do
+            table.insert(rnames, rname)
+        end
+        local env = {title=title, user=user, gname=gname, rnames=rnames}
+        return lud.template.render_file("view/group.html", env)
+    end},
+    {"GET", "/group/([%w_-]+)/repo/([%w_-]+)",
+    function (req, gname, rname)
+        local user = get_user(req.cookies)
+        if not allowed(user, gname) then
+            return "/login", 303
+        end
+        local repo = self.groups[gname][rname]
+        local bnames = repo:branches()
+        local tnames = repo:tags()
+        local env = {
+            title=title, user=user, repo=repo, gname=gname,
+            rname=rname, bnames=bnames, tnames=tnames,
+        }
+        return lud.template.render_file("view/repo.html", env)
+    end},
+    {"GET", "/group/([%w_-]+)/repo/([%w_-]+)/history/([%w_-]+)",
+    function (req, gname, rname, first)
+        local user = get_user(req.cookies)
+        if not allowed(user, gname) then
+            return "/login", 303
+        end
+        local repo = self.groups[gname][rname]
+        local commit = repo:commit(first)
+        local prev = repo:find_prev(commit:id(), conf.limit)
+        local env = {
+            title=title, user=user, gname=gname, rname=rname, bname=bname,
+            commit=commit, limit=conf.limit, prev=prev, first=first,
+        }
+        return lud.template.render_file("view/history.html", env)
+    end},
+    {"GET", "/group/([%w_-]+)/repo/([%w_-]+)/commit/([%w_-]+)",
+    function (req, gname, rname, cid)
+        local user = get_user(req.cookies)
+        if not allowed(user, gname) then
+            return "/login", 303
+        end
+        local repo = self.groups[gname][rname]
+        local commit = repo:commit(cid)
+        local prev = repo:find_prev(commit:id(), 1)
+        local sig = commit:signature()
+        local time_str = time_fmt(sig)
+        local diff = repo:diff(commit, diff_cb)
+        local env = {
+            title=title, user=user, gname=gname, rname=rname, bname=bname,
+            commit=commit, time_str=time_str, sig=sig, cid=cid, prev=prev, diff=diff,
+        }
+        return lud.template.render_file("view/commit.html", env)
+    end},
+    {"GET", "/group/([%w_-]+)/repo/([%w_-]+)/commit/([%w_-]+)/tree/(.*)",
+    function (req, gname, rname, cid, path)
+        local user = get_user(req.cookies)
+        if not allowed(user, gname) then
+            return "/login", 303
+        end
+        local repo = self.groups[gname][rname]
+        local commit = repo:commit(cid)
+        local node = commit:tree_entry(path)
+        if node == nil then
+            return "File not found", 404, "Not found"
+        end
+        local parts = {}
+        for part in path:gmatch("[^/]+") do
+            table.insert(parts, part)
+        end
+        local base = req.path
+        if base:sub(#base) ~= "/" then
+            base = base .. "/"
+        end
+        local env = {
+            title=title, user=user, gname=gname, rname=rname, cid=cid,
+            path=path, base=base, parts=parts, node=node,
+        }
+        if node.type_ == "dir" then
+            return lud.template.render_file("view/dir.html", env)
+        elseif node.type_ == "file" then
+            return lud.template.render_file("view/file.html", env)
+        end
+    end},
+} end
+
+local function new_cogit(path)
+    local self = setmetatable({}, Cogit)
+    self.path = path
+    self.sessions = {}
+    self.initialized = false
+    self.conf = dofile(path.."/conf.lua")
+    self:init()
+    self:scan()
+    self.app = lud.app.new_app(self:routes())
+    return self
+end
+
+return {new_cogit=new_cogit}

diff --git a/cogit/git.lua b/cogit/git.lua
new file mode 100644
index 0000000..b16ff24
--- /dev/null
+++ b/cogit/git.lua
@@ -0,0 +1,365 @@
+local ffi = require "ffi"
+
+ffi.cdef[[
+typedef int64_t git_off_t;
+typedef int64_t git_time_t;
+typedef uint64_t git_object_size_t;
+typedef struct git_repository git_repository;
+typedef struct git_object git_object;
+typedef struct git_commit git_commit;
+typedef struct git_tree git_tree;
+typedef struct git_tree_entry git_tree_entry;
+typedef struct git_diff git_diff;
+typedef struct git_blob git_blob;
+typedef struct git_tag git_tag;
+typedef struct git_revwalk git_revwalk;
+typedef enum {
+	GIT_OBJECT_ANY =      -2, /**< Object can be any of the following */
+	GIT_OBJECT_INVALID =  -1, /**< Object is invalid. */
+	GIT_OBJECT_COMMIT =    1, /**< A commit object. */
+	GIT_OBJECT_TREE =      2, /**< A tree (directory listing) object. */
+	GIT_OBJECT_BLOB =      3, /**< A file revision object. */
+	GIT_OBJECT_TAG =       4, /**< An annotated tag object. */
+	GIT_OBJECT_OFS_DELTA = 6, /**< A delta, base is given by an offset. */
+	GIT_OBJECT_REF_DELTA = 7  /**< A delta, base is given by object id. */
+} git_object_t;
+typedef struct git_oid {
+	/** raw binary formatted id */
+	unsigned char id[20];
+} git_oid;
+typedef struct git_strarray {
+	char **strings;
+	size_t count;
+} git_strarray;
+typedef struct git_time {
+	git_time_t time; /**< time in seconds from epoch */
+	int offset; /**< timezone offset, in minutes */
+	char sign; /**< indicator for questionable '-0000' offsets in signature */
+} git_time;
+typedef struct git_signature {
+	char *name; /**< full name of the author */
+	char *email; /**< email of the author */
+	git_time when; /**< time when the action happened */
+} git_signature;
+typedef struct git_diff_options git_diff_options;
+typedef enum {
+	GIT_DIFF_FORMAT_PATCH        = 1u, /**< full git diff */
+	GIT_DIFF_FORMAT_PATCH_HEADER = 2u, /**< just the file headers of patch */
+	GIT_DIFF_FORMAT_RAW          = 3u, /**< like git diff --raw */
+	GIT_DIFF_FORMAT_NAME_ONLY    = 4u, /**< like git diff --name-only */
+	GIT_DIFF_FORMAT_NAME_STATUS  = 5u, /**< like git diff --name-status */
+	GIT_DIFF_FORMAT_PATCH_ID     = 6u  /**< git diff as used by git patch-id */
+} git_diff_format_t;
+typedef enum {
+	GIT_DELTA_UNMODIFIED = 0,  /**< no changes */
+	GIT_DELTA_ADDED = 1,	   /**< entry does not exist in old version */
+	GIT_DELTA_DELETED = 2,	   /**< entry does not exist in new version */
+	GIT_DELTA_MODIFIED = 3,    /**< entry content changed between old and new */
+	GIT_DELTA_RENAMED = 4,     /**< entry was renamed between old and new */
+	GIT_DELTA_COPIED = 5,      /**< entry was copied from another old entry */
+	GIT_DELTA_IGNORED = 6,     /**< entry is ignored item in workdir */
+	GIT_DELTA_UNTRACKED = 7,   /**< entry is untracked item in workdir */
+	GIT_DELTA_TYPECHANGE = 8,  /**< type of entry changed between old and new */
+	GIT_DELTA_UNREADABLE = 9,  /**< entry is unreadable */
+	GIT_DELTA_CONFLICTED = 10  /**< entry in the index is conflicted */
+} git_delta_t;
+typedef struct {
+	git_oid            id;
+	const char        *path;
+	git_object_size_t  size;
+	uint32_t           flags;
+	uint16_t           mode;
+	uint16_t           id_abbrev;
+} git_diff_file;
+typedef struct {
+	git_delta_t   status;
+	uint32_t      flags;	   /**< git_diff_flag_t values */
+	uint16_t      similarity;  /**< for RENAMED and COPIED, value 0-100 */
+	uint16_t      nfiles;	   /**< number of files in this delta */
+	git_diff_file old_file;
+	git_diff_file new_file;
+} git_diff_delta;
+typedef struct {
+	int    old_start;     /**< Starting line number in old_file */
+	int    old_lines;     /**< Number of lines in old_file */
+	int    new_start;     /**< Starting line number in new_file */
+	int    new_lines;     /**< Number of lines in new_file */
+	size_t header_len;    /**< Number of bytes in header text */
+	char   header[128];   /**< Header text, NUL-byte terminated */
+} git_diff_hunk;
+typedef struct {
+	char   origin;       /**< A git_diff_line_t value */
+	int    old_lineno;   /**< Line number in old file or -1 for added line */
+	int    new_lineno;   /**< Line number in new file or -1 for deleted line */
+	int    num_lines;    /**< Number of newline characters in content */
+	size_t content_len;  /**< Number of bytes of data */
+	git_off_t content_offset; /**< Offset in the original file to the content */
+	const char *content; /**< Pointer to diff text, not NUL-byte terminated */
+} git_diff_line;
+typedef int (git_diff_line_cb)(const git_diff_delta *delta, const git_diff_hunk *hunk, const git_diff_line *line, void *payload);
+
+int git_libgit2_init();
+int git_libgit2_shutdown();
+int git_libgit2_version(int *major, int *minor, int *rev);
+
+const git_oid * git_object_id(const git_object *obj);
+void git_object_free(git_object *object);
+
+int git_repository_open_ext(git_repository **out, const char *path, unsigned int flags, const char *ceiling_dirs);
+int git_repository_open_bare(git_repository **out, const char *bare_path);
+
+int git_oid_fromstr(git_oid *out, const char *str);
+char * git_oid_tostr(char *out, size_t n, const git_oid *id);
+
+int git_reference_list(git_strarray *array, git_repository *repo);
+void git_strarray_free(git_strarray *array);
+
+int git_tag_list(git_strarray *tag_names, git_repository *repo);
+
+int git_revparse_single(git_object **out, git_repository *repo, const char *spec);
+
+int git_commit_lookup(git_commit **commit, git_repository *repo, const git_oid *id);
+int git_commit_parent(git_commit **out, const git_commit *commit, unsigned int n);
+const git_oid * git_commit_id(const git_commit *commit);
+const char * git_commit_message(const git_commit *commit);
+const char * git_commit_summary(git_commit *commit);
+const git_signature * git_commit_author(const git_commit *commit);
+
+int git_revwalk_new(git_revwalk **out, git_repository *repo);
+int git_revwalk_push_range(git_revwalk *walk, const char *range);
+int git_revwalk_next(git_oid *out, git_revwalk *walk);
+
+int git_commit_tree(git_tree **tree_out, const git_commit *commit);
+size_t git_tree_entrycount(const git_tree *tree);
+const git_tree_entry * git_tree_entry_byindex(const git_tree *tree, size_t idx);
+git_object_t git_tree_entry_type(const git_tree_entry *entry);
+const char * git_tree_entry_name(const git_tree_entry *entry);
+int git_tree_entry_bypath(git_tree_entry **out, const git_tree *root, const char *path);
+int git_tree_entry_to_object(git_object **object_out, git_repository *repo, const git_tree_entry *entry);
+void git_tree_entry_free(git_tree_entry *entry);
+
+int git_blob_is_binary(const git_blob *blob);
+git_object_size_t git_blob_rawsize(const git_blob *blob);
+const void * git_blob_rawcontent(const git_blob *blob);
+
+int git_diff_tree_to_tree(git_diff **diff, git_repository *repo, git_tree *old_tree, git_tree *new_tree, const git_diff_options *opts);
+int git_diff_print(git_diff *diff, git_diff_format_t format, git_diff_line_cb print_cb, void *payload);
+]]
+local C = ffi.load("git2")
+
+local function strarr_to_table(strarr)
+    local tab = {}
+    for i = 1, tonumber(strarr[0].count) do
+        local str = ffi.string(strarr[0].strings[i-1])
+        table.insert(tab, str)
+    end
+    return tab
+end
+
+local Commit = {}
+Commit.__index = Commit
+
+local function get_commit(commit, repo)
+    return setmetatable({commit=commit, repo=repo}, Commit)
+end
+
+function Commit:id(len)
+    len = len or 16
+    local oid = ffi.new("char ["..(len+1).."]")
+    C.git_oid_tostr(oid, len, C.git_commit_id(self.commit))
+    return ffi.string(oid)
+end
+
+function Commit:signature()
+    local sign = C.git_commit_author(self.commit)
+    return {
+        time_=tonumber(sign.when.time),
+        offset=sign.when.offset,
+        name=ffi.string(sign.name),
+        email=ffi.string(sign.email),
+    }
+end
+
+function Commit:message()
+    return ffi.string(C.git_commit_message(self.commit))
+end
+
+function Commit:summary()
+    return ffi.string(C.git_commit_summary(self.commit))
+end
+
+function Commit:parent(n)
+    n = n or 1
+    local pcommit = ffi.new("git_commit *[1]")
+    local ret = C.git_commit_parent(pcommit, self.commit, n-1)
+    if ret == 0 then
+        return get_commit(pcommit[0], self.repo)
+    else
+        return nil
+    end
+end
+
+function Commit:tree()
+    local ptree = ffi.new("git_tree *[1]")
+    local ret = C.git_commit_tree(ptree, self.commit)
+    if ret == 0 then
+        return ptree[0]
+    else
+        return nil
+    end
+end
+
+function Commit:children(tree)
+    local n = tonumber(C.git_tree_entrycount(tree))
+    local children = {}
+    for i = 1, n do
+        local entry = C.git_tree_entry_byindex(tree, i-1)
+        local entry_type = C.git_tree_entry_type(entry)
+        local entry_name = ffi.string(C.git_tree_entry_name(entry))
+        local child
+        if entry_type == 2 then
+            child = entry_name.."/"
+        elseif entry_type == 3 then
+            child = entry_name
+        end
+        table.insert(children, child)
+    end
+    return children
+end
+
+function Commit:node(entry)
+    local entry_type = C.git_tree_entry_type(entry)
+    local entry_name = C.git_tree_entry_name(entry)
+    local pobj = ffi.new("git_object *[1]")
+    C.git_tree_entry_to_object(pobj, self.repo, entry)
+    if entry_type == 2 then
+        local tree = ffi.cast("git_tree *", pobj[0])
+        local children = self:children(tree)
+        return {type_="dir", name=name, children=children}
+    elseif entry_type == 3 then
+        local blob = ffi.cast("git_blob *", pobj[0])
+        local data = ffi.string(C.git_blob_rawcontent(blob), C.git_blob_rawsize(blob))
+        local bin = C.git_blob_is_binary(blob) ~= 0
+        return {type_="file", name=name, data=data, bin=bin}
+    else
+        return nil
+    end
+end
+
+function Commit:tree_entry(path)
+    local tree = self:tree()
+    if tree == nil then
+        return nil
+    end
+    if path == "" then
+        local children = self:children(tree)
+        return {type_="dir", name="", children=children}
+    end
+    local pentry = ffi.new("git_tree_entry *[1]")
+    local ret = C.git_tree_entry_bypath(pentry, tree, path)
+    if ret == 0 then
+        return self:node(pentry[0])
+    else
+        return nil
+    end
+end
+
+local Repo = {}
+Repo.__index = Repo
+
+function Repo:refs()
+    local strarr = ffi.new("git_strarray[1]")
+    C.git_reference_list(strarr, self.repo)
+    local refs = strarr_to_table(strarr)
+    C.git_strarray_free(strarr);
+    return refs
+end
+
+function Repo:branches()
+    local branches = {}
+    for _, ref in ipairs(self:refs()) do
+        local branch_name = ref:match("refs/heads/(.*)")
+        if branch_name ~= nil then
+            table.insert(branches, branch_name)
+        end
+    end
+    return branches
+end
+
+function Repo:tags()
+    local strarr = ffi.new("git_strarray[1]")
+    C.git_tag_list(strarr, self.repo)
+    local tags = strarr_to_table(strarr)
+    C.git_strarray_free(strarr);
+    return tags
+end
+
+function Repo:commit(rev)
+    local pobj = ffi.new("git_object *[1]")
+    C.git_revparse_single(pobj, self.repo, rev)
+    local obj = pobj[0]
+    local oid = C.git_object_id(obj)
+    local pcommit = ffi.new("git_commit *[1]")
+    C.git_commit_lookup(pcommit, self.repo, oid)
+    local commit = pcommit[0]
+    C.git_object_free(obj)
+    return get_commit(commit, self.repo)
+end
+
+function Repo:find_prev(cid, dist)
+    for i, bname in ipairs(self:branches()) do
+        local cids = {}
+        local commit = self:commit(bname)
+        while commit do
+            local cur_cid = commit:id()
+            table.insert(cids, cur_cid)
+            if cur_cid == cid then
+                break
+            end
+            commit = commit:parent()
+        end
+        if cids[#cids] == cid then
+            if #cids > 1 then
+                local index = math.max(1, #cids - dist)
+                return self:commit(cids[index])
+            else
+                return nil
+            end
+        end
+    end
+    return nil
+end
+
+function Repo:diff(commit, custom_cb)
+    local new_tree = commit:tree()
+    local parent = commit:parent()
+    local old_tree = parent and parent:tree()
+    local pdiff = ffi.new("git_diff *[1]")
+    C.git_diff_tree_to_tree(pdiff, self.repo, old_tree, new_tree, nil)
+    local diff = pdiff[0]
+    local s = ""
+    local function cb(delta, hunk, line, payload)
+        local lua_line = ffi.string(line.content, line.content_len)
+        local line_type = string.char(line.origin)
+        local diff_line
+        if custom_cb ~= nil then
+            diff_line = custom_cb(line_type, lua_line)
+        else
+            diff_line = line_type .. lua_line
+        end
+        s = s .. diff_line
+        return 0
+    end
+    C.git_diff_print(diff, C.GIT_DIFF_FORMAT_PATCH, cb, nil)
+    return s
+end
+
+local function open(path)
+    local self = setmetatable({}, Repo)
+    local prepo = ffi.new("git_repository *[1]")
+    assert(C.git_repository_open_bare(prepo, path) == 0)
+    self.repo = prepo[0]
+    return self
+end
+
+return {init=C.git_libgit2_init, shutdown=C.git_libgit2_shutdown, open=open}

diff --git a/cogit/hash.lua b/cogit/hash.lua
new file mode 100644
index 0000000..c396c66
--- /dev/null
+++ b/cogit/hash.lua
@@ -0,0 +1,38 @@
+local lud = require "ludweb"
+
+local function get_pass(prompt)
+    io.write(prompt)
+    os.execute("stty -echo")
+    local pass = io.read()
+    os.execute("stty echo")
+    io.write("\n")
+    return pass
+end
+
+local function get_salt()
+    return lud.crypt.urandom(32)
+end
+
+local function hash_pass(pass, salt)
+    return lud.crypt.pbkdf2(pass, salt, 10000, 64)
+end
+
+if arg[0] ~= "hash.lua" then
+    return {get_salt=get_salt, hash_pass=hash_pass}
+end
+
+local pass, pass2
+
+repeat
+    pass  = get_pass("password: ")
+    pass2 = get_pass("repeat  : ")
+until pass2 == pass
+
+local salt = get_salt()
+local hash = hash_pass(pass, salt)
+
+local salt_b64 = lud.crypt.b64_enc(salt)
+local hash_b64 = lud.crypt.b64_enc(hash)
+
+print("salt: " .. salt_b64)
+print("hash: " .. hash_b64)

diff --git a/cogit/scan.lua b/cogit/scan.lua
new file mode 100644
index 0000000..7c7d603
--- /dev/null
+++ b/cogit/scan.lua
@@ -0,0 +1,69 @@
+local ffi = require "ffi"
+local git = require "cogit.git"
+
+ffi.cdef[[
+typedef unsigned long ino_t;
+typedef unsigned long off_t;
+struct dirent {
+    ino_t          d_ino;       /* Inode number */
+    off_t          d_off;       /* Not an offset; see below */
+    unsigned short d_reclen;    /* Length of this record */
+    unsigned char  d_type;      /* Type of file; not supported
+                                  by all filesystem types */
+    char           d_name[256]; /* Null-terminated filename */
+};
+int scandir(const char *restrict dirp,
+    struct dirent ***restrict namelist,
+    int (*filter)(const struct dirent *),
+    int (*compar)(const struct dirent **,
+        const struct dirent **));
+int alphasort(const struct dirent **a, const struct dirent **b);
+enum {
+    DT_UNKNOWN  =  0,
+    DT_FIFO     =  1,
+    DT_CHR      =  2,
+    DT_DIR      =  4,
+    DT_BLK      =  6,
+    DT_REG      =  8,
+    DT_LNK      = 10,
+    DT_SOCK     = 12,
+};
+]]
+local C = ffi.C
+
+local function listdir(path, filter)
+    local c_filter = filter and function (entry)
+        return filter(entry)
+    end
+    local pentries = ffi.new("struct dirent **[1]")
+    local n = C.scandir(path, pentries, c_filter, C.alphasort)
+    local entries = pentries[0]
+    local list = {}
+    for i = 1, n do
+        table.insert(list, ffi.string(entries[i-1].d_name))
+    end
+    return list
+end
+
+local function scanrepos(path)
+    local folders = listdir(path, function (e)
+        return e.d_type == C.DT_DIR and ffi.string(e.d_name):sub(1, 1) ~= "."
+    end)
+    local groups = {}
+    for _, folder in ipairs(folders) do
+        local group_path = path.."/"..folder
+        local git_folders = listdir(group_path, function (e)
+            return e.d_type == C.DT_DIR and (ffi.string(e.d_name):match(".*%.git$") ~= nil)
+        end)
+        local group = {}
+        for _, git_folder in ipairs(git_folders) do
+            local repo_path = group_path.."/"..git_folder
+            local repo_name = git_folder:match("(.*)%.git$")
+            group[repo_name] = git.open(repo_path)
+        end
+        groups[folder] = group
+    end
+    return groups
+end
+
+return {scanrepos=scanrepos}

diff --git a/git.lua b/git.lua
deleted file mode 100644
index b16ff24..0000000
--- a/git.lua
+++ /dev/null
@@ -1,365 +0,0 @@
-local ffi = require "ffi"
-
-ffi.cdef[[
-typedef int64_t git_off_t;
-typedef int64_t git_time_t;
-typedef uint64_t git_object_size_t;
-typedef struct git_repository git_repository;
-typedef struct git_object git_object;
-typedef struct git_commit git_commit;
-typedef struct git_tree git_tree;
-typedef struct git_tree_entry git_tree_entry;
-typedef struct git_diff git_diff;
-typedef struct git_blob git_blob;
-typedef struct git_tag git_tag;
-typedef struct git_revwalk git_revwalk;
-typedef enum {
-	GIT_OBJECT_ANY =      -2, /**< Object can be any of the following */
-	GIT_OBJECT_INVALID =  -1, /**< Object is invalid. */
-	GIT_OBJECT_COMMIT =    1, /**< A commit object. */
-	GIT_OBJECT_TREE =      2, /**< A tree (directory listing) object. */
-	GIT_OBJECT_BLOB =      3, /**< A file revision object. */
-	GIT_OBJECT_TAG =       4, /**< An annotated tag object. */
-	GIT_OBJECT_OFS_DELTA = 6, /**< A delta, base is given by an offset. */
-	GIT_OBJECT_REF_DELTA = 7  /**< A delta, base is given by object id. */
-} git_object_t;
-typedef struct git_oid {
-	/** raw binary formatted id */
-	unsigned char id[20];
-} git_oid;
-typedef struct git_strarray {
-	char **strings;
-	size_t count;
-} git_strarray;
-typedef struct git_time {
-	git_time_t time; /**< time in seconds from epoch */
-	int offset; /**< timezone offset, in minutes */
-	char sign; /**< indicator for questionable '-0000' offsets in signature */
-} git_time;
-typedef struct git_signature {
-	char *name; /**< full name of the author */
-	char *email; /**< email of the author */
-	git_time when; /**< time when the action happened */
-} git_signature;
-typedef struct git_diff_options git_diff_options;
-typedef enum {
-	GIT_DIFF_FORMAT_PATCH        = 1u, /**< full git diff */
-	GIT_DIFF_FORMAT_PATCH_HEADER = 2u, /**< just the file headers of patch */
-	GIT_DIFF_FORMAT_RAW          = 3u, /**< like git diff --raw */
-	GIT_DIFF_FORMAT_NAME_ONLY    = 4u, /**< like git diff --name-only */
-	GIT_DIFF_FORMAT_NAME_STATUS  = 5u, /**< like git diff --name-status */
-	GIT_DIFF_FORMAT_PATCH_ID     = 6u  /**< git diff as used by git patch-id */
-} git_diff_format_t;
-typedef enum {
-	GIT_DELTA_UNMODIFIED = 0,  /**< no changes */
-	GIT_DELTA_ADDED = 1,	   /**< entry does not exist in old version */
-	GIT_DELTA_DELETED = 2,	   /**< entry does not exist in new version */
-	GIT_DELTA_MODIFIED = 3,    /**< entry content changed between old and new */
-	GIT_DELTA_RENAMED = 4,     /**< entry was renamed between old and new */
-	GIT_DELTA_COPIED = 5,      /**< entry was copied from another old entry */
-	GIT_DELTA_IGNORED = 6,     /**< entry is ignored item in workdir */
-	GIT_DELTA_UNTRACKED = 7,   /**< entry is untracked item in workdir */
-	GIT_DELTA_TYPECHANGE = 8,  /**< type of entry changed between old and new */
-	GIT_DELTA_UNREADABLE = 9,  /**< entry is unreadable */
-	GIT_DELTA_CONFLICTED = 10  /**< entry in the index is conflicted */
-} git_delta_t;
-typedef struct {
-	git_oid            id;
-	const char        *path;
-	git_object_size_t  size;
-	uint32_t           flags;
-	uint16_t           mode;
-	uint16_t           id_abbrev;
-} git_diff_file;
-typedef struct {
-	git_delta_t   status;
-	uint32_t      flags;	   /**< git_diff_flag_t values */
-	uint16_t      similarity;  /**< for RENAMED and COPIED, value 0-100 */
-	uint16_t      nfiles;	   /**< number of files in this delta */
-	git_diff_file old_file;
-	git_diff_file new_file;
-} git_diff_delta;
-typedef struct {
-	int    old_start;     /**< Starting line number in old_file */
-	int    old_lines;     /**< Number of lines in old_file */
-	int    new_start;     /**< Starting line number in new_file */
-	int    new_lines;     /**< Number of lines in new_file */
-	size_t header_len;    /**< Number of bytes in header text */
-	char   header[128];   /**< Header text, NUL-byte terminated */
-} git_diff_hunk;
-typedef struct {
-	char   origin;       /**< A git_diff_line_t value */
-	int    old_lineno;   /**< Line number in old file or -1 for added line */
-	int    new_lineno;   /**< Line number in new file or -1 for deleted line */
-	int    num_lines;    /**< Number of newline characters in content */
-	size_t content_len;  /**< Number of bytes of data */
-	git_off_t content_offset; /**< Offset in the original file to the content */
-	const char *content; /**< Pointer to diff text, not NUL-byte terminated */
-} git_diff_line;
-typedef int (git_diff_line_cb)(const git_diff_delta *delta, const git_diff_hunk *hunk, const git_diff_line *line, void *payload);
-
-int git_libgit2_init();
-int git_libgit2_shutdown();
-int git_libgit2_version(int *major, int *minor, int *rev);
-
-const git_oid * git_object_id(const git_object *obj);
-void git_object_free(git_object *object);
-
-int git_repository_open_ext(git_repository **out, const char *path, unsigned int flags, const char *ceiling_dirs);
-int git_repository_open_bare(git_repository **out, const char *bare_path);
-
-int git_oid_fromstr(git_oid *out, const char *str);
-char * git_oid_tostr(char *out, size_t n, const git_oid *id);
-
-int git_reference_list(git_strarray *array, git_repository *repo);
-void git_strarray_free(git_strarray *array);
-
-int git_tag_list(git_strarray *tag_names, git_repository *repo);
-
-int git_revparse_single(git_object **out, git_repository *repo, const char *spec);
-
-int git_commit_lookup(git_commit **commit, git_repository *repo, const git_oid *id);
-int git_commit_parent(git_commit **out, const git_commit *commit, unsigned int n);
-const git_oid * git_commit_id(const git_commit *commit);
-const char * git_commit_message(const git_commit *commit);
-const char * git_commit_summary(git_commit *commit);
-const git_signature * git_commit_author(const git_commit *commit);
-
-int git_revwalk_new(git_revwalk **out, git_repository *repo);
-int git_revwalk_push_range(git_revwalk *walk, const char *range);
-int git_revwalk_next(git_oid *out, git_revwalk *walk);
-
-int git_commit_tree(git_tree **tree_out, const git_commit *commit);
-size_t git_tree_entrycount(const git_tree *tree);
-const git_tree_entry * git_tree_entry_byindex(const git_tree *tree, size_t idx);
-git_object_t git_tree_entry_type(const git_tree_entry *entry);
-const char * git_tree_entry_name(const git_tree_entry *entry);
-int git_tree_entry_bypath(git_tree_entry **out, const git_tree *root, const char *path);
-int git_tree_entry_to_object(git_object **object_out, git_repository *repo, const git_tree_entry *entry);
-void git_tree_entry_free(git_tree_entry *entry);
-
-int git_blob_is_binary(const git_blob *blob);
-git_object_size_t git_blob_rawsize(const git_blob *blob);
-const void * git_blob_rawcontent(const git_blob *blob);
-
-int git_diff_tree_to_tree(git_diff **diff, git_repository *repo, git_tree *old_tree, git_tree *new_tree, const git_diff_options *opts);
-int git_diff_print(git_diff *diff, git_diff_format_t format, git_diff_line_cb print_cb, void *payload);
-]]
-local C = ffi.load("git2")
-
-local function strarr_to_table(strarr)
-    local tab = {}
-    for i = 1, tonumber(strarr[0].count) do
-        local str = ffi.string(strarr[0].strings[i-1])
-        table.insert(tab, str)
-    end
-    return tab
-end
-
-local Commit = {}
-Commit.__index = Commit
-
-local function get_commit(commit, repo)
-    return setmetatable({commit=commit, repo=repo}, Commit)
-end
-
-function Commit:id(len)
-    len = len or 16
-    local oid = ffi.new("char ["..(len+1).."]")
-    C.git_oid_tostr(oid, len, C.git_commit_id(self.commit))
-    return ffi.string(oid)
-end
-
-function Commit:signature()
-    local sign = C.git_commit_author(self.commit)
-    return {
-        time_=tonumber(sign.when.time),
-        offset=sign.when.offset,
-        name=ffi.string(sign.name),
-        email=ffi.string(sign.email),
-    }
-end
-
-function Commit:message()
-    return ffi.string(C.git_commit_message(self.commit))
-end
-
-function Commit:summary()
-    return ffi.string(C.git_commit_summary(self.commit))
-end
-
-function Commit:parent(n)
-    n = n or 1
-    local pcommit = ffi.new("git_commit *[1]")
-    local ret = C.git_commit_parent(pcommit, self.commit, n-1)
-    if ret == 0 then
-        return get_commit(pcommit[0], self.repo)
-    else
-        return nil
-    end
-end
-
-function Commit:tree()
-    local ptree = ffi.new("git_tree *[1]")
-    local ret = C.git_commit_tree(ptree, self.commit)
-    if ret == 0 then
-        return ptree[0]
-    else
-        return nil
-    end
-end
-
-function Commit:children(tree)
-    local n = tonumber(C.git_tree_entrycount(tree))
-    local children = {}
-    for i = 1, n do
-        local entry = C.git_tree_entry_byindex(tree, i-1)
-        local entry_type = C.git_tree_entry_type(entry)
-        local entry_name = ffi.string(C.git_tree_entry_name(entry))
-        local child
-        if entry_type == 2 then
-            child = entry_name.."/"
-        elseif entry_type == 3 then
-            child = entry_name
-        end
-        table.insert(children, child)
-    end
-    return children
-end
-
-function Commit:node(entry)
-    local entry_type = C.git_tree_entry_type(entry)
-    local entry_name = C.git_tree_entry_name(entry)
-    local pobj = ffi.new("git_object *[1]")
-    C.git_tree_entry_to_object(pobj, self.repo, entry)
-    if entry_type == 2 then
-        local tree = ffi.cast("git_tree *", pobj[0])
-        local children = self:children(tree)
-        return {type_="dir", name=name, children=children}
-    elseif entry_type == 3 then
-        local blob = ffi.cast("git_blob *", pobj[0])
-        local data = ffi.string(C.git_blob_rawcontent(blob), C.git_blob_rawsize(blob))
-        local bin = C.git_blob_is_binary(blob) ~= 0
-        return {type_="file", name=name, data=data, bin=bin}
-    else
-        return nil
-    end
-end
-
-function Commit:tree_entry(path)
-    local tree = self:tree()
-    if tree == nil then
-        return nil
-    end
-    if path == "" then
-        local children = self:children(tree)
-        return {type_="dir", name="", children=children}
-    end
-    local pentry = ffi.new("git_tree_entry *[1]")
-    local ret = C.git_tree_entry_bypath(pentry, tree, path)
-    if ret == 0 then
-        return self:node(pentry[0])
-    else
-        return nil
-    end
-end
-
-local Repo = {}
-Repo.__index = Repo
-
-function Repo:refs()
-    local strarr = ffi.new("git_strarray[1]")
-    C.git_reference_list(strarr, self.repo)
-    local refs = strarr_to_table(strarr)
-    C.git_strarray_free(strarr);
-    return refs
-end
-
-function Repo:branches()
-    local branches = {}
-    for _, ref in ipairs(self:refs()) do
-        local branch_name = ref:match("refs/heads/(.*)")
-        if branch_name ~= nil then
-            table.insert(branches, branch_name)
-        end
-    end
-    return branches
-end
-
-function Repo:tags()
-    local strarr = ffi.new("git_strarray[1]")
-    C.git_tag_list(strarr, self.repo)
-    local tags = strarr_to_table(strarr)
-    C.git_strarray_free(strarr);
-    return tags
-end
-
-function Repo:commit(rev)
-    local pobj = ffi.new("git_object *[1]")
-    C.git_revparse_single(pobj, self.repo, rev)
-    local obj = pobj[0]
-    local oid = C.git_object_id(obj)
-    local pcommit = ffi.new("git_commit *[1]")
-    C.git_commit_lookup(pcommit, self.repo, oid)
-    local commit = pcommit[0]
-    C.git_object_free(obj)
-    return get_commit(commit, self.repo)
-end
-
-function Repo:find_prev(cid, dist)
-    for i, bname in ipairs(self:branches()) do
-        local cids = {}
-        local commit = self:commit(bname)
-        while commit do
-            local cur_cid = commit:id()
-            table.insert(cids, cur_cid)
-            if cur_cid == cid then
-                break
-            end
-            commit = commit:parent()
-        end
-        if cids[#cids] == cid then
-            if #cids > 1 then
-                local index = math.max(1, #cids - dist)
-                return self:commit(cids[index])
-            else
-                return nil
-            end
-        end
-    end
-    return nil
-end
-
-function Repo:diff(commit, custom_cb)
-    local new_tree = commit:tree()
-    local parent = commit:parent()
-    local old_tree = parent and parent:tree()
-    local pdiff = ffi.new("git_diff *[1]")
-    C.git_diff_tree_to_tree(pdiff, self.repo, old_tree, new_tree, nil)
-    local diff = pdiff[0]
-    local s = ""
-    local function cb(delta, hunk, line, payload)
-        local lua_line = ffi.string(line.content, line.content_len)
-        local line_type = string.char(line.origin)
-        local diff_line
-        if custom_cb ~= nil then
-            diff_line = custom_cb(line_type, lua_line)
-        else
-            diff_line = line_type .. lua_line
-        end
-        s = s .. diff_line
-        return 0
-    end
-    C.git_diff_print(diff, C.GIT_DIFF_FORMAT_PATCH, cb, nil)
-    return s
-end
-
-local function open(path)
-    local self = setmetatable({}, Repo)
-    local prepo = ffi.new("git_repository *[1]")
-    assert(C.git_repository_open_bare(prepo, path) == 0)
-    self.repo = prepo[0]
-    return self
-end
-
-return {init=C.git_libgit2_init, shutdown=C.git_libgit2_shutdown, open=open}

diff --git a/hash.lua b/hash.lua
deleted file mode 100644
index c396c66..0000000
--- a/hash.lua
+++ /dev/null
@@ -1,38 +0,0 @@
-local lud = require "ludweb"
-
-local function get_pass(prompt)
-    io.write(prompt)
-    os.execute("stty -echo")
-    local pass = io.read()
-    os.execute("stty echo")
-    io.write("\n")
-    return pass
-end
-
-local function get_salt()
-    return lud.crypt.urandom(32)
-end
-
-local function hash_pass(pass, salt)
-    return lud.crypt.pbkdf2(pass, salt, 10000, 64)
-end
-
-if arg[0] ~= "hash.lua" then
-    return {get_salt=get_salt, hash_pass=hash_pass}
-end
-
-local pass, pass2
-
-repeat
-    pass  = get_pass("password: ")
-    pass2 = get_pass("repeat  : ")
-until pass2 == pass
-
-local salt = get_salt()
-local hash = hash_pass(pass, salt)
-
-local salt_b64 = lud.crypt.b64_enc(salt)
-local hash_b64 = lud.crypt.b64_enc(hash)
-
-print("salt: " .. salt_b64)
-print("hash: " .. hash_b64)

diff --git a/main.lua b/main.lua
index 28f1316..94ad8f6 100644
--- a/main.lua
+++ b/main.lua
@@ -1,3 +1,3 @@
-local cogit = require "cogit"
+local cogit = require "cogit.cogit"
 
 cogit.new_cogit(arg[1]):run()

diff --git a/scan.lua b/scan.lua
deleted file mode 100644
index bd4c5a1..0000000
--- a/scan.lua
+++ /dev/null
@@ -1,69 +0,0 @@
-local ffi = require "ffi"
-local git = require "git"
-
-ffi.cdef[[
-typedef unsigned long ino_t;
-typedef unsigned long off_t;
-struct dirent {
-    ino_t          d_ino;       /* Inode number */
-    off_t          d_off;       /* Not an offset; see below */
-    unsigned short d_reclen;    /* Length of this record */
-    unsigned char  d_type;      /* Type of file; not supported
-                                  by all filesystem types */
-    char           d_name[256]; /* Null-terminated filename */
-};
-int scandir(const char *restrict dirp,
-    struct dirent ***restrict namelist,
-    int (*filter)(const struct dirent *),
-    int (*compar)(const struct dirent **,
-        const struct dirent **));
-int alphasort(const struct dirent **a, const struct dirent **b);
-enum {
-    DT_UNKNOWN  =  0,
-    DT_FIFO     =  1,
-    DT_CHR      =  2,
-    DT_DIR      =  4,
-    DT_BLK      =  6,
-    DT_REG      =  8,
-    DT_LNK      = 10,
-    DT_SOCK     = 12,
-};
-]]
-local C = ffi.C
-
-local function listdir(path, filter)
-    local c_filter = filter and function (entry)
-        return filter(entry)
-    end
-    local pentries = ffi.new("struct dirent **[1]")
-    local n = C.scandir(path, pentries, c_filter, C.alphasort)
-    local entries = pentries[0]
-    local list = {}
-    for i = 1, n do
-        table.insert(list, ffi.string(entries[i-1].d_name))
-    end
-    return list
-end
-
-local function scanrepos(path)
-    local folders = listdir(path, function (e)
-        return e.d_type == C.DT_DIR and ffi.string(e.d_name):sub(1, 1) ~= "."
-    end)
-    local groups = {}
-    for _, folder in ipairs(folders) do
-        local group_path = path.."/"..folder
-        local git_folders = listdir(group_path, function (e)
-            return e.d_type == C.DT_DIR and (ffi.string(e.d_name):match(".*%.git$") ~= nil)
-        end)
-        local group = {}
-        for _, git_folder in ipairs(git_folders) do
-            local repo_path = group_path.."/"..git_folder
-            local repo_name = git_folder:match("(.*)%.git$")
-            group[repo_name] = git.open(repo_path)
-        end
-        groups[folder] = group
-    end
-    return groups
-end
-
-return {scanrepos=scanrepos}