Fossil SCM
Add the new "fossil describe" command.
Commit
36ca26647977e7e96678e11cebc55f31f70713c7004014db07fa9030e83491b0
Parent
b80ae0215eb65fb…
1 file changed
+193
+193
| --- src/info.c | ||
| +++ src/info.c | ||
| @@ -3678,5 +3678,198 @@ | ||
| 3678 | 3678 | db_column_text(&q,2), |
| 3679 | 3679 | db_column_text(&q,3)); |
| 3680 | 3680 | } |
| 3681 | 3681 | db_finalize(&q); |
| 3682 | 3682 | } |
| 3683 | + | |
| 3684 | +#if INTERFACE | |
| 3685 | +/* | |
| 3686 | +** Description of a checkin relative to an earlier, tagged checkin. | |
| 3687 | +*/ | |
| 3688 | +typedef struct CommitDescr { | |
| 3689 | + char *zRelTagname; /* Tag name on the relative checkin */ | |
| 3690 | + int nCommitsSince; /* Number of commits since then */ | |
| 3691 | + char *zCommitHash; /* Hash of the described checkin */ | |
| 3692 | + int isDirty; /* Working directory has uncommitted changes */ | |
| 3693 | +} CommitDescr; | |
| 3694 | +#endif | |
| 3695 | + | |
| 3696 | +/* | |
| 3697 | +** Describe the checkin given by 'zName', and possibly matching 'matchGlob', | |
| 3698 | +** relative to an earlier, tagged checkin. Use 'descr' for the output. | |
| 3699 | +** | |
| 3700 | +** Finds the closest ancestor (ignoring merge-ins) that has a non-propagating | |
| 3701 | +** label tag and the number of steps backwards that we had to search in | |
| 3702 | +** order to find that tag. | |
| 3703 | +** | |
| 3704 | +** Return values: | |
| 3705 | +** 0: ok | |
| 3706 | +** -1: zName does not resolve to a commit | |
| 3707 | +** -2: zName resolves to more than a commit | |
| 3708 | +** -3: no ancestor commit with a fitting non-propagating tag found | |
| 3709 | +*/ | |
| 3710 | +int describe_commit(const char *zName, const char *matchGlob, | |
| 3711 | + CommitDescr *descr){ | |
| 3712 | + int rid; /* rid for zName */ | |
| 3713 | + const char *zUuid; /* Hash of rid */ | |
| 3714 | + int nRet = 0; /* Value to be returned */ | |
| 3715 | + Stmt q; /* Query for tagged ancestors */ | |
| 3716 | + | |
| 3717 | + rid = symbolic_name_to_rid(zName, "ci"); /* only commits */ | |
| 3718 | + | |
| 3719 | + if( rid<=0 ){ | |
| 3720 | + /* Commit does not exist or is ambiguous */ | |
| 3721 | + descr->zRelTagname = mprintf(""); | |
| 3722 | + descr->nCommitsSince = -1; | |
| 3723 | + descr->zCommitHash = mprintf(""); | |
| 3724 | + descr->isDirty = -1; | |
| 3725 | + return (rid-1); | |
| 3726 | + } | |
| 3727 | + | |
| 3728 | + zUuid = rid_to_uuid(rid); | |
| 3729 | + descr->zCommitHash = mprintf("%s", zUuid); | |
| 3730 | + descr->isDirty = unsaved_changes(0); | |
| 3731 | + | |
| 3732 | + db_multi_exec( | |
| 3733 | + "DROP TABLE IF EXISTS singletonTaggedAncestors;" | |
| 3734 | + "CREATE TEMP TABLE singletonTaggedAncestors AS" | |
| 3735 | + " WITH RECURSIVE " | |
| 3736 | + " singletonTaggedCommits(rid,mtime,shorttag) AS (" | |
| 3737 | + " SELECT DISTINCT b.rid,e.mtime,substr(t.tagname,5) AS shorttag" | |
| 3738 | + " FROM blob b" | |
| 3739 | + " INNER JOIN event e ON e.objid=b.rid" | |
| 3740 | + " INNER JOIN tagxref tx ON tx.rid=b.rid" | |
| 3741 | + " INNER JOIN tag t ON t.tagid=tx.tagid" | |
| 3742 | + " WHERE e.type='ci'" | |
| 3743 | + " AND tx.tagtype=1" | |
| 3744 | + " AND t.tagname GLOB 'sym-%q'" | |
| 3745 | + " )," | |
| 3746 | + " parent(pid,cid,isCP,isPrim) AS (" | |
| 3747 | + " SELECT plink.pid, plink.cid, 0, isPrim FROM plink" | |
| 3748 | + " UNION ALL" | |
| 3749 | + " SELECT parentid, childid, 1, 0 FROM cherrypick WHERE NOT isExclude" | |
| 3750 | + " )," | |
| 3751 | + " ancestor(rid, mtime, isCP, isPrim) AS (" | |
| 3752 | + " SELECT objid, mtime, 0, 1 FROM event WHERE objid=%d" | |
| 3753 | + " UNION" | |
| 3754 | + " SELECT parent.pid, event.mtime, parent.isCP, parent.isPrim" | |
| 3755 | + " FROM ancestor, parent, event" | |
| 3756 | + " WHERE parent.cid=ancestor.rid" | |
| 3757 | + " AND event.objid=parent.pid" | |
| 3758 | + " AND NOT ancestor.isCP" | |
| 3759 | + " AND (event.mtime >= " | |
| 3760 | + " (SELECT max(mtime) FROM singletonTaggedCommits" | |
| 3761 | + " WHERE mtime<=(SELECT mtime FROM event WHERE objid=%d)))" | |
| 3762 | + " ORDER BY mtime DESC" | |
| 3763 | + " LIMIT 1000000" | |
| 3764 | + " ) " | |
| 3765 | + "SELECT rid, mtime, isCP, isPrim, ROW_NUMBER() OVER (ORDER BY mtime DESC) rn" | |
| 3766 | + " FROM ancestor", | |
| 3767 | + (matchGlob ? matchGlob : "*"), rid, rid | |
| 3768 | + ); | |
| 3769 | + | |
| 3770 | + db_prepare(&q, | |
| 3771 | + "SELECT ta.rid, ta.mtime, ta.rn, b.uuid, substr(t.tagname, 5)" | |
| 3772 | + " FROM singletonTaggedAncestors ta" | |
| 3773 | + " INNER JOIN blob b ON b.rid=ta.rid" | |
| 3774 | + " INNER JOIN tagxref tx ON tx.rid=ta.rid" | |
| 3775 | + " INNER JOIN tag t ON tx.tagid=t.tagid" | |
| 3776 | + " WHERE tx.tagtype=1 AND t.tagname GLOB 'sym-%q' " | |
| 3777 | + " AND rn=(SELECT MAX(rn) FROM singletonTaggedAncestors)" | |
| 3778 | + " ORDER BY tx.mtime DESC, t.tagname DESC LIMIT 1", | |
| 3779 | + (matchGlob ? matchGlob : "*") | |
| 3780 | + ); | |
| 3781 | + | |
| 3782 | + if( db_step(&q)==SQLITE_ROW ){ | |
| 3783 | + const char *lastTag = db_column_text(&q, 4); | |
| 3784 | + descr->zRelTagname = mprintf("%s", lastTag); | |
| 3785 | + descr->nCommitsSince = db_column_int(&q, 2)-1; | |
| 3786 | + nRet = 0; | |
| 3787 | + }else{ | |
| 3788 | + /* no ancestor commit with a fitting singleton tag found */ | |
| 3789 | + descr->zRelTagname = mprintf(""); | |
| 3790 | + descr->nCommitsSince = -1; | |
| 3791 | + nRet = -3; | |
| 3792 | + } | |
| 3793 | + | |
| 3794 | + db_finalize(&q); | |
| 3795 | + return nRet; | |
| 3796 | +} | |
| 3797 | + | |
| 3798 | +/* | |
| 3799 | +** COMMAND: describe | |
| 3800 | +** | |
| 3801 | +** Usage: %fossil describe ?VERSION? ?OPTIONS? | |
| 3802 | +** | |
| 3803 | +** Provide a description of the given VERSION by showing a non-propagating | |
| 3804 | +** tag of the youngest tagged ancestor, followed by the number of commits | |
| 3805 | +** since that, and the short hash of VERSION. If VERSION and the found | |
| 3806 | +** ancestor refer to the same commit, the last two components are omitted, | |
| 3807 | +** unless --long is provided. | |
| 3808 | +** | |
| 3809 | +** If no VERSION is provided, describe the current checked-out version. When | |
| 3810 | +** no fitting tagged ancestor is found, show only the short hash of VERSION. | |
| 3811 | +** | |
| 3812 | +** Options: | |
| 3813 | +** | |
| 3814 | +** --digits Display so many hex digits of the hash (default 10) | |
| 3815 | +** -d|--dirty Show whether there are changes to be committed | |
| 3816 | +** --long Always show all three components | |
| 3817 | +** --match GLOB Consider only non-propagating tags matching GLOB | |
| 3818 | +*/ | |
| 3819 | +void describe_cmd(void){ | |
| 3820 | + const char *zName; | |
| 3821 | + const char *zMatchGlob; | |
| 3822 | + const char *zDigits; | |
| 3823 | + int nDigits; | |
| 3824 | + int bDirtyFlag = 0; | |
| 3825 | + int bLongFlag = 0; | |
| 3826 | + CommitDescr descr; | |
| 3827 | + | |
| 3828 | + db_find_and_open_repository(0,0); | |
| 3829 | + bDirtyFlag = find_option("dirty","d",0)!=0; | |
| 3830 | + bLongFlag = find_option("long","",0)!=0; | |
| 3831 | + zMatchGlob = find_option("match", 0, 1); | |
| 3832 | + zDigits = find_option("digits", 0, 1); | |
| 3833 | + | |
| 3834 | + if ( !zDigits || ((nDigits=atoi(zDigits))==0) ){ | |
| 3835 | + nDigits = 10; | |
| 3836 | + } | |
| 3837 | + | |
| 3838 | + /* We should be done with options.. */ | |
| 3839 | + verify_all_options(); | |
| 3840 | + if( g.argc<3 ){ | |
| 3841 | + zName = "current"; | |
| 3842 | + }else{ | |
| 3843 | + zName = g.argv[2]; | |
| 3844 | + } | |
| 3845 | + | |
| 3846 | + if( bDirtyFlag ){ | |
| 3847 | + if ( g.argc>=3 ) fossil_fatal("cannot use --dirty with specific checkin"); | |
| 3848 | + } | |
| 3849 | + | |
| 3850 | + switch( describe_commit(zName, zMatchGlob, &descr) ){ | |
| 3851 | + case -1: | |
| 3852 | + fossil_fatal("commit %s does not exist", zName); | |
| 3853 | + break; | |
| 3854 | + case -2: | |
| 3855 | + fossil_fatal("commit %s is ambiguous", zName); | |
| 3856 | + break; | |
| 3857 | + case -3: | |
| 3858 | + fossil_print("%.*s%s\n", nDigits, descr.zCommitHash, | |
| 3859 | + bDirtyFlag ? (descr.isDirty ? "-dirty" : "") : ""); | |
| 3860 | + break; | |
| 3861 | + case 0: | |
| 3862 | + if( descr.nCommitsSince==0 && !bLongFlag ){ | |
| 3863 | + fossil_print("%s%s\n", descr.zRelTagname, | |
| 3864 | + bDirtyFlag ? (descr.isDirty ? "-dirty" : "") : ""); | |
| 3865 | + }else{ | |
| 3866 | + fossil_print("%s-%d-%.*s%s\n", descr.zRelTagname, | |
| 3867 | + descr.nCommitsSince, nDigits, descr.zCommitHash, | |
| 3868 | + bDirtyFlag ? (descr.isDirty ? "-dirty" : "") : ""); | |
| 3869 | + } | |
| 3870 | + break; | |
| 3871 | + default: | |
| 3872 | + fossil_fatal("cannot describe commit"); | |
| 3873 | + break; | |
| 3874 | + } | |
| 3875 | +} | |
| 3683 | 3876 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -3678,5 +3678,198 @@ | |
| 3678 | db_column_text(&q,2), |
| 3679 | db_column_text(&q,3)); |
| 3680 | } |
| 3681 | db_finalize(&q); |
| 3682 | } |
| 3683 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -3678,5 +3678,198 @@ | |
| 3678 | db_column_text(&q,2), |
| 3679 | db_column_text(&q,3)); |
| 3680 | } |
| 3681 | db_finalize(&q); |
| 3682 | } |
| 3683 | |
| 3684 | #if INTERFACE |
| 3685 | /* |
| 3686 | ** Description of a checkin relative to an earlier, tagged checkin. |
| 3687 | */ |
| 3688 | typedef struct CommitDescr { |
| 3689 | char *zRelTagname; /* Tag name on the relative checkin */ |
| 3690 | int nCommitsSince; /* Number of commits since then */ |
| 3691 | char *zCommitHash; /* Hash of the described checkin */ |
| 3692 | int isDirty; /* Working directory has uncommitted changes */ |
| 3693 | } CommitDescr; |
| 3694 | #endif |
| 3695 | |
| 3696 | /* |
| 3697 | ** Describe the checkin given by 'zName', and possibly matching 'matchGlob', |
| 3698 | ** relative to an earlier, tagged checkin. Use 'descr' for the output. |
| 3699 | ** |
| 3700 | ** Finds the closest ancestor (ignoring merge-ins) that has a non-propagating |
| 3701 | ** label tag and the number of steps backwards that we had to search in |
| 3702 | ** order to find that tag. |
| 3703 | ** |
| 3704 | ** Return values: |
| 3705 | ** 0: ok |
| 3706 | ** -1: zName does not resolve to a commit |
| 3707 | ** -2: zName resolves to more than a commit |
| 3708 | ** -3: no ancestor commit with a fitting non-propagating tag found |
| 3709 | */ |
| 3710 | int describe_commit(const char *zName, const char *matchGlob, |
| 3711 | CommitDescr *descr){ |
| 3712 | int rid; /* rid for zName */ |
| 3713 | const char *zUuid; /* Hash of rid */ |
| 3714 | int nRet = 0; /* Value to be returned */ |
| 3715 | Stmt q; /* Query for tagged ancestors */ |
| 3716 | |
| 3717 | rid = symbolic_name_to_rid(zName, "ci"); /* only commits */ |
| 3718 | |
| 3719 | if( rid<=0 ){ |
| 3720 | /* Commit does not exist or is ambiguous */ |
| 3721 | descr->zRelTagname = mprintf(""); |
| 3722 | descr->nCommitsSince = -1; |
| 3723 | descr->zCommitHash = mprintf(""); |
| 3724 | descr->isDirty = -1; |
| 3725 | return (rid-1); |
| 3726 | } |
| 3727 | |
| 3728 | zUuid = rid_to_uuid(rid); |
| 3729 | descr->zCommitHash = mprintf("%s", zUuid); |
| 3730 | descr->isDirty = unsaved_changes(0); |
| 3731 | |
| 3732 | db_multi_exec( |
| 3733 | "DROP TABLE IF EXISTS singletonTaggedAncestors;" |
| 3734 | "CREATE TEMP TABLE singletonTaggedAncestors AS" |
| 3735 | " WITH RECURSIVE " |
| 3736 | " singletonTaggedCommits(rid,mtime,shorttag) AS (" |
| 3737 | " SELECT DISTINCT b.rid,e.mtime,substr(t.tagname,5) AS shorttag" |
| 3738 | " FROM blob b" |
| 3739 | " INNER JOIN event e ON e.objid=b.rid" |
| 3740 | " INNER JOIN tagxref tx ON tx.rid=b.rid" |
| 3741 | " INNER JOIN tag t ON t.tagid=tx.tagid" |
| 3742 | " WHERE e.type='ci'" |
| 3743 | " AND tx.tagtype=1" |
| 3744 | " AND t.tagname GLOB 'sym-%q'" |
| 3745 | " )," |
| 3746 | " parent(pid,cid,isCP,isPrim) AS (" |
| 3747 | " SELECT plink.pid, plink.cid, 0, isPrim FROM plink" |
| 3748 | " UNION ALL" |
| 3749 | " SELECT parentid, childid, 1, 0 FROM cherrypick WHERE NOT isExclude" |
| 3750 | " )," |
| 3751 | " ancestor(rid, mtime, isCP, isPrim) AS (" |
| 3752 | " SELECT objid, mtime, 0, 1 FROM event WHERE objid=%d" |
| 3753 | " UNION" |
| 3754 | " SELECT parent.pid, event.mtime, parent.isCP, parent.isPrim" |
| 3755 | " FROM ancestor, parent, event" |
| 3756 | " WHERE parent.cid=ancestor.rid" |
| 3757 | " AND event.objid=parent.pid" |
| 3758 | " AND NOT ancestor.isCP" |
| 3759 | " AND (event.mtime >= " |
| 3760 | " (SELECT max(mtime) FROM singletonTaggedCommits" |
| 3761 | " WHERE mtime<=(SELECT mtime FROM event WHERE objid=%d)))" |
| 3762 | " ORDER BY mtime DESC" |
| 3763 | " LIMIT 1000000" |
| 3764 | " ) " |
| 3765 | "SELECT rid, mtime, isCP, isPrim, ROW_NUMBER() OVER (ORDER BY mtime DESC) rn" |
| 3766 | " FROM ancestor", |
| 3767 | (matchGlob ? matchGlob : "*"), rid, rid |
| 3768 | ); |
| 3769 | |
| 3770 | db_prepare(&q, |
| 3771 | "SELECT ta.rid, ta.mtime, ta.rn, b.uuid, substr(t.tagname, 5)" |
| 3772 | " FROM singletonTaggedAncestors ta" |
| 3773 | " INNER JOIN blob b ON b.rid=ta.rid" |
| 3774 | " INNER JOIN tagxref tx ON tx.rid=ta.rid" |
| 3775 | " INNER JOIN tag t ON tx.tagid=t.tagid" |
| 3776 | " WHERE tx.tagtype=1 AND t.tagname GLOB 'sym-%q' " |
| 3777 | " AND rn=(SELECT MAX(rn) FROM singletonTaggedAncestors)" |
| 3778 | " ORDER BY tx.mtime DESC, t.tagname DESC LIMIT 1", |
| 3779 | (matchGlob ? matchGlob : "*") |
| 3780 | ); |
| 3781 | |
| 3782 | if( db_step(&q)==SQLITE_ROW ){ |
| 3783 | const char *lastTag = db_column_text(&q, 4); |
| 3784 | descr->zRelTagname = mprintf("%s", lastTag); |
| 3785 | descr->nCommitsSince = db_column_int(&q, 2)-1; |
| 3786 | nRet = 0; |
| 3787 | }else{ |
| 3788 | /* no ancestor commit with a fitting singleton tag found */ |
| 3789 | descr->zRelTagname = mprintf(""); |
| 3790 | descr->nCommitsSince = -1; |
| 3791 | nRet = -3; |
| 3792 | } |
| 3793 | |
| 3794 | db_finalize(&q); |
| 3795 | return nRet; |
| 3796 | } |
| 3797 | |
| 3798 | /* |
| 3799 | ** COMMAND: describe |
| 3800 | ** |
| 3801 | ** Usage: %fossil describe ?VERSION? ?OPTIONS? |
| 3802 | ** |
| 3803 | ** Provide a description of the given VERSION by showing a non-propagating |
| 3804 | ** tag of the youngest tagged ancestor, followed by the number of commits |
| 3805 | ** since that, and the short hash of VERSION. If VERSION and the found |
| 3806 | ** ancestor refer to the same commit, the last two components are omitted, |
| 3807 | ** unless --long is provided. |
| 3808 | ** |
| 3809 | ** If no VERSION is provided, describe the current checked-out version. When |
| 3810 | ** no fitting tagged ancestor is found, show only the short hash of VERSION. |
| 3811 | ** |
| 3812 | ** Options: |
| 3813 | ** |
| 3814 | ** --digits Display so many hex digits of the hash (default 10) |
| 3815 | ** -d|--dirty Show whether there are changes to be committed |
| 3816 | ** --long Always show all three components |
| 3817 | ** --match GLOB Consider only non-propagating tags matching GLOB |
| 3818 | */ |
| 3819 | void describe_cmd(void){ |
| 3820 | const char *zName; |
| 3821 | const char *zMatchGlob; |
| 3822 | const char *zDigits; |
| 3823 | int nDigits; |
| 3824 | int bDirtyFlag = 0; |
| 3825 | int bLongFlag = 0; |
| 3826 | CommitDescr descr; |
| 3827 | |
| 3828 | db_find_and_open_repository(0,0); |
| 3829 | bDirtyFlag = find_option("dirty","d",0)!=0; |
| 3830 | bLongFlag = find_option("long","",0)!=0; |
| 3831 | zMatchGlob = find_option("match", 0, 1); |
| 3832 | zDigits = find_option("digits", 0, 1); |
| 3833 | |
| 3834 | if ( !zDigits || ((nDigits=atoi(zDigits))==0) ){ |
| 3835 | nDigits = 10; |
| 3836 | } |
| 3837 | |
| 3838 | /* We should be done with options.. */ |
| 3839 | verify_all_options(); |
| 3840 | if( g.argc<3 ){ |
| 3841 | zName = "current"; |
| 3842 | }else{ |
| 3843 | zName = g.argv[2]; |
| 3844 | } |
| 3845 | |
| 3846 | if( bDirtyFlag ){ |
| 3847 | if ( g.argc>=3 ) fossil_fatal("cannot use --dirty with specific checkin"); |
| 3848 | } |
| 3849 | |
| 3850 | switch( describe_commit(zName, zMatchGlob, &descr) ){ |
| 3851 | case -1: |
| 3852 | fossil_fatal("commit %s does not exist", zName); |
| 3853 | break; |
| 3854 | case -2: |
| 3855 | fossil_fatal("commit %s is ambiguous", zName); |
| 3856 | break; |
| 3857 | case -3: |
| 3858 | fossil_print("%.*s%s\n", nDigits, descr.zCommitHash, |
| 3859 | bDirtyFlag ? (descr.isDirty ? "-dirty" : "") : ""); |
| 3860 | break; |
| 3861 | case 0: |
| 3862 | if( descr.nCommitsSince==0 && !bLongFlag ){ |
| 3863 | fossil_print("%s%s\n", descr.zRelTagname, |
| 3864 | bDirtyFlag ? (descr.isDirty ? "-dirty" : "") : ""); |
| 3865 | }else{ |
| 3866 | fossil_print("%s-%d-%.*s%s\n", descr.zRelTagname, |
| 3867 | descr.nCommitsSince, nDigits, descr.zCommitHash, |
| 3868 | bDirtyFlag ? (descr.isDirty ? "-dirty" : "") : ""); |
| 3869 | } |
| 3870 | break; |
| 3871 | default: |
| 3872 | fossil_fatal("cannot describe commit"); |
| 3873 | break; |
| 3874 | } |
| 3875 | } |
| 3876 |