diff --git a/Documentation/git-mktree.txt b/Documentation/git-mktree.txt
index db90fdcdc8f..a660438c67f 100644
--- a/Documentation/git-mktree.txt
+++ b/Documentation/git-mktree.txt
@@ -55,6 +55,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.
diff --git a/builtin/mktree.c b/builtin/mktree.c
index 00b77869a56..e94c9ca7e87 100644
--- a/builtin/mktree.c
+++ b/builtin/mktree.c
@@ -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 {
@@ -106,7 +106,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 */
@@ -122,7 +122,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] = '/';
 	}
 
@@ -208,7 +208,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);
@@ -272,6 +272,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 };
@@ -445,36 +448,39 @@ static int mktree_line(unsigned int mode, struct object_id *oid,
 		       const char *path, void *cbdata)
 {
 	struct mktree_line_data *data = cbdata;
-	enum object_type mode_type = object_type(mode);
-	struct object_info oi = OBJECT_INFO_INIT;
-	enum object_type parsed_obj_type;
 
-	if (obj_type && mode_type != obj_type)
-		die("object type (%s) doesn't match mode type (%s)",
-		    type_name(obj_type), type_name(mode_type));
+	if (mode) {
+		struct object_info oi = OBJECT_INFO_INIT;
+		enum object_type parsed_obj_type;
+		enum object_type mode_type = object_type(mode);
 
-	oi.typep = &parsed_obj_type;
+		if (obj_type && mode_type != obj_type)
+			die("object type (%s) doesn't match mode type (%s)",
+			    type_name(obj_type), type_name(mode_type));
 
-	if (oid_object_info_extended(the_repository, oid, &oi,
-				     OBJECT_INFO_LOOKUP_REPLACE |
-				     OBJECT_INFO_QUICK |
-				     OBJECT_INFO_SKIP_FETCH_OBJECT) < 0)
-		parsed_obj_type = -1;
+		oi.typep = &parsed_obj_type;
 
-	if (parsed_obj_type < 0) {
-		if (data->allow_missing || S_ISGITLINK(mode)) {
-			; /* no problem - missing objects & submodules are presumed to be of the right type */
-		} else {
-			die("entry '%s' object %s is unavailable", path, oid_to_hex(oid));
+		if (oid_object_info_extended(the_repository, oid, &oi,
+					     OBJECT_INFO_LOOKUP_REPLACE |
+					     OBJECT_INFO_QUICK |
+					     OBJECT_INFO_SKIP_FETCH_OBJECT) < 0)
+			parsed_obj_type = -1;
+
+		if (parsed_obj_type < 0) {
+			if (data->allow_missing || S_ISGITLINK(mode)) {
+				; /* no problem - missing objects & submodules are presumed to be of the right type */
+			} else {
+				die("entry '%s' object %s is unavailable", path, oid_to_hex(oid));
+			}
+		} else if (parsed_obj_type != mode_type) {
+			/*
+			* The object exists but is of the wrong type.
+			* This is a problem regardless of allow_missing
+			* because the new tree entry will never be correct.
+			*/
+			die("entry '%s' object %s is a %s but specified type was (%s)",
+			path, oid_to_hex(oid), type_name(parsed_obj_type), type_name(mode_type));
 		}
-	} else if (parsed_obj_type != mode_type) {
-		/*
-		 * The object exists but is of the wrong type.
-		 * This is a problem regardless of allow_missing
-		 * because the new tree entry will never be correct.
-		 */
-		die("entry '%s' object %s is a %s but specified type was (%s)",
-		    path, oid_to_hex(oid), type_name(parsed_obj_type), type_name(mode_type));
 	}
 
 	append_to_tree(mode, oid, path, data->arr, data->literally);
diff --git a/t/t1010-mktree.sh b/t/t1010-mktree.sh
index 1d6365141fc..7cb88e32d4f 100755
--- a/t/t1010-mktree.sh
+++ b/t/t1010-mktree.sh
@@ -373,4 +373,42 @@ test_expect_success 'mktree fails on directory-file conflict' '
 	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
