Fossil SCM

Add the new "fossil describe" command.

drh 2022-03-30 14:45 trunk merge
Commit 36ca26647977e7e96678e11cebc55f31f70713c7004014db07fa9030e83491b0
1 file changed +193
+193
--- src/info.c
+++ src/info.c
@@ -3678,5 +3678,198 @@
36783678
db_column_text(&q,2),
36793679
db_column_text(&q,3));
36803680
}
36813681
db_finalize(&q);
36823682
}
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
+}
36833876
--- 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

Keyboard Shortcuts

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