Add 'ident' conversion.

The 'ident' attribute set to path squashes "$ident:<any bytes
except dollor sign>$" to "$ident$" upon checkin, and expands it
to "$ident: <blob SHA-1> $" upon checkout.

As we have two conversions that affect checkin/checkout paths,
clarify how they interact with each other.

Signed-off-by: Junio C Hamano <junkio@cox.net>
This commit is contained in:
Junio C Hamano
2007-04-21 19:09:02 -07:00
parent da94faf671
commit 3fed15f568
3 changed files with 252 additions and 12 deletions

View File

@ -78,12 +78,17 @@ are attributes-aware.
Checking-out and checking-in Checking-out and checking-in
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The attribute `crlf` affects how the contents stored in the These attributes affect how the contents stored in the
repository are copied to the working tree files when commands repository are copied to the working tree files when commands
such as `git checkout` and `git merge` run. It also affects how such as `git checkout` and `git merge` run. They also affect how
git stores the contents you prepare in the working tree in the git stores the contents you prepare in the working tree in the
repository upon `git add` and `git commit`. repository upon `git add` and `git commit`.
`crlf`
^^^^^^
This attribute controls the line-ending convention.
Set:: Set::
Setting the `crlf` attribute on a path is meant to mark Setting the `crlf` attribute on a path is meant to mark
@ -129,6 +134,28 @@ converted to LF upon checkin, but there is no conversion done
upon checkout. upon checkout.
`ident`
^^^^^^^
When the attribute `ident` is set to a path, git replaces
`$ident$` in the blob object with `$ident:`, followed by
40-character hexadecimal blob object name, followed by a dollar
sign `$` upon checkout. Any byte sequence that begins with
`$ident:` and ends with `$` in the worktree file is replaced
with `$ident$` upon check-in.
Interaction between checkin/checkout attributes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In the check-in codepath, the worktree file is first converted
with `ident` (if specified), and then with `crlf` (again, if
specified and applicable).
In the check-out codepath, the blob content is first converted
with `crlf`, and then `ident`.
Generating diff text Generating diff text
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~

194
convert.c
View File

@ -1,5 +1,6 @@
#include "cache.h" #include "cache.h"
#include "attr.h" #include "attr.h"
#include "run-command.h"
/* /*
* convert.c - convert a file when checking it out and checking it in. * convert.c - convert a file when checking it out and checking it in.
@ -203,10 +204,152 @@ static char *crlf_to_worktree(const char *path, const char *src, unsigned long *
static void setup_convert_check(struct git_attr_check *check) static void setup_convert_check(struct git_attr_check *check)
{ {
static struct git_attr *attr_crlf; static struct git_attr *attr_crlf;
static struct git_attr *attr_ident;
if (!attr_crlf) if (!attr_crlf) {
attr_crlf = git_attr("crlf", 4); attr_crlf = git_attr("crlf", 4);
check->attr = attr_crlf; attr_ident = git_attr("ident", 5);
}
check[0].attr = attr_crlf;
check[1].attr = attr_ident;
}
static int count_ident(const char *cp, unsigned long size)
{
/*
* "$ident: 0000000000000000000000000000000000000000 $" <=> "$ident$"
*/
int cnt = 0;
char ch;
while (size) {
ch = *cp++;
size--;
if (ch != '$')
continue;
if (size < 6)
break;
if (memcmp("ident", cp, 5))
continue;
ch = cp[5];
cp += 6;
size -= 6;
if (ch == '$')
cnt++; /* $ident$ */
if (ch != ':')
continue;
/*
* "$ident: ... "; scan up to the closing dollar sign and discard.
*/
while (size) {
ch = *cp++;
size--;
if (ch == '$') {
cnt++;
break;
}
}
}
return cnt;
}
static char *ident_to_git(const char *path, const char *src, unsigned long *sizep, int ident)
{
int cnt;
unsigned long size;
char *dst, *buf;
if (!ident)
return NULL;
size = *sizep;
cnt = count_ident(src, size);
if (!cnt)
return NULL;
buf = xmalloc(size);
for (dst = buf; size; size--) {
char ch = *src++;
*dst++ = ch;
if ((ch == '$') && (6 <= size) &&
!memcmp("ident:", src, 6)) {
unsigned long rem = size - 6;
const char *cp = src + 6;
do {
ch = *cp++;
if (ch == '$')
break;
rem--;
} while (rem);
if (!rem)
continue;
memcpy(dst, "ident$", 6);
dst += 6;
size -= (cp - src);
src = cp;
}
}
*sizep = dst - buf;
return buf;
}
static char *ident_to_worktree(const char *path, const char *src, unsigned long *sizep, int ident)
{
int cnt;
unsigned long size;
char *dst, *buf;
unsigned char sha1[20];
if (!ident)
return NULL;
size = *sizep;
cnt = count_ident(src, size);
if (!cnt)
return NULL;
hash_sha1_file(src, size, "blob", sha1);
buf = xmalloc(size + cnt * 43);
for (dst = buf; size; size--) {
const char *cp;
char ch = *src++;
*dst++ = ch;
if ((ch != '$') || (size < 6) || memcmp("ident", src, 5))
continue;
if (src[5] == ':') {
/* discard up to but not including the closing $ */
unsigned long rem = size - 6;
cp = src + 6;
do {
ch = *cp++;
if (ch == '$')
break;
rem--;
} while (rem);
if (!rem)
continue;
size -= (cp - src);
} else if (src[5] == '$')
cp = src + 5;
else
continue;
memcpy(dst, "ident: ", 7);
dst += 7;
memcpy(dst, sha1_to_hex(sha1), 40);
dst += 40;
*dst++ = ' ';
size -= (cp - src);
src = cp;
*dst++ = *src++;
size--;
}
*sizep = dst - buf;
return buf;
} }
static int git_path_check_crlf(const char *path, struct git_attr_check *check) static int git_path_check_crlf(const char *path, struct git_attr_check *check)
@ -224,26 +367,57 @@ static int git_path_check_crlf(const char *path, struct git_attr_check *check)
return CRLF_GUESS; return CRLF_GUESS;
} }
static int git_path_check_ident(const char *path, struct git_attr_check *check)
{
const char *value = check->value;
return !!ATTR_TRUE(value);
}
char *convert_to_git(const char *path, const char *src, unsigned long *sizep) char *convert_to_git(const char *path, const char *src, unsigned long *sizep)
{ {
struct git_attr_check check[1]; struct git_attr_check check[2];
int crlf = CRLF_GUESS; int crlf = CRLF_GUESS;
int ident = 0;
char *buf, *buf2;
setup_convert_check(check); setup_convert_check(check);
if (!git_checkattr(path, 1, check)) { if (!git_checkattr(path, ARRAY_SIZE(check), check)) {
crlf = git_path_check_crlf(path, check); crlf = git_path_check_crlf(path, check + 0);
ident = git_path_check_ident(path, check + 1);
} }
return crlf_to_git(path, src, sizep, crlf);
buf = crlf_to_git(path, src, sizep, crlf);
buf2 = ident_to_git(path, buf ? buf : src, sizep, ident);
if (buf2) {
free(buf);
buf = buf2;
}
return buf;
} }
char *convert_to_working_tree(const char *path, const char *src, unsigned long *sizep) char *convert_to_working_tree(const char *path, const char *src, unsigned long *sizep)
{ {
struct git_attr_check check[1]; struct git_attr_check check[2];
int crlf = CRLF_GUESS; int crlf = CRLF_GUESS;
int ident = 0;
char *buf, *buf2;
setup_convert_check(check); setup_convert_check(check);
if (!git_checkattr(path, 1, check)) { if (!git_checkattr(path, ARRAY_SIZE(check), check)) {
crlf = git_path_check_crlf(path, check); crlf = git_path_check_crlf(path, check + 0);
ident = git_path_check_ident(path, check + 1);
} }
return crlf_to_worktree(path, src, sizep, crlf);
buf = ident_to_worktree(path, src, sizep, ident);
buf2 = crlf_to_worktree(path, buf ? buf : src, sizep, crlf);
if (buf2) {
free(buf);
buf = buf2;
}
return buf;
} }

39
t/t0021-conversion.sh Executable file
View File

@ -0,0 +1,39 @@
#!/bin/sh
test_description='blob conversion via gitattributes'
. ./test-lib.sh
test_expect_success setup '
{
echo "*.i ident"
} >.gitattributes &&
{
echo a b c d e f g h i j k l m
echo n o p q r s t u v w x y z
echo '\''$ident$'\''
} >test &&
cat test >test.t &&
cat test >test.o &&
cat test >test.i &&
git add test test.t test.i &&
rm -f test test.t test.i &&
git checkout -- test test.t test.i
'
script='s/^\$ident: \([0-9a-f]*\) \$/\1/p'
test_expect_success check '
cmp test.o test &&
cmp test.o test.t &&
# ident should be stripped in the repository
git diff --raw --exit-code :test :test.i &&
id=$(git rev-parse --verify :test) &&
embedded=$(sed -ne "$script" test.i) &&
test "z$id" = "z$embedded"
'
test_done