baseline

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

cmd-commit.c (5422B)


      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>
     18 #include <stdlib.h> /* EXIT_FAILURE */
     19 #include <string.h> /* strstr(3) */
     20 #include <limits.h> /* PATH_MAX */
     21 #include <unistd.h> /* getcwd(3) */
     22 #include <err.h>    /* err(3) */
     23 
     24 #include "defaults.h"
     25 #include "session.h"
     26 #include "config.h"
     27 #include "cmd.h"
     28 
     29 
     30 static const char msg_head[] = {
     31 	"\n"
     32 	"# Please enter your commit message.\n"
     33 	"# Lines starting with \'#\' will be ignored.\n"
     34 };
     35 
     36 static int
     37 gen_temp_msgfile_from_msg(const char *path, char **filename, const char *msg)
     38 {
     39 	char *msg_file = NULL;
     40 	int msgfd;
     41 
     42 	if (msg == NULL)
     43 		return EXIT_FAILURE;
     44         asprintf(&msg_file, "%s/tmp.XXXXXX", path);
     45 	if ((msgfd = mkstemp(msg_file)) == -1)
     46 		return EXIT_FAILURE;
     47 	if (write(msgfd, msg, strlen(msg)) == -1)
     48 		return EXIT_FAILURE;
     49 	if (write(msgfd, "\n", strlen("\n")) == -1)
     50 		return EXIT_FAILURE;
     51 	close(msgfd);
     52 	*filename = msg_file;
     53 	return EXIT_SUCCESS;
     54 }
     55 
     56 static int
     57 gen_temp_msgfile(const char *path, char **filename)
     58 {
     59 	char *msg_file = NULL;
     60 	int msgfd;
     61 
     62         asprintf(&msg_file, "%s/tmp.XXXXXX", path);
     63 	if ((msgfd = mkstemp(msg_file)) == -1)
     64 		return EXIT_FAILURE;
     65 	if (write(msgfd, msg_head, sizeof(msg_head) - 1) == -1)
     66 		return EXIT_FAILURE;
     67 	close(msgfd);
     68 	*filename = msg_file;
     69 	return EXIT_SUCCESS;
     70 }
     71 
     72 static int
     73 process_msgfile(const char *path, char **filename)
     74 {
     75 	char *proc_file = NULL;
     76 	char *line = NULL;
     77 	int pfd;
     78 	size_t size = 0;
     79 	FILE *fp;
     80 
     81         asprintf(&proc_file, "%s/tmp.XXXXXX", path);
     82 	if ((pfd = mkstemp(proc_file)) == -1)
     83 		return EXIT_FAILURE;
     84 	if((fp = fopen(*filename, "r")) == NULL) {
     85 		unlink(proc_file);
     86 		goto failure;
     87 	}
     88 	while (getline(&line, &size, fp) != -1) {
     89 		if(line[0] == '#')
     90 			continue;
     91 		if (write(pfd, line, strlen(line)) == -1)
     92 			goto failure;
     93 	}
     94 	close(pfd);
     95 	fclose(fp);
     96 	unlink(*filename);
     97 	free(*filename);
     98 	*filename = proc_file;
     99 	return EXIT_SUCCESS;
    100 failure:
    101 	free(proc_file);
    102 	*filename = NULL;
    103 	return EXIT_FAILURE;
    104 }
    105 
    106 int
    107 cmd_commit(int argc, char **argv)
    108 {
    109 	char *commit_msg = NULL, *msg_file = NULL;
    110 	char *exec;
    111 	char *branch_head, *workdir_head;
    112 	const char *editor;
    113 	int ch;
    114 	int flag_msg = 0, flag_error = 0, flag_force = 0;
    115 	struct session s;
    116 
    117 	baseline_session_begin(&s, 0);
    118 
    119 	/* parse command line options */
    120 	while ((ch = getopt(argc, argv, "fm:")) != -1) {
    121 		switch (ch) {
    122 		case 'f':
    123 			flag_force = 1;
    124 			break;
    125 		case 'm':
    126 			flag_msg = 1;
    127 			commit_msg = strdup(optarg);
    128 			break;
    129 		default:
    130 			flag_error = 1;
    131 			break;
    132 		}
    133 	}
    134 	argc -= optind;
    135 	argv += optind;
    136 
    137 	if (flag_error) {
    138 		return EXIT_FAILURE;
    139 	}
    140 	if (flag_msg) {
    141 		/* TODO: should be per-stage file */
    142 		if (gen_temp_msgfile_from_msg(s.repo_baselinedir, &msg_file, commit_msg) == EXIT_FAILURE)
    143 			errx(EXIT_FAILURE, "error, failed to generate commit message.");
    144 		free(commit_msg);
    145 	}
    146 	else {
    147 		if (gen_temp_msgfile(s.repo_baselinedir, &msg_file) == EXIT_FAILURE)
    148 			errx(EXIT_FAILURE, "error, failed to generate commit message.");
    149 		if((editor = baseline_config_get_val("editor")) != NULL && strcmp(editor, "")) {
    150 			asprintf(&exec, "%s %s", editor, msg_file);
    151 			if (system(exec) != EXIT_SUCCESS)
    152 				errx(EXIT_FAILURE, "error, failed to launch \'%s\' text editor.", editor);
    153 			free(exec);
    154 		}
    155 		else if ((editor = getenv("EDITOR")) != NULL) {
    156 			asprintf(&exec, "%s %s", editor, msg_file);
    157 			if (system(exec) != EXIT_SUCCESS)
    158 				errx(EXIT_FAILURE, "error, failed to launch \'%s\' text editor.", editor);
    159 			free(exec);
    160 		}
    161 		else {
    162 			errx(EXIT_FAILURE, "error, commit message not specified.");
    163 		}
    164 	}
    165 	if (process_msgfile(s.repo_baselinedir, &msg_file) == EXIT_FAILURE)
    166 		errx(EXIT_FAILURE, "error, failed to process commit message.");
    167 
    168 	/* make sure that the working dir matches the branch's head */
    169 	if (s.db_ops->branch_get_head(s.db_ctx, s.branch, &branch_head) == EXIT_FAILURE)
    170 		errx(EXIT_FAILURE, "error, failed to query the status of the current branch.");
    171 	if (s.dc_ops->workdir_get(s.dc_ctx, &workdir_head) == EXIT_FAILURE)
    172 		errx(EXIT_FAILURE, "error, failed to query the status of the working directory.");
    173 	/* check if it's the first commit ever */
    174 	if ((branch_head == NULL && workdir_head == NULL) || (strcmp(branch_head, workdir_head) == 0) || flag_force)
    175 		goto next;
    176 	else
    177 		errx(EXIT_FAILURE, "error, the heads of the current branch and working directory do not match.\n"
    178 			"you can:\n\t[1] switch to the correct branch, or\n\t[2] checkout the last head, or\n\t[3] use -f to force commit");
    179 
    180 next:
    181 	if (s.dc_ops->commit(s.dc_ctx, msg_file) == EXIT_FAILURE)
    182 		errx(EXIT_FAILURE, "error, failed to commit your changes.\n");
    183 
    184 	unlink(msg_file);
    185 	free(msg_file);
    186 
    187 	baseline_session_end(&s);
    188 	return EXIT_SUCCESS;
    189 }
    190