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>
Nguyễn Thái Ngọc Duy committed
Nov 28, 2010 at 10:37 UTC
979f792951913d75f992f87022b75610303a614f
3 files changed
+100
-3
Documentation/revisions.txt
+4
@@ -121,6 +121,10 @@ the `$GIT_DIR/refs` directory or from the `$GIT_DIR/packed-refs` file.
121
':path' (with an empty part before the colon, e.g. `:README`)
122
is a special case of the syntax described next: content
123
recorded in the index at the given path.
124
+ A path starting with './' or '../' is relative to current working directory.
125
+ The given path will be converted to be relative to working tree's root directory.
126
+ This is most useful to address a blob or tree from a commit or tree that has
127
+ the same tree structure with the working tree.
128
129
* A colon, optionally followed by a stage number (0 to 3) and a
130
colon, followed by a path (e.g. `:0:README`); this names a blob object in the
sha1_name.c
+34
-3
@@ -1046,6 +1046,23 @@ int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode,
1046
return ret;
1047
}
1048
1049
+static char *resolve_relative_path(const char *rel)
1050
+{
1051
+ if (prefixcmp(rel, "./") && prefixcmp(rel, "../"))
1052
+ return NULL;
1053
+
1054
+ if (!startup_info)
1055
+ die("BUG: startup_info struct is not initialized.");
1056
+
1057
+ if (!is_inside_work_tree())
1058
+ die("relative path syntax can't be used outside working tree.");
1059
+
1060
+ /* die() inside prefix_path() if resolved path is outside worktree */
1061
+ return prefix_path(startup_info->prefix,
1062
+ startup_info->prefix ? strlen(startup_info->prefix) : 0,
1063
+ rel);
1064
+}
1065
+
1066
int get_sha1_with_context_1(const char *name, unsigned char *sha1,
1067
struct object_context *oc,
1068
int gently, const char *prefix)
@@ -1060,25 +1077,31 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
1077
if (!ret)
1078
return ret;
1079
/* sha1:path --> object name of path in ent sha1
1063
- * :path -> object name of path in index
1080
+ * :path -> object name of absolute path in index
1081
+ * :./path -> object name of path relative to cwd in index
1082
* :[0-3]:path -> object name of path in index at stage
1083
* :/foo -> recent commit matching foo
1084
*/
1085
if (name[0] == ':') {
1086
int stage = 0;
1087
struct cache_entry *ce;
1088
+ char *new_path = NULL;
1089
int pos;
1090
if (namelen > 2 && name[1] == '/')
1091
return get_sha1_oneline(name + 2, sha1);
1092
if (namelen < 3 ||
1093
name[2] != ':' ||
1075
- name[1] < '0' || '3' < name[1])
1094
+ name[1] < '0' || '3' < name[1]) {
1095
cp = name + 1;
1096
+ new_path = resolve_relative_path(cp);
1097
+ if (new_path)
1098
+ cp = new_path;
1099
+ }
1100
else {
1101
stage = name[1] - '0';
1102
cp = name + 3;
1103
}
1081
- namelen = namelen - (cp - name);
1104
+ namelen = strlen(cp);
1105
1106
strncpy(oc->path, cp,
1107
sizeof(oc->path));
@@ -1096,12 +1119,14 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
1119
break;
1120
if (ce_stage(ce) == stage) {
1121
hashcpy(sha1, ce->sha1);
1122
+ free(new_path);
1123
return 0;
1124
}
1125
pos++;
1126
}
1127
if (!gently)
1128
diagnose_invalid_index_path(stage, prefix, cp);
1129
+ free(new_path);
1130
return -1;
1131
}
1132
for (cp = name, bracket_depth = 0; *cp; cp++) {
@@ -1122,6 +1147,11 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
1147
}
1148
if (!get_sha1_1(name, cp-name, tree_sha1)) {
1149
const char *filename = cp+1;
1150
+ char *new_filename = NULL;
1151
+
1152
+ new_filename = resolve_relative_path(filename);
1153
+ if (new_filename)
1154
+ filename = new_filename;
1155
ret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode);
1156
if (!gently) {
1157
diagnose_invalid_sha1_path(prefix, filename,
@@ -1133,6 +1163,7 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
1163
sizeof(oc->path));
1164
oc->path[sizeof(oc->path)-1] = '\0';
1165
1166
+ free(new_filename);
1167
return ret;
1168
} else {
1169
if (!gently)
t/t1506-rev-parse-diagnosis.sh
+62
@@ -31,6 +31,43 @@ test_expect_success 'correct file objects' '
31
test $HASH_file = $(git rev-parse :0:file.txt) )
32
'
33
34
+test_expect_success 'correct relative file objects (0)' '
35
+ git rev-parse :file.txt >expected &&
36
+ git rev-parse :./file.txt >result &&
37
+ test_cmp expected result
38
+'
39
+
40
+test_expect_success 'correct relative file objects (1)' '
41
+ git rev-parse HEAD:file.txt >expected &&
42
+ git rev-parse HEAD:./file.txt >result &&
43
+ test_cmp expected result
44
+'
45
+
46
+test_expect_success 'correct relative file objects (2)' '
47
+ (
48
+ cd subdir &&
49
+ git rev-parse HEAD:../file.txt >result &&
50
+ test_cmp ../expected result
51
+ )
52
+'
53
+
54
+test_expect_success 'correct relative file objects (3)' '
55
+ (
56
+ cd subdir &&
57
+ git rev-parse HEAD:../subdir/../file.txt >result &&
58
+ test_cmp ../expected result
59
+ )
60
+'
61
+
62
+test_expect_success 'correct relative file objects (4)' '
63
+ git rev-parse HEAD:subdir/file.txt >expected &&
64
+ (
65
+ cd subdir &&
66
+ git rev-parse HEAD:./file.txt >result &&
67
+ test_cmp ../expected result
68
+ )
69
+'
70
+
71
test_expect_success 'incorrect revision id' '
72
test_must_fail git rev-parse foobar:file.txt 2>error &&
73
grep "Invalid object name '"'"'foobar'"'"'." error &&
@@ -75,4 +112,29 @@ test_expect_success 'invalid @{n} reference' '
112
grep "fatal: Log for [^ ]* only has [0-9][0-9]* entries." error
113
'
114
115
+test_expect_success 'relative path not found' '
116
+ (
117
+ cd subdir &&
118
+ test_must_fail git rev-parse HEAD:./nonexistent.txt 2>error &&
119
+ grep subdir/nonexistent.txt error
120
+ )
121
+'
122
+
123
+test_expect_success 'relative path outside worktree' '
124
+ test_must_fail git rev-parse HEAD:../file.txt >output 2>error &&
125
+ test -z "$(cat output)" &&
126
+ grep "outside repository" error
127
+'
128
+
129
+test_expect_success 'relative path when cwd is outside worktree' '
130
+ test_must_fail git --git-dir=.git --work-tree=subdir rev-parse HEAD:./file.txt >output 2>error &&
131
+ test -z "$(cat output)" &&
132
+ grep "relative path syntax can.t be used outside working tree." error
133
+'
134
+
135
+test_expect_success 'relative path when startup_info is NULL' '
136
+ test_must_fail test-match-trees HEAD:./file.txt HEAD:./file.txt 2>error &&
137
+ grep "BUG: startup_info struct is not initialized." error
138
+'
139
+
140
test_done