Merge branch 'jw/builtin-objectmode-attr'

The builtin_objectmode attribute is populated for each path
without adding anything in .gitattributes files, which would be
useful in magic pathspec, e.g., ":(attr:builtin_objectmode=100755)"
to limit to executables.

* jw/builtin-objectmode-attr:
  attr: add builtin objectmode values support
This commit is contained in:
Junio C Hamano 2024-01-12 16:09:55 -08:00
commit 3e8558438d
5 changed files with 210 additions and 3 deletions

View file

@ -100,6 +100,21 @@ for a path to `Unspecified` state. This can be done by listing
the name of the attribute prefixed with an exclamation point `!`.
RESERVED BUILTIN_* ATTRIBUTES
-----------------------------
builtin_* is a reserved namespace for builtin attribute values. Any
user defined attributes under this namespace will be ignored and
trigger a warning.
`builtin_objectmode`
~~~~~~~~~~~~~~~~~~~~
This attribute is for filtering files by their file bit modes (40000,
120000, 160000, 100755, 100644). e.g. ':(attr:builtin_objectmode=160000)'.
You may also check these values with `git check-attr builtin_objectmode -- <file>`.
If the object is not in the index `git check-attr --cached` will return unspecified.
EFFECTS
-------

95
attr.c
View file

@ -17,6 +17,7 @@
#include "utf8.h"
#include "quote.h"
#include "read-cache-ll.h"
#include "refs.h"
#include "revision.h"
#include "object-store-ll.h"
#include "setup.h"
@ -183,6 +184,15 @@ static void all_attrs_init(struct attr_hashmap *map, struct attr_check *check)
}
}
/*
* Atribute name cannot begin with "builtin_" which
* is a reserved namespace for built in attributes values.
*/
static int attr_name_reserved(const char *name)
{
return starts_with(name, "builtin_");
}
static int attr_name_valid(const char *name, size_t namelen)
{
/*
@ -315,7 +325,7 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,
cp++;
len--;
}
if (!attr_name_valid(cp, len)) {
if (!attr_name_valid(cp, len) || attr_name_reserved(cp)) {
report_invalid_attr(cp, len, src, lineno);
return NULL;
}
@ -379,7 +389,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
name += strlen(ATTRIBUTE_MACRO_PREFIX);
name += strspn(name, blank);
namelen = strcspn(name, blank);
if (!attr_name_valid(name, namelen)) {
if (!attr_name_valid(name, namelen) || attr_name_reserved(name)) {
report_invalid_attr(name, namelen, src, lineno);
goto fail_return;
}
@ -1240,6 +1250,85 @@ static struct object_id *default_attr_source(void)
return &attr_source;
}
static const char *interned_mode_string(unsigned int mode)
{
static struct {
unsigned int val;
char str[7];
} mode_string[] = {
{ .val = 0040000 },
{ .val = 0100644 },
{ .val = 0100755 },
{ .val = 0120000 },
{ .val = 0160000 },
};
int i;
for (i = 0; i < ARRAY_SIZE(mode_string); i++) {
if (mode_string[i].val != mode)
continue;
if (!*mode_string[i].str)
snprintf(mode_string[i].str, sizeof(mode_string[i].str),
"%06o", mode);
return mode_string[i].str;
}
BUG("Unsupported mode 0%o", mode);
}
static const char *builtin_object_mode_attr(struct index_state *istate, const char *path)
{
unsigned int mode;
if (direction == GIT_ATTR_CHECKIN) {
struct object_id oid;
struct stat st;
if (lstat(path, &st))
die_errno(_("unable to stat '%s'"), path);
mode = canon_mode(st.st_mode);
if (S_ISDIR(mode)) {
/*
*`path` is either a directory or it is a submodule,
* in which case it is already indexed as submodule
* or it does not exist in the index yet and we need to
* check if we can resolve to a ref.
*/
int pos = index_name_pos(istate, path, strlen(path));
if (pos >= 0) {
if (S_ISGITLINK(istate->cache[pos]->ce_mode))
mode = istate->cache[pos]->ce_mode;
} else if (resolve_gitlink_ref(path, "HEAD", &oid) == 0) {
mode = S_IFGITLINK;
}
}
} else {
/*
* For GIT_ATTR_CHECKOUT and GIT_ATTR_INDEX we only check
* for mode in the index.
*/
int pos = index_name_pos(istate, path, strlen(path));
if (pos >= 0)
mode = istate->cache[pos]->ce_mode;
else
return ATTR__UNSET;
}
return interned_mode_string(mode);
}
static const char *compute_builtin_attr(struct index_state *istate,
const char *path,
const struct git_attr *attr) {
static const struct git_attr *object_mode_attr;
if (!object_mode_attr)
object_mode_attr = git_attr("builtin_objectmode");
if (attr == object_mode_attr)
return builtin_object_mode_attr(istate, path);
return ATTR__UNSET;
}
void git_check_attr(struct index_state *istate,
const char *path,
struct attr_check *check)
@ -1253,7 +1342,7 @@ void git_check_attr(struct index_state *istate,
unsigned int n = check->items[i].attr->attr_nr;
const char *value = check->all_attrs[n].value;
if (value == ATTR__UNKNOWN)
value = ATTR__UNSET;
value = compute_builtin_attr(istate, path, check->all_attrs[n].attr);
check->items[i].value = value;
}
}

0
neue Normal file
View file

View file

@ -19,6 +19,20 @@ attr_check () {
test_must_be_empty err
}
attr_check_object_mode_basic () {
path="$1" &&
expect="$2" &&
check_opts="$3" &&
git check-attr $check_opts builtin_objectmode -- "$path" >actual 2>err &&
echo "$path: builtin_objectmode: $expect" >expect &&
test_cmp expect actual
}
attr_check_object_mode () {
attr_check_object_mode_basic "$@" &&
test_must_be_empty err
}
attr_check_quote () {
path="$1" quoted_path="$2" expect="$3" &&
@ -558,4 +572,66 @@ test_expect_success EXPENSIVE 'large attributes file ignored in index' '
test_cmp expect err
'
test_expect_success 'builtin object mode attributes work (dir and regular paths)' '
>normal &&
attr_check_object_mode normal 100644 &&
mkdir dir &&
attr_check_object_mode dir 040000
'
test_expect_success POSIXPERM 'builtin object mode attributes work (executable)' '
>exec &&
chmod +x exec &&
attr_check_object_mode exec 100755
'
test_expect_success SYMLINKS 'builtin object mode attributes work (symlinks)' '
ln -s to_sym sym &&
attr_check_object_mode sym 120000
'
test_expect_success 'native object mode attributes work with --cached' '
>normal &&
git add normal &&
empty_blob=$(git rev-parse :normal) &&
git update-index --index-info <<-EOF &&
100755 $empty_blob 0 exec
120000 $empty_blob 0 symlink
EOF
attr_check_object_mode normal 100644 --cached &&
attr_check_object_mode exec 100755 --cached &&
attr_check_object_mode symlink 120000 --cached
'
test_expect_success 'check object mode attributes work for submodules' '
mkdir sub &&
(
cd sub &&
git init &&
mv .git .real &&
echo "gitdir: .real" >.git &&
test_commit first
) &&
attr_check_object_mode sub 160000 &&
attr_check_object_mode sub unspecified --cached &&
git add sub &&
attr_check_object_mode sub 160000 --cached
'
test_expect_success 'we do not allow user defined builtin_* attributes' '
echo "foo* builtin_foo" >.gitattributes &&
git add .gitattributes 2>actual &&
echo "builtin_foo is not a valid attribute name: .gitattributes:1" >expect &&
test_cmp expect actual
'
test_expect_success 'user defined builtin_objectmode values are ignored' '
echo "foo* builtin_objectmode=12345" >.gitattributes &&
git add .gitattributes &&
>foo_1 &&
attr_check_object_mode_basic foo_1 100644 &&
echo "builtin_objectmode is not a valid attribute name: .gitattributes:1" >expect &&
test_cmp expect err
'
test_done

View file

@ -393,4 +393,31 @@ test_expect_success 'reading from .gitattributes in a subdirectory (3)' '
test_cmp expect actual
'
test_expect_success POSIXPERM 'pathspec with builtin_objectmode attr can be used' '
>mode_exec_file_1 &&
git status -s ":(attr:builtin_objectmode=100644)mode_exec_*" >actual &&
echo ?? mode_exec_file_1 >expect &&
test_cmp expect actual &&
git add mode_exec_file_1 &&
chmod +x mode_exec_file_1 &&
git status -s ":(attr:builtin_objectmode=100755)mode_exec_*" >actual &&
echo AM mode_exec_file_1 >expect &&
test_cmp expect actual
'
test_expect_success POSIXPERM 'builtin_objectmode attr can be excluded' '
>mode_1_regular &&
>mode_1_exec &&
chmod +x mode_1_exec &&
git status -s ":(exclude,attr:builtin_objectmode=100644)" "mode_1_*" >actual &&
echo ?? mode_1_exec >expect &&
test_cmp expect actual &&
git status -s ":(exclude,attr:builtin_objectmode=100755)" "mode_1_*" >actual &&
echo ?? mode_1_regular >expect &&
test_cmp expect actual
'
test_done