summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSadeep Madurange <sadeep@asciimx.com>2026-03-17 20:17:06 +0800
committerSadeep Madurange <sadeep@asciimx.com>2026-03-22 16:38:00 +0800
commita21b1fbf041bbd09ba7ae41bea5e26335b9c3e91 (patch)
tree8ef8a9baa6716bb9464441cf540033145bee6b95
parent8006b2bdddf7f863cd65053be77b98935041047d (diff)
downloadcvn-a21b1fbf041bbd09ba7ae41bea5e26335b9c3e91.tar.gz
wip: add.
-rw-r--r--main.c348
1 files changed, 288 insertions, 60 deletions
diff --git a/main.c b/main.c
index 3f26f1e..716e41c 100644
--- a/main.c
+++ b/main.c
@@ -4,19 +4,27 @@
#include <fnmatch.h>
#include <ftw.h>
#include <limits.h>
+#include <sha1.h>
+#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
-#define VCX_DIR ".vcx"
-#define HEAD VCX_DIR "/head"
-#define TMP_DIR VCX_DIR "/tmp"
-#define OBJ_DIR VCX_DIR "/obj"
+#define VCX_DIR ".vcx"
+#define HEAD VCX_DIR "/head"
+#define TMP_DIR VCX_DIR "/tmp"
+#define OBJ_DIR VCX_DIR "/obj"
+
#define EXCLUDE_PATHS ".vcxignore"
-#define MAX_DEPTH 256
+#define BUFLEN 8192
+#define MAX_DEPTH 256
+
+#define KIND_DEL 'D'
+#define KIND_MOD 'M'
+#define KIND_NEW 'N'
#define MALLOC(s) _xmalloc((s), __FILE__, __LINE__)
#define REALLOC(p, s) _xrealloc((p), (s), __FILE__, __LINE__)
@@ -25,14 +33,20 @@ static inline void init(int argc, char *argv[]);
static inline void status(int argc, char *argv[]);
static inline void add(int argc, char *argv[]);
-static inline void print_status(const char *file, char kind);
+static inline void init_stg_area(void);
static inline int cmp_links(const char *link1, const char *link2);
-static inline int cmp_content(const char *file1, const char *file2);
-static inline char *concat_path(const char *dir, const char *file);
+static inline int cmp_files(const char *file1, const char *file2);
+
+static inline void stage_file(const char *file, char kind, int islnk);
+static inline void print_status(const char *file, char kind, int islnk);
static inline int scan_head(const char * path, const struct stat *st, int flag, struct FTW *ftwbuf);
static inline int scan_tree(const char * path, const struct stat *st, int flag, struct FTW *ftwbuf);
+static inline void mkdirs(const char *path);
+static inline int copy_file(const char *src, const char *dst);
+static inline void concat(char *dst, size_t n, const char *arg1, ...);
+
static inline void *_xmalloc(size_t s, const char *file, int line);
static inline void *_xrealloc(void *ptr, size_t s, const char *file, int line);
@@ -42,7 +56,9 @@ struct command {
};
struct command cmd[] = {
- {"init", init}, {"status", status}, {"add", add},
+ {"init", init},
+ {"add", add},
+ {"status", status},
{NULL, NULL}
};
@@ -77,15 +93,10 @@ static inline void init(int argc, char *argv[])
err(1, "Failed to create %s", OBJ_DIR);
}
- if (mkdir(TMP_DIR, 0755) == -1) {
- if (errno != EEXIST)
- err(1, "Failed to create %s", TMP_DIR);
- }
-
printf("Repository ready\n");
}
-static void (*scan_res_cb)(const char *file, char kind) = NULL;
+static void (*scan_res_cb)(const char *file, char kind, int islnk) = NULL;
static inline void status(int argc, char *argv[])
{
@@ -100,26 +111,54 @@ static inline void status(int argc, char *argv[])
static inline void add(int argc, char *argv[])
{
- char **paths;
-
+ size_t i;
+ struct stat st;
+ char *wt_path;
+
if (argc < 2)
errx(1, "Usage: %s [<files>]", argv[0]);
- paths = &argv[1];
+ init_stg_area();
+ scan_res_cb = stage_file;
+
+ for (i = 1; i < argc; i++) {
+ wt_path = argv[i];
+ if (lstat(wt_path, &st) == -1)
+ err(1, "%s", wt_path);
+
+ printf("Loop index %zu, path: %s\n", i, wt_path);
+
+ size_t hd_sz = strlen(HEAD) + strlen(wt_path) + 2;
+ char hd_path[hd_sz];
+ concat(hd_path, hd_sz, HEAD, "/", wt_path, NULL);
+
+ size_t tmp_sz = strlen(TMP_DIR) + strlen(wt_path) + 2;
+ char tmp_path[tmp_sz];
+ concat(tmp_path, tmp_sz, TMP_DIR, "/", wt_path, NULL);
+
+ if (nftw(hd_path, scan_head, MAX_DEPTH, FTW_PHYS) == -1)
+ err(1, "Failed to scan head");
+ if (nftw(wt_path, scan_tree, MAX_DEPTH, FTW_PHYS) == -1)
+ err(1, "Failed to scan work tree");
+ }
}
int scan_head(const char * path, const struct stat *sb, int flag,
struct FTW *ftwbuf)
{
- char *wt_path;
struct stat wt_stat;
if (flag == FTW_F) {
const char *filename = path + ftwbuf->base;
- wt_path = concat_path(".", filename);
+
+ size_t wt_sz = strlen(filename) + 3;
+ char wt_path[wt_sz];
+ concat(wt_path, wt_sz, ".", "/", filename, NULL);
+
if (lstat(wt_path, &wt_stat) == -1) {
if (errno == ENOENT) {
- scan_res_cb(wt_path, 'D');
+ // file/link doesn't matter for deletions
+ scan_res_cb(wt_path, KIND_DEL, 0);
return 0;
}
else
@@ -131,11 +170,11 @@ int scan_head(const char * path, const struct stat *sb, int flag,
return 0; // no change
if (S_ISREG(wt_stat.st_mode)) {
- if (cmp_content(path, wt_path) == 1)
- scan_res_cb(wt_path, 'M');
+ if (cmp_files(path, wt_path) == 1)
+ scan_res_cb(wt_path, KIND_MOD, 0);
} else if (S_ISLNK(wt_stat.st_mode)) {
if (cmp_links(path, wt_path) == 1)
- scan_res_cb(wt_path, 'M');
+ scan_res_cb(wt_path, KIND_MOD, 1);
}
}
@@ -145,22 +184,179 @@ int scan_head(const char * path, const struct stat *sb, int flag,
static inline int scan_tree(const char * path, const struct stat *st,
int flag, struct FTW *ftwbuf)
{
- char *hd_path;
-
if (fnmatch("./.vcx/*", path, FNM_PATHNAME) == 0)
return 0;
if (flag == FTW_F) {
const char *filename = path + ftwbuf->base;
- hd_path = concat_path(HEAD, filename);
+
+ size_t hd_sz = strlen(HEAD) + strlen(filename) + 2;
+ char hd_path[hd_sz];
+ concat(hd_path, hd_sz, HEAD, "/", filename, NULL);
+
if (access(hd_path, F_OK) != 0)
- scan_res_cb(path, 'N');
+ scan_res_cb(path, KIND_NEW, S_ISLNK(st->st_mode) != 0);
}
return 0;
}
-static inline int cmp_content(const char *file1, const char *file2)
+static inline int cmp_files(const char *file1, const char *file2)
+{
+ int rc;
+ int fd1, fd2;
+ ssize_t r1, r2;
+ char buf1[BUFLEN], buf2[BUFLEN];
+
+ if ((fd1 = open(file1, O_RDONLY)) < 0)
+ err(1, "Couldn't open %s to compare", file1);
+
+ if ((fd2 = open(file2, O_RDONLY)) < 0)
+ err(1, "Couldn't open %s to compare", file2);
+
+ rc = 0;
+ do {
+ r1 = read(fd1, buf1, BUFLEN);
+ r2 = read(fd2, buf2, BUFLEN);
+
+ if (r1 != r2 || memcmp(buf1, buf2, r1) != 0) {
+ rc = 1;
+ break;
+ }
+ } while (r1 > 0);
+
+ close(fd1);
+ close(fd2);
+ return rc;
+}
+
+static inline int cmp_links(const char *link1, const char *link2)
+{
+ ssize_t len1, len2;
+ char buf1[PATH_MAX], buf2[PATH_MAX];
+
+ len1 = readlink(link1, buf1, sizeof(buf1) - 1);
+ len2 = readlink(link2, buf2, sizeof(buf2) - 1);
+
+ if (len1 < 0 || len2 < 0)
+ err(1, "readlink error on %s or %s", link1, link2);
+
+ if (len1 != len2)
+ return 1;
+
+ buf1[len1] = '\0';
+ buf2[len2] = '\0';
+
+ return strcmp(buf1, buf2);
+}
+
+static inline void init_stg_area(void)
+{
+ pid_t rm_pid, cp_pid;
+
+ rm_pid = fork();
+ if (rm_pid == 0) {
+ if (access(TMP_DIR, F_OK) == 0) {
+ execlp("rm", "rm", "-rf", TMP_DIR, NULL);
+ err(1, "Failed to delete %s", TMP_DIR);
+ }
+ _exit(0);
+ } else {
+ waitpid(rm_pid, NULL, 0);
+ cp_pid = fork();
+ if (cp_pid == 0) {
+ execlp("cp", "cp", "-RP", HEAD, TMP_DIR, NULL);
+ err(1, "Failed to copy %s to %s", HEAD, TMP_DIR);
+ }
+ waitpid(cp_pid, NULL, 0);
+ }
+}
+
+static inline void stage_file(const char *file, char kind, int islnk)
+{
+ int len, obj_len;
+ char obj_name[SHA1_DIGEST_STRING_LENGTH];
+
+ switch (kind) {
+ case KIND_MOD:
+ break;
+ case KIND_NEW:
+ if (!islnk) {
+ len = strlen(file);
+ obj_len = SHA1_DIGEST_STRING_LENGTH - 1;
+
+ if (SHA1Data((const uint8_t *)file, len, obj_name) == NULL)
+ err(1, "Failed to compute hash for %s", file);
+
+ size_t obj_sz = strlen(OBJ_DIR) + obj_len + 6;
+ char obj_path[obj_sz];
+ concat(obj_path, obj_sz, OBJ_DIR, "/", obj_name, ".tmp", NULL);
+ copy_file(file, obj_path);
+
+ // create link relative to tmp/
+ size_t lnkt_sz = strlen("../obj/") + obj_len + 5;
+ char lnk_target[lnkt_sz];
+ concat(lnk_target, lnkt_sz, "../obj/", obj_name, ".tmp", NULL);
+
+ size_t lnkp_sz = strlen(TMP_DIR) + len + 2;
+ char lnk_path[lnkp_sz];
+ concat(lnk_path, lnkp_sz, TMP_DIR, "/", file, NULL);
+
+ printf("Link: %s -> %s\n", lnk_path, lnk_target);
+ mkdirs(lnk_path);
+ if (symlink(lnk_target, lnk_path) != 0)
+ err(1, "symlink error on %s", file);
+ } else {
+
+ }
+ break;
+ case KIND_DEL:
+ break;
+ default:
+ break;
+ }
+}
+
+static inline void print_status(const char *file, char kind, int islnk)
+{
+ printf("[%c] %s\n", kind, file);
+}
+
+static inline void concat(char *dst, size_t n, const char *arg1, ...)
+{
+ va_list ap;
+ const char *part;
+ int curlen, partlen;
+
+ if (!dst || n == 0)
+ return;
+
+ dst[0] = '\0';
+ curlen = 0;
+
+ if (!arg1)
+ return;
+
+ va_start(ap, arg1);
+ part = arg1;
+
+ while (part) {
+ partlen = strlen(part);
+ if (curlen + partlen + 1 > n) {
+ dst[curlen] = '\0';
+ va_end(ap);
+ errx(1, "Path too long: %s%s", dst, part);
+ }
+ memcpy(dst + curlen, part, partlen);
+ curlen += partlen;
+ part = va_arg(ap, const char *);
+ }
+
+ dst[curlen] = '\0';
+ va_end(ap);
+}
+
+static inline int diff(const char *file1, const char *file2)
{
pid_t pid;
int null_fd, status, rc;
@@ -190,49 +386,81 @@ static inline int cmp_content(const char *file1, const char *file2)
err(1, "fork");
}
-static inline int cmp_links(const char *link1, const char *link2)
+static inline int copy_file(const char *src, const char *dst)
{
- ssize_t len1, len2;
- char buf1[PATH_MAX], buf2[PATH_MAX];
-
- len1 = readlink(link1, buf1, sizeof(buf1) - 1);
- len2 = readlink(link2, buf2, sizeof(buf2) - 1);
-
- if (len1 < 0 || len2 < 0)
- err(1, "readlink error on %s or %s", link1, link2);
+ int fdin, fdout;
+ struct stat st;
+ char buf[BUFLEN], *outptr;
+ ssize_t nread, nwrite, res;
- if (len1 != len2)
- return 1;
+ if ((fdin = open(src, O_RDONLY)) < 0)
+ err(1, "open failed for %s", src);
- buf1[len1] = '\0';
- buf2[len2] = '\0';
+ if (fstat(fdin, &st) < 0) {
+ close(fdin);
+ err(1, "fstat failed for %s", src);
+ }
- return strcmp(buf1, buf2);
-}
+ mkdirs(dst);
-static inline void print_status(const char *file, char kind)
-{
- printf("[%c] %s\n", kind, file);
+ fdout = open(dst, O_WRONLY | O_CREAT | O_TRUNC, st.st_mode & 0777);
+ if (fdout < 0) {
+ close(fdin);
+ err(1, "open failed for %s", dst);
+ }
+
+ while ((nread = read(fdin, buf, sizeof(buf))) > 0) {
+ nwrite = 0;
+ outptr = buf;
+
+ // Handle partial writes (possible with large files/slow disks)
+ while (nwrite < nread) {
+ res = write(fdout, outptr + nwrite, nread - nwrite);
+ if (res < 0) {
+ if (errno == EINTR) // interrupted by a signal - retry
+ continue;
+ close(fdin);
+ close(fdout);
+ err(1, "Copy error on %s", src); // bad error: e.g, disk full
+ }
+ nwrite += res;
+ }
+ }
+
+ close(fdin);
+ close(fdout);
+ return (nread < 0) ? -1 : 0;
}
-static inline char *concat_path(const char *dir, const char *file)
+static inline void mkdirs(const char *path)
{
- static char *path_buf = NULL;
- static size_t path_buflen = 1024;
+ char *p;
+ int pathlen;
- size_t path_len;
+ static char *buf = NULL;
+ static size_t buflen = 1024;
- if (!path_buf)
- path_buf = MALLOC(sizeof(path_buf[0]) * path_buflen);
+ if (!buf)
+ buf = MALLOC(buflen);
- path_len = strlen(dir) + strlen(file) + 2;
- if (path_len > path_buflen) {
- path_buflen <<= 1;
- path_buf = REALLOC(path_buf, sizeof(path_buf[0]) * path_buflen);
+ pathlen = strlen(path);
+ if (buflen < pathlen + 1) {
+ buflen *= 2;
+ buf = REALLOC(buf, buflen);
+ }
+ buf[0] = '\0';
+ strcpy(buf, path);
+
+ for (p = buf; *p; p++) {
+ if (*p == '/') {
+ *p = '\0';
+ if (mkdir(buf, 0755) != 0) {
+ if (errno != EEXIST)
+ err(1, "mkdir failed for %s", buf);
+ }
+ *p = '/';
+ }
}
-
- snprintf(path_buf, path_buflen, "%s/%s", dir, file);
- return path_buf;
}
static inline void *_xmalloc(size_t s, const char *file, int line)