commit: allow partial commits with relative paths

In order to do partial commits, git-commit overlays a tree on the cache and checks pathspecs against the result. Currently, the overlaying is done using "prefix" which prevents relative pathspecs with ".." and absolute pathspec from matching when they refer to files not under "prefix" and absent from the index, but still in the tree (i.e. files staged for removal). The point of providing a prefix at all is performance optimization. If we say there is no common prefix for the files of interest, then we have to read the entire tree into the index. But even if we cannot use the working directory as a prefix, we can still figure out if there is a common prefix for all given paths, and use that instead. The pathspec_prefix() routine from ls-files.c does exactly that. Any use of global variables is removed from pathspec_prefix() so that it can be called from commit.c. Reported-by: Reuben Thomas <rrt@sc3d.org> Analyzed-by: Michael J Gruber <git@drmicha.warpmail.net> Signed-off-by: Clemens Buchacher <drizzd@aon.at> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Clemens Buchacher committed Jul 30, 2011 at 19:13 UTC 8894d5358095a08c2f700a87ce9fdefb0b6eb61b
4 files changed +39 -38
builtin/commit.c
+4 -2
@@ -256,8 +256,10 @@ static int list_paths(struct string_list *list, const char *with_tree,
256 ;
257 m = xcalloc(1, i);
258
259 - if (with_tree)
260 - overlay_tree_on_cache(with_tree, prefix);
259 + if (with_tree) {
260 + const char *max_prefix = pathspec_prefix(prefix, pattern);
261 + overlay_tree_on_cache(with_tree, max_prefix);
262 + }
263
264 for (i = 0; i < active_nr; i++) {
265 struct cache_entry *ce = active_cache[i];
builtin/ls-files.c
+2 -36
@@ -276,41 +276,6 @@ static void prune_cache(const char *prefix)
276 active_nr = last;
277 }
278
279 -static const char *pathspec_prefix(const char *prefix)
280 -{
281 - const char **p, *n, *prev;
282 - unsigned long max;
283 -
284 - if (!pathspec) {
285 - max_prefix_len = prefix ? strlen(prefix) : 0;
286 - return prefix;
287 - }
288 -
289 - prev = NULL;
290 - max = PATH_MAX;
291 - for (p = pathspec; (n = *p) != NULL; p++) {
292 - int i, len = 0;
293 - for (i = 0; i < max; i++) {
294 - char c = n[i];
295 - if (prev && prev[i] != c)
296 - break;
297 - if (!c || c == '*' || c == '?')
298 - break;
299 - if (c == '/')
300 - len = i+1;
301 - }
302 - prev = n;
303 - if (len < max) {
304 - max = len;
305 - if (!max)
306 - break;
307 - }
308 - }
309 -
310 - max_prefix_len = max;
311 - return max ? xmemdupz(prev, max) : NULL;
312 -}
313 -
279 static void strip_trailing_slash_from_submodules(void)
280 {
281 const char **p;
@@ -576,7 +541,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
541 strip_trailing_slash_from_submodules();
542
543 /* Find common prefix for all pathspec's */
579 - max_prefix = pathspec_prefix(prefix);
544 + max_prefix = pathspec_prefix(prefix, pathspec);
545 + max_prefix_len = max_prefix ? strlen(max_prefix) : 0;
546
547 /* Treat unmatching pathspec elements as errors */
548 if (pathspec && error_unmatch) {
cache.h
+1
@@ -426,6 +426,7 @@ extern void set_git_work_tree(const char *tree);
426 #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
427
428 extern const char **get_pathspec(const char *prefix, const char **pathspec);
429 +extern const char *pathspec_prefix(const char *prefix, const char **pathspec);
430 extern void setup_work_tree(void);
431 extern const char *setup_git_directory_gently(int *);
432 extern const char *setup_git_directory(void);
setup.c
+32
@@ -264,6 +264,38 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
264 return pathspec;
265 }
266
267 +const char *pathspec_prefix(const char *prefix, const char **pathspec)
268 +{
269 + const char **p, *n, *prev;
270 + unsigned long max;
271 +
272 + if (!pathspec)
273 + return prefix ? xmemdupz(prefix, strlen(prefix)) : NULL;
274 +
275 + prev = NULL;
276 + max = PATH_MAX;
277 + for (p = pathspec; (n = *p) != NULL; p++) {
278 + int i, len = 0;
279 + for (i = 0; i < max; i++) {
280 + char c = n[i];
281 + if (prev && prev[i] != c)
282 + break;
283 + if (!c || c == '*' || c == '?')
284 + break;
285 + if (c == '/')
286 + len = i+1;
287 + }
288 + prev = n;
289 + if (len < max) {
290 + max = len;
291 + if (!max)
292 + break;
293 + }
294 + }
295 +
296 + return max ? xmemdupz(prev, max) : NULL;
297 +}
298 +
299 /*
300 * Test if it looks like we're at a git directory.
301 * We want to see: