@@ -1025,3 +1025,152 @@ void git_attr_session__free(git_attr_session *session)
10251025
10261026 memset (session , 0 , sizeof (git_attr_session ));
10271027}
1028+
1029+
1030+ /**
1031+ * A negative ignore pattern can negate a positive one without
1032+ * wildcards if it is a basename only and equals the basename of
1033+ * the positive pattern. Thus
1034+ *
1035+ * foo/bar
1036+ * !bar
1037+ *
1038+ * would result in foo/bar being unignored again while
1039+ *
1040+ * moo/foo/bar
1041+ * !foo/bar
1042+ *
1043+ * would do nothing. The reverse also holds true: a positive
1044+ * basename pattern can be negated by unignoring the basename in
1045+ * subdirectories. Thus
1046+ *
1047+ * bar
1048+ * !foo/bar
1049+ *
1050+ * would result in foo/bar being unignored again. As with the
1051+ * first case,
1052+ *
1053+ * foo/bar
1054+ * !moo/foo/bar
1055+ *
1056+ * would do nothing, again.
1057+ */
1058+ static int does_negate_pattern (git_attr_fnmatch * rule , git_attr_fnmatch * neg )
1059+ {
1060+ int (* cmp )(const char * , const char * , size_t );
1061+ git_attr_fnmatch * longer , * shorter ;
1062+ char * p ;
1063+
1064+ if ((rule -> flags & GIT_ATTR_FNMATCH_NEGATIVE ) != 0
1065+ || (neg -> flags & GIT_ATTR_FNMATCH_NEGATIVE ) == 0 )
1066+ return false;
1067+
1068+ if (neg -> flags & GIT_ATTR_FNMATCH_ICASE )
1069+ cmp = git__strncasecmp ;
1070+ else
1071+ cmp = git__strncmp ;
1072+
1073+ /* If lengths match we need to have an exact match */
1074+ if (rule -> length == neg -> length ) {
1075+ return cmp (rule -> pattern , neg -> pattern , rule -> length ) == 0 ;
1076+ } else if (rule -> length < neg -> length ) {
1077+ shorter = rule ;
1078+ longer = neg ;
1079+ } else {
1080+ shorter = neg ;
1081+ longer = rule ;
1082+ }
1083+
1084+ /* Otherwise, we need to check if the shorter
1085+ * rule is a basename only (that is, it contains
1086+ * no path separator) and, if so, if it
1087+ * matches the tail of the longer rule */
1088+ p = longer -> pattern + longer -> length - shorter -> length ;
1089+
1090+ if (p [-1 ] != '/' )
1091+ return false;
1092+ if (memchr (shorter -> pattern , '/' , shorter -> length ) != NULL )
1093+ return false;
1094+
1095+ return cmp (p , shorter -> pattern , shorter -> length ) == 0 ;
1096+ }
1097+
1098+ /**
1099+ * A negative ignore can only unignore a file which is given explicitly before, thus
1100+ *
1101+ * foo
1102+ * !foo/bar
1103+ *
1104+ * does not unignore 'foo/bar' as it's not in the list. However
1105+ *
1106+ * foo/<star>
1107+ * !foo/bar
1108+ *
1109+ * does unignore 'foo/bar', as it is contained within the 'foo/<star>' rule.
1110+ */
1111+ int git_attr__does_negate_rule (int * out , git_vector * rules , git_attr_fnmatch * match )
1112+ {
1113+ int error = 0 , wildmatch_flags , effective_flags ;
1114+ size_t i ;
1115+ git_attr_fnmatch * rule ;
1116+ char * path ;
1117+ git_str buf = GIT_STR_INIT ;
1118+
1119+ * out = 0 ;
1120+
1121+ wildmatch_flags = WM_PATHNAME ;
1122+ if (match -> flags & GIT_ATTR_FNMATCH_ICASE )
1123+ wildmatch_flags |= WM_CASEFOLD ;
1124+
1125+ /* path of the file relative to the workdir, so we match the rules in subdirs */
1126+ if (match -> containing_dir ) {
1127+ git_str_puts (& buf , match -> containing_dir );
1128+ }
1129+ if (git_str_puts (& buf , match -> pattern ) < 0 )
1130+ return -1 ;
1131+
1132+ path = git_str_detach (& buf );
1133+
1134+ git_vector_foreach (rules , i , rule ) {
1135+ if (!(rule -> flags & GIT_ATTR_FNMATCH_HASWILD )) {
1136+ if (does_negate_pattern (rule , match )) {
1137+ error = 0 ;
1138+ * out = 1 ;
1139+ goto out ;
1140+ }
1141+ else
1142+ continue ;
1143+ }
1144+
1145+ git_str_clear (& buf );
1146+ if (rule -> containing_dir )
1147+ git_str_puts (& buf , rule -> containing_dir );
1148+ git_str_puts (& buf , rule -> pattern );
1149+
1150+ if (git_str_oom (& buf ))
1151+ goto out ;
1152+
1153+ /*
1154+ * if rule isn't for full path we match without PATHNAME flag
1155+ * as lines like *.txt should match something like dir/test.txt
1156+ * requiring * to also match /
1157+ */
1158+ effective_flags = wildmatch_flags ;
1159+ if (!(rule -> flags & GIT_ATTR_FNMATCH_FULLPATH ))
1160+ effective_flags &= ~WM_PATHNAME ;
1161+
1162+ /* if we found a match, we want to keep this rule */
1163+ if ((wildmatch (git_str_cstr (& buf ), path , effective_flags )) == WM_MATCH ) {
1164+ * out = 1 ;
1165+ error = 0 ;
1166+ goto out ;
1167+ }
1168+ }
1169+
1170+ error = 0 ;
1171+
1172+ out :
1173+ git__free (path );
1174+ git_str_dispose (& buf );
1175+ return error ;
1176+ }
0 commit comments