@@ -1034,3 +1034,248 @@ int git_tree_walk(
10341034 return error ;
10351035}
10361036
1037+ static int compare_entries (const void * _a , const void * _b )
1038+ {
1039+ const git_tree_update * a = (git_tree_update * ) _a ;
1040+ const git_tree_update * b = (git_tree_update * ) _b ;
1041+
1042+ return strcmp (a -> path , b -> path );
1043+ }
1044+
1045+ static int on_dup_entry (void * * old , void * new )
1046+ {
1047+ GIT_UNUSED (old ); GIT_UNUSED (new );
1048+
1049+ giterr_set (GITERR_TREE , "duplicate entries given for update" );
1050+ return -1 ;
1051+ }
1052+
1053+ /*
1054+ * We keep the previous tree and the new one at each level of the
1055+ * stack. When we leave a level we're done with that tree and we can
1056+ * write it out to the odb.
1057+ */
1058+ typedef struct {
1059+ git_treebuilder * bld ;
1060+ git_tree * tree ;
1061+ char * name ;
1062+ } tree_stack_entry ;
1063+
1064+ /** Count how many slashes (i.e. path components) there are in this string */
1065+ GIT_INLINE (size_t ) count_slashes (const char * path )
1066+ {
1067+ size_t count = 0 ;
1068+ const char * slash ;
1069+
1070+ while ((slash = strchr (path , '/' )) != NULL ) {
1071+ count ++ ;
1072+ path = slash + 1 ;
1073+ }
1074+
1075+ return count ;
1076+ }
1077+
1078+ static bool next_component (git_buf * out , const char * in )
1079+ {
1080+ const char * slash = strchr (in , '/' );
1081+
1082+ git_buf_clear (out );
1083+
1084+ if (slash )
1085+ git_buf_put (out , in , slash - in );
1086+
1087+ return !!slash ;
1088+ }
1089+
1090+ static int create_popped_tree (tree_stack_entry * current , tree_stack_entry * popped , git_buf * component )
1091+ {
1092+ int error ;
1093+ git_oid new_tree ;
1094+
1095+ git_tree_free (popped -> tree );
1096+ error = git_treebuilder_write (& new_tree , popped -> bld );
1097+ git_treebuilder_free (popped -> bld );
1098+
1099+ if (error < 0 ) {
1100+ git__free (popped -> name );
1101+ return error ;
1102+ }
1103+
1104+ /* We've written out the tree, now we have to put the new value into its parent */
1105+ git_buf_clear (component );
1106+ git_buf_puts (component , popped -> name );
1107+ git__free (popped -> name );
1108+
1109+ GITERR_CHECK_ALLOC (component -> ptr );
1110+
1111+ /* Error out if this would create a D/F conflict in this update */
1112+ if (current -> tree ) {
1113+ const git_tree_entry * to_replace ;
1114+ to_replace = git_tree_entry_byname (current -> tree , component -> ptr );
1115+ if (to_replace && git_tree_entry_type (to_replace ) != GIT_OBJ_TREE ) {
1116+ giterr_set (GITERR_TREE , "D/F conflict when updating tree" );
1117+ return -1 ;
1118+ }
1119+ }
1120+
1121+ return git_treebuilder_insert (NULL , current -> bld , component -> ptr , & new_tree , GIT_FILEMODE_TREE );
1122+ }
1123+
1124+ int git_tree_create_updated (git_oid * out , git_repository * repo , git_tree * baseline , size_t nupdates , const git_tree_update * updates )
1125+ {
1126+ git_array_t (tree_stack_entry ) stack = GIT_ARRAY_INIT ;
1127+ tree_stack_entry * root_elem ;
1128+ git_vector entries ;
1129+ int error ;
1130+ size_t i ;
1131+ git_buf component = GIT_BUF_INIT ;
1132+
1133+ if ((error = git_vector_init (& entries , nupdates , compare_entries )) < 0 )
1134+ return error ;
1135+
1136+ /* Sort the entries for treversal */
1137+ for (i = 0 ; i < nupdates ; i ++ ) {
1138+ if ((error = git_vector_insert_sorted (& entries , (void * ) & updates [i ], on_dup_entry )) < 0 )
1139+ goto cleanup ;
1140+ }
1141+
1142+ root_elem = git_array_alloc (stack );
1143+ GITERR_CHECK_ALLOC (root_elem );
1144+ memset (root_elem , 0 , sizeof (* root_elem ));
1145+
1146+ if (baseline && (error = git_tree_dup (& root_elem -> tree , baseline )) < 0 )
1147+ goto cleanup ;
1148+
1149+ if ((error = git_treebuilder_new (& root_elem -> bld , repo , root_elem -> tree )) < 0 )
1150+ goto cleanup ;
1151+
1152+ for (i = 0 ; i < nupdates ; i ++ ) {
1153+ const git_tree_update * last_update = i == 0 ? NULL : & updates [i - 1 ];
1154+ const git_tree_update * update = & updates [i ];
1155+ size_t common_prefix = 0 , steps_up , j ;
1156+ const char * path ;
1157+
1158+ /* Figure out how much we need to change from the previous tree */
1159+ if (last_update )
1160+ common_prefix = git_path_common_dirlen (last_update -> path , update -> path );
1161+
1162+ /*
1163+ * The entries are sorted, so when we find we're no
1164+ * longer in the same directory, we need to abandon
1165+ * the old tree (steps up) and dive down to the next
1166+ * one.
1167+ */
1168+ steps_up = last_update == NULL ? 0 : count_slashes (& last_update -> path [common_prefix ]);
1169+
1170+ for (j = 0 ; j < steps_up ; j ++ ) {
1171+ tree_stack_entry * current , * popped = git_array_pop (stack );
1172+ assert (popped );
1173+
1174+ current = git_array_last (stack );
1175+ assert (current );
1176+
1177+ if ((error = create_popped_tree (current , popped , & component )) < 0 )
1178+ goto cleanup ;
1179+ }
1180+
1181+ /* Now that we've created the trees we popped from the stack, let's go back down */
1182+ path = & update -> path [common_prefix ];
1183+ while (next_component (& component , path )) {
1184+ tree_stack_entry * last , * new_entry ;
1185+ const git_tree_entry * entry ;
1186+
1187+ last = git_array_last (stack );
1188+ entry = last -> tree ? git_tree_entry_byname (last -> tree , component .ptr ) : NULL ;
1189+ if (entry && git_tree_entry_type (entry ) != GIT_OBJ_TREE ) {
1190+ giterr_set (GITERR_TREE , "D/F conflict when updating tree" );
1191+ error = -1 ;
1192+ goto cleanup ;
1193+ }
1194+
1195+ new_entry = git_array_alloc (stack );
1196+ GITERR_CHECK_ALLOC (new_entry );
1197+ memset (new_entry , 0 , sizeof (* new_entry ));
1198+
1199+ new_entry -> tree = NULL ;
1200+ if (entry && (error = git_tree_lookup (& new_entry -> tree , repo , git_tree_entry_id (entry ))) < 0 )
1201+ goto cleanup ;
1202+
1203+ if ((error = git_treebuilder_new (& new_entry -> bld , repo , new_entry -> tree )) < 0 )
1204+ goto cleanup ;
1205+
1206+ new_entry -> name = git__strdup (component .ptr );
1207+ GITERR_CHECK_ALLOC (new_entry -> name );
1208+
1209+ /* Get to the start of the next component */
1210+ path += component .size + 1 ;
1211+ }
1212+
1213+ /* After all that, we're finally at the place where we want to perform the update */
1214+ switch (update -> action ) {
1215+ case GIT_TREE_UPDATE_UPSERT :
1216+ {
1217+ /* Make sure we're replacing something of the same type */
1218+ tree_stack_entry * last = git_array_last (stack );
1219+ const char * basename = git_path_basename (update -> path );
1220+ const git_tree_entry * e = git_treebuilder_get (last -> bld , basename );
1221+ if (e && git_tree_entry_type (e ) != git_object__type_from_filemode (update -> filemode )) {
1222+ giterr_set (GITERR_TREE , "Cannot replace '%s' with '%s' at '%s'" ,
1223+ git_object_type2string (git_tree_entry_type (e )),
1224+ git_object_type2string (git_object__type_from_filemode (update -> filemode )),
1225+ update -> path );
1226+ return -1 ;
1227+ }
1228+
1229+ error = git_treebuilder_insert (NULL , last -> bld , basename , & update -> id , update -> filemode );
1230+ break ;
1231+ }
1232+ case GIT_TREE_UPDATE_REMOVE :
1233+ error = git_treebuilder_remove (git_array_last (stack )-> bld , update -> path );
1234+ break ;
1235+ default :
1236+ giterr_set (GITERR_TREE , "unkown action for update" );
1237+ error = -1 ;
1238+ goto cleanup ;
1239+ }
1240+
1241+ if (error < 0 )
1242+ goto cleanup ;
1243+ }
1244+
1245+ /* We're done, go up the stack again and write out the tree */
1246+ {
1247+ tree_stack_entry * current = NULL , * popped = NULL ;
1248+ while ((popped = git_array_pop (stack )) != NULL ) {
1249+ current = git_array_last (stack );
1250+ /* We've reached the top, current is the root tree */
1251+ if (!current )
1252+ break ;
1253+
1254+ if ((error = create_popped_tree (current , popped , & component )) < 0 )
1255+ goto cleanup ;
1256+ }
1257+
1258+ /* Write out the root tree */
1259+ git__free (popped -> name );
1260+ git_tree_free (popped -> tree );
1261+
1262+ error = git_treebuilder_write (out , popped -> bld );
1263+ git_treebuilder_free (popped -> bld );
1264+ if (error < 0 )
1265+ goto cleanup ;
1266+ }
1267+
1268+ cleanup :
1269+ {
1270+ tree_stack_entry * e ;
1271+ while ((e = git_array_pop (stack )) != NULL ) {
1272+ git_treebuilder_free (e -> bld );
1273+ git_tree_free (e -> tree );
1274+ git__free (e -> name );
1275+ }
1276+ }
1277+
1278+ git_array_clear (stack );
1279+ git_vector_free (& entries );
1280+ return error ;
1281+ }
0 commit comments