Merge branch 'nd/extended-sha1-relpath'

* nd/extended-sha1-relpath:
  get_sha1: teach ":$n:<path>" the same relative path logic
  get_sha1: support relative path ":path" syntax
  Make prefix_path() return char* without const

Conflicts:
	sha1_name.c
This commit is contained in:
Junio C Hamano 2010-12-16 12:51:05 -08:00
commit 620b89cd98
5 changed files with 127 additions and 4 deletions

View File

@ -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

View File

@ -428,7 +428,7 @@ extern const char **get_pathspec(const char *prefix, const char **pathspec);
extern void setup_work_tree(void); extern void setup_work_tree(void);
extern const char *setup_git_directory_gently(int *); extern const char *setup_git_directory_gently(int *);
extern const char *setup_git_directory(void); extern const char *setup_git_directory(void);
extern const char *prefix_path(const char *prefix, int len, const char *path); extern char *prefix_path(const char *prefix, int len, const char *path);
extern const char *prefix_filename(const char *prefix, int len, const char *path); extern const char *prefix_filename(const char *prefix, int len, const char *path);
extern int check_filename(const char *prefix, const char *name); extern int check_filename(const char *prefix, const char *name);
extern void verify_filename(const char *prefix, const char *name); extern void verify_filename(const char *prefix, const char *name);

View File

@ -4,7 +4,7 @@
static int inside_git_dir = -1; static int inside_git_dir = -1;
static int inside_work_tree = -1; static int inside_work_tree = -1;
const char *prefix_path(const char *prefix, int len, const char *path) char *prefix_path(const char *prefix, int len, const char *path)
{ {
const char *orig = path; const char *orig = path;
char *sanitized = xmalloc(len + strlen(path) + 1); char *sanitized = xmalloc(len + strlen(path) + 1);

View File

@ -1066,6 +1066,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)
@ -1080,13 +1097,15 @@ 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] == '/')
/* don't need mode for commit */ /* don't need mode for commit */
@ -1099,7 +1118,13 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
stage = name[1] - '0'; stage = name[1] - '0';
cp = name + 3; cp = name + 3;
} }
namelen = namelen - (cp - name); new_path = resolve_relative_path(cp);
if (!new_path) {
namelen = namelen - (cp - name);
} else {
cp = new_path;
namelen = strlen(cp);
}
strncpy(oc->path, cp, strncpy(oc->path, cp,
sizeof(oc->path)); sizeof(oc->path));
@ -1118,12 +1143,14 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
if (ce_stage(ce) == stage) { if (ce_stage(ce) == stage) {
hashcpy(sha1, ce->sha1); hashcpy(sha1, ce->sha1);
oc->mode = ce->ce_mode; oc->mode = ce->ce_mode;
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++) {
@ -1144,6 +1171,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,
@ -1155,6 +1187,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)

View File

@ -31,6 +31,67 @@ 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 &&
git rev-parse :0:./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 'correct relative file objects (5)' '
git rev-parse :subdir/file.txt >expected &&
(
cd subdir &&
git rev-parse :./file.txt >result &&
test_cmp ../expected result &&
git rev-parse :0:./file.txt >result &&
test_cmp ../expected result
)
'
test_expect_success 'correct relative file objects (6)' '
git rev-parse :file.txt >expected &&
(
cd subdir &&
git rev-parse :../file.txt >result &&
test_cmp ../expected result &&
git rev-parse :0:../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 +136,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