diff -uNr monotone-0.19/file_io.cc monotone-0.19-symlink-clean/file_io.cc
--- monotone-0.19/file_io.cc	2005-04-23 19:54:17.000000000 +0900
+++ monotone-0.19-symlink-clean/file_io.cc	2005-06-27 01:53:38.000000000 +0900
@@ -16,6 +16,7 @@
 #include "lua.hh"
 #include "sanity.hh"
 #include "transforms.hh"
+#include "platform.hh"
 
 // this file deals with talking to the filesystem, loading and
 // saving files.
@@ -240,13 +241,13 @@
 bool 
 file_exists(file_path const & p) 
 { 
-  return fs::exists(localized(p)); 
+  return fs::exists(localized(p)) || fs::symbolic_link_exists(localized(p)); 
 }
 
 bool 
 file_exists(local_path const & p) 
 { 
-  return fs::exists(localized(p)); 
+  return fs::exists(localized(p)) || fs::symbolic_link_exists(localized(p)); 
 }
 
 void 
@@ -355,6 +356,15 @@
 read_data_impl(fs::path const & p,
                data & dat)
 {
+  if (fs::symbolic_link_exists(p)) {
+    string in;
+    char *rlbuf = symlink_readlink(p.native_file_string().c_str());
+    CryptoPP::StringSource f(rlbuf, true, new CryptoPP::StringSink(in));
+    dat = in;
+    free(rlbuf);
+    return;
+  }
+
   if (!fs::exists(p))
     throw oops("file '" + p.string() + "' does not exist");
   
@@ -602,7 +612,8 @@
           || di->string() == "." 
           || di->string() == "..") 
         ;                       // ignore
-      else if (fs::is_directory(entry))
+      else if (fs::is_directory(entry)
+               && !fs::symbolic_link_exists(entry))
         walk_tree_recursive(entry, rel_entry, walker);
       else
         {
@@ -630,7 +641,7 @@
 {
   if (fs::exists(localized(path)))
     {
-      if (! fs::is_directory(localized(path)))
+      if (! fs::is_directory(localized(path)) || fs::symbolic_link_exists(localized(path)))
         walker.visit_file(path);
       else
         {
diff -uNr monotone-0.19/lua.cc monotone-0.19-symlink-clean/lua.cc
--- monotone-0.19/lua.cc	2005-04-28 04:06:37.000000000 +0900
+++ monotone-0.19-symlink-clean/lua.cc	2005-06-27 00:25:11.000000000 +0900
@@ -105,6 +105,32 @@
   }
 
   static int
+  monotone_is_symlink_for_lua(lua_State *L)
+  {
+    const char *path = lua_tostring(L, -1);
+    lua_pushboolean(L, is_symlink(path));
+    return 1;
+  }
+
+  static int
+  monotone_make_symlink_for_lua(lua_State *L)
+  {
+    const char *path = lua_tostring(L, -1);
+    lua_pushnumber(L, make_symlink(path));
+    return 1;
+  }
+
+  static int
+  monotone_symlink_readlink_for_lua(lua_State *L)
+  {
+    const char *path = lua_tostring(L, -1);
+    char *buf = symlink_readlink(path);
+    lua_pushstring(L, buf);
+    free(buf);
+    return 1;
+  }
+
+  static int
   monotone_spawn_for_lua(lua_State *L)
   {
     int n = lua_gettop(L);
@@ -178,6 +204,9 @@
   lua_register(st, "existsonpath", monotone_existsonpath_for_lua);
   lua_register(st, "is_executable", monotone_is_executable_for_lua);
   lua_register(st, "make_executable", monotone_make_executable_for_lua);
+  lua_register(st, "is_symlink", monotone_is_symlink_for_lua);
+  lua_register(st, "make_symlink", monotone_make_symlink_for_lua);
+  lua_register(st, "symlink_readlink", monotone_symlink_readlink_for_lua);
   lua_register(st, "spawn", monotone_spawn_for_lua);
   lua_register(st, "wait", monotone_wait_for_lua);
   lua_register(st, "kill", monotone_kill_for_lua);
diff -uNr monotone-0.19/platform.hh monotone-0.19-symlink-clean/platform.hh
--- monotone-0.19/platform.hh	2005-04-20 14:34:49.000000000 +0900
+++ monotone-0.19-symlink-clean/platform.hh	2005-06-27 00:29:23.000000000 +0900
@@ -16,10 +16,13 @@
 void read_password(std::string const & prompt, char * buf, size_t bufsz);
 void get_system_flavour(std::string & ident);
 bool is_executable(const char *path);
+bool is_symlink(const char *path);
 
 // For LUA
 int existsonpath(const char *exe);
 int make_executable(const char *path);
+int make_symlink(const char *path);
+char *symlink_readlink(const char *path);
 int process_spawn(const char * const argv[]);
 int process_wait(int pid, int *res);
 int process_kill(int pid, int signal);
diff -uNr monotone-0.19/process.cc monotone-0.19-symlink-clean/process.cc
--- monotone-0.19/process.cc	1970-01-01 09:00:00.000000000 +0900
+++ monotone-0.19-symlink-clean/process.cc	2005-06-27 10:49:20.000000000 +0900
@@ -0,0 +1,196 @@
+// copyright (C) 2005 Jon Bright <jon@siliconcircus.com>
+// all rights reserved.
+// licensed to the public under the terms of the GNU GPL (>= 2)
+// see the file COPYING for details
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <sstream>
+
+#include "sanity.hh"
+#include "platform.hh"
+
+int existsonpath(const char *exe)
+{
+	const char * const args[3] = {"which", exe, NULL};
+	int pid;
+	int res;
+	pid = process_spawn(args);
+	if (pid==-1)
+		return -1;
+	if (process_wait(pid, &res))
+		return -1;
+	if (res==0)
+		return 0;
+	return -1;
+}
+
+#ifndef S_ISLNK
+#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
+#endif
+
+bool is_symlink(const char *path)
+{
+	struct stat s;
+
+	int rc = lstat(path, &s);
+	N(rc != -1, F("lstat() error on file %s") % path);
+
+	return S_ISLNK(s.st_mode) ? true : false;
+}
+
+#define SYMLINK_MAGIC "SyMlInK:"
+#define SYMLINK_MAGIC_SIZE 8
+
+char *symlink_readlink(const char *path)
+{
+	struct stat s;
+	int rc = lstat(path, &s);
+	N(rc != -1, F("lstat() error on file %s") % path);
+	// Linux fills in s.st_size with the length of the path pointed to
+	int bufsize = s.st_size+1+SYMLINK_MAGIC_SIZE;
+	char *buf = (char *)malloc(bufsize);
+	N(buf != NULL, F("malloc() failed reading symbolic link %s") % path);
+	for (;;) {
+		rc = readlink(path, &buf[SYMLINK_MAGIC_SIZE], bufsize-SYMLINK_MAGIC_SIZE);
+		N(rc != -1, F("readlink() error on file %s") % path);
+		if (rc+SYMLINK_MAGIC_SIZE < bufsize) {
+			buf[rc+SYMLINK_MAGIC_SIZE] = '\0';
+			break;
+		}
+		bufsize = bufsize * 2;
+		buf = (char *)realloc(buf, bufsize);
+		N(buf != NULL, F("realloc() failed reading symbolic link %s") % path);
+	}
+	memcpy(buf, SYMLINK_MAGIC, SYMLINK_MAGIC_SIZE);
+	return buf;
+}
+
+static int ln_sf(const char *path, const char *newpath)
+{
+	int rc = unlink(newpath);
+	N(rc != -1, F("unlink() error on file %s") % newpath);
+	return symlink(path, newpath);
+}
+
+int make_symlink(const char *path)
+{
+	struct stat s;
+	int rc = lstat(path, &s);
+	N(rc != -1, F("stat() error on file %s") % path);
+	// after add, the symbolic link is not overwritten
+	if (S_ISLNK(s.st_mode))
+		return 0;
+	int fd = open(path, O_RDONLY);
+	N(fd != -1, F("open() failed reading symbolic link %s") % path);
+	off_t pos = 0, len = SYMLINK_MAGIC_SIZE;
+	ssize_t r;
+	char magicbuf[SYMLINK_MAGIC_SIZE];
+	do {
+		r = read(fd, &magicbuf[pos], len);
+		if (r == -1 && errno == EINTR)
+			continue;
+		N(r > 0, F("read() failed reading symbolic link %s") % path);
+		pos += r;
+		len -= r;
+	} while (len);
+	if (pos < SYMLINK_MAGIC_SIZE
+	    || memcmp(magicbuf, SYMLINK_MAGIC, SYMLINK_MAGIC_SIZE)) {
+		W(F("symbolic link magic not present in %s") % path);
+		return -1;
+	}
+	len = s.st_size;
+	char *buf = (char *)malloc(len+1);
+	N(buf != NULL, F("malloc() failed reading symbolic link %s") % path);
+	pos = 0;
+	do {
+		r = read(fd, &buf[pos], len);
+		if (r == -1 && errno == EINTR)
+			continue;
+		N(r > 0, F("read() failed reading symbolic link %s") % path);
+		pos += r;
+		len -= r;
+	} while (len);
+	buf[pos] = '\0';
+	rc = close(fd);
+	N(rc != -1, F("close() failed reading symbolic link %s") % path);
+	rc = ln_sf(buf, path);
+	free(buf);
+	N(rc != -1, F("symlink() error on file %s") % path);
+	return rc;
+}
+
+bool is_executable(const char *path)
+{
+	struct stat s;
+
+	int rc = lstat(path, &s);
+	N(rc != -1, F("lstat() error on file %s") % path);
+
+	if (S_ISLNK(s.st_mode))
+		return false;
+	return s.st_mode & S_IXUSR;
+}
+
+int make_executable(const char *path)
+{
+	mode_t mode;
+	struct stat s;
+	if (stat(path, &s))
+		return -1;
+	mode = s.st_mode;
+	mode |= S_IXUSR;
+	return chmod(path, mode);
+}
+
+int process_spawn(const char * const argv[])
+{
+	{
+		std::ostringstream cmdline_ss;
+		for (const char *const *i = argv; *i; ++i)
+		{
+			if (i)
+				cmdline_ss << ", ";
+			cmdline_ss << "'" << *i << "'";
+		}
+		L(F("spawning command: %s\n") % cmdline_ss.str());
+	}       
+	pid_t pid;
+	pid = fork();
+	switch (pid)
+	{
+		case -1: /* Error */
+			return -1;
+		case 0: /* Child */
+			execvp(argv[0], (char * const *)argv);
+			return -1;
+		default: /* Parent */
+			return (int)pid;
+	}
+}
+
+int process_wait(int pid, int *res)
+{
+	int status;
+	pid = waitpid((pid_t)pid, &status, 0);
+	if (WIFEXITED(status))	  
+		*res = WEXITSTATUS(status);
+	else
+		*res = -1;
+	return 0;
+}
+
+int process_kill(int pid, int signal)
+{
+	return kill((pid_t)pid, signal);
+}
+
+int process_sleep(unsigned int seconds)
+{
+	return sleep(seconds);
+}
diff -uNr monotone-0.19/std_hooks.lua monotone-0.19-symlink-clean/std_hooks.lua
--- monotone-0.19/std_hooks.lua	2005-04-28 04:06:38.000000000 +0900
+++ monotone-0.19-symlink-clean/std_hooks.lua	2005-06-27 00:24:51.000000000 +0900
@@ -41,6 +41,15 @@
       end 
    end
 
+attr_init_functions["symlink"] = 
+   function(filename)
+      if (is_symlink(filename)) then 
+        return "true"
+      else 
+        return nil 
+      end 
+   end
+
 if (attr_functions == nil) then
    attr_functions = {}
 end
@@ -53,6 +62,13 @@
       end
    end
 
+attr_functions["symlink"] = 
+   function(filename, value) 
+      if (value == "true") then
+         make_symlink(filename)
+      end
+   end
+
 
 function ignore_file(name)
    if (string.find(name, "%.a$")) then return true end
diff -uNr monotone-0.19/tests/t_add_symlink.at monotone-0.19-symlink-clean/tests/t_add_symlink.at
--- monotone-0.19/tests/t_add_symlink.at	1970-01-01 09:00:00.000000000 +0900
+++ monotone-0.19-symlink-clean/tests/t_add_symlink.at	2005-06-27 02:26:45.000000000 +0900
@@ -0,0 +1,17 @@
+AT_SETUP([add symlink])
+MONOTONE_SETUP
+
+NOT_ON_WIN32
+
+AT_DATA(foo-hard, [blah blah
+])
+AT_CHECK(ln -s foo-hard foo)
+# Have to use RAW_MONOTONE, because we're testing the standard hooks...
+AT_CHECK(RAW_MONOTONE --rcfile=test_hooks.lua add foo, [], [ignore], [ignore])
+COMMIT(testbranch)
+
+# Have to use RAW_MONOTONE, because we're testing the standard hooks...
+AT_CHECK(RAW_MONOTONE --rcfile=test_hooks.lua checkout --branch=testbranch codir, [], [ignore], [ignore])
+AT_CHECK(test -L codir/foo)
+
+AT_CLEANUP
diff -uNr monotone-0.19/testsuite.at monotone-0.19-symlink-clean/testsuite.at
--- monotone-0.19/testsuite.at	2005-05-03 10:07:26.000000000 +0900
+++ monotone-0.19-symlink-clean/testsuite.at	2005-06-24 16:35:38.000000000 +0900
@@ -584,6 +584,7 @@
 m4_include(tests/t_log_depth_single.at)
 m4_include(tests/t_attr_init.at)
 m4_include(tests/t_add_executable.at)
+m4_include(tests/t_add_symlink.at)
 m4_include(tests/t_inodeprints_hook.at)
 m4_include(tests/t_bad_packets.at)
 m4_include(tests/t_multiple_heads_msg.at)
diff -uNr monotone-0.19/transforms.cc monotone-0.19-symlink-clean/transforms.cc
--- monotone-0.19/transforms.cc	2005-04-30 17:43:03.000000000 +0900
+++ monotone-0.19-symlink-clean/transforms.cc	2005-06-27 09:37:40.000000000 +0900
@@ -33,6 +33,7 @@
 #include "vocab.hh"
 #include "work.hh"
 #include "xdelta.hh"
+#include "platform.hh"
 
 using namespace std;
 
@@ -331,6 +332,20 @@
       // no conversions necessary, use streaming form
       // still have to localize the filename
       fs::path localized_file = localized(file);
+      // ident of symlink is the symlink path and the linked file path concatenated.
+      if (fs::symbolic_link_exists(localized_file)) {
+        CryptoPP::SHA hash;
+        unsigned int const sz = 2 * CryptoPP::SHA::DIGESTSIZE;
+        char buffer[sz];
+        char *rlbuf = symlink_readlink(localized_file.native_file_string().c_str());
+        CryptoPP::StringSource f(rlbuf,
+                               true, new CryptoPP::HashFilter
+                               (hash, new CryptoPP::HexEncoder
+                                (new CryptoPP::ArraySink(reinterpret_cast<byte *>(buffer), sz))));
+        ident = lowercase(string(buffer, sz));
+        free(rlbuf);
+        return;
+      }
       // crypto++'s FileSource will simply treat directories as empty files,
       // so we'd better check ourselves.
       I(fs::exists(localized_file) && !fs::is_directory(localized_file));
diff -uNr monotone-0.19/unix/process.cc monotone-0.19-symlink-clean/unix/process.cc
--- monotone-0.19/unix/process.cc	2005-04-20 14:34:49.000000000 +0900
+++ monotone-0.19-symlink-clean/unix/process.cc	2005-06-27 01:53:10.000000000 +0900
@@ -8,6 +8,7 @@
 #include <sys/stat.h>
 #include <signal.h>
 #include <unistd.h>
+#include <fcntl.h>
 
 #include <sstream>
 
@@ -29,13 +30,95 @@
 	return -1;
 }
 
+#ifndef S_ISLNK
+#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
+#endif
+
+bool is_symlink(const char *path)
+{
+	struct stat s;
+
+	int rc = lstat(path, &s);
+	N(rc != -1, F("lstat() error on file %s)") % path);
+
+	return S_ISLNK(s.st_mode) ? true : false;
+}
+
+#define SYMLINK_MAGIC "SyMlInK:"
+#define SYMLINK_MAGIC_SIZE 8
+
+char *symlink_readlink(const char *path)
+{
+	struct stat s;
+	int rc = lstat(path, &s);
+	N(rc != -1, F("lstat() error on file %s)") % path);
+	// Linux fills in s.st_size with the length of the path pointed to
+	int bufsize = s.st_size+1+SYMLINK_MAGIC_SIZE;
+	char *buf = (char *)malloc(bufsize);
+	N(buf != NULL, F("malloc() failed reading symbolic link %s)") % path);
+	for (;;) {
+		rc = readlink(path, &buf[SYMLINK_MAGIC_SIZE], bufsize-SYMLINK_MAGIC_SIZE);
+		N(rc != -1, F("readlink() error on file %s)") % path);
+		if (rc+SYMLINK_MAGIC_SIZE < bufsize) {
+			buf[rc+SYMLINK_MAGIC_SIZE] = '\0';
+			break;
+		}
+		bufsize = bufsize * 2;
+		buf = (char *)realloc(buf, bufsize);
+		N(buf != NULL, F("realloc() failed reading symbolic link %s)") % path);
+	}
+	memcpy(buf, SYMLINK_MAGIC, SYMLINK_MAGIC_SIZE);
+	return buf;
+}
+
+static int ln_sf(const char *path, const char *newpath)
+{
+	int rc = unlink(newpath);
+	N(rc != -1, F("unlink() error on file %s)") % newpath);
+	return symlink(path, newpath);
+}
+
+int make_symlink(const char *path)
+{
+	struct stat s;
+	int rc = lstat(path, &s);
+	N(rc != -1, F("stat() error on file %s)") % path);
+	// after add, the symbolic link is not overwritten
+	if (S_ISLNK(s.st_mode))
+		return 0;
+	off_t pos, len = s.st_size;
+	char *buf = (char *)malloc(len+1);
+	N(buf != NULL, F("malloc() failed reading symbolic link %s)") % path);
+	int fd = open(path, O_RDONLY);
+	N(fd != -1, F("open() failed reading symbolic link %s)") % path);
+	ssize_t r;
+	pos = 0;
+	do {
+		r = read(fd, &buf[pos], len);
+		N(r >= 0, F("read() failed reading symbolic link %s)") % path);
+		pos += r;
+		len -= r;
+	} while (len);
+	buf[pos] = '\0';
+	rc = close(fd);
+	N(rc != -1, F("close() failed reading symbolic link %s)") % path);
+	N(pos >= SYMLINK_MAGIC_SIZE, F("symbolic link magic not present in %s)") % path);
+	N(!memcmp(buf, SYMLINK_MAGIC, SYMLINK_MAGIC_SIZE), F("symbolic link magic not present in %s)") % path);
+	rc = ln_sf(&buf[SYMLINK_MAGIC_SIZE], path);
+	free(buf);
+	N(rc != -1, F("symlink() error on file %s)") % path);
+	return rc;
+}
+
 bool is_executable(const char *path)
 {
 	struct stat s;
 
-	int rc = stat(path, &s);
+	int rc = lstat(path, &s);
 	N(rc != -1, F("stat() error on file %s)") % path);
 
+	if (S_ISLNK(s.st_mode))
+		return false;
 	return s.st_mode & S_IXUSR;
 }
 
diff -uNr monotone-0.19/win32/process.cc monotone-0.19-symlink-clean/win32/process.cc
--- monotone-0.19/win32/process.cc	2005-04-20 14:34:49.000000000 +0900
+++ monotone-0.19-symlink-clean/win32/process.cc	2005-06-27 00:23:56.000000000 +0900
@@ -27,6 +27,23 @@
   return 0; /* Basically meaningless on win32 */
 }
 
+bool is_symlink(const char *path)
+{
+  return false;	
+}
+
+char *symlink_readlink(const char *path)
+{
+  // boost fs::symbolic_link_exists should never return true,
+  // thus this function should never be called
+  return NULL;
+}
+
+int make_symlink(const char *path, const char *newpath)
+{
+  return -1;
+}
+
 int process_spawn(const char * const argv[])
 {
   int i;
