get_sha1: support relative path ":path" syntax

Currently :path and ref:path can be used to refer to a specific object
in index or ref respectively. "path" component is absolute path. This
patch allows "path" to be written as "./path" or "../path", which is
relative to user's original cwd.

This does not work in commands for which startup_info is NULL
(i.e. non-builtin ones, it seems none of them needs this anyway).

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Nguyễn Thái Ngọc Duy 2010-11-28 10:37:32 +07:00 коммит произвёл Junio C Hamano
Родитель edc54fb5d4
Коммит 979f792951
3 изменённых файлов: 100 добавлений и 3 удалений

Просмотреть файл

@ -121,6 +121,10 @@ the `$GIT_DIR/refs` directory or from the `$GIT_DIR/packed-refs` file.
':path' (with an empty part before the colon, e.g. `:README`) ':path' (with an empty part before the colon, e.g. `:README`)
is a special case of the syntax described next: content is a special case of the syntax described next: content
recorded in the index at the given path. recorded in the index at the given path.
A path starting with './' or '../' is relative to current working directory.
The given path will be converted to be relative to working tree's root directory.
This is most useful to address a blob or tree from a commit or tree that has
the same tree structure with the working tree.
* A colon, optionally followed by a stage number (0 to 3) and a * A colon, optionally followed by a stage number (0 to 3) and a
colon, followed by a path (e.g. `:0:README`); this names a blob object in the colon, followed by a path (e.g. `:0:README`); this names a blob object in the

Просмотреть файл

@ -1046,6 +1046,23 @@ int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode,
return ret; return ret;
} }
static char *resolve_relative_path(const char *rel)
{
if (prefixcmp(rel, "./") && prefixcmp(rel, "../"))
return NULL;
if (!startup_info)
die("BUG: startup_info struct is not initialized.");
if (!is_inside_work_tree())
die("relative path syntax can't be used outside working tree.");
/* die() inside prefix_path() if resolved path is outside worktree */
return prefix_path(startup_info->prefix,
startup_info->prefix ? strlen(startup_info->prefix) : 0,
rel);
}
int get_sha1_with_context_1(const char *name, unsigned char *sha1, int get_sha1_with_context_1(const char *name, unsigned char *sha1,
struct object_context *oc, struct object_context *oc,
int gently, const char *prefix) int gently, const char *prefix)
@ -1060,25 +1077,31 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
if (!ret) if (!ret)
return ret; return ret;
/* sha1:path --> object name of path in ent sha1 /* sha1:path --> object name of path in ent sha1
* :path -> object name of path in index * :path -> object name of absolute path in index
* :./path -> object name of path relative to cwd in index
* :[0-3]:path -> object name of path in index at stage * :[0-3]:path -> object name of path in index at stage
* :/foo -> recent commit matching foo * :/foo -> recent commit matching foo
*/ */
if (name[0] == ':') { if (name[0] == ':') {
int stage = 0; int stage = 0;
struct cache_entry *ce; struct cache_entry *ce;
char *new_path = NULL;
int pos; int pos;
if (namelen > 2 && name[1] == '/') if (namelen > 2 && name[1] == '/')
return get_sha1_oneline(name + 2, sha1); return get_sha1_oneline(name + 2, sha1);
if (namelen < 3 || if (namelen < 3 ||
name[2] != ':' || name[2] != ':' ||
name[1] < '0' || '3' < name[1]) name[1] < '0' || '3' < name[1]) {
cp = name + 1; cp = name + 1;
new_path = resolve_relative_path(cp);
if (new_path)
cp = new_path;
}
else { else {
stage = name[1] - '0'; stage = name[1] - '0';
cp = name + 3; cp = name + 3;
} }
namelen = namelen - (cp - name); namelen = strlen(cp);
strncpy(oc->path, cp, strncpy(oc->path, cp,
sizeof(oc->path)); sizeof(oc->path));
@ -1096,12 +1119,14 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
break; break;
if (ce_stage(ce) == stage) { if (ce_stage(ce) == stage) {
hashcpy(sha1, ce->sha1); hashcpy(sha1, ce->sha1);
free(new_path);
return 0; return 0;
} }
pos++; pos++;
} }
if (!gently) if (!gently)
diagnose_invalid_index_path(stage, prefix, cp); diagnose_invalid_index_path(stage, prefix, cp);
free(new_path);
return -1; return -1;
} }
for (cp = name, bracket_depth = 0; *cp; cp++) { for (cp = name, bracket_depth = 0; *cp; cp++) {
@ -1122,6 +1147,11 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
} }
if (!get_sha1_1(name, cp-name, tree_sha1)) { if (!get_sha1_1(name, cp-name, tree_sha1)) {
const char *filename = cp+1; const char *filename = cp+1;
char *new_filename = NULL;
new_filename = resolve_relative_path(filename);
if (new_filename)
filename = new_filename;
ret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode); ret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode);
if (!gently) { if (!gently) {
diagnose_invalid_sha1_path(prefix, filename, diagnose_invalid_sha1_path(prefix, filename,
@ -1133,6 +1163,7 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
sizeof(oc->path)); sizeof(oc->path));
oc->path[sizeof(oc->path)-1] = '\0'; oc->path[sizeof(oc->path)-1] = '\0';
free(new_filename);
return ret; return ret;
} else { } else {
if (!gently) if (!gently)

Просмотреть файл

@ -31,6 +31,43 @@ test_expect_success 'correct file objects' '
test $HASH_file = $(git rev-parse :0:file.txt) ) test $HASH_file = $(git rev-parse :0:file.txt) )
' '
test_expect_success 'correct relative file objects (0)' '
git rev-parse :file.txt >expected &&
git rev-parse :./file.txt >result &&
test_cmp expected result
'
test_expect_success 'correct relative file objects (1)' '
git rev-parse HEAD:file.txt >expected &&
git rev-parse HEAD:./file.txt >result &&
test_cmp expected result
'
test_expect_success 'correct relative file objects (2)' '
(
cd subdir &&
git rev-parse HEAD:../file.txt >result &&
test_cmp ../expected result
)
'
test_expect_success 'correct relative file objects (3)' '
(
cd subdir &&
git rev-parse HEAD:../subdir/../file.txt >result &&
test_cmp ../expected result
)
'
test_expect_success 'correct relative file objects (4)' '
git rev-parse HEAD:subdir/file.txt >expected &&
(
cd subdir &&
git rev-parse HEAD:./file.txt >result &&
test_cmp ../expected result
)
'
test_expect_success 'incorrect revision id' ' test_expect_success 'incorrect revision id' '
test_must_fail git rev-parse foobar:file.txt 2>error && test_must_fail git rev-parse foobar:file.txt 2>error &&
grep "Invalid object name '"'"'foobar'"'"'." error && grep "Invalid object name '"'"'foobar'"'"'." error &&
@ -75,4 +112,29 @@ test_expect_success 'invalid @{n} reference' '
grep "fatal: Log for [^ ]* only has [0-9][0-9]* entries." error grep "fatal: Log for [^ ]* only has [0-9][0-9]* entries." error
' '
test_expect_success 'relative path not found' '
(
cd subdir &&
test_must_fail git rev-parse HEAD:./nonexistent.txt 2>error &&
grep subdir/nonexistent.txt error
)
'
test_expect_success 'relative path outside worktree' '
test_must_fail git rev-parse HEAD:../file.txt >output 2>error &&
test -z "$(cat output)" &&
grep "outside repository" error
'
test_expect_success 'relative path when cwd is outside worktree' '
test_must_fail git --git-dir=.git --work-tree=subdir rev-parse HEAD:./file.txt >output 2>error &&
test -z "$(cat output)" &&
grep "relative path syntax can.t be used outside working tree." error
'
test_expect_success 'relative path when startup_info is NULL' '
test_must_fail test-match-trees HEAD:./file.txt HEAD:./file.txt 2>error &&
grep "BUG: startup_info struct is not initialized." error
'
test_done test_done