Fossil SCM

Add options -t/-T to 'blame'/'praise' to display for each tracked file the last modifying check-in, its author, and (with -T) a comment snippet.

danield 2026-01-23 14:38 trunk
Commit 644134097e74c2c711bd4f7645c909bac14a4d073b360aea0600358e2b787724
2 files changed +30 -7 +21 -1
+30 -7
--- src/checkin.c
+++ src/checkin.c
@@ -714,16 +714,17 @@
714714
}
715715
716716
/*
717717
** Take care of -r version of ls command
718718
*/
719
-static void ls_cmd_rev(
719
+void ls_cmd_rev(
720720
const char *zRev, /* Revision string given */
721721
int verboseFlag, /* Verbose flag given */
722722
int showAge, /* Age flag given */
723723
int showFileHash, /* Show file hash flag given */
724724
int showCkinHash, /* Show check-in hash flag given */
725
+ int showCkinInfo, /* Show check-in infos */
725726
int timeOrder, /* Order by time flag given */
726727
int treeFmt /* Show output in the tree format */
727728
){
728729
Stmt q;
729730
char *zOrderBy = "pathname COLLATE nocase";
@@ -763,22 +764,36 @@
763764
if( timeOrder ){
764765
zOrderBy = "mtime DESC";
765766
}
766767
767768
compute_fileage(rid,0);
768
- db_prepare(&q,
769
+ if( showCkinInfo ){
770
+ db_prepare(&q,
771
+ "SELECT datetime(fileage.mtime, toLocal()), fileage.pathname,\n"
772
+ " bfh.size, fileage.uuid, bch.uuid,\n"
773
+ " coalesce(e.ecomment, e.comment), coalesce(e.euser, e.user)\n"
774
+ " FROM fileage, blob bfh, blob bch, event e\n"
775
+ " WHERE bfh.rid=fileage.fid AND bch.rid=fileage.mid\n"
776
+ " AND e.objid = fileage.mid %s\n"
777
+ " ORDER BY %s;",
778
+ blob_sql_text(&where),
779
+ zOrderBy /*safe-for-%s*/
780
+ );
781
+ }else{
782
+ db_prepare(&q,
769783
"SELECT datetime(fileage.mtime, toLocal()), fileage.pathname,\n"
770784
" bfh.size, fileage.uuid %s\n"
771785
" FROM fileage, blob bfh %s\n"
772786
" WHERE bfh.rid=fileage.fid %s %s\n"
773
- " ORDER BY %s;",
787
+ " ORDER BY %s;",
774788
showCkinHash ? ", bch.uuid" : "",
775789
showCkinHash ? ", blob bch" : "",
776790
showCkinHash ? "\n AND bch.rid=fileage.mid" : "",
777791
blob_sql_text(&where),
778792
zOrderBy /*safe-for-%s*/
779
- );
793
+ );
794
+ }
780795
blob_reset(&where);
781796
if( treeFmt ) blob_init(&out, 0, 0);
782797
783798
while( db_step(&q)==SQLITE_ROW ){
784799
const char *zTime = db_column_text(&q,0);
@@ -785,11 +800,18 @@
785800
const char *zFile = db_column_text(&q,1);
786801
int size = db_column_int(&q,2);
787802
if( treeFmt ){
788803
blob_appendf(&out, "%s\n", zFile);
789804
}else if( verboseFlag ){
790
- if( showFileHash ){
805
+ if( showCkinInfo ){
806
+ const char *zUuidC = db_column_text(&q,4);
807
+ const char *zComm = db_column_text(&q,5);
808
+ const char *zUser = db_column_text(&q,6);
809
+ fossil_print("%s [%S] %12s ", zTime, zUuidC, zUser);
810
+ if( showCkinInfo==2 ) fossil_print("%-20.20s ", zComm);
811
+ fossil_print("%s\n", zFile);
812
+ }else if( showFileHash ){
791813
const char *zUuidF = db_column_text(&q,3);
792814
fossil_print("%s %7d [%S] %s\n", zTime, size, zUuidF, zFile);
793815
}else if( showCkinHash ){
794816
const char *zUuidC = db_column_text(&q,4);
795817
fossil_print("%s %7d [%S] %s\n", zTime, size, zUuidC, zFile);
@@ -887,11 +909,12 @@
887909
}
888910
889911
if( zRev!=0 ){
890912
db_find_and_open_repository(0, 0);
891913
verify_all_options();
892
- ls_cmd_rev(zRev,verboseFlag,showAge,showFHash,showCHash,timeOrder,treeFmt);
914
+ ls_cmd_rev(zRev, verboseFlag, showAge, showFHash, showCHash, 0, timeOrder,
915
+ treeFmt);
893916
return;
894917
}else if( find_option("R",0,1)!=0 ){
895918
fossil_fatal("the -r is required in addition to -R");
896919
}
897920
@@ -1010,11 +1033,11 @@
10101033
10111034
zRev = find_option("r","r",1);
10121035
if( zRev==0 ) zRev = "current";
10131036
db_find_and_open_repository(0, 0);
10141037
verify_all_options();
1015
- ls_cmd_rev(zRev,0,0,0,0,0,1);
1038
+ ls_cmd_rev(zRev,0,0,0,0,0,0,1);
10161039
}
10171040
10181041
/*
10191042
** COMMAND: extras
10201043
**
10211044
--- src/checkin.c
+++ src/checkin.c
@@ -714,16 +714,17 @@
714 }
715
716 /*
717 ** Take care of -r version of ls command
718 */
719 static void ls_cmd_rev(
720 const char *zRev, /* Revision string given */
721 int verboseFlag, /* Verbose flag given */
722 int showAge, /* Age flag given */
723 int showFileHash, /* Show file hash flag given */
724 int showCkinHash, /* Show check-in hash flag given */
 
725 int timeOrder, /* Order by time flag given */
726 int treeFmt /* Show output in the tree format */
727 ){
728 Stmt q;
729 char *zOrderBy = "pathname COLLATE nocase";
@@ -763,22 +764,36 @@
763 if( timeOrder ){
764 zOrderBy = "mtime DESC";
765 }
766
767 compute_fileage(rid,0);
768 db_prepare(&q,
 
 
 
 
 
 
 
 
 
 
 
 
 
769 "SELECT datetime(fileage.mtime, toLocal()), fileage.pathname,\n"
770 " bfh.size, fileage.uuid %s\n"
771 " FROM fileage, blob bfh %s\n"
772 " WHERE bfh.rid=fileage.fid %s %s\n"
773 " ORDER BY %s;",
774 showCkinHash ? ", bch.uuid" : "",
775 showCkinHash ? ", blob bch" : "",
776 showCkinHash ? "\n AND bch.rid=fileage.mid" : "",
777 blob_sql_text(&where),
778 zOrderBy /*safe-for-%s*/
779 );
 
780 blob_reset(&where);
781 if( treeFmt ) blob_init(&out, 0, 0);
782
783 while( db_step(&q)==SQLITE_ROW ){
784 const char *zTime = db_column_text(&q,0);
@@ -785,11 +800,18 @@
785 const char *zFile = db_column_text(&q,1);
786 int size = db_column_int(&q,2);
787 if( treeFmt ){
788 blob_appendf(&out, "%s\n", zFile);
789 }else if( verboseFlag ){
790 if( showFileHash ){
 
 
 
 
 
 
 
791 const char *zUuidF = db_column_text(&q,3);
792 fossil_print("%s %7d [%S] %s\n", zTime, size, zUuidF, zFile);
793 }else if( showCkinHash ){
794 const char *zUuidC = db_column_text(&q,4);
795 fossil_print("%s %7d [%S] %s\n", zTime, size, zUuidC, zFile);
@@ -887,11 +909,12 @@
887 }
888
889 if( zRev!=0 ){
890 db_find_and_open_repository(0, 0);
891 verify_all_options();
892 ls_cmd_rev(zRev,verboseFlag,showAge,showFHash,showCHash,timeOrder,treeFmt);
 
893 return;
894 }else if( find_option("R",0,1)!=0 ){
895 fossil_fatal("the -r is required in addition to -R");
896 }
897
@@ -1010,11 +1033,11 @@
1010
1011 zRev = find_option("r","r",1);
1012 if( zRev==0 ) zRev = "current";
1013 db_find_and_open_repository(0, 0);
1014 verify_all_options();
1015 ls_cmd_rev(zRev,0,0,0,0,0,1);
1016 }
1017
1018 /*
1019 ** COMMAND: extras
1020 **
1021
--- src/checkin.c
+++ src/checkin.c
@@ -714,16 +714,17 @@
714 }
715
716 /*
717 ** Take care of -r version of ls command
718 */
719 void ls_cmd_rev(
720 const char *zRev, /* Revision string given */
721 int verboseFlag, /* Verbose flag given */
722 int showAge, /* Age flag given */
723 int showFileHash, /* Show file hash flag given */
724 int showCkinHash, /* Show check-in hash flag given */
725 int showCkinInfo, /* Show check-in infos */
726 int timeOrder, /* Order by time flag given */
727 int treeFmt /* Show output in the tree format */
728 ){
729 Stmt q;
730 char *zOrderBy = "pathname COLLATE nocase";
@@ -763,22 +764,36 @@
764 if( timeOrder ){
765 zOrderBy = "mtime DESC";
766 }
767
768 compute_fileage(rid,0);
769 if( showCkinInfo ){
770 db_prepare(&q,
771 "SELECT datetime(fileage.mtime, toLocal()), fileage.pathname,\n"
772 " bfh.size, fileage.uuid, bch.uuid,\n"
773 " coalesce(e.ecomment, e.comment), coalesce(e.euser, e.user)\n"
774 " FROM fileage, blob bfh, blob bch, event e\n"
775 " WHERE bfh.rid=fileage.fid AND bch.rid=fileage.mid\n"
776 " AND e.objid = fileage.mid %s\n"
777 " ORDER BY %s;",
778 blob_sql_text(&where),
779 zOrderBy /*safe-for-%s*/
780 );
781 }else{
782 db_prepare(&q,
783 "SELECT datetime(fileage.mtime, toLocal()), fileage.pathname,\n"
784 " bfh.size, fileage.uuid %s\n"
785 " FROM fileage, blob bfh %s\n"
786 " WHERE bfh.rid=fileage.fid %s %s\n"
787 " ORDER BY %s;",
788 showCkinHash ? ", bch.uuid" : "",
789 showCkinHash ? ", blob bch" : "",
790 showCkinHash ? "\n AND bch.rid=fileage.mid" : "",
791 blob_sql_text(&where),
792 zOrderBy /*safe-for-%s*/
793 );
794 }
795 blob_reset(&where);
796 if( treeFmt ) blob_init(&out, 0, 0);
797
798 while( db_step(&q)==SQLITE_ROW ){
799 const char *zTime = db_column_text(&q,0);
@@ -785,11 +800,18 @@
800 const char *zFile = db_column_text(&q,1);
801 int size = db_column_int(&q,2);
802 if( treeFmt ){
803 blob_appendf(&out, "%s\n", zFile);
804 }else if( verboseFlag ){
805 if( showCkinInfo ){
806 const char *zUuidC = db_column_text(&q,4);
807 const char *zComm = db_column_text(&q,5);
808 const char *zUser = db_column_text(&q,6);
809 fossil_print("%s [%S] %12s ", zTime, zUuidC, zUser);
810 if( showCkinInfo==2 ) fossil_print("%-20.20s ", zComm);
811 fossil_print("%s\n", zFile);
812 }else if( showFileHash ){
813 const char *zUuidF = db_column_text(&q,3);
814 fossil_print("%s %7d [%S] %s\n", zTime, size, zUuidF, zFile);
815 }else if( showCkinHash ){
816 const char *zUuidC = db_column_text(&q,4);
817 fossil_print("%s %7d [%S] %s\n", zTime, size, zUuidC, zFile);
@@ -887,11 +909,12 @@
909 }
910
911 if( zRev!=0 ){
912 db_find_and_open_repository(0, 0);
913 verify_all_options();
914 ls_cmd_rev(zRev, verboseFlag, showAge, showFHash, showCHash, 0, timeOrder,
915 treeFmt);
916 return;
917 }else if( find_option("R",0,1)!=0 ){
918 fossil_fatal("the -r is required in addition to -R");
919 }
920
@@ -1010,11 +1033,11 @@
1033
1034 zRev = find_option("r","r",1);
1035 if( zRev==0 ) zRev = "current";
1036 db_find_and_open_repository(0, 0);
1037 verify_all_options();
1038 ls_cmd_rev(zRev,0,0,0,0,0,0,1);
1039 }
1040
1041 /*
1042 ** COMMAND: extras
1043 **
1044
+21 -1
--- src/diff.c
+++ src/diff.c
@@ -3622,11 +3622,11 @@
36223622
iLimit = 0;
36233623
mxTime = current_time_in_milliseconds()+1000;
36243624
}
36253625
db_begin_transaction();
36263626
3627
- /* Get the artifact ID for the check-in begin analyzed */
3627
+ /* Get the artifact ID for the check-in being analyzed */
36283628
if( zRevision ){
36293629
cid = name_to_typed_rid(zRevision, "ci");
36303630
}else{
36313631
db_must_be_within_tree();
36323632
cid = db_lget_int("checkout", 0);
@@ -3939,10 +3939,15 @@
39393939
** (example: "-o trunk") then these commands show changes moving towards
39403940
** that alternative origin. Thus using "-o trunk" on an historical version
39413941
** of the file shows the first time each line in the file was changed or
39423942
** removed by any subsequent check-in.
39433943
**
3944
+** With -t or -T, the "blame" and "praise" commands show for each file the
3945
+** latest (relative to the revision given by -r) check-in that modified it and
3946
+** the check-in's author. If not given, the revision defaults to "current" for
3947
+** a check-out. Option -T additionally shows a comment snippet for the check-in.
3948
+**
39443949
** Options:
39453950
** --filevers Show file version numbers rather than
39463951
** check-in versions
39473952
** -r|--revision VERSION The specific check-in containing the file
39483953
** -l|--log List all versions analyzed
@@ -3954,10 +3959,13 @@
39543959
** root of the repository. Set to the name of
39553960
** the main branch (usually "trunk") or
39563961
** similar for a reverse annotation.
39573962
** -w|--ignore-all-space Ignore white space when comparing lines
39583963
** -Z|--ignore-trailing-space Ignore whitespace at line end
3964
+** -t Show latest check-in and its author for each
3965
+** tracked file in the tree as of VERSION
3966
+** -T Like -t, plus comment snippet
39593967
**
39603968
** See also: [[info]], [[finfo]], [[timeline]]
39613969
*/
39623970
void annotate_cmd(void){
39633971
const char *zRevision; /* Revision name, or NULL for current check-in */
@@ -3967,16 +3975,28 @@
39673975
const char *zOrig; /* The value for -o|--origin */
39683976
int showLog; /* True to show the log */
39693977
int fileVers; /* Show file version instead of check-in versions */
39703978
u64 annFlags = 0; /* Flags to control annotation properties */
39713979
int bBlame = 0; /* True for BLAME output. False for ANNOTATE. */
3980
+ int bTreeInfo = 0; /* Show for the entire tree: 1=checkin, 2=with comment */
39723981
int szHash; /* Display size of a version hash */
39733982
Blob treename; /* Name of file to be annotated */
39743983
char *zFilename; /* Name of file to be annotated */
39753984
39763985
bBlame = g.argv[1][0]!='a';
3986
+ if( find_option("t","t",0)!=0 ) bTreeInfo = 1;
3987
+ if( find_option("T","T",0)!=0 ) bTreeInfo = 2;
39773988
zRevision = find_option("revision","r",1);
3989
+ if( bBlame && bTreeInfo ){
3990
+ if( find_repository_option()!=0 && zRevision==0 ){
3991
+ fossil_fatal("the -r is required in addition to -R");
3992
+ }
3993
+ db_find_and_open_repository(0, 0);
3994
+ if( zRevision==0 ) zRevision = "current";
3995
+ ls_cmd_rev(zRevision,1,1,0,1,bTreeInfo,0,0);
3996
+ return;
3997
+ }
39783998
zLimit = find_option("limit","n",1);
39793999
zOrig = find_option("origin","o",1);
39804000
showLog = find_option("log","l",0)!=0;
39814001
if( find_option("ignore-trailing-space","Z",0)!=0 ){
39824002
annFlags = DIFF_IGNORE_EOLWS;
39834003
--- src/diff.c
+++ src/diff.c
@@ -3622,11 +3622,11 @@
3622 iLimit = 0;
3623 mxTime = current_time_in_milliseconds()+1000;
3624 }
3625 db_begin_transaction();
3626
3627 /* Get the artifact ID for the check-in begin analyzed */
3628 if( zRevision ){
3629 cid = name_to_typed_rid(zRevision, "ci");
3630 }else{
3631 db_must_be_within_tree();
3632 cid = db_lget_int("checkout", 0);
@@ -3939,10 +3939,15 @@
3939 ** (example: "-o trunk") then these commands show changes moving towards
3940 ** that alternative origin. Thus using "-o trunk" on an historical version
3941 ** of the file shows the first time each line in the file was changed or
3942 ** removed by any subsequent check-in.
3943 **
 
 
 
 
 
3944 ** Options:
3945 ** --filevers Show file version numbers rather than
3946 ** check-in versions
3947 ** -r|--revision VERSION The specific check-in containing the file
3948 ** -l|--log List all versions analyzed
@@ -3954,10 +3959,13 @@
3954 ** root of the repository. Set to the name of
3955 ** the main branch (usually "trunk") or
3956 ** similar for a reverse annotation.
3957 ** -w|--ignore-all-space Ignore white space when comparing lines
3958 ** -Z|--ignore-trailing-space Ignore whitespace at line end
 
 
 
3959 **
3960 ** See also: [[info]], [[finfo]], [[timeline]]
3961 */
3962 void annotate_cmd(void){
3963 const char *zRevision; /* Revision name, or NULL for current check-in */
@@ -3967,16 +3975,28 @@
3967 const char *zOrig; /* The value for -o|--origin */
3968 int showLog; /* True to show the log */
3969 int fileVers; /* Show file version instead of check-in versions */
3970 u64 annFlags = 0; /* Flags to control annotation properties */
3971 int bBlame = 0; /* True for BLAME output. False for ANNOTATE. */
 
3972 int szHash; /* Display size of a version hash */
3973 Blob treename; /* Name of file to be annotated */
3974 char *zFilename; /* Name of file to be annotated */
3975
3976 bBlame = g.argv[1][0]!='a';
 
 
3977 zRevision = find_option("revision","r",1);
 
 
 
 
 
 
 
 
 
3978 zLimit = find_option("limit","n",1);
3979 zOrig = find_option("origin","o",1);
3980 showLog = find_option("log","l",0)!=0;
3981 if( find_option("ignore-trailing-space","Z",0)!=0 ){
3982 annFlags = DIFF_IGNORE_EOLWS;
3983
--- src/diff.c
+++ src/diff.c
@@ -3622,11 +3622,11 @@
3622 iLimit = 0;
3623 mxTime = current_time_in_milliseconds()+1000;
3624 }
3625 db_begin_transaction();
3626
3627 /* Get the artifact ID for the check-in being analyzed */
3628 if( zRevision ){
3629 cid = name_to_typed_rid(zRevision, "ci");
3630 }else{
3631 db_must_be_within_tree();
3632 cid = db_lget_int("checkout", 0);
@@ -3939,10 +3939,15 @@
3939 ** (example: "-o trunk") then these commands show changes moving towards
3940 ** that alternative origin. Thus using "-o trunk" on an historical version
3941 ** of the file shows the first time each line in the file was changed or
3942 ** removed by any subsequent check-in.
3943 **
3944 ** With -t or -T, the "blame" and "praise" commands show for each file the
3945 ** latest (relative to the revision given by -r) check-in that modified it and
3946 ** the check-in's author. If not given, the revision defaults to "current" for
3947 ** a check-out. Option -T additionally shows a comment snippet for the check-in.
3948 **
3949 ** Options:
3950 ** --filevers Show file version numbers rather than
3951 ** check-in versions
3952 ** -r|--revision VERSION The specific check-in containing the file
3953 ** -l|--log List all versions analyzed
@@ -3954,10 +3959,13 @@
3959 ** root of the repository. Set to the name of
3960 ** the main branch (usually "trunk") or
3961 ** similar for a reverse annotation.
3962 ** -w|--ignore-all-space Ignore white space when comparing lines
3963 ** -Z|--ignore-trailing-space Ignore whitespace at line end
3964 ** -t Show latest check-in and its author for each
3965 ** tracked file in the tree as of VERSION
3966 ** -T Like -t, plus comment snippet
3967 **
3968 ** See also: [[info]], [[finfo]], [[timeline]]
3969 */
3970 void annotate_cmd(void){
3971 const char *zRevision; /* Revision name, or NULL for current check-in */
@@ -3967,16 +3975,28 @@
3975 const char *zOrig; /* The value for -o|--origin */
3976 int showLog; /* True to show the log */
3977 int fileVers; /* Show file version instead of check-in versions */
3978 u64 annFlags = 0; /* Flags to control annotation properties */
3979 int bBlame = 0; /* True for BLAME output. False for ANNOTATE. */
3980 int bTreeInfo = 0; /* Show for the entire tree: 1=checkin, 2=with comment */
3981 int szHash; /* Display size of a version hash */
3982 Blob treename; /* Name of file to be annotated */
3983 char *zFilename; /* Name of file to be annotated */
3984
3985 bBlame = g.argv[1][0]!='a';
3986 if( find_option("t","t",0)!=0 ) bTreeInfo = 1;
3987 if( find_option("T","T",0)!=0 ) bTreeInfo = 2;
3988 zRevision = find_option("revision","r",1);
3989 if( bBlame && bTreeInfo ){
3990 if( find_repository_option()!=0 && zRevision==0 ){
3991 fossil_fatal("the -r is required in addition to -R");
3992 }
3993 db_find_and_open_repository(0, 0);
3994 if( zRevision==0 ) zRevision = "current";
3995 ls_cmd_rev(zRevision,1,1,0,1,bTreeInfo,0,0);
3996 return;
3997 }
3998 zLimit = find_option("limit","n",1);
3999 zOrig = find_option("origin","o",1);
4000 showLog = find_option("log","l",0)!=0;
4001 if( find_option("ignore-trailing-space","Z",0)!=0 ){
4002 annFlags = DIFF_IGNORE_EOLWS;
4003

Keyboard Shortcuts

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