/* $OpenBSD: commit.c,v 1.5 2004/11/09 22:22:47 krapht Exp $ */ /* * Copyright (c) 2004 Jean-Francois Brousseau * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include "cvs.h" #include "log.h" #include "buf.h" #include "proto.h" #define CVS_COMMIT_BIGMSG 32000 #define CVS_COMMIT_FTMPL "/tmp/cvsXXXXXXXXXX" #define CVS_COMMIT_LOGPREFIX "CVS:" #define CVS_COMMIT_LOGLINE \ "----------------------------------------------------------------------" static char* cvs_commit_openmsg (const char *); static char* cvs_commit_getmsg (const char *); /* * cvs_commit() * * Handler for the `cvs commit' command. */ int cvs_commit(int argc, char **argv) { int ch, recurse; char *msg, *mfile; recurse = 1; mfile = NULL; msg = NULL; cvs_commit_getmsg("."); while ((ch = getopt(argc, argv, "F:flm:R")) != -1) { switch (ch) { case 'F': mfile = optarg; break; case 'f': recurse = 0; break; case 'l': recurse = 0; break; case 'm': msg = optarg; break; case 'R': recurse = 1; break; default: return (EX_USAGE); } } if ((msg != NULL) && (mfile != NULL)) { cvs_log(LP_ERR, "the -F and -m flags are mutually exclusive"); return (EX_USAGE); } if ((mfile != NULL) && (msg = cvs_commit_openmsg(mfile)) == NULL) return (EX_DATAERR); argc -= optind; argv += optind; return (0); } /* * cvs_commit_openmsg() * * Open the file specified by and allocate a buffer large enough to * hold all of the file's contents. The returned value must later be freed * using the free() function. * Returns a pointer to the allocated buffer on success, or NULL on failure. */ static char* cvs_commit_openmsg(const char *path) { int ch; size_t len; char lbuf[256], *msg; struct stat st; FILE *fp; BUF *bp; if (stat(path, &st) == -1) { cvs_log(LP_ERRNO, "failed to stat `%s'", path); return (NULL); } if (!S_ISREG(st.st_mode)) { cvs_log(LP_ERR, "message file must be a regular file"); return (NULL); } if (st.st_size > CVS_COMMIT_BIGMSG) { do { fprintf(stderr, "The specified message file seems big. " "Proceed anyways? (y/n) "); if (fgets(lbuf, sizeof(lbuf), stdin) == NULL) { cvs_log(LP_ERRNO, "failed to read from standard input"); return (NULL); } len = strlen(lbuf); if ((len == 0) || (len > 2) || ((lbuf[0] != 'y') && (lbuf[0] != 'n'))) { fprintf(stderr, "invalid input\n"); continue; } else if (lbuf[0] == 'y') break; else if (lbuf[0] == 'n') { cvs_log(LP_ERR, "aborted by user"); return (NULL); } } while (1); } if ((fp = fopen(path, "r")) == NULL) { cvs_log(LP_ERRNO, "failed to open message file `%s'", path); return (NULL); } bp = cvs_buf_alloc(128, BUF_AUTOEXT); if (bp == NULL) { return (NULL); } while (fgets(lbuf, sizeof(lbuf), fp) != NULL) { len = strlen(lbuf); if (len == 0) continue; /* skip lines starting with the prefix */ if (strncmp(lbuf, CVS_COMMIT_LOGPREFIX, strlen(CVS_COMMIT_LOGPREFIX)) == 0) continue; cvs_buf_append(bp, lbuf, strlen(lbuf)); } cvs_buf_putc(bp, '\0'); msg = (char *)cvs_buf_release(bp); return (msg); } /* * cvs_commit_getmsg() * * Get a commit log message by forking the user's editor. * Returns the message in a dynamically allocated string on success, NULL on * failure. */ static char* cvs_commit_getmsg(const char *dir) { int ret, fd, argc, fds[3]; size_t len; char *argv[4], buf[16], path[MAXPATHLEN], *msg; FILE *fp; struct stat st1, st2; msg = NULL; fds[0] = -1; fds[1] = -1; fds[2] = -1; strlcpy(path, CVS_COMMIT_FTMPL, sizeof(path)); argc = 0; argv[argc++] = cvs_editor; argv[argc++] = path; argv[argc] = NULL; if ((fd = mkstemp(path)) == -1) { cvs_log(LP_ERRNO, "failed to create temporary file"); return (NULL); } fp = fdopen(fd, "w"); if (fp == NULL) { cvs_log(LP_ERRNO, "failed to fdopen"); } else { fprintf(fp, "\n%s %s\n%s Enter Log. Lines beginning with `%s' are " "removed automatically\n%s\n%s Commiting in %s\n" "%s\n%s Modified Files:\n", CVS_COMMIT_LOGPREFIX, CVS_COMMIT_LOGLINE, CVS_COMMIT_LOGPREFIX, CVS_COMMIT_LOGPREFIX, CVS_COMMIT_LOGPREFIX, CVS_COMMIT_LOGPREFIX, dir, CVS_COMMIT_LOGPREFIX, CVS_COMMIT_LOGPREFIX); /* XXX list files here */ fprintf(fp, "%s %s\n", CVS_COMMIT_LOGPREFIX, CVS_COMMIT_LOGLINE); } (void)fflush(fp); if (fstat(fd, &st1) == -1) { cvs_log(LP_ERRNO, "failed to stat log message file"); (void)fclose(fp); if (unlink(path) == -1) cvs_log(LP_ERRNO, "failed to unlink log file %s", path); return (NULL); } for (;;) { ret = cvs_exec(argc, argv, fds); if (ret == -1) break; if (fstat(fd, &st2) == -1) { cvs_log(LP_ERRNO, "failed to stat log message file"); break; } if (st2.st_mtime != st1.st_mtime) break; /* nothing was entered */ fprintf(stderr, "Log message unchanged or not specified\na)bort, " "c)ontinue, e)dit, !)reuse this message unchanged " "for remaining dirs\nAction: (continue) "); if (fgets(buf, sizeof(buf), stdin) == NULL) { cvs_log(LP_ERRNO, "failed to read from standard input"); break; } len = strlen(buf); if ((len == 0) || (len > 2)) { fprintf(stderr, "invalid input\n"); continue; } else if (buf[0] == 'a') { cvs_log(LP_ERR, "aborted by user"); break; } else if ((buf[0] == '\n') || (buf[0] == 'c')) { /* empty message */ msg = strdup(""); break; } else if (ret == 'e') continue; else if (ret == '!') { /* XXX do something */ } } (void)fclose(fp); (void)close(fd); if (unlink(path) == -1) cvs_log(LP_ERRNO, "failed to unlink log file %s", path); return (msg); }