@@ -63,6 +63,10 @@ entries nested within one or more directories. These entries are inserted
into the appropriate tree in the base tree-ish if one exists. Otherwise,
empty parent trees are created to contain the entries.
+An entry with a mode of "0" will remove an entry of the same name from the
+base tree-ish. If no tree-ish argument is given, or the entry does not exist
+in that tree, the entry is ignored.
+
The order of the tree entries is normalized by `mktree` so pre-sorting the
input by path is not required. Multiple entries provided with the same path
are deduplicated, with only the last one specified added to the tree.
@@ -32,7 +32,7 @@ struct tree_entry {
static inline size_t df_path_len(size_t pathlen, unsigned int mode)
{
- return S_ISDIR(mode) ? pathlen - 1 : pathlen;
+ return (S_ISDIR(mode) || !mode) ? pathlen - 1 : pathlen;
}
struct tree_entry_array {
@@ -108,7 +108,7 @@ static void append_to_tree(unsigned mode, struct object_id *oid, const char *pat
size_t len_to_copy = len;
/* Normalize and validate entry path */
- if (S_ISDIR(mode)) {
+ if (S_ISDIR(mode) || !mode) {
while(len_to_copy > 0 && is_dir_sep(path[len_to_copy - 1]))
len_to_copy--;
len = len_to_copy + 1; /* add space for trailing slash */
@@ -124,7 +124,7 @@ static void append_to_tree(unsigned mode, struct object_id *oid, const char *pat
arr->has_nested_entries = 1;
/* Add trailing slash to dir */
- if (S_ISDIR(mode))
+ if (S_ISDIR(mode) || !mode)
ent->name[len - 1] = '/';
}
@@ -209,7 +209,7 @@ static void sort_and_dedup_tree_entry_array(struct tree_entry_array *arr)
if (!skip_entry) {
arr->entries[arr->nr++] = curr;
- if (S_ISDIR(curr->mode))
+ if (S_ISDIR(curr->mode) || !curr->mode)
tree_entry_array_push(&parent_dir_ents, curr);
} else {
FREE_AND_NULL(curr);
@@ -270,6 +270,9 @@ static int build_index_from_tree(const struct object_id *oid,
static int add_tree_entry_to_index(struct build_index_data *data,
struct tree_entry *ent)
{
+ if (!ent->mode)
+ return 0;
+
if (ent->expand_dir) {
int ret = 0;
struct pathspec ps = { 0 };
@@ -450,6 +453,10 @@ static int mktree_line(unsigned int mode, struct object_id *oid,
if (stage)
die(_("path '%s' is unmerged"), path);
+ /* OID ignored for zero-mode entries; append unconditionally */
+ if (!mode)
+ goto append_entry;
+
if (obj_type != OBJ_ANY && mode_type != obj_type)
die("object type (%s) doesn't match mode type (%s)",
type_name(obj_type), type_name(mode_type));
@@ -484,6 +491,7 @@ static int mktree_line(unsigned int mode, struct object_id *oid,
}
}
+append_entry:
append_to_tree(mode, oid, path, data->arr, data->literally);
return 0;
}
@@ -369,4 +369,42 @@ test_expect_success 'mktree fails on directory-file conflict' '
test_grep "You have both folder/one and folder/one/deeper/deep" err
'
+test_expect_success 'mktree with remove entries' '
+ tree_oid="$(cat tree)" &&
+ blob_oid="$(git rev-parse $tree_oid:folder.txt)" &&
+
+ {
+ printf "100644 blob $blob_oid\ttest/deeper/deep.txt\n" &&
+ printf "100644 blob $blob_oid\ttest.txt\n" &&
+ printf "100644 blob $blob_oid\texample\n" &&
+ printf "100644 blob $blob_oid\texample.a/file\n" &&
+ printf "100644 blob $blob_oid\texample.txt\n" &&
+ printf "040000 tree $tree_oid\tfolder\n" &&
+ printf "0 $ZERO_OID\tfolder\n" &&
+ printf "0 $ZERO_OID\tmissing\n"
+ } | git mktree >tree.base &&
+
+ {
+ printf "0 $ZERO_OID\texample.txt\n" &&
+ printf "0 $ZERO_OID\ttest/deeper\n"
+ } | git mktree $(cat tree.base) >tree.actual &&
+
+ {
+ printf "100644 blob $blob_oid\texample\n" &&
+ printf "100644 blob $blob_oid\texample.a/file\n" &&
+ printf "100644 blob $blob_oid\ttest.txt\n"
+ } >expect &&
+ git ls-tree -r $(cat tree.actual) >actual &&
+
+ test_cmp expect actual
+'
+
+test_expect_success 'type and oid not checked if entry mode is 0' '
+ # type and oid do not match
+ printf "0 commit $EMPTY_TREE\tfolder.txt\n" |
+ git mktree >tree.actual &&
+
+ test "$(cat tree.actual)" = $EMPTY_TREE
+'
+
test_done