Skip to contentSkip to conversation

Make git rebase -r's label generation more resilient

PATCH Superseded Johannes Schindelin via GitGitGadget 8 participants 22 messages Sep 2, 2019 – Nov 21, 2019 1 message not in the archive

lore
Share & tools
lore
#0 lore raw

cover Make git rebase -r's label generation more resilient

Those labels must be valid ref names, and therefore valid file names.

This just came in via Git for Windows.

Matt R (1):
rebase -r: let label generate safer labels

 sequencer.c              | 12 +++++++++++-
 t/t3430-rebase-merges.sh |  5 +++++
 2 files changed, 16 insertions(+), 1 deletion(-)

base-commit: 5fa0f5238b0cd46cfe7f6fa76c3f526ea98148d9
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-327%2Fdscho%2Ffix-rebase-r-labels-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-327/dscho/fix-rebase-r-labels-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/327

Signature · 1 line
gitgitgadget
#1 lore raw

PATCH 1/1 rebase -r: let `label` generate safer labels

From: Matt R <[email protected]>

The label todo command in interactive rebases creates temporary refs in the refs/rewritten/ namespace. These refs are stored as loose refs, i.e. as files in .git/refs/rewritten/, therefore they have to conform with file name limitations on the current filesystem.

This poses a problem in particular on NTFS/FAT, where e.g. the colon character is not a valid part of a file name.

Let's safeguard against this by replacing not only white-space characters by dashes, but all non-alpha-numeric ones.

However, we exempt non-ASCII UTF-8 characters from that, as it should be quite possible to reflect branch names such as ↯↯↯ in refs/file names.

---
 sequencer.c              | 12 +++++++++++-
 t/t3430-rebase-merges.sh |  5 +++++
 2 files changed, 16 insertions(+), 1 deletion(-)
sequencer.c +11 -1
index 34ebf8ed94..23f4a0876a 100644
@@ -4635,8 +4635,18 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 		else
 			strbuf_addbuf(&label, &oneline);
 
+		/*
+		 * Sanitize labels by replacing non-alpha-numeric characters
+		 * (including white-space ones) by dashes, as they might be
+		 * illegal in file names (and hence in ref names).
+		 *
+		 * Note that we retain non-ASCII UTF-8 characters (identified
+		 * via the most significant bit). They should be all acceptable
+		 * in file names. We do not validate the UTF-8 here, that's not
+		 * the job of this function.
+		 */
 		for (p1 = label.buf; *p1; p1++)
-			if (isspace(*p1))
+			if (!(*p1 & 0x80) && !isalnum(*p1))
 				*(char *)p1 = '-';
 
 		strbuf_reset(&buf);
t/t3430-rebase-merges.sh +5 -0
index 7b6c4847ad..737396f944 100755
@@ -441,4 +441,9 @@ test_expect_success '--continue after resolving conflicts after a merge' '
 	test_path_is_missing .git/MERGE_HEAD
 '
 
+test_expect_success '--rebase-merges with commit that can generate bad characters for filename' '
+	git checkout -b colon-in-label E &&
+	git merge -m "colon: this should work" G &&
+	git rebase --rebase-merges --force-rebase E
+'
 test_done
Signature · 1 line
gitgitgadget
#2 lore raw

Re: [PATCH 1/1] rebase -r: let `label` generate safer labels

Hi Matt

This is definitely worth fixing, I've got a couple of comments below

Quoted 9 lines · Matt R via GitGitGadget

From: Matt R <[email protected]>

The label todo command in interactive rebases creates temporary refs in the refs/rewritten/ namespace. These refs are stored as loose refs, i.e. as files in .git/refs/rewritten/, therefore they have to conform with file name limitations on the current filesystem.

This poses a problem in particular on NTFS/FAT, where e.g. the colon character is not a valid part of a file name.

Being picking I'll point out that ':' is not a valid in refs either. Looking at https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file I think only " and | are not allowed on NTFS/FAT but are valid in refs (see the man page for git check-ref-format for all the details). So the main limitation is actually what git allows in refs.

Quoted 25 lines

Let's safeguard against this by replacing not only white-space characters by dashes, but all non-alpha-numeric ones.

However, we exempt non-ASCII UTF-8 characters from that, as it should be quite possible to reflect branch names such as ↯↯↯ in refs/file names.

---
  sequencer.c              | 12 +++++++++++-
  t/t3430-rebase-merges.sh |  5 +++++
  2 files changed, 16 insertions(+), 1 deletion(-)
sequencer.c +4 -0
index 34ebf8ed94..23f4a0876a 100644
@@ -4635,8 +4635,18 @@ static int make_script_with_merges(struct pretty_print_context *pp,
  		else
  			strbuf_addbuf(&label, &oneline);
 
+		/*
+		 * Sanitize labels by replacing non-alpha-numeric characters
+		 * (including white-space ones) by dashes, as they might be
+		 * illegal in file names (and hence in ref names).

sequencer.c static int make_script_with_merges(struct pretty_print_context *pp, · line 4650

Quoted 10 lines
sequencer.c +7 -1
+		 *
+		 * Note that we retain non-ASCII UTF-8 characters (identified
+		 * via the most significant bit). They should be all acceptable
+		 * in file names. We do not validate the UTF-8 here, that's not
+		 * the job of this function.
+		 */
  		for (p1 = label.buf; *p1; p1++)
-			if (isspace(*p1))
+			if (!(*p1 & 0x80) && !isalnum(*p1))
  				*(char *)p1 = '-';

I'm sightly concerned that this opens the possibility for unexpected effects if two different labels get sanitized to the same string. I suspect it's unlikely to happen in practice but doing something like percent encoding non-alphanumeric characters would avoid the problem entirely.

Best Wishes

Phillip

Quoted 16 lines
sequencer.c +0 -0
  		strbuf_reset(&buf);
t/t3430-rebase-merges.sh +5 -0
index 7b6c4847ad..737396f944 100755
@@ -441,4 +441,9 @@ test_expect_success '--continue after resolving conflicts after a merge' '
  	test_path_is_missing .git/MERGE_HEAD
  '
 
+test_expect_success '--rebase-merges with commit that can generate bad characters for filename' '
+	git checkout -b colon-in-label E &&
+	git merge -m "colon: this should work" G &&
+	git rebase --rebase-merges --force-rebase E
+'
  test_done
#3 lore raw

Re: [PATCH 1/1] rebase -r: let `label` generate safer labels

Quoted 6 lines · Phillip Wood

Being picking I'll point out that ':' is not a valid in refs either. Looking at
https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file I think only " and | are not allowed on NTFS/FAT but are valid in refs (see the man page for git check-ref-format for all the details). So the main limitation is actually what git allows in refs.

Yeah, trying to use the contents of the log message without sufficient sanitization is looking for trouble.

Quoted 10 lines

sequencer.c static int make_script_with_merges(struct pretty_print_context *pp, · line 4650

Quoted 4 lines

for (p1 = label.buf; *p1; p1++)

sequencer.c +1 -1
-			if (isspace(*p1))
+			if (!(*p1 & 0x80) && !isalnum(*p1))
  				*(char *)p1 = '-';

I'm sightly concerned that this opens the possibility for unexpected effects if two different labels get sanitized to the same string. I suspect it's unlikely to happen in practice but doing something like percent encoding non-alphanumeric characters would avoid the problem entirely.

I'd rather see 'x' used instead of '-' (double-or-more dashes and leading dash in refnames may currently be allowed but double-or-more exes and leading ex would be much more likely to stay valid) if we just want to redact invalid characters.

I see there are "lets make sure it is unique by suffixing "-%d" in other codepaths; would that help if this piece of code yields a label that is not unique?

#11 lore raw

Re: [PATCH 1/1] rebase -r: let `label` generate safer labels

In reply to Phillip Wood

Hi Phillip,

Quoted 4 lines · Phillip Wood

This is definitely worth fixing, I've got a couple of comments below

Quoted 1 line · Matt R via GitGitGadget

From: Matt R <[email protected]>

I just noticed that the surname is abbreviated. The full name of the author is "Matt Rogers". (Matt, Git uses the Signed-off-by: lines as some sort of legally-binding assurance that you are free to submit these changes under the GPLv2, so your full name is kinda required.)

Quoted 9 lines
Quoted 7 lines

The label todo command in interactive rebases creates temporary refs in the refs/rewritten/ namespace. These refs are stored as loose refs, i.e. as files in .git/refs/rewritten/, therefore they have to conform with file name limitations on the current filesystem.

This poses a problem in particular on NTFS/FAT, where e.g. the colon character is not a valid part of a file name.

Being picking I'll point out that ':' is not a valid in refs either.

True, but that was not the primary concern here ;-)

Quoted 5 lines

Looking at
https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file I think only " and | are not allowed on NTFS/FAT but are valid in refs (see the man page for git check-ref-format for all the details). So the main limitation is actually what git allows in refs.

Right. And this example shows that we really need to be a bit more conservative than just disallowing characters that would not be allowed in refs.

I think it is more important to keep in mind the vagaries of various current and future filesystems to justify the change in this patch.

Quoted 34 lines
Quoted 34 lines

Let's safeguard against this by replacing not only white-space characters by dashes, but all non-alpha-numeric ones.

However, we exempt non-ASCII UTF-8 characters from that, as it should be quite possible to reflect branch names such as ↯↯↯ in refs/file names.

---
  sequencer.c              | 12 +++++++++++-
  t/t3430-rebase-merges.sh |  5 +++++
  2 files changed, 16 insertions(+), 1 deletion(-)
sequencer.c +6 -0
index 34ebf8ed94..23f4a0876a 100644
@@ -4635,8 +4635,18 @@ static int make_script_with_merges(struct
 pretty_print_context *pp,
    else
     strbuf_addbuf(&label, &oneline);
  +		/*
+		 * Sanitize labels by replacing non-alpha-numeric characters
+		 * (including white-space ones) by dashes, as they might be
+		 * illegal in file names (and hence in ref names).
+		 *
+		 * Note that we retain non-ASCII UTF-8 characters (identified
+		 * via the most significant bit). They should be all

acceptable

sequencer.c +1 -0
+		 * in file names. We do not validate the UTF-8 here, that's

not

sequencer.c +2 -0
+		 * the job of this function.
+		 */
  		for (p1 = label.buf; *p1; p1++)
Quoted 8 lines

sequencer.c static int make_script_with_merges(struct pretty_print_context *pp, · line 4650

Quoted 3 lines
sequencer.c +1 -1
-			if (isspace(*p1))
+			if (!(*p1 & 0x80) && !isalnum(*p1))
      *(char *)p1 = '-';

I'm sightly concerned that this opens the possibility for unexpected effects if two different labels get sanitized to the same string. I suspect it's unlikely to happen in practice but doing something like percent encoding non-alphanumeric characters would avoid the problem entirely.

Oh, but we make sure that the labels are unique, via the label_oid() function! Otherwise, we would not be able to label more than one merge parent ;-)

Ciao,
Dscho

#4 lore raw

Re: [PATCH 1/1] rebase -r: let `label` generate safer labels

In reply to Junio C Hamano
Quoted 9 lines · Junio C Hamano
Quoted 6 lines · Phillip Wood

Being picking I'll point out that ':' is not a valid in refs either. Looking at
https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file I think only " and | are not allowed on NTFS/FAT but are valid in refs (see the man page for git check-ref-format for all the details). So the main limitation is actually what git allows in refs.

Quoted 7 lines

Yeah, trying to use the contents of the log message without sufficient sanitization is looking for trouble.

Quoted 4 lines

sequencer.c static int make_script_with_merges(struct pretty_print_context *pp, · line 4650

Quoted 4 lines

for (p1 = label.buf; *p1; p1++)

sequencer.c +1 -1
-			if (isspace(*p1))
+			if (!(*p1 & 0x80) && !isalnum(*p1))
  				*(char *)p1 = '-';

While we're thinking of things that could go wrong, note that it's also possible for the commit message to contain non-UTF-8 characters (if the user is using a non-UTF-8 encoding), which will cause sadness on Windows and macOS. Non-Mac Unix systems aren't a problem here, but then again, they aren't the reason for this patch.

Quoted 9 lines
Quoted 5 lines

I'm sightly concerned that this opens the possibility for unexpected effects if two different labels get sanitized to the same string. I suspect it's unlikely to happen in practice but doing something like percent encoding non-alphanumeric characters would avoid the problem entirely.

I see there are "lets make sure it is unique by suffixing "-%d" in other codepaths; would that help if this piece of code yields a label that is not unique?

I was thinking the same thing. Since we're being much less lenient on what's allowed (which is fine), we're at increased risk for collision.

Signature · 2 lines
brian m. carlson: Houston, Texas, US
OpenPGP: https://keybase.io/bk2204
#5 lore raw

Re: [PATCH 1/1] rebase -r: let `label` generate safer labels

In reply to Junio C Hamano
Quoted 3 lines · Junio C Hamano

I see there are "lets make sure it is unique by suffixing "-%d" in other codepaths; would that help if this piece of code yields a label that is not unique?

maybe use a trailing 4 characters  of the oid to get a reasonably unique label?

Oh, just seen dscho's "we make sure that the labels are unique, via the label_oid() function!", maybe needs mentioning in the commit message if re-rolled.

Philip

#7 lore raw

Re: [PATCH 1/1] rebase -r: let `label` generate safer labels

In reply to a message not in the archive
Quoted 3 lines · Matt Rogers

I can redo the commit, I had thought that I had previously fixed the author but I guess I was mistaken.

As for issues with non utf-8 encodings, I don't know of any simple way to check for those except for restricting to asci alphanumeric characters

If I read the Wikipedia article [1] about the utf-8 design choices it is pretty reasonable and robust most of the time, though that maybe a part one way trapdoor.

Quoted 1 line

Also the code given doesn't resolve onelines that consist only of restricted file names (e.g. COM, NUL, etc. On windows)

It maybe that the rebase doc may need (if it happens) a short comment warning of that.

Also need to check if the label_oid() function actually makes the label distinct, hence prevents such labels from being used as such a restricted file name - i.e. does it include the oid element.

Ultimately the label could be tweaked to have say the 4char prefix to fool the Windows 'starts with' name detection - which assumes I understand how some of those bad filenames are detected...

Quoted 13 lines · Philip Oakley

On 02/09/2019 19:29, Junio C Hamano wrote:
> I see there are "lets make sure it is unique by suffixing "-%d" in > other codepaths; would that help if this piece of code yields a > label that is not unique?
maybe use a trailing 4 characters  of the oid to get a reasonably unique
label?

Quoted 8 lines

Oh, just seen dscho's "we make sure that the labels are unique, via the
label_oid() function!", maybe needs mentioning in the commit message
if re-rolled.

Philip

[1] https://en.wikipedia.org/wiki/UTF-8#Description

#8 lore raw

Re: [PATCH 1/1] rebase -r: let `label` generate safer labels

In reply to Junio C Hamano

Hi Junio,

Quoted 13 lines · Junio C Hamano
Quoted 10 lines · Phillip Wood

sequencer.c static int make_script_with_merges(struct pretty_print_context *pp, · line 4650

Quoted 4 lines

for (p1 = label.buf; *p1; p1++)

sequencer.c +1 -1
-			if (isspace(*p1))
+			if (!(*p1 & 0x80) && !isalnum(*p1))
  				*(char *)p1 = '-';

I'm sightly concerned that this opens the possibility for unexpected effects if two different labels get sanitized to the same string. I suspect it's unlikely to happen in practice but doing something like percent encoding non-alphanumeric characters would avoid the problem entirely.

Quoted 4 lines

I'd rather see 'x' used instead of '-' (double-or-more dashes and leading dash in refnames may currently be allowed but double-or-more exes and leading ex would be much more likely to stay valid) if we just want to redact invalid characters.

Hmm. Let's take a concrete example from the VFS for Git fork:

Merge pull request #160: trace2:gvfs:experiment Add experimental regions and data events to help diagnose checkout and reset perf problems

(Yes, we have some quite verbose merge commits, with very, very long onelines. Not a good practice, we stopped doing it, but it was well within what Git allows.)

And now use dashes to encode all white-space:

Merge-pull-request-#160:-trace2:gvfs:experiment-Add-experimental-regions-and-data-events-to-help-diagnose-checkout-and-reset-perf-problems

Pretty long, but looks okay. Of course, it does not work, because colons. So here is the label with Matt's patch:

Merge-pull-request--160--trace2-gvfs-experiment-Add-experimental-regions-and-data-events-to-help-diagnose-checkout-and-reset-perf-problems

And here is the label with your proposed xs.

	Mergexpullxrequestxx160xxtrace2xgvfsxexperimentxAddxexperimentalxregionsxandxdataxeventsxtoxhelpxdiagnosexcheckoutxandxresetxperfxproblems

I cannot speak for you, of course, but I can speak for myself: this is not only way too reminiscent of xoxoxothxbye, but it is also really, totally unreadable.

If you care deeply about double dashes and leading dashes, how about this instead?

		char *from, *to;

for (from = to = label.buf; *from; from++)

			if ((*from & 0x80) || isalnum(*from))
				*(to++) = *from;
			/* avoid leading dash and double-dashes */
			else if (to != label.buf && to[-1] != '-')
   				*(to++) = '-';
		strbuf_setlen(&label, to - label.buf);

That would result in

Merge-pull-request-160-trace2-gvfs-experiment-Add-experimental-regions-and-data-events-to-help-diagnose-checkout-and-reset-perf-problems

Quoted 3 lines

I see there are "lets make sure it is unique by suffixing "-%d" in other codepaths; would that help if this piece of code yields a label that is not unique?

I'm way ahead of you. The sequencer already goes out of its way to guarantee the uniqueness of the labels (it's part of the design, as applied in 1644c73c6d4 (rebase-helper --make-script: introduce a flag to rebase merges, 2018-04-25)).

The patch you are looking at in this thread is only concerned about the initial phase, label_oid() does a lot more. Not only does it make the label unique (case-insensitively!), it also prevents it from looking like a full 40-hex digit SHA-1, so that we can guarantee that unique abbreviations of commit hashes will work as labels, too.

Ciao,
Dscho

#12 lore raw

Re: [PATCH 1/1] rebase -r: let `label` generate safer labels

In reply to Johannes Schindelin
Quoted 8 lines · Johannes Schindelin
Quoted 4 lines

I'm sightly concerned that this opens the possibility for unexpected effects if two different labels get sanitized to the same string. I suspect it's unlikely to happen in practice but doing something like percent encoding non-alphanumeric characters would avoid the problem entirely.

Oh, but we make sure that the labels are unique, via the label_oid() function! Otherwise, we would not be able to label more than one merge parent ;-)

It somewhat feels suboptimal, from code followability's point of view, to have this "pre-sanitization" to replace isspace() to a dash, which is being extended to "all non-alnums", and the uniquefy of labels in label_oid(), in two separate places. I wonder if the resulting code becomes easier to follow and harder to introduce new bugs, if this part is made to just yield label.buf it obtained form the log message as-is and leave the munging to label_oid()?

#9 lore raw

Re: [PATCH 1/1] rebase -r: let `label` generate safer labels

In reply to Johannes Schindelin
Quoted 5 lines · Johannes Schindelin

If you care deeply about double dashes and leading dashes, how about this instead?

		char *from, *to;
Quoted 7 lines
   		for (from = to = label.buf; *from; from++)
			if ((*from & 0x80) || isalnum(*from))
				*(to++) = *from;
			/* avoid leading dash and double-dashes */
			else if (to != label.buf && to[-1] != '-')
   				*(to++) = '-';
		strbuf_setlen(&label, to - label.buf);

Simple enough and is a good change when judged locally.

It still would cause readers to wonder if label_oid() later append '-%d' to end up with double-dash near the end, etc., which made me wonder if the resulting code becomes better if sanitization and uniquefying are done at the same single place in the other message.

#10 lore raw

Re: [PATCH 1/1] rebase -r: let `label` generate safer labels

I agree that the code locally was simple enough.

Ultimately I feel that sanitizing and uniqueifying the label should probably be done closer together/at the same place. I'm just not familiar enough with the codebase to know a good place (if any) to move that to. Eventually though this would still need to be expanded further to protect against reserved filenames (e.g. NUL on windows). Although the behavior around these (espescially with file extensions like NUL.txt) become less reliable, and although they are much more unlikely to be encountered in practice, are still allowed by git as oneliners.

Quoted 22 lines · Junio C Hamano
Quoted 12 lines · Johannes Schindelin

If you care deeply about double dashes and leading dashes, how about this instead?

              char *from, *to;

              for (from = to = label.buf; *from; from++)
                      if ((*from & 0x80) || isalnum(*from))
                              *(to++) = *from;
                      /* avoid leading dash and double-dashes */
                      else if (to != label.buf && to[-1] != '-')
                              *(to++) = '-';
              strbuf_setlen(&label, to - label.buf);

Simple enough and is a good change when judged locally.

It still would cause readers to wonder if label_oid() later append '-%d' to end up with double-dash near the end, etc., which made me wonder if the resulting code becomes better if sanitization and uniquefying are done at the same single place in the other message.

Signature · 1 line
Matthew Rogers
#14 lore raw

v2 cover Make git rebase -r's label generation more resilient

In reply to Johannes Schindelin via GitGitGadget

Those labels must be valid ref names, and therefore valid file names. The initial patch came in via Git for Windows.

Change since v1:

* moved the entire sanitizing logic to label_oid(), as a preparatory step.

Johannes Schindelin (1):
rebase-merges: move labels' whitespace mangling into label_oid()

Matthew Rogers (1):
rebase -r: let label generate safer labels

 sequencer.c              | 72 +++++++++++++++++++++++++---------------
 t/t3430-rebase-merges.sh |  6 ++++
 2 files changed, 51 insertions(+), 27 deletions(-)

base-commit: d9f6f3b6195a0ca35642561e530798ad1469bd41
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-327%2Fdscho%2Ffix-rebase-r-labels-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-327/dscho/fix-rebase-r-labels-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/327

Range-diff vs v1:

 -:  ---------- > 1:  3f4d086e80 rebase-merges: move labels' whitespace mangling into `label_oid()`
 1:  4a02c38442 ! 2:  adc22c8ba2 rebase -r: let `label` generate safer labels

@@ -1,14 +1,15 @@
-Author: Matt R <[email protected]>
+Author: Matthew Rogers <[email protected]>

rebase -r: let label generate safer labels

The label todo command in interactive rebases creates temporary refs in the refs/rewritten/ namespace. These refs are stored as loose refs, i.e. as files in .git/refs/rewritten/, therefore they have to conform
- with file name limitations on the current filesystem.
+ with file name limitations on the current filesystem in addition to the
+ accepted ref format.

- This poses a problem in particular on NTFS/FAT, where e.g. the colon
- character is not a valid part of a file name.
+ This poses a problem in particular on NTFS/FAT, where e.g. the colon,
+ double-quote and pipe characters are disallowed as part of a file name.

Let's safeguard against this by replacing not only white-space characters by dashes, but all non-alpha-numeric ones.
@@ -23,8 +24,8 @@
--- a/sequencer.c
+++ b/sequencer.c
@@
- else
- strbuf_addbuf(&label, &oneline);
+ } else {
+ struct strbuf *buf = &state->buf;

+ /*
+ * Sanitize labels by replacing non-alpha-numeric characters
@@ -36,18 +37,26 @@
+ * in file names. We do not validate the UTF-8 here, that's not
+ * the job of this function.
+ */
- for (p1 = label.buf; *p1; p1++)
-- if (isspace(*p1))
-+ if (!(*p1 & 0x80) && !isalnum(*p1))
- *(char *)p1 = '-';
+ for (; *label; label++)
+- strbuf_addch(buf, isspace(*label) ? '-' : *label);
++ if ((*label & 0x80) || isalnum(*label))
++ strbuf_addch(buf, *label);
++ /* avoid leading dash and double-dashes */
++ else if (buf->len && buf->buf[buf->len - 1] != '-')
++ strbuf_addch(buf, '-');
++ if (!buf->len) {
++ strbuf_addstr(buf, "rev-");
++ strbuf_add_unique_abbrev(buf, oid, default_abbrev);
++ }
+ label = buf->buf;

- strbuf_reset(&buf);
+ if ((buf->len == the_hash_algo->hexsz &&

diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh --- a/t/t3430-rebase-merges.sh
+++ b/t/t3430-rebase-merges.sh
@@
- test_path_is_missing .git/MERGE_HEAD
+ test_cmp expect G.t
'

+test_expect_success '--rebase-merges with commit that can generate bad characters for filename' '
@@ -55,4 +64,5 @@
+ git merge -m "colon: this should work" G &&
+ git rebase --rebase-merges --force-rebase E
+'
++
test_done

Signature · 1 line
gitgitgadget
#16 lore raw

PATCH v2 2/2 rebase -r: let `label` generate safer labels

From: Matthew Rogers <[email protected]>

The label todo command in interactive rebases creates temporary refs in the refs/rewritten/ namespace. These refs are stored as loose refs, i.e. as files in .git/refs/rewritten/, therefore they have to conform with file name limitations on the current filesystem in addition to the accepted ref format.

This poses a problem in particular on NTFS/FAT, where e.g. the colon, double-quote and pipe characters are disallowed as part of a file name.

Let's safeguard against this by replacing not only white-space characters by dashes, but all non-alpha-numeric ones.

However, we exempt non-ASCII UTF-8 characters from that, as it should be quite possible to reflect branch names such as ↯↯↯ in refs/file names.

---
 sequencer.c              | 20 +++++++++++++++++++-
 t/t3430-rebase-merges.sh |  6 ++++++
 2 files changed, 25 insertions(+), 1 deletion(-)
sequencer.c +19 -1
index 85c66f489f..fece07b680 100644
@@ -4471,8 +4471,26 @@ static const char *label_oid(struct object_id *oid, const char *label,
 	} else {
 		struct strbuf *buf = &state->buf;
 
+		/*
+		 * Sanitize labels by replacing non-alpha-numeric characters
+		 * (including white-space ones) by dashes, as they might be
+		 * illegal in file names (and hence in ref names).
+		 *
+		 * Note that we retain non-ASCII UTF-8 characters (identified
+		 * via the most significant bit). They should be all acceptable
+		 * in file names. We do not validate the UTF-8 here, that's not
+		 * the job of this function.
+		 */
 		for (; *label; label++)
-			strbuf_addch(buf, isspace(*label) ? '-' : *label);
+			if ((*label & 0x80) || isalnum(*label))
+				strbuf_addch(buf, *label);
+			/* avoid leading dash and double-dashes */
+			else if (buf->len && buf->buf[buf->len - 1] != '-')
+				strbuf_addch(buf, '-');
+		if (!buf->len) {
+			strbuf_addstr(buf, "rev-");
+			strbuf_add_unique_abbrev(buf, oid, default_abbrev);
+		}
 		label = buf->buf;
 
 		if ((buf->len == the_hash_algo->hexsz &&
t/t3430-rebase-merges.sh +6 -0
index 9efcf4808a..f728aba995 100755
@@ -468,4 +468,10 @@ test_expect_success '--rebase-merges with strategies' '
 	test_cmp expect G.t
 '
 
+test_expect_success '--rebase-merges with commit that can generate bad characters for filename' '
+	git checkout -b colon-in-label E &&
+	git merge -m "colon: this should work" G &&
+	git rebase --rebase-merges --force-rebase E
+'
+
 test_done
Signature · 1 line
gitgitgadget
#15 lore raw

PATCH v2 1/2 rebase-merges: move labels' whitespace mangling into `label_oid()`

In reply to Johannes Schindelin via GitGitGadget

From: Johannes Schindelin <[email protected]>

One of the trickier aspects of the design of git rebase --rebase-merges is the way labels are generated for the initial todo
list: those labels are supposed to be intuitive and first and foremost unique.

To that end, label_oid() appends a unique suffix when necessary.

Those labels not only need to be unique, but they also need to be valid refs. To make sure of that, make_script_with_merges() replaces whitespace by dashes.

That would appear to be the wrong layer for that sanitizing step,
though: all callers of label_oid() should get that same benefit.

Even if it does not make a difference currently (the only called of label_oid() that passes a label that might need to be sanitized _is_ make_script_with_merges()), let's move the responsibility for sanitizing labels into the label_oid() function.

This commit is best viewed with -w because it unfortunately needs to change the indentation of a large block of code in label_oid().

---
 sequencer.c | 56 ++++++++++++++++++++++++++---------------------------
 1 file changed, 28 insertions(+), 28 deletions(-)
sequencer.c +28 -28
index 8952cfa89b..85c66f489f 100644
@@ -4423,7 +4423,6 @@ static const char *label_oid(struct object_id *oid, const char *label,
 	struct labels_entry *labels_entry;
 	struct string_entry *string_entry;
 	struct object_id dummy;
-	size_t len;
 	int i;
 
 	string_entry = oidmap_get(&state->commit2label, oid);
@@ -4443,10 +4442,10 @@ static const char *label_oid(struct object_id *oid, const char *label,
 	 * abbreviation for any uninteresting commit's names that does not
 	 * clash with any other label.
 	 */
+	strbuf_reset(&state->buf);
 	if (!label) {
 		char *p;
 
-		strbuf_reset(&state->buf);
 		strbuf_grow(&state->buf, GIT_MAX_HEXSZ);
 		label = p = state->buf.buf;
 
@@ -4469,32 +4468,37 @@ static const char *label_oid(struct object_id *oid, const char *label,
 				p[i] = save;
 			}
 		}
-	} else if (((len = strlen(label)) == the_hash_algo->hexsz &&
-		    !get_oid_hex(label, &dummy)) ||
-		   (len == 1 && *label == '#') ||
-		   hashmap_get_from_hash(&state->labels,
-					 strihash(label), label)) {
-		/*
-		 * If the label already exists, or if the label is a valid full
-		 * OID, or the label is a '#' (which we use as a separator
-		 * between merge heads and oneline), we append a dash and a
-		 * number to make it unique.
-		 */
+	} else {
 		struct strbuf *buf = &state->buf;
 
-		strbuf_reset(buf);
-		strbuf_add(buf, label, len);
+		for (; *label; label++)
+			strbuf_addch(buf, isspace(*label) ? '-' : *label);
+		label = buf->buf;
 
-		for (i = 2; ; i++) {
-			strbuf_setlen(buf, len);
-			strbuf_addf(buf, "-%d", i);
-			if (!hashmap_get_from_hash(&state->labels,
-						   strihash(buf->buf),
-						   buf->buf))
-				break;
-		}
+		if ((buf->len == the_hash_algo->hexsz &&
+		     !get_oid_hex(label, &dummy)) ||
+		    (buf->len == 1 && *label == '#') ||
+		    hashmap_get_from_hash(&state->labels,
+					  strihash(label), label)) {
+			/*
+			 * If the label already exists, or if the label is a
+			 * valid full OID, or the label is a '#' (which we use
+			 * as a separator between merge heads and oneline), we
+			 * append a dash and a number to make it unique.
+			 */
+			size_t len = buf->len;
 
-		label = buf->buf;
+			for (i = 2; ; i++) {
+				strbuf_setlen(buf, len);
+				strbuf_addf(buf, "-%d", i);
+				if (!hashmap_get_from_hash(&state->labels,
+							   strihash(buf->buf),
+							   buf->buf))
+					break;
+			}
+
+			label = buf->buf;
+		}
 	}
 
 	FLEX_ALLOC_STR(labels_entry, label, label);
@@ -4596,10 +4600,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 		else
 			strbuf_addbuf(&label, &oneline);
 
-		for (p1 = label.buf; *p1; p1++)
-			if (isspace(*p1))
-				*(char *)p1 = '-';
-
 		strbuf_reset(&buf);
 		strbuf_addf(&buf, "%s -C %s",
 			    cmd_merge, oid_to_hex(&commit->object.oid));
Signature · 1 line
gitgitgadget
#17 lore raw
In reply to Johannes Schindelin via GitGitGadget

"Johannes Schindelin via GitGitGadget" <[email protected]>

Quoted 10 lines · writes:

Those labels must be valid ref names, and therefore valid file names. The initial patch came in via Git for Windows.

Change since v1:

* moved the entire sanitizing logic to label_oid(), as a preparatory step.

Johannes Schindelin (1):
rebase-merges: move labels' whitespace mangling into label_oid()

Quoted 6 lines

Matthew Rogers (1):
rebase -r: let label generate safer labels

 sequencer.c              | 72 +++++++++++++++++++++++++---------------
 t/t3430-rebase-merges.sh |  6 ++++
 2 files changed, 51 insertions(+), 27 deletions(-)

I think Dscho meant to Cc you as these two patches are meant to be a more complete solution to supersede your [*1*].

[Reference]

*1* <860dee65f49ea7eacf5a0c7c8ffe59095a51b1ce.1573205699.git.congdanhqx@gmail.com>

#18 lore raw

PATCH 1/1 sequencer: handle rebase-merge for "onto" message

Quoted 16 lines · Junio C Hamano

"Johannes Schindelin via GitGitGadget" <[email protected]>

Quoted 13 lines · writes:

Those labels must be valid ref names, and therefore valid file names. The initial patch came in via Git for Windows.

Change since v1:

* moved the entire sanitizing logic to label_oid(), as a preparatory step.

Johannes Schindelin (1):
rebase-merges: move labels' whitespace mangling into label_oid()

Matthew Rogers (1):
rebase -r: let label generate safer labels

Quoted 6 lines
Quoted 3 lines
 sequencer.c              | 72 +++++++++++++++++++++++++---------------
 t/t3430-rebase-merges.sh |  6 ++++
 2 files changed, 51 insertions(+), 27 deletions(-)

I think Dscho meant to Cc you as these two patches are meant to be a more complete solution to supersede your [*1*].

I've applied their patches over my branches,
my introduced test_expect_failure was flipped.
That's nice.

Anyway, when reading their patch, I discovered a problem with git rebase --rebase-merges when its message is onto.

Here is a patch to fix it.

I applied Dscho's patches over my dd/sequencer-utf8 then writing this patch, in case you have problem applying it.

The context for the diff is coming from Dscho's patches.

-------8<--------------------
From 48205889b404b82baa4b30c2eedd52243c349e3e Mon Sep 17 00:00:00 2001
From: Doan Tran Cong Danh <[email protected]>
Date: Mon, 18 Nov 2019 18:02:05 +0700
Subject: [PATCH] sequencer: handle rebase-merge for "onto" message

In order to work correctly, git-rebase --rebase-merges needs to make initial todo list with unique labels.

Those unique labels is being handled by employing a hashmap and suffixing an unique number if any duplicate is found.

But we forgat that beside of those labels for side branches, we also make a special label `onto' for our so-called new-base.

In a special case that any of those labels for side branches named `onto', git will run into trouble.

Correct it.

---
 sequencer.c              |  5 +++++
 t/t3430-rebase-merges.sh | 21 +++++++++++++++++++++
 2 files changed, 26 insertions(+)
sequencer.c +5 -0
index 350045b1b4..fc81e43f0f 100644
@@ -4569,10 +4569,15 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 	strbuf_init(&state.buf, 32);
 
 	if (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {
+		struct labels_entry *onto_label_entry;
 		struct object_id *oid = &revs->cmdline.rev[0].item->oid;
 		FLEX_ALLOC_STR(entry, string, "onto");
 		oidcpy(&entry->entry.oid, oid);
 		oidmap_put(&state.commit2label, entry);
+
+		FLEX_ALLOC_STR(onto_label_entry, label, "onto");
+		hashmap_entry_init(&onto_label_entry->entry, strihash("onto"));
+		hashmap_add(&state.labels, &onto_label_entry->entry);
 	}
 
 	/*
t/t3430-rebase-merges.sh +21 -0
index f728aba995..4e2c0ede51 100755
@@ -474,4 +474,25 @@ test_expect_success '--rebase-merges with commit that can generate bad character
 	git rebase --rebase-merges --force-rebase E
 '
 
+test_expect_success '--rebase-merges with message matched with onto label' '
+	git checkout -b onto-label E &&
+	git merge -m onto G &&
+	git rebase --rebase-merges --force-rebase E &&
+	test_cmp_graph <<-\EOF
+	*   onto
+	|\
+	| * G
+	| * F
+	* |   E
+	|\ \
+	| * | B
+	* | | D
+	| |/
+	|/|
+	* | C
+	|/
+	* A
+	EOF
+'
+
 test_done
Signature · 1 line
2.24.0.10.gc61c3b979f.dirty
#19 lore raw

PATCH v2 1/1 sequencer: handle rebase-merges for "onto" message

In order to work correctly, git-rebase --rebase-merges needs to make initial todo list with unique labels.

Those unique labels is being handled by employing a hashmap and appending an unique number if any duplicate is found.

But, we forget that beside those labels for side branches, we also have a special label `onto' for our so-called new-base.

In a special case that any of those labels for side branches named `onto', git will run into trouble.

Correct it.

---
Sorry for the noise, I forgot to check spelling for v1

And I forgot to delete From line when append to my MUA.

Range-diff against v1:
1: 48205889b4 ! 1: 9246beacf2 sequencer: handle rebase-merge for "onto" message
@@ Metadata
Author: Doan Tran Cong Danh <[email protected]>

## Commit message ##
- sequencer: handle rebase-merge for "onto" message
+ sequencer: handle rebase-merges for "onto" message

In order to work correctly, git-rebase --rebase-merges needs to make initial todo list with unique labels.

Those unique labels is being handled by employing a hashmap and
- suffixing an unique number if any duplicate is found.
+ appending an unique number if any duplicate is found.

- But we forgat that beside of those labels for side branches,
- we also make a special label `onto' for our so-called new-base.
+ But, we forget that beside those labels for side branches,
+ we also have a special label `onto' for our so-called new-base.

In a special case that any of those labels for side branches named `onto', git will run into trouble.

 sequencer.c              |  5 +++++
 t/t3430-rebase-merges.sh | 21 +++++++++++++++++++++
 2 files changed, 26 insertions(+)
sequencer.c +5 -0
index 350045b1b4..fc81e43f0f 100644
@@ -4569,10 +4569,15 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 	strbuf_init(&state.buf, 32);
 
 	if (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {
+		struct labels_entry *onto_label_entry;
 		struct object_id *oid = &revs->cmdline.rev[0].item->oid;
 		FLEX_ALLOC_STR(entry, string, "onto");
 		oidcpy(&entry->entry.oid, oid);
 		oidmap_put(&state.commit2label, entry);
+
+		FLEX_ALLOC_STR(onto_label_entry, label, "onto");
+		hashmap_entry_init(&onto_label_entry->entry, strihash("onto"));
+		hashmap_add(&state.labels, &onto_label_entry->entry);
 	}
 
 	/*
t/t3430-rebase-merges.sh +21 -0
index f728aba995..4e2c0ede51 100755
@@ -474,4 +474,25 @@ test_expect_success '--rebase-merges with commit that can generate bad character
 	git rebase --rebase-merges --force-rebase E
 '
 
+test_expect_success '--rebase-merges with message matched with onto label' '
+	git checkout -b onto-label E &&
+	git merge -m onto G &&
+	git rebase --rebase-merges --force-rebase E &&
+	test_cmp_graph <<-\EOF
+	*   onto
+	|\
+	| * G
+	| * F
+	* |   E
+	|\ \
+	| * | B
+	* | | D
+	| |/
+	|/|
+	* | C
+	|/
+	* A
+	EOF
+'
+
 test_done
Signature · 1 line
2.24.0.10.gc61c3b979f.dirty
#20 lore raw

Re: [PATCH v2] sequencer: handle rebase-merges for "onto" message

Hi,

Quoted 9 lines · Doan Tran Cong Danh

In order to work correctly, git-rebase --rebase-merges needs to make initial todo list with unique labels.

Those unique labels is being handled by employing a hashmap and appending an unique number if any duplicate is found.

But, we forget that beside those labels for side branches, we also have a special label `onto' for our so-called new-base.

Quoted 7 lines

In a special case that any of those labels for side branches named `onto', git will run into trouble.

Correct it.

---

Looks obviously correct to me. ACK!

Thank you,
Dscho

Quoted 86 lines

Sorry for the noise, I forgot to check spelling for v1

And I forgot to delete From line when append to my MUA.

Range-diff against v1:
1: 48205889b4 ! 1: 9246beacf2 sequencer: handle rebase-merge for "onto" message
@@ Metadata
Author: Doan Tran Cong Danh <[email protected]>

## Commit message ##
- sequencer: handle rebase-merge for "onto" message
+ sequencer: handle rebase-merges for "onto" message

In order to work correctly, git-rebase --rebase-merges needs to make initial todo list with unique labels.

Those unique labels is being handled by employing a hashmap and
- suffixing an unique number if any duplicate is found.
+ appending an unique number if any duplicate is found.

- But we forgat that beside of those labels for side branches,
- we also make a special label `onto' for our so-called new-base.
+ But, we forget that beside those labels for side branches,
+ we also have a special label `onto' for our so-called new-base.

In a special case that any of those labels for side branches named `onto', git will run into trouble.

 sequencer.c              |  5 +++++
 t/t3430-rebase-merges.sh | 21 +++++++++++++++++++++
 2 files changed, 26 insertions(+)
sequencer.c +5 -0
index 350045b1b4..fc81e43f0f 100644
@@ -4569,10 +4569,15 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 	strbuf_init(&state.buf, 32);
 
 	if (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {
+		struct labels_entry *onto_label_entry;
 		struct object_id *oid = &revs->cmdline.rev[0].item->oid;
 		FLEX_ALLOC_STR(entry, string, "onto");
 		oidcpy(&entry->entry.oid, oid);
 		oidmap_put(&state.commit2label, entry);
+
+		FLEX_ALLOC_STR(onto_label_entry, label, "onto");
+		hashmap_entry_init(&onto_label_entry->entry, strihash("onto"));
+		hashmap_add(&state.labels, &onto_label_entry->entry);
 	}
 
 	/*
t/t3430-rebase-merges.sh +21 -1
index f728aba995..4e2c0ede51 100755
@@ -474,4 +474,25 @@ test_expect_success '--rebase-merges with commit that can generate bad character
 	git rebase --rebase-merges --force-rebase E
 '
 
+test_expect_success '--rebase-merges with message matched with onto label' '
+	git checkout -b onto-label E &&
+	git merge -m onto G &&
+	git rebase --rebase-merges --force-rebase E &&
+	test_cmp_graph <<-\EOF
+	*   onto
+	|\
+	| * G
+	| * F
+	* |   E
+	|\ \
+	| * | B
+	* | | D
+	| |/
+	|/|
+	* | C
+	|/
+	* A
+	EOF
+'
+
 test_done
--

2.24.0.10.gc61c3b979f.dirty

#13 lore raw

Re: [PATCH 1/1] rebase -r: let `label` generate safer labels

In reply to Junio C Hamano

Hi Junio,

Quoted 11 lines · Junio C Hamano
Quoted 8 lines · Johannes Schindelin
Quoted 4 lines

I'm sightly concerned that this opens the possibility for unexpected effects if two different labels get sanitized to the same string. I suspect it's unlikely to happen in practice but doing something like percent encoding non-alphanumeric characters would avoid the problem entirely.

Oh, but we make sure that the labels are unique, via the label_oid() function! Otherwise, we would not be able to label more than one merge parent ;-)

Quoted 7 lines

It somewhat feels suboptimal, from code followability's point of view, to have this "pre-sanitization" to replace isspace() to a dash, which is being extended to "all non-alnums", and the uniquefy of labels in label_oid(), in two separate places. I wonder if the resulting code becomes easier to follow and harder to introduce new bugs, if this part is made to just yield label.buf it obtained form the log message as-is and leave the munging to label_oid()?

While the patch looks somewhat bulky without -w (due to the indentation change), I agree that it is conceptually a good idea, therefore I made it so.

Ciao,
Dscho

#22 lore raw
In reply to Junio C Hamano

Hi Junio,

Quoted 16 lines · Junio C Hamano

"Johannes Schindelin via GitGitGadget" <[email protected]>

Quoted 13 lines · writes:

Those labels must be valid ref names, and therefore valid file names. The initial patch came in via Git for Windows.

Change since v1:

* moved the entire sanitizing logic to label_oid(), as a preparatory step.

Johannes Schindelin (1):
rebase-merges: move labels' whitespace mangling into label_oid()

Matthew Rogers (1):
rebase -r: let label generate safer labels

Quoted 6 lines
Quoted 3 lines
 sequencer.c              | 72 +++++++++++++++++++++++++---------------
 t/t3430-rebase-merges.sh |  6 ++++
 2 files changed, 51 insertions(+), 27 deletions(-)

I think Dscho meant to Cc you as these two patches are meant to be a more complete solution to supersede your [*1*].

Whoops, totally forgot. Thanks!
Dscho

Quoted 6 lines

[Reference]

*1* <860dee65f49ea7eacf5a0c7c8ffe59095a51b1ce.1573205699.git.congdanhqx@gmail.com>

#21 lore raw

Re: [PATCH] sequencer: handle rebase-merge for "onto" message

In reply to Danh Doan
Quoted 9 lines · Danh Doan

Anyway, when reading their patch, I discovered a problem with git rebase --rebase-merges when its message is onto.

Here is a patch to fix it.

I applied Dscho's patches over my dd/sequencer-utf8 then writing this patch, in case you have problem applying it.

The context for the diff is coming from Dscho's patches.

Thanks. While technically this is independent from the "safer rebase-merges labels" topic (specifically its preparation step), in the larger picture, this too is to ensure we do not use a wrong string as a label ;-), so I'll queue it on top of those two patches, just like how you developed.

Thanks.

Quoted 76 lines

-------8<--------------------
From 48205889b404b82baa4b30c2eedd52243c349e3e Mon Sep 17 00:00:00 2001
From: Doan Tran Cong Danh <[email protected]>
Date: Mon, 18 Nov 2019 18:02:05 +0700
Subject: [PATCH] sequencer: handle rebase-merge for "onto" message

In order to work correctly, git-rebase --rebase-merges needs to make initial todo list with unique labels.

Those unique labels is being handled by employing a hashmap and suffixing an unique number if any duplicate is found.

But we forgat that beside of those labels for side branches, we also make a special label `onto' for our so-called new-base.

In a special case that any of those labels for side branches named `onto', git will run into trouble.

Correct it.

---
 sequencer.c              |  5 +++++
 t/t3430-rebase-merges.sh | 21 +++++++++++++++++++++
 2 files changed, 26 insertions(+)
sequencer.c +5 -0
index 350045b1b4..fc81e43f0f 100644
@@ -4569,10 +4569,15 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 	strbuf_init(&state.buf, 32);
 
 	if (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {
+		struct labels_entry *onto_label_entry;
 		struct object_id *oid = &revs->cmdline.rev[0].item->oid;
 		FLEX_ALLOC_STR(entry, string, "onto");
 		oidcpy(&entry->entry.oid, oid);
 		oidmap_put(&state.commit2label, entry);
+
+		FLEX_ALLOC_STR(onto_label_entry, label, "onto");
+		hashmap_entry_init(&onto_label_entry->entry, strihash("onto"));
+		hashmap_add(&state.labels, &onto_label_entry->entry);
 	}
 
 	/*
t/t3430-rebase-merges.sh +21 -0
index f728aba995..4e2c0ede51 100755
@@ -474,4 +474,25 @@ test_expect_success '--rebase-merges with commit that can generate bad character
 	git rebase --rebase-merges --force-rebase E
 '
 
+test_expect_success '--rebase-merges with message matched with onto label' '
+	git checkout -b onto-label E &&
+	git merge -m onto G &&
+	git rebase --rebase-merges --force-rebase E &&
+	test_cmp_graph <<-\EOF
+	*   onto
+	|\
+	| * G
+	| * F
+	* |   E
+	|\ \
+	| * | B
+	* | | D
+	| |/
+	|/|
+	* | C
+	|/
+	* A
+	EOF
+'
+
 test_done