Skip to content

Working-tree materialization (clone/restore) drops the executable bit (100755 -> 0644), and status/diff don't detect it #470

Description

@genedna

Summary

Libra preserves file mode metadata correctly in its index/object layers, but working-tree materialization (libra clone, libra restore) writes regular files as 0644 regardless of the recorded mode, silently dropping the executable bit of 100755 entries. Additionally, libra status/diff do not treat the mode mismatch as a modification, so the corruption is invisible to libra itself.

Observed with libra 0.22.10 (Linux x86_64). Source at 0.22.12 was not re-tested.

Reproduction 1: libra clone from a git repository

$ git init -q -b main git-src && cd git-src
$ printf '#!/bin/sh\necho hi\n' > run.sh && chmod 755 run.sh
$ git add . && git -c user.name=t -c user.email=t@t commit -q -m init
$ stat -c '%a' run.sh
755
$ cd .. && libra clone git-src libra-clone
$ stat -c '%a' libra-clone/run.sh
644          # expected 755

Reproduction 2: libra-native addrestore cycle

$ libra init libra-own && cd libra-own
$ printf '#!/bin/sh\necho hi\n' > run.sh && chmod 755 run.sh
$ libra add run.sh && libra commit -m x
$ rm run.sh
$ libra restore run.sh      # "Updated 1 path(s) from the index"
$ stat -c '%a' run.sh
644                          # expected 755
$ libra ls-files --stage -- run.sh
100755 4163036efa65bd4a469e752267498f01ea36a55c 0	run.sh
$ libra status
On branch main               # clean — mode loss is NOT reported as a change

Analysis

  • The metadata layers are intact: libra ls-files --stage (and equivalently GIT_INDEX_FILE=.libra/index git ls-files --stage, since .libra/index is git-index-v2 compatible) still records 100755 after the round-trip. Only the step that writes blobs back to the working tree loses the bit — it appears to write files without applying the mode stored in the index/tree entry (e.g. plain create/truncate + write, defaulting to 0666 & ~umask).
  • libra status reports the working tree as clean while the index says 100755 and disk is 0644, so libra's status/diff path does not compare the mode component.

Real-world impact

  • Every working tree obtained via libra loses +x on executable scripts (scripts/*.sh, hooks, build wrappers), so direct execution (./scripts/check-licenses.sh) fails until the user manually re-runs chmod +x.
  • Git interop is polluted: running git status in such a tree (e.g. a repo also cloned normally with git) shows phantom old mode 100755 / new mode 100644 diffs for every executable file. We hit exactly this in https://github.com/gitmono-dev/megaui — 6 scripts/check-*.sh files appeared mode-modified against origin/main (content identical), and committing from that tree would have silently stripped the executable bits in the upstream repository.
  • Because libra status treats it as clean, nothing alerts the user.

Expected behavior

  1. When materializing a blob into the working tree (clone/checkout/restore/merge/stash-pop), apply the mode from the index/tree entry: 0755 for S_IFREG | 0755, 0644 for S_IFREG | 0644 (and preserve symlinks as-is).
  2. status/diff should treat a working-tree mode change vs. the index as a modification (parity with git), so the regression above is at least visible.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingvcs

Type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions