baseline

yet another open-source distributed versioning control system
Log | Files | Refs

cmd-diff.c (10140B)


      1 /*
      2  * Copyright (c) 2014 Mohamed Aslan <maslan@sce.carleton.ca>
      3  *
      4  * Permission to use, copy, modify, and distribute this software for any
      5  * purpose with or without fee is hereby granted, provided that the above
      6  * copyright notice and this permission notice appear in all copies.
      7  *
      8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
      9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 #include <stdio.h> /* printf(3) */
     18 #include <stdlib.h> /* EXIT_SUCCESS */
     19 #include <string.h> /* strdup(3) */
     20 #include <fcntl.h> /* open(2) */
     21 #include <err.h>
     22 #include <unistd.h> /* read(2), write(2) */
     23 #include <sys/stat.h> /* S_ISDIR */
     24 #include <sys/wait.h> /* waitpid(2) */
     25 
     26 #include "cmd.h"
     27 #include "session.h"
     28 
     29 #include "objects.h"
     30 
     31 #include "common.h"
     32 
     33 
     34 static char *
     35 make_tmpdir()
     36 {
     37 	char *name = NULL;
     38 
     39 	name = strdup("/tmp/baseline.XXXXXX");
     40 	if (mkdtemp(name) == NULL) {
     41 		errx(EXIT_FAILURE, "error, failed to create a temporary directory (%s).", name);
     42 	}
     43 	return name;
     44 }
     45 
     46 static char *
     47 make_fifo(const char *path, const char *name)
     48 {
     49 	char *fifo_name = NULL;
     50 
     51 	asprintf(&fifo_name, "%s/%s", path, name); 
     52 	if (mkfifo(fifo_name, S_IRUSR | S_IWUSR) == -1) {
     53 		errx(EXIT_FAILURE, "error, failed to create FIFO \'%s\'.", fifo_name);
     54 	}
     55 	return fifo_name;
     56 }
     57 
     58 static int
     59 open_fifo(const char *fifo)
     60 {
     61 	int fd;
     62 
     63 	if ((fd = open(fifo, O_WRONLY)) == -1) {
     64 		errx(EXIT_FAILURE, "error, failed to open FIFO \'%s\'.", fifo);
     65 	}
     66 	return fd;
     67 }
     68 
     69 static void
     70 copy_to_fifo(struct file *f, int fifo)
     71 {
     72 	char buf[2048];
     73 	ssize_t n;
     74 
     75 	while ((n = read(f->fd, buf, sizeof(buf))) > 0) {
     76 		write(fifo, buf, n);
     77 	}
     78 	if (n == -1) {
     79 		errx(EXIT_FAILURE, "error, failed to read file.");
     80 	}
     81 }
     82 
     83 static void
     84 exec_ext_diff(const char *old, const char *old_label, const char *new, const char *new_label)
     85 {
     86 	char buf[2048];
     87 	char *cmd = NULL;
     88 	size_t n;
     89 	FILE *fp;
     90 
     91 	if (old == NULL && new == NULL)
     92 		return;
     93 
     94 	if (old == NULL)
     95 		asprintf(&cmd, "diff -u %s %s -L %s -L %s", "/dev/null", new, "/dev/null", new_label == NULL ? new : new_label);
     96 	else if(new == NULL)
     97 		asprintf(&cmd, "diff -u %s %s -L %s -L %s", old, "/dev/null", old_label == NULL ? old : old_label, "/dev/null");
     98 	else
     99 		asprintf(&cmd, "diff -u %s %s -L %s -L %s", old, new, old_label == NULL ? old : old_label, new_label == NULL ? new : new_label);
    100 
    101 	if ((fp = popen(cmd, "r")) == NULL)
    102 		errx(EXIT_FAILURE, "error, failed to run external diff.");
    103 	/*
    104 	while ((n = fread(buf, sizeof(char), sizeof(buf), pf)) > 0)
    105 		fwrite(buf, sizeof(char), n, stdout);
    106 	*/
    107 	while ((n = read(fileno(fp), buf, sizeof(buf))) > 0)
    108 		write(1, buf, n);
    109 	if (n == -1)
    110 		errx(EXIT_FAILURE, "error, reading output from external diff.");
    111 	pclose(fp);
    112 	free(cmd);
    113 }
    114 
    115 static void
    116 ext_diff_proc(struct session *s, struct dirent *ent, char *fifo)
    117 {
    118 	int fifo_fd;
    119 	struct file *f;
    120 
    121 	fifo_fd = open_fifo(fifo);
    122 
    123 	f = baseline_file_new();
    124 	s->db_ops->select_file(s->db_ctx, ent->id, f);
    125 
    126 	copy_to_fifo(f, fifo_fd);
    127 	close(fifo_fd);
    128 	unlink(fifo);
    129 
    130 	baseline_file_free(f);
    131 }
    132 
    133 static void
    134 ext_diff(struct session *s, const char *tmpdir, struct dirent *ent1, struct dirent *ent2, const char *path)
    135 {
    136 	char *dir1 = NULL, *dir2 = NULL;
    137 	char *label1, *label2;
    138 	char *fifo1 = NULL, *fifo2 = NULL;
    139 	pid_t pid1, pid2;
    140 
    141 	label1 = NULL;
    142 	label2 = NULL;
    143 	if (ent1 == NULL && ent2 == NULL)
    144 		return;
    145 
    146 	if (ent1 != NULL) {
    147 		asprintf(&dir1, "%s/XXXXXXX", tmpdir);
    148 		if (mkdtemp(dir1) == NULL)
    149 			errx(EXIT_FAILURE, "error, failed to create a temporary directory (%s).", dir1);
    150 		fifo1 = make_fifo(dir1, ent1->id);
    151 		//free(dir1);
    152 		if (strlen(path) > 0)
    153 			asprintf(&label1, "%s/%s", path, ent1->name);
    154 		else
    155 			asprintf(&label1, "%s", ent1->name);
    156 	}
    157 	if(ent2 != NULL) {
    158 		asprintf(&dir2, "%s/XXXXXXX", tmpdir);
    159 		if (mkdtemp(dir2) == NULL)
    160 			errx(EXIT_FAILURE, "error, failed to create a temporary directory (%s).", dir2);
    161 		fifo2 = make_fifo(dir2, ent2->id);
    162 		//free(dir);
    163 		if (strlen(path) > 0)
    164 			asprintf(&label2, "%s/%s", path, ent2->name);
    165 		else
    166 			asprintf(&label2, "%s", ent2->name);
    167 	}
    168 
    169 	pid1 = fork();
    170 	if (pid1 < 0) {
    171 		errx(EXIT_FAILURE, "error, failed to create a new process.");
    172 	}
    173 	else if (pid1 == 0) {
    174 		exec_ext_diff(fifo1, label1, fifo2, label2);
    175 		exit(0);
    176 	}
    177 	else {
    178 		if (ent1 != NULL && ent2 != NULL) {
    179 			pid2 = fork();
    180 		
    181 			if (pid2 < 0) {
    182 				errx(EXIT_FAILURE, "error, failed to create a new process.");
    183 			}
    184 			else if (pid2 == 0) {
    185 				ext_diff_proc(s, ent1, fifo1); 
    186 				exit(0);
    187 			}
    188 			else {
    189 				ext_diff_proc(s, ent2, fifo2); 
    190 			}
    191 		}
    192 		else {
    193 			if (ent1 != NULL)
    194 				ext_diff_proc(s, ent1, fifo1); 
    195 			else
    196 				ext_diff_proc(s, ent2, fifo2); 
    197 		}
    198 		waitpid(pid1, NULL, 0);
    199 	}
    200 	remove(dir1);
    201 	remove(dir2);
    202 	free(dir1);
    203 	free(dir2);
    204 	free(fifo1);
    205 	free(fifo2);
    206 }
    207 
    208 
    209 static void
    210 diff_r(struct session *s, struct dir *d1, struct dir *d2, const char *p, const char *tmpdir)
    211 {
    212 	char *pnext = NULL;
    213 	struct dir *child1, *child2;
    214 	struct dirent *ent1 = NULL, *ent2 = NULL;
    215 
    216 	if (d1 == NULL && d2 == NULL)
    217 		return;
    218 	if (p == NULL || tmpdir == NULL)
    219 		return;
    220 	if (d1 != NULL)
    221 		ent1 = d1->children;
    222 	if (d2 != NULL)
    223 		ent2 = d2->children;
    224 	if (*p == '/')
    225 		p++;
    226 
    227 	while (1) {
    228 		if (ent1 == NULL && ent2 == NULL)
    229 			break;
    230 		else if (ent1 == NULL) {
    231 			/* +++ ent2 */
    232 			if (S_ISREG(ent2->mode))
    233 				ext_diff(s, tmpdir, NULL, ent2, p);
    234 			else if (S_ISDIR(ent2->mode)) {
    235 				asprintf(&pnext, "%s/%s", p, ent2->name);
    236 				child2 = baseline_dir_new();
    237 				s->db_ops->select_dir(s->db_ctx, ent2->id, child2);
    238 				diff_r(s, NULL, child2, pnext, tmpdir);
    239 				baseline_dir_free(child2);
    240 				free(pnext);
    241 			}
    242 			else
    243 				errx(EXIT_FAILURE, "error, file mode not supported.");
    244 			ent2 = ent2->next;
    245 		}
    246 		else if (ent2 == NULL) {
    247 			/* --- ent1 */
    248 			if (S_ISREG(ent1->mode))
    249 				ext_diff(s, tmpdir, ent1, NULL, p);
    250 			else if (S_ISDIR(ent1->mode)) {
    251 				asprintf(&pnext, "%s/%s", p, ent1->name);
    252 				child1 = baseline_dir_new();
    253 				s->db_ops->select_dir(s->db_ctx, ent1->id, child1);
    254 				diff_r(s, child1, NULL, pnext, tmpdir);
    255 				baseline_dir_free(child1);
    256 				free(pnext);
    257 			}
    258 			else
    259 				errx(EXIT_FAILURE, "error, file mode not supported.");
    260 			ent1 = ent1->next;
    261 		}
    262 		else if (!strcmp(ent1->name, ent2->name)) {
    263 			if (strcmp(ent1->id, ent2->id)) {
    264 				/* *** ent1 & ent2 */
    265 				if (S_ISREG(ent1->mode) && S_ISREG(ent2->mode))
    266 					ext_diff(s, tmpdir, ent1, ent2, p);
    267 				else if (S_ISREG(ent1->mode) && S_ISDIR(ent2->mode)) {
    268 					/* delete old file */
    269 					ext_diff(s, tmpdir, ent1, NULL, p);
    270 					/* add new dir */
    271 					asprintf(&pnext, "%s/%s", p, ent2->name);
    272 					child2 = baseline_dir_new();
    273 					s->db_ops->select_dir(s->db_ctx, ent2->id, child2);
    274 					diff_r(s, NULL, child2, pnext, tmpdir);
    275 					baseline_dir_free(child2);
    276 					free(pnext);
    277 				}
    278 				else if (S_ISDIR(ent1->mode) && S_ISREG(ent2->mode)) {
    279 					/* delete old dir */
    280 					asprintf(&pnext, "%s/%s", p, ent1->name);
    281 					child1 = baseline_dir_new();
    282 					s->db_ops->select_dir(s->db_ctx, ent1->id, child1);
    283 					diff_r(s, child1, NULL, pnext, tmpdir);
    284 					baseline_dir_free(child1);
    285 					free(pnext);
    286 					/* add new file */
    287 					ext_diff(s, tmpdir, NULL, ent2, p);
    288 				}
    289 				else if (S_ISDIR(ent1->mode) && S_ISDIR(ent2->mode)) {
    290 					asprintf(&pnext, "%s/%s", p, ent1->name);
    291 					child1 = baseline_dir_new();
    292 					child2 = baseline_dir_new();
    293 					s->db_ops->select_dir(s->db_ctx, ent1->id, child1);
    294 					s->db_ops->select_dir(s->db_ctx, ent2->id, child2);
    295 					diff_r(s, child1, child2, pnext, tmpdir);
    296 					baseline_dir_free(child1);
    297 					baseline_dir_free(child2);
    298 					free(pnext);
    299 				}
    300 				else
    301 					errx(EXIT_FAILURE, "error, file mode not supported.");
    302 			}
    303 			ent1 = ent1->next;
    304 			ent2 = ent2->next;
    305 		}
    306 		else if (strcmp(ent1->name, ent2->name) < 0) {
    307 			/* --- ent1 */
    308 			if (S_ISREG(ent1->mode))
    309 				ext_diff(s, tmpdir, ent1, NULL, p);
    310 			else if (S_ISDIR(ent1->mode)) {
    311 				asprintf(&pnext, "%s/%s", p, ent1->name);
    312 				child1 = baseline_dir_new();
    313 				s->db_ops->select_dir(s->db_ctx, ent1->id, child1);
    314 				diff_r(s, child1, NULL, pnext, tmpdir);
    315 				baseline_dir_free(child1);
    316 				free(pnext);
    317 			}
    318 			else
    319 				errx(EXIT_FAILURE, "error, file mode not supported.");
    320 			ent1 = ent1->next;
    321 		}
    322 		else if (strcmp(ent1->name, ent2->name) > 0) {
    323 			/* +++ ent2 */
    324 			if (S_ISREG(ent2->mode))
    325 				ext_diff(s, tmpdir, NULL, ent2, p);
    326 			else if (S_ISDIR(ent2->mode)) {
    327 				asprintf(&pnext, "%s/%s", p, ent2->name);
    328 				child2 = baseline_dir_new();
    329 				s->db_ops->select_dir(s->db_ctx, ent2->id, child2);
    330 				diff_r(s, NULL, child2, pnext, tmpdir);
    331 				baseline_dir_free(child2);
    332 				free(pnext);
    333 			}
    334 			else
    335 				errx(EXIT_FAILURE, "error, file mode not supported.");
    336 			ent2 = ent2->next;
    337 		}
    338 	}
    339 }
    340 
    341 int
    342 cmd_diff(int argc, char **argv)
    343 {
    344 	char *old = NULL, *new = NULL;
    345 	char *tmpdir = NULL;
    346 	struct session s;
    347 	struct commit *comm_old, *comm_new;
    348 	struct dir *dir_old, *dir_new;
    349 
    350 	baseline_session_begin(&s, 0);
    351 
    352 	if (argc == 2) {
    353 		new = strdup(argv[1]);
    354 	}
    355 	else if (argc == 3) {
    356 		old = strdup(argv[1]);
    357 		new = strdup(argv[2]);
    358 	}
    359 	else {
    360 		errx(EXIT_FAILURE, "wrong number of arguments (%d)\n", argc);
    361 	}
    362 
    363 	tmpdir = make_tmpdir();
    364 
    365 	comm_new = baseline_commit_new();
    366 	s.db_ops->select_commit(s.db_ctx, new, comm_new);
    367 
    368 	dir_new = baseline_dir_new();
    369 	s.db_ops->select_dir(s.db_ctx, comm_new->dir, dir_new);
    370 
    371 	if (old == NULL) {
    372 		if (comm_new->n_parents > 0)
    373 			old = comm_new->parents[0];
    374 		else
    375 			goto ret;
    376 	}
    377 
    378 	comm_old = baseline_commit_new();
    379 	s.db_ops->select_commit(s.db_ctx, old, comm_old);
    380 
    381 	dir_old = baseline_dir_new();
    382 	s.db_ops->select_dir(s.db_ctx, comm_old->dir, dir_old);
    383 
    384 	diff_r(&s, dir_old, dir_new, "", tmpdir);
    385 
    386 	baseline_dir_free(dir_old);
    387 	baseline_dir_free(dir_new);
    388 
    389 ret:
    390 	remove(tmpdir);
    391 	free(tmpdir);
    392 	free(old);
    393 	free(new);
    394 	baseline_session_end(&s);
    395 	return EXIT_SUCCESS;
    396 }
    397