Skip to content

Commit d8233fe

Browse files
committed
reflog: allow adding entries with newlines in their message
Currently, the reflog disallows any entries that have a message with newlines, as that would effectively break the reflog format, which may contain a single line per entry, only. Upstream git behaves a bit differently, though, especially when considering stashes: instead of rejecting any reflog entry with newlines, git will simply replace newlines with spaces. E.g. executing 'git stash push -m "foo\nbar"' will create a reflog entry with "foo bar" as entry message. This commit adjusts our own logic to stop rejecting commit messages with newlines. Previously, this logic was part of `git_reflog_append`, only. There is a second place though where we add reflog entries, which is the serialization code in the filesystem refdb. As it didn't contain any sanity checks whatsoever, the refdb would have been perfectly happy to write malformatted reflog entries to the disk. This is being fixed with the same logic as for the reflog itself.
1 parent 2848160 commit d8233fe

File tree

5 files changed

+60
-16
lines changed

5 files changed

+60
-16
lines changed

src/refdb_fs.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1856,8 +1856,15 @@ static int serialize_reflog_entry(
18561856
git_buf_rtrim(buf);
18571857

18581858
if (msg) {
1859+
size_t i;
1860+
18591861
git_buf_putc(buf, '\t');
18601862
git_buf_puts(buf, msg);
1863+
1864+
for (i = 0; i < buf->size - 2; i++)
1865+
if (buf->ptr[i] == '\n')
1866+
buf->ptr[i] = ' ';
1867+
git_buf_rtrim(buf);
18611868
}
18621869

18631870
git_buf_putc(buf, '\n');

src/reflog.c

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,8 @@ int git_reflog_write(git_reflog *reflog)
7474

7575
int git_reflog_append(git_reflog *reflog, const git_oid *new_oid, const git_signature *committer, const char *msg)
7676
{
77-
git_reflog_entry *entry;
7877
const git_reflog_entry *previous;
79-
const char *newline;
78+
git_reflog_entry *entry;
8079

8180
assert(reflog && new_oid && committer);
8281

@@ -87,19 +86,18 @@ int git_reflog_append(git_reflog *reflog, const git_oid *new_oid, const git_sign
8786
goto cleanup;
8887

8988
if (msg != NULL) {
90-
if ((entry->msg = git__strdup(msg)) == NULL)
91-
goto cleanup;
89+
size_t i, msglen = strlen(msg);
9290

93-
newline = strchr(msg, '\n');
94-
95-
if (newline) {
96-
if (newline[1] != '\0') {
97-
git_error_set(GIT_ERROR_INVALID, "reflog message cannot contain newline");
98-
goto cleanup;
99-
}
91+
if ((entry->msg = git__strndup(msg, msglen)) == NULL)
92+
goto cleanup;
10093

101-
entry->msg[newline - msg] = '\0';
102-
}
94+
/*
95+
* Replace all newlines with spaces, except for
96+
* the final trailing newline.
97+
*/
98+
for (i = 0; i < msglen; i++)
99+
if (entry->msg[i] == '\n')
100+
entry->msg[i] = ' ';
103101
}
104102

105103
previous = git_reflog_entry_byindex(reflog, 0);

tests/refs/reflog/messages.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,27 @@ void test_refs_reflog_messages__creating_a_direct_reference(void)
281281
git_reference_free(reference);
282282
}
283283

284+
void test_refs_reflog_messages__newline_gets_replaced(void)
285+
{
286+
const git_reflog_entry *entry;
287+
git_signature *signature;
288+
git_reflog *reflog;
289+
git_oid oid;
290+
291+
cl_git_pass(git_signature_now(&signature, "me", "foo@example.com"));
292+
cl_git_pass(git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"));
293+
294+
cl_git_pass(git_reflog_read(&reflog, g_repo, "HEAD"));
295+
cl_assert_equal_sz(7, git_reflog_entrycount(reflog));
296+
cl_git_pass(git_reflog_append(reflog, &oid, signature, "inner\nnewline"));
297+
cl_assert_equal_sz(8, git_reflog_entrycount(reflog));
298+
299+
cl_assert(entry = git_reflog_entry_byindex(reflog, 0));
300+
cl_assert_equal_s(git_reflog_entry_message(entry), "inner newline");
301+
302+
git_signature_free(signature);
303+
git_reflog_free(reflog);
304+
}
284305

285306
void test_refs_reflog_messages__renaming_ref(void)
286307
{

tests/refs/reflog/reflog.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,13 @@ void test_refs_reflog_reflog__append_then_read(void)
8787
cl_git_pass(git_signature_now(&committer, "foo", "foo@bar"));
8888

8989
cl_git_pass(git_reflog_read(&reflog, g_repo, new_ref));
90-
91-
cl_git_fail(git_reflog_append(reflog, &oid, committer, "no inner\nnewline"));
9290
cl_git_pass(git_reflog_append(reflog, &oid, committer, NULL));
9391
cl_git_pass(git_reflog_append(reflog, &oid, committer, commit_msg "\n"));
9492
cl_git_pass(git_reflog_write(reflog));
95-
git_reflog_free(reflog);
9693

9794
assert_appends(committer, &oid);
9895

96+
git_reflog_free(reflog);
9997
git_signature_free(committer);
10098
}
10199

tests/stash/save.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,26 @@ void test_stash_save__stashing_updates_the_reflog(void)
283283
assert_object_oid("refs/stash@{1}", NULL, GIT_OBJECT_COMMIT);
284284
}
285285

286+
void test_stash_save__multiline_message(void)
287+
{
288+
const char *msg = "This\n\nis a multiline message\n";
289+
const git_reflog_entry *entry;
290+
git_reflog *reflog;
291+
292+
assert_object_oid("refs/stash@{0}", NULL, GIT_OBJECT_COMMIT);
293+
294+
cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, msg, GIT_STASH_DEFAULT));
295+
296+
cl_git_pass(git_reflog_read(&reflog, repo, "refs/stash"));
297+
cl_assert(entry = git_reflog_entry_byindex(reflog, 0));
298+
cl_assert_equal_s(git_reflog_entry_message(entry), "On master: This is a multiline message");
299+
300+
assert_object_oid("refs/stash@{0}", git_oid_tostr_s(&stash_tip_oid), GIT_OBJECT_COMMIT);
301+
assert_commit_message_contains("refs/stash@{0}", msg);
302+
303+
git_reflog_free(reflog);
304+
}
305+
286306
void test_stash_save__cannot_stash_when_there_are_no_local_change(void)
287307
{
288308
git_index *index;

0 commit comments

Comments
 (0)