Transform invalid reserved pathname components

Fixes mnauw/git-remote-hg#58
This commit is contained in:
Mark Nauwelaerts
2025-05-03 17:13:06 +02:00
parent e1a9c3e91b
commit 9813797360
3 changed files with 99 additions and 2 deletions

View File

@@ -104,6 +104,20 @@ the invalid '~'
% git config --global remote-hg.ignore-name ~
--------------------------------------
Even though the "gitdir" is configurable (using `GIT_DIR`), git does not accept
certain pathname components, e.g. `.git` or `.gitmodules` (case-insensitive).
Problems arise if the hg repo contains such pathnames, and recent git versions
will reject this in a very hard way. So these pathnames are now mapped
from "hg space" to "git space" in a one-to-one way, where (e.g.)
`.git[0 or more suffix]` is mapped to `.git[1 or more suffix]` (obviously by
appending or removing a suffix). The "suffix" in question defaults to `_`,
but can be configured using
--------------------------------------
% git config --global remote-hg.dotfile-suffix _
--------------------------------------
NOTES
-----

View File

@@ -438,7 +438,7 @@ def export_file(ctx, fname):
puts(b"data %d" % len(d))
puts(f.data())
path = fix_file_path(f.path())
path = fixup_path_to_git(fix_file_path(f.path()))
return (gitmode(f.flags()), mark, path)
def get_filechanges(repo, ctx, parent):
@@ -518,6 +518,51 @@ def fixup_user(user):
return b'%s <%s>' % (name, mail)
# (recent) git fast-import does not accept .git or .gitmodule component names
# (anywhere, case-insensitive)
# in any case, surprising things may happen, so add some front-end replacement magic;
# transform of (hg) .git(0 or more suffix) to (git) .git(1 or more suffix)
# (likewise so for any invalid git keyword)
def fixup_dotfile_path(path, suffix, add):
def subst(part):
if (not part) or part[0] != ord(b'.'):
return part
for prefix in (b'.git', b'.gitmodules'):
pl = len(prefix)
tail = len(part) - pl
if tail < 0:
continue
if part[0:pl].lower() == prefix and part[pl:] == suffix * tail:
if add:
return part + suffix
elif tail == 0:
# .git should not occur in git space
# so complain
if pl == 3:
die('invalid path component %s' % part)
else:
# but .gitmodules might
# leave as-is, it is handled/ignored elsewhere
return part
else:
return part[0:-1]
return part
# quick optimization check;
if (not path) or (path[0] != ord(b'.') and path.find(b'/.') < 0):
return path
sep = b'/'
return sep.join((subst(part) for part in path.split(sep)))
def fixup_path_to_git(path):
if not dotfile_suffix:
return path
return fixup_dotfile_path(path, dotfile_suffix, True)
def fixup_path_from_git(path):
if not dotfile_suffix:
return path
return fixup_dotfile_path(path, dotfile_suffix, False)
def updatebookmarks(repo, peer):
remotemarks = peer.listkeys(b'bookmarks')
@@ -749,7 +794,7 @@ def export_ref(repo, name, kind, head):
puts(b"merge :%u" % (rev_to_mark(parents[1])))
for f in removed:
puts(b"D %s" % (fix_file_path(f)))
puts(b"D %s" % fixup_path_to_git(fix_file_path(f)))
for f in modified_final:
puts(b"M %s :%u %s" % f)
puts()
@@ -1042,6 +1087,7 @@ def parse_commit(parser):
else:
die(b'Unknown file command: %s' % line)
path = c_style_unescape(path)
path = fixup_path_from_git(path)
files[path] = files.get(path, {})
files[path].update(f)
@@ -1867,6 +1913,7 @@ def main(args):
global capability_push
global remove_username_quotes
global marksdir
global dotfile_suffix
marks = None
is_tmp = False
@@ -1886,6 +1933,7 @@ def main(args):
track_branches = get_config_bool('remote-hg.track-branches', True)
capability_push = get_config_bool('remote-hg.capability-push', True)
remove_username_quotes = get_config_bool('remote-hg.remove-username-quotes', True)
dotfile_suffix = get_config('remote-hg.dotfile-suffix').strip() or b'_'
force_push = False
if hg_git_compat:

View File

@@ -268,6 +268,41 @@ test_expect_success 'strip' '
test_cmp actual expected
'
test_expect_success 'dotfiles' '
test_when_finished "rm -rf hgrepo gitrepo" &&
(
hg init hgrepo &&
cd hgrepo &&
echo one >.git &&
echo ONE >.GIT &&
mkdir a && echo two > a/.gitmodules &&
hg add .git .GIT a/.gitmodules &&
hg commit -m zero
) &&
git clone "hg::hgrepo" gitrepo &&
test_cmp gitrepo/.git_ hgrepo/.git &&
test_cmp gitrepo/.GIT_ hgrepo/.GIT &&
test_cmp gitrepo/a/.gitmodules_ hgrepo/a/.gitmodules &&
(
cd gitrepo &&
echo three >.git_ &&
echo THREE >.GIT &&
echo four >a/.gitmodules_ &&
git add .git_ .GIT_ a/.gitmodules_ &&
git commit -m one &&
git push
) &&
hg -R hgrepo update &&
test_cmp gitrepo/.git_ hgrepo/.git &&
test_cmp gitrepo/.GIT_ hgrepo/.GIT &&
test_cmp gitrepo/a/.gitmodules_ hgrepo/a/.gitmodules
'
test_expect_success 'remote push with master bookmark' '
test_when_finished "rm -rf hgrepo gitrepo*" &&