Fossil

Check-in [ed2ef7e9]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Add symlink support for Unix. New settings flag "allow-symlinks" controls this (off by default).
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | symlinks
Files: files | file ages | folders
SHA1: ed2ef7e9a3b6a46da6bad49aa777e10bf675f003
User & Date: dmitry 2011-01-28 19:04:30
Context
2011-02-07
17:50
Fix segmentation fault in historical_version_of_file() when file has no permissions in manifest. check-in: 8e4e30fb user: dmitry tags: symlinks
2011-01-28
19:04
Add symlink support for Unix. New settings flag "allow-symlinks" controls this (off by default). check-in: ed2ef7e9 user: dmitry tags: symlinks
18:57
Create new branch named "symlinks". Mailing list thread check-in: a7b7ff3a user: dmitry tags: symlinks
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to src/add.c.

   181    181         }
   182    182         blob_appendf(&path, "/%s", pEntry->d_name);
   183    183         zPath = blob_str(&path);
   184    184         if( shouldBeIgnored(pIgnore, zPath) ){
   185    185           /* Noop */
   186    186         }else if( file_isdir(zPath)==1 ){
   187    187           add_directory_content(zPath, pIgnore);
   188         -      }else if( file_isfile(zPath) ){
          188  +      }else if( file_isfile_or_link(zPath) ){
   189    189           db_multi_exec("INSERT INTO sfile VALUES(%Q)", zPath);
   190    190         }
   191    191         blob_resize(&path, origSize);
   192    192       }
   193    193     }
   194    194     closedir(d);
   195    195     blob_reset(&path);
................................................................................
   302    302           if( pEntry->d_name[1]==0 ) continue;
   303    303           if( pEntry->d_name[1]=='.' && pEntry->d_name[2]==0 ) continue;
   304    304         }
   305    305         blob_appendf(&path, "/%s", pEntry->d_name);
   306    306         zPath = blob_str(&path);
   307    307         if( file_isdir(zPath)==1 ){
   308    308           del_directory_content(zPath);
   309         -      }else if( file_isfile(zPath) ){
          309  +      }else if( file_isfile_or_link(zPath) ){
   310    310           char *zFilePath;
   311    311           Blob pathname;
   312    312           file_tree_name(zPath, &pathname, 1);
   313    313           zFilePath = blob_str(&pathname);
   314    314           if( !db_exists(
   315    315               "SELECT 1 FROM vfile WHERE pathname=%Q AND NOT deleted", zFilePath)
   316    316           ){
................................................................................
   460    460     );
   461    461     while( db_step(&q)==SQLITE_ROW ){
   462    462       const char * zFile;
   463    463       const char * zPath;
   464    464   
   465    465       zFile = db_column_text(&q, 0);
   466    466       zPath = db_column_text(&q, 1);
   467         -    if( !file_isfile(zPath) ){
          467  +    if( !file_isfile_or_link(zPath) ){
   468    468         if( !isTest ){
   469    469           db_multi_exec("UPDATE vfile SET deleted=1 WHERE pathname=%Q", zFile);
   470    470         }
   471    471         printf("DELETED  %s\n", zFile);
   472    472         nDelete++;
   473    473       }
   474    474     }

Changes to src/blob.c.

   643    643     got = fread(blob_buffer(pBlob), 1, size, in);
   644    644     fclose(in);
   645    645     if( got<size ){
   646    646       blob_resize(pBlob, got);
   647    647     }
   648    648     return got;
   649    649   }
          650  +
          651  +/*
          652  +** Reads symlink destination path and puts int into blob.
          653  +** Any prior content of the blob is discarded, not freed.
          654  +**
          655  +** Returns length of destination path.
          656  +**
          657  +** On windows, zeros blob and returns 0.
          658  +*/
          659  +int blob_read_link(Blob *pBlob, const char *zFilename){
          660  +#if !defined(_WIN32)
          661  +  char zBuf[1024];
          662  +  ssize_t len = readlink(zFilename, zBuf, 1023);
          663  +  zBuf[len] = 0;
          664  +  blob_zero(pBlob);
          665  +  blob_appendf(pBlob, "%s", zBuf);
          666  +  return len;
          667  +#else
          668  +  blob_zero(pBlob);
          669  +  return 0;
          670  +#endif
          671  +}
          672  +
   650    673   
   651    674   /*
   652    675   ** Write the content of a blob into a file.
   653    676   **
   654    677   ** If the filename is blank or "-" then write to standard output.
   655    678   **
   656    679   ** Return the number of bytes written.

Changes to src/checkin.c.

    51     51       int isChnged = db_column_int(&q,2);
    52     52       int isNew = db_column_int(&q,3)==0;
    53     53       int isRenamed = db_column_int(&q,4);
    54     54       char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname);
    55     55       blob_append(report, zPrefix, nPrefix);
    56     56       if( isDeleted ){
    57     57         blob_appendf(report, "DELETED    %s\n", zPathname);
    58         -    }else if( !file_isfile(zFullName) ){
           58  +    }else if( !file_isfile_or_link(zFullName) ){
    59     59         if( access(zFullName, 0)==0 ){
    60     60           blob_appendf(report, "NOT_A_FILE %s\n", zPathname);
    61     61           if( missingIsFatal ){
    62     62             fossil_warning("not a file: %s", zPathname);
    63     63             nErr++;
    64     64           }
    65     65         }else{
................................................................................
   179    179       char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname);
   180    180       if( isBrief ){
   181    181         printf("%s\n", zPathname);
   182    182       }else if( isNew ){
   183    183         printf("ADDED      %s\n", zPathname);
   184    184       }else if( isDeleted ){
   185    185         printf("DELETED    %s\n", zPathname);
   186         -    }else if( !file_isfile(zFullName) ){
          186  +    }else if( !file_isfile_or_link(zFullName) ){
   187    187         if( access(zFullName, 0)==0 ){
   188    188           printf("NOT_A_FILE %s\n", zPathname);
   189    189         }else{
   190    190           printf("MISSING    %s\n", zPathname);
   191    191         }
   192    192       }else if( chnged ){
   193    193         printf("EDITED     %s\n", zPathname);
................................................................................
   617    617       pFile = 0;
   618    618     }
   619    619     blob_appendf(pOut, "C %F\n", blob_str(pComment));
   620    620     zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now");
   621    621     blob_appendf(pOut, "D %s\n", zDate);
   622    622     zDate[10] = ' ';
   623    623     db_prepare(&q,
   624         -    "SELECT pathname, uuid, origname, blob.rid, isexe,"
          624  +    "SELECT pathname, uuid, origname, blob.rid, isexe, islink,"
   625    625       "       file_is_selected(vfile.id)"
   626    626       "  FROM vfile JOIN blob ON vfile.mrid=blob.rid"
   627    627       " WHERE (NOT deleted OR NOT file_is_selected(vfile.id))"
   628    628       "   AND vfile.vid=%d"
   629    629       " ORDER BY 1", vid);
   630    630     blob_zero(&filename);
   631    631     blob_appendf(&filename, "%s", g.zLocalRoot);
................................................................................
   632    632     nBasename = blob_size(&filename);
   633    633     while( db_step(&q)==SQLITE_ROW ){
   634    634       const char *zName = db_column_text(&q, 0);
   635    635       const char *zUuid = db_column_text(&q, 1);
   636    636       const char *zOrig = db_column_text(&q, 2);
   637    637       int frid = db_column_int(&q, 3);
   638    638       int isexe = db_column_int(&q, 4);
   639         -    int isSelected = db_column_int(&q, 5);
          639  +    int isLink = db_column_int(&q, 5);
          640  +    int isSelected = db_column_int(&q, 6);
   640    641       const char *zPerm;
   641    642       int cmp;
   642    643   #if !defined(_WIN32)
   643    644       /* For unix, extract the "executable" permission bit directly from
   644    645       ** the filesystem.  On windows, the "executable" bit is retained
   645    646       ** unchanged from the original. 
   646    647       */
   647    648       blob_resize(&filename, nBasename);
   648    649       blob_append(&filename, zName, -1);
   649    650       isexe = file_isexe(blob_str(&filename));
          651  +    
          652  +    /* For unix, check if the file on the filesystem is symlink.
          653  +    ** On windows, the bit is retained unchanged from original. 
          654  +    */
          655  +    isLink = file_islink(blob_str(&filename));
   650    656   #endif
   651    657       if( isexe ){
   652    658         zPerm = " x";
          659  +    }else if( isLink ){
          660  +      zPerm = " l"; /* note: symlinks don't have executable bit on unix */
   653    661       }else{
   654    662         zPerm = "";
   655    663       }
   656    664       if( !g.markPrivate ) content_make_public(frid);
   657    665       while( pFile && fossil_strcmp(pFile->zName,zName)<0 ){
   658    666         blob_appendf(pOut, "F %F\n", pFile->zName);
   659    667         pFile = manifest_file_next(pBaseline, 0);
................................................................................
   991    999       Blob content;
   992   1000   
   993   1001       id = db_column_int(&q, 0);
   994   1002       zFullname = db_column_text(&q, 1);
   995   1003       rid = db_column_int(&q, 2);
   996   1004   
   997   1005       blob_zero(&content);
   998         -    blob_read_from_file(&content, zFullname);
         1006  +    if( file_islink(zFullname) ){
         1007  +      /* Instead of file content, put link destination path */
         1008  +      blob_read_link(&content, zFullname);
         1009  +    }else{
         1010  +      blob_read_from_file(&content, zFullname);        
         1011  +    }
   999   1012       nrid = content_put(&content, 0, 0, 0);
  1000   1013       blob_reset(&content);
  1001   1014       if( rid>0 ){
  1002   1015         content_deltify(rid, nrid, 0);
  1003   1016       }
  1004   1017       db_multi_exec("UPDATE vfile SET mrid=%d, rid=%d WHERE id=%d", nrid,nrid,id);
  1005   1018       db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);

Changes to src/checkout.c.

   100    100     db_bind_text(&s, ":path", zFilename);
   101    101     db_step(&s);
   102    102     db_reset(&s);
   103    103   }
   104    104   
   105    105   /*
   106    106   ** Set or clear the execute permission bit (as appropriate) for all
   107         -** files in the current check-out.
          107  +** files in the current check-out, and replace files that have
          108  +** symlink bit with actual symlinks.
   108    109   */
   109    110   void checkout_set_all_exe(int vid){
   110    111     Blob filename;
   111    112     int baseLen;
   112    113     Manifest *pManifest;
   113    114     ManifestFile *pFile;
   114    115   

Changes to src/db.c.

   738    738     */
   739    739     rc = sqlite3_prepare(g.db, "SELECT isexe FROM vfile", -1, &pStmt, 0);
   740    740     sqlite3_finalize(pStmt);
   741    741     if( rc==SQLITE_ERROR ){
   742    742       sqlite3_exec(g.db, "ALTER TABLE vfile ADD COLUMN isexe BOOLEAN", 0, 0, 0);
   743    743     }
   744    744   
          745  +  /* If the "islink" column is missing from the vfile table, then
          746  +  ** add it now.   This code added on 2011-01-17.  After all users have
          747  +  ** upgraded, this code can be safely deleted. 
          748  +  */
          749  +  rc = sqlite3_prepare(g.db, "SELECT islink FROM vfile", -1, &pStmt, 0);
          750  +  sqlite3_finalize(pStmt);
          751  +  if( rc==SQLITE_ERROR ){
          752  +    sqlite3_exec(g.db, "ALTER TABLE vfile ADD COLUMN islink BOOLEAN", 0, 0, 0);
          753  +  }
          754  +
          755  +#if 0  
          756  +  /* If the "isLink" column is missing from the stashfile table, then
          757  +  ** add it now.   This code added on 2011-01-18.  After all users have
          758  +  ** upgraded, this code can be safely deleted. 
          759  +  **
          760  +  ** Table stashfile may not exist at all. We don't handle this case,
          761  +  ** and leave it to sqlite.
          762  +  */
          763  +  rc = sqlite3_prepare(g.db, "SELECT isLink FROM stashfile", -1, &pStmt, 0);
          764  +  sqlite3_finalize(pStmt);
          765  +  /* NOTE: this prints "SQLITE_ERROR: no such column: isLink" for some reason */
          766  +  if( rc==SQLITE_ERROR ){
          767  +    sqlite3_exec(g.db, "ALTER TABLE stashfile ADD COLUMN isLink BOOLEAN", 0, 0, 0);
          768  +  }
          769  +#endif
          770  +
   745    771   #if 0
   746    772     /* If the "mtime" column is missing from the vfile table, then
   747    773     ** add it now.   This code added on 2008-12-06.  After all users have
   748    774     ** upgraded, this code can be safely deleted. 
   749    775     */
   750    776     rc = sqlite3_prepare(g.db, "SELECT mtime FROM vfile", -1, &pStmt, 0);
   751    777     sqlite3_finalize(pStmt);
................................................................................
   845    871       }else{
   846    872         fossil_panic("not a valid repository: %s", zDbName);
   847    873       }
   848    874     }
   849    875     db_open_or_attach(zDbName, "repository");
   850    876     g.repositoryOpen = 1;
   851    877     g.zRepositoryName = mprintf("%s", zDbName);
          878  +  /* Cache "allow-symlinks" option, because we'll need it on every stat call */
          879  +  g.allowSymlinks = db_get_boolean("allow-symlinks", 0);
   852    880   }
   853    881   
   854    882   /*
   855    883   ** Flags for the db_find_and_open_repository() function.
   856    884   */
   857    885   #if INTERFACE
   858    886   #define OPEN_OK_NOT_FOUND    0x001      /* Do not error out if not found */
................................................................................
  1603   1631     char const *name;     /* Name of the setting */
  1604   1632     char const *var;      /* Internal variable name used by db_set() */
  1605   1633     int width;            /* Width of display.  0 for boolean values */
  1606   1634     char const *def;      /* Default value */
  1607   1635   };
  1608   1636   #endif /* INTERFACE */
  1609   1637   struct stControlSettings const ctrlSettings[] = {
         1638  +  { "allow-symlinks",0,                0, "off"                 },
  1610   1639     { "access-log",    0,                0, "off"                 },
  1611   1640     { "auto-captcha",  "autocaptcha",    0, "on"                  },
  1612   1641     { "auto-shun",     0,                0, "on"                  },
  1613   1642     { "autosync",      0,                0, "on"                  },
  1614   1643     { "binary-glob",   0,               32, ""                    },
  1615   1644     { "clearsign",     0,                0, "off"                 },
  1616   1645     { "default-perms", 0,               16, "u"                   },
................................................................................
  1641   1670   **
  1642   1671   ** The "settings" command with no arguments lists all properties and their
  1643   1672   ** values.  With just a property name it shows the value of that property.
  1644   1673   ** With a value argument it changes the property for the current repository.
  1645   1674   **
  1646   1675   ** The "unset" command clears a property setting.
  1647   1676   **
         1677  +**
         1678  +**    allow-symlinks   If enabled, don't follow symlinks, and instead treat
         1679  +**                     them as symlinks on Unix. Has no effect on Windows
         1680  +**                     (existing links in repository created on Unix become 
         1681  +**                     plain-text files with link destination path inside).
         1682  +**                     Default: off
  1648   1683   **
  1649   1684   **    auto-captcha     If enabled, the Login page provides a button to
  1650   1685   **                     fill in the captcha password.  Default: on
  1651   1686   **
  1652   1687   **    auto-shun        If enabled, automatically pull the shunning list
  1653   1688   **                     from a server to which the client autosyncs.
  1654   1689   **                     Default: on

Changes to src/diffcmd.c.

    57     57       const char *zName2;       /* Name of zFile2 for display */
    58     58   
    59     59       /* Read content of zFile2 into memory */
    60     60       blob_zero(&file2);
    61     61       if( file_size(zFile2)<0 ){
    62     62         zName2 = "/dev/null";
    63     63       }else{
    64         -      blob_read_from_file(&file2, zFile2);
           64  +      if( file_islink(zFile2) ){
           65  +        blob_read_link(&file2, zFile2);
           66  +      }else{
           67  +        blob_read_from_file(&file2, zFile2);
           68  +      }
    65     69         zName2 = zName;
    66     70       }
    67     71   
    68     72       /* Compute and output the differences */
    69     73       blob_zero(&out);
    70     74       text_diff(pFile1, &file2, &out, 5, ignoreEolWs);
    71     75       printf("--- %s\n+++ %s\n", zName, zName2);
................................................................................
   166    170   static void diff_one_against_disk(
   167    171     const char *zFrom,        /* Name of file */
   168    172     const char *zDiffCmd,     /* Use this "diff" command */
   169    173     int ignoreEolWs           /* Ignore whitespace changes at end of lines */
   170    174   ){
   171    175     Blob fname;
   172    176     Blob content;
          177  +  int isLink;
   173    178     file_tree_name(g.argv[2], &fname, 1);
   174         -  historical_version_of_file(zFrom, blob_str(&fname), &content, 0);
   175         -  diff_file(&content, g.argv[2], g.argv[2], zDiffCmd, ignoreEolWs);
          179  +  historical_version_of_file(zFrom, blob_str(&fname), &content, &isLink, 0);
          180  +  if( !isLink != !file_islink(zFrom) ){
          181  +    printf("cannot compute difference between symlink and regular file\n");
          182  +  }else{
          183  +    diff_file(&content, g.argv[2], g.argv[2], zDiffCmd, ignoreEolWs);
          184  +  }
   176    185     blob_reset(&content);
   177    186     blob_reset(&fname);
   178    187   }
   179    188   
   180    189   /*
   181    190   ** Run a diff between the version zFrom and files on disk.  zFrom might
   182    191   ** be NULL which means to simply show the difference between the edited
................................................................................
   202    211     if( zFrom ){
   203    212       int rid = name_to_rid(zFrom);
   204    213       if( !is_a_version(rid) ){
   205    214         fossil_fatal("no such check-in: %s", zFrom);
   206    215       }
   207    216       load_vfile_from_rid(rid);
   208    217       blob_appendf(&sql,
   209         -      "SELECT v2.pathname, v2.deleted, v2.chnged, v2.rid==0, v1.rid"
          218  +      "SELECT v2.pathname, v2.deleted, v2.chnged, v2.rid==0, v1.rid, v1.islink"
   210    219         "  FROM vfile v1, vfile v2 "
   211    220         " WHERE v1.pathname=v2.pathname AND v1.vid=%d AND v2.vid=%d"
   212    221         "   AND (v2.deleted OR v2.chnged OR v1.mrid!=v2.rid)"
   213    222         "UNION "
   214         -      "SELECT pathname, 1, 0, 0, 0"
          223  +      "SELECT pathname, 1, 0, 0, 0, islink"
   215    224         "  FROM vfile v1"
   216    225         " WHERE v1.vid=%d"
   217    226         "   AND NOT EXISTS(SELECT 1 FROM vfile v2"
   218    227                           " WHERE v2.vid=%d AND v2.pathname=v1.pathname)"
   219    228         "UNION "
   220         -      "SELECT pathname, 0, 0, 1, 0"
          229  +      "SELECT pathname, 0, 0, 1, 0, islink"
   221    230         "  FROM vfile v2"
   222    231         " WHERE v2.vid=%d"
   223    232         "   AND NOT EXISTS(SELECT 1 FROM vfile v1"
   224    233                           " WHERE v1.vid=%d AND v1.pathname=v2.pathname)"
   225    234         " ORDER BY 1",
   226    235         rid, vid, rid, vid, vid, rid
   227    236       );
   228    237     }else{
   229    238       blob_appendf(&sql,
   230         -      "SELECT pathname, deleted, chnged , rid==0, rid"
          239  +      "SELECT pathname, deleted, chnged , rid==0, rid, islink"
   231    240         "  FROM vfile"
   232    241         " WHERE vid=%d"
   233    242         "   AND (deleted OR chnged OR rid==0)"
   234    243         " ORDER BY pathname",
   235    244         vid
   236    245       );
   237    246     }
................................................................................
   238    247     db_prepare(&q, blob_str(&sql));
   239    248     while( db_step(&q)==SQLITE_ROW ){
   240    249       const char *zPathname = db_column_text(&q,0);
   241    250       int isDeleted = db_column_int(&q, 1);
   242    251       int isChnged = db_column_int(&q,2);
   243    252       int isNew = db_column_int(&q,3);
   244    253       int srcid = db_column_int(&q, 4);
          254  +    int isLink = db_column_int(&q, 5);
   245    255       char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname);
   246    256       char *zToFree = zFullName;
   247    257       int showDiff = 1;
   248    258       if( isDeleted ){
   249    259         printf("DELETED  %s\n", zPathname);
   250    260         if( !asNewFile ){ showDiff = 0; zFullName = "/dev/null"; }
   251    261       }else if( access(zFullName, 0) ){
................................................................................
   257    267         if( !asNewFile ){ showDiff = 0; }
   258    268       }else if( isChnged==3 ){
   259    269         printf("ADDED_BY_MERGE %s\n", zPathname);
   260    270         srcid = 0;
   261    271         if( !asNewFile ){ showDiff = 0; }
   262    272       }
   263    273       if( showDiff ){
          274  +      if( !isLink != !file_islink(zFullName) ){
          275  +        diff_print_index(zPathname);
          276  +        printf("--- %s\n+++ %s\n", zPathname, zPathname);
          277  +        printf("cannot compute difference between symlink and regular file\n");
          278  +        continue;
          279  +      }
   264    280         Blob content;
   265    281         if( srcid>0 ){
   266    282           content_get(srcid, &content);
   267    283         }else{
   268    284           blob_zero(&content);
   269    285         }
   270    286         diff_print_index(zPathname);
................................................................................
   287    303     const char *zTo,
   288    304     const char *zDiffCmd,
   289    305     int ignoreEolWs
   290    306   ){
   291    307     char *zName;
   292    308     Blob fname;
   293    309     Blob v1, v2;
          310  +  int isLink1, isLink2;
   294    311     file_tree_name(g.argv[2], &fname, 1);
   295    312     zName = blob_str(&fname);
   296         -  historical_version_of_file(zFrom, zName, &v1, 0);
   297         -  historical_version_of_file(zTo, zName, &v2, 0);
   298         -  diff_file_mem(&v1, &v2, zName, zDiffCmd, ignoreEolWs);
          313  +  historical_version_of_file(zFrom, zName, &v1, &isLink1, 0);
          314  +  historical_version_of_file(zTo, zName, &v2, &isLink2, 0);
          315  +  if( isLink1 != isLink2 ){
          316  +    printf("--- %s\n+++ %s\n", zName, zName);
          317  +    printf("cannot compute difference between symlink and regular file\n");
          318  +  }else{
          319  +    diff_file_mem(&v1, &v2, zName, zDiffCmd, ignoreEolWs);
          320  +  }
   299    321     blob_reset(&v1);
   300    322     blob_reset(&v2);
   301    323     blob_reset(&fname);
   302    324   }
   303    325   
   304    326   /*
   305    327   ** Show the difference between two files identified by ManifestFile

Changes to src/file.c.

    32     32   #if defined(_WIN32) && defined(__MSVCRT__)
    33     33     static struct _stati64 fileStat;
    34     34   # define stat _stati64
    35     35   #else
    36     36     static struct stat fileStat;
    37     37   #endif
    38     38   static int fileStatValid = 0;
           39  +
           40  +int fossil_stat(const char *zFilename, struct stat *buf){
           41  +#if !defined(_WIN32)
           42  +  if( g.allowSymlinks ){
           43  +    return lstat(zFilename, buf);
           44  +  }else{
           45  +    return stat(zFilename, buf);
           46  +  }
           47  +#else
           48  +  return stat(zFilename, buf);
           49  +#endif
           50  +}
    39     51   
    40     52   /*
    41     53   ** Fill in the fileStat variable for the file named zFilename.
    42     54   ** If zFilename==0, then use the previous value of fileStat if
    43     55   ** there is a previous value.
    44     56   **
    45     57   ** Return the number of errors.  No error messages are generated.
    46     58   */
    47     59   static int getStat(const char *zFilename){
    48     60     int rc = 0;
    49     61     if( zFilename==0 ){
    50     62       if( fileStatValid==0 ) rc = 1;
    51     63     }else{
    52         -    if( stat(zFilename, &fileStat)!=0 ){
           64  +    if( fossil_stat(zFilename, &fileStat)!=0 ){
    53     65         fileStatValid = 0;
    54     66         rc = 1;
    55     67       }else{
    56     68         fileStatValid = 1;
    57     69         rc = 0;
    58     70       }
    59     71     }
................................................................................
    74     86   ** Return the modification time for a file.  Return -1 if the file
    75     87   ** does not exist.  If zFilename is NULL return the size of the most
    76     88   ** recently stat-ed file.
    77     89   */
    78     90   i64 file_mtime(const char *zFilename){
    79     91     return getStat(zFilename) ? -1 : fileStat.st_mtime;
    80     92   }
           93  +
           94  +/*
           95  +** Return TRUE if the named file is an ordinary file or symlink 
           96  +** (if symlinks are allowed).
           97  +** Return false for directories, devices, fifos, etc.
           98  +*/
           99  +int file_isfile_or_link(const char *zFilename){
          100  +#if !defined(_WIN32)
          101  +  if ( g.allowSymlinks ){
          102  +    return getStat(zFilename) ? 0 : S_ISREG(fileStat.st_mode) || S_ISLNK(fileStat.st_mode);
          103  +  }else{
          104  +    return getStat(zFilename) ? 0 : S_ISREG(fileStat.st_mode);    
          105  +  }
          106  +#else
          107  +  return getStat(zFilename) ? 0 : S_ISREG(fileStat.st_mode);
          108  +#endif
          109  +}
    81    110   
    82    111   /*
    83    112   ** Return TRUE if the named file is an ordinary file.  Return false
    84         -** for directories, devices, fifos, symlinks, etc.
          113  +** for directories, devices, fifos, etc.
          114  +**
    85    115   */
    86    116   int file_isfile(const char *zFilename){
    87    117     return getStat(zFilename) ? 0 : S_ISREG(fileStat.st_mode);
    88    118   }
          119  +
          120  +/*
          121  +** Return TRUE if the named file is a symlink.  Return false
          122  +** for all other cases.
          123  +** On Windows, always return False.
          124  +*/
          125  +int file_islink(const char *zFilename){
          126  +#if !defined(_WIN32)
          127  +  if( g.allowSymlinks ){
          128  +    return getStat(zFilename) ? 0 : S_ISLNK(fileStat.st_mode);
          129  +  }else{
          130  +    return 0;
          131  +  }
          132  +#else
          133  +  return 0;
          134  +#endif
          135  +}
          136  +
          137  +/*
          138  +** Create symlink to file on Unix, or plain-text file with
          139  +** symlink target if "allow-symlinks" is off or we're on Windows.
          140  +**
          141  +** Arguments: target file (symlink will point to it), link file
          142  +**/
          143  +void create_symlink(const char *zTargetFile, const char *zLinkFile){
          144  +#if !defined(_WIN32)
          145  +  if( g.allowSymlinks ){
          146  +    int i, nName;
          147  +    char *zName, zBuf[1000];
          148  +
          149  +    nName = strlen(zLinkFile);
          150  +    if( nName>=sizeof(zBuf) ){
          151  +      zName = mprintf("%s", zLinkFile);
          152  +    }else{
          153  +      zName = zBuf;
          154  +      memcpy(zName, zLinkFile, nName+1);
          155  +    }
          156  +    nName = file_simplify_name(zName, nName);
          157  +    for(i=1; i<nName; i++){
          158  +      if( zName[i]=='/' ){
          159  +        zName[i] = 0;
          160  +          if( file_mkdir(zName, 1) ){
          161  +            fossil_fatal_recursive("unable to create directory %s", zName);
          162  +            return;
          163  +          }
          164  +        zName[i] = '/';
          165  +      }
          166  +    }
          167  +    if( zName!=zBuf ) free(zName);
          168  +
          169  +    if( symlink(zTargetFile, zName)!=0 ){
          170  +      fossil_fatal_recursive("unable to create symlink \"%s\"", zName);      
          171  +    }
          172  +  }else
          173  +#endif 
          174  +  {
          175  +    Blob content;
          176  +    blob_set(&content, zTargetFile);
          177  +    blob_write_to_file(&content, zLinkFile);
          178  +    blob_reset(&content);
          179  +  }
          180  +}
    89    181   
    90    182   /*
    91    183   ** Return TRUE if the named file is an executable.  Return false
    92    184   ** for directories, devices, fifos, symlinks, etc.
    93    185   */
    94    186   int file_isexe(const char *zFilename){
    95    187     if( getStat(zFilename) || !S_ISREG(fileStat.st_mode) ) return 0;
................................................................................
   116    208       char *zFN = mprintf("%s", zFilename);
   117    209       file_simplify_name(zFN, -1);
   118    210       rc = getStat(zFN);
   119    211       free(zFN);
   120    212     }else{
   121    213       rc = getStat(0);
   122    214     }
          215  +#if !defined(_WIN32)
          216  +  if( g.allowSymlinks ){
          217  +    return rc ? 0 : (S_ISDIR(fileStat.st_mode) && !S_ISLNK(fileStat.st_mode) ? 1 : 2);
          218  +  }else{
          219  +    return rc ? 0 : (S_ISDIR(fileStat.st_mode) ? 1 : 2);    
          220  +  }
          221  +#else
   123    222     return rc ? 0 : (S_ISDIR(fileStat.st_mode) ? 1 : 2);
          223  +#endif
   124    224   }
   125    225   
   126    226   /*
   127    227   ** Return the tail of a file pathname.  The tail is the last component
   128    228   ** of the path.  For example, the tail of "/a/b/c.d" is "c.d".
   129    229   */
   130    230   const char *file_tail(const char *z){
................................................................................
   156    256   
   157    257   /*
   158    258   ** Set or clear the execute bit on a file.
   159    259   */
   160    260   void file_setexe(const char *zFilename, int onoff){
   161    261   #if !defined(_WIN32)
   162    262     struct stat buf;
   163         -  if( stat(zFilename, &buf)!=0 ) return;
          263  +  if( fossil_stat(zFilename, &buf)!=0 || S_ISLNK(buf.st_mode) ) return;
   164    264     if( onoff ){
   165    265       if( (buf.st_mode & 0111)!=0111 ){
   166    266         chmod(zFilename, buf.st_mode | 0111);
   167    267       }
   168    268     }else{
   169    269       if( (buf.st_mode & 0111)!=0 ){
   170    270         chmod(zFilename, buf.st_mode & ~0111);
................................................................................
   375    475       printf("[%s] -> [%s]\n", zName, blob_buffer(&x));
   376    476       blob_reset(&x);
   377    477       sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", file_size(zName));
   378    478       printf("  file_size   = %s\n", zBuf);
   379    479       sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", file_mtime(zName));
   380    480       printf("  file_mtime  = %s\n", zBuf);
   381    481       printf("  file_isfile = %d\n", file_isfile(zName));
          482  +    printf("  file_islink = %d\n", file_islink(zName));
   382    483       printf("  file_isexe  = %d\n", file_isexe(zName));
   383    484       printf("  file_isdir  = %d\n", file_isdir(zName));
          485  +    printf("  file_isfile_or_link = %d\n", file_isfile_or_link(zName));
   384    486     }
   385    487   }
   386    488   
   387    489   /*
   388    490   ** Return TRUE if the given filename is canonical.
   389    491   **
   390    492   ** Canonical names are full pathnames using "/" not "\" and which
................................................................................
   630    732     i64 iSize;
   631    733     int rc;
   632    734     Blob onDisk;
   633    735   
   634    736     iSize = file_size(zName);
   635    737     if( iSize<0 ) return 0;
   636    738     if( iSize!=blob_size(pContent) ) return 0;
   637         -  blob_read_from_file(&onDisk, zName);
          739  +  if( file_islink(zName) ){
          740  +    blob_read_link(&onDisk, zName);
          741  +  }else{
          742  +    blob_read_from_file(&onDisk, zName);
          743  +  }
   638    744     rc = blob_compare(&onDisk, pContent);
   639    745     blob_reset(&onDisk);
   640    746     return rc==0;
   641    747   }

Changes to src/finfo.c.

    98     98     }else if( find_option("print","p",0) ){
    99     99       Blob record;
   100    100       Blob fname;
   101    101       const char *zRevision = find_option("revision", "r", 1);
   102    102   
   103    103       file_tree_name(g.argv[2], &fname, 1);
   104    104       if( zRevision ){
   105         -      historical_version_of_file(zRevision, blob_str(&fname), &record, 0);
          105  +      historical_version_of_file(zRevision, blob_str(&fname), &record, NULL, 0);
   106    106       }else{
   107    107         int rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B", &fname);
   108    108         if( rid==0 ){
   109    109           fossil_fatal("no history for file: %b", &fname);
   110    110         }
   111    111         content_get(rid, &record);
   112    112       }

Changes to src/import.c.

    28     28   */
    29     29   struct ImportFile { 
    30     30     char *zName;           /* Name of a file */
    31     31     char *zUuid;           /* UUID of the file */
    32     32     char *zPrior;          /* Prior name if the name was changed */
    33     33     char isFrom;           /* True if obtained from the parent */
    34     34     char isExe;            /* True if executable */
           35  +  char isLink;           /* True if symlink */
    35     36   };
    36     37   #endif
    37     38   
    38     39   
    39     40   /*
    40     41   ** State information about an on-going fast-import parse.
    41     42   */
................................................................................
    56     57     int nMerge;                 /* Number of merge values */
    57     58     int nMergeAlloc;            /* Number of slots in azMerge[] */
    58     59     char **azMerge;             /* Merge values */
    59     60     int nFile;                  /* Number of aFile values */
    60     61     int nFileAlloc;             /* Number of slots in aFile[] */
    61     62     ImportFile *aFile;          /* Information about files in a commit */
    62     63     int fromLoaded;             /* True zFrom content loaded into aFile[] */
           64  +  int hasLinks;               /* True if git repository contains symlinks */
    63     65   } gg;
    64     66   
    65     67   /*
    66     68   ** Duplicate a string.
    67     69   */
    68     70   char *fossil_strdup(const char *zOrig){
    69     71     char *z = 0;
................................................................................
   227    229     blob_appendf(&record, "D %s\n", gg.zDate);
   228    230     for(i=0; i<gg.nFile; i++){
   229    231       const char *zUuid = gg.aFile[i].zUuid;
   230    232       if( zUuid==0 ) continue;
   231    233       blob_appendf(&record, "F %F %s", gg.aFile[i].zName, zUuid);
   232    234       if( gg.aFile[i].isExe ){
   233    235         blob_append(&record, " x\n", 3);
          236  +    }else if( gg.aFile[i].isLink ){
          237  +      blob_append(&record, " l\n", 3);
          238  +      gg.hasLinks = 1;
   234    239       }else{
   235    240         blob_append(&record, "\n", 1);
   236    241       }
   237    242     }
   238    243     if( gg.zFrom ){
   239    244       blob_appendf(&record, "P %s", gg.zFrom);
   240    245       for(i=0; i<gg.nMerge; i++){
................................................................................
   369    374     p = manifest_get(rid, CFTYPE_MANIFEST);
   370    375     if( p==0 ) return;
   371    376     manifest_file_rewind(p);
   372    377     while( (pOld = manifest_file_next(p, 0))!=0 ){
   373    378       pNew = import_add_file();
   374    379       pNew->zName = fossil_strdup(pOld->zName);
   375    380       pNew->isExe = pOld->zPerm && strstr(pOld->zPerm, "x")!=0;
          381  +    pNew->isLink = pOld->zPerm && strstr(pOld->zPerm, "l")!=0;
   376    382       pNew->zUuid = fossil_strdup(pOld->zUuid);
   377    383       pNew->isFrom = 1;
   378    384     }
   379    385     manifest_destroy(p);
   380    386   }
   381    387   
   382    388   /*
................................................................................
   524    530         i = 0;
   525    531         pFile = import_find_file(zName, &i, gg.nFile);
   526    532         if( pFile==0 ){
   527    533           pFile = import_add_file();
   528    534           pFile->zName = fossil_strdup(zName);
   529    535         }
   530    536         pFile->isExe = (fossil_strcmp(zPerm, "100755")==0);
          537  +      pFile->isLink = (fossil_strcmp(zPerm, "120000")==0);      
   531    538         fossil_free(pFile->zUuid);
   532    539         pFile->zUuid = resolve_committish(zUuid);
   533    540         pFile->isFrom = 0;
   534    541       }else
   535    542       if( memcmp(zLine, "D ", 2)==0 ){
   536    543         import_prior_files();
   537    544         z = &zLine[2];
................................................................................
   561    568           pFile = &gg.aFile[i-1];
   562    569           if( strlen(pFile->zName)>nFrom ){
   563    570             pNew->zName = mprintf("%s%s", zTo, pFile->zName[nFrom]);
   564    571           }else{
   565    572             pNew->zName = fossil_strdup(pFile->zName);
   566    573           }
   567    574           pNew->isExe = pFile->isExe;
          575  +        pNew->isLink = pFile->isLink;
   568    576           pNew->zUuid = fossil_strdup(pFile->zUuid);
   569    577           pNew->isFrom = 0;
   570    578         }
   571    579       }else
   572    580       if( memcmp(zLine, "R ", 2)==0 ){
   573    581         int nFrom;
   574    582         import_prior_files();
................................................................................
   584    592           if( strlen(pFile->zName)>nFrom ){
   585    593             pNew->zName = mprintf("%s%s", zTo, pFile->zName[nFrom]);
   586    594           }else{
   587    595             pNew->zName = fossil_strdup(pFile->zName);
   588    596           }
   589    597           pNew->zPrior = pFile->zName;
   590    598           pNew->isExe = pFile->isExe;
          599  +        pNew->isLink = pFile->isLink;
   591    600           pNew->zUuid = pFile->zUuid;
   592    601           pNew->isFrom = 0;
   593    602           gg.nFile--;
   594    603           *pFile = *pNew;
   595    604           memset(pNew, 0, sizeof(*pNew));
   596    605         }
   597    606         fossil_fatal("cannot handle R records, use --full-tree");
................................................................................
   604    613       }else
   605    614   
   606    615       {
   607    616         goto malformed_line;
   608    617       }
   609    618     }
   610    619     gg.xFinish();
          620  +  if( gg.hasLinks ){
          621  +    db_set_int("allow-symlinks", 1, 0);
          622  +  }
   611    623     import_reset(1);
   612    624     return;
   613    625   
   614    626   malformed_line:
   615    627     trim_newline(zLine);
   616    628     fossil_fatal("bad fast-import line: [%s]", zLine);
   617    629     return;

Changes to src/main.c.

   148    148     /* Storage for the aux() and/or option() SQL function arguments */
   149    149     int nAux;                    /* Number of distinct aux() or option() values */
   150    150     const char *azAuxName[MX_AUX]; /* Name of each aux() or option() value */
   151    151     char *azAuxParam[MX_AUX];      /* Param of each aux() or option() value */
   152    152     const char *azAuxVal[MX_AUX];  /* Value of each aux() or option() value */
   153    153     const char **azAuxOpt[MX_AUX]; /* Options of each option() value */
   154    154     int anAuxCols[MX_AUX];         /* Number of columns for option() values */
          155  +  
          156  +  int allowSymlinks;             /* Cached "allow-symlinks" option */
   155    157   };
   156    158   
   157    159   /*
   158    160   ** Macro for debugging:
   159    161   */
   160    162   #define CGIDEBUG(X)  if( g.fDebug ) cgi_debug X
   161    163   

Changes to src/merge.c.

   157    157       "  idp INTEGER,"              /* VFILE entry for the pivot */
   158    158       "  idm INTEGER,"              /* VFILE entry for version merging in */
   159    159       "  chnged BOOLEAN,"           /* True if current version has been edited */
   160    160       "  ridv INTEGER,"             /* Record ID for current version */
   161    161       "  ridp INTEGER,"             /* Record ID for pivot */
   162    162       "  ridm INTEGER,"             /* Record ID for merge */
   163    163       "  fnp TEXT,"                 /* The filename in the pivot */
   164         -    "  fnm TEXT"                  /* the filename in the merged version */
          164  +    "  fnm TEXT,"                 /* the filename in the merged version */
          165  +    "  islinkv BOOLEAN,"          /* True if current version is a symlink */
          166  +    "  islinkm BOOLEAN"           /* True if merged version in is a symlink */
   165    167       ");"
   166    168     );
   167    169   
   168    170     /* Add files found in V
   169    171     */
   170    172     db_multi_exec(
   171    173       "INSERT OR IGNORE INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,chnged)"
................................................................................
   301    303       if( !nochangeFlag ){
   302    304         undo_save(zName);
   303    305         vfile_to_disk(0, idm, 0, 0);
   304    306       }
   305    307     }
   306    308     db_finalize(&q);
   307    309     
          310  +  /*
          311  +  **  Add islink information for files in V and M
          312  +  **
          313  +  */
          314  +  db_multi_exec(
          315  +    "UPDATE fv SET"
          316  +    " islinkv=coalesce((SELECT islink FROM vfile WHERE vid=%d AND pathname=fnm),0),"
          317  +    " islinkm=coalesce((SELECT islink FROM vfile WHERE vid=%d AND pathname=fnm),0)",
          318  +    vid, mid
          319  +  );
          320  +  
   308    321     /*
   309    322     ** Find files that have changed from P->M but not P->V. 
   310    323     ** Copy the M content over into V.
   311    324     */
   312    325     db_prepare(&q,
   313    326       "SELECT idv, ridm, fn FROM fv"
   314    327       " WHERE idp>0 AND idv>0 AND idm>0"
................................................................................
   330    343     }
   331    344     db_finalize(&q);
   332    345   
   333    346     /*
   334    347     ** Do a three-way merge on files that have changes on both P->M and P->V.
   335    348     */
   336    349     db_prepare(&q,
   337         -    "SELECT ridm, idv, ridp, ridv, %s, fn FROM fv"
          350  +    "SELECT ridm, idv, ridp, ridv, %s, fn, islinkv, islinkm FROM fv"
   338    351       " WHERE idp>0 AND idv>0 AND idm>0"
   339    352       "   AND ridm!=ridp AND (ridv!=ridp OR chnged)",
   340    353       glob_expr("fv.fn", zBinGlob)
   341    354     );
   342    355     while( db_step(&q)==SQLITE_ROW ){
   343    356       int ridm = db_column_int(&q, 0);
   344    357       int idv = db_column_int(&q, 1);
   345    358       int ridp = db_column_int(&q, 2);
   346    359       int ridv = db_column_int(&q, 3);
   347    360       int isBinary = db_column_int(&q, 4);
   348    361       const char *zName = db_column_text(&q, 5);
          362  +    int islinkv = db_column_int(&q, 6);
          363  +    int islinkm = db_column_int(&q, 7);
   349    364       int rc;
   350    365       char *zFullPath;
   351    366       Blob m, p, v, r;
   352    367       /* Do a 3-way merge of idp->idm into idp->idv.  The results go into idv. */
   353    368       if( detailFlag ){
   354    369         printf("MERGE %s  (pivot=%d v1=%d v2=%d)\n", zName, ridp, ridm, ridv);
   355    370       }else{
   356    371         printf("MERGE %s\n", zName);
   357    372       }
   358         -    undo_save(zName);
   359         -    zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
   360         -    content_get(ridp, &p);
   361         -    content_get(ridm, &m);
   362         -    blob_zero(&v);
   363         -    blob_read_from_file(&v, zFullPath);
   364         -    if( isBinary ){
   365         -      rc = -1;
   366         -      blob_zero(&r);
          373  +    if( islinkv || islinkm /* || file_islink(zFullPath) */ ){
          374  +      printf("***** Cannot merge symlink %s\n", zName);
          375  +      nConflict++;        
   367    376       }else{
   368         -      rc = blob_merge(&p, &v, &m, &r);
   369         -    }
   370         -    if( rc>=0 ){
   371         -      if( !nochangeFlag ){
   372         -        blob_write_to_file(&r, zFullPath);
          377  +      undo_save(zName);
          378  +      zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
          379  +      content_get(ridp, &p);
          380  +      content_get(ridm, &m);
          381  +      blob_zero(&v);
          382  +      blob_read_from_file(&v, zFullPath);
          383  +      if( isBinary ){
          384  +        rc = -1;
          385  +        blob_zero(&r);
          386  +      }else{
          387  +        rc = blob_merge(&p, &v, &m, &r);
   373    388         }
   374         -      db_multi_exec("UPDATE vfile SET mtime=0 WHERE id=%d", idv);
   375         -      if( rc>0 ){
   376         -        printf("***** %d merge conflicts in %s\n", rc, zName);
          389  +      if( rc>=0 ){
          390  +        if( !nochangeFlag ){
          391  +          blob_write_to_file(&r, zFullPath);
          392  +        }
          393  +        db_multi_exec("UPDATE vfile SET mtime=0 WHERE id=%d", idv);
          394  +        if( rc>0 ){
          395  +          printf("***** %d merge conflicts in %s\n", rc, zName);
          396  +          nConflict++;
          397  +        }
          398  +      }else{
          399  +        printf("***** Cannot merge binary file %s\n", zName);
   377    400           nConflict++;
   378    401         }
   379         -    }else{
   380         -      printf("***** Cannot merge binary file %s\n", zName);
   381         -      nConflict++;
          402  +      blob_reset(&p);
          403  +      blob_reset(&m);
          404  +      blob_reset(&v);
          405  +      blob_reset(&r);
   382    406       }
   383         -    blob_reset(&p);
   384         -    blob_reset(&m);
   385         -    blob_reset(&v);
   386         -    blob_reset(&r);
   387    407       db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(%d,%d)",
   388    408                     idv,ridm);
   389    409     }
   390    410     db_finalize(&q);
   391    411   
   392    412     /*
   393    413     ** Drop files that are in P and V but not in M

Changes to src/schema.c.

   427    427   @ --
   428    428   @ CREATE TABLE vfile(
   429    429   @   id INTEGER PRIMARY KEY,           -- ID of the checked out file
   430    430   @   vid INTEGER REFERENCES blob,      -- The baseline this file is part of.
   431    431   @   chnged INT DEFAULT 0,             -- 0:unchnged 1:edited 2:m-chng 3:m-add
   432    432   @   deleted BOOLEAN DEFAULT 0,        -- True if deleted 
   433    433   @   isexe BOOLEAN,                    -- True if file should be executable
          434  +@   islink BOOLEAN,                    -- True if file should be symlink
   434    435   @   rid INTEGER,                      -- Originally from this repository record
   435    436   @   mrid INTEGER,                     -- Based on this record due to a merge
   436    437   @   mtime INTEGER,                    -- Modification time of file on disk
   437    438   @   pathname TEXT,                    -- Full pathname relative to root
   438    439   @   origname TEXT,                    -- Original pathname. NULL if unchanged
   439    440   @   UNIQUE(pathname,vid)
   440    441   @ );

Changes to src/sha1.c.

   263    263   ** Return the number of errors.
   264    264   */
   265    265   int sha1sum_file(const char *zFilename, Blob *pCksum){
   266    266     FILE *in;
   267    267     SHA1Context ctx;
   268    268     unsigned char zResult[20];
   269    269     char zBuf[10240];
          270  +
          271  +  if( file_islink(zFilename) ){
          272  +    /* Instead of file content, return sha1 of link destination path */
          273  +    Blob destinationPath;
          274  +    int rc;
          275  +    
          276  +    blob_read_link(&destinationPath, zFilename);
          277  +    rc = sha1sum_blob(&destinationPath, pCksum);
          278  +    blob_reset(&destinationPath);
          279  +    return rc;
          280  +  }
   270    281   
   271    282     in = fopen(zFilename,"rb");
   272    283     if( in==0 ){
   273    284       return 1;
   274    285     }
   275    286     SHA1Init(&ctx);
   276    287     for(;;){

Changes to src/stash.c.

    33     33   @ );
    34     34   @ CREATE TABLE IF NOT EXISTS %s.stashfile(
    35     35   @   stashid INTEGER REFERENCES stash,  -- Stash that contains this file
    36     36   @   rid INTEGER,                       -- Baseline content in BLOB table or 0.
    37     37   @   isAdded BOOLEAN,                   -- True if this is an added file
    38     38   @   isRemoved BOOLEAN,                 -- True if this file is deleted
    39     39   @   isExec BOOLEAN,                    -- True if file is executable
           40  +@   isLink BOOLEAN,                    -- True if file is a symlink
    40     41   @   origname TEXT,                     -- Original filename
    41     42   @   newname TEXT,                      -- New name for file at next check-in
    42     43   @   delta BLOB,                        -- Delta from baseline. Content if rid=0
    43     44   @   PRIMARY KEY(origname, stashid)
    44     45   @ );
    45     46   @ INSERT OR IGNORE INTO vvar(name, value) VALUES('stash-next', 1);
    46     47   ;
................................................................................
    59     60     Stmt ins;             /* Insert statement */
    60     61   
    61     62     zFile = mprintf("%/", zFName);
    62     63     file_tree_name(zFile, &fname, 1);
    63     64     zTreename = blob_str(&fname);
    64     65     blob_zero(&sql);
    65     66     blob_appendf(&sql,
    66         -    "SELECT deleted, isexe, mrid, pathname, coalesce(origname,pathname)"
           67  +    "SELECT deleted, isexe, islink, mrid, pathname, coalesce(origname,pathname)"
    67     68       "  FROM vfile"
    68     69       " WHERE vid=%d AND (chnged OR deleted OR origname NOT NULL OR mrid==0)",
    69     70       vid
    70     71     );
    71     72     if( fossil_strcmp(zTreename,".")!=0 ){
    72     73       blob_appendf(&sql,
    73     74         "   AND (pathname GLOB '%q/*' OR origname GLOB '%q/*'"
................................................................................
    74     75               "  OR pathname=%Q OR origname=%Q)",
    75     76         zTreename, zTreename, zTreename, zTreename
    76     77       );
    77     78     }
    78     79     db_prepare(&q, blob_str(&sql));
    79     80     blob_reset(&sql);
    80     81     db_prepare(&ins,
    81         -     "INSERT INTO stashfile(stashid, rid, isAdded, isRemoved, isExec,"
           82  +     "INSERT INTO stashfile(stashid, rid, isAdded, isRemoved, isExec, isLink,"
    82     83                              "origname, newname, delta)"
    83         -     "VALUES(%d,:rid,:isadd,:isrm,:isexe,:orig,:new,:content)",
           84  +     "VALUES(%d,:rid,:isadd,:isrm,:isexe,:islink,:orig,:new,:content)",
    84     85        stashid
    85     86     );
    86     87     while( db_step(&q)==SQLITE_ROW ){
    87     88       int deleted = db_column_int(&q, 0);
    88         -    int rid = db_column_int(&q, 2);
    89         -    const char *zName = db_column_text(&q, 3);
    90         -    const char *zOrig = db_column_text(&q, 4);
           89  +    //int isLink = db_column_int(&q, 2);
           90  +    int rid = db_column_int(&q, 3);
           91  +    const char *zName = db_column_text(&q, 4);
           92  +    const char *zOrig = db_column_text(&q, 5);
    91     93       char *zPath = mprintf("%s%s", g.zLocalRoot, zName);
    92     94       Blob content;
           95  +    int isNewLink = file_islink(zPath);
    93     96   
    94     97       db_bind_int(&ins, ":rid", rid);
    95     98       db_bind_int(&ins, ":isadd", rid==0);
    96     99       db_bind_int(&ins, ":isrm", deleted);
    97    100   #ifdef _WIN32
    98    101       db_bind_int(&ins, ":isexe", db_column_int(&q, 1));
          102  +    //db_bind_int(&ins, ":islink", isLink);
    99    103   #endif
   100    104       db_bind_text(&ins, ":orig", zOrig);
   101    105       db_bind_text(&ins, ":new", zName);
          106  +
   102    107       if( rid==0 ){
   103    108         /* A new file */
   104         -      blob_read_from_file(&content, zPath);
          109  +      if( isNewLink ){
          110  +        blob_read_link(&content, zPath);
          111  +      }else{
          112  +        blob_read_from_file(&content, zPath);
          113  +      }
   105    114         db_bind_blob(&ins, ":content", &content);
   106    115       }else if( deleted ){
   107    116         db_bind_null(&ins, ":content");
   108    117       }else{
   109    118         /* A modified file */
   110    119         Blob orig;
   111    120         Blob disk;
   112         -      blob_read_from_file(&disk, zPath);
          121  +      
          122  +      if( isNewLink ){
          123  +        blob_read_link(&disk, zPath);
          124  +      }else{
          125  +        blob_read_from_file(&disk, zPath);
          126  +      }
   113    127         content_get(rid, &orig);
   114    128         blob_delta_create(&orig, &disk, &content);
   115    129         blob_reset(&orig);
   116    130         blob_reset(&disk);
   117    131         db_bind_blob(&ins, ":content", &content);
   118    132       }
          133  +    db_bind_int(&ins, ":islink", isNewLink);
   119    134       db_step(&ins);
   120    135       db_reset(&ins);
   121    136       fossil_free(zPath);
   122    137       blob_reset(&content);
   123    138     }
   124    139     db_finalize(&ins);
   125    140     db_finalize(&q);
................................................................................
   166    181   
   167    182   /*
   168    183   ** Apply a stash to the current check-out.
   169    184   */
   170    185   static void stash_apply(int stashid, int nConflict){
   171    186     Stmt q;
   172    187     db_prepare(&q,
   173         -     "SELECT rid, isRemoved, isExec, origname, newname, delta"
          188  +     "SELECT rid, isRemoved, isExec, isLink, origname, newname, delta"
   174    189        "  FROM stashfile WHERE stashid=%d",
   175    190        stashid
   176    191     );
   177    192     while( db_step(&q)==SQLITE_ROW ){
   178    193       int rid = db_column_int(&q, 0);
   179    194       int isRemoved = db_column_int(&q, 1);
   180         -    const char *zOrig = db_column_text(&q, 3);
   181         -    const char *zNew = db_column_text(&q, 4);
          195  +    int isLink = db_column_int(&q, 3);
          196  +    const char *zOrig = db_column_text(&q, 4);
          197  +    const char *zNew = db_column_text(&q, 5);
   182    198       char *zOPath = mprintf("%s%s", g.zLocalRoot, zOrig);
   183    199       char *zNPath = mprintf("%s%s", g.zLocalRoot, zNew);
   184    200       Blob delta;
   185    201       undo_save(zNew);
   186    202       blob_zero(&delta);
   187    203       if( rid==0 ){
   188    204         db_ephemeral_blob(&q, 5, &delta);
................................................................................
   189    205         blob_write_to_file(&delta, zNPath);
   190    206         printf("ADD %s\n", zNew);
   191    207       }else if( isRemoved ){
   192    208         printf("DELETE %s\n", zOrig);
   193    209         unlink(zOPath);
   194    210       }else{
   195    211         Blob a, b, out, disk;
          212  +      int isNewLink = file_islink(zOPath);
   196    213         db_ephemeral_blob(&q, 5, &delta);
   197         -      blob_read_from_file(&disk, zOPath);     
          214  +      if( isNewLink ){
          215  +        blob_read_link(&disk, zOPath);
          216  +      }else{
          217  +        blob_read_from_file(&disk, zOPath);
          218  +      }
   198    219         content_get(rid, &a);
   199    220         blob_delta_apply(&a, &delta, &b);
   200         -      if( blob_compare(&disk, &a)==0 ){
   201         -        blob_write_to_file(&b, zNPath);
          221  +      if( blob_compare(&disk, &a)==0 && isLink == isNewLink ){
          222  +        if( isLink || isNewLink ){
          223  +          unlink(zNPath);
          224  +        }
          225  +        if( isLink ){
          226  +          create_symlink(blob_str(&b), zNPath);
          227  +        }else{
          228  +          blob_write_to_file(&b, zNPath);          
          229  +        }
   202    230           printf("UPDATE %s\n", zNew);
   203    231         }else{
   204         -        int rc = blob_merge(&a, &disk, &b, &out);
   205         -        blob_write_to_file(&out, zNPath);
          232  +        int rc;
          233  +        if( isLink || isNewLink ){
          234  +          rc = -1;
          235  +          blob_zero(&b); /* because we reset it later */
          236  +          //TODO write something to disk?
          237  +        }else{
          238  +          rc = blob_merge(&a, &disk, &b, &out);
          239  +          blob_write_to_file(&out, zNPath);          
          240  +          blob_reset(&out);
          241  +        }
   206    242           if( rc ){
   207    243             printf("CONFLICT %s\n", zNew);
   208    244             nConflict++;
   209    245           }else{
   210    246             printf("MERGE %s\n", zNew);
   211    247           }
   212         -        blob_reset(&out);
   213    248         }
   214    249         blob_reset(&a);
   215    250         blob_reset(&b);
   216    251         blob_reset(&disk);
   217    252       }
   218    253       blob_reset(&delta);
   219    254       if( fossil_strcmp(zOrig,zNew)!=0 ){
   220    255         undo_save(zOrig);
   221    256         unlink(zOPath);
   222    257       }
   223    258     }
   224    259     db_finalize(&q);
   225    260     if( nConflict ){
   226         -    printf("WARNING: merge conflicts - see messages above for details.\n");
          261  +    printf("WARNING: %d merge conflicts - see messages above for details.\n",
          262  +            nConflict);
   227    263     }
   228    264   }
   229    265   
   230    266   /*
   231    267   ** Show the diffs associate with a single stash.
   232    268   */
   233    269   static void stash_diff(int stashid, const char *zDiffCmd){
   234    270     Stmt q;
   235    271     Blob empty;
   236    272     blob_zero(&empty);
   237    273     db_prepare(&q,
   238         -     "SELECT rid, isRemoved, isExec, origname, newname, delta"
          274  +     "SELECT rid, isRemoved, isExec, isLink, origname, newname, delta"
   239    275        "  FROM stashfile WHERE stashid=%d",
   240    276        stashid
   241    277     );
   242    278     while( db_step(&q)==SQLITE_ROW ){
   243    279       int rid = db_column_int(&q, 0);
   244    280       int isRemoved = db_column_int(&q, 1);
   245         -    const char *zOrig = db_column_text(&q, 3);
   246         -    const char *zNew = db_column_text(&q, 4);
          281  +    int isLink = db_column_int(&q, 3);
          282  +    const char *zOrig = db_column_text(&q, 4);
          283  +    const char *zNew = db_column_text(&q, 5);
   247    284       char *zOPath = mprintf("%s%s", g.zLocalRoot, zOrig);
   248    285       Blob delta;
   249    286       if( rid==0 ){
   250    287         db_ephemeral_blob(&q, 5, &delta);
   251    288         printf("ADDED %s\n", zNew);
   252    289         diff_print_index(zNew);
   253    290         diff_file_mem(&empty, &delta, zNew, zDiffCmd, 0);
   254    291       }else if( isRemoved ){
   255    292         printf("DELETE %s\n", zOrig);
   256         -      blob_read_from_file(&delta, zOPath);
          293  +      if( file_islink(zOPath) ){
          294  +        blob_read_link(&delta, zOPath);
          295  +      }else{
          296  +        blob_read_from_file(&delta, zOPath);
          297  +      }
   257    298         diff_print_index(zNew);
   258    299         diff_file_mem(&delta, &empty, zOrig, zDiffCmd, 0);
   259    300       }else{
   260    301         Blob a, b, disk;
          302  +      int isOrigLink = file_islink(zOPath);
   261    303         db_ephemeral_blob(&q, 5, &delta);
   262         -      blob_read_from_file(&disk, zOPath);     
   263         -      content_get(rid, &a);
   264         -      blob_delta_apply(&a, &delta, &b);
          304  +      if( isOrigLink ){
          305  +        blob_read_link(&disk, zOPath);
          306  +      }else{
          307  +        blob_read_from_file(&disk, zOPath);        
          308  +      }
   265    309         printf("CHANGED %s\n", zNew);
   266         -      diff_file_mem(&disk, &b, zNew, zDiffCmd, 0);
   267         -      blob_reset(&a);
   268         -      blob_reset(&b);
          310  +      if( !isOrigLink != !isLink ){
          311  +        diff_print_index(zNew);
          312  +        printf("--- %s\n+++ %s\n", zOrig, zNew);
          313  +        printf("cannot compute difference between symlink and regular file\n");
          314  +      }else{
          315  +        content_get(rid, &a);
          316  +        blob_delta_apply(&a, &delta, &b);
          317  +        diff_file_mem(&disk, &b, zNew, zDiffCmd, 0);
          318  +        blob_reset(&a);
          319  +        blob_reset(&b);
          320  +      }
   269    321         blob_reset(&disk);
   270    322       }
   271    323       blob_reset(&delta);
   272    324    }
   273    325     db_finalize(&q);
   274    326   }
   275    327   
................................................................................
   345    397   **  fossil stash drop ?STASHID? ?--all?
   346    398   **
   347    399   **     Forget everything about STASHID.  Forget the whole stash if the
   348    400   **     --all flag is used.  Individual drops are undoable but --all is not.
   349    401   **
   350    402   **  fossil stash snapshot ?-m COMMENT? ?FILES...?
   351    403   **
   352         -**     Save the current changes in the working tress as a new stash
          404  +**     Save the current changes in the working tree as a new stash
   353    405   **     but, unlike "save", do not revert those changes.
   354    406   **
   355    407   **  fossil stash diff ?STASHID?
   356    408   **  fossil stash gdiff ?STASHID?
   357    409   **
   358    410   **     Show diffs of the current working directory and what that
   359    411   **     directory would be if STASHID were applied.  

Changes to src/undo.c.

    28     28   ** true the redo a change.  If there is nothing to undo (or redo) then
    29     29   ** this routine is a noop.
    30     30   */
    31     31   static void undo_one(const char *zPathname, int redoFlag){
    32     32     Stmt q;
    33     33     char *zFullname;
    34     34     db_prepare(&q,
    35         -    "SELECT content, existsflag FROM undo WHERE pathname=%Q AND redoflag=%d",
           35  +    "SELECT content, existsflag, islink FROM undo WHERE pathname=%Q AND redoflag=%d",
    36     36        zPathname, redoFlag
    37     37     );
    38     38     if( db_step(&q)==SQLITE_ROW ){
    39     39       int old_exists;
    40     40       int new_exists;
    41     41       Blob current;
    42     42       Blob new;
           43  +    int isLink = db_column_int(&q, 2);
    43     44       zFullname = mprintf("%s/%s", g.zLocalRoot, zPathname);
    44     45       new_exists = file_size(zFullname)>=0;
           46  +    int isNewLink = file_islink(zFullname);
    45     47       if( new_exists ){
    46         -      blob_read_from_file(&current, zFullname);
           48  +      if( isNewLink ){
           49  +        blob_read_link(&current, zFullname);
           50  +      }else{
           51  +        blob_read_from_file(&current, zFullname);        
           52  +      }
    47     53       }else{
    48     54         blob_zero(&current);
    49     55       }
    50     56       blob_zero(&new);
    51     57       old_exists = db_column_int(&q, 1);
    52     58       if( old_exists ){
    53     59         db_ephemeral_blob(&q, 0, &new);
................................................................................
    54     60       }
    55     61       if( old_exists ){
    56     62         if( new_exists ){
    57     63           printf("%s %s\n", redoFlag ? "REDO" : "UNDO", zPathname);
    58     64         }else{
    59     65           printf("NEW %s\n", zPathname);
    60     66         }
    61         -      blob_write_to_file(&new, zFullname);
           67  +      if( new_exists && (isNewLink || isLink) ){
           68  +        unlink(zFullname);
           69  +      }
           70  +      if( isLink ){
           71  +        create_symlink(blob_str(&new), zFullname);
           72  +      }else{
           73  +        blob_write_to_file(&new, zFullname);
           74  +      }
    62     75       }else{
    63     76         printf("DELETE %s\n", zPathname);
    64     77         unlink(zFullname);
    65     78       }
    66     79       blob_reset(&new);
    67     80       free(zFullname);
    68     81       db_finalize(&q);
    69     82       db_prepare(&q, 
    70         -       "UPDATE undo SET content=:c, existsflag=%d, redoflag=NOT redoflag"
           83  +       "UPDATE undo SET content=:c, existsflag=%d, redoflag=NOT redoflag, islink=%d"
    71     84          " WHERE pathname=%Q",
    72         -       new_exists, zPathname
           85  +       new_exists, isNewLink, zPathname
    73     86       );
    74     87       if( new_exists ){
    75     88         db_bind_blob(&q, ":c", &current);
    76     89       }
    77     90       db_step(&q);
    78     91       blob_reset(&current);
    79     92     }
................................................................................
   198    211     int cid;
   199    212     const char *zDb = db_name("localdb");
   200    213     static const char zSql[] = 
   201    214       @ CREATE TABLE %s.undo(
   202    215       @   pathname TEXT UNIQUE,             -- Name of the file
   203    216       @   redoflag BOOLEAN,                 -- 0 for undoable.  1 for redoable
   204    217       @   existsflag BOOLEAN,               -- True if the file exists
          218  +    @   islink BOOLEAN,                   -- True if the file is symlink
   205    219       @   content BLOB                      -- Saved content
   206    220       @ );
   207    221       @ CREATE TABLE %s.undo_vfile AS SELECT * FROM vfile;
   208    222       @ CREATE TABLE %s.undo_vmerge AS SELECT * FROM vmerge;
   209    223     ;
   210    224     if( undoDisable ) return;
   211    225     undo_reset();
................................................................................
   238    252   ** will be undoable.  The name is relative to the root of the
   239    253   ** tree.
   240    254   */
   241    255   void undo_save(const char *zPathname){
   242    256     char *zFullname;
   243    257     Blob content;
   244    258     int existsFlag;
          259  +  int isLink;
   245    260     Stmt q;
   246    261   
   247    262     if( !undoActive ) return;
   248    263     zFullname = mprintf("%s%s", g.zLocalRoot, zPathname);
   249    264     existsFlag = file_size(zFullname)>=0;
          265  +  isLink = file_islink(zFullname);
   250    266     db_prepare(&q,
   251         -    "INSERT OR IGNORE INTO undo(pathname,redoflag,existsflag,content)"
   252         -    " VALUES(%Q,0,%d,:c)",
   253         -    zPathname, existsFlag
          267  +    "INSERT OR IGNORE INTO undo(pathname,redoflag,existsflag,islink,content)"
          268  +    " VALUES(%Q,0,%d,%d,:c)",
          269  +    zPathname, existsFlag, isLink
   254    270     );
   255    271     if( existsFlag ){
   256         -    blob_read_from_file(&content, zFullname);
          272  +    if( isLink ){
          273  +      blob_read_link(&content, zFullname); 
          274  +    }else{
          275  +      blob_read_from_file(&content, zFullname);
          276  +    }
   257    277       db_bind_blob(&q, ":c", &content);
   258    278     }
   259    279     free(zFullname);
   260    280     db_step(&q);
   261    281     db_finalize(&q);
   262    282     if( existsFlag ){
   263    283       blob_reset(&content);

Changes to src/update.c.

   193    193     db_multi_exec(
   194    194       "DROP TABLE IF EXISTS fv;"
   195    195       "CREATE TEMP TABLE fv("
   196    196       "  fn TEXT PRIMARY KEY,"      /* The filename relative to root */
   197    197       "  idv INTEGER,"              /* VFILE entry for current version */
   198    198       "  idt INTEGER,"              /* VFILE entry for target version */
   199    199       "  chnged BOOLEAN,"           /* True if current version has been edited */
          200  +    "  islinkv BOOLEAN,"          /* True if current file is a link */
          201  +    "  islinkt BOOLEAN,"          /* True if target file is a link */
   200    202       "  ridv INTEGER,"             /* Record ID for current version */
   201    203       "  ridt INTEGER,"             /* Record ID for target */
   202    204       "  fnt TEXT"                  /* Filename of same file on target version */
   203    205       ");"
   204    206     );
   205    207   
   206    208     /* Add files found in the current version
................................................................................
   243    245     */
   244    246     db_multi_exec(
   245    247       "UPDATE fv SET"
   246    248       " idt=coalesce((SELECT id FROM vfile WHERE vid=%d AND pathname=fnt),0),"
   247    249       " ridt=coalesce((SELECT rid FROM vfile WHERE vid=%d AND pathname=fnt),0)",
   248    250       tid, tid
   249    251     );
          252  +
          253  +  /*
          254  +  ** Add islink information
          255  +  */
          256  +  db_multi_exec(
          257  +    "UPDATE fv SET"
          258  +    " islinkv=coalesce((SELECT islink FROM vfile WHERE vid=%d AND pathname=fnt),0),"
          259  +    " islinkt=coalesce((SELECT islink FROM vfile WHERE vid=%d AND pathname=fnt),0)",
          260  +    vid, tid
          261  +  );
          262  +
   250    263   
   251    264     if( debugFlag ){
   252    265       db_prepare(&q,
   253         -       "SELECT rowid, fn, fnt, chnged, ridv, ridt FROM fv"
          266  +       "SELECT rowid, fn, fnt, chnged, ridv, ridt, islinkv, islinkt FROM fv"
   254    267       );
   255    268       while( db_step(&q)==SQLITE_ROW ){
   256         -       printf("%3d: ridv=%-4d ridt=%-4d chnged=%d\n",
          269  +       printf("%3d:  ridv=%-4d  ridt=%-4d  chnged=%d  islinkv=%d  islinkt=%d\n",
   257    270             db_column_int(&q, 0),
   258    271             db_column_int(&q, 4),
   259    272             db_column_int(&q, 5),
   260         -          db_column_int(&q, 3));
          273  +          db_column_int(&q, 3),
          274  +          db_column_int(&q, 6),
          275  +          db_column_int(&q, 7));
   261    276          printf("     fnv = [%s]\n", db_column_text(&q, 1));
   262    277          printf("     fnt = [%s]\n", db_column_text(&q, 2));
   263    278       }
   264    279       db_finalize(&q);
   265    280     }
   266    281   
   267    282     /* If FILES appear on the command-line, remove from the "fv" table
................................................................................
   297    312     }
   298    313   
   299    314     /*
   300    315     ** Alter the content of the checkout so that it conforms with the
   301    316     ** target
   302    317     */
   303    318     db_prepare(&q, 
   304         -    "SELECT fn, idv, ridv, idt, ridt, chnged, fnt FROM fv ORDER BY 1"
          319  +    "SELECT fn, idv, ridv, idt, ridt, chnged, fnt, islinkv, islinkt FROM fv ORDER BY 1"
   305    320     );
   306    321     db_prepare(&mtimeXfer,
   307    322       "UPDATE vfile SET mtime=(SELECT mtime FROM vfile WHERE id=:idv)"
   308    323       " WHERE id=:idt"
   309    324     );
   310    325     assert( g.zLocalRoot!=0 );
   311    326     assert( strlen(g.zLocalRoot)>1 );
................................................................................
   314    329       const char *zName = db_column_text(&q, 0);  /* The filename from root */
   315    330       int idv = db_column_int(&q, 1);             /* VFILE entry for current */
   316    331       int ridv = db_column_int(&q, 2);            /* RecordID for current */
   317    332       int idt = db_column_int(&q, 3);             /* VFILE entry for target */
   318    333       int ridt = db_column_int(&q, 4);            /* RecordID for target */
   319    334       int chnged = db_column_int(&q, 5);          /* Current is edited */
   320    335       const char *zNewName = db_column_text(&q,6);/* New filename */
          336  +    int islinkv = db_column_int(&q, 7);         /* Is current file is a link */
          337  +    int islinkt = db_column_int(&q, 8);         /* Is target file is a link */
   321    338       char *zFullPath;                            /* Full pathname of the file */
   322    339       char *zFullNewPath;                         /* Full pathname of dest */
   323    340       char nameChng;                              /* True if the name changed */
   324    341   
   325    342       zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
   326    343       zFullNewPath = mprintf("%s%s", g.zLocalRoot, zNewName);
   327    344       nameChng = fossil_strcmp(zName, zNewName);
................................................................................
   367    384         Blob e, r, t, v;
   368    385         int rc;
   369    386         if( nameChng ){
   370    387           printf("MERGE %s -> %s\n", zName, zNewName);
   371    388         }else{
   372    389           printf("MERGE %s\n", zName);
   373    390         }
   374         -      undo_save(zName);
   375         -      content_get(ridt, &t);
   376         -      content_get(ridv, &v);
   377         -      blob_zero(&e);
   378         -      blob_read_from_file(&e, zFullPath);
   379         -      rc = blob_merge(&v, &e, &t, &r);
   380         -      if( rc>=0 ){
   381         -        if( !nochangeFlag ) blob_write_to_file(&r, zFullNewPath);
   382         -        if( rc>0 ){
   383         -          printf("***** %d merge conflicts in %s\n", rc, zNewName);
          391  +      if( islinkv || islinkt /* || file_islink(zFullPath) */ ){
          392  +        //if( !nochangeFlag ) blob_write_to_file(&t, zFullNewPath);
          393  +        printf("***** Cannot merge symlink %s\n", zNewName);
          394  +        nConflict++;        
          395  +      }else{
          396  +        undo_save(zName);
          397  +        content_get(ridt, &t);
          398  +        content_get(ridv, &v);
          399  +        blob_zero(&e);
          400  +        blob_read_from_file(&e, zFullPath);
          401  +        rc = blob_merge(&v, &e, &t, &r);
          402  +        if( rc>=0 ){
          403  +          if( !nochangeFlag ) blob_write_to_file(&r, zFullNewPath);
          404  +          if( rc>0 ){
          405  +            printf("***** %d merge conflicts in %s\n", rc, zNewName);
          406  +            nConflict++;
          407  +          }
          408  +        }else{
          409  +          if( !nochangeFlag ) blob_write_to_file(&t, zFullNewPath);
          410  +          printf("***** Cannot merge binary file %s\n", zNewName);
   384    411             nConflict++;
   385    412           }
   386         -      }else{
   387         -        if( !nochangeFlag ) blob_write_to_file(&t, zFullNewPath);
   388         -        printf("***** Cannot merge binary file %s\n", zNewName);
   389         -        nConflict++;
          413  +        if( nameChng && !nochangeFlag ) unlink(zFullPath);
          414  +        blob_reset(&v);
          415  +        blob_reset(&e);
          416  +        blob_reset(&t);
          417  +        blob_reset(&r);
   390    418         }
   391         -      if( nameChng && !nochangeFlag ) unlink(zFullPath);
   392         -      blob_reset(&v);
   393         -      blob_reset(&e);
   394         -      blob_reset(&t);
   395         -      blob_reset(&r);
   396    419       }else{
   397    420         if( chnged ){
   398    421           if( verboseFlag ) printf("EDITED %s\n", zName);
   399    422         }else{
   400    423           db_bind_int(&mtimeXfer, ":idv", idv);
   401    424           db_bind_int(&mtimeXfer, ":idt", idt);
   402    425           db_step(&mtimeXfer);
................................................................................
   450    473   ** Get the contents of a file within the checking "revision".  If
   451    474   ** revision==NULL then get the file content for the current checkout.
   452    475   */
   453    476   int historical_version_of_file(
   454    477     const char *revision,    /* The checkin containing the file */
   455    478     const char *file,        /* Full treename of the file */
   456    479     Blob *content,           /* Put the content here */
          480  +  int *isLink,             /* Put islink here. Pass NULL if you don't need it */
   457    481     int errCode              /* Error code if file not found.  Panic if 0. */
   458    482   ){
   459    483     Manifest *pManifest;
   460    484     ManifestFile *pFile;
   461    485     int rid=0;
   462    486     
   463    487     if( revision ){
................................................................................
   473    497     
   474    498     if( pManifest ){
   475    499       manifest_file_rewind(pManifest);
   476    500       while( (pFile = manifest_file_next(pManifest,0))!=0 ){
   477    501         if( fossil_strcmp(pFile->zName, file)==0 ){
   478    502           rid = uuid_to_rid(pFile->zUuid, 0);
   479    503           manifest_destroy(pManifest);
          504  +        if( isLink!=NULL ){
          505  +          *isLink = strstr(pFile->zPerm, "l") ? 1 : 0;
          506  +        }
   480    507           return content_get(rid, content);
   481    508         }
   482    509       }
   483    510       manifest_destroy(pManifest);
   484    511       if( errCode<=0 ){
   485    512         fossil_fatal("file %s does not exist in checkin: %s", file, revision);
   486    513       }
................................................................................
   548    575         "  FROM vfile "
   549    576         " WHERE chnged OR deleted OR rid=0 OR pathname!=origname;"
   550    577       );
   551    578     }
   552    579     blob_zero(&record);
   553    580     db_prepare(&q, "SELECT name FROM torevert");
   554    581     while( db_step(&q)==SQLITE_ROW ){
          582  +    int isLink = 0;
   555    583       zFile = db_column_text(&q, 0);
   556    584       if( zRevision!=0 ){
   557         -      errCode = historical_version_of_file(zRevision, zFile, &record, 2);
          585  +      errCode = historical_version_of_file(zRevision, zFile, &record, &isLink, 2);
   558    586       }else{
   559    587         rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q", zFile);
   560    588         if( rid==0 ){
   561    589           errCode = 2;
   562    590         }else{
   563    591           content_get(rid, &record);
   564    592           errCode = 0;
................................................................................
   566    594       }
   567    595   
   568    596       if( errCode==2 ){
   569    597         fossil_warning("file not in repository: %s", zFile);
   570    598       }else{
   571    599         char *zFull = mprintf("%/%/", g.zLocalRoot, zFile);
   572    600         undo_save(zFile);
   573         -      blob_write_to_file(&record, zFull);
          601  +      if( file_size(zFull)>=0 && (isLink || file_islink(zFull)) ){
          602  +        unlink(zFull);
          603  +      }
          604  +      if( isLink ){
          605  +        create_symlink(blob_str(&record), zFull);
          606  +      }else{
          607  +        blob_write_to_file(&record, zFull);
          608  +      }
   574    609         printf("REVERTED: %s\n", zFile);
   575    610         if( zRevision==0 ){
   576    611           sqlite3_int64 mtime = file_mtime(zFull);
   577    612           db_multi_exec(
   578    613              "UPDATE vfile"
   579         -           "   SET mtime=%lld, chnged=0, deleted=0,"
          614  +           "   SET mtime=%lld, chnged=0, deleted=0, islink=%d,"
   580    615              "       pathname=coalesce(origname,pathname), origname=NULL"     
   581    616              " WHERE pathname=%Q",
   582         -           mtime, zFile
          617  +           mtime, isLink, zFile
   583    618           );
   584    619         }
   585    620         free(zFull);
   586    621       }
   587    622       blob_reset(&record);
   588    623     }
   589    624     db_finalize(&q);
   590    625     undo_finish();
   591    626     db_end_transaction(0);
   592    627   }

Changes to src/vfile.c.

    82     82     ManifestFile *pFile;
    83     83   
    84     84     db_begin_transaction();
    85     85     p = manifest_get(vid, CFTYPE_MANIFEST);
    86     86     if( p==0 ) return;
    87     87     db_multi_exec("DELETE FROM vfile WHERE vid=%d", vid);
    88     88     db_prepare(&ins,
    89         -    "INSERT INTO vfile(vid,rid,mrid,pathname) "
    90         -    " VALUES(:vid,:id,:id,:name)");
           89  +    "INSERT INTO vfile(vid,rid,mrid,pathname,islink) "
           90  +    " VALUES(:vid,:id,:id,:name,:islink)");
    91     91     db_bind_int(&ins, ":vid", vid);
    92     92     manifest_file_rewind(p);
    93     93     while( (pFile = manifest_file_next(p,0))!=0 ){
    94     94       if( pFile->zUuid==0 || uuid_is_shunned(pFile->zUuid) ) continue;
    95     95       rid = uuid_to_rid(pFile->zUuid, 0);
    96     96       if( rid==0 || db_int(-1, "SELECT size FROM blob WHERE rid=%d", rid)<0 ){
    97     97         fossil_warning("content missing for %s", pFile->zName);
    98     98         continue;
    99     99       }
   100    100       db_bind_int(&ins, ":id", rid);
   101    101       db_bind_text(&ins, ":name", pFile->zName);
          102  +    db_bind_int(&ins, ":islink", pFile->zPerm && strstr(pFile->zPerm, "l"));
   102    103       db_step(&ins);
   103    104       db_reset(&ins);
   104    105     }
   105    106     db_finalize(&ins);
   106    107     manifest_destroy(p);
   107    108     db_end_transaction(0);
   108    109   }
................................................................................
   146    147       zName = db_column_text(&q, 1);
   147    148       rid = db_column_int(&q, 2);
   148    149       isDeleted = db_column_int(&q, 3);
   149    150       oldChnged = db_column_int(&q, 4);
   150    151       oldMtime = db_column_int64(&q, 7);
   151    152       if( isDeleted ){
   152    153         chnged = 1;
   153         -    }else if( !file_isfile(zName) && file_size(0)>=0 ){
          154  +    }else if( !file_isfile_or_link(zName) && file_size(0)>=0 ){
   154    155         if( notFileIsFatal ){
   155    156           fossil_warning("not an ordinary file: %s", zName);
   156    157           nErr++;
   157    158         }
   158    159         chnged = 1;
   159    160       }else if( oldChnged>=2 ){
   160    161         chnged = oldChnged;
................................................................................
   204    205     int promptFlag         /* Prompt user to confirm overwrites */
   205    206   ){
   206    207     Stmt q;
   207    208     Blob content;
   208    209     int nRepos = strlen(g.zLocalRoot);
   209    210   
   210    211     if( vid>0 && id==0 ){
   211         -    db_prepare(&q, "SELECT id, %Q || pathname, mrid"
          212  +    db_prepare(&q, "SELECT id, %Q || pathname, mrid, islink"
   212    213                      "  FROM vfile"
   213    214                      " WHERE vid=%d AND mrid>0",
   214    215                      g.zLocalRoot, vid);
   215    216     }else{
   216    217       assert( vid==0 && id>0 );
   217         -    db_prepare(&q, "SELECT id, %Q || pathname, mrid"
          218  +    db_prepare(&q, "SELECT id, %Q || pathname, mrid, islink"
   218    219                      "  FROM vfile"
   219    220                      " WHERE id=%d AND mrid>0",
   220    221                      g.zLocalRoot, id);
   221    222     }
   222    223     while( db_step(&q)==SQLITE_ROW ){
   223         -    int id, rid;
          224  +    int id, rid, isLink;
   224    225       const char *zName;
   225    226   
   226    227       id = db_column_int(&q, 0);
   227    228       zName = db_column_text(&q, 1);
   228    229       rid = db_column_int(&q, 2);
          230  +    isLink = db_column_int(&q, 3);
   229    231       content_get(rid, &content);
   230    232       if( file_is_the_same(&content, zName) ){
   231    233         blob_reset(&content);
   232    234         continue;
   233    235       }
   234    236       if( promptFlag && file_size(zName)>=0 ){
   235    237         Blob ans;
................................................................................
   246    248         }
   247    249         if( cReply=='n' || cReply=='N' ){
   248    250           blob_reset(&content);
   249    251           continue;
   250    252         }
   251    253       }
   252    254       if( verbose ) printf("%s\n", &zName[nRepos]);
   253         -    blob_write_to_file(&content, zName);
          255  +    if( file_isdir(zName) == 1 ){
          256  +      //TODO remove directories?
          257  +      fossil_fatal("%s is directory, cannot overwrite\n", zName);
          258  +    }    
          259  +    if( file_size(zName)>=0 && (isLink || file_islink(zName)) ){
          260  +      unlink(zName);
          261  +    }
          262  +    if( isLink ){
          263  +      create_symlink(blob_str(&content), zName);
          264  +    }else{
          265  +      blob_write_to_file(&content, zName);
          266  +    }
   254    267       blob_reset(&content);
   255    268       db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d",
   256    269                     file_mtime(zName), id);
   257    270     }
   258    271     db_finalize(&q);
   259    272   }
   260    273   
................................................................................
   303    316           if( pEntry->d_name[1]==0 ) continue;
   304    317           if( pEntry->d_name[1]=='.' && pEntry->d_name[2]==0 ) continue;
   305    318         }
   306    319         blob_appendf(pPath, "/%s", pEntry->d_name);
   307    320         zPath = blob_str(pPath);
   308    321         if( file_isdir(zPath)==1 ){
   309    322           vfile_scan(vid, pPath, nPrefix, allFlag);
   310         -      }else if( file_isfile(zPath) && !db_exists(zSql, &zPath[nPrefix+1]) ){
          323  +      }else if( file_isfile_or_link(zPath) && !db_exists(zSql, &zPath[nPrefix+1]) ){
   311    324           db_multi_exec("INSERT INTO sfile VALUES(%Q)", &zPath[nPrefix+1]);
   312    325         }
   313    326         blob_resize(pPath, origSize);
   314    327       }
   315    328     }
   316    329     closedir(d);
   317    330   }
................................................................................
   354    367     while( db_step(&q)==SQLITE_ROW ){
   355    368       const char *zFullpath = db_column_text(&q, 0);
   356    369       const char *zName = db_column_text(&q, 1);
   357    370       int isSelected = db_column_int(&q, 3);
   358    371   
   359    372       if( isSelected ){
   360    373         md5sum_step_text(zName, -1);
   361         -      in = fopen(zFullpath,"rb");
   362         -      if( in==0 ){
   363         -        md5sum_step_text(" 0\n", -1);
   364         -        continue;
   365         -      }
   366         -      fseek(in, 0L, SEEK_END);
   367         -      sqlite3_snprintf(sizeof(zBuf), zBuf, " %ld\n", ftell(in));
   368         -      fseek(in, 0L, SEEK_SET);
   369         -      md5sum_step_text(zBuf, -1);
   370         -      /*printf("%s %s %s",md5sum_current_state(),zName,zBuf); fflush(stdout);*/
   371         -      for(;;){
   372         -        int n;
   373         -        n = fread(zBuf, 1, sizeof(zBuf), in);
   374         -        if( n<=0 ) break;
   375         -        md5sum_step_text(zBuf, n);
   376         -      }
   377         -      fclose(in);
          374  +      if( file_islink(zFullpath) ){
          375  +        /* Instead of file content, use link destination path */
          376  +        Blob pathBuf;
          377  +
          378  +        sqlite3_snprintf(sizeof(zBuf), zBuf, " %ld\n", 
          379  +                         blob_read_link(&pathBuf, zFullpath));
          380  +        md5sum_step_text(zBuf, -1);
          381  +        md5sum_step_text(blob_str(&pathBuf), -1);
          382  +        blob_reset(&pathBuf);
          383  +      }else{
          384  +        in = fopen(zFullpath,"rb");
          385  +        if( in==0 ){
          386  +          md5sum_step_text(" 0\n", -1);
          387  +          continue;
          388  +        }
          389  +        fseek(in, 0L, SEEK_END);
          390  +        sqlite3_snprintf(sizeof(zBuf), zBuf, " %ld\n", ftell(in));
          391  +        fseek(in, 0L, SEEK_SET);
          392  +        md5sum_step_text(zBuf, -1);
          393  +        /*printf("%s %s %s",md5sum_current_state(),zName,zBuf); fflush(stdout);*/
          394  +        for(;;){
          395  +          int n;
          396  +          n = fread(zBuf, 1, sizeof(zBuf), in);
          397  +          if( n<=0 ) break;
          398  +          md5sum_step_text(zBuf, n);
          399  +        }
          400  +        fclose(in);
          401  +      }
   378    402       }else{
   379    403         int rid = db_column_int(&q, 4);
   380    404         const char *zOrigName = db_column_text(&q, 2);
   381    405         char zBuf[100];
   382    406         Blob file;
   383    407   
   384    408         if( zOrigName ) zName = zOrigName;
................................................................................
   415    439     md5sum_init();
   416    440     while( db_step(&q)==SQLITE_ROW ){
   417    441       const char *zFullpath = db_column_text(&q, 0);
   418    442       const char *zName = db_column_text(&q, 1);
   419    443       int rid = db_column_int(&q, 2);
   420    444   
   421    445       blob_zero(&disk);
   422         -    rc = blob_read_from_file(&disk, zFullpath);
   423         -    if( rc<0 ){
   424         -      printf("ERROR: cannot read file [%s]\n", zFullpath);
   425         -      blob_reset(&disk);
   426         -      continue;
          446  +    if( file_islink(zFullpath) ){
          447  +      blob_read_link(&disk, zFullpath);
          448  +    }else{
          449  +      rc = blob_read_from_file(&disk, zFullpath);
          450  +      if( rc<0 ){
          451  +        printf("ERROR: cannot read file [%s]\n", zFullpath);
          452  +        blob_reset(&disk);
          453  +        continue;
          454  +      }
   427    455       }
   428    456       blob_zero(&repo);
   429    457       content_get(rid, &repo);
   430    458       if( blob_size(&repo)!=blob_size(&disk) ){
   431    459         printf("ERROR: [%s] is %d bytes on disk but %d in the repository\n",
   432    460                zName, blob_size(&disk), blob_size(&repo));
   433    461         blob_reset(&disk);