| | @@ -716,10 +716,328 @@ |
| 716 | 716 | malformed_line: |
| 717 | 717 | trim_newline(zLine); |
| 718 | 718 | fossil_fatal("bad fast-import line: [%s]", zLine); |
| 719 | 719 | return; |
| 720 | 720 | } |
| 721 | + |
| 722 | +typedef struct { |
| 723 | + const char *zKey; |
| 724 | + const char *zVal; |
| 725 | +} KeyVal; |
| 726 | +typedef struct { |
| 727 | + KeyVal *aHeaders; |
| 728 | + int nHeaders; |
| 729 | + char *pRawProps; |
| 730 | + KeyVal *aProps; |
| 731 | + int nProps; |
| 732 | + Blob content; |
| 733 | + int nContent; |
| 734 | +} SvnRecord; |
| 735 | + |
| 736 | +#define svn_find_header(rec, zHeader) \ |
| 737 | + svn_find_keyval((rec).aHeaders, (rec).nHeaders, (zHeader)) |
| 738 | +#define svn_find_prop(rec, zProp) \ |
| 739 | + svn_find_keyval((rec).aProps, (rec).nProps, (zProp)) |
| 740 | +static const char *svn_find_keyval( |
| 741 | + KeyVal *aKeyVal, |
| 742 | + int nKeyVal, |
| 743 | + const char *zKey |
| 744 | +){ |
| 745 | + int i; |
| 746 | + for(i=0; i<nKeyVal; i++){ |
| 747 | + if( fossil_strcmp(aKeyVal[i].zKey, zKey)==0 ){ |
| 748 | + return aKeyVal[i].zVal; |
| 749 | + } |
| 750 | + } |
| 751 | + return 0; |
| 752 | +} |
| 753 | + |
| 754 | +static void svn_free_rec(SvnRecord *rec){ |
| 755 | + int i; |
| 756 | + for(i=0; i<rec->nHeaders; i++){ |
| 757 | + fossil_free(rec->aHeaders[i].zKey); |
| 758 | + } |
| 759 | + fossil_free(rec->aHeaders); |
| 760 | + fossil_free(rec->aProps); |
| 761 | + fossil_free(rec->pRawProps); |
| 762 | + blob_reset(&rec->content); |
| 763 | +} |
| 764 | + |
| 765 | +static int svn_read_headers(FILE *pIn, SvnRecord *rec){ |
| 766 | + char zLine[1000]; |
| 767 | + |
| 768 | + rec->aHeaders = 0; |
| 769 | + rec->nHeaders = 0; |
| 770 | + while( fgets(zLine, sizeof(zLine), pIn) ){ |
| 771 | + if( zLine[0]!='\n' ) break; |
| 772 | + } |
| 773 | + if( feof(pIn) ) return 0; |
| 774 | + do{ |
| 775 | + char *sep; |
| 776 | + if( zLine[0]=='\n' ) break; |
| 777 | + rec->nHeaders += 1; |
| 778 | + rec->aHeaders = fossil_realloc(rec->aHeaders, |
| 779 | + sizeof(rec->aHeaders[0])*rec->nHeaders); |
| 780 | + rec->aHeaders[rec->nHeaders-1].zKey = mprintf("%s", zLine); |
| 781 | + sep = strchr(rec->aHeaders[rec->nHeaders-1].zKey, ':'); |
| 782 | + if( !sep ){ |
| 783 | + trim_newline(zLine); |
| 784 | + fossil_fatal("bad header line: [%s]", zLine); |
| 785 | + } |
| 786 | + *sep = 0; |
| 787 | + rec->aHeaders[rec->nHeaders-1].zVal = sep+1; |
| 788 | + sep = strchr(rec->aHeaders[rec->nHeaders-1].zVal, '\n'); |
| 789 | + *sep = 0; |
| 790 | + while(rec->aHeaders[rec->nHeaders-1].zVal |
| 791 | + && fossil_isspace(*(rec->aHeaders[rec->nHeaders-1].zVal)) ) |
| 792 | + { |
| 793 | + rec->aHeaders[rec->nHeaders-1].zVal++; |
| 794 | + } |
| 795 | + }while( fgets(zLine, sizeof(zLine), pIn) ); |
| 796 | + if( zLine[0]!='\n' ){ |
| 797 | + trim_newline(zLine); |
| 798 | + fossil_fatal("svn-dump data ended unexpectedly"); |
| 799 | + } |
| 800 | + return 1; |
| 801 | +} |
| 802 | + |
| 803 | +static void svn_read_props(FILE *pIn, SvnRecord *rec){ |
| 804 | + int nRawProps = 0; |
| 805 | + char *pRawProps; |
| 806 | + const char *zLen; |
| 807 | + |
| 808 | + rec->pRawProps = 0; |
| 809 | + rec->aProps = 0; |
| 810 | + rec->nProps = 0; |
| 811 | + zLen = svn_find_header(*rec, "Prop-content-length"); |
| 812 | + if( zLen ){ |
| 813 | + nRawProps = atoi(zLen); |
| 814 | + } |
| 815 | + if( nRawProps ){ |
| 816 | + int got; |
| 817 | + char *zLine; |
| 818 | + rec->pRawProps = pRawProps = fossil_malloc( nRawProps ); |
| 819 | + got = fread(rec->pRawProps, 1, nRawProps, pIn); |
| 820 | + if( got!=nRawProps ){ |
| 821 | + fossil_fatal("short read: got %d of %d bytes", got, nRawProps); |
| 822 | + } |
| 823 | + if( memcmp(&pRawProps[got-10], "PROPS-END\n", 10)!=0 ){ |
| 824 | + fossil_fatal("svn-dump data ended unexpectedly"); |
| 825 | + } |
| 826 | + zLine = pRawProps; |
| 827 | + while( zLine<(pRawProps+nRawProps-10) ){ |
| 828 | + char *eol; |
| 829 | + int propLen; |
| 830 | + if( zLine[0]!='K' ){ |
| 831 | + fossil_fatal("svn-dump data format broken"); |
| 832 | + } |
| 833 | + propLen = atoi(&zLine[2]); |
| 834 | + eol = strchr(zLine, '\n'); |
| 835 | + zLine = eol+1; |
| 836 | + eol = zLine+propLen; |
| 837 | + if( *eol!='\n' ){ |
| 838 | + fossil_fatal("svn-dump data format broken"); |
| 839 | + } |
| 840 | + *eol = 0; |
| 841 | + rec->nProps += 1; |
| 842 | + rec->aProps = fossil_realloc(rec->aProps, |
| 843 | + sizeof(rec->aProps[0])*rec->nProps); |
| 844 | + rec->aProps[rec->nProps-1].zKey = zLine; |
| 845 | + zLine = eol+1; |
| 846 | + if( zLine[0]!='V' ){ |
| 847 | + fossil_fatal("svn-dump data format broken"); |
| 848 | + } |
| 849 | + propLen = atoi(&zLine[2]); |
| 850 | + eol = strchr(zLine, '\n'); |
| 851 | + zLine = eol+1; |
| 852 | + eol = zLine+propLen; |
| 853 | + if( *eol!='\n' ){ |
| 854 | + fossil_fatal("svn-dump data format broken"); |
| 855 | + } |
| 856 | + *eol = 0; |
| 857 | + rec->aProps[rec->nProps-1].zVal = zLine; |
| 858 | + zLine = eol+1; |
| 859 | + } |
| 860 | + } |
| 861 | +} |
| 862 | + |
| 863 | +static int svn_read_rec(FILE *pIn, SvnRecord *rec){ |
| 864 | + const char *zLen; |
| 865 | + int nLen = 0; |
| 866 | + if( svn_read_headers(pIn, rec)==0 ) return 0; |
| 867 | + svn_read_props(pIn, rec); |
| 868 | + blob_zero(&rec->content); |
| 869 | + zLen = svn_find_header(*rec, "Text-content-length"); |
| 870 | + if( zLen ){ |
| 871 | + nLen = atoi(zLen); |
| 872 | + } |
| 873 | + if( nLen>=0 ){ |
| 874 | + blob_read_from_channel(&rec->content, pIn, nLen); |
| 875 | + if( blob_size(&rec->content)!=nLen ){ |
| 876 | + fossil_fatal("short read: got %d of %d bytes", |
| 877 | + blob_size(&rec->content), nLen |
| 878 | + ); |
| 879 | + } |
| 880 | + } |
| 881 | + return 1; |
| 882 | +} |
| 883 | + |
| 884 | +static void svn_create_manifests(){ |
| 885 | + Blob manifest; |
| 886 | + Stmt qRev; |
| 887 | + Stmt qFiles; |
| 888 | + |
| 889 | + blob_zero(&manifest); |
| 890 | + db_prepare(&qRev, "SELECT trev, tuser, tmsg, ttime FROM xrevisions" |
| 891 | + " ORDER BY trev"); |
| 892 | + db_prepare(&qFiles, "SELECT tpath, uuid, tperm" |
| 893 | + " FROM xfiles JOIN blob ON xfiles.trid=blob.rid" |
| 894 | + " WHERE trev=:rev ORDER BY tpath"); |
| 895 | + while( db_step(&qRev)==SQLITE_ROW ){ |
| 896 | + int rev = db_column_int(&qRev, 0); |
| 897 | + const char *zUser = db_column_text(&qRev, 1); |
| 898 | + const char *zMsg = db_column_text(&qRev, 2); |
| 899 | + const char *zTime = db_column_text(&qRev, 3); |
| 900 | + int parentRid = 0; |
| 901 | + Blob mcksum; |
| 902 | + blob_reset(&manifest); |
| 903 | + if( zMsg ){ |
| 904 | + blob_appendf(&manifest, "C %F\n", zMsg); |
| 905 | + }else{ |
| 906 | + blob_append(&manifest, "C (no\\scomment)\n", 16); |
| 907 | + } |
| 908 | + blob_appendf(&manifest, "D %s\n", zTime); |
| 909 | + db_bind_int(&qFiles, ":rev", rev); |
| 910 | + while( db_step(&qFiles)==SQLITE_ROW ){ |
| 911 | + const char *zFile = db_column_text(&qFiles, 0); |
| 912 | + const char *zUuid = db_column_text(&qFiles, 1); |
| 913 | + const char *zPerm = db_column_text(&qFiles, 2); |
| 914 | + blob_appendf(&manifest, "F %F %s %s\n", zFile, zUuid, zPerm); |
| 915 | + } |
| 916 | + db_reset(&qFiles); |
| 917 | + if( parentRid>0 ){ |
| 918 | + const char *zParent; |
| 919 | + zParent = db_text(0, "SELECT uuid FROM blob WEHRE rid=%d", parentRid); |
| 920 | + blob_appendf(&manifest, "P %s", zParent); |
| 921 | + fossil_free(zParent); |
| 922 | + } |
| 923 | + if( zUser ){ |
| 924 | + blob_appendf(&manifest, "U %F\n", zUser); |
| 925 | + }else{ |
| 926 | + const char *zUserOvrd = find_option("user-override",0,1); |
| 927 | + blob_appendf(&manifest, "U %F\n", |
| 928 | + zUserOvrd ? zUserOvrd : login_name()); |
| 929 | + } |
| 930 | + md5sum_blob(&manifest, &mcksum); |
| 931 | + blob_appendf(&manifest, "Z %b\n", &mcksum); |
| 932 | + blob_reset(&mcksum); |
| 933 | + |
| 934 | + parentRid = content_put(&manifest); |
| 935 | + } |
| 936 | + db_finalize(&qRev); |
| 937 | + db_finalize(&qFiles); |
| 938 | +} |
| 939 | +/* |
| 940 | +** Read the svn-dump format from pIn and insert the corresponding |
| 941 | +** content into the database. |
| 942 | +*/ |
| 943 | +static void svn_dump_import(FILE *pIn){ |
| 944 | + SvnRecord rec; |
| 945 | + int ver; |
| 946 | + const char *zTemp; |
| 947 | + const char *zUuid; |
| 948 | + Stmt insRev; |
| 949 | + Stmt insFile; |
| 950 | + int rev = 0; |
| 951 | + |
| 952 | + /* version */ |
| 953 | + if( svn_read_rec(pIn, &rec) |
| 954 | + && (zTemp = svn_find_header(rec, "SVN-fs-dump-format-version")) ){ |
| 955 | + ver = atoi(zTemp); |
| 956 | + if( ver!=2 ){ |
| 957 | + fossil_fatal("Unknown svn-dump format version: %d", ver); |
| 958 | + } |
| 959 | + }else{ |
| 960 | + fossil_fatal("Input is not an svn-dump!"); |
| 961 | + } |
| 962 | + svn_free_rec(&rec); |
| 963 | + /* UUID */ |
| 964 | + if( !svn_read_rec(pIn, &rec) || !(zUuid = svn_find_header(rec, "UUID")) ){ |
| 965 | + fossil_fatal("Missing UUID!"); |
| 966 | + } |
| 967 | + svn_free_rec(&rec); |
| 968 | + /* content */ |
| 969 | + db_prepare(&insRev, |
| 970 | + "INSERT INTO xrevisions (trev, tuser, tmsg, ttime)" |
| 971 | + "VALUES(:rev, :user, :msg, :time)" |
| 972 | + ); |
| 973 | + db_prepare(&insFile, |
| 974 | + "INSERT INTO xfiles (trev, tpath, trid, tperm)" |
| 975 | + "VALUES(:rev, :path, :rid, :perm)" |
| 976 | + ); |
| 977 | + while( svn_read_rec(pIn, &rec) ){ |
| 978 | + if( zTemp = svn_find_header(rec, "Revision-number") ){ |
| 979 | + const char *zUser = svn_find_prop(rec, "svn:author"); |
| 980 | + const char *zLog = svn_find_prop(rec, "svn:log"); |
| 981 | + const char *zDate = svn_find_prop(rec, "svn:date"); |
| 982 | + zDate = date_in_standard_format(zDate); |
| 983 | + rev = atoi(zTemp); |
| 984 | + db_bind_int(&insRev, ":rev", rev); |
| 985 | + db_bind_text(&insRev, ":user", zUser); |
| 986 | + db_bind_text(&insRev, ":msg", zLog); |
| 987 | + db_bind_text(&insRev, ":time", zDate); |
| 988 | + db_step(&insRev); |
| 989 | + db_reset(&insRev); |
| 990 | + fossil_free(zDate); |
| 991 | + }else |
| 992 | + if( zTemp = svn_find_header(rec, "Node-path") ){ |
| 993 | + const char *zPath = zTemp; |
| 994 | + const char *zAction = svn_find_header(rec, "Node-action"); |
| 995 | + const char *zKind = svn_find_header(rec, "Node-kind"); |
| 996 | + const char *zSrcPath = svn_find_header(rec, "Node-copyfrom-path"); |
| 997 | + const char *zPerm = svn_find_prop(rec, "svn:executable") ? "x" : 0; |
| 998 | + int srcRev = -1; |
| 999 | + int rid = 0; |
| 1000 | + if( zKind && strncmp(zKind, "dir", 3)==0 ){ |
| 1001 | + svn_free_rec(&rec); |
| 1002 | + continue; |
| 1003 | + } |
| 1004 | + zTemp = svn_find_header(rec, "Node-copyfrom-rev"); |
| 1005 | + if( zTemp ){ |
| 1006 | + srcRev = atoi(zTemp); |
| 1007 | + } |
| 1008 | + rid = content_put(&rec.content); |
| 1009 | + if( strncmp(zAction, "add", 3)==0 ){ |
| 1010 | + db_bind_int(&insFile, ":rev", rev); |
| 1011 | + db_bind_int(&insFile, ":rid", rid); |
| 1012 | + db_bind_text(&insFile, ":path", zPath); |
| 1013 | + db_bind_text(&insFile, ":perm", zPerm); |
| 1014 | + db_step(&insFile); |
| 1015 | + db_reset(&insFile); |
| 1016 | + }else |
| 1017 | + if( strncmp(zAction, "change", 6)==0 ){ |
| 1018 | + db_bind_int(&insFile, ":rev", rev); |
| 1019 | + db_bind_int(&insFile, ":rid", rid); |
| 1020 | + db_bind_text(&insFile, ":path", zPath); |
| 1021 | + db_bind_text(&insFile, ":perm", zPerm); |
| 1022 | + db_step(&insFile); |
| 1023 | + db_reset(&insFile); |
| 1024 | + }else |
| 1025 | + if( strncmp(zAction, "delete", 6)==0 ){ |
| 1026 | + }else |
| 1027 | + if( strncmp(zAction, "replace", 7)==0 ){ |
| 1028 | + }else{ |
| 1029 | + } |
| 1030 | + }else{ |
| 1031 | + fossil_fatal("Unknown record type"); |
| 1032 | + } |
| 1033 | + svn_free_rec(&rec); |
| 1034 | + } |
| 1035 | + svn_create_manifests(); |
| 1036 | + db_finalize(&insRev); |
| 1037 | + db_finalize(&insFile); |
| 1038 | +} |
| 721 | 1039 | |
| 722 | 1040 | /* |
| 723 | 1041 | ** COMMAND: import |
| 724 | 1042 | ** |
| 725 | 1043 | ** Usage: %fossil import FORMAT ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE? |
| | @@ -804,11 +1122,22 @@ |
| 804 | 1122 | db_ephemeral_blob(&q, 0, &record); |
| 805 | 1123 | fast_insert_content(&record, 0, 0); |
| 806 | 1124 | import_reset(0); |
| 807 | 1125 | } |
| 808 | 1126 | db_finalize(&q); |
| 809 | | - }else if( svnFlag ){ |
| 1127 | + }else |
| 1128 | + if( svnFlag ){ |
| 1129 | + db_multi_exec( |
| 1130 | + "CREATE TEMP TABLE xrevisions(" |
| 1131 | + " trev INT, tuser TEXT, tmsg TEXT, ttime DATETIME" |
| 1132 | + ");" |
| 1133 | + "CREATE TEMP TABLE xfiles(" |
| 1134 | + " trev INT, tpath TEXT, trid TEXT, tperm TEXT" |
| 1135 | + ");" |
| 1136 | + ); |
| 1137 | + |
| 1138 | + svn_dump_import(pIn); |
| 810 | 1139 | } |
| 811 | 1140 | |
| 812 | 1141 | db_end_transaction(0); |
| 813 | 1142 | db_begin_transaction(); |
| 814 | 1143 | fossil_print("Rebuilding repository meta-data...\n"); |
| 815 | 1144 | |