Fossil SCM

Add new --attribute option to the 'fossil import --git' command that enables attributing commits to a username rather than the corresponding Git committer/author contact info of a given commit. Conversely, construct a full 'user <emailaddr>' Git committer string for commits when using 'fossil git export' by querying the new 'fx_git' table if it exists, or the 'info' column in the 'user' table. If no user specified emailaddr can be parsed, then use the generic '[email protected]' string.

jamsek 2020-11-12 14:26 trunk
Commit cd4fbdee000875dde44054a2c4e0a8394f279bf5f30235494e1259c946586e80
2 files changed +23 -3 +52 -1
+23 -3
--- src/export.c
+++ src/export.c
@@ -1088,10 +1088,11 @@
10881088
Blob sql; /* String of SQL for part of the query */
10891089
Blob comment; /* The comment text for the check-in */
10901090
int nErr = 0; /* Number of errors */
10911091
int bPhantomOk; /* True if phantom files should be ignored */
10921092
char buf[24];
1093
+ char *zEmail; /* Contact info for Git committer field */
10931094
10941095
pMan = manifest_get(rid, CFTYPE_MANIFEST, 0);
10951096
if( pMan==0 ){
10961097
/* Must be a phantom. Return without doing anything, and in particular
10971098
** without creating a mark for this check-in. */
@@ -1164,13 +1165,32 @@
11641165
fprintf(xCmd, "mark %s\n", zMark);
11651166
fossil_free(zMark);
11661167
sqlite3_snprintf(sizeof(buf), buf, "%lld",
11671168
(sqlite3_int64)((pMan->rDate-2440587.5)*86400.0)
11681169
);
1169
- fprintf(xCmd, "committer %s <%[email protected]> %s +0000\n",
1170
- pMan->zUser, pMan->zUser, buf
1171
- );
1170
+ /*
1171
+ ** Check for 'fx_' table from previous Git import, otherwise take contact info
1172
+ ** from user table for <emailaddr> in committer field. If no emailaddr, check
1173
+ ** if username is in email form, otherwise use generic '[email protected]'.
1174
+ */
1175
+ if (db_table_exists("repository", "fx_git")) {
1176
+ zEmail = db_text(0, "SELECT email FROM fx_git WHERE user=%Q", pMan->zUser);
1177
+ } else {
1178
+ zEmail = db_text(0, "SELECT info FROM user WHERE login=%Q", pMan->zUser);
1179
+ }
1180
+ /* Some repo 'info' fields return an empty string hence the second check */
1181
+ if (zEmail == NULL || zEmail[0] == '\0') {
1182
+ /* If username is in emailaddr form, don't append '@noemail.net' */
1183
+ if (strchr(pMan->zUser, '@') == NULL) {
1184
+ zEmail = mprintf("%[email protected]", pMan->zUser);
1185
+ } else {
1186
+ zEmail = fossil_strdup(pMan->zUser);
1187
+ }
1188
+ }
1189
+ fprintf(xCmd, "committer %s <%s> %s +0000\n", pMan->zUser,
1190
+ strchr(pMan->zUser, '@') == NULL ? zEmail : pMan->zUser, buf);
1191
+ fossil_free(zEmail);
11721192
blob_init(&comment, pMan->zComment, -1);
11731193
if( blob_size(&comment)==0 ){
11741194
blob_append(&comment, "(no comment)", -1);
11751195
}
11761196
blob_appendf(&comment, "\n\nFossilOrigin-Name: %s", zUuid);
11771197
--- src/export.c
+++ src/export.c
@@ -1088,10 +1088,11 @@
1088 Blob sql; /* String of SQL for part of the query */
1089 Blob comment; /* The comment text for the check-in */
1090 int nErr = 0; /* Number of errors */
1091 int bPhantomOk; /* True if phantom files should be ignored */
1092 char buf[24];
 
1093
1094 pMan = manifest_get(rid, CFTYPE_MANIFEST, 0);
1095 if( pMan==0 ){
1096 /* Must be a phantom. Return without doing anything, and in particular
1097 ** without creating a mark for this check-in. */
@@ -1164,13 +1165,32 @@
1164 fprintf(xCmd, "mark %s\n", zMark);
1165 fossil_free(zMark);
1166 sqlite3_snprintf(sizeof(buf), buf, "%lld",
1167 (sqlite3_int64)((pMan->rDate-2440587.5)*86400.0)
1168 );
1169 fprintf(xCmd, "committer %s <%[email protected]> %s +0000\n",
1170 pMan->zUser, pMan->zUser, buf
1171 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1172 blob_init(&comment, pMan->zComment, -1);
1173 if( blob_size(&comment)==0 ){
1174 blob_append(&comment, "(no comment)", -1);
1175 }
1176 blob_appendf(&comment, "\n\nFossilOrigin-Name: %s", zUuid);
1177
--- src/export.c
+++ src/export.c
@@ -1088,10 +1088,11 @@
1088 Blob sql; /* String of SQL for part of the query */
1089 Blob comment; /* The comment text for the check-in */
1090 int nErr = 0; /* Number of errors */
1091 int bPhantomOk; /* True if phantom files should be ignored */
1092 char buf[24];
1093 char *zEmail; /* Contact info for Git committer field */
1094
1095 pMan = manifest_get(rid, CFTYPE_MANIFEST, 0);
1096 if( pMan==0 ){
1097 /* Must be a phantom. Return without doing anything, and in particular
1098 ** without creating a mark for this check-in. */
@@ -1164,13 +1165,32 @@
1165 fprintf(xCmd, "mark %s\n", zMark);
1166 fossil_free(zMark);
1167 sqlite3_snprintf(sizeof(buf), buf, "%lld",
1168 (sqlite3_int64)((pMan->rDate-2440587.5)*86400.0)
1169 );
1170 /*
1171 ** Check for 'fx_' table from previous Git import, otherwise take contact info
1172 ** from user table for <emailaddr> in committer field. If no emailaddr, check
1173 ** if username is in email form, otherwise use generic '[email protected]'.
1174 */
1175 if (db_table_exists("repository", "fx_git")) {
1176 zEmail = db_text(0, "SELECT email FROM fx_git WHERE user=%Q", pMan->zUser);
1177 } else {
1178 zEmail = db_text(0, "SELECT info FROM user WHERE login=%Q", pMan->zUser);
1179 }
1180 /* Some repo 'info' fields return an empty string hence the second check */
1181 if (zEmail == NULL || zEmail[0] == '\0') {
1182 /* If username is in emailaddr form, don't append '@noemail.net' */
1183 if (strchr(pMan->zUser, '@') == NULL) {
1184 zEmail = mprintf("%[email protected]", pMan->zUser);
1185 } else {
1186 zEmail = fossil_strdup(pMan->zUser);
1187 }
1188 }
1189 fprintf(xCmd, "committer %s <%s> %s +0000\n", pMan->zUser,
1190 strchr(pMan->zUser, '@') == NULL ? zEmail : pMan->zUser, buf);
1191 fossil_free(zEmail);
1192 blob_init(&comment, pMan->zComment, -1);
1193 if( blob_size(&comment)==0 ){
1194 blob_append(&comment, "(no comment)", -1);
1195 }
1196 blob_appendf(&comment, "\n\nFossilOrigin-Name: %s", zUuid);
1197
+52 -1
--- src/import.c
+++ src/import.c
@@ -540,10 +540,15 @@
540540
541541
542542
static struct{
543543
const char *zMasterName; /* Name of master branch */
544544
int authorFlag; /* Use author as checkin committer */
545
+ int nGitAttr; /* Number of Git --attribute entries */
546
+ struct { /* Git --attribute details */
547
+ char *zUser;
548
+ char *zEmail;
549
+ } *gitUserInfo;
545550
} ggit;
546551
547552
/*
548553
** Read the git-fast-import format from pIn and insert the corresponding
549554
** content into the database.
@@ -664,11 +669,15 @@
664669
sqlite3_int64 secSince1970;
665670
z = strchr(zLine, ' ');
666671
while( fossil_isspace(*z) ) z++;
667672
if( (zTo=strchr(z, '>'))==NULL ) goto malformed_line;
668673
*(++zTo) = '\0';
669
- /* Lookup user by contact info. */
674
+ /*
675
+ ** If --attribute requested, lookup user in fx_ table by email address,
676
+ ** otherwise lookup Git {author,committer} contact info in user table. If
677
+ ** no matches, use email address as username for check-in attribution.
678
+ */
670679
fossil_free(gg.zUser);
671680
gg.zUser = db_text(0, "SELECT login FROM user WHERE info=%Q", z);
672681
if( gg.zUser==NULL ){
673682
/* If there is no user with this contact info,
674683
* then use the email address as the username. */
@@ -675,10 +684,14 @@
675684
if ( (z=strchr(z, '<'))==NULL ) goto malformed_line;
676685
z++;
677686
*(zTo-1) = '\0';
678687
gg.zUser = fossil_strdup(z);
679688
}
689
+ if (ggit.nGitAttr > 0) {
690
+ gg.zUser = db_text(gg.zUser,
691
+ "SELECT user FROM fx_git WHERE email=%Q", z);
692
+ }
680693
secSince1970 = 0;
681694
for(zTo++; fossil_isdigit(*zTo); zTo++){
682695
secSince1970 = secSince1970*10 + *zTo - '0';
683696
}
684697
fossil_free(gg.zDate);
@@ -1648,10 +1661,12 @@
16481661
** Options:
16491662
** --import-marks FILE Restore marks table from FILE
16501663
** --export-marks FILE Save marks table to FILE
16511664
** --rename-master NAME Renames the master branch to NAME
16521665
** --use-author Uses author as the committer
1666
+** --attribute "EMAIL USER" Attribute commits to USER
1667
+** instead of Git committer EMAIL address
16531668
**
16541669
** --svn Import from the svnadmin-dump file format. The default
16551670
** behaviour (unless overridden by --flat) is to treat 3
16561671
** folders in the SVN root as special, following the
16571672
** common layout of SVN repositories. These are (by
@@ -1690,10 +1705,15 @@
16901705
**
16911706
** --ignore-tree is useful for importing Subversion repositories which
16921707
** move branches to subdirectories of "branches/deleted" instead of
16931708
** deleting them. It can be supplied multiple times if necessary.
16941709
**
1710
+** The --attribute option takes a quoted string argument comprised of a
1711
+** Git committer email and the username to be attributed to corresponding
1712
+** check-ins in the Fossil repository. This option can be repeated. For
1713
+** example, --attribute "[email protected] drh" --attribute "[email protected] X"
1714
+**
16951715
** See also: export
16961716
*/
16971717
void import_cmd(void){
16981718
char *zPassword;
16991719
FILE *pIn;
@@ -1776,10 +1796,23 @@
17761796
markfile_out = find_option("export-marks", 0, 1);
17771797
if( !(ggit.zMasterName = find_option("rename-master", 0, 1)) ){
17781798
ggit.zMasterName = "master";
17791799
}
17801800
ggit.authorFlag = find_option("use-author", 0, 0)!=0;
1801
+ /*
1802
+ ** Extract --attribute 'emailaddr username' args that will populate
1803
+ ** new 'fx_' table to later match username for check-in attribution.
1804
+ */
1805
+ const char *zGitUser = find_option("attribute", 0, 1);
1806
+ while( zGitUser != 0 ){
1807
+ ggit.gitUserInfo = fossil_realloc(ggit.gitUserInfo, ++ggit.nGitAttr
1808
+ * sizeof(ggit.gitUserInfo[0]));
1809
+ char *currGitUser = fossil_strdup(zGitUser);
1810
+ ggit.gitUserInfo[ggit.nGitAttr-1].zEmail = next_token(&currGitUser);
1811
+ ggit.gitUserInfo[ggit.nGitAttr-1].zUser = rest_of_line(&currGitUser);
1812
+ zGitUser = find_option("attribute", 0, 1);
1813
+ }
17811814
}
17821815
verify_all_options();
17831816
17841817
if( g.argc!=3 && g.argc!=4 ){
17851818
usage("--git|--svn ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE?");
@@ -1901,10 +1934,28 @@
19011934
}
19021935
fclose(f);
19031936
}
19041937
19051938
manifest_crosslink_begin();
1939
+ /*
1940
+ ** The following 'fx_' table is used to hold information needed for
1941
+ ** importing and exporting to attribute Fossil check-ins or Git commits
1942
+ ** to either a desired username or full contact information string.
1943
+ */
1944
+ if(ggit.nGitAttr > 0) {
1945
+ db_unprotect(PROTECT_ALL);
1946
+ db_multi_exec(
1947
+ "CREATE TABLE fx_git(user TEXT, email TEXT UNIQUE);"
1948
+ );
1949
+ for( int idx = 0; idx < ggit.nGitAttr; ++idx ){
1950
+ db_multi_exec(
1951
+ "INSERT OR IGNORE INTO fx_git(user, email) VALUES(%Q, %Q)",
1952
+ ggit.gitUserInfo[idx].zUser, ggit.gitUserInfo[idx].zEmail
1953
+ );
1954
+ }
1955
+ db_protect_pop();
1956
+ }
19061957
git_fast_import(pIn);
19071958
db_prepare(&q, "SELECT tcontent FROM xtag");
19081959
while( db_step(&q)==SQLITE_ROW ){
19091960
Blob record;
19101961
db_ephemeral_blob(&q, 0, &record);
19111962
--- src/import.c
+++ src/import.c
@@ -540,10 +540,15 @@
540
541
542 static struct{
543 const char *zMasterName; /* Name of master branch */
544 int authorFlag; /* Use author as checkin committer */
 
 
 
 
 
545 } ggit;
546
547 /*
548 ** Read the git-fast-import format from pIn and insert the corresponding
549 ** content into the database.
@@ -664,11 +669,15 @@
664 sqlite3_int64 secSince1970;
665 z = strchr(zLine, ' ');
666 while( fossil_isspace(*z) ) z++;
667 if( (zTo=strchr(z, '>'))==NULL ) goto malformed_line;
668 *(++zTo) = '\0';
669 /* Lookup user by contact info. */
 
 
 
 
670 fossil_free(gg.zUser);
671 gg.zUser = db_text(0, "SELECT login FROM user WHERE info=%Q", z);
672 if( gg.zUser==NULL ){
673 /* If there is no user with this contact info,
674 * then use the email address as the username. */
@@ -675,10 +684,14 @@
675 if ( (z=strchr(z, '<'))==NULL ) goto malformed_line;
676 z++;
677 *(zTo-1) = '\0';
678 gg.zUser = fossil_strdup(z);
679 }
 
 
 
 
680 secSince1970 = 0;
681 for(zTo++; fossil_isdigit(*zTo); zTo++){
682 secSince1970 = secSince1970*10 + *zTo - '0';
683 }
684 fossil_free(gg.zDate);
@@ -1648,10 +1661,12 @@
1648 ** Options:
1649 ** --import-marks FILE Restore marks table from FILE
1650 ** --export-marks FILE Save marks table to FILE
1651 ** --rename-master NAME Renames the master branch to NAME
1652 ** --use-author Uses author as the committer
 
 
1653 **
1654 ** --svn Import from the svnadmin-dump file format. The default
1655 ** behaviour (unless overridden by --flat) is to treat 3
1656 ** folders in the SVN root as special, following the
1657 ** common layout of SVN repositories. These are (by
@@ -1690,10 +1705,15 @@
1690 **
1691 ** --ignore-tree is useful for importing Subversion repositories which
1692 ** move branches to subdirectories of "branches/deleted" instead of
1693 ** deleting them. It can be supplied multiple times if necessary.
1694 **
 
 
 
 
 
1695 ** See also: export
1696 */
1697 void import_cmd(void){
1698 char *zPassword;
1699 FILE *pIn;
@@ -1776,10 +1796,23 @@
1776 markfile_out = find_option("export-marks", 0, 1);
1777 if( !(ggit.zMasterName = find_option("rename-master", 0, 1)) ){
1778 ggit.zMasterName = "master";
1779 }
1780 ggit.authorFlag = find_option("use-author", 0, 0)!=0;
 
 
 
 
 
 
 
 
 
 
 
 
 
1781 }
1782 verify_all_options();
1783
1784 if( g.argc!=3 && g.argc!=4 ){
1785 usage("--git|--svn ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE?");
@@ -1901,10 +1934,28 @@
1901 }
1902 fclose(f);
1903 }
1904
1905 manifest_crosslink_begin();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1906 git_fast_import(pIn);
1907 db_prepare(&q, "SELECT tcontent FROM xtag");
1908 while( db_step(&q)==SQLITE_ROW ){
1909 Blob record;
1910 db_ephemeral_blob(&q, 0, &record);
1911
--- src/import.c
+++ src/import.c
@@ -540,10 +540,15 @@
540
541
542 static struct{
543 const char *zMasterName; /* Name of master branch */
544 int authorFlag; /* Use author as checkin committer */
545 int nGitAttr; /* Number of Git --attribute entries */
546 struct { /* Git --attribute details */
547 char *zUser;
548 char *zEmail;
549 } *gitUserInfo;
550 } ggit;
551
552 /*
553 ** Read the git-fast-import format from pIn and insert the corresponding
554 ** content into the database.
@@ -664,11 +669,15 @@
669 sqlite3_int64 secSince1970;
670 z = strchr(zLine, ' ');
671 while( fossil_isspace(*z) ) z++;
672 if( (zTo=strchr(z, '>'))==NULL ) goto malformed_line;
673 *(++zTo) = '\0';
674 /*
675 ** If --attribute requested, lookup user in fx_ table by email address,
676 ** otherwise lookup Git {author,committer} contact info in user table. If
677 ** no matches, use email address as username for check-in attribution.
678 */
679 fossil_free(gg.zUser);
680 gg.zUser = db_text(0, "SELECT login FROM user WHERE info=%Q", z);
681 if( gg.zUser==NULL ){
682 /* If there is no user with this contact info,
683 * then use the email address as the username. */
@@ -675,10 +684,14 @@
684 if ( (z=strchr(z, '<'))==NULL ) goto malformed_line;
685 z++;
686 *(zTo-1) = '\0';
687 gg.zUser = fossil_strdup(z);
688 }
689 if (ggit.nGitAttr > 0) {
690 gg.zUser = db_text(gg.zUser,
691 "SELECT user FROM fx_git WHERE email=%Q", z);
692 }
693 secSince1970 = 0;
694 for(zTo++; fossil_isdigit(*zTo); zTo++){
695 secSince1970 = secSince1970*10 + *zTo - '0';
696 }
697 fossil_free(gg.zDate);
@@ -1648,10 +1661,12 @@
1661 ** Options:
1662 ** --import-marks FILE Restore marks table from FILE
1663 ** --export-marks FILE Save marks table to FILE
1664 ** --rename-master NAME Renames the master branch to NAME
1665 ** --use-author Uses author as the committer
1666 ** --attribute "EMAIL USER" Attribute commits to USER
1667 ** instead of Git committer EMAIL address
1668 **
1669 ** --svn Import from the svnadmin-dump file format. The default
1670 ** behaviour (unless overridden by --flat) is to treat 3
1671 ** folders in the SVN root as special, following the
1672 ** common layout of SVN repositories. These are (by
@@ -1690,10 +1705,15 @@
1705 **
1706 ** --ignore-tree is useful for importing Subversion repositories which
1707 ** move branches to subdirectories of "branches/deleted" instead of
1708 ** deleting them. It can be supplied multiple times if necessary.
1709 **
1710 ** The --attribute option takes a quoted string argument comprised of a
1711 ** Git committer email and the username to be attributed to corresponding
1712 ** check-ins in the Fossil repository. This option can be repeated. For
1713 ** example, --attribute "[email protected] drh" --attribute "[email protected] X"
1714 **
1715 ** See also: export
1716 */
1717 void import_cmd(void){
1718 char *zPassword;
1719 FILE *pIn;
@@ -1776,10 +1796,23 @@
1796 markfile_out = find_option("export-marks", 0, 1);
1797 if( !(ggit.zMasterName = find_option("rename-master", 0, 1)) ){
1798 ggit.zMasterName = "master";
1799 }
1800 ggit.authorFlag = find_option("use-author", 0, 0)!=0;
1801 /*
1802 ** Extract --attribute 'emailaddr username' args that will populate
1803 ** new 'fx_' table to later match username for check-in attribution.
1804 */
1805 const char *zGitUser = find_option("attribute", 0, 1);
1806 while( zGitUser != 0 ){
1807 ggit.gitUserInfo = fossil_realloc(ggit.gitUserInfo, ++ggit.nGitAttr
1808 * sizeof(ggit.gitUserInfo[0]));
1809 char *currGitUser = fossil_strdup(zGitUser);
1810 ggit.gitUserInfo[ggit.nGitAttr-1].zEmail = next_token(&currGitUser);
1811 ggit.gitUserInfo[ggit.nGitAttr-1].zUser = rest_of_line(&currGitUser);
1812 zGitUser = find_option("attribute", 0, 1);
1813 }
1814 }
1815 verify_all_options();
1816
1817 if( g.argc!=3 && g.argc!=4 ){
1818 usage("--git|--svn ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE?");
@@ -1901,10 +1934,28 @@
1934 }
1935 fclose(f);
1936 }
1937
1938 manifest_crosslink_begin();
1939 /*
1940 ** The following 'fx_' table is used to hold information needed for
1941 ** importing and exporting to attribute Fossil check-ins or Git commits
1942 ** to either a desired username or full contact information string.
1943 */
1944 if(ggit.nGitAttr > 0) {
1945 db_unprotect(PROTECT_ALL);
1946 db_multi_exec(
1947 "CREATE TABLE fx_git(user TEXT, email TEXT UNIQUE);"
1948 );
1949 for( int idx = 0; idx < ggit.nGitAttr; ++idx ){
1950 db_multi_exec(
1951 "INSERT OR IGNORE INTO fx_git(user, email) VALUES(%Q, %Q)",
1952 ggit.gitUserInfo[idx].zUser, ggit.gitUserInfo[idx].zEmail
1953 );
1954 }
1955 db_protect_pop();
1956 }
1957 git_fast_import(pIn);
1958 db_prepare(&q, "SELECT tcontent FROM xtag");
1959 while( db_step(&q)==SQLITE_ROW ){
1960 Blob record;
1961 db_ephemeral_blob(&q, 0, &record);
1962

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button