Skip to content

Commit e95d515

Browse files
peffgitster
authored andcommitted
apply: canonicalize modes read from patches
Git stores only canonical modes for blobs. So for a regular file, we care about only "100644" or "100755" (depending only on the executable bit), but never modes where the group or other permissions are more exotic. So never "100664", "100700", etc. When a file in the working tree has such a mode, we quietly turn it into one of the two canonical modes, and that's what is stored both in the index and in tree objects. However, we don't canonicalize modes we read from incoming patches in git-apply. These may appear in a few lines: - "old mode" / "new mode" lines for mode changes - "new file mode" lines for newly created files - "deleted file mode" for removing files For "new mode" and for "new file mode", this is harmless. The patch is asking the result to have a certain mode, but: - when we add an index entry (for --index or --cached), it is canonicalized as we create the entry, via create_ce_mode(). - for a working tree file, try_create_file() passes either 0777 or 0666 to open(), so what you get depends only on your umask, not any other bits (aside from the executable bit) in the original mode. However, for "old mode" and "deleted file mode", there is a minor annoyance. We compare the patch's expected preimage mode with the current state. But that current state is always going to be a canonical mode itself: - updating an index entry via --cached will have the canonical mode in the index - for updating a working tree file, check_preimage() runs the mode through ce_mode_from_stat(), which does the usual canonicalization So if the patch feeds a non-canonical mode, it's impossible for it to match, and we will always complain with something like: file has type 100644, expected 100664 Since this is just a warning, the operation proceeds, but it's confusing and annoying. These cases should be pretty rare in practice. Git would never produce a patch with non-canonical modes itself (since it doesn't store them). And while we do accept patches from other programs, all of those lines were invented by Git. So you'd need a program trying to be Git compatible, but not handling canonicalization the same way. Reportedly "quilt" is such a program. We should canonicalize the modes as we read them so that the user never sees the useless warning. A few notes on the tests: - I've covered instances of all lines for completeness, even though the "new mode" / "new file mode" ones behave OK currently. - the tests apply patches to both the index and working tree, and check the result of both. Again, we know that all of these paths canonicalize anyway, but it's giving us extra coverage (although we are even less likely to have such a bug now since we canonicalize up front). - the test patches are missing "index" lines, which is also something Git would never produce. But they don't matter for the test, they do match the case from quilt we saw in the wild, and they avoid some sha1/sha256 complexity. Reported-by: Andrew Morton <[email protected]> Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent c2b3f2b commit e95d515

File tree

2 files changed

+63
-0
lines changed

2 files changed

+63
-0
lines changed

apply.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -992,6 +992,7 @@ static int parse_mode_line(const char *line, int linenr, unsigned int *mode)
992992
*mode = strtoul(line, &end, 8);
993993
if (end == line || !isspace(*end))
994994
return error(_("invalid mode on line %d: %s"), linenr, line);
995+
*mode = canon_mode(*mode);
995996
return 0;
996997
}
997998

t/t4129-apply-samemode.sh

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,66 @@ test_expect_success 'git apply respects core.fileMode' '
130130
test_grep ! "has type 100644, expected 100755" err
131131
'
132132

133+
test_expect_success POSIXPERM 'patch mode for new file is canonicalized' '
134+
cat >patch <<-\EOF &&
135+
diff --git a/non-canon b/non-canon
136+
new file mode 100660
137+
--- /dev/null
138+
+++ b/non-canon
139+
+content
140+
EOF
141+
test_when_finished "git reset --hard" &&
142+
(
143+
umask 0 &&
144+
git apply --index patch 2>err
145+
) &&
146+
test_must_be_empty err &&
147+
git ls-files -s -- non-canon >staged &&
148+
test_grep "^100644" staged &&
149+
ls -l non-canon >worktree &&
150+
test_grep "^-rw-rw-rw" worktree
151+
'
152+
153+
test_expect_success POSIXPERM 'patch mode for deleted file is canonicalized' '
154+
test_when_finished "git reset --hard" &&
155+
echo content >non-canon &&
156+
git add non-canon &&
157+
chmod 666 non-canon &&
158+
159+
cat >patch <<-\EOF &&
160+
diff --git a/non-canon b/non-canon
161+
deleted file mode 100660
162+
--- a/non-canon
163+
+++ /dev/null
164+
@@ -1 +0,0 @@
165+
-content
166+
EOF
167+
git apply --index patch 2>err &&
168+
test_must_be_empty err &&
169+
git ls-files -- non-canon >staged &&
170+
test_must_be_empty staged &&
171+
test_path_is_missing non-canon
172+
'
173+
174+
test_expect_success POSIXPERM 'patch mode for mode change is canonicalized' '
175+
test_when_finished "git reset --hard" &&
176+
echo content >non-canon &&
177+
git add non-canon &&
178+
179+
cat >patch <<-\EOF &&
180+
diff --git a/non-canon b/non-canon
181+
old mode 100660
182+
new mode 100770
183+
EOF
184+
(
185+
umask 0 &&
186+
git apply --index patch 2>err
187+
) &&
188+
test_must_be_empty err &&
189+
git ls-files -s -- non-canon >staged &&
190+
test_grep "^100755" staged &&
191+
ls -l non-canon >worktree &&
192+
test_grep "^-rwxrwxrwx" worktree
193+
'
194+
133195
test_done

0 commit comments

Comments
 (0)