Skip to content

Commit f69cc6b

Browse files
committed
commit: allow editing notes in commit message editor
One workflow for git-notes is to informally keep a list of changes from one version of a patch to another as the patch is modified via "commit --amend" and "git rebase". Often the most convenient time for this is while editing the commit message, since you see it during amend, during "rebase -i" edit stops, and when "rebase" finds a conflict. This patch adds a "--notes" option which displays existing notes in the commit editor (in the case of --amend), and extracts new notes from the editor message to add to the newly created commit. The feature is activated only for interactive edits, so "-F" and "-m" messages are safe from munging. Signed-off-by: Jeff King <peff@peff.net>
1 parent 56a4f3c commit f69cc6b

4 files changed

Lines changed: 284 additions & 3 deletions

File tree

builtin/commit.c

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@
4343
#include "commit-graph.h"
4444
#include "pretty.h"
4545
#include "trailer.h"
46+
#include "notes-utils.h"
47+
#include "blob.h"
48+
#include "object-file.h"
4649

4750
static const char * const builtin_commit_usage[] = {
4851
N_("git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]\n"
@@ -145,6 +148,8 @@ static char *cleanup_config;
145148
static enum commit_whence whence;
146149
static int use_editor = 1, include_status = 1;
147150
static int have_option_m;
151+
static const char *edit_notes;
152+
static struct notes_tree edit_notes_tree;
148153
static struct strbuf message = STRBUF_INIT;
149154

150155
static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
@@ -759,6 +764,71 @@ static void change_data_free(void *util, const char *str UNUSED)
759764
free(d);
760765
}
761766

767+
static void init_edit_notes(void) {
768+
struct strbuf ref = STRBUF_INIT;
769+
if (edit_notes_tree.initialized)
770+
return;
771+
strbuf_addstr(&ref, edit_notes);
772+
expand_notes_ref(&ref);
773+
init_notes(&edit_notes_tree, ref.buf,
774+
combine_notes_overwrite, 0);
775+
}
776+
777+
static void add_notes_from_commit(struct strbuf *out, const char *name)
778+
{
779+
struct commit *commit;
780+
struct strbuf note = STRBUF_INIT;
781+
782+
init_edit_notes();
783+
784+
commit = lookup_commit_reference_by_name(name);
785+
if (!commit)
786+
die("could not lookup commit %s", name);
787+
format_note(&edit_notes_tree, &commit->object.oid, &note,
788+
get_commit_output_encoding(), 0);
789+
790+
if (note.len) {
791+
strbuf_addstr(out, "\n---\n");
792+
strbuf_addbuf(out, &note);
793+
}
794+
strbuf_release(&note);
795+
}
796+
797+
static void extract_notes_from_message(struct strbuf *msg, struct strbuf *notes)
798+
{
799+
const char *separator = strstr(msg->buf, "\n---\n");
800+
801+
if (!separator)
802+
return;
803+
804+
strbuf_addstr(notes, separator + 5);
805+
strbuf_setlen(msg, separator - msg->buf + 1);
806+
}
807+
808+
static void update_notes_for_commit(struct strbuf *notes,
809+
const struct object_id *commit_oid)
810+
{
811+
init_edit_notes();
812+
813+
if (cleanup_mode != COMMIT_MSG_CLEANUP_NONE)
814+
strbuf_stripspace(notes,
815+
cleanup_mode == COMMIT_MSG_CLEANUP_ALL ?
816+
comment_line_str :
817+
NULL);
818+
819+
if (!notes->len)
820+
remove_note(&edit_notes_tree, commit_oid->hash);
821+
else {
822+
struct object_id blob_oid;
823+
if (write_object_file(notes->buf, notes->len,
824+
OBJ_BLOB, &blob_oid) < 0)
825+
die("unable to write note blob");
826+
add_note(&edit_notes_tree, commit_oid, &blob_oid,
827+
combine_notes_overwrite);
828+
}
829+
commit_notes(the_repository, &edit_notes_tree, "updated by commit --notes");
830+
}
831+
762832
static int prepare_to_commit(const char *index_file, const char *prefix,
763833
struct commit *current_head,
764834
struct wt_status *s,
@@ -928,6 +998,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
928998
if (signoff)
929999
append_signoff(&sb, ignored_log_message_bytes(sb.buf, sb.len), 0);
9301000

1001+
if (edit_notes && amend)
1002+
add_notes_from_commit(&sb, "HEAD");
9311003
if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)
9321004
die_errno(_("could not write commit template"));
9331005

@@ -1325,6 +1397,12 @@ static int parse_and_validate_options(int argc, const char *argv[],
13251397
if (logfile || have_option_m || use_message)
13261398
use_editor = 0;
13271399

1400+
if (!use_editor)
1401+
edit_notes = NULL;
1402+
/* Magic value for "no ref passed" */
1403+
if (edit_notes && !*edit_notes)
1404+
edit_notes = default_notes_ref(the_repository);
1405+
13281406
/* Sanity check options */
13291407
if (amend && !current_head)
13301408
die(_("You have nothing to amend."));
@@ -1737,6 +1815,15 @@ int cmd_commit(int argc,
17371815
.flags = PARSE_OPT_OPTARG,
17381816
.defval = (intptr_t) "",
17391817
},
1818+
{
1819+
.type = OPTION_STRING,
1820+
.long_name = "notes",
1821+
.value = &edit_notes,
1822+
.argh = N_("ref"),
1823+
.help = N_("edit notes interactively"),
1824+
.flags = PARSE_OPT_OPTARG,
1825+
.defval = (intptr_t) "",
1826+
},
17401827
/* end commit message options */
17411828

17421829
OPT_GROUP(N_("Commit contents options")),
@@ -1787,6 +1874,7 @@ int cmd_commit(int argc,
17871874

17881875
struct strbuf sb = STRBUF_INIT;
17891876
struct strbuf author_ident = STRBUF_INIT;
1877+
struct strbuf notes = STRBUF_INIT;
17901878
const char *index_file, *reflog_msg;
17911879
struct object_id oid;
17921880
struct commit_list *parents = NULL;
@@ -1901,6 +1989,9 @@ int cmd_commit(int argc,
19011989
die(_("could not read commit message: %s"), strerror(saved_errno));
19021990
}
19031991

1992+
/* XXX this maybe needs to go in the middle of cleanup_message()? */
1993+
if (edit_notes)
1994+
extract_notes_from_message(&sb, &notes);
19041995
cleanup_message(&sb, cleanup_mode, verbose);
19051996

19061997
if (message_is_empty(&sb, cleanup_mode) && !allow_empty_message) {
@@ -1942,6 +2033,10 @@ int cmd_commit(int argc,
19422033
die(_("failed to write commit object"));
19432034
}
19442035

2036+
if (edit_notes)
2037+
update_notes_for_commit(&notes, &oid);
2038+
strbuf_release(&notes);
2039+
19452040
if (update_head_with_reflog(current_head, &oid, reflog_msg, &sb,
19462041
&err)) {
19472042
rollback_index_files();
@@ -1965,7 +2060,7 @@ int cmd_commit(int argc,
19652060
run_auto_maintenance(the_repository, quiet);
19662061
run_commit_hook(use_editor, repo_get_index_file(the_repository),
19672062
NULL, "post-commit", NULL);
1968-
if (amend && !no_post_rewrite) {
2063+
if (!edit_notes && !amend && !no_post_rewrite) {
19692064
commit_post_rewrite(the_repository, current_head, &oid);
19702065
}
19712066
if (!quiet) {

notes.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,8 +1276,8 @@ void free_notes(struct notes_tree *t)
12761276
* (raw != 0) gives the %N userformat; otherwise, the note message is given
12771277
* for human consumption.
12781278
*/
1279-
static void format_note(struct notes_tree *t, const struct object_id *object_oid,
1280-
struct strbuf *sb, const char *output_encoding, int raw)
1279+
void format_note(struct notes_tree *t, const struct object_id *object_oid,
1280+
struct strbuf *sb, const char *output_encoding, int raw)
12811281
{
12821282
static const char utf8[] = "utf-8";
12831283
const struct object_id *oid;

notes.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,9 @@ void disable_display_notes(struct display_notes_opt *opt, int *show_notes);
303303
*/
304304
void load_display_notes(struct display_notes_opt *opt);
305305

306+
void format_note(struct notes_tree *t, const struct object_id *object_oid,
307+
struct strbuf *sb, const char *output_encoding, int raw);
308+
306309
/*
307310
* Append notes for the given 'object_sha1' from all trees set up by
308311
* load_display_notes() to 'sb'.

t/t7510-commit-notes.sh

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
#!/bin/sh
2+
3+
test_description='commit w/ --notes'
4+
. ./test-lib.sh
5+
6+
# Fake editor to simulate user adding a note.
7+
cat >add.sh <<'EOF'
8+
perl -i -pe '
9+
BEGIN { $n = shift }
10+
# insert at $n-th blank line
11+
if (/^$/ && ++$count == $n) {
12+
print "---\n";
13+
print "added note\n";
14+
print "with multiple lines\n";
15+
}
16+
' "$@"
17+
EOF
18+
cat >expect-add <<'EOF'
19+
added note
20+
with multiple lines
21+
EOF
22+
23+
# Fake editor to simulate user deleting a note.
24+
cat >del.sh <<'EOF'
25+
perl -i -ne '
26+
if (/^---$/) {
27+
while (<>) {
28+
last if /^$/;
29+
}
30+
next;
31+
}
32+
print;
33+
' "$1"
34+
EOF
35+
36+
# Fake editor to simulate user modifying a note.
37+
cat >mod.sh <<'EOF'
38+
perl -i -pe '
39+
s/added note/modified note/
40+
' "$1"
41+
EOF
42+
cat >expect-mod <<'EOF'
43+
modified note
44+
with multiple lines
45+
EOF
46+
47+
# Fake editor for leaving notes untouched.
48+
cat >nil.sh <<'EOF'
49+
EOF
50+
51+
# Convenience function for setting up editor.
52+
set_editor() {
53+
git config core.editor "\"$SHELL_PATH\" $1.sh $2"
54+
}
55+
56+
cat >expect-msg <<'EOF'
57+
commit one
58+
59+
this is the body of commit one
60+
EOF
61+
62+
test_expect_success 'setup' '
63+
test_commit one &&
64+
git commit --amend -F expect-msg
65+
'
66+
67+
test_expect_success 'add a note' '
68+
set_editor add 2 &&
69+
git commit --notes --amend &&
70+
git notes show >actual &&
71+
test_cmp expect-add actual &&
72+
git log -1 --pretty=format:%B >actual &&
73+
test_cmp expect-msg actual
74+
'
75+
76+
test_expect_success 'notes are preserved' '
77+
set_editor nil &&
78+
git commit --notes --amend &&
79+
git notes show >actual &&
80+
test_cmp expect-add actual &&
81+
git log -1 --pretty=format:%B >actual &&
82+
test_cmp expect-msg actual
83+
'
84+
85+
test_expect_success 'modify a note' '
86+
set_editor mod &&
87+
git commit --notes --amend &&
88+
git notes show >actual &&
89+
test_cmp expect-mod actual &&
90+
git log -1 --pretty=format:%B >actual &&
91+
test_cmp expect-msg actual
92+
'
93+
94+
test_expect_success 'delete a note' '
95+
set_editor del &&
96+
git commit --notes --amend &&
97+
test_must_fail git notes show &&
98+
git log -1 --pretty=format:%B >actual &&
99+
test_cmp expect-msg actual
100+
'
101+
102+
test_expect_success 'add to commit without body' '
103+
test_commit two &&
104+
git log -1 --pretty=format:%B >expect-msg &&
105+
set_editor add 1 &&
106+
git commit --notes --amend &&
107+
git notes show >actual &&
108+
test_cmp expect-add actual &&
109+
git log -1 --pretty=format:%B >actual &&
110+
test_cmp expect-msg actual
111+
'
112+
113+
cat >expect-verbatim-msg <<'EOF'
114+
verbatim subject
115+
116+
verbatim body
117+
# embedded comment
118+
119+
EOF
120+
cat >expect-verbatim-note <<'EOF'
121+
122+
verbatim note
123+
with leading and trailing whitespace
124+
# and embedded comments
125+
126+
EOF
127+
cat >verbatim.sh <<'EOF'
128+
{
129+
cat expect-verbatim-msg &&
130+
echo --- &&
131+
cat expect-verbatim-note
132+
} >"$1"
133+
EOF
134+
135+
test_expect_success 'commit --cleanup=verbatim preserves message and notes' '
136+
test_tick &&
137+
echo content >>file && git add file &&
138+
set_editor verbatim &&
139+
git commit --notes --cleanup=verbatim &&
140+
git cat-file commit HEAD >msg.raw &&
141+
sed "1,/^\$/d" <msg.raw >actual &&
142+
test_cmp expect-verbatim-msg actual &&
143+
git notes show >actual &&
144+
test_cmp expect-verbatim-note actual
145+
'
146+
147+
test_expect_success 'commit -v does not interfere with notes' '
148+
test_commit three &&
149+
git log -1 --pretty=format:%B >expect-msg
150+
set_editor add 1 &&
151+
git commit -v --notes --amend &&
152+
git notes show >actual &&
153+
test_cmp actual expect-add &&
154+
git log -1 --pretty=format:%B >actual &&
155+
test_cmp expect-msg actual
156+
'
157+
158+
test_expect_success 'edit notes on alternate ref' '
159+
test_commit four &&
160+
set_editor add 1 &&
161+
git commit --notes=foo --amend &&
162+
test_must_fail git notes show &&
163+
git notes --ref refs/notes/foo show >actual &&
164+
test_cmp expect-add actual
165+
'
166+
167+
test_expect_success 'ref rewriting does not overwrite our edits' '
168+
git config notes.rewriteRef refs/notes/commits &&
169+
test_commit five &&
170+
set_editor add 1 &&
171+
git commit --notes --amend &&
172+
git notes show >actual &&
173+
test_cmp expect-add actual &&
174+
set_editor mod &&
175+
git commit --notes --amend &&
176+
git notes show >actual &&
177+
test_cmp expect-mod actual &&
178+
set_editor del &&
179+
git commit --notes --amend &&
180+
test_must_fail git notes show
181+
'
182+
183+
test_done

0 commit comments

Comments
 (0)