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