From 3fff4bb3172f77b292b0c913749e81bedd3545f3 Mon Sep 17 00:00:00 2001
From: "Rafael G. Martins" <rafael@rafaelmartins.eng.br>
Date: Thu, 29 Sep 2016 00:49:36 +0200
Subject: git-receiver: splitted code

---
 src/blogc-git-receiver/main.c         | 481 +---------------------------------
 src/blogc-git-receiver/post-receive.c |  29 ++
 src/blogc-git-receiver/post-receive.h |  14 +
 src/blogc-git-receiver/pre-receive.c  | 281 ++++++++++++++++++++
 src/blogc-git-receiver/pre-receive.h  |  14 +
 src/blogc-git-receiver/shell.c        | 206 +++++++++++++++
 src/blogc-git-receiver/shell.h        |  14 +
 7 files changed, 564 insertions(+), 475 deletions(-)
 create mode 100644 src/blogc-git-receiver/post-receive.c
 create mode 100644 src/blogc-git-receiver/post-receive.h
 create mode 100644 src/blogc-git-receiver/pre-receive.c
 create mode 100644 src/blogc-git-receiver/pre-receive.h
 create mode 100644 src/blogc-git-receiver/shell.c
 create mode 100644 src/blogc-git-receiver/shell.h

(limited to 'src')

diff --git a/src/blogc-git-receiver/main.c b/src/blogc-git-receiver/main.c
index 86e1027..fb6e724 100644
--- a/src/blogc-git-receiver/main.c
+++ b/src/blogc-git-receiver/main.c
@@ -6,481 +6,12 @@
  * See the file LICENSE.
  */
 
-#include <stdbool.h>
-#include <stddef.h>
 #include <stdio.h>
-#include <stdlib.h>
 #include <string.h>
 #include <libgen.h>
-#include <unistd.h>
-#include <errno.h>
-#include <sys/stat.h>
-#include <dirent.h>
-#include <time.h>
-
-#include "../common/utils.h"
-
-#ifndef BUFFER_SIZE
-#define BUFFER_SIZE 4096
-#endif
-
-
-static unsigned int
-cpu_count(void)
-{
-#ifdef _SC_NPROCESSORS_ONLN
-    long num = sysconf(_SC_NPROCESSORS_ONLN);
-    if (num >= 1)
-        return (unsigned int) num;
-#endif
-    return 1;
-}
-
-
-static void
-rmdir_recursive(const char *dir)
-{
-    struct stat buf;
-    if (0 != stat(dir, &buf)) {
-        fprintf(stderr, "warning: failed to remove directory (%s): %s\n", dir,
-            strerror(errno));
-        return;
-    }
-    if (!S_ISDIR(buf.st_mode)) {
-        fprintf(stderr, "error: trying to remove invalid directory: %s\n", dir);
-        exit(2);
-    }
-    DIR *d = opendir(dir);
-    if (d == NULL) {
-        fprintf(stderr, "error: failed to open directory: %s\n",
-            strerror(errno));
-        exit(2);
-    }
-    struct dirent *e;
-    while (NULL != (e = readdir(d))) {
-        if ((0 == strcmp(e->d_name, ".")) || (0 == strcmp(e->d_name, "..")))
-            continue;
-        char *f = bc_strdup_printf("%s/%s", dir, e->d_name);
-        if (0 != stat(f, &buf)) {
-            fprintf(stderr, "error: failed to stat directory entry (%s): %s\n",
-                e->d_name, strerror(errno));
-            free(f);
-            exit(2);
-        }
-        if (S_ISDIR(buf.st_mode)) {
-            rmdir_recursive(f);
-        }
-        else if (0 != unlink(f)) {
-            fprintf(stderr, "error: failed to remove file (%s): %s\n", f,
-                strerror(errno));
-            free(f);
-            exit(2);
-        }
-        free(f);
-    }
-    if (0 != closedir(d)) {
-        fprintf(stderr, "error: failed to close directory: %s\n",
-            strerror(errno));
-        exit(2);
-    }
-    if (0 != rmdir(dir)) {
-        fprintf(stderr, "error: failed to remove directory: %s\n",
-            strerror(errno));
-        exit(2);
-    }
-}
-
-
-static int
-git_shell(int argc, char *argv[])
-{
-    int rv = 0;
-
-    char *repo = NULL;
-    char *command_orig = NULL;
-    char *command_name = NULL;
-    char command_new[BUFFER_SIZE];
-
-    bool exec_git = false;
-
-    // validate git command
-    size_t len = strlen(argv[2]);
-    if (!((len > 17 && (0 == strncmp(argv[2], "git-receive-pack ", 17))) ||
-          (len > 16 && (0 == strncmp(argv[2], "git-upload-pack ", 16))) ||
-          (len > 19 && (0 == strncmp(argv[2], "git-upload-archive ", 19)))))
-    {
-        fprintf(stderr, "error: unsupported git command: %s\n", argv[2]);
-        rv = 1;
-        goto cleanup;
-    }
-
-    // get shell path
-    char *self = getenv("SHELL");
-    if (self == NULL) {
-        fprintf(stderr, "error: failed to find blogc-git-receiver path\n");
-        rv = 1;
-        goto cleanup;
-    }
-
-    // get home path
-    char *home = getenv("HOME");
-    if (home == NULL) {
-        fprintf(stderr, "error: failed to find user home path\n");
-        rv = 1;
-        goto cleanup;
-    }
-
-    // get git repository
-    command_orig = bc_strdup(argv[2]);
-    char *p, *r;
-    for (p = command_orig; *p != ' ' && *p != '\0'; p++);
-    if (*p == ' ')
-        p++;
-    if (*p == '\'' || *p == '"')
-        p++;
-    if (*p == '/')
-        p++;
-    for (r = p; *p != '\'' && *p != '"' && *p != '\0'; p++);
-    if (*p == '\'' || *p == '"')
-        *p = '\0';
-    if (*--p == '/')
-        *p = '\0';
-
-    repo = bc_strdup_printf("repos/%s", r);
-
-    // check if repository is sane
-    if (0 == strlen(repo)) {
-        fprintf(stderr, "error: invalid repository\n");
-        rv = 1;
-        goto cleanup;
-    }
-
-    if (0 == strncmp(argv[2], "git-upload-", 11))  // no need to check len here
-        goto git_exec;
-
-    if (0 != chdir(home)) {
-        fprintf(stderr, "error: failed to chdir (%s): %s\n", home,
-            strerror(errno));
-        rv = 1;
-        goto cleanup;
-    }
-
-    if (0 != access(repo, F_OK)) {
-        char *git_init_cmd = bc_strdup_printf(
-            "git init --bare \"%s\" > /dev/null", repo);
-        if (0 != system(git_init_cmd)) {
-            fprintf(stderr, "error: failed to create git repository: %s\n",
-                repo);
-            rv = 1;
-            free(git_init_cmd);
-            goto cleanup;
-        }
-        free(git_init_cmd);
-    }
-
-    if (0 != chdir(repo)) {
-        fprintf(stderr, "error: failed to chdir (%s/%s): %s\n", home, repo,
-            strerror(errno));
-        rv = 1;
-        goto cleanup;
-    }
-
-    if (0 != access("hooks", F_OK)) {
-        // openwrt git package won't install git templates, then the git
-        // repositories created with it won't have the hooks/ directory.
-        if (0 != mkdir("hooks", 0777)) {  // mkdir honors umask for us.
-            fprintf(stderr, "error: failed to create directory (%s/%s/hooks): "
-                "%s\n", home, repo, strerror(errno));
-            rv = 1;
-            goto cleanup;
-        }
-    }
-
-    if (0 != chdir("hooks")) {
-        fprintf(stderr, "error: failed to chdir (%s/%s/hooks): %s\n", home,
-            repo, strerror(errno));
-        rv = 1;
-        goto cleanup;
-    }
-
-    if (0 == access("pre-receive", F_OK)) {
-        if (0 != unlink("pre-receive")) {
-            fprintf(stderr, "error: failed to remove old symlink "
-                "(%s/%s/hooks/pre-receive): %s\n", home, repo, strerror(errno));
-            rv = 1;
-            goto cleanup;
-        }
-    }
-
-    if (0 != symlink(self, "pre-receive")) {
-        fprintf(stderr, "error: failed to create symlink "
-            "(%s/%s/hooks/pre-receive): %s\n", home, repo, strerror(errno));
-        rv = 1;
-        goto cleanup;
-    }
-
-    if (0 == access("post-receive", F_OK)) {
-        if (0 != unlink("post-receive")) {
-            fprintf(stderr, "error: failed to remove old symlink "
-                "(%s/%s/hooks/post-receive): %s\n", home, repo, strerror(errno));
-            rv = 1;
-            goto cleanup;
-        }
-    }
-
-    if (0 != symlink(self, "post-receive")) {
-        fprintf(stderr, "error: failed to create symlink "
-            "(%s/%s/hooks/post-receive): %s\n", home, repo, strerror(errno));
-        rv = 1;
-        goto cleanup;
-    }
-
-    if (0 != chdir(home)) {
-        fprintf(stderr, "error: failed to chdir (%s): %s\n", home,
-            strerror(errno));
-        rv = 1;
-        goto cleanup;
-    }
-
-git_exec:
-    command_name = bc_strdup(argv[2]);
-    for (p = command_name; *p != ' ' && *p != '\0'; p++);
-    if (*p == ' ')
-        *p = '\0';
-
-    if (BUFFER_SIZE < (strlen(command_name) + strlen(repo) + 4)) {
-        fprintf(stderr, "error: git-shell command is too big\n");
-        rv = 1;
-        goto cleanup;
-    }
-
-    if (snprintf(command_new, BUFFER_SIZE, "%s '%s'", command_name, repo))
-        exec_git = true;
-
-cleanup:
-    free(repo);
-    free(command_orig);
-    free(command_name);
-
-    if (exec_git) {
-        execlp("git-shell", "git-shell", "-c", command_new, NULL);
-
-        // execlp only returns on error, then something bad happened
-        fprintf(stderr, "error: failed to execute git-shell\n");
-        rv = 1;
-    }
-
-    return rv;
-}
-
-
-static int
-git_post_receive_hook(int argc, char *argv[])
-{
-    if (0 != system("git config --local remote.mirror.pushurl &> /dev/null")) {
-        if (0 != system("git config --local remote.mirror.url &> /dev/null")) {
-            fprintf(stderr, "warning: repository mirroring disabled\n");
-            return 0;
-        }
-    }
-
-    // at this point we know that we have a remote called mirror, we can just
-    // push to it.
-    if (0 != system("git push --mirror mirror"))
-        fprintf(stderr, "warning: failed push to git mirror\n");
-
-    return 0;
-}
-
-
-typedef enum {
-    START_OLD = 1,
-    OLD,
-    START_NEW,
-    NEW,
-    START_REF,
-    REF
-} input_state_t;
-
-
-static int
-git_pre_receive_hook(int argc, char *argv[])
-{
-    int c;
-    char buffer[BUFFER_SIZE];
-
-    input_state_t state = START_OLD;
-    size_t i = 0;
-    size_t start = 0;
-
-    int rv = 0;
-    char *new = NULL;
-    char *master = NULL;
-
-    while (EOF != (c = getc(stdin))) {
-
-        buffer[i] = (char) c;
-
-        switch (state) {
-            case START_OLD:
-                start = i;
-                state = OLD;
-                break;
-            case OLD:
-                if (c != ' ')
-                    break;
-                // no need to store old
-                state = START_NEW;
-                break;
-            case START_NEW:
-                start = i;
-                state = NEW;
-                break;
-            case NEW:
-                if (c != ' ')
-                    break;
-                state = START_REF;
-                new = strndup(buffer + start, i - start);
-                break;
-            case START_REF:
-                start = i;
-                state = REF;
-                break;
-            case REF:
-                if (c != '\n')
-                    break;
-                state = START_OLD;
-                // we just care about a ref (refs/heads/master), everything
-                // else is disposable :)
-                if (!((i - start == 17) &&
-                      (0 == strncmp("refs/heads/master", buffer + start, 17))))
-                {
-                    free(new);
-                    new = NULL;
-                    break;
-                }
-                master = new;
-                break;
-        }
-
-        if (++i >= BUFFER_SIZE) {
-            fprintf(stderr, "error: pre-receive hook payload is too big.\n");
-            rv = 1;
-            goto cleanup2;
-        }
-    }
-
-    if (master == NULL) {
-        fprintf(stderr, "warning: no reference to master branch found. "
-            "nothing to deploy.\n");
-        goto cleanup2;
-    }
-
-    char *repo_dir = NULL;
-    char *output_dir = NULL;
-
-    if (NULL == getcwd(buffer, BUFFER_SIZE)) {
-        fprintf(stderr, "error: failed to get repository remote path: %s\n",
-            strerror(errno));
-        rv = 1;
-        goto cleanup;
-    }
-
-    repo_dir = bc_strdup(buffer);
-
-    char dir[] = "/tmp/blogc_XXXXXX";
-    if (NULL == mkdtemp(dir)) {
-        rv = 1;
-        goto cleanup;
-    }
-
-    char *git_archive_cmd = bc_strdup_printf(
-        "git archive \"%s\" | tar -x -C \"%s\" -f -", master, dir);
-    if (0 != system(git_archive_cmd)) {
-        fprintf(stderr, "error: failed to extract git content to temporary "
-            "directory: %s\n", dir);
-        rv = 1;
-        free(git_archive_cmd);
-        goto cleanup;
-    }
-    free(git_archive_cmd);
-
-    if (0 != chdir(dir)) {
-        fprintf(stderr, "error: failed to chdir (%s): %s\n", dir,
-            strerror(errno));
-        rv = 1;
-        goto cleanup;
-    }
-
-    if ((0 != access("Makefile", F_OK)) && (0 != access("GNUMakefile", F_OK))) {
-        fprintf(stderr, "warning: no makefile found. skipping ...\n");
-        goto cleanup;
-    }
-
-    char *home = getenv("HOME");
-    if (home == NULL) {
-        fprintf(stderr, "error: failed to find user home path\n");
-        rv = 1;
-        goto cleanup;
-    }
-
-    unsigned long epoch = time(NULL);
-    output_dir = bc_strdup_printf("%s/builds/%s-%lu", home, master, epoch);
-    char *gmake_cmd = bc_strdup_printf(
-        "gmake -j%d OUTPUT_DIR=\"%s\" BLOGC_GIT_RECEIVER=1",
-        cpu_count(), output_dir);
-    fprintf(stdout, "running command: %s\n\n", gmake_cmd);
-    fflush(stdout);
-    if (0 != system(gmake_cmd)) {
-        fprintf(stderr, "error: failed to build website ...\n");
-        rmdir_recursive(output_dir);
-        free(gmake_cmd);
-        rv = 1;
-        goto cleanup;
-    }
-    free(gmake_cmd);
-
-    if (0 != chdir(repo_dir)) {
-        fprintf(stderr, "error: failed to chdir (%s): %s\n", repo_dir,
-            strerror(errno));
-        rmdir_recursive(output_dir);
-        rv = 1;
-        goto cleanup;
-    }
-
-    char *htdocs_sym = NULL;
-    ssize_t htdocs_sym_len = readlink("htdocs", buffer, BUFFER_SIZE);
-    if (0 < htdocs_sym_len) {
-        if (0 != unlink("htdocs")) {
-            fprintf(stderr, "error: failed to remove symlink (%s/htdocs): %s\n",
-                repo_dir, strerror(errno));
-            rmdir_recursive(output_dir);
-            rv = 1;
-            goto cleanup;
-        }
-        buffer[htdocs_sym_len] = '\0';
-        htdocs_sym = buffer;
-    }
-
-    if (0 != symlink(output_dir, "htdocs")) {
-        fprintf(stderr, "error: failed to create symlink (%s/htdocs): %s\n",
-            repo_dir, strerror(errno));
-        rmdir_recursive(output_dir);
-        rv = 1;
-        goto cleanup;
-    }
-
-    if (htdocs_sym != NULL)
-        rmdir_recursive(htdocs_sym);
-
-cleanup:
-    free(output_dir);
-    rmdir_recursive(dir);
-    free(repo_dir);
-cleanup2:
-    free(new);
-    return rv;
-}
+#include "shell.h"
+#include "pre-receive.h"
+#include "post-receive.h"
 
 
 int
@@ -488,13 +19,13 @@ main(int argc, char *argv[])
 {
     if (argc > 0) {
         if (0 == strcmp(basename(argv[0]), "pre-receive"))
-            return git_pre_receive_hook(argc, argv);
+            return bgr_pre_receive_hook(argc, argv);
         if (0 == strcmp(basename(argv[0]), "post-receive"))
-            return git_post_receive_hook(argc, argv);
+            return bgr_post_receive_hook(argc, argv);
     }
 
     if (argc == 3 && (0 == strcmp(argv[1], "-c")))
-        return git_shell(argc, argv);
+        return bgr_shell(argc, argv);
 
     fprintf(stderr, "error: this is a special shell, go away!\n");
     return 1;
diff --git a/src/blogc-git-receiver/post-receive.c b/src/blogc-git-receiver/post-receive.c
new file mode 100644
index 0000000..8310939
--- /dev/null
+++ b/src/blogc-git-receiver/post-receive.c
@@ -0,0 +1,29 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file LICENSE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+
+int
+bgr_post_receive_hook(int argc, char *argv[])
+{
+    if (0 != system("git config --local remote.mirror.pushurl &> /dev/null")) {
+        if (0 != system("git config --local remote.mirror.url &> /dev/null")) {
+            fprintf(stderr, "warning: repository mirroring disabled\n");
+            return 0;
+        }
+    }
+
+    // at this point we know that we have a remote called mirror, we can just
+    // push to it.
+    if (0 != system("git push --mirror mirror"))
+        fprintf(stderr, "warning: failed push to git mirror\n");
+
+    return 0;
+}
diff --git a/src/blogc-git-receiver/post-receive.h b/src/blogc-git-receiver/post-receive.h
new file mode 100644
index 0000000..a28dd5a
--- /dev/null
+++ b/src/blogc-git-receiver/post-receive.h
@@ -0,0 +1,14 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file LICENSE.
+ */
+
+#ifndef _POST_RECEIVE_H
+#define _POST_RECEIVE_H
+
+int bgr_post_receive_hook(int argc, char *argv[]);
+
+#endif /* _POST_RECEIVE_H */
diff --git a/src/blogc-git-receiver/pre-receive.c b/src/blogc-git-receiver/pre-receive.c
new file mode 100644
index 0000000..8c35537
--- /dev/null
+++ b/src/blogc-git-receiver/pre-receive.c
@@ -0,0 +1,281 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file LICENSE.
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <libgen.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <time.h>
+
+#include "../common/utils.h"
+
+#ifndef BUFFER_SIZE
+#define BUFFER_SIZE 4096
+#endif
+
+
+static unsigned int
+cpu_count(void)
+{
+#ifdef _SC_NPROCESSORS_ONLN
+    long num = sysconf(_SC_NPROCESSORS_ONLN);
+    if (num >= 1)
+        return (unsigned int) num;
+#endif
+    return 1;
+}
+
+
+static void
+rmdir_recursive(const char *dir)
+{
+    struct stat buf;
+    if (0 != stat(dir, &buf)) {
+        fprintf(stderr, "warning: failed to remove directory (%s): %s\n", dir,
+            strerror(errno));
+        return;
+    }
+    if (!S_ISDIR(buf.st_mode)) {
+        fprintf(stderr, "error: trying to remove invalid directory: %s\n", dir);
+        exit(2);
+    }
+    DIR *d = opendir(dir);
+    if (d == NULL) {
+        fprintf(stderr, "error: failed to open directory: %s\n",
+            strerror(errno));
+        exit(2);
+    }
+    struct dirent *e;
+    while (NULL != (e = readdir(d))) {
+        if ((0 == strcmp(e->d_name, ".")) || (0 == strcmp(e->d_name, "..")))
+            continue;
+        char *f = bc_strdup_printf("%s/%s", dir, e->d_name);
+        if (0 != stat(f, &buf)) {
+            fprintf(stderr, "error: failed to stat directory entry (%s): %s\n",
+                e->d_name, strerror(errno));
+            free(f);
+            exit(2);
+        }
+        if (S_ISDIR(buf.st_mode)) {
+            rmdir_recursive(f);
+        }
+        else if (0 != unlink(f)) {
+            fprintf(stderr, "error: failed to remove file (%s): %s\n", f,
+                strerror(errno));
+            free(f);
+            exit(2);
+        }
+        free(f);
+    }
+    if (0 != closedir(d)) {
+        fprintf(stderr, "error: failed to close directory: %s\n",
+            strerror(errno));
+        exit(2);
+    }
+    if (0 != rmdir(dir)) {
+        fprintf(stderr, "error: failed to remove directory: %s\n",
+            strerror(errno));
+        exit(2);
+    }
+}
+
+
+typedef enum {
+    START_OLD = 1,
+    OLD,
+    START_NEW,
+    NEW,
+    START_REF,
+    REF
+} input_state_t;
+
+
+int
+bgr_pre_receive_hook(int argc, char *argv[])
+{
+    int c;
+    char buffer[BUFFER_SIZE];
+
+    input_state_t state = START_OLD;
+    size_t i = 0;
+    size_t start = 0;
+
+    int rv = 0;
+    char *new = NULL;
+    char *master = NULL;
+
+    while (EOF != (c = getc(stdin))) {
+
+        buffer[i] = (char) c;
+
+        switch (state) {
+            case START_OLD:
+                start = i;
+                state = OLD;
+                break;
+            case OLD:
+                if (c != ' ')
+                    break;
+                // no need to store old
+                state = START_NEW;
+                break;
+            case START_NEW:
+                start = i;
+                state = NEW;
+                break;
+            case NEW:
+                if (c != ' ')
+                    break;
+                state = START_REF;
+                new = strndup(buffer + start, i - start);
+                break;
+            case START_REF:
+                start = i;
+                state = REF;
+                break;
+            case REF:
+                if (c != '\n')
+                    break;
+                state = START_OLD;
+                // we just care about a ref (refs/heads/master), everything
+                // else is disposable :)
+                if (!((i - start == 17) &&
+                      (0 == strncmp("refs/heads/master", buffer + start, 17))))
+                {
+                    free(new);
+                    new = NULL;
+                    break;
+                }
+                master = new;
+                break;
+        }
+
+        if (++i >= BUFFER_SIZE) {
+            fprintf(stderr, "error: pre-receive hook payload is too big.\n");
+            rv = 1;
+            goto cleanup2;
+        }
+    }
+
+    if (master == NULL) {
+        fprintf(stderr, "warning: no reference to master branch found. "
+            "nothing to deploy.\n");
+        goto cleanup2;
+    }
+
+    char *repo_dir = NULL;
+    char *output_dir = NULL;
+
+    if (NULL == getcwd(buffer, BUFFER_SIZE)) {
+        fprintf(stderr, "error: failed to get repository remote path: %s\n",
+            strerror(errno));
+        rv = 1;
+        goto cleanup;
+    }
+
+    repo_dir = bc_strdup(buffer);
+
+    char dir[] = "/tmp/blogc_XXXXXX";
+    if (NULL == mkdtemp(dir)) {
+        rv = 1;
+        goto cleanup;
+    }
+
+    char *git_archive_cmd = bc_strdup_printf(
+        "git archive \"%s\" | tar -x -C \"%s\" -f -", master, dir);
+    if (0 != system(git_archive_cmd)) {
+        fprintf(stderr, "error: failed to extract git content to temporary "
+            "directory: %s\n", dir);
+        rv = 1;
+        free(git_archive_cmd);
+        goto cleanup;
+    }
+    free(git_archive_cmd);
+
+    if (0 != chdir(dir)) {
+        fprintf(stderr, "error: failed to chdir (%s): %s\n", dir,
+            strerror(errno));
+        rv = 1;
+        goto cleanup;
+    }
+
+    if ((0 != access("Makefile", F_OK)) && (0 != access("GNUMakefile", F_OK))) {
+        fprintf(stderr, "warning: no makefile found. skipping ...\n");
+        goto cleanup;
+    }
+
+    char *home = getenv("HOME");
+    if (home == NULL) {
+        fprintf(stderr, "error: failed to find user home path\n");
+        rv = 1;
+        goto cleanup;
+    }
+
+    unsigned long epoch = time(NULL);
+    output_dir = bc_strdup_printf("%s/builds/%s-%lu", home, master, epoch);
+    char *gmake_cmd = bc_strdup_printf(
+        "gmake -j%d OUTPUT_DIR=\"%s\" BLOGC_GIT_RECEIVER=1",
+        cpu_count(), output_dir);
+    fprintf(stdout, "running command: %s\n\n", gmake_cmd);
+    fflush(stdout);
+    if (0 != system(gmake_cmd)) {
+        fprintf(stderr, "error: failed to build website ...\n");
+        rmdir_recursive(output_dir);
+        free(gmake_cmd);
+        rv = 1;
+        goto cleanup;
+    }
+    free(gmake_cmd);
+
+    if (0 != chdir(repo_dir)) {
+        fprintf(stderr, "error: failed to chdir (%s): %s\n", repo_dir,
+            strerror(errno));
+        rmdir_recursive(output_dir);
+        rv = 1;
+        goto cleanup;
+    }
+
+    char *htdocs_sym = NULL;
+    ssize_t htdocs_sym_len = readlink("htdocs", buffer, BUFFER_SIZE);
+    if (0 < htdocs_sym_len) {
+        if (0 != unlink("htdocs")) {
+            fprintf(stderr, "error: failed to remove symlink (%s/htdocs): %s\n",
+                repo_dir, strerror(errno));
+            rmdir_recursive(output_dir);
+            rv = 1;
+            goto cleanup;
+        }
+        buffer[htdocs_sym_len] = '\0';
+        htdocs_sym = buffer;
+    }
+
+    if (0 != symlink(output_dir, "htdocs")) {
+        fprintf(stderr, "error: failed to create symlink (%s/htdocs): %s\n",
+            repo_dir, strerror(errno));
+        rmdir_recursive(output_dir);
+        rv = 1;
+        goto cleanup;
+    }
+
+    if (htdocs_sym != NULL)
+        rmdir_recursive(htdocs_sym);
+
+cleanup:
+    free(output_dir);
+    rmdir_recursive(dir);
+    free(repo_dir);
+cleanup2:
+    free(new);
+    return rv;
+}
diff --git a/src/blogc-git-receiver/pre-receive.h b/src/blogc-git-receiver/pre-receive.h
new file mode 100644
index 0000000..5606e21
--- /dev/null
+++ b/src/blogc-git-receiver/pre-receive.h
@@ -0,0 +1,14 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file LICENSE.
+ */
+
+#ifndef _PRE_RECEIVE_H
+#define _PRE_RECEIVE_H
+
+int bgr_pre_receive_hook(int argc, char *argv[]);
+
+#endif /* _PRE_RECEIVE_H */
diff --git a/src/blogc-git-receiver/shell.c b/src/blogc-git-receiver/shell.c
new file mode 100644
index 0000000..290526b
--- /dev/null
+++ b/src/blogc-git-receiver/shell.c
@@ -0,0 +1,206 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file LICENSE.
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include "../common/utils.h"
+#include "shell.h"
+
+#ifndef BUFFER_SIZE
+#define BUFFER_SIZE 4096
+#endif
+
+
+int
+bgr_shell(int argc, char *argv[])
+{
+    int rv = 0;
+
+    char *repo = NULL;
+    char *command_orig = NULL;
+    char *command_name = NULL;
+    char command_new[BUFFER_SIZE];
+
+    bool exec_git = false;
+
+    // validate git command
+    size_t len = strlen(argv[2]);
+    if (!((len > 17 && (0 == strncmp(argv[2], "git-receive-pack ", 17))) ||
+          (len > 16 && (0 == strncmp(argv[2], "git-upload-pack ", 16))) ||
+          (len > 19 && (0 == strncmp(argv[2], "git-upload-archive ", 19)))))
+    {
+        fprintf(stderr, "error: unsupported git command: %s\n", argv[2]);
+        rv = 1;
+        goto cleanup;
+    }
+
+    // get shell path
+    char *self = getenv("SHELL");
+    if (self == NULL) {
+        fprintf(stderr, "error: failed to find blogc-git-receiver path\n");
+        rv = 1;
+        goto cleanup;
+    }
+
+    // get home path
+    char *home = getenv("HOME");
+    if (home == NULL) {
+        fprintf(stderr, "error: failed to find user home path\n");
+        rv = 1;
+        goto cleanup;
+    }
+
+    // get git repository
+    command_orig = bc_strdup(argv[2]);
+    char *p, *r;
+    for (p = command_orig; *p != ' ' && *p != '\0'; p++);
+    if (*p == ' ')
+        p++;
+    if (*p == '\'' || *p == '"')
+        p++;
+    if (*p == '/')
+        p++;
+    for (r = p; *p != '\'' && *p != '"' && *p != '\0'; p++);
+    if (*p == '\'' || *p == '"')
+        *p = '\0';
+    if (*--p == '/')
+        *p = '\0';
+
+    repo = bc_strdup_printf("repos/%s", r);
+
+    // check if repository is sane
+    if (0 == strlen(repo)) {
+        fprintf(stderr, "error: invalid repository\n");
+        rv = 1;
+        goto cleanup;
+    }
+
+    if (0 == strncmp(argv[2], "git-upload-", 11))  // no need to check len here
+        goto git_exec;
+
+    if (0 != chdir(home)) {
+        fprintf(stderr, "error: failed to chdir (%s): %s\n", home,
+            strerror(errno));
+        rv = 1;
+        goto cleanup;
+    }
+
+    if (0 != access(repo, F_OK)) {
+        char *git_init_cmd = bc_strdup_printf(
+            "git init --bare \"%s\" > /dev/null", repo);
+        if (0 != system(git_init_cmd)) {
+            fprintf(stderr, "error: failed to create git repository: %s\n",
+                repo);
+            rv = 1;
+            free(git_init_cmd);
+            goto cleanup;
+        }
+        free(git_init_cmd);
+    }
+
+    if (0 != chdir(repo)) {
+        fprintf(stderr, "error: failed to chdir (%s/%s): %s\n", home, repo,
+            strerror(errno));
+        rv = 1;
+        goto cleanup;
+    }
+
+    if (0 != access("hooks", F_OK)) {
+        // openwrt git package won't install git templates, then the git
+        // repositories created with it won't have the hooks/ directory.
+        if (0 != mkdir("hooks", 0777)) {  // mkdir honors umask for us.
+            fprintf(stderr, "error: failed to create directory (%s/%s/hooks): "
+                "%s\n", home, repo, strerror(errno));
+            rv = 1;
+            goto cleanup;
+        }
+    }
+
+    if (0 != chdir("hooks")) {
+        fprintf(stderr, "error: failed to chdir (%s/%s/hooks): %s\n", home,
+            repo, strerror(errno));
+        rv = 1;
+        goto cleanup;
+    }
+
+    if (0 == access("pre-receive", F_OK)) {
+        if (0 != unlink("pre-receive")) {
+            fprintf(stderr, "error: failed to remove old symlink "
+                "(%s/%s/hooks/pre-receive): %s\n", home, repo, strerror(errno));
+            rv = 1;
+            goto cleanup;
+        }
+    }
+
+    if (0 != symlink(self, "pre-receive")) {
+        fprintf(stderr, "error: failed to create symlink "
+            "(%s/%s/hooks/pre-receive): %s\n", home, repo, strerror(errno));
+        rv = 1;
+        goto cleanup;
+    }
+
+    if (0 == access("post-receive", F_OK)) {
+        if (0 != unlink("post-receive")) {
+            fprintf(stderr, "error: failed to remove old symlink "
+                "(%s/%s/hooks/post-receive): %s\n", home, repo, strerror(errno));
+            rv = 1;
+            goto cleanup;
+        }
+    }
+
+    if (0 != symlink(self, "post-receive")) {
+        fprintf(stderr, "error: failed to create symlink "
+            "(%s/%s/hooks/post-receive): %s\n", home, repo, strerror(errno));
+        rv = 1;
+        goto cleanup;
+    }
+
+    if (0 != chdir(home)) {
+        fprintf(stderr, "error: failed to chdir (%s): %s\n", home,
+            strerror(errno));
+        rv = 1;
+        goto cleanup;
+    }
+
+git_exec:
+    command_name = bc_strdup(argv[2]);
+    for (p = command_name; *p != ' ' && *p != '\0'; p++);
+    if (*p == ' ')
+        *p = '\0';
+
+    if (BUFFER_SIZE < (strlen(command_name) + strlen(repo) + 4)) {
+        fprintf(stderr, "error: git-shell command is too big\n");
+        rv = 1;
+        goto cleanup;
+    }
+
+    if (snprintf(command_new, BUFFER_SIZE, "%s '%s'", command_name, repo))
+        exec_git = true;
+
+cleanup:
+    free(repo);
+    free(command_orig);
+    free(command_name);
+
+    if (exec_git) {
+        execlp("git-shell", "git-shell", "-c", command_new, NULL);
+
+        // execlp only returns on error, then something bad happened
+        fprintf(stderr, "error: failed to execute git-shell\n");
+        rv = 1;
+    }
+
+    return rv;
+}
diff --git a/src/blogc-git-receiver/shell.h b/src/blogc-git-receiver/shell.h
new file mode 100644
index 0000000..c05e4e1
--- /dev/null
+++ b/src/blogc-git-receiver/shell.h
@@ -0,0 +1,14 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file LICENSE.
+ */
+
+#ifndef _SHELL_H
+#define _SHELL_H
+
+int bgr_shell(int argc, char *argv[]);
+
+#endif /* _SHELL_H */
-- 
cgit v1.2.3-18-g5258