Fossil SCM

Complete the parts of the "fossil unversioned" command used to add and remove content.

drh 2016-08-06 21:01 unversioned-files
Commit fb3c81d8ba0f632ad05fd59c7e8089ae745ed8b8
1 file changed +146 -42
+146 -42
--- src/unversioned.c
+++ src/unversioned.c
@@ -48,10 +48,41 @@
4848
void unversioned_schema(void){
4949
if( !db_table_exists("repository", "unversioned") ){
5050
db_multi_exec(zUnversionedInit /*works-like:"%w"*/, db_name("repository"));
5151
}
5252
}
53
+
54
+/*
55
+** Return a string which is the hash of the unversioned content.
56
+** This is the hash used by repositories to compare content before
57
+** exchanging a catalog. So all repositories must compute this hash
58
+** in exactly the same way.
59
+**
60
+** If debugFlag is set, force the value to be recomputed and write
61
+** the text of the hashed string to stdout.
62
+*/
63
+const char *unversioned_content_hash(int debugFlag){
64
+ const char *zHash = debugFlag ? 0 : db_get("uv-hash", 0);
65
+ if( zHash==0 ){
66
+ Stmt q;
67
+ db_prepare(&q,
68
+ "SELECT printf('%%s %%s %%s\n',name,datetime(mtime,'unixepoch'),hash)"
69
+ " FROM unversioned"
70
+ " WHERE hash IS NOT NULL"
71
+ " ORDER BY name"
72
+ );
73
+ while( db_step(&q)==SQLITE_ROW ){
74
+ const char *z = db_column_text(&q, 0);
75
+ if( debugFlag ) fossil_print("%s", z);
76
+ sha1sum_step_text(z,-1);
77
+ }
78
+ db_finalize(&q);
79
+ db_set("uv-hash", sha1sum_finish(0), 0);
80
+ zHash = db_get("uv-hash",0);
81
+ }
82
+ return zHash;
83
+}
5384
5485
/*
5586
** COMMAND: unversioned
5687
**
5788
** Usage: %fossil unversioned SUBCOMMAND ARGS...
@@ -61,18 +92,20 @@
6192
** of each UV-file is retained. Changes to an UV-file are permanent and cannot
6293
** be undone, so use appropriate caution with this command.
6394
**
6495
** Subcommands:
6596
**
66
-** add FILE ?INPUT? Add or update an unversioned file FILE in the local
67
-** repository so that it matches the INPUT file on disk.
68
-** Content is read from stdin if INPUT is "-" or omitted.
97
+** add FILE ... Add or update an unversioned files in the local
98
+** repository so that it matches FILE on disk.
99
+** Use "--as UVFILE" to give the file a different name
100
+** in the repository than what it called on disk.
69101
** Changes are not pushed to other repositories until
70102
** the next sync.
71103
**
72
-** cat FILE ?OUTFILE? Write the content of unversioned file FILE to OUTFILE
73
-** on disk, or to stdout if OUTFILE is "-" or is omitted.
104
+** cat FILE ... Concatenate the content of FILEs to stdout.
105
+**
106
+** export FILE OUTPUT Write the content of FILE into OUTPUT on disk
74107
**
75108
** list | ls Show all unversioned files held in the local repository.
76109
**
77110
** revert ?URL? Restore the state of all unversioned files in the local
78111
** repository to match the remote repository URL.
@@ -104,69 +137,140 @@
104137
}else{
105138
mtime = db_int(0, "SELECT strftime('%%s',%Q)", zMtime);
106139
if( mtime<=0 ) fossil_fatal("bad timestamp: %Q", zMtime);
107140
}
108141
if( memcmp(zCmd, "add", nCmd)==0 ){
109
- const char *zFile;
110142
const char *zIn;
143
+ const char *zAs;
111144
Blob file;
112145
Blob hash;
113146
Blob compressed;
114147
Stmt ins;
115
- if( g.argc!=4 && g.argc!=5 ) usage("add FILE ?INPUT?");
116
- zFile = g.argv[3];
117
- if( !file_is_simple_pathname(zFile,1) ){
118
- fossil_fatal("'%Q' is not an acceptable filename", zFile);
119
- }
120
- zIn = g.argc==5 ? g.argv[4] : "-";
121
- blob_init(&file,0,0);
122
- blob_read_from_file(&file, zIn);
123
- sha1sum_blob(&file, &hash);
124
- blob_compress(&file, &compressed);
148
+ int i;
149
+
150
+ zAs = find_option("as",0,1);
151
+ if( zAs && g.argc!=4 ) usage("add DISKFILE --as UVFILE");
152
+ verify_all_options();
125153
db_begin_transaction();
126154
content_rcvid_init();
127155
db_prepare(&ins,
128156
"REPLACE INTO unversioned(name,rcvid,mtime,hash,sz,content)"
129157
" VALUES(:name,:rcvid,:mtime,:hash,:sz,:content)"
130158
);
131
- db_bind_text(&ins, ":name", zFile);
132
- db_bind_int(&ins, ":rcvid", g.rcvid);
133
- db_bind_int64(&ins, ":mtime", mtime);
134
- db_bind_text(&ins, ":hash", blob_str(&hash));
135
- db_bind_int(&ins, ":sz", blob_size(&file));
136
- db_bind_blob(&ins, ":content", &compressed);
137
- db_step(&ins);
159
+ for(i=3; i<g.argc; i++){
160
+ zIn = zAs ? zAs : g.argv[i];
161
+ if( zIn[0]==0 || zIn[0]=='/' || !file_is_simple_pathname(zIn,1) ){
162
+ fossil_fatal("'%Q' is not an acceptable filename", zIn);
163
+ }
164
+ blob_init(&file,0,0);
165
+ blob_read_from_file(&file, g.argv[i]);
166
+ sha1sum_blob(&file, &hash);
167
+ blob_compress(&file, &compressed);
168
+ db_bind_text(&ins, ":name", zIn);
169
+ db_bind_int(&ins, ":rcvid", g.rcvid);
170
+ db_bind_int64(&ins, ":mtime", mtime);
171
+ db_bind_text(&ins, ":hash", blob_str(&hash));
172
+ db_bind_int(&ins, ":sz", blob_size(&file));
173
+ db_bind_blob(&ins, ":content", &compressed);
174
+ db_step(&ins);
175
+ db_reset(&ins);
176
+ blob_reset(&compressed);
177
+ blob_reset(&hash);
178
+ blob_reset(&file);
179
+ }
138180
db_finalize(&ins);
139
- blob_reset(&compressed);
140
- blob_reset(&hash);
141
- blob_reset(&file);
142
- /* Clear the uvhash cache */
181
+ db_unset("uv-hash", 0);
143182
db_end_transaction(0);
144183
}else if( memcmp(zCmd, "cat", nCmd)==0 ){
184
+ int i;
185
+ verify_all_options();
186
+ db_begin_transaction();
187
+ for(i=3; i<g.argc; i++){
188
+ Blob content;
189
+ blob_init(&content, 0, 0);
190
+ db_blob(&content, "SELECT content FROM unversioned WHERE name=%Q",g.argv[i]);
191
+ if( blob_size(&content)==0 ){
192
+ fossil_fatal("no such uv-file: %Q", g.argv[i]);
193
+ }
194
+ blob_uncompress(&content, &content);
195
+ blob_write_to_file(&content, "-");
196
+ blob_reset(&content);
197
+ }
198
+ db_end_transaction(0);
199
+ }else if( memcmp(zCmd, "export", nCmd)==0 ){
200
+ Blob content;
201
+ verify_all_options();
202
+ if( g.argc!=5 ) usage("export UVFILE OUTPUT");
203
+ blob_init(&content, 0, 0);
204
+ db_blob(&content, "SELECT content FROM unversioned WHERE name=%Q", g.argv[3]);
205
+ if( blob_size(&content)==0 ){
206
+ fossil_fatal("no such uv-file: %Q", g.argv[3]);
207
+ }
208
+ blob_uncompress(&content, &content);
209
+ blob_write_to_file(&content, g.argv[4]);
210
+ blob_reset(&content);
211
+ }else if( memcmp(zCmd, "hash", nCmd)==0 ){ /* undocumented */
212
+ /* Show the hash value used during uv sync */
213
+ int debugFlag = find_option("debug",0,0)!=0;
214
+ fossil_print("%s\n", unversioned_content_hash(debugFlag));
145215
}else if( memcmp(zCmd, "list", nCmd)==0 || memcmp(zCmd, "ls", nCmd)==0 ){
146216
Stmt q;
147
- db_prepare(&q,
148
- "SELECT hash, datetime(mtime,'unixepoch'), sz, name, content IS NULL"
149
- " FROM unversioned"
150
- " WHERE hash IS NOT NULL"
151
- " ORDER BY name;"
152
- );
153
- while( db_step(&q)==SQLITE_ROW ){
154
- fossil_print("%12.12s %s %8d %s%s\n",
155
- db_column_text(&q,0),
156
- db_column_text(&q,1),
157
- db_column_int(&q,2),
158
- db_column_text(&q,3),
159
- db_column_int(&q,4) ? " ** no content ** ": ""
160
- );
217
+ int allFlag = find_option("all","a",0)!=0;
218
+ int longFlag = find_option("l",0,0)!=0 || (nCmd>1 && zCmd[1]=='i');
219
+ verify_all_options();
220
+ if( !longFlag ){
221
+ if( allFlag ){
222
+ db_prepare(&q, "SELECT name FROM unversioned ORDER BY name");
223
+ }else{
224
+ db_prepare(&q, "SELECT name FROM unversioned WHERE hash IS NOT NULL"
225
+ " ORDER BY name");
226
+ }
227
+ while( db_step(&q)==SQLITE_ROW ){
228
+ fossil_print("%s\n", db_column_text(&q,0));
229
+ }
230
+ }else{
231
+ db_prepare(&q,
232
+ "SELECT hash, datetime(mtime,'unixepoch'), sz, name, content IS NULL"
233
+ " FROM unversioned"
234
+ " ORDER BY name;"
235
+ );
236
+ while( db_step(&q)==SQLITE_ROW ){
237
+ const char *zHash = db_column_text(&q, 0);
238
+ const char *zNoContent = "";
239
+ if( zHash==0 ){
240
+ if( !allFlag ) continue;
241
+ zHash = "(deleted)";
242
+ }else if( db_column_int(&q,4) ){
243
+ zNoContent = " (no content)";
244
+ }
245
+ fossil_print("%12.12s %s %8d %s%s\n",
246
+ zHash,
247
+ db_column_text(&q,1),
248
+ db_column_int(&q,2),
249
+ db_column_text(&q,3),
250
+ zNoContent
251
+ );
252
+ }
161253
}
162254
db_finalize(&q);
163255
}else if( memcmp(zCmd, "revert", nCmd)==0 || memcmp(zCmd,"sync",nCmd)==0 ){
164256
fossil_fatal("not yet implemented...");
165257
}else if( memcmp(zCmd, "rm", nCmd)==0 ){
258
+ int i;
259
+ verify_all_options();
260
+ db_begin_transaction();
261
+ for(i=3; i<g.argc; i++){
262
+ db_multi_exec(
263
+ "UPDATE unversioned"
264
+ " SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name=%Q",
265
+ mtime, g.argv[i]
266
+ );
267
+ }
268
+ db_unset("uv-hash", 0);
269
+ db_end_transaction(0);
166270
}else{
167
- usage("add|cat|ls|revert|rm|sync");
271
+ usage("add|cat|export|ls|revert|rm|sync");
168272
}
169273
}
170274
171275
#if 0
172276
***************************************************************************
173277
--- src/unversioned.c
+++ src/unversioned.c
@@ -48,10 +48,41 @@
48 void unversioned_schema(void){
49 if( !db_table_exists("repository", "unversioned") ){
50 db_multi_exec(zUnversionedInit /*works-like:"%w"*/, db_name("repository"));
51 }
52 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
54 /*
55 ** COMMAND: unversioned
56 **
57 ** Usage: %fossil unversioned SUBCOMMAND ARGS...
@@ -61,18 +92,20 @@
61 ** of each UV-file is retained. Changes to an UV-file are permanent and cannot
62 ** be undone, so use appropriate caution with this command.
63 **
64 ** Subcommands:
65 **
66 ** add FILE ?INPUT? Add or update an unversioned file FILE in the local
67 ** repository so that it matches the INPUT file on disk.
68 ** Content is read from stdin if INPUT is "-" or omitted.
 
69 ** Changes are not pushed to other repositories until
70 ** the next sync.
71 **
72 ** cat FILE ?OUTFILE? Write the content of unversioned file FILE to OUTFILE
73 ** on disk, or to stdout if OUTFILE is "-" or is omitted.
 
74 **
75 ** list | ls Show all unversioned files held in the local repository.
76 **
77 ** revert ?URL? Restore the state of all unversioned files in the local
78 ** repository to match the remote repository URL.
@@ -104,69 +137,140 @@
104 }else{
105 mtime = db_int(0, "SELECT strftime('%%s',%Q)", zMtime);
106 if( mtime<=0 ) fossil_fatal("bad timestamp: %Q", zMtime);
107 }
108 if( memcmp(zCmd, "add", nCmd)==0 ){
109 const char *zFile;
110 const char *zIn;
 
111 Blob file;
112 Blob hash;
113 Blob compressed;
114 Stmt ins;
115 if( g.argc!=4 && g.argc!=5 ) usage("add FILE ?INPUT?");
116 zFile = g.argv[3];
117 if( !file_is_simple_pathname(zFile,1) ){
118 fossil_fatal("'%Q' is not an acceptable filename", zFile);
119 }
120 zIn = g.argc==5 ? g.argv[4] : "-";
121 blob_init(&file,0,0);
122 blob_read_from_file(&file, zIn);
123 sha1sum_blob(&file, &hash);
124 blob_compress(&file, &compressed);
125 db_begin_transaction();
126 content_rcvid_init();
127 db_prepare(&ins,
128 "REPLACE INTO unversioned(name,rcvid,mtime,hash,sz,content)"
129 " VALUES(:name,:rcvid,:mtime,:hash,:sz,:content)"
130 );
131 db_bind_text(&ins, ":name", zFile);
132 db_bind_int(&ins, ":rcvid", g.rcvid);
133 db_bind_int64(&ins, ":mtime", mtime);
134 db_bind_text(&ins, ":hash", blob_str(&hash));
135 db_bind_int(&ins, ":sz", blob_size(&file));
136 db_bind_blob(&ins, ":content", &compressed);
137 db_step(&ins);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138 db_finalize(&ins);
139 blob_reset(&compressed);
140 blob_reset(&hash);
141 blob_reset(&file);
142 /* Clear the uvhash cache */
143 db_end_transaction(0);
144 }else if( memcmp(zCmd, "cat", nCmd)==0 ){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145 }else if( memcmp(zCmd, "list", nCmd)==0 || memcmp(zCmd, "ls", nCmd)==0 ){
146 Stmt q;
147 db_prepare(&q,
148 "SELECT hash, datetime(mtime,'unixepoch'), sz, name, content IS NULL"
149 " FROM unversioned"
150 " WHERE hash IS NOT NULL"
151 " ORDER BY name;"
152 );
153 while( db_step(&q)==SQLITE_ROW ){
154 fossil_print("%12.12s %s %8d %s%s\n",
155 db_column_text(&q,0),
156 db_column_text(&q,1),
157 db_column_int(&q,2),
158 db_column_text(&q,3),
159 db_column_int(&q,4) ? " ** no content ** ": ""
160 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161 }
162 db_finalize(&q);
163 }else if( memcmp(zCmd, "revert", nCmd)==0 || memcmp(zCmd,"sync",nCmd)==0 ){
164 fossil_fatal("not yet implemented...");
165 }else if( memcmp(zCmd, "rm", nCmd)==0 ){
 
 
 
 
 
 
 
 
 
 
 
 
166 }else{
167 usage("add|cat|ls|revert|rm|sync");
168 }
169 }
170
171 #if 0
172 ***************************************************************************
173
--- src/unversioned.c
+++ src/unversioned.c
@@ -48,10 +48,41 @@
48 void unversioned_schema(void){
49 if( !db_table_exists("repository", "unversioned") ){
50 db_multi_exec(zUnversionedInit /*works-like:"%w"*/, db_name("repository"));
51 }
52 }
53
54 /*
55 ** Return a string which is the hash of the unversioned content.
56 ** This is the hash used by repositories to compare content before
57 ** exchanging a catalog. So all repositories must compute this hash
58 ** in exactly the same way.
59 **
60 ** If debugFlag is set, force the value to be recomputed and write
61 ** the text of the hashed string to stdout.
62 */
63 const char *unversioned_content_hash(int debugFlag){
64 const char *zHash = debugFlag ? 0 : db_get("uv-hash", 0);
65 if( zHash==0 ){
66 Stmt q;
67 db_prepare(&q,
68 "SELECT printf('%%s %%s %%s\n',name,datetime(mtime,'unixepoch'),hash)"
69 " FROM unversioned"
70 " WHERE hash IS NOT NULL"
71 " ORDER BY name"
72 );
73 while( db_step(&q)==SQLITE_ROW ){
74 const char *z = db_column_text(&q, 0);
75 if( debugFlag ) fossil_print("%s", z);
76 sha1sum_step_text(z,-1);
77 }
78 db_finalize(&q);
79 db_set("uv-hash", sha1sum_finish(0), 0);
80 zHash = db_get("uv-hash",0);
81 }
82 return zHash;
83 }
84
85 /*
86 ** COMMAND: unversioned
87 **
88 ** Usage: %fossil unversioned SUBCOMMAND ARGS...
@@ -61,18 +92,20 @@
92 ** of each UV-file is retained. Changes to an UV-file are permanent and cannot
93 ** be undone, so use appropriate caution with this command.
94 **
95 ** Subcommands:
96 **
97 ** add FILE ... Add or update an unversioned files in the local
98 ** repository so that it matches FILE on disk.
99 ** Use "--as UVFILE" to give the file a different name
100 ** in the repository than what it called on disk.
101 ** Changes are not pushed to other repositories until
102 ** the next sync.
103 **
104 ** cat FILE ... Concatenate the content of FILEs to stdout.
105 **
106 ** export FILE OUTPUT Write the content of FILE into OUTPUT on disk
107 **
108 ** list | ls Show all unversioned files held in the local repository.
109 **
110 ** revert ?URL? Restore the state of all unversioned files in the local
111 ** repository to match the remote repository URL.
@@ -104,69 +137,140 @@
137 }else{
138 mtime = db_int(0, "SELECT strftime('%%s',%Q)", zMtime);
139 if( mtime<=0 ) fossil_fatal("bad timestamp: %Q", zMtime);
140 }
141 if( memcmp(zCmd, "add", nCmd)==0 ){
 
142 const char *zIn;
143 const char *zAs;
144 Blob file;
145 Blob hash;
146 Blob compressed;
147 Stmt ins;
148 int i;
149
150 zAs = find_option("as",0,1);
151 if( zAs && g.argc!=4 ) usage("add DISKFILE --as UVFILE");
152 verify_all_options();
 
 
 
 
 
153 db_begin_transaction();
154 content_rcvid_init();
155 db_prepare(&ins,
156 "REPLACE INTO unversioned(name,rcvid,mtime,hash,sz,content)"
157 " VALUES(:name,:rcvid,:mtime,:hash,:sz,:content)"
158 );
159 for(i=3; i<g.argc; i++){
160 zIn = zAs ? zAs : g.argv[i];
161 if( zIn[0]==0 || zIn[0]=='/' || !file_is_simple_pathname(zIn,1) ){
162 fossil_fatal("'%Q' is not an acceptable filename", zIn);
163 }
164 blob_init(&file,0,0);
165 blob_read_from_file(&file, g.argv[i]);
166 sha1sum_blob(&file, &hash);
167 blob_compress(&file, &compressed);
168 db_bind_text(&ins, ":name", zIn);
169 db_bind_int(&ins, ":rcvid", g.rcvid);
170 db_bind_int64(&ins, ":mtime", mtime);
171 db_bind_text(&ins, ":hash", blob_str(&hash));
172 db_bind_int(&ins, ":sz", blob_size(&file));
173 db_bind_blob(&ins, ":content", &compressed);
174 db_step(&ins);
175 db_reset(&ins);
176 blob_reset(&compressed);
177 blob_reset(&hash);
178 blob_reset(&file);
179 }
180 db_finalize(&ins);
181 db_unset("uv-hash", 0);
 
 
 
182 db_end_transaction(0);
183 }else if( memcmp(zCmd, "cat", nCmd)==0 ){
184 int i;
185 verify_all_options();
186 db_begin_transaction();
187 for(i=3; i<g.argc; i++){
188 Blob content;
189 blob_init(&content, 0, 0);
190 db_blob(&content, "SELECT content FROM unversioned WHERE name=%Q",g.argv[i]);
191 if( blob_size(&content)==0 ){
192 fossil_fatal("no such uv-file: %Q", g.argv[i]);
193 }
194 blob_uncompress(&content, &content);
195 blob_write_to_file(&content, "-");
196 blob_reset(&content);
197 }
198 db_end_transaction(0);
199 }else if( memcmp(zCmd, "export", nCmd)==0 ){
200 Blob content;
201 verify_all_options();
202 if( g.argc!=5 ) usage("export UVFILE OUTPUT");
203 blob_init(&content, 0, 0);
204 db_blob(&content, "SELECT content FROM unversioned WHERE name=%Q", g.argv[3]);
205 if( blob_size(&content)==0 ){
206 fossil_fatal("no such uv-file: %Q", g.argv[3]);
207 }
208 blob_uncompress(&content, &content);
209 blob_write_to_file(&content, g.argv[4]);
210 blob_reset(&content);
211 }else if( memcmp(zCmd, "hash", nCmd)==0 ){ /* undocumented */
212 /* Show the hash value used during uv sync */
213 int debugFlag = find_option("debug",0,0)!=0;
214 fossil_print("%s\n", unversioned_content_hash(debugFlag));
215 }else if( memcmp(zCmd, "list", nCmd)==0 || memcmp(zCmd, "ls", nCmd)==0 ){
216 Stmt q;
217 int allFlag = find_option("all","a",0)!=0;
218 int longFlag = find_option("l",0,0)!=0 || (nCmd>1 && zCmd[1]=='i');
219 verify_all_options();
220 if( !longFlag ){
221 if( allFlag ){
222 db_prepare(&q, "SELECT name FROM unversioned ORDER BY name");
223 }else{
224 db_prepare(&q, "SELECT name FROM unversioned WHERE hash IS NOT NULL"
225 " ORDER BY name");
226 }
227 while( db_step(&q)==SQLITE_ROW ){
228 fossil_print("%s\n", db_column_text(&q,0));
229 }
230 }else{
231 db_prepare(&q,
232 "SELECT hash, datetime(mtime,'unixepoch'), sz, name, content IS NULL"
233 " FROM unversioned"
234 " ORDER BY name;"
235 );
236 while( db_step(&q)==SQLITE_ROW ){
237 const char *zHash = db_column_text(&q, 0);
238 const char *zNoContent = "";
239 if( zHash==0 ){
240 if( !allFlag ) continue;
241 zHash = "(deleted)";
242 }else if( db_column_int(&q,4) ){
243 zNoContent = " (no content)";
244 }
245 fossil_print("%12.12s %s %8d %s%s\n",
246 zHash,
247 db_column_text(&q,1),
248 db_column_int(&q,2),
249 db_column_text(&q,3),
250 zNoContent
251 );
252 }
253 }
254 db_finalize(&q);
255 }else if( memcmp(zCmd, "revert", nCmd)==0 || memcmp(zCmd,"sync",nCmd)==0 ){
256 fossil_fatal("not yet implemented...");
257 }else if( memcmp(zCmd, "rm", nCmd)==0 ){
258 int i;
259 verify_all_options();
260 db_begin_transaction();
261 for(i=3; i<g.argc; i++){
262 db_multi_exec(
263 "UPDATE unversioned"
264 " SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name=%Q",
265 mtime, g.argv[i]
266 );
267 }
268 db_unset("uv-hash", 0);
269 db_end_transaction(0);
270 }else{
271 usage("add|cat|export|ls|revert|rm|sync");
272 }
273 }
274
275 #if 0
276 ***************************************************************************
277

Keyboard Shortcuts

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