Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

libgit2 disagrees with git-for-windows on symlink target with a trailing slash

Abierto
#7,383 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
4/5
Tiempo estimado
3-5 días
Aptitud para principiantes
68/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
c

Línea de trabajo

Start with git_win32_path_readlink_w and follow its call into git_win32_path_remove_namespace and git_win32_path_trim_end in src/util/win32/path_w32.c; compare this with git_fs_path_mkposix in p_readlink. Reproduce the Windows symlink case described in the issue, then verify that index blobs, status, and diff preserve targetdir/ and agree with Git for Windows.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

Summary

On Windows, when a directory symlink’s target ends with a separator (targetdir\), Git for Windows stores targetdir/, while libgit2 stores targetdir — the trailing separator is dropped.

A repo written by libgit2/pygit2 then looks dirty under git diff / git status, and the reverse is also dirty.

Versions
  • OS: Windows 10, with NTFS symlink permission enabled (by enabling Windows Developer Mode)
  • libgit2 1.9.7 (via pygit2 1.20.1)
  • Git for Windows 2.55.0.windows.5
  • core.symlinks=true
Reproduction (pygit2)

Requires Windows Developer Mode (or equivalent) so real symlinks can be created.

> python.exe
Python 3.14.7 (tags/v3.14.7:823f032, Aug  5 2026, 10:51:32) [MSC v.1944 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> import pygit2
>>> pygit2.__version__
'1.20.1'
>>> pygit2.LIBGIT2_VERSION
'1.9.7'
>>> import os
>>> import subprocess
>>> import tempfile
>>> from pathlib import Path
>>>
>>> def setup(repo_dir):
...     Path(repo_dir, "targetdir").mkdir()
...     Path(repo_dir, "targetdir", "x.txt").write_bytes(b"x")
...     Path(repo_dir, "dirlink").symlink_to("targetdir" + os.sep, target_is_directory=True)
...
>>> td = tempfile.TemporaryDirectory()
>>> td.name
'C:\\Users\\X\\AppData\\Local\\Temp\\tmp4m27u5uk'
>>>
>>>
>>> libgit2_dir = Path(td.name) / "libgit2"
>>> libgit2_dir.mkdir()
>>> libgit2_repo = pygit2.init_repository(libgit2_dir)
>>> setup(libgit2_dir)
>>> os.readlink(libgit2_dir / "dirlink")
'targetdir\\'
>>> libgit2_repo.index.add_all()
>>> libgit2_repo.index.write()
>>> libgit2_repo[libgit2_repo.index["dirlink"].id].data
b'targetdir'
>>> list(libgit2_repo.diff())
[]
>>> subprocess.check_call(["git", "diff"], cwd=libgit2_dir)
diff --git a/dirlink b/dirlink
index 9cf79c6..4a6c2d5 120000
--- a/dirlink
+++ b/dirlink
@@ -1 +1 @@
-targetdir
\ No newline at end of file
+targetdir/
\ No newline at end of file
0
>>>
>>>
>>> git_dir = Path(td.name) / "git"
>>> git_dir.mkdir()
>>> subprocess.check_call(["git", "init"], cwd=git_dir)
Initialized empty Git repository in C:/Users/X/AppData/Local/Temp/tmp4m27u5uk/git/.git/
0
>>> setup(git_dir)
>>> os.readlink(git_dir / "dirlink")
'targetdir\\'
>>> subprocess.check_call(["git", "add", "--all"], cwd=git_dir)
0
>>> subprocess.check_output(["git", "cat-file", "-p", ":dirlink"], cwd=git_dir)
b'targetdir/'
>>> subprocess.check_call(["git", "diff"], cwd=git_dir)
0
>>> git_repo = pygit2.Repository(git_dir)
>>> git_repo[git_repo.index["dirlink"].id].data
b'targetdir/'
>>> for patch in git_repo.diff():
...     print(patch.text)
...
diff --git a/dirlink b/dirlink
index 4a6c2d5..9cf79c6 120000
--- a/dirlink
+++ b/dirlink
@@ -1 +1 @@
-targetdir/
\ No newline at end of file
+targetdir
\ No newline at end of file

>>>
Expected

Match Git for Windows: normalize \ to /, keep a trailing separator. Blob / status / diff should all see targetdir/.

Actual

libgit2 drops the trailing separator. git_index_add_* writes a different blob than git add. git_status / git_diff then disagree with git status / git diff on the same worktree.

Likely cause (This section is generated by AI and not verified by me)

Windows git_win32_path_readlink_w reads SubstituteName, then always runs it through git_win32_path_remove_namespace, which ends with git_win32_path_trim_end (src/util/win32/path_w32.c). That trim is correct for opening \\?\ paths (Win32 APIs that fail on a trailing \). It should not rewrite symlink target bytes that Git stores as a blob.

Git for Windows compat/mingw.c normalize_ntpath() strips NT prefixes and converts \/, and does not trim a trailing separator.

git_fs_path_mkposix in p_readlink only does the slash conversion; the missing / is from git_win32_path_trim_end.

When is the trailing slash useful?

On Windows, when extracting a tarball, the built-in tar (bsdtar 3.8.4) treats a trailing slash on the symlink target as the directory-link hint (even without the LIBARCHIVE.symlinktype= pax extension): extracting dirlink -> targetdir/ creates a <SYMLINKD> even if targetdir is missing, while dirlink -> targetdir creates a file <SYMLINK> even if targetdir already exists as a directory. A file symlink to a directory does not work on Windows. So a trailing slash helps tar on Windows create the right kind of symlink.

Lenguaje dominante
C
Estrellas
10.6k
Forks
2.7k
Métricas de merge de PR
Sin PR fusionados en 30 d

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de libgit2/libgit2

Todos los issues de libgit2/libgit2

Issues similares

Más issues de C

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.