]> git.scripts.mit.edu Git - git.git/commitdiff
utf8: add is_hfs_dotgit() helper
authorJeff King <peff@peff.net>
Mon, 15 Dec 2014 22:56:59 +0000 (17:56 -0500)
committerJunio C Hamano <gitster@pobox.com>
Wed, 17 Dec 2014 19:04:39 +0000 (11:04 -0800)
We do not allow paths with a ".git" component to be added to
the index, as that would mean repository contents could
overwrite our repository files. However, asking "is this
path the same as .git" is not as simple as strcmp() on some
filesystems.

HFS+'s case-folding does more than just fold uppercase into
lowercase (which we already handle with strcasecmp). It may
also skip past certain "ignored" Unicode code points, so
that (for example) ".gi\u200ct" is mapped ot ".git".

The full list of folds can be found in the tables at:

  https://www.opensource.apple.com/source/xnu/xnu-1504.15.3/bsd/hfs/hfscommon/Unicode/UCStringCompareData.h

Implementing a full "is this path the same as that path"
comparison would require us importing the whole set of
tables.  However, what we want to do is much simpler: we
only care about checking ".git". We know that 'G' is the
only thing that folds to 'g', and so on, so we really only
need to deal with the set of ignored code points, which is
much smaller.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
utf8.c
utf8.h

diff --git a/utf8.c b/utf8.c
index 0d20e0acb2b6fb2dd1d63abed676a036b2b7ec2f..2c6442cc112dc065fd49d0374af14c6966acb78b 100644 (file)
--- a/utf8.c
+++ b/utf8.c
@@ -628,3 +628,67 @@ int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding)
 
        return chrlen;
 }
+
+/*
+ * Pick the next char from the stream, folding as an HFS+ filename comparison
+ * would. Note that this is _not_ complete by any means. It's just enough
+ * to make is_hfs_dotgit() work, and should not be used otherwise.
+ */
+static ucs_char_t next_hfs_char(const char **in)
+{
+       while (1) {
+               ucs_char_t out = pick_one_utf8_char(in, NULL);
+               /*
+                * check for malformed utf8. Technically this
+                * gets converted to a percent-sequence, but
+                * returning 0 is good enough for is_hfs_dotgit
+                * to realize it cannot be .git
+                */
+               if (!*in)
+                       return 0;
+
+               /* these code points are ignored completely */
+               switch (out) {
+               case 0x200c: /* ZERO WIDTH NON-JOINER */
+               case 0x200d: /* ZERO WIDTH JOINER */
+               case 0x200e: /* LEFT-TO-RIGHT MARK */
+               case 0x200f: /* RIGHT-TO-LEFT MARK */
+               case 0x202a: /* LEFT-TO-RIGHT EMBEDDING */
+               case 0x202b: /* RIGHT-TO-LEFT EMBEDDING */
+               case 0x202c: /* POP DIRECTIONAL FORMATTING */
+               case 0x202d: /* LEFT-TO-RIGHT OVERRIDE */
+               case 0x202e: /* RIGHT-TO-LEFT OVERRIDE */
+               case 0x206a: /* INHIBIT SYMMETRIC SWAPPING */
+               case 0x206b: /* ACTIVATE SYMMETRIC SWAPPING */
+               case 0x206c: /* INHIBIT ARABIC FORM SHAPING */
+               case 0x206d: /* ACTIVATE ARABIC FORM SHAPING */
+               case 0x206e: /* NATIONAL DIGIT SHAPES */
+               case 0x206f: /* NOMINAL DIGIT SHAPES */
+               case 0xfeff: /* ZERO WIDTH NO-BREAK SPACE */
+                       continue;
+               }
+
+               /*
+                * there's a great deal of other case-folding that occurs,
+                * but this is enough to catch anything that will convert
+                * to ".git"
+                */
+               return tolower(out);
+       }
+}
+
+int is_hfs_dotgit(const char *path)
+{
+       ucs_char_t c;
+
+       if (next_hfs_char(&path) != '.' ||
+           next_hfs_char(&path) != 'g' ||
+           next_hfs_char(&path) != 'i' ||
+           next_hfs_char(&path) != 't')
+               return 0;
+       c = next_hfs_char(&path);
+       if (c && !is_dir_sep(c))
+               return 0;
+
+       return 1;
+}
diff --git a/utf8.h b/utf8.h
index 65d0e42b96b5a55ecf7362c58d486b9693bcfc46..e4d9183c5fec81f8dbc75d1f2aea136f9b397a72 100644 (file)
--- a/utf8.h
+++ b/utf8.h
@@ -42,4 +42,12 @@ static inline char *reencode_string(const char *in,
 
 int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding);
 
+/*
+ * Returns true if the the path would match ".git" after HFS case-folding.
+ * The path should be NUL-terminated, but we will match variants of both ".git\0"
+ * and ".git/..." (but _not_ ".../.git"). This makes it suitable for both fsck
+ * and verify_path().
+ */
+int is_hfs_dotgit(const char *path);
+
 #endif