2005-08-04 13:15:49 +08:00
|
|
|
#include "cache.h"
|
2005-10-14 09:57:40 +08:00
|
|
|
#include "tag.h"
|
2005-08-04 13:15:49 +08:00
|
|
|
#include "commit.h"
|
2005-10-14 09:57:40 +08:00
|
|
|
#include "tree.h"
|
|
|
|
#include "blob.h"
|
2006-04-20 02:56:53 +08:00
|
|
|
#include "tree-walk.h"
|
2006-05-17 17:56:09 +08:00
|
|
|
#include "refs.h"
|
2009-09-10 23:25:57 +08:00
|
|
|
#include "remote.h"
|
2005-08-04 13:15:49 +08:00
|
|
|
|
2010-12-13 11:01:15 +08:00
|
|
|
static int get_sha1_oneline(const char *, unsigned char *, struct commit_list *);
|
|
|
|
|
2005-08-04 13:15:49 +08:00
|
|
|
static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
|
|
|
|
{
|
2005-10-03 12:40:51 +08:00
|
|
|
struct alternate_object_database *alt;
|
2005-08-04 13:15:49 +08:00
|
|
|
char hex[40];
|
2005-10-03 12:40:51 +08:00
|
|
|
int found = 0;
|
|
|
|
static struct alternate_object_database *fakeent;
|
|
|
|
|
|
|
|
if (!fakeent) {
|
|
|
|
const char *objdir = get_object_directory();
|
|
|
|
int objdir_len = strlen(objdir);
|
|
|
|
int entlen = objdir_len + 43;
|
|
|
|
fakeent = xmalloc(sizeof(*fakeent) + entlen);
|
|
|
|
memcpy(fakeent->base, objdir, objdir_len);
|
|
|
|
fakeent->name = fakeent->base + objdir_len + 1;
|
|
|
|
fakeent->name[-1] = '/';
|
|
|
|
}
|
|
|
|
fakeent->next = alt_odb_list;
|
2005-08-04 13:15:49 +08:00
|
|
|
|
|
|
|
sprintf(hex, "%.2s", name);
|
2005-10-03 12:40:51 +08:00
|
|
|
for (alt = fakeent; alt && found < 2; alt = alt->next) {
|
2005-08-04 13:15:49 +08:00
|
|
|
struct dirent *de;
|
2005-10-03 12:40:51 +08:00
|
|
|
DIR *dir;
|
|
|
|
sprintf(alt->name, "%.2s/", name);
|
|
|
|
dir = opendir(alt->base);
|
|
|
|
if (!dir)
|
|
|
|
continue;
|
2005-08-04 13:15:49 +08:00
|
|
|
while ((de = readdir(dir)) != NULL) {
|
|
|
|
if (strlen(de->d_name) != 38)
|
|
|
|
continue;
|
2005-10-03 12:40:51 +08:00
|
|
|
if (memcmp(de->d_name, name + 2, len - 2))
|
2005-08-04 13:15:49 +08:00
|
|
|
continue;
|
2005-10-03 12:40:51 +08:00
|
|
|
if (!found) {
|
|
|
|
memcpy(hex + 2, de->d_name, 38);
|
|
|
|
found++;
|
|
|
|
}
|
|
|
|
else if (memcmp(hex + 2, de->d_name, 38)) {
|
|
|
|
found = 2;
|
2005-08-04 13:15:49 +08:00
|
|
|
break;
|
2005-10-03 12:40:51 +08:00
|
|
|
}
|
2005-08-04 13:15:49 +08:00
|
|
|
}
|
|
|
|
closedir(dir);
|
|
|
|
}
|
|
|
|
if (found == 1)
|
|
|
|
return get_sha1_hex(hex, sha1) == 0;
|
2005-10-03 12:40:51 +08:00
|
|
|
return found;
|
2005-08-04 13:15:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int match_sha(unsigned len, const unsigned char *a, const unsigned char *b)
|
|
|
|
{
|
|
|
|
do {
|
|
|
|
if (*a != *b)
|
|
|
|
return 0;
|
|
|
|
a++;
|
|
|
|
b++;
|
|
|
|
len -= 2;
|
|
|
|
} while (len > 1);
|
|
|
|
if (len)
|
|
|
|
if ((*a ^ *b) & 0xf0)
|
|
|
|
return 0;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int find_short_packed_object(int len, const unsigned char *match, unsigned char *sha1)
|
|
|
|
{
|
|
|
|
struct packed_git *p;
|
2007-04-05 04:49:04 +08:00
|
|
|
const unsigned char *found_sha1 = NULL;
|
2005-10-03 12:40:51 +08:00
|
|
|
int found = 0;
|
2005-08-04 13:15:49 +08:00
|
|
|
|
|
|
|
prepare_packed_git();
|
2005-10-03 12:40:51 +08:00
|
|
|
for (p = packed_git; p && found < 2; p = p->next) {
|
2007-05-30 07:29:51 +08:00
|
|
|
uint32_t num, last;
|
|
|
|
uint32_t first = 0;
|
|
|
|
open_pack_index(p);
|
|
|
|
num = p->num_objects;
|
|
|
|
last = num;
|
2005-08-04 13:15:49 +08:00
|
|
|
while (first < last) {
|
2007-03-07 09:44:19 +08:00
|
|
|
uint32_t mid = (first + last) / 2;
|
2007-04-05 04:49:04 +08:00
|
|
|
const unsigned char *now;
|
2005-08-04 13:15:49 +08:00
|
|
|
int cmp;
|
|
|
|
|
2007-04-05 04:49:04 +08:00
|
|
|
now = nth_packed_object_sha1(p, mid);
|
2006-08-18 02:54:57 +08:00
|
|
|
cmp = hashcmp(match, now);
|
2005-08-04 13:15:49 +08:00
|
|
|
if (!cmp) {
|
|
|
|
first = mid;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (cmp > 0) {
|
|
|
|
first = mid+1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
last = mid;
|
|
|
|
}
|
|
|
|
if (first < num) {
|
2007-04-05 04:49:04 +08:00
|
|
|
const unsigned char *now, *next;
|
|
|
|
now = nth_packed_object_sha1(p, first);
|
2005-08-04 13:15:49 +08:00
|
|
|
if (match_sha(len, match, now)) {
|
2007-04-05 04:49:04 +08:00
|
|
|
next = nth_packed_object_sha1(p, first+1);
|
|
|
|
if (!next|| !match_sha(len, match, next)) {
|
2005-10-03 12:40:51 +08:00
|
|
|
/* unique within this pack */
|
|
|
|
if (!found) {
|
2007-04-05 04:49:04 +08:00
|
|
|
found_sha1 = now;
|
2005-10-03 12:40:51 +08:00
|
|
|
found++;
|
|
|
|
}
|
2006-08-18 02:54:57 +08:00
|
|
|
else if (hashcmp(found_sha1, now)) {
|
2005-10-03 12:40:51 +08:00
|
|
|
found = 2;
|
|
|
|
break;
|
|
|
|
}
|
2005-10-03 12:40:51 +08:00
|
|
|
}
|
2005-10-03 12:40:51 +08:00
|
|
|
else {
|
|
|
|
/* not even unique within this pack */
|
2005-10-03 12:40:51 +08:00
|
|
|
found = 2;
|
|
|
|
break;
|
2005-08-04 13:15:49 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2005-10-03 12:40:51 +08:00
|
|
|
if (found == 1)
|
2006-08-23 14:49:00 +08:00
|
|
|
hashcpy(sha1, found_sha1);
|
2005-10-03 12:40:51 +08:00
|
|
|
return found;
|
|
|
|
}
|
|
|
|
|
2005-10-12 06:22:48 +08:00
|
|
|
#define SHORT_NAME_NOT_FOUND (-1)
|
|
|
|
#define SHORT_NAME_AMBIGUOUS (-2)
|
|
|
|
|
2005-10-03 12:40:51 +08:00
|
|
|
static int find_unique_short_object(int len, char *canonical,
|
|
|
|
unsigned char *res, unsigned char *sha1)
|
|
|
|
{
|
|
|
|
int has_unpacked, has_packed;
|
|
|
|
unsigned char unpacked_sha1[20], packed_sha1[20];
|
|
|
|
|
2007-05-26 13:25:11 +08:00
|
|
|
prepare_alt_odb();
|
2005-10-03 12:40:51 +08:00
|
|
|
has_unpacked = find_short_object_filename(len, canonical, unpacked_sha1);
|
|
|
|
has_packed = find_short_packed_object(len, res, packed_sha1);
|
|
|
|
if (!has_unpacked && !has_packed)
|
2005-10-12 06:22:48 +08:00
|
|
|
return SHORT_NAME_NOT_FOUND;
|
2005-10-03 12:40:51 +08:00
|
|
|
if (1 < has_unpacked || 1 < has_packed)
|
2005-10-12 06:22:48 +08:00
|
|
|
return SHORT_NAME_AMBIGUOUS;
|
2005-10-03 12:40:51 +08:00
|
|
|
if (has_unpacked != has_packed) {
|
2006-08-23 14:49:00 +08:00
|
|
|
hashcpy(sha1, (has_packed ? packed_sha1 : unpacked_sha1));
|
2005-10-03 12:40:51 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/* Both have unique ones -- do they match? */
|
2006-08-18 02:54:57 +08:00
|
|
|
if (hashcmp(packed_sha1, unpacked_sha1))
|
2006-01-26 19:26:15 +08:00
|
|
|
return SHORT_NAME_AMBIGUOUS;
|
2006-08-23 14:49:00 +08:00
|
|
|
hashcpy(sha1, packed_sha1);
|
2005-08-04 13:15:49 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-10-12 06:22:48 +08:00
|
|
|
static int get_short_sha1(const char *name, int len, unsigned char *sha1,
|
|
|
|
int quietly)
|
2005-08-04 13:15:49 +08:00
|
|
|
{
|
2005-10-12 06:22:48 +08:00
|
|
|
int i, status;
|
2005-08-04 13:15:49 +08:00
|
|
|
char canonical[40];
|
|
|
|
unsigned char res[20];
|
|
|
|
|
2006-10-19 09:34:41 +08:00
|
|
|
if (len < MINIMUM_ABBREV || len > 40)
|
2005-09-20 06:16:03 +08:00
|
|
|
return -1;
|
2006-08-24 04:57:23 +08:00
|
|
|
hashclr(res);
|
2005-08-04 13:15:49 +08:00
|
|
|
memset(canonical, 'x', 40);
|
2005-09-20 06:16:03 +08:00
|
|
|
for (i = 0; i < len ;i++) {
|
2005-08-04 13:15:49 +08:00
|
|
|
unsigned char c = name[i];
|
|
|
|
unsigned char val;
|
|
|
|
if (c >= '0' && c <= '9')
|
|
|
|
val = c - '0';
|
|
|
|
else if (c >= 'a' && c <= 'f')
|
|
|
|
val = c - 'a' + 10;
|
|
|
|
else if (c >= 'A' && c <='F') {
|
|
|
|
val = c - 'A' + 10;
|
|
|
|
c -= 'A' - 'a';
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return -1;
|
|
|
|
canonical[i] = c;
|
|
|
|
if (!(i & 1))
|
|
|
|
val <<= 4;
|
|
|
|
res[i >> 1] |= val;
|
|
|
|
}
|
2005-10-03 12:40:51 +08:00
|
|
|
|
2005-10-12 06:22:48 +08:00
|
|
|
status = find_unique_short_object(i, canonical, res, sha1);
|
|
|
|
if (!quietly && (status == SHORT_NAME_AMBIGUOUS))
|
|
|
|
return error("short SHA1 %.*s is ambiguous.", len, canonical);
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *find_unique_abbrev(const unsigned char *sha1, int len)
|
|
|
|
{
|
2008-03-02 15:35:32 +08:00
|
|
|
int status, exists;
|
2005-10-12 06:22:48 +08:00
|
|
|
static char hex[41];
|
2005-12-14 09:21:41 +08:00
|
|
|
|
2008-03-02 15:35:32 +08:00
|
|
|
exists = has_sha1_file(sha1);
|
2005-10-12 06:22:48 +08:00
|
|
|
memcpy(hex, sha1_to_hex(sha1), 40);
|
2006-08-10 04:17:04 +08:00
|
|
|
if (len == 40 || !len)
|
2005-12-14 09:21:41 +08:00
|
|
|
return hex;
|
2005-10-12 06:22:48 +08:00
|
|
|
while (len < 40) {
|
|
|
|
unsigned char sha1_ret[20];
|
|
|
|
status = get_short_sha1(hex, len, sha1_ret, 1);
|
2008-03-02 15:35:32 +08:00
|
|
|
if (exists
|
|
|
|
? !status
|
|
|
|
: status == SHORT_NAME_NOT_FOUND) {
|
2011-03-11 14:41:14 +08:00
|
|
|
hex[len] = 0;
|
2005-10-12 06:22:48 +08:00
|
|
|
return hex;
|
|
|
|
}
|
|
|
|
len++;
|
|
|
|
}
|
2008-03-02 15:35:32 +08:00
|
|
|
return hex;
|
2005-08-04 13:15:49 +08:00
|
|
|
}
|
|
|
|
|
2005-12-16 04:54:00 +08:00
|
|
|
static int ambiguous_path(const char *path, int len)
|
Be more careful about reference parsing
This does two things:
- we don't allow "." and ".." as components of a refname. Thus get_sha1()
will not accept "./refname" as being the same as "refname" any more.
- git-rev-parse stops doing revision translation after seeing a pathname,
to match the brhaviour of all the tools (once we see a pathname,
everything else will also be parsed as a pathname).
Basically, if you did
git log *
and "gitk" was somewhere in the "*", we don't want to replace the filename
"gitk" with the SHA1 of the branch with the same name.
Of course, if there is any change of ambiguity, you should always use "--"
to make it explicit what are filenames and what are revisions, but this
makes the normal cases sane. The refname rule also means that instead of
the "--", you can do the same thing we're used to doing with filenames
that start with a slash: use "./filename" instead, and now it's a
filename, not an option (and not a revision).
So "git log ./*.c" is now actually a perfectly valid thing to do, even if
the first C-file might have the same name as a branch.
Trivial test:
git-rev-parse gitk ./gitk gitk
should output something like
9843c3074dfbf57117565f6b7c93e3e6812857ee
./gitk
gitk
where the "./gitk" isn't seen as a revision, and the second "gitk" is a
filename simply because we've seen filenames already, and thus stopped
doing revision parsing.
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2005-10-29 03:41:49 +08:00
|
|
|
{
|
|
|
|
int slash = 1;
|
2005-12-16 04:54:00 +08:00
|
|
|
int cnt;
|
Be more careful about reference parsing
This does two things:
- we don't allow "." and ".." as components of a refname. Thus get_sha1()
will not accept "./refname" as being the same as "refname" any more.
- git-rev-parse stops doing revision translation after seeing a pathname,
to match the brhaviour of all the tools (once we see a pathname,
everything else will also be parsed as a pathname).
Basically, if you did
git log *
and "gitk" was somewhere in the "*", we don't want to replace the filename
"gitk" with the SHA1 of the branch with the same name.
Of course, if there is any change of ambiguity, you should always use "--"
to make it explicit what are filenames and what are revisions, but this
makes the normal cases sane. The refname rule also means that instead of
the "--", you can do the same thing we're used to doing with filenames
that start with a slash: use "./filename" instead, and now it's a
filename, not an option (and not a revision).
So "git log ./*.c" is now actually a perfectly valid thing to do, even if
the first C-file might have the same name as a branch.
Trivial test:
git-rev-parse gitk ./gitk gitk
should output something like
9843c3074dfbf57117565f6b7c93e3e6812857ee
./gitk
gitk
where the "./gitk" isn't seen as a revision, and the second "gitk" is a
filename simply because we've seen filenames already, and thus stopped
doing revision parsing.
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2005-10-29 03:41:49 +08:00
|
|
|
|
2005-12-16 04:54:00 +08:00
|
|
|
for (cnt = 0; cnt < len; cnt++) {
|
Be more careful about reference parsing
This does two things:
- we don't allow "." and ".." as components of a refname. Thus get_sha1()
will not accept "./refname" as being the same as "refname" any more.
- git-rev-parse stops doing revision translation after seeing a pathname,
to match the brhaviour of all the tools (once we see a pathname,
everything else will also be parsed as a pathname).
Basically, if you did
git log *
and "gitk" was somewhere in the "*", we don't want to replace the filename
"gitk" with the SHA1 of the branch with the same name.
Of course, if there is any change of ambiguity, you should always use "--"
to make it explicit what are filenames and what are revisions, but this
makes the normal cases sane. The refname rule also means that instead of
the "--", you can do the same thing we're used to doing with filenames
that start with a slash: use "./filename" instead, and now it's a
filename, not an option (and not a revision).
So "git log ./*.c" is now actually a perfectly valid thing to do, even if
the first C-file might have the same name as a branch.
Trivial test:
git-rev-parse gitk ./gitk gitk
should output something like
9843c3074dfbf57117565f6b7c93e3e6812857ee
./gitk
gitk
where the "./gitk" isn't seen as a revision, and the second "gitk" is a
filename simply because we've seen filenames already, and thus stopped
doing revision parsing.
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2005-10-29 03:41:49 +08:00
|
|
|
switch (*path++) {
|
|
|
|
case '\0':
|
|
|
|
break;
|
|
|
|
case '/':
|
|
|
|
if (slash)
|
|
|
|
break;
|
|
|
|
slash = 1;
|
|
|
|
continue;
|
|
|
|
case '.':
|
|
|
|
continue;
|
|
|
|
default:
|
|
|
|
slash = 0;
|
|
|
|
continue;
|
|
|
|
}
|
2005-12-17 16:00:50 +08:00
|
|
|
break;
|
Be more careful about reference parsing
This does two things:
- we don't allow "." and ".." as components of a refname. Thus get_sha1()
will not accept "./refname" as being the same as "refname" any more.
- git-rev-parse stops doing revision translation after seeing a pathname,
to match the brhaviour of all the tools (once we see a pathname,
everything else will also be parsed as a pathname).
Basically, if you did
git log *
and "gitk" was somewhere in the "*", we don't want to replace the filename
"gitk" with the SHA1 of the branch with the same name.
Of course, if there is any change of ambiguity, you should always use "--"
to make it explicit what are filenames and what are revisions, but this
makes the normal cases sane. The refname rule also means that instead of
the "--", you can do the same thing we're used to doing with filenames
that start with a slash: use "./filename" instead, and now it's a
filename, not an option (and not a revision).
So "git log ./*.c" is now actually a perfectly valid thing to do, even if
the first C-file might have the same name as a branch.
Trivial test:
git-rev-parse gitk ./gitk gitk
should output something like
9843c3074dfbf57117565f6b7c93e3e6812857ee
./gitk
gitk
where the "./gitk" isn't seen as a revision, and the second "gitk" is a
filename simply because we've seen filenames already, and thus stopped
doing revision parsing.
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2005-10-29 03:41:49 +08:00
|
|
|
}
|
2005-12-16 04:54:00 +08:00
|
|
|
return slash;
|
Be more careful about reference parsing
This does two things:
- we don't allow "." and ".." as components of a refname. Thus get_sha1()
will not accept "./refname" as being the same as "refname" any more.
- git-rev-parse stops doing revision translation after seeing a pathname,
to match the brhaviour of all the tools (once we see a pathname,
everything else will also be parsed as a pathname).
Basically, if you did
git log *
and "gitk" was somewhere in the "*", we don't want to replace the filename
"gitk" with the SHA1 of the branch with the same name.
Of course, if there is any change of ambiguity, you should always use "--"
to make it explicit what are filenames and what are revisions, but this
makes the normal cases sane. The refname rule also means that instead of
the "--", you can do the same thing we're used to doing with filenames
that start with a slash: use "./filename" instead, and now it's a
filename, not an option (and not a revision).
So "git log ./*.c" is now actually a perfectly valid thing to do, even if
the first C-file might have the same name as a branch.
Trivial test:
git-rev-parse gitk ./gitk gitk
should output something like
9843c3074dfbf57117565f6b7c93e3e6812857ee
./gitk
gitk
where the "./gitk" isn't seen as a revision, and the second "gitk" is a
filename simply because we've seen filenames already, and thus stopped
doing revision parsing.
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2005-10-29 03:41:49 +08:00
|
|
|
}
|
|
|
|
|
2009-01-18 02:08:12 +08:00
|
|
|
/*
|
|
|
|
* *string and *len will only be substituted, and *string returned (for
|
2010-01-20 15:17:11 +08:00
|
|
|
* later free()ing) if the string passed in is a magic short-hand form
|
|
|
|
* to name a branch.
|
2009-01-18 02:08:12 +08:00
|
|
|
*/
|
2009-03-22 03:51:34 +08:00
|
|
|
static char *substitute_branch_name(const char **string, int *len)
|
2009-01-18 02:08:12 +08:00
|
|
|
{
|
|
|
|
struct strbuf buf = STRBUF_INIT;
|
2009-03-22 03:51:34 +08:00
|
|
|
int ret = interpret_branch_name(*string, &buf);
|
2009-01-18 02:08:12 +08:00
|
|
|
|
|
|
|
if (ret == *len) {
|
|
|
|
size_t size;
|
|
|
|
*string = strbuf_detach(&buf, &size);
|
|
|
|
*len = size;
|
|
|
|
return (char *)*string;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2007-01-19 17:15:15 +08:00
|
|
|
int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
|
2005-08-04 13:15:49 +08:00
|
|
|
{
|
2009-03-22 03:51:34 +08:00
|
|
|
char *last_branch = substitute_branch_name(&str, &len);
|
2007-01-19 17:15:15 +08:00
|
|
|
const char **p, *r;
|
|
|
|
int refs_found = 0;
|
|
|
|
|
|
|
|
*ref = NULL;
|
add refname_match()
We use at least two rulesets for matching abbreviated refnames with
full refnames (starting with 'refs/'). git-rev-parse and git-fetch
use slightly different rules.
This commit introduces a new function refname_match
(const char *abbrev_name, const char *full_name, const char **rules).
abbrev_name is expanded using the rules and matched against full_name.
If a match is found the function returns true. rules is a NULL-terminate
list of format patterns with "%.*s", for example:
const char *ref_rev_parse_rules[] = {
"%.*s",
"refs/%.*s",
"refs/tags/%.*s",
"refs/heads/%.*s",
"refs/remotes/%.*s",
"refs/remotes/%.*s/HEAD",
NULL
};
Asterisks are included in the format strings because this is the form
required in sha1_name.c. Sharing the list with the functions there is
a good idea to avoid duplicating the rules. Hopefully this
facilitates unified matching rules in the future.
This commit makes the rules used by rev-parse for resolving refs to
sha1s available for string comparison. Before this change, the rules
were buried in get_sha1*() and dwim_ref().
A follow-up commit will refactor the rules used by fetch.
refname_match() will be used for matching refspecs in git-send-pack.
Thanks to Daniel Barkalow <barkalow@iabervon.org> for pointing
out that ref_matches_abbrev in remote.c solves a similar problem
and care should be taken to avoid confusion.
Signed-off-by: Steffen Prohaska <prohaska@zib.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2007-11-11 22:01:46 +08:00
|
|
|
for (p = ref_rev_parse_rules; *p; p++) {
|
2008-10-27 06:07:24 +08:00
|
|
|
char fullref[PATH_MAX];
|
2007-01-19 17:15:15 +08:00
|
|
|
unsigned char sha1_from_ref[20];
|
|
|
|
unsigned char *this_result;
|
2009-02-09 15:52:01 +08:00
|
|
|
int flag;
|
2007-01-19 17:15:15 +08:00
|
|
|
|
|
|
|
this_result = refs_found ? sha1_from_ref : sha1;
|
2008-10-27 06:07:24 +08:00
|
|
|
mksnpath(fullref, sizeof(fullref), *p, len, str);
|
2009-02-09 15:52:01 +08:00
|
|
|
r = resolve_ref(fullref, this_result, 1, &flag);
|
2007-01-19 17:15:15 +08:00
|
|
|
if (r) {
|
|
|
|
if (!refs_found++)
|
|
|
|
*ref = xstrdup(r);
|
|
|
|
if (!warn_ambiguous_refs)
|
|
|
|
break;
|
2010-02-16 15:03:16 +08:00
|
|
|
} else if ((flag & REF_ISSYMREF) && strcmp(fullref, "HEAD"))
|
2009-02-09 15:52:01 +08:00
|
|
|
warning("ignoring dangling symref %s.", fullref);
|
2007-01-19 17:15:15 +08:00
|
|
|
}
|
2009-01-18 02:08:12 +08:00
|
|
|
free(last_branch);
|
2007-01-19 17:15:15 +08:00
|
|
|
return refs_found;
|
|
|
|
}
|
|
|
|
|
2007-02-09 08:28:23 +08:00
|
|
|
int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
|
2007-02-04 10:49:16 +08:00
|
|
|
{
|
2009-03-22 03:51:34 +08:00
|
|
|
char *last_branch = substitute_branch_name(&str, &len);
|
2007-02-04 10:49:16 +08:00
|
|
|
const char **p;
|
|
|
|
int logs_found = 0;
|
|
|
|
|
|
|
|
*log = NULL;
|
add refname_match()
We use at least two rulesets for matching abbreviated refnames with
full refnames (starting with 'refs/'). git-rev-parse and git-fetch
use slightly different rules.
This commit introduces a new function refname_match
(const char *abbrev_name, const char *full_name, const char **rules).
abbrev_name is expanded using the rules and matched against full_name.
If a match is found the function returns true. rules is a NULL-terminate
list of format patterns with "%.*s", for example:
const char *ref_rev_parse_rules[] = {
"%.*s",
"refs/%.*s",
"refs/tags/%.*s",
"refs/heads/%.*s",
"refs/remotes/%.*s",
"refs/remotes/%.*s/HEAD",
NULL
};
Asterisks are included in the format strings because this is the form
required in sha1_name.c. Sharing the list with the functions there is
a good idea to avoid duplicating the rules. Hopefully this
facilitates unified matching rules in the future.
This commit makes the rules used by rev-parse for resolving refs to
sha1s available for string comparison. Before this change, the rules
were buried in get_sha1*() and dwim_ref().
A follow-up commit will refactor the rules used by fetch.
refname_match() will be used for matching refspecs in git-send-pack.
Thanks to Daniel Barkalow <barkalow@iabervon.org> for pointing
out that ref_matches_abbrev in remote.c solves a similar problem
and care should be taken to avoid confusion.
Signed-off-by: Steffen Prohaska <prohaska@zib.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2007-11-11 22:01:46 +08:00
|
|
|
for (p = ref_rev_parse_rules; *p; p++) {
|
2007-02-04 10:49:16 +08:00
|
|
|
struct stat st;
|
2007-02-09 15:24:51 +08:00
|
|
|
unsigned char hash[20];
|
|
|
|
char path[PATH_MAX];
|
|
|
|
const char *ref, *it;
|
|
|
|
|
2008-10-27 06:07:24 +08:00
|
|
|
mksnpath(path, sizeof(path), *p, len, str);
|
2008-07-24 08:22:58 +08:00
|
|
|
ref = resolve_ref(path, hash, 1, NULL);
|
2007-02-09 15:24:51 +08:00
|
|
|
if (!ref)
|
|
|
|
continue;
|
2007-02-04 10:49:16 +08:00
|
|
|
if (!stat(git_path("logs/%s", path), &st) &&
|
2007-02-09 15:24:51 +08:00
|
|
|
S_ISREG(st.st_mode))
|
|
|
|
it = path;
|
|
|
|
else if (strcmp(ref, path) &&
|
|
|
|
!stat(git_path("logs/%s", ref), &st) &&
|
|
|
|
S_ISREG(st.st_mode))
|
|
|
|
it = ref;
|
|
|
|
else
|
|
|
|
continue;
|
|
|
|
if (!logs_found++) {
|
|
|
|
*log = xstrdup(it);
|
|
|
|
hashcpy(sha1, hash);
|
2007-02-04 10:49:16 +08:00
|
|
|
}
|
2007-02-09 15:24:51 +08:00
|
|
|
if (!warn_ambiguous_refs)
|
|
|
|
break;
|
2007-02-04 10:49:16 +08:00
|
|
|
}
|
2009-01-18 02:08:12 +08:00
|
|
|
free(last_branch);
|
2007-02-04 10:49:16 +08:00
|
|
|
return logs_found;
|
|
|
|
}
|
|
|
|
|
2010-01-20 15:17:11 +08:00
|
|
|
static inline int upstream_mark(const char *string, int len)
|
|
|
|
{
|
|
|
|
const char *suffix[] = { "@{upstream}", "@{u}" };
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(suffix); i++) {
|
|
|
|
int suffix_len = strlen(suffix[i]);
|
|
|
|
if (suffix_len <= len
|
|
|
|
&& !memcmp(string, suffix[i], suffix_len))
|
|
|
|
return suffix_len;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-01-18 00:09:55 +08:00
|
|
|
static int get_sha1_1(const char *name, int len, unsigned char *sha1);
|
|
|
|
|
2007-01-19 17:15:15 +08:00
|
|
|
static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
|
|
|
|
{
|
2010-08-24 12:52:43 +08:00
|
|
|
static const char *warn_msg = "refname '%.*s' is ambiguous.";
|
2006-09-12 11:17:35 +08:00
|
|
|
char *real_ref = NULL;
|
2006-10-06 14:16:15 +08:00
|
|
|
int refs_found = 0;
|
|
|
|
int at, reflog_len;
|
2005-08-04 13:15:49 +08:00
|
|
|
|
2005-08-14 02:05:25 +08:00
|
|
|
if (len == 40 && !get_sha1_hex(str, sha1))
|
2005-08-04 13:15:49 +08:00
|
|
|
return 0;
|
|
|
|
|
2009-01-18 00:09:55 +08:00
|
|
|
/* basic@{time or number or -number} format to query ref-log */
|
2006-10-24 12:15:34 +08:00
|
|
|
reflog_len = at = 0;
|
2009-01-28 07:07:46 +08:00
|
|
|
if (len && str[len-1] == '}') {
|
2009-01-18 02:08:12 +08:00
|
|
|
for (at = len-2; at >= 0; at--) {
|
2006-10-06 14:16:15 +08:00
|
|
|
if (str[at] == '@' && str[at+1] == '{') {
|
2010-01-20 15:17:11 +08:00
|
|
|
if (!upstream_mark(str + at, len - at)) {
|
2009-09-10 23:25:57 +08:00
|
|
|
reflog_len = (len-1) - (at+2);
|
|
|
|
len = at;
|
|
|
|
}
|
2006-10-06 14:16:15 +08:00
|
|
|
break;
|
|
|
|
}
|
2006-05-17 17:56:09 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Be more careful about reference parsing
This does two things:
- we don't allow "." and ".." as components of a refname. Thus get_sha1()
will not accept "./refname" as being the same as "refname" any more.
- git-rev-parse stops doing revision translation after seeing a pathname,
to match the brhaviour of all the tools (once we see a pathname,
everything else will also be parsed as a pathname).
Basically, if you did
git log *
and "gitk" was somewhere in the "*", we don't want to replace the filename
"gitk" with the SHA1 of the branch with the same name.
Of course, if there is any change of ambiguity, you should always use "--"
to make it explicit what are filenames and what are revisions, but this
makes the normal cases sane. The refname rule also means that instead of
the "--", you can do the same thing we're used to doing with filenames
that start with a slash: use "./filename" instead, and now it's a
filename, not an option (and not a revision).
So "git log ./*.c" is now actually a perfectly valid thing to do, even if
the first C-file might have the same name as a branch.
Trivial test:
git-rev-parse gitk ./gitk gitk
should output something like
9843c3074dfbf57117565f6b7c93e3e6812857ee
./gitk
gitk
where the "./gitk" isn't seen as a revision, and the second "gitk" is a
filename simply because we've seen filenames already, and thus stopped
doing revision parsing.
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2005-10-29 03:41:49 +08:00
|
|
|
/* Accept only unambiguous ref paths. */
|
2007-02-02 06:29:33 +08:00
|
|
|
if (len && ambiguous_path(str, len))
|
Be more careful about reference parsing
This does two things:
- we don't allow "." and ".." as components of a refname. Thus get_sha1()
will not accept "./refname" as being the same as "refname" any more.
- git-rev-parse stops doing revision translation after seeing a pathname,
to match the brhaviour of all the tools (once we see a pathname,
everything else will also be parsed as a pathname).
Basically, if you did
git log *
and "gitk" was somewhere in the "*", we don't want to replace the filename
"gitk" with the SHA1 of the branch with the same name.
Of course, if there is any change of ambiguity, you should always use "--"
to make it explicit what are filenames and what are revisions, but this
makes the normal cases sane. The refname rule also means that instead of
the "--", you can do the same thing we're used to doing with filenames
that start with a slash: use "./filename" instead, and now it's a
filename, not an option (and not a revision).
So "git log ./*.c" is now actually a perfectly valid thing to do, even if
the first C-file might have the same name as a branch.
Trivial test:
git-rev-parse gitk ./gitk gitk
should output something like
9843c3074dfbf57117565f6b7c93e3e6812857ee
./gitk
gitk
where the "./gitk" isn't seen as a revision, and the second "gitk" is a
filename simply because we've seen filenames already, and thus stopped
doing revision parsing.
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2005-10-29 03:41:49 +08:00
|
|
|
return -1;
|
|
|
|
|
2007-02-02 06:29:33 +08:00
|
|
|
if (!len && reflog_len) {
|
2009-01-18 00:09:55 +08:00
|
|
|
struct strbuf buf = STRBUF_INIT;
|
|
|
|
int ret;
|
|
|
|
/* try the @{-N} syntax for n-th checkout */
|
2009-03-22 03:51:34 +08:00
|
|
|
ret = interpret_branch_name(str+at, &buf);
|
2009-01-18 00:09:55 +08:00
|
|
|
if (ret > 0) {
|
|
|
|
/* substitute this branch name and restart */
|
|
|
|
return get_sha1_1(buf.buf, buf.len, sha1);
|
|
|
|
} else if (ret == 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2007-02-02 06:29:33 +08:00
|
|
|
/* allow "@{...}" to mean the current branch reflog */
|
|
|
|
refs_found = dwim_ref("HEAD", 4, sha1, &real_ref);
|
2007-02-04 10:49:16 +08:00
|
|
|
} else if (reflog_len)
|
|
|
|
refs_found = dwim_log(str, len, sha1, &real_ref);
|
|
|
|
else
|
2007-02-02 06:29:33 +08:00
|
|
|
refs_found = dwim_ref(str, len, sha1, &real_ref);
|
2006-05-17 17:56:09 +08:00
|
|
|
|
|
|
|
if (!refs_found)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (warn_ambiguous_refs && refs_found > 1)
|
2010-08-24 12:52:43 +08:00
|
|
|
warning(warn_msg, len, str);
|
2006-05-17 17:56:09 +08:00
|
|
|
|
2006-10-06 14:16:15 +08:00
|
|
|
if (reflog_len) {
|
|
|
|
int nth, i;
|
|
|
|
unsigned long at_time;
|
2007-01-19 17:19:05 +08:00
|
|
|
unsigned long co_time;
|
|
|
|
int co_tz, co_cnt;
|
|
|
|
|
2010-01-28 17:56:43 +08:00
|
|
|
/* a @{-N} placed anywhere except the start is an error */
|
|
|
|
if (str[at+2] == '-')
|
|
|
|
return -1;
|
|
|
|
|
2007-02-02 01:33:23 +08:00
|
|
|
/* Is it asking for N-th entry, or approxidate? */
|
2006-10-06 14:16:15 +08:00
|
|
|
for (i = nth = 0; 0 <= nth && i < reflog_len; i++) {
|
|
|
|
char ch = str[at+2+i];
|
|
|
|
if ('0' <= ch && ch <= '9')
|
|
|
|
nth = nth * 10 + ch - '0';
|
|
|
|
else
|
|
|
|
nth = -1;
|
|
|
|
}
|
2008-08-21 23:40:44 +08:00
|
|
|
if (100000000 <= nth) {
|
|
|
|
at_time = nth;
|
|
|
|
nth = -1;
|
|
|
|
} else if (0 <= nth)
|
2006-10-06 14:16:15 +08:00
|
|
|
at_time = 0;
|
2008-04-30 12:13:58 +08:00
|
|
|
else {
|
2010-01-27 03:58:00 +08:00
|
|
|
int errors = 0;
|
2008-04-30 12:13:58 +08:00
|
|
|
char *tmp = xstrndup(str + at + 2, reflog_len);
|
2010-01-27 03:58:00 +08:00
|
|
|
at_time = approxidate_careful(tmp, &errors);
|
2008-04-30 12:13:58 +08:00
|
|
|
free(tmp);
|
2010-01-28 02:53:09 +08:00
|
|
|
if (errors)
|
|
|
|
return -1;
|
2008-04-30 12:13:58 +08:00
|
|
|
}
|
2007-01-19 17:19:05 +08:00
|
|
|
if (read_ref_at(real_ref, at_time, nth, sha1, NULL,
|
|
|
|
&co_time, &co_tz, &co_cnt)) {
|
|
|
|
if (at_time)
|
2010-08-24 12:52:43 +08:00
|
|
|
warning("Log for '%.*s' only goes "
|
|
|
|
"back to %s.", len, str,
|
2007-07-14 14:14:52 +08:00
|
|
|
show_date(co_time, co_tz, DATE_RFC2822));
|
2010-08-24 12:52:42 +08:00
|
|
|
else {
|
|
|
|
free(real_ref);
|
|
|
|
die("Log for '%.*s' only has %d entries.",
|
|
|
|
len, str, co_cnt);
|
|
|
|
}
|
2007-01-19 17:19:05 +08:00
|
|
|
}
|
2006-05-17 17:56:09 +08:00
|
|
|
}
|
|
|
|
|
2006-09-12 11:17:35 +08:00
|
|
|
free(real_ref);
|
2006-05-17 17:56:09 +08:00
|
|
|
return 0;
|
2005-08-04 13:15:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int get_parent(const char *name, int len,
|
|
|
|
unsigned char *result, int idx)
|
|
|
|
{
|
|
|
|
unsigned char sha1[20];
|
|
|
|
int ret = get_sha1_1(name, len, sha1);
|
|
|
|
struct commit *commit;
|
|
|
|
struct commit_list *p;
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
commit = lookup_commit_reference(sha1);
|
|
|
|
if (!commit)
|
|
|
|
return -1;
|
|
|
|
if (parse_commit(commit))
|
|
|
|
return -1;
|
|
|
|
if (!idx) {
|
2006-08-23 14:49:00 +08:00
|
|
|
hashcpy(result, commit->object.sha1);
|
2005-08-04 13:15:49 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
p = commit->parents;
|
|
|
|
while (p) {
|
|
|
|
if (!--idx) {
|
2006-08-23 14:49:00 +08:00
|
|
|
hashcpy(result, p->item->object.sha1);
|
2005-08-04 13:15:49 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
p = p->next;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2005-08-21 17:43:54 +08:00
|
|
|
static int get_nth_ancestor(const char *name, int len,
|
|
|
|
unsigned char *result, int generation)
|
|
|
|
{
|
|
|
|
unsigned char sha1[20];
|
2008-03-15 02:49:40 +08:00
|
|
|
struct commit *commit;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = get_sha1_1(name, len, sha1);
|
2005-08-21 17:43:54 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
2008-03-15 02:49:40 +08:00
|
|
|
commit = lookup_commit_reference(sha1);
|
|
|
|
if (!commit)
|
|
|
|
return -1;
|
2005-08-21 17:43:54 +08:00
|
|
|
|
|
|
|
while (generation--) {
|
2008-03-15 02:49:40 +08:00
|
|
|
if (parse_commit(commit) || !commit->parents)
|
2005-08-21 17:43:54 +08:00
|
|
|
return -1;
|
2008-03-15 02:49:40 +08:00
|
|
|
commit = commit->parents->item;
|
2005-08-21 17:43:54 +08:00
|
|
|
}
|
2008-03-15 02:49:40 +08:00
|
|
|
hashcpy(result, commit->object.sha1);
|
2005-08-21 17:43:54 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-12-24 16:51:01 +08:00
|
|
|
struct object *peel_to_type(const char *name, int namelen,
|
|
|
|
struct object *o, enum object_type expected_type)
|
|
|
|
{
|
|
|
|
if (name && !namelen)
|
|
|
|
namelen = strlen(name);
|
|
|
|
if (!o) {
|
|
|
|
unsigned char sha1[20];
|
|
|
|
if (get_sha1_1(name, namelen, sha1))
|
|
|
|
return NULL;
|
|
|
|
o = parse_object(sha1);
|
|
|
|
}
|
|
|
|
while (1) {
|
|
|
|
if (!o || (!o->parsed && !parse_object(o->sha1)))
|
|
|
|
return NULL;
|
|
|
|
if (o->type == expected_type)
|
|
|
|
return o;
|
|
|
|
if (o->type == OBJ_TAG)
|
|
|
|
o = ((struct tag*) o)->tagged;
|
|
|
|
else if (o->type == OBJ_COMMIT)
|
|
|
|
o = &(((struct commit *) o)->tree->object);
|
|
|
|
else {
|
|
|
|
if (name)
|
|
|
|
error("%.*s: expected %s type, but the object "
|
|
|
|
"dereferences to %s type",
|
|
|
|
namelen, name, typename(expected_type),
|
|
|
|
typename(o->type));
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-10-14 09:57:40 +08:00
|
|
|
static int peel_onion(const char *name, int len, unsigned char *sha1)
|
|
|
|
{
|
|
|
|
unsigned char outer[20];
|
|
|
|
const char *sp;
|
Shrink "struct object" a bit
This shrinks "struct object" by a small amount, by getting rid of the
"struct type *" pointer and replacing it with a 3-bit bitfield instead.
In addition, we merge the bitfields and the "flags" field, which
incidentally should also remove a useless 4-byte padding from the object
when in 64-bit mode.
Now, our "struct object" is still too damn large, but it's now less
obviously bloated, and of the remaining fields, only the "util" (which is
not used by most things) is clearly something that should be eventually
discarded.
This shrinks the "git-rev-list --all" memory use by about 2.5% on the
kernel archive (and, perhaps more importantly, on the larger mozilla
archive). That may not sound like much, but I suspect it's more on a
64-bit platform.
There are other remaining inefficiencies (the parent lists, for example,
probably have horrible malloc overhead), but this was pretty obvious.
Most of the patch is just changing the comparison of the "type" pointer
from one of the constant string pointers to the appropriate new TYPE_xxx
small integer constant.
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2006-06-15 07:45:13 +08:00
|
|
|
unsigned int expected_type = 0;
|
2005-10-14 09:57:40 +08:00
|
|
|
struct object *o;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* "ref^{type}" dereferences ref repeatedly until you cannot
|
|
|
|
* dereference anymore, or you get an object of given type,
|
|
|
|
* whichever comes first. "ref^{}" means just dereference
|
|
|
|
* tags until you get a non-tag. "ref^0" is a shorthand for
|
|
|
|
* "ref^{commit}". "commit^{tree}" could be used to find the
|
|
|
|
* top-level tree of the given commit.
|
|
|
|
*/
|
|
|
|
if (len < 4 || name[len-1] != '}')
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
for (sp = name + len - 1; name <= sp; sp--) {
|
|
|
|
int ch = *sp;
|
|
|
|
if (ch == '{' && name < sp && sp[-1] == '^')
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (sp <= name)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
sp++; /* beginning of type name, or closing brace for empty */
|
|
|
|
if (!strncmp(commit_type, sp, 6) && sp[6] == '}')
|
2006-07-12 11:45:31 +08:00
|
|
|
expected_type = OBJ_COMMIT;
|
2005-10-14 09:57:40 +08:00
|
|
|
else if (!strncmp(tree_type, sp, 4) && sp[4] == '}')
|
2006-07-12 11:45:31 +08:00
|
|
|
expected_type = OBJ_TREE;
|
2005-10-14 09:57:40 +08:00
|
|
|
else if (!strncmp(blob_type, sp, 4) && sp[4] == '}')
|
2006-07-12 11:45:31 +08:00
|
|
|
expected_type = OBJ_BLOB;
|
2005-10-14 09:57:40 +08:00
|
|
|
else if (sp[0] == '}')
|
2006-07-12 11:45:31 +08:00
|
|
|
expected_type = OBJ_NONE;
|
2010-12-13 11:01:15 +08:00
|
|
|
else if (sp[0] == '/')
|
|
|
|
expected_type = OBJ_COMMIT;
|
2005-10-14 09:57:40 +08:00
|
|
|
else
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (get_sha1_1(name, sp - name - 2, outer))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
o = parse_object(outer);
|
|
|
|
if (!o)
|
|
|
|
return -1;
|
Shrink "struct object" a bit
This shrinks "struct object" by a small amount, by getting rid of the
"struct type *" pointer and replacing it with a 3-bit bitfield instead.
In addition, we merge the bitfields and the "flags" field, which
incidentally should also remove a useless 4-byte padding from the object
when in 64-bit mode.
Now, our "struct object" is still too damn large, but it's now less
obviously bloated, and of the remaining fields, only the "util" (which is
not used by most things) is clearly something that should be eventually
discarded.
This shrinks the "git-rev-list --all" memory use by about 2.5% on the
kernel archive (and, perhaps more importantly, on the larger mozilla
archive). That may not sound like much, but I suspect it's more on a
64-bit platform.
There are other remaining inefficiencies (the parent lists, for example,
probably have horrible malloc overhead), but this was pretty obvious.
Most of the patch is just changing the comparison of the "type" pointer
from one of the constant string pointers to the appropriate new TYPE_xxx
small integer constant.
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2006-06-15 07:45:13 +08:00
|
|
|
if (!expected_type) {
|
2005-11-03 07:19:13 +08:00
|
|
|
o = deref_tag(o, name, sp - name - 2);
|
2005-10-20 13:48:16 +08:00
|
|
|
if (!o || (!o->parsed && !parse_object(o->sha1)))
|
|
|
|
return -1;
|
2006-08-23 14:49:00 +08:00
|
|
|
hashcpy(sha1, o->sha1);
|
2010-12-13 11:01:15 +08:00
|
|
|
return 0;
|
2005-10-14 09:57:40 +08:00
|
|
|
}
|
2010-12-13 11:01:15 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* At this point, the syntax look correct, so
|
|
|
|
* if we do not get the needed object, we should
|
|
|
|
* barf.
|
|
|
|
*/
|
|
|
|
o = peel_to_type(name, len, o, expected_type);
|
|
|
|
if (!o)
|
2007-12-24 16:51:01 +08:00
|
|
|
return -1;
|
2010-12-13 11:01:15 +08:00
|
|
|
|
|
|
|
hashcpy(sha1, o->sha1);
|
|
|
|
if (sp[0] == '/') {
|
|
|
|
/* "$commit^{/foo}" */
|
|
|
|
char *prefix;
|
|
|
|
int ret;
|
|
|
|
struct commit_list *list = NULL;
|
|
|
|
|
2007-12-24 16:51:01 +08:00
|
|
|
/*
|
2010-12-15 17:02:54 +08:00
|
|
|
* $commit^{/}. Some regex implementation may reject.
|
|
|
|
* We don't need regex anyway. '' pattern always matches.
|
2005-10-14 09:57:40 +08:00
|
|
|
*/
|
2010-12-15 17:02:54 +08:00
|
|
|
if (sp[1] == '}')
|
2007-12-24 16:51:01 +08:00
|
|
|
return 0;
|
2010-12-15 17:02:54 +08:00
|
|
|
|
2010-12-13 11:01:15 +08:00
|
|
|
prefix = xstrndup(sp + 1, name + len - 1 - (sp + 1));
|
|
|
|
commit_list_insert((struct commit *)o, &list);
|
|
|
|
ret = get_sha1_oneline(prefix, sha1, list);
|
|
|
|
free(prefix);
|
|
|
|
return ret;
|
2005-10-14 09:57:40 +08:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-09-21 07:11:08 +08:00
|
|
|
static int get_describe_name(const char *name, int len, unsigned char *sha1)
|
|
|
|
{
|
|
|
|
const char *cp;
|
|
|
|
|
|
|
|
for (cp = name + len - 1; name + 2 <= cp; cp--) {
|
|
|
|
char ch = *cp;
|
|
|
|
if (hexval(ch) & ~0377) {
|
|
|
|
/* We must be looking at g in "SOMETHING-g"
|
|
|
|
* for it to be describe output.
|
|
|
|
*/
|
|
|
|
if (ch == 'g' && cp[-1] == '-') {
|
|
|
|
cp++;
|
|
|
|
len -= cp - name;
|
|
|
|
return get_short_sha1(cp, len, sha1, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2005-08-04 13:15:49 +08:00
|
|
|
static int get_sha1_1(const char *name, int len, unsigned char *sha1)
|
|
|
|
{
|
2006-02-03 15:48:36 +08:00
|
|
|
int ret, has_suffix;
|
2005-08-21 17:43:54 +08:00
|
|
|
const char *cp;
|
2005-08-04 13:15:49 +08:00
|
|
|
|
2008-03-15 02:49:40 +08:00
|
|
|
/*
|
|
|
|
* "name~3" is "name^^^", "name~" is "name~1", and "name^" is "name^1".
|
2005-08-21 17:43:54 +08:00
|
|
|
*/
|
2006-02-03 15:48:36 +08:00
|
|
|
has_suffix = 0;
|
2005-08-21 17:43:54 +08:00
|
|
|
for (cp = name + len - 1; name <= cp; cp--) {
|
|
|
|
int ch = *cp;
|
|
|
|
if ('0' <= ch && ch <= '9')
|
|
|
|
continue;
|
2006-02-03 15:48:36 +08:00
|
|
|
if (ch == '~' || ch == '^')
|
|
|
|
has_suffix = ch;
|
2005-08-21 17:43:54 +08:00
|
|
|
break;
|
|
|
|
}
|
2006-02-03 15:48:36 +08:00
|
|
|
|
|
|
|
if (has_suffix) {
|
|
|
|
int num = 0;
|
2005-08-21 17:43:54 +08:00
|
|
|
int len1 = cp - name;
|
|
|
|
cp++;
|
|
|
|
while (cp < name + len)
|
2006-02-03 15:48:36 +08:00
|
|
|
num = num * 10 + *cp++ - '0';
|
2008-03-15 02:49:40 +08:00
|
|
|
if (!num && len1 == len - 1)
|
|
|
|
num = 1;
|
|
|
|
if (has_suffix == '^')
|
2006-02-03 15:48:36 +08:00
|
|
|
return get_parent(name, len1, sha1, num);
|
|
|
|
/* else if (has_suffix == '~') -- goes without saying */
|
|
|
|
return get_nth_ancestor(name, len1, sha1, num);
|
2005-08-21 17:43:54 +08:00
|
|
|
}
|
|
|
|
|
2005-10-14 09:57:40 +08:00
|
|
|
ret = peel_onion(name, len, sha1);
|
|
|
|
if (!ret)
|
|
|
|
return 0;
|
|
|
|
|
2005-08-04 13:15:49 +08:00
|
|
|
ret = get_sha1_basic(name, len, sha1);
|
|
|
|
if (!ret)
|
|
|
|
return 0;
|
2006-09-21 07:11:08 +08:00
|
|
|
|
|
|
|
/* It could be describe output that is "SOMETHING-gXXXX" */
|
|
|
|
ret = get_describe_name(name, len, sha1);
|
|
|
|
if (!ret)
|
|
|
|
return 0;
|
|
|
|
|
2005-10-12 06:22:48 +08:00
|
|
|
return get_short_sha1(name, len, sha1, 0);
|
2005-08-04 13:15:49 +08:00
|
|
|
}
|
|
|
|
|
2010-08-03 05:37:06 +08:00
|
|
|
/*
|
|
|
|
* This interprets names like ':/Initial revision of "git"' by searching
|
|
|
|
* through history and returning the first commit whose message starts
|
2010-08-13 09:32:49 +08:00
|
|
|
* the given regular expression.
|
2010-08-03 05:37:06 +08:00
|
|
|
*
|
|
|
|
* For future extension, ':/!' is reserved. If you want to match a message
|
|
|
|
* beginning with a '!', you have to repeat the exclamation mark.
|
|
|
|
*/
|
|
|
|
#define ONELINE_SEEN (1u<<20)
|
|
|
|
|
2007-02-24 10:08:20 +08:00
|
|
|
static int handle_one_ref(const char *path,
|
|
|
|
const unsigned char *sha1, int flag, void *cb_data)
|
|
|
|
{
|
|
|
|
struct commit_list **list = cb_data;
|
|
|
|
struct object *object = parse_object(sha1);
|
|
|
|
if (!object)
|
|
|
|
return 0;
|
2008-02-18 15:31:54 +08:00
|
|
|
if (object->type == OBJ_TAG) {
|
2007-02-24 10:08:20 +08:00
|
|
|
object = deref_tag(object, path, strlen(path));
|
2008-02-18 15:31:54 +08:00
|
|
|
if (!object)
|
|
|
|
return 0;
|
|
|
|
}
|
2007-02-24 10:08:20 +08:00
|
|
|
if (object->type != OBJ_COMMIT)
|
|
|
|
return 0;
|
2010-11-27 09:58:14 +08:00
|
|
|
commit_list_insert_by_date((struct commit *)object, list);
|
2007-02-24 10:08:20 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-12-13 11:01:14 +08:00
|
|
|
static int get_sha1_oneline(const char *prefix, unsigned char *sha1,
|
|
|
|
struct commit_list *list)
|
2007-02-24 10:08:20 +08:00
|
|
|
{
|
2010-12-13 11:01:14 +08:00
|
|
|
struct commit_list *backup = NULL, *l;
|
2010-12-13 14:19:00 +08:00
|
|
|
int found = 0;
|
2010-04-23 23:20:20 +08:00
|
|
|
regex_t regex;
|
2007-02-24 10:08:20 +08:00
|
|
|
|
|
|
|
if (prefix[0] == '!') {
|
|
|
|
if (prefix[1] != '!')
|
|
|
|
die ("Invalid search pattern: %s", prefix);
|
|
|
|
prefix++;
|
|
|
|
}
|
2010-04-23 23:20:20 +08:00
|
|
|
|
|
|
|
if (regcomp(®ex, prefix, REG_EXTENDED))
|
|
|
|
die("Invalid search pattern: %s", prefix);
|
|
|
|
|
2010-12-13 11:01:14 +08:00
|
|
|
for (l = list; l; l = l->next) {
|
|
|
|
l->item->object.flags |= ONELINE_SEEN;
|
2007-02-24 10:08:20 +08:00
|
|
|
commit_list_insert(l->item, &backup);
|
2010-12-13 11:01:14 +08:00
|
|
|
}
|
2007-03-12 02:49:08 +08:00
|
|
|
while (list) {
|
2010-12-13 14:19:00 +08:00
|
|
|
char *p, *to_free = NULL;
|
2007-03-13 02:30:38 +08:00
|
|
|
struct commit *commit;
|
2007-12-04 02:42:39 +08:00
|
|
|
enum object_type type;
|
|
|
|
unsigned long size;
|
2010-12-13 14:19:00 +08:00
|
|
|
int matches;
|
2007-03-12 02:49:08 +08:00
|
|
|
|
|
|
|
commit = pop_most_recent_commit(&list, ONELINE_SEEN);
|
2008-02-19 04:47:53 +08:00
|
|
|
if (!parse_object(commit->object.sha1))
|
|
|
|
continue;
|
2007-12-04 02:42:39 +08:00
|
|
|
if (commit->buffer)
|
|
|
|
p = commit->buffer;
|
|
|
|
else {
|
|
|
|
p = read_sha1_file(commit->object.sha1, &type, &size);
|
|
|
|
if (!p)
|
|
|
|
continue;
|
2010-12-13 14:19:00 +08:00
|
|
|
to_free = p;
|
2007-12-04 02:42:39 +08:00
|
|
|
}
|
2010-12-13 14:19:00 +08:00
|
|
|
|
|
|
|
p = strstr(p, "\n\n");
|
|
|
|
matches = p && !regexec(®ex, p + 2, 0, NULL, 0);
|
|
|
|
free(to_free);
|
|
|
|
|
|
|
|
if (matches) {
|
2007-02-24 10:08:20 +08:00
|
|
|
hashcpy(sha1, commit->object.sha1);
|
2010-12-13 14:19:00 +08:00
|
|
|
found = 1;
|
2007-02-24 10:08:20 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2010-04-23 23:20:20 +08:00
|
|
|
regfree(®ex);
|
2007-02-24 10:08:20 +08:00
|
|
|
free_commit_list(list);
|
|
|
|
for (l = backup; l; l = l->next)
|
|
|
|
clear_commit_marks(l->item, ONELINE_SEEN);
|
2010-12-13 14:19:00 +08:00
|
|
|
free_commit_list(backup);
|
|
|
|
return found ? 0 : -1;
|
2007-02-24 10:08:20 +08:00
|
|
|
}
|
|
|
|
|
2009-01-18 00:09:53 +08:00
|
|
|
struct grab_nth_branch_switch_cbdata {
|
2009-01-19 16:04:25 +08:00
|
|
|
long cnt, alloc;
|
2009-01-18 00:09:53 +08:00
|
|
|
struct strbuf *buf;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
|
|
|
|
const char *email, unsigned long timestamp, int tz,
|
|
|
|
const char *message, void *cb_data)
|
|
|
|
{
|
|
|
|
struct grab_nth_branch_switch_cbdata *cb = cb_data;
|
2009-01-18 00:09:54 +08:00
|
|
|
const char *match = NULL, *target = NULL;
|
|
|
|
size_t len;
|
2009-01-19 16:04:25 +08:00
|
|
|
int nth;
|
2009-01-18 00:09:54 +08:00
|
|
|
|
|
|
|
if (!prefixcmp(message, "checkout: moving from ")) {
|
|
|
|
match = message + strlen("checkout: moving from ");
|
2009-01-21 16:37:38 +08:00
|
|
|
target = strstr(match, " to ");
|
2009-01-18 00:09:53 +08:00
|
|
|
}
|
|
|
|
|
2009-01-20 08:44:08 +08:00
|
|
|
if (!match || !target)
|
2009-01-18 00:09:53 +08:00
|
|
|
return 0;
|
|
|
|
|
2009-01-21 16:37:38 +08:00
|
|
|
len = target - match;
|
2009-01-19 16:04:25 +08:00
|
|
|
nth = cb->cnt++ % cb->alloc;
|
|
|
|
strbuf_reset(&cb->buf[nth]);
|
|
|
|
strbuf_add(&cb->buf[nth], match, len);
|
2009-01-18 00:09:53 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2010-01-20 15:17:11 +08:00
|
|
|
* Parse @{-N} syntax, return the number of characters parsed
|
|
|
|
* if successful; otherwise signal an error with negative value.
|
2009-01-18 00:09:53 +08:00
|
|
|
*/
|
2010-01-20 15:17:11 +08:00
|
|
|
static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf)
|
2009-01-18 00:09:53 +08:00
|
|
|
{
|
2009-01-19 16:04:25 +08:00
|
|
|
long nth;
|
2009-01-20 13:58:31 +08:00
|
|
|
int i, retval;
|
2009-01-18 00:09:53 +08:00
|
|
|
struct grab_nth_branch_switch_cbdata cb;
|
2009-01-18 00:09:54 +08:00
|
|
|
const char *brace;
|
|
|
|
char *num_end;
|
2009-01-18 00:09:53 +08:00
|
|
|
|
|
|
|
if (name[0] != '@' || name[1] != '{' || name[2] != '-')
|
|
|
|
return -1;
|
2009-01-18 00:09:54 +08:00
|
|
|
brace = strchr(name, '}');
|
|
|
|
if (!brace)
|
|
|
|
return -1;
|
|
|
|
nth = strtol(name+3, &num_end, 10);
|
|
|
|
if (num_end != brace)
|
2009-01-18 00:09:53 +08:00
|
|
|
return -1;
|
2009-01-19 16:04:25 +08:00
|
|
|
if (nth <= 0)
|
|
|
|
return -1;
|
|
|
|
cb.alloc = nth;
|
|
|
|
cb.buf = xmalloc(nth * sizeof(struct strbuf));
|
|
|
|
for (i = 0; i < nth; i++)
|
|
|
|
strbuf_init(&cb.buf[i], 20);
|
|
|
|
cb.cnt = 0;
|
2009-01-20 13:58:31 +08:00
|
|
|
retval = 0;
|
2009-01-20 14:18:29 +08:00
|
|
|
for_each_recent_reflog_ent("HEAD", grab_nth_branch_switch, 40960, &cb);
|
|
|
|
if (cb.cnt < nth) {
|
|
|
|
cb.cnt = 0;
|
|
|
|
for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
|
|
|
|
}
|
2009-01-19 16:04:25 +08:00
|
|
|
if (cb.cnt < nth)
|
2009-01-20 13:58:31 +08:00
|
|
|
goto release_return;
|
2009-01-19 16:04:25 +08:00
|
|
|
i = cb.cnt % nth;
|
|
|
|
strbuf_reset(buf);
|
|
|
|
strbuf_add(buf, cb.buf[i].buf, cb.buf[i].len);
|
2009-01-20 13:58:31 +08:00
|
|
|
retval = brace-name+1;
|
|
|
|
|
|
|
|
release_return:
|
2009-01-19 16:04:25 +08:00
|
|
|
for (i = 0; i < nth; i++)
|
|
|
|
strbuf_release(&cb.buf[i]);
|
|
|
|
free(cb.buf);
|
2009-01-18 00:09:54 +08:00
|
|
|
|
2009-01-20 13:58:31 +08:00
|
|
|
return retval;
|
2009-01-18 00:09:53 +08:00
|
|
|
}
|
|
|
|
|
2009-10-19 03:34:56 +08:00
|
|
|
int get_sha1_mb(const char *name, unsigned char *sha1)
|
|
|
|
{
|
|
|
|
struct commit *one, *two;
|
|
|
|
struct commit_list *mbs;
|
|
|
|
unsigned char sha1_tmp[20];
|
|
|
|
const char *dots;
|
|
|
|
int st;
|
|
|
|
|
|
|
|
dots = strstr(name, "...");
|
|
|
|
if (!dots)
|
|
|
|
return get_sha1(name, sha1);
|
|
|
|
if (dots == name)
|
|
|
|
st = get_sha1("HEAD", sha1_tmp);
|
|
|
|
else {
|
|
|
|
struct strbuf sb;
|
|
|
|
strbuf_init(&sb, dots - name);
|
|
|
|
strbuf_add(&sb, name, dots - name);
|
|
|
|
st = get_sha1(sb.buf, sha1_tmp);
|
|
|
|
strbuf_release(&sb);
|
|
|
|
}
|
|
|
|
if (st)
|
|
|
|
return st;
|
|
|
|
one = lookup_commit_reference_gently(sha1_tmp, 0);
|
|
|
|
if (!one)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (get_sha1(dots[3] ? (dots + 3) : "HEAD", sha1_tmp))
|
|
|
|
return -1;
|
|
|
|
two = lookup_commit_reference_gently(sha1_tmp, 0);
|
|
|
|
if (!two)
|
|
|
|
return -1;
|
|
|
|
mbs = get_merge_bases(one, two, 1);
|
|
|
|
if (!mbs || mbs->next)
|
|
|
|
st = -1;
|
|
|
|
else {
|
|
|
|
st = 0;
|
|
|
|
hashcpy(sha1, mbs->item->object.sha1);
|
|
|
|
}
|
|
|
|
free_commit_list(mbs);
|
|
|
|
return st;
|
|
|
|
}
|
|
|
|
|
2010-01-20 15:17:11 +08:00
|
|
|
/*
|
|
|
|
* This reads short-hand syntax that not only evaluates to a commit
|
|
|
|
* object name, but also can act as if the end user spelled the name
|
|
|
|
* of the branch from the command line.
|
|
|
|
*
|
|
|
|
* - "@{-N}" finds the name of the Nth previous branch we were on, and
|
|
|
|
* places the name of the branch in the given buf and returns the
|
|
|
|
* number of characters parsed if successful.
|
|
|
|
*
|
|
|
|
* - "<branch>@{upstream}" finds the name of the other ref that
|
|
|
|
* <branch> is configured to merge with (missing <branch> defaults
|
|
|
|
* to the current branch), and places the name of the branch in the
|
|
|
|
* given buf and returns the number of characters parsed if
|
|
|
|
* successful.
|
|
|
|
*
|
|
|
|
* If the input is not of the accepted format, it returns a negative
|
|
|
|
* number to signal an error.
|
|
|
|
*
|
|
|
|
* If the input was ok but there are not N branch switches in the
|
|
|
|
* reflog, it returns 0.
|
|
|
|
*/
|
|
|
|
int interpret_branch_name(const char *name, struct strbuf *buf)
|
|
|
|
{
|
|
|
|
char *cp;
|
|
|
|
struct branch *upstream;
|
|
|
|
int namelen = strlen(name);
|
|
|
|
int len = interpret_nth_prior_checkout(name, buf);
|
|
|
|
int tmp_len;
|
|
|
|
|
|
|
|
if (!len)
|
|
|
|
return len; /* syntax Ok, not enough switches */
|
2010-01-28 17:52:22 +08:00
|
|
|
if (0 < len && len == namelen)
|
|
|
|
return len; /* consumed all */
|
|
|
|
else if (0 < len) {
|
|
|
|
/* we have extra data, which might need further processing */
|
|
|
|
struct strbuf tmp = STRBUF_INIT;
|
|
|
|
int used = buf->len;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
strbuf_add(buf, name + len, namelen - len);
|
|
|
|
ret = interpret_branch_name(buf->buf, &tmp);
|
|
|
|
/* that data was not interpreted, remove our cruft */
|
|
|
|
if (ret < 0) {
|
|
|
|
strbuf_setlen(buf, used);
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
strbuf_reset(buf);
|
|
|
|
strbuf_addbuf(buf, &tmp);
|
|
|
|
strbuf_release(&tmp);
|
|
|
|
/* tweak for size of {-N} versus expanded ref name */
|
|
|
|
return ret - used + len;
|
|
|
|
}
|
|
|
|
|
2010-01-20 15:17:11 +08:00
|
|
|
cp = strchr(name, '@');
|
|
|
|
if (!cp)
|
|
|
|
return -1;
|
|
|
|
tmp_len = upstream_mark(cp, namelen - (cp - name));
|
|
|
|
if (!tmp_len)
|
|
|
|
return -1;
|
|
|
|
len = cp + tmp_len - name;
|
|
|
|
cp = xstrndup(name, cp - name);
|
|
|
|
upstream = branch_get(*cp ? cp : NULL);
|
|
|
|
if (!upstream
|
|
|
|
|| !upstream->merge
|
|
|
|
|| !upstream->merge[0]->dst)
|
|
|
|
return error("No upstream branch found for '%s'", cp);
|
|
|
|
free(cp);
|
|
|
|
cp = shorten_unambiguous_ref(upstream->merge[0]->dst, 0);
|
|
|
|
strbuf_reset(buf);
|
|
|
|
strbuf_addstr(buf, cp);
|
|
|
|
free(cp);
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2010-11-06 19:46:52 +08:00
|
|
|
int strbuf_branchname(struct strbuf *sb, const char *name)
|
|
|
|
{
|
|
|
|
int len = strlen(name);
|
|
|
|
if (interpret_branch_name(name, sb) == len)
|
|
|
|
return 0;
|
|
|
|
strbuf_add(sb, name, len);
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
int strbuf_check_branch_ref(struct strbuf *sb, const char *name)
|
|
|
|
{
|
|
|
|
strbuf_branchname(sb, name);
|
|
|
|
if (name[0] == '-')
|
|
|
|
return CHECK_REF_FORMAT_ERROR;
|
|
|
|
strbuf_splice(sb, 0, 0, "refs/heads/", 11);
|
|
|
|
return check_ref_format(sb->buf);
|
|
|
|
}
|
|
|
|
|
2005-08-04 13:15:49 +08:00
|
|
|
/*
|
|
|
|
* This is like "get_sha1_basic()", except it allows "sha1 expressions",
|
|
|
|
* notably "xyz^" for "parent of xyz"
|
|
|
|
*/
|
|
|
|
int get_sha1(const char *name, unsigned char *sha1)
|
|
|
|
{
|
2010-06-10 01:02:06 +08:00
|
|
|
struct object_context unused;
|
|
|
|
return get_sha1_with_context(name, sha1, &unused);
|
2007-04-24 04:55:05 +08:00
|
|
|
}
|
|
|
|
|
2009-12-07 18:10:50 +08:00
|
|
|
/* Must be called only when object_name:filename doesn't exist. */
|
|
|
|
static void diagnose_invalid_sha1_path(const char *prefix,
|
|
|
|
const char *filename,
|
|
|
|
const unsigned char *tree_sha1,
|
|
|
|
const char *object_name)
|
|
|
|
{
|
|
|
|
struct stat st;
|
|
|
|
unsigned char sha1[20];
|
|
|
|
unsigned mode;
|
|
|
|
|
|
|
|
if (!prefix)
|
|
|
|
prefix = "";
|
|
|
|
|
|
|
|
if (!lstat(filename, &st))
|
|
|
|
die("Path '%s' exists on disk, but not in '%s'.",
|
|
|
|
filename, object_name);
|
|
|
|
if (errno == ENOENT || errno == ENOTDIR) {
|
|
|
|
char *fullname = xmalloc(strlen(filename)
|
|
|
|
+ strlen(prefix) + 1);
|
|
|
|
strcpy(fullname, prefix);
|
|
|
|
strcat(fullname, filename);
|
|
|
|
|
|
|
|
if (!get_tree_entry(tree_sha1, fullname,
|
|
|
|
sha1, &mode)) {
|
|
|
|
die("Path '%s' exists, but not '%s'.\n"
|
2011-03-31 17:17:34 +08:00
|
|
|
"Did you mean '%s:%s' aka '%s:./%s'?",
|
2009-12-07 18:10:50 +08:00
|
|
|
fullname,
|
|
|
|
filename,
|
|
|
|
object_name,
|
2011-03-31 17:17:34 +08:00
|
|
|
fullname,
|
|
|
|
object_name,
|
|
|
|
filename);
|
2009-12-07 18:10:50 +08:00
|
|
|
}
|
|
|
|
die("Path '%s' does not exist in '%s'",
|
|
|
|
filename, object_name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Must be called only when :stage:filename doesn't exist. */
|
|
|
|
static void diagnose_invalid_index_path(int stage,
|
|
|
|
const char *prefix,
|
|
|
|
const char *filename)
|
|
|
|
{
|
|
|
|
struct stat st;
|
|
|
|
struct cache_entry *ce;
|
|
|
|
int pos;
|
|
|
|
unsigned namelen = strlen(filename);
|
|
|
|
unsigned fullnamelen;
|
|
|
|
char *fullname;
|
|
|
|
|
|
|
|
if (!prefix)
|
|
|
|
prefix = "";
|
|
|
|
|
|
|
|
/* Wrong stage number? */
|
|
|
|
pos = cache_name_pos(filename, namelen);
|
|
|
|
if (pos < 0)
|
|
|
|
pos = -pos - 1;
|
2010-02-28 23:49:15 +08:00
|
|
|
if (pos < active_nr) {
|
|
|
|
ce = active_cache[pos];
|
|
|
|
if (ce_namelen(ce) == namelen &&
|
|
|
|
!memcmp(ce->name, filename, namelen))
|
|
|
|
die("Path '%s' is in the index, but not at stage %d.\n"
|
|
|
|
"Did you mean ':%d:%s'?",
|
|
|
|
filename, stage,
|
|
|
|
ce_stage(ce), filename);
|
|
|
|
}
|
2009-12-07 18:10:50 +08:00
|
|
|
|
|
|
|
/* Confusion between relative and absolute filenames? */
|
|
|
|
fullnamelen = namelen + strlen(prefix);
|
|
|
|
fullname = xmalloc(fullnamelen + 1);
|
|
|
|
strcpy(fullname, prefix);
|
|
|
|
strcat(fullname, filename);
|
|
|
|
pos = cache_name_pos(fullname, fullnamelen);
|
|
|
|
if (pos < 0)
|
|
|
|
pos = -pos - 1;
|
2010-02-28 23:49:15 +08:00
|
|
|
if (pos < active_nr) {
|
|
|
|
ce = active_cache[pos];
|
|
|
|
if (ce_namelen(ce) == fullnamelen &&
|
|
|
|
!memcmp(ce->name, fullname, fullnamelen))
|
|
|
|
die("Path '%s' is in the index, but not '%s'.\n"
|
2011-03-31 17:17:34 +08:00
|
|
|
"Did you mean ':%d:%s' aka ':%d:./%s'?",
|
2010-02-28 23:49:15 +08:00
|
|
|
fullname, filename,
|
2011-03-31 17:17:34 +08:00
|
|
|
ce_stage(ce), fullname,
|
|
|
|
ce_stage(ce), filename);
|
2010-02-28 23:49:15 +08:00
|
|
|
}
|
2009-12-07 18:10:50 +08:00
|
|
|
|
|
|
|
if (!lstat(filename, &st))
|
|
|
|
die("Path '%s' exists on disk, but not in the index.", filename);
|
|
|
|
if (errno == ENOENT || errno == ENOTDIR)
|
|
|
|
die("Path '%s' does not exist (neither on disk nor in the index).",
|
|
|
|
filename);
|
|
|
|
|
|
|
|
free(fullname);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, int gently, const char *prefix)
|
2010-06-10 01:02:06 +08:00
|
|
|
{
|
|
|
|
struct object_context oc;
|
|
|
|
int ret;
|
|
|
|
ret = get_sha1_with_context_1(name, sha1, &oc, gently, prefix);
|
|
|
|
*mode = oc.mode;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2010-11-28 11:37:32 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2010-06-10 01:02:06 +08:00
|
|
|
int get_sha1_with_context_1(const char *name, unsigned char *sha1,
|
|
|
|
struct object_context *oc,
|
|
|
|
int gently, const char *prefix)
|
2007-04-24 04:55:05 +08:00
|
|
|
{
|
|
|
|
int ret, bracket_depth;
|
2006-04-22 08:31:04 +08:00
|
|
|
int namelen = strlen(name);
|
|
|
|
const char *cp;
|
2006-04-19 07:45:16 +08:00
|
|
|
|
2010-06-10 01:02:06 +08:00
|
|
|
memset(oc, 0, sizeof(*oc));
|
|
|
|
oc->mode = S_IFINVALID;
|
2006-04-22 08:31:04 +08:00
|
|
|
ret = get_sha1_1(name, namelen, sha1);
|
|
|
|
if (!ret)
|
|
|
|
return ret;
|
|
|
|
/* sha1:path --> object name of path in ent sha1
|
2010-11-28 11:37:32 +08:00
|
|
|
* :path -> object name of absolute path in index
|
|
|
|
* :./path -> object name of path relative to cwd in index
|
2006-04-22 08:31:04 +08:00
|
|
|
* :[0-3]:path -> object name of path in index at stage
|
2010-09-25 00:43:59 +08:00
|
|
|
* :/foo -> recent commit matching foo
|
2006-04-22 08:31:04 +08:00
|
|
|
*/
|
|
|
|
if (name[0] == ':') {
|
|
|
|
int stage = 0;
|
|
|
|
struct cache_entry *ce;
|
2010-11-28 11:37:32 +08:00
|
|
|
char *new_path = NULL;
|
2006-04-22 08:31:04 +08:00
|
|
|
int pos;
|
2010-12-13 11:01:14 +08:00
|
|
|
if (namelen > 2 && name[1] == '/') {
|
|
|
|
struct commit_list *list = NULL;
|
|
|
|
for_each_ref(handle_one_ref, &list);
|
|
|
|
return get_sha1_oneline(name + 2, sha1, list);
|
|
|
|
}
|
2006-04-22 08:31:04 +08:00
|
|
|
if (namelen < 3 ||
|
|
|
|
name[2] != ':' ||
|
|
|
|
name[1] < '0' || '3' < name[1])
|
|
|
|
cp = name + 1;
|
|
|
|
else {
|
|
|
|
stage = name[1] - '0';
|
|
|
|
cp = name + 3;
|
2006-04-19 07:45:16 +08:00
|
|
|
}
|
2010-12-10 05:38:05 +08:00
|
|
|
new_path = resolve_relative_path(cp);
|
|
|
|
if (!new_path) {
|
|
|
|
namelen = namelen - (cp - name);
|
|
|
|
} else {
|
|
|
|
cp = new_path;
|
|
|
|
namelen = strlen(cp);
|
|
|
|
}
|
2010-06-10 01:02:06 +08:00
|
|
|
|
|
|
|
strncpy(oc->path, cp,
|
|
|
|
sizeof(oc->path));
|
|
|
|
oc->path[sizeof(oc->path)-1] = '\0';
|
|
|
|
|
2006-04-22 08:31:04 +08:00
|
|
|
if (!active_cache)
|
|
|
|
read_cache();
|
|
|
|
pos = cache_name_pos(cp, namelen);
|
|
|
|
if (pos < 0)
|
|
|
|
pos = -pos - 1;
|
|
|
|
while (pos < active_nr) {
|
|
|
|
ce = active_cache[pos];
|
|
|
|
if (ce_namelen(ce) != namelen ||
|
|
|
|
memcmp(ce->name, cp, namelen))
|
|
|
|
break;
|
|
|
|
if (ce_stage(ce) == stage) {
|
2006-08-23 14:49:00 +08:00
|
|
|
hashcpy(sha1, ce->sha1);
|
2010-09-29 19:35:24 +08:00
|
|
|
oc->mode = ce->ce_mode;
|
2010-11-28 11:37:32 +08:00
|
|
|
free(new_path);
|
2006-04-22 08:31:04 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2006-05-09 06:44:06 +08:00
|
|
|
pos++;
|
2006-04-22 08:31:04 +08:00
|
|
|
}
|
2009-12-07 18:10:50 +08:00
|
|
|
if (!gently)
|
|
|
|
diagnose_invalid_index_path(stage, prefix, cp);
|
2010-11-28 11:37:32 +08:00
|
|
|
free(new_path);
|
2006-04-22 08:31:04 +08:00
|
|
|
return -1;
|
|
|
|
}
|
2006-05-19 15:29:43 +08:00
|
|
|
for (cp = name, bracket_depth = 0; *cp; cp++) {
|
|
|
|
if (*cp == '{')
|
|
|
|
bracket_depth++;
|
|
|
|
else if (bracket_depth && *cp == '}')
|
|
|
|
bracket_depth--;
|
|
|
|
else if (!bracket_depth && *cp == ':')
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (*cp == ':') {
|
2006-04-22 08:31:04 +08:00
|
|
|
unsigned char tree_sha1[20];
|
2009-12-07 18:10:50 +08:00
|
|
|
char *object_name = NULL;
|
|
|
|
if (!gently) {
|
|
|
|
object_name = xmalloc(cp-name+1);
|
|
|
|
strncpy(object_name, name, cp-name);
|
|
|
|
object_name[cp-name] = '\0';
|
|
|
|
}
|
|
|
|
if (!get_sha1_1(name, cp-name, tree_sha1)) {
|
|
|
|
const char *filename = cp+1;
|
2010-11-28 11:37:32 +08:00
|
|
|
char *new_filename = NULL;
|
|
|
|
|
|
|
|
new_filename = resolve_relative_path(filename);
|
|
|
|
if (new_filename)
|
|
|
|
filename = new_filename;
|
2010-06-10 01:02:06 +08:00
|
|
|
ret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode);
|
2009-12-07 18:10:50 +08:00
|
|
|
if (!gently) {
|
|
|
|
diagnose_invalid_sha1_path(prefix, filename,
|
|
|
|
tree_sha1, object_name);
|
|
|
|
free(object_name);
|
|
|
|
}
|
2010-06-10 01:02:06 +08:00
|
|
|
hashcpy(oc->tree, tree_sha1);
|
|
|
|
strncpy(oc->path, filename,
|
|
|
|
sizeof(oc->path));
|
|
|
|
oc->path[sizeof(oc->path)-1] = '\0';
|
|
|
|
|
2010-11-28 11:37:32 +08:00
|
|
|
free(new_filename);
|
2009-12-07 18:10:50 +08:00
|
|
|
return ret;
|
|
|
|
} else {
|
|
|
|
if (!gently)
|
|
|
|
die("Invalid object name '%s'.", object_name);
|
|
|
|
}
|
2006-04-19 07:45:16 +08:00
|
|
|
}
|
|
|
|
return ret;
|
2005-08-04 13:15:49 +08:00
|
|
|
}
|