commit - a5a46be28fbbc316a4ce0adb5d92571a59a7e115
commit + 28cf319f780087bba863715f31d4ec417eb87a6d
blob - 5a69c331382003ee5c2d946293c4d1647cf56088
blob + 1991d4245ec041b192b623d07a0b5659e4757156
--- got/got.1
+++ got/got.1
.It Cm rv
Short alias for
.Cm revert .
-.It Cm commit Oo Fl m Ar message Oc Oo Fl S Oc Op Ar path ...
+.It Cm commit Oo Fl F Ar path Oc Oo Fl m Ar message Oc Oo Fl N Oc Oo Fl S Oc Op Ar path ...
Create a new commit in the repository from changes in a work tree
and use this commit as the new base commit for the work tree.
If no
only commit staged changes and reject any specified paths which
have not been staged.
.Pp
+.Cm got commit
+opens a temporary file in an editor where a log message can be written
+unless the
+.Fl m
+option is used
+or the
+.Fl F
+and
+.Fl N
+options are used together.
+.Pp
Show the status of each affected file, using the following status codes:
.Bl -column YXZ description
.It M Ta modified file
.Cm got commit
are as follows:
.Bl -tag -width Ds
-.It Fl m Ar message
-Use the specified log message when creating the new commit.
-Without the
+.It Fl F Ar path
+Use the prepared log message stored in the file found at
+.Ar path
+when creating the new commit.
+.Cm got commit
+opens a temporary file in an editor where the prepared log message can be
+reviewed and edited further if needed.
+Cannot be used together with the
.Fl m
-option,
+option.
+.It Fl m Ar message
+Use the specified log message when creating the new commit.
+Cannot be used together with the
+.Fl F
+option.
+.It Fl N
+This option prevents
.Cm got commit
-opens a temporary file in an editor where a log message can be written.
+from opening the commit message in an editor.
+It has no effect unless it is used together with the
+.Fl F
+option and is intended for non-interactive use such as scripting.
.It Fl S
Allow the addition of symbolic links which point outside of the path space
that is under version control.
.Pp
Alternatively, create a new commit from local changes in a work tree
directory with a log message that has been prepared in the file
-.Pa /tmp/msg .
-If
-.Xr vi 1
-is set as the
-.Ev EDITOR ,
-.Pa /tmp/msg
-can be read into the buffer for review:
+.Pa /tmp/msg:
.Pp
-.Dl $ got commit
-.Dl :r /tmp/msg
+.Dl $ got commit -F /tmp/msg
.Pp
Update any work tree checked out from the
.Dq unified-buffer-cache
blob - f1d690299d64d615136d348d8a119f0828400162
blob + 7a85d64a195dba5931737769c04f02796efa3258
--- got/got.c
+++ got/got.c
static const struct got_error *
edit_logmsg(char **logmsg, const char *editor, const char *logmsg_path,
- const char *initial_content, size_t initial_content_len, int check_comments)
+ const char *initial_content, size_t initial_content_len,
+ int require_modification)
{
const struct got_error *err = NULL;
char *line = NULL;
if (stat(logmsg_path, &st2) == -1)
return got_error_from_errno("stat");
- if (st.st_mtime == st2.st_mtime && st.st_size == st2.st_size)
+ if (require_modification &&
+ st.st_mtime == st2.st_mtime && st.st_size == st2.st_size)
return got_error_msg(GOT_ERR_COMMIT_MSG_EMPTY,
"no changes made to commit message, aborting");
"commit message cannot be empty, aborting");
goto done;
}
- if (check_comments && strcmp(*logmsg, initial_content_stripped) == 0)
+ if (require_modification &&
+ strcmp(*logmsg, initial_content_stripped) == 0)
err = got_error_msg(GOT_ERR_COMMIT_MSG_EMPTY,
"no changes made to commit message, aborting");
done:
__dead static void
usage_commit(void)
{
- fprintf(stderr, "usage: %s commit [-m msg] [-S] [path ...]\n",
- getprogname());
+ fprintf(stderr, "usage: %s commit [-F path] [-m msg] [-N] [-S] "
+ "[path ...]\n", getprogname());
exit(1);
}
struct collect_commit_logmsg_arg {
const char *cmdline_log;
+ const char *prepared_log;
+ int non_interactive;
const char *editor;
const char *worktree_path;
const char *branch_name;
char *logmsg_path;
};
+
+static const struct got_error *
+read_prepared_logmsg(char **logmsg, const char *path)
+{
+ const struct got_error *err = NULL;
+ FILE *f = NULL;
+ struct stat sb;
+ size_t r;
+
+ *logmsg = NULL;
+ memset(&sb, 0, sizeof(sb));
+
+ f = fopen(path, "r");
+ if (f == NULL)
+ return got_error_from_errno2("fopen", path);
+
+ if (fstat(fileno(f), &sb) == -1) {
+ err = got_error_from_errno2("fstat", path);
+ goto done;
+ }
+ if (sb.st_size == 0) {
+ err = got_error(GOT_ERR_COMMIT_MSG_EMPTY);
+ goto done;
+ }
+
+ *logmsg = malloc(sb.st_size + 1);
+ if (*logmsg == NULL) {
+ err = got_error_from_errno("malloc");
+ goto done;
+ }
+
+ r = fread(*logmsg, 1, sb.st_size, f);
+ if (r != sb.st_size) {
+ if (ferror(f))
+ err = got_error_from_errno2("fread", path);
+ else
+ err = got_error(GOT_ERR_IO);
+ goto done;
+ }
+ (*logmsg)[sb.st_size] = '\0';
+done:
+ if (fclose(f) == EOF && err == NULL)
+ err = got_error_from_errno2("fclose", path);
+ if (err) {
+ free(*logmsg);
+ *logmsg = NULL;
+ }
+ return err;
+
+}
static const struct got_error *
collect_commit_logmsg(struct got_pathlist_head *commitable_paths, char **logmsg,
return got_error_from_errno("malloc");
strlcpy(*logmsg, a->cmdline_log, len);
return NULL;
- }
+ } else if (a->prepared_log != NULL && a->non_interactive)
+ return read_prepared_logmsg(logmsg, a->prepared_log);
if (asprintf(&template, "%s/logmsg", a->worktree_path) == -1)
return got_error_from_errno("asprintf");
+ err = got_opentemp_named_fd(&a->logmsg_path, &fd, template);
+ if (err)
+ goto done;
+
+ if (a->prepared_log) {
+ char *msg;
+ err = read_prepared_logmsg(&msg, a->prepared_log);
+ if (err)
+ goto done;
+ if (write(fd, msg, strlen(msg)) == -1) {
+ err = got_error_from_errno2("write", a->logmsg_path);
+ free(msg);
+ goto done;
+ }
+ free(msg);
+ }
+
initial_content_len = asprintf(&initial_content,
"\n# changes to be committed on branch %s:\n",
a->branch_name);
- if (initial_content_len == -1)
- return got_error_from_errno("asprintf");
-
- err = got_opentemp_named_fd(&a->logmsg_path, &fd, template);
- if (err)
+ if (initial_content_len == -1) {
+ err = got_error_from_errno("asprintf");
goto done;
+ }
if (write(fd, initial_content, initial_content_len) == -1) {
err = got_error_from_errno2("write", a->logmsg_path);
}
err = edit_logmsg(logmsg, a->editor, a->logmsg_path, initial_content,
- initial_content_len, 1);
+ initial_content_len, a->prepared_log ? 0 : 1);
done:
free(initial_content);
free(template);
char *cwd = NULL, *id_str = NULL;
struct got_object_id *id = NULL;
const char *logmsg = NULL;
+ char *prepared_logmsg = NULL;
struct collect_commit_logmsg_arg cl_arg;
char *gitconfig_path = NULL, *editor = NULL, *author = NULL;
int ch, rebase_in_progress, histedit_in_progress, preserve_logmsg = 0;
- int allow_bad_symlinks = 0;
+ int allow_bad_symlinks = 0, non_interactive = 0;
struct got_pathlist_head paths;
TAILQ_INIT(&paths);
cl_arg.logmsg_path = NULL;
- while ((ch = getopt(argc, argv, "m:S")) != -1) {
+ while ((ch = getopt(argc, argv, "F:m:NS")) != -1) {
switch (ch) {
+ case 'F':
+ if (logmsg != NULL)
+ option_conflict('F', 'm');
+ prepared_logmsg = realpath(optarg, NULL);
+ if (prepared_logmsg == NULL)
+ return got_error_from_errno2("realpath",
+ optarg);
+ break;
case 'm':
+ if (prepared_logmsg)
+ option_conflict('m', 'F');
logmsg = optarg;
+ break;
+ case 'N':
+ non_interactive = 1;
break;
case 'S':
allow_bad_symlinks = 1;
cl_arg.editor = editor;
cl_arg.cmdline_log = logmsg;
+ cl_arg.prepared_log = prepared_logmsg;
+ cl_arg.non_interactive = non_interactive;
cl_arg.worktree_path = got_worktree_get_root_path(worktree);
cl_arg.branch_name = got_worktree_get_head_ref_name(worktree);
if (!histedit_in_progress) {
free(gitconfig_path);
free(editor);
free(author);
+ free(prepared_logmsg);
return error;
}
blob - 1e055a9b1f81d1721ec308592989b045d4f027c9
blob + 3956dc0289b56e9ce806eab6702d0522b243de79
--- regress/cmdline/commit.sh
+++ regress/cmdline/commit.sh
test_done "$testroot" "0"
}
+
+test_commit_prepared_logmsg() {
+ local testroot=`test_init commit_prepared_logmsg`
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "modified alpha" > $testroot/wt/alpha
+ (cd $testroot/wt && got rm beta >/dev/null)
+ echo "new file" > $testroot/wt/new
+ (cd $testroot/wt && got add new >/dev/null)
+
+ echo 'test commit_prepared_logmsg' > $testroot/logmsg
+
+ cat > $testroot/editor.sh <<EOF
+#!/bin/sh
+sed -i 's/foo/bar/' "\$1"
+EOF
+ chmod +x $testroot/editor.sh
+
+ (cd $testroot/wt && env EDITOR="$testroot/editor.sh" \
+ got commit -F "$testroot/logmsg" > $testroot/stdout)
+ local head_rev=`git_show_head $testroot/repo`
+ echo "A new" > $testroot/stdout.expected
+ echo "M alpha" >> $testroot/stdout.expected
+ echo "D beta" >> $testroot/stdout.expected
+ echo "Created commit $head_rev" >> $testroot/stdout.expected
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ local author_time=`git_show_author_time $testroot/repo`
+ d=`env TZ=UTC date -r $author_time +"%a %b %e %X %Y UTC"`
+ echo "-----------------------------------------------" > $testroot/stdout.expected
+ echo "commit $head_rev (master)" >> $testroot/stdout.expected
+ echo "from: $GOT_AUTHOR" >> $testroot/stdout.expected
+ echo "date: $d" >> $testroot/stdout.expected
+ echo " " >> $testroot/stdout.expected
+ echo " test commit_prepared_logmsg" >> $testroot/stdout.expected
+ echo " " >> $testroot/stdout.expected
+
+ (cd $testroot/wt && got log -l 1 > $testroot/stdout)
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "modified alpha again" > $testroot/wt/alpha
+
+ echo 'test commit_prepared_logmsg non-interactive' \
+ > $testroot/logmsg
+
+ (cd $testroot/wt && got commit -N -F "$testroot/logmsg" \
+ > $testroot/stdout)
+
+ local head_rev=`git_show_head $testroot/repo`
+ echo "M alpha" > $testroot/stdout.expected
+ echo "Created commit $head_rev" >> $testroot/stdout.expected
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ local author_time=`git_show_author_time $testroot/repo`
+ d=`env TZ=UTC date -r $author_time +"%a %b %e %X %Y UTC"`
+ echo "-----------------------------------------------" \
+ > $testroot/stdout.expected
+ echo "commit $head_rev (master)" >> $testroot/stdout.expected
+ echo "from: $GOT_AUTHOR" >> $testroot/stdout.expected
+ echo "date: $d" >> $testroot/stdout.expected
+ echo " " >> $testroot/stdout.expected
+ echo " test commit_prepared_logmsg non-interactive" \
+ >> $testroot/stdout.expected
+ echo " " >> $testroot/stdout.expected
+
+ (cd $testroot/wt && got log -l 1 > $testroot/stdout)
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ fi
+ test_done "$testroot" "$ret"
+}
+
test_parseargs "$@"
run_test test_commit_basic
run_test test_commit_new_subdir
run_test test_commit_with_unrelated_submodule
run_test test_commit_symlink
run_test test_commit_fix_bad_symlink
+run_test test_commit_prepared_logmsg