/*
** Copyright (c) 2006 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License version 2 as published by the Free Software Foundation.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code used to convert user-supplied object names into
** canonical UUIDs.
**
** A user-supplied object name is any unique prefix of a valid UUID but
** not necessarily in canonical form.
*/
#include "config.h"
#include "name.h"
#include <assert.h>
/*
** This routine takes a user-entered UUID which might be in mixed
** case and might only be a prefix of the full UUID and converts it
** into the full-length UUID in canonical form.
**
** If the input is not a UUID or a UUID prefix, then try to resolve
** the name as a tag.
**
** Return the number of errors.
*/
int name_to_uuid(Blob *pName, int iErrPriority){
int rc;
int sz;
sz = blob_size(pName);
if( sz>UUID_SIZE || sz<4 || !validate16(blob_buffer(pName), sz) ){
Blob uuid;
static const char prefix[] = "tag:";
static const int preflen = sizeof(prefix)-1;
const char *zName = blob_str(pName);
if( strncmp(zName, prefix, preflen)==0 ){
zName += preflen;
}
sym_tag_to_uuid(zName, &uuid);
if( blob_size(&uuid)==0 ){
fossil_error(iErrPriority, "not a valid object name: %s", zName);
blob_reset(&uuid);
return 1;
}else{
blob_reset(pName);
*pName = uuid;
return 0;
}
}
blob_materialize(pName);
canonical16(blob_buffer(pName), sz);
if( sz==UUID_SIZE ){
rc = db_int(1, "SELECT 0 FROM blob WHERE uuid=%B", pName);
if( rc ){
fossil_error(iErrPriority, "no such artifact: %b", pName);
blob_reset(pName);
}
}else if( sz<UUID_SIZE && sz>=4 ){
Stmt q;
char zOrig[UUID_SIZE+1];
memcpy(zOrig, blob_buffer(pName), sz);
zOrig[sz] = 0;
blob_reset(pName);
db_prepare(&q, "SELECT uuid FROM blob"
" WHERE uuid>='%s'"
" AND substr(uuid,1,%d)='%s'",
zOrig, sz, zOrig);
if( db_step(&q)!=SQLITE_ROW ){
db_finalize(&q);
fossil_error(iErrPriority, "no artifacts match the prefix \"%s\"", zOrig);
return 1;
}
blob_append(pName, db_column_text(&q, 0), db_column_bytes(&q, 0));
if( db_step(&q)==SQLITE_ROW ){
fossil_error(iErrPriority,
"multiple artifacts match the prefix \"%s\"",
zOrig
);
blob_reset(pName);
db_finalize(&q);
return 1;
}
db_finalize(&q);
rc = 0;
}else{
rc = 0;
}
return rc;
}
/*
** This routine takes a name which might be a tag and attempts to
** produce a UUID. The UUID (if any) is returned in the blob pointed
** to by the second argument.
**
** Return as follows:
** 0 Name is not a tag
** 1 A single UUID was found
** 2 More than one UUID was found, so this is presumably a
** propagating tag. The return UUID is the most recent,
** which is most likely to be the one wanted.
*/
int tag_to_uuid(const char *pName, Blob *pUuid,const char *pPrefix){
Stmt q;
int count = 0;
db_prepare(&q,
"SELECT (SELECT uuid FROM blob WHERE rid=objid)"
" FROM tagxref JOIN event ON rid=objid"
" WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q||%Q)"
" AND tagtype>0"
" AND value IS NULL"
" ORDER BY event.mtime DESC",
pPrefix,
pName
);
blob_zero(pUuid);
while( db_step(&q)==SQLITE_ROW ){
count++;
if(count>1){
break;
}
db_column_blob(&q, 0, pUuid);
}
db_finalize(&q);
return count;
}
/*
** This routine takes a name which might be a symbolic tag and
** attempts to produce a UUID. See tag_to_uuid.
*/
int sym_tag_to_uuid(const char *pName, Blob *pUuid){
return tag_to_uuid(pName,pUuid,"sym-");
}
/*
** COMMAND: test-name-to-id
**
** Convert a name to a full artifact ID.
*/
void test_name_to_id(void){
int i;
Blob name;
db_must_be_within_tree();
for(i=2; i<g.argc; i++){
blob_init(&name, g.argv[i], -1);
printf("%s -> ", g.argv[i]);
if( name_to_uuid(&name, 1) ){
printf("ERROR: %s\n", g.zErrMsg);
fossil_error_reset();
}else{
printf("%s\n", blob_buffer(&name));
}
blob_reset(&name);
}
}
/*
** Convert a name to a rid. If the name is a small integer value then
** just use atoi() to do the conversion. If the name contains alphabetic
** characters or is not an existing rid, then use name_to_uuid then
** convert the uuid to a rid.
**
** This routine is used in test routines to resolve command-line inputs
** into a rid.
*/
int name_to_rid(const char *zName){
int i;
int rid;
Blob name;
for(i=0; zName[i] && isdigit(zName[i]); i++){}
if( zName[i]==0 ){
rid = atoi(zName);
if( db_exists("SELECT 1 FROM blob WHERE rid=%d", rid) ){
return rid;
}
}
blob_init(&name, zName, -1);
if( name_to_uuid(&name, 1) ){
fossil_fatal("%s", g.zErrMsg);
}
rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%B", &name);
blob_reset(&name);
return rid;
}