diff --git a/builtin-add.c b/builtin-add.c
index bf13aa3..02c6751 100644
--- a/builtin-add.c
+++ b/builtin-add.c
@@ -123,6 +123,7 @@ int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
 	init_revisions(&rev, prefix);
 	setup_revisions(0, NULL, &rev, NULL);
 	rev.prune_data = pathspec;
+	rev.glob_paths = 0; /* git-add has its own filename matching machinery */
 	rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
 	rev.diffopt.format_callback = update_callback;
 	data.flags = flags;
diff --git a/builtin-blame.c b/builtin-blame.c
index 9bced3b..237d1fe 100644
--- a/builtin-blame.c
+++ b/builtin-blame.c
@@ -343,7 +343,7 @@ static struct origin *find_origin(struct scoreboard *sb,
 	paths[0] = origin->path;
 	paths[1] = NULL;
 
-	diff_tree_setup_paths(paths, &diff_opts);
+	diff_tree_setup_paths(paths, &diff_opts, 0);
 	if (diff_setup_done(&diff_opts) < 0)
 		die("diff-setup");
 
@@ -417,7 +417,7 @@ static struct origin *find_rename(struct scoreboard *sb,
 	diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
 	diff_opts.single_follow = origin->path;
 	paths[0] = NULL;
-	diff_tree_setup_paths(paths, &diff_opts);
+	diff_tree_setup_paths(paths, &diff_opts, 0);
 	if (diff_setup_done(&diff_opts) < 0)
 		die("diff-setup");
 
@@ -1099,7 +1099,7 @@ static int find_copy_in_parent(struct scoreboard *sb,
 	diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
 
 	paths[0] = NULL;
-	diff_tree_setup_paths(paths, &diff_opts);
+	diff_tree_setup_paths(paths, &diff_opts, 0);
 	if (diff_setup_done(&diff_opts) < 0)
 		die("diff-setup");
 
@@ -2346,6 +2346,11 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
 parse_done:
 	argc = parse_options_end(&ctx);
 
+	if (revs.glob_paths) {
+		error("git blame does not support `--glob-paths'");
+		usage_with_options(blame_opt_usage, options);
+	}
+
 	if (!blame_move_score)
 		blame_move_score = BLAME_DEFAULT_MOVE_SCORE;
 	if (!blame_copy_score)
diff --git a/builtin-reset.c b/builtin-reset.c
index 2e5a886..6026b34 100644
--- a/builtin-reset.c
+++ b/builtin-reset.c
@@ -128,14 +128,15 @@ static void update_index_from_diff(struct diff_queue_struct *q,
 }
 
 static int read_from_tree(const char *prefix, const char **argv,
-		unsigned char *tree_sha1)
+		unsigned char *tree_sha1, int glob_paths)
 {
 	struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
 	int index_fd, index_was_discarded = 0;
 	struct diff_options opt;
 
 	memset(&opt, 0, sizeof(opt));
-	diff_tree_setup_paths(get_pathspec(prefix, (const char **)argv), &opt);
+	diff_tree_setup_paths(get_pathspec(prefix, (const char **)argv),
+			&opt, glob_paths);
 	opt.output_format = DIFF_FORMAT_CALLBACK;
 	opt.format_callback = update_index_from_diff;
 	opt.format_callback_data = &index_was_discarded;
@@ -171,6 +172,7 @@ static const char *reset_type_names[] = { "mixed", "soft", "hard", NULL };
 int cmd_reset(int argc, const char **argv, const char *prefix)
 {
 	int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;
+	int glob_paths = 0;
 	const char *rev = "HEAD";
 	unsigned char sha1[20], *orig = NULL, sha1_orig[20],
 				*old_orig = NULL, sha1_old_orig[20];
@@ -182,6 +184,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT),
 		OPT_SET_INT(0, "hard", &reset_type,
 				"reset HEAD, index and working tree", HARD),
+		OPT_BOOLEAN(0, "glob-paths", &glob_paths,
+				"match paths with fnmatch"),
 		OPT_BOOLEAN('q', NULL, &quiet,
 				"disable showing new HEAD in hard reset and progress message"),
 		OPT_END()
@@ -246,7 +250,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 		else if (reset_type != NONE)
 			die("Cannot do %s reset with paths.",
 					reset_type_names[reset_type]);
-		return read_from_tree(prefix, argv + i, sha1);
+		return read_from_tree(prefix, argv + i, sha1, glob_paths);
 	}
 	if (reset_type == NONE)
 		reset_type = MIXED; /* by default */
diff --git a/builtin-update-index.c b/builtin-update-index.c
index 38eb53c..28b09a3 100644
--- a/builtin-update-index.c
+++ b/builtin-update-index.c
@@ -23,6 +23,7 @@ static int allow_replace;
 static int info_only;
 static int force_remove;
 static int verbose;
+static int glob_paths;
 static int mark_valid_only;
 #define MARK_VALID 1
 #define UNMARK_VALID 2
@@ -534,7 +535,7 @@ static int do_reupdate(int ac, const char **av,
 		struct cache_entry *old = NULL;
 		int save_nr;
 
-		if (ce_stage(ce) || !ce_path_match(ce, pathspec))
+		if (ce_stage(ce) || !ce_path_match(ce, pathspec, glob_paths))
 			continue;
 		if (has_head)
 			old = read_one_ent(NULL, head_sha1,
@@ -659,6 +660,10 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 				force_remove = 1;
 				continue;
 			}
+			if (!strcmp(path, "--glob-paths")) {
+				glob_paths = 1;
+				continue;
+			}
 			if (!strcmp(path, "-z")) {
 				line_termination = 0;
 				continue;
@@ -702,6 +707,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 				usage(update_index_usage);
 			die("unknown option %s", path);
 		}
+		if (glob_paths)
+			die("--glob-paths without -g");
 		p = prefix_path(prefix, prefix_length, path);
 		update_one(p, NULL, 0);
 		if (set_executable_bit)
@@ -712,6 +719,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 	if (read_from_stdin) {
 		struct strbuf buf, nbuf;
 
+		if (glob_paths)
+			die("--glob-paths without -g");
 		strbuf_init(&buf, 0);
 		strbuf_init(&nbuf, 0);
 		while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
diff --git a/cache.h b/cache.h
index a779d92..5560195 100644
--- a/cache.h
+++ b/cache.h
@@ -387,7 +387,8 @@ extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
 extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
 extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
 
-extern int ce_path_match(const struct cache_entry *ce, const char **pathspec);
+extern int ce_path_match(const struct cache_entry *ce, const char **pathspec,
+		int glob_paths);
 extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path);
 extern int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object);
 extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object);
diff --git a/diff-lib.c b/diff-lib.c
index e7eaff9..87925a2 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -77,7 +77,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
 			DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES))
 			break;
 
-		if (!ce_path_match(ce, revs->prune_data))
+		if (!ce_path_match(ce, revs->prune_data, revs->glob_paths))
 			continue;
 
 		if (ce_stage(ce)) {
@@ -431,7 +431,7 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o)
 	if (tree == o->df_conflict_entry)
 		tree = NULL;
 
-	if (ce_path_match(idx ? idx : tree, revs->prune_data))
+	if (ce_path_match(idx ? idx : tree, revs->prune_data, revs->glob_paths))
 		do_oneway_diff(o, idx, tree);
 
 	return 0;
@@ -508,6 +508,7 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
 
 	init_revisions(&revs, NULL);
 	revs.prune_data = opt->paths;
+	revs.glob_paths = opt->glob_paths;
 	tree = parse_tree_indirect(tree_sha1);
 	if (!tree)
 		die("bad tree object %s", sha1_to_hex(tree_sha1));
diff --git a/diff-no-index.c b/diff-no-index.c
index f6994cf..ec549a7 100644
--- a/diff-no-index.c
+++ b/diff-no-index.c
@@ -240,6 +240,7 @@ void diff_no_index(struct rev_info *revs,
 	}
 	else
 		revs->diffopt.paths = argv + argc - 2;
+	revs->diffopt.glob_paths = 0;
 	revs->diffopt.nr_paths = 2;
 
 	DIFF_OPT_SET(&revs->diffopt, EXIT_WITH_STATUS);
diff --git a/diff.h b/diff.h
index 50fb5dd..56f0857 100644
--- a/diff.h
+++ b/diff.h
@@ -102,6 +102,7 @@ struct diff_options {
 	FILE *file;
 	int close_file;
 
+	int glob_paths;
 	int nr_paths;
 	const char **paths;
 	int *pathlens;
@@ -128,7 +129,8 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix);
 
 extern const char mime_boundary_leader[];
 
-extern void diff_tree_setup_paths(const char **paths, struct diff_options *);
+extern void diff_tree_setup_paths(const char **paths, struct diff_options *opt,
+		int glob_paths);
 extern void diff_tree_release_paths(struct diff_options *);
 extern int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
 		     const char *base, struct diff_options *opt);
diff --git a/gitk-git/gitk b/gitk-git/gitk
index fddcb45..18c5cbc 100644
--- a/gitk-git/gitk
+++ b/gitk-git/gitk
@@ -1866,6 +1866,7 @@ proc makewindow {} {
     set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
 		[mc "containing:"] \
 		[mc "touching paths:"] \
+		[mc "touching paths (glob):"] \
 		[mc "adding/removing string:"]]
     trace add variable gdttype write gdttype_change
     pack .tf.lbar.gdttype -side left -fill y
@@ -3588,6 +3589,11 @@ proc do_file_hl {serial} {
 	set highlight_paths [makepatterns $paths]
 	highlight_filelist
 	set gdtargs [concat -- $paths]
+    } elseif {$gdttype eq [mc "touching paths (glob):"]} {
+	if {[catch {set paths [shellsplit $highlight_files]}]} return
+	set highlight_paths $paths
+	highlight_filelist
+	set gdtargs [concat --glob-paths -- $paths]
     } elseif {$gdttype eq [mc "adding/removing string:"]} {
 	set gdtargs [list "-S$highlight_files"]
     } else {
diff --git a/read-cache.c b/read-cache.c
index 1648428..c11ded9 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -582,7 +582,8 @@ int ce_same_name(struct cache_entry *a, struct cache_entry *b)
 	return ce_namelen(b) == len && !memcmp(a->name, b->name, len);
 }
 
-int ce_path_match(const struct cache_entry *ce, const char **pathspec)
+static int ce_path_match_standard(const struct cache_entry *ce,
+		const char **pathspec)
 {
 	const char *match, *name;
 	int len;
@@ -608,6 +609,31 @@ int ce_path_match(const struct cache_entry *ce, const char **pathspec)
 	return 0;
 }
 
+static int ce_path_match_globbed(const struct cache_entry *ce,
+		const char **pathspec)
+{
+	const char *match, *name;
+
+	if (!pathspec)
+		return 1;
+
+	name = ce->name;
+	while ((match = *pathspec++) != NULL) {
+		if (!fnmatch(match, name, 0))
+			return 1;
+	}
+	return 0;
+}
+
+int ce_path_match(const struct cache_entry *ce,
+		const char **pathspec, int glob_paths)
+{
+	if (glob_paths)
+		return ce_path_match_globbed(ce, pathspec);
+	else
+		return ce_path_match_standard(ce, pathspec);
+}
+
 /*
  * We fundamentally don't like some paths: we don't want
  * dot or dot-dot anywhere, and for obvious reasons don't
diff --git a/revision.c b/revision.c
index 3897fec..0dd1091 100644
--- a/revision.c
+++ b/revision.c
@@ -519,6 +519,7 @@ static void cherry_pick_list(struct commit_list *list, struct rev_info *revs)
 	if (revs->diffopt.nr_paths) {
 		ids.diffopts.nr_paths = revs->diffopt.nr_paths;
 		ids.diffopts.paths = revs->diffopt.paths;
+		ids.diffopts.glob_paths = revs->diffopt.glob_paths; /* CHECKME */
 		ids.diffopts.pathlens = revs->diffopt.pathlens;
 	}
 
@@ -826,7 +827,7 @@ static void prepare_show_merge(struct rev_info *revs)
 		struct cache_entry *ce = active_cache[i];
 		if (!ce_stage(ce))
 			continue;
-		if (ce_path_match(ce, revs->prune_data)) {
+		if (ce_path_match(ce, revs->prune_data, revs->glob_paths)) {
 			prune_num++;
 			prune = xrealloc(prune, sizeof(*prune) * prune_num);
 			prune[prune_num-2] = ce->name;
@@ -837,6 +838,7 @@ static void prepare_show_merge(struct rev_info *revs)
 			i++;
 	}
 	revs->prune_data = prune;
+	revs->glob_paths = 0;
 	revs->limited = 1;
 }
 
@@ -1033,6 +1035,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 		revs->min_age = approxidate(arg + 8);
 	} else if (!strcmp(arg, "--first-parent")) {
 		revs->first_parent_only = 1;
+	} else if (!strcmp(arg, "--glob-paths")) {
+		revs->glob_paths = 1;
 	} else if (!strcmp(arg, "-g") || !strcmp(arg, "--walk-reflogs")) {
 		init_reflog_walk(&revs->reflog_info);
 	} else if (!strcmp(arg, "--default")) {
@@ -1220,6 +1224,7 @@ void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx,
 int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def)
 {
 	int i, flags, left, seen_dashdash;
+	const char **paths = NULL;
 
 	/* First, search for "--" */
 	seen_dashdash = 0;
@@ -1230,7 +1235,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
 		argv[i] = NULL;
 		argc = i;
 		if (argv[i + 1])
-			revs->prune_data = get_pathspec(revs->prefix, argv + i + 1);
+			paths = argv + i + 1;
 		seen_dashdash = 1;
 		break;
 	}
@@ -1290,6 +1295,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
 			if (seen_dashdash || *arg == '^')
 				die("bad revision '%s'", arg);
 
+			if (revs->glob_paths)
+				die("--glob-paths without --");
+
 			/* If we didn't have a "--":
 			 * (1) all filenames must exist;
 			 * (2) all rev-args must not be interpretable
@@ -1301,10 +1309,19 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
 
 			revs->prune_data = get_pathspec(revs->prefix,
 							argv + i);
+			revs->glob_paths = 0;
 			break;
 		}
 	}
 
+	/* Third, handle paths listed after -- */
+	if (paths != NULL) {
+		if (revs->glob_paths)
+			revs->prune_data = paths;
+		else
+			revs->prune_data = get_pathspec(revs->prefix, paths);
+	}
+
 	if (revs->def == NULL)
 		revs->def = def;
 	if (revs->show_merge)
@@ -1333,12 +1350,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
 		revs->limited = 1;
 
 	if (revs->prune_data) {
-		diff_tree_setup_paths(revs->prune_data, &revs->pruning);
+		diff_tree_setup_paths(revs->prune_data, &revs->pruning,
+				revs->glob_paths);
 		/* Can't prune commits with rename following: the paths change.. */
 		if (!DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES))
 			revs->prune = 1;
 		if (!revs->full_diff)
-			diff_tree_setup_paths(revs->prune_data, &revs->diffopt);
+			diff_tree_setup_paths(revs->prune_data, &revs->diffopt,
+				revs->glob_paths);
 	}
 	if (revs->combine_merges) {
 		revs->ignore_merges = 0;
diff --git a/revision.h b/revision.h
index fa68c65..a68cdb8 100644
--- a/revision.h
+++ b/revision.h
@@ -32,6 +32,9 @@ struct rev_info {
 	void *prune_data;
 	unsigned int early_output;
 
+	/* whether prune_data contains fnmatch() patterns */
+	unsigned int	glob_paths:1;
+
 	/* Traversal flags */
 	unsigned int	dense:1,
 			prune:1,
diff --git a/tree-diff.c b/tree-diff.c
index bbb126f..0aa1e9b 100644
--- a/tree-diff.c
+++ b/tree-diff.c
@@ -82,6 +82,11 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
 	return 0;
 }
 
+static int tree_entry_interesting_globbed(struct tree_desc *, const char *,
+		int, struct diff_options *);
+static int tree_entry_interesting_standard(struct tree_desc *, const char *,
+		int, struct diff_options *);
+
 /*
  * Is a tree entry interesting given the pathspec we have?
  *
@@ -91,7 +96,19 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
  *  - zero for no
  *  - negative for "no, and no subsequent entries will be either"
  */
-static int tree_entry_interesting(struct tree_desc *desc, const char *base, int baselen, struct diff_options *opt)
+static int tree_entry_interesting(struct tree_desc *desc,
+		const char *base, int baselen, struct diff_options *opt)
+{
+	if (opt->glob_paths)
+		return tree_entry_interesting_globbed(desc, base,
+				baselen, opt);
+	else
+		return tree_entry_interesting_standard(desc, base,
+				baselen, opt);
+}
+
+static int tree_entry_interesting_standard(struct tree_desc *desc,
+		const char *base, int baselen, struct diff_options *opt)
 {
 	const char *path;
 	const unsigned char *sha1;
@@ -190,6 +207,41 @@ static int tree_entry_interesting(struct tree_desc *desc, const char *base, int
 	return never_interesting; /* No matches */
 }
 
+static int tree_entry_interesting_globbed(struct tree_desc *desc,
+		const char *base, int baselen, struct diff_options *opt)
+{
+	const char *path;
+	char *fullpath;
+	const unsigned char *sha1;
+	unsigned mode;
+	int i;
+	int pathlen;
+	int result;
+
+	if (!opt->nr_paths)
+		return 1;
+	sha1 = tree_entry_extract(desc, &path, &mode);
+	if (S_ISDIR(mode))
+		return 1;
+	pathlen = tree_entry_len(path, sha1);
+
+	fullpath = xmalloc(pathlen + baselen + 1);
+	memcpy(fullpath, base, baselen);
+	memcpy(fullpath + baselen, path, pathlen + 1);
+
+	result = 0;
+	for (i = 0; i < opt->nr_paths; i++) {
+		const char *match = opt->paths[i];
+		if (!fnmatch(match, fullpath, 0)) {
+			result = 1;
+			break;
+		}
+	}
+
+	free(fullpath);
+	return result;
+}
+
 /* A whole sub-tree went away or appeared */
 static void show_tree(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base, int baselen)
 {
@@ -338,7 +390,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
 	diff_opts.single_follow = opt->paths[0];
 	diff_opts.break_opt = opt->break_opt;
 	paths[0] = NULL;
-	diff_tree_setup_paths(paths, &diff_opts);
+	diff_tree_setup_paths(paths, &diff_opts, 0);
 	if (diff_setup_done(&diff_opts) < 0)
 		die("unable to set up diff options to follow renames");
 	diff_tree(t1, t2, base, &diff_opts);
@@ -362,7 +414,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
 			/* Update the path we use from now on.. */
 			diff_tree_release_paths(opt);
 			opt->paths[0] = xstrdup(p->one->path);
-			diff_tree_setup_paths(opt->paths, opt);
+			diff_tree_setup_paths(opt->paths, opt, 0);
 			break;
 		}
 	}
@@ -440,11 +492,13 @@ void diff_tree_release_paths(struct diff_options *opt)
 	free(opt->pathlens);
 }
 
-void diff_tree_setup_paths(const char **p, struct diff_options *opt)
+void diff_tree_setup_paths(const char **p, struct diff_options *opt,
+		int glob_paths)
 {
 	opt->nr_paths = 0;
 	opt->pathlens = NULL;
 	opt->paths = NULL;
+	opt->glob_paths = glob_paths;
 
 	if (p) {
 		int i;
