Fossil SCM

More sync fixes: The previous version was not pulling new branches off of the server. This should fix that.

drh 2007-08-10 03:50 trunk
Commit 50150adeecefe2019e9a8ef00b136245ac22c4b7
1 file changed +64 -30
+64 -30
--- src/xfer.c
+++ src/xfer.c
@@ -191,11 +191,11 @@
191191
*/
192192
static void send_file(Xfer *pXfer, int rid, Blob *pUuid, int srcId){
193193
Blob content, uuid;
194194
int size = 0;
195195
196
- if( db_exists("SELECT 1 FROM sent WHERE rid=%d", rid) ){
196
+ if( db_exists("SELECT 1 FROM onremote WHERE rid=%d", rid) ){
197197
return;
198198
}
199199
blob_zero(&uuid);
200200
if( pUuid==0 ){
201201
db_blob(&uuid, "SELECT uuid FROM blob WHERE rid=%d AND size>=0", rid);
@@ -221,11 +221,11 @@
221221
blob_append(pXfer->pOut, blob_buffer(&content), size);
222222
pXfer->nFileSent++;
223223
}else{
224224
pXfer->nDeltaSent++;
225225
}
226
- db_multi_exec("INSERT INTO sent VALUES(%d)", rid);
226
+ db_multi_exec("INSERT INTO onremote VALUES(%d)", rid);
227227
blob_reset(&uuid);
228228
}
229229
230230
/*
231231
** This routine runs when either client or server is notified that
@@ -379,11 +379,11 @@
379379
xfer.pOut = cgi_output_blob();
380380
xfer.mxSend = db_get_int("max-download", 1000000);
381381
382382
db_begin_transaction();
383383
db_multi_exec(
384
- "CREATE TEMP TABLE sent(rid INTEGER PRIMARY KEY);"
384
+ "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
385385
);
386386
while( blob_line(xfer.pIn, &xfer.line) ){
387387
xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
388388
389389
/* file UUID SIZE \n CONTENT
@@ -390,11 +390,11 @@
390390
** file UUID DELTASRC SIZE \n CONTENT
391391
**
392392
** Accept a file from the client.
393393
*/
394394
if( blob_eq(&xfer.aToken[0], "file") ){
395
- if( !g.okWrite ){
395
+ if( !isPush ){
396396
cgi_reset_content();
397397
@ error not\sauthorized\sto\swrite
398398
nErr++;
399399
break;
400400
}
@@ -413,11 +413,11 @@
413413
*/
414414
if( blob_eq(&xfer.aToken[0], "gimme")
415415
&& xfer.nToken==2
416416
&& blob_is_uuid(&xfer.aToken[1])
417417
){
418
- if( g.okRead ){
418
+ if( isPull ){
419419
int rid = rid_from_uuid(&xfer.aToken[1], 0);
420420
if( rid ){
421421
send_file(&xfer, rid, &xfer.aToken[1], 0);
422422
}
423423
}
@@ -429,28 +429,34 @@
429429
*/
430430
if( xfer.nToken==2
431431
&& blob_eq(&xfer.aToken[0], "igot")
432432
&& blob_is_uuid(&xfer.aToken[1])
433433
){
434
- if( g.okWrite ){
434
+ if( isPush ){
435435
rid_from_uuid(&xfer.aToken[1], 1);
436436
}
437437
}else
438438
439439
440440
/* leaf UUID
441441
**
442
- ** Client announces that it has a particular manifest
442
+ ** Client announces that it has a particular manifest. If
443
+ ** the server has children of this leaf, then send those
444
+ ** children back to the client. If the server lacks this
445
+ ** leaf, request it.
443446
*/
444447
if( xfer.nToken==2
445448
&& blob_eq(&xfer.aToken[0], "leaf")
446449
&& blob_is_uuid(&xfer.aToken[1])
447450
){
448
- if( g.okRead ){
449
- int rid = rid_from_uuid(&xfer.aToken[1], 0);
451
+ int rid = rid_from_uuid(&xfer.aToken[1], 0);
452
+ if( isPull && rid ){
450453
leaf_response(&xfer, rid);
451454
}
455
+ if( isPush && !rid ){
456
+ content_put(0, blob_str(&xfer.aToken[1]), 0);
457
+ }
452458
}else
453459
454460
/* pull SERVERCODE PROJECTCODE
455461
** push SERVERCODE PROJECTCODE
456462
**
@@ -619,40 +625,46 @@
619625
assert( pushFlag || pullFlag || cloneFlag );
620626
assert( !g.urlIsFile ); /* This only works for networking */
621627
622628
db_begin_transaction();
623629
db_multi_exec(
624
- "CREATE TEMP TABLE sent(rid INTEGER PRIMARY KEY);"
630
+ "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
625631
);
626632
blobarray_zero(xfer.aToken, count(xfer.aToken));
627633
blob_zero(&send);
628634
blob_zero(&recv);
629635
blob_zero(&xfer.err);
630636
blob_zero(&xfer.line);
631637
638
+ /*
639
+ ** Always begin with a clone, pull, or push message
640
+ */
641
+ if( cloneFlag ){
642
+ blob_appendf(&send, "clone\n");
643
+ pushFlag = 0;
644
+ pullFlag = 0;
645
+ nMsg++;
646
+ }else if( pullFlag ){
647
+ blob_appendf(&send, "pull %s %s\n", zSCode, zPCode);
648
+ nMsg++;
649
+ }
650
+ if( pushFlag ){
651
+ blob_appendf(&send, "push %s %s\n", zSCode, zPCode);
652
+ nMsg++;
653
+ }
654
+
632655
633656
while( go ){
657
+ int newPhantom = 0;
634658
635
- /* Generate a request to be sent to the server.
636
- ** Always begin with a clone, pull, or push message
659
+ /* Generate gimme messages for phantoms and leaf messages
660
+ ** for all leaves.
637661
*/
638
-
639
- if( cloneFlag ){
640
- blob_appendf(&send, "clone\n");
641
- pushFlag = 0;
642
- pullFlag = 0;
643
- nMsg++;
644
- }else if( pullFlag ){
645
- blob_appendf(&send, "pull %s %s\n", zSCode, zPCode);
646
- nMsg++;
662
+ if( pullFlag ){
647663
request_phantoms(&xfer);
648664
send_leaves(&xfer);
649665
}
650
- if( pushFlag ){
651
- blob_appendf(&send, "push %s %s\n", zSCode, zPCode);
652
- nMsg++;
653
- }
654666
655667
/* Exchange messages with the server */
656668
nFileSend = xfer.nFileSent + xfer.nDeltaSent;
657669
printf("Send: %10d bytes, %3d messages, %3d files (%d+%d)\n",
658670
blob_size(&send), nMsg+xfer.nGimmeSent+xfer.nIGotSent,
@@ -661,10 +673,23 @@
661673
xfer.nFileSent = 0;
662674
xfer.nDeltaSent = 0;
663675
xfer.nGimmeSent = 0;
664676
http_exchange(&send, &recv);
665677
blob_reset(&send);
678
+
679
+ /* Begin constructing the next message (which might never be
680
+ ** sent) by beginning with the pull or push messages
681
+ */
682
+ if( pullFlag ){
683
+ blob_appendf(&send, "pull %s %s\n", zSCode, zPCode);
684
+ nMsg++;
685
+ }
686
+ if( pushFlag ){
687
+ blob_appendf(&send, "push %s %s\n", zSCode, zPCode);
688
+ nMsg++;
689
+ }
690
+
666691
667692
/* Process the reply that came back from the server */
668693
while( blob_line(&recv, &xfer.line) ){
669694
if( blob_buffer(&xfer.line)[0]=='#' ){
670695
continue;
@@ -706,10 +731,11 @@
706731
nMsg++;
707732
if( pullFlag ){
708733
if( !db_exists("SELECT 1 FROM blob WHERE uuid='%b' AND size>=0",
709734
&xfer.aToken[1]) ){
710735
content_put(0, blob_str(&xfer.aToken[1]), 0);
736
+ newPhantom = 1;
711737
}
712738
}
713739
}else
714740
715741
@@ -720,13 +746,17 @@
720746
if( xfer.nToken==2
721747
&& blob_eq(&xfer.aToken[0], "leaf")
722748
&& blob_is_uuid(&xfer.aToken[1])
723749
){
724750
nMsg++;
725
- if( pushFlag ){
726
- int rid = rid_from_uuid(&xfer.aToken[1], 0);
751
+ int rid = rid_from_uuid(&xfer.aToken[1], 0);
752
+ if( pushFlag && rid ){
727753
leaf_response(&xfer, rid);
754
+ }
755
+ if( pullFlag && rid==0 ){
756
+ content_put(0, blob_str(&xfer.aToken[1]), 0);
757
+ newPhantom = 1;
728758
}
729759
}else
730760
731761
732762
/* push SERVERCODE PRODUCTCODE
@@ -747,10 +777,12 @@
747777
zPCode = mprintf("%b", &xfer.aToken[2]);
748778
db_set("project-code", zPCode);
749779
}
750780
cloneFlag = 0;
751781
pullFlag = 1;
782
+ blob_appendf(&send, "pull %s %s\n", zSCode, zPCode);
783
+ nMsg++;
752784
}else
753785
754786
/* error MESSAGE
755787
**
756788
** Report an error
@@ -783,14 +815,16 @@
783815
xfer.nDeltaRcvd = 0;
784816
xfer.nDanglingFile = 0;
785817
nCycle++;
786818
go = 0;
787819
788
- /* If we have received one or more files on this cycle and
789
- ** we have one or more phantoms, then go for another round
820
+ /* If we have received one or more files on this cycle or if
821
+ ** we have received information that has caused us to create
822
+ ** new phantoms and we have one or more phantoms, then go for
823
+ ** another round
790824
*/
791
- if(xfer.nFileRcvd+xfer.nDeltaRcvd+xfer.nDanglingFile>0
825
+ if( (xfer.nFileRcvd+xfer.nDeltaRcvd+xfer.nDanglingFile>0 || newPhantom)
792826
&& db_exists("SELECT 1 FROM phantom")
793827
){
794828
go = 1;
795829
}
796830
797831
--- src/xfer.c
+++ src/xfer.c
@@ -191,11 +191,11 @@
191 */
192 static void send_file(Xfer *pXfer, int rid, Blob *pUuid, int srcId){
193 Blob content, uuid;
194 int size = 0;
195
196 if( db_exists("SELECT 1 FROM sent WHERE rid=%d", rid) ){
197 return;
198 }
199 blob_zero(&uuid);
200 if( pUuid==0 ){
201 db_blob(&uuid, "SELECT uuid FROM blob WHERE rid=%d AND size>=0", rid);
@@ -221,11 +221,11 @@
221 blob_append(pXfer->pOut, blob_buffer(&content), size);
222 pXfer->nFileSent++;
223 }else{
224 pXfer->nDeltaSent++;
225 }
226 db_multi_exec("INSERT INTO sent VALUES(%d)", rid);
227 blob_reset(&uuid);
228 }
229
230 /*
231 ** This routine runs when either client or server is notified that
@@ -379,11 +379,11 @@
379 xfer.pOut = cgi_output_blob();
380 xfer.mxSend = db_get_int("max-download", 1000000);
381
382 db_begin_transaction();
383 db_multi_exec(
384 "CREATE TEMP TABLE sent(rid INTEGER PRIMARY KEY);"
385 );
386 while( blob_line(xfer.pIn, &xfer.line) ){
387 xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
388
389 /* file UUID SIZE \n CONTENT
@@ -390,11 +390,11 @@
390 ** file UUID DELTASRC SIZE \n CONTENT
391 **
392 ** Accept a file from the client.
393 */
394 if( blob_eq(&xfer.aToken[0], "file") ){
395 if( !g.okWrite ){
396 cgi_reset_content();
397 @ error not\sauthorized\sto\swrite
398 nErr++;
399 break;
400 }
@@ -413,11 +413,11 @@
413 */
414 if( blob_eq(&xfer.aToken[0], "gimme")
415 && xfer.nToken==2
416 && blob_is_uuid(&xfer.aToken[1])
417 ){
418 if( g.okRead ){
419 int rid = rid_from_uuid(&xfer.aToken[1], 0);
420 if( rid ){
421 send_file(&xfer, rid, &xfer.aToken[1], 0);
422 }
423 }
@@ -429,28 +429,34 @@
429 */
430 if( xfer.nToken==2
431 && blob_eq(&xfer.aToken[0], "igot")
432 && blob_is_uuid(&xfer.aToken[1])
433 ){
434 if( g.okWrite ){
435 rid_from_uuid(&xfer.aToken[1], 1);
436 }
437 }else
438
439
440 /* leaf UUID
441 **
442 ** Client announces that it has a particular manifest
 
 
 
443 */
444 if( xfer.nToken==2
445 && blob_eq(&xfer.aToken[0], "leaf")
446 && blob_is_uuid(&xfer.aToken[1])
447 ){
448 if( g.okRead ){
449 int rid = rid_from_uuid(&xfer.aToken[1], 0);
450 leaf_response(&xfer, rid);
451 }
 
 
 
452 }else
453
454 /* pull SERVERCODE PROJECTCODE
455 ** push SERVERCODE PROJECTCODE
456 **
@@ -619,40 +625,46 @@
619 assert( pushFlag || pullFlag || cloneFlag );
620 assert( !g.urlIsFile ); /* This only works for networking */
621
622 db_begin_transaction();
623 db_multi_exec(
624 "CREATE TEMP TABLE sent(rid INTEGER PRIMARY KEY);"
625 );
626 blobarray_zero(xfer.aToken, count(xfer.aToken));
627 blob_zero(&send);
628 blob_zero(&recv);
629 blob_zero(&xfer.err);
630 blob_zero(&xfer.line);
631
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
632
633 while( go ){
 
634
635 /* Generate a request to be sent to the server.
636 ** Always begin with a clone, pull, or push message
637 */
638
639 if( cloneFlag ){
640 blob_appendf(&send, "clone\n");
641 pushFlag = 0;
642 pullFlag = 0;
643 nMsg++;
644 }else if( pullFlag ){
645 blob_appendf(&send, "pull %s %s\n", zSCode, zPCode);
646 nMsg++;
647 request_phantoms(&xfer);
648 send_leaves(&xfer);
649 }
650 if( pushFlag ){
651 blob_appendf(&send, "push %s %s\n", zSCode, zPCode);
652 nMsg++;
653 }
654
655 /* Exchange messages with the server */
656 nFileSend = xfer.nFileSent + xfer.nDeltaSent;
657 printf("Send: %10d bytes, %3d messages, %3d files (%d+%d)\n",
658 blob_size(&send), nMsg+xfer.nGimmeSent+xfer.nIGotSent,
@@ -661,10 +673,23 @@
661 xfer.nFileSent = 0;
662 xfer.nDeltaSent = 0;
663 xfer.nGimmeSent = 0;
664 http_exchange(&send, &recv);
665 blob_reset(&send);
 
 
 
 
 
 
 
 
 
 
 
 
 
666
667 /* Process the reply that came back from the server */
668 while( blob_line(&recv, &xfer.line) ){
669 if( blob_buffer(&xfer.line)[0]=='#' ){
670 continue;
@@ -706,10 +731,11 @@
706 nMsg++;
707 if( pullFlag ){
708 if( !db_exists("SELECT 1 FROM blob WHERE uuid='%b' AND size>=0",
709 &xfer.aToken[1]) ){
710 content_put(0, blob_str(&xfer.aToken[1]), 0);
 
711 }
712 }
713 }else
714
715
@@ -720,13 +746,17 @@
720 if( xfer.nToken==2
721 && blob_eq(&xfer.aToken[0], "leaf")
722 && blob_is_uuid(&xfer.aToken[1])
723 ){
724 nMsg++;
725 if( pushFlag ){
726 int rid = rid_from_uuid(&xfer.aToken[1], 0);
727 leaf_response(&xfer, rid);
 
 
 
 
728 }
729 }else
730
731
732 /* push SERVERCODE PRODUCTCODE
@@ -747,10 +777,12 @@
747 zPCode = mprintf("%b", &xfer.aToken[2]);
748 db_set("project-code", zPCode);
749 }
750 cloneFlag = 0;
751 pullFlag = 1;
 
 
752 }else
753
754 /* error MESSAGE
755 **
756 ** Report an error
@@ -783,14 +815,16 @@
783 xfer.nDeltaRcvd = 0;
784 xfer.nDanglingFile = 0;
785 nCycle++;
786 go = 0;
787
788 /* If we have received one or more files on this cycle and
789 ** we have one or more phantoms, then go for another round
 
 
790 */
791 if(xfer.nFileRcvd+xfer.nDeltaRcvd+xfer.nDanglingFile>0
792 && db_exists("SELECT 1 FROM phantom")
793 ){
794 go = 1;
795 }
796
797
--- src/xfer.c
+++ src/xfer.c
@@ -191,11 +191,11 @@
191 */
192 static void send_file(Xfer *pXfer, int rid, Blob *pUuid, int srcId){
193 Blob content, uuid;
194 int size = 0;
195
196 if( db_exists("SELECT 1 FROM onremote WHERE rid=%d", rid) ){
197 return;
198 }
199 blob_zero(&uuid);
200 if( pUuid==0 ){
201 db_blob(&uuid, "SELECT uuid FROM blob WHERE rid=%d AND size>=0", rid);
@@ -221,11 +221,11 @@
221 blob_append(pXfer->pOut, blob_buffer(&content), size);
222 pXfer->nFileSent++;
223 }else{
224 pXfer->nDeltaSent++;
225 }
226 db_multi_exec("INSERT INTO onremote VALUES(%d)", rid);
227 blob_reset(&uuid);
228 }
229
230 /*
231 ** This routine runs when either client or server is notified that
@@ -379,11 +379,11 @@
379 xfer.pOut = cgi_output_blob();
380 xfer.mxSend = db_get_int("max-download", 1000000);
381
382 db_begin_transaction();
383 db_multi_exec(
384 "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
385 );
386 while( blob_line(xfer.pIn, &xfer.line) ){
387 xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
388
389 /* file UUID SIZE \n CONTENT
@@ -390,11 +390,11 @@
390 ** file UUID DELTASRC SIZE \n CONTENT
391 **
392 ** Accept a file from the client.
393 */
394 if( blob_eq(&xfer.aToken[0], "file") ){
395 if( !isPush ){
396 cgi_reset_content();
397 @ error not\sauthorized\sto\swrite
398 nErr++;
399 break;
400 }
@@ -413,11 +413,11 @@
413 */
414 if( blob_eq(&xfer.aToken[0], "gimme")
415 && xfer.nToken==2
416 && blob_is_uuid(&xfer.aToken[1])
417 ){
418 if( isPull ){
419 int rid = rid_from_uuid(&xfer.aToken[1], 0);
420 if( rid ){
421 send_file(&xfer, rid, &xfer.aToken[1], 0);
422 }
423 }
@@ -429,28 +429,34 @@
429 */
430 if( xfer.nToken==2
431 && blob_eq(&xfer.aToken[0], "igot")
432 && blob_is_uuid(&xfer.aToken[1])
433 ){
434 if( isPush ){
435 rid_from_uuid(&xfer.aToken[1], 1);
436 }
437 }else
438
439
440 /* leaf UUID
441 **
442 ** Client announces that it has a particular manifest. If
443 ** the server has children of this leaf, then send those
444 ** children back to the client. If the server lacks this
445 ** leaf, request it.
446 */
447 if( xfer.nToken==2
448 && blob_eq(&xfer.aToken[0], "leaf")
449 && blob_is_uuid(&xfer.aToken[1])
450 ){
451 int rid = rid_from_uuid(&xfer.aToken[1], 0);
452 if( isPull && rid ){
453 leaf_response(&xfer, rid);
454 }
455 if( isPush && !rid ){
456 content_put(0, blob_str(&xfer.aToken[1]), 0);
457 }
458 }else
459
460 /* pull SERVERCODE PROJECTCODE
461 ** push SERVERCODE PROJECTCODE
462 **
@@ -619,40 +625,46 @@
625 assert( pushFlag || pullFlag || cloneFlag );
626 assert( !g.urlIsFile ); /* This only works for networking */
627
628 db_begin_transaction();
629 db_multi_exec(
630 "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
631 );
632 blobarray_zero(xfer.aToken, count(xfer.aToken));
633 blob_zero(&send);
634 blob_zero(&recv);
635 blob_zero(&xfer.err);
636 blob_zero(&xfer.line);
637
638 /*
639 ** Always begin with a clone, pull, or push message
640 */
641 if( cloneFlag ){
642 blob_appendf(&send, "clone\n");
643 pushFlag = 0;
644 pullFlag = 0;
645 nMsg++;
646 }else if( pullFlag ){
647 blob_appendf(&send, "pull %s %s\n", zSCode, zPCode);
648 nMsg++;
649 }
650 if( pushFlag ){
651 blob_appendf(&send, "push %s %s\n", zSCode, zPCode);
652 nMsg++;
653 }
654
655
656 while( go ){
657 int newPhantom = 0;
658
659 /* Generate gimme messages for phantoms and leaf messages
660 ** for all leaves.
661 */
662 if( pullFlag ){
 
 
 
 
 
 
 
 
663 request_phantoms(&xfer);
664 send_leaves(&xfer);
665 }
 
 
 
 
666
667 /* Exchange messages with the server */
668 nFileSend = xfer.nFileSent + xfer.nDeltaSent;
669 printf("Send: %10d bytes, %3d messages, %3d files (%d+%d)\n",
670 blob_size(&send), nMsg+xfer.nGimmeSent+xfer.nIGotSent,
@@ -661,10 +673,23 @@
673 xfer.nFileSent = 0;
674 xfer.nDeltaSent = 0;
675 xfer.nGimmeSent = 0;
676 http_exchange(&send, &recv);
677 blob_reset(&send);
678
679 /* Begin constructing the next message (which might never be
680 ** sent) by beginning with the pull or push messages
681 */
682 if( pullFlag ){
683 blob_appendf(&send, "pull %s %s\n", zSCode, zPCode);
684 nMsg++;
685 }
686 if( pushFlag ){
687 blob_appendf(&send, "push %s %s\n", zSCode, zPCode);
688 nMsg++;
689 }
690
691
692 /* Process the reply that came back from the server */
693 while( blob_line(&recv, &xfer.line) ){
694 if( blob_buffer(&xfer.line)[0]=='#' ){
695 continue;
@@ -706,10 +731,11 @@
731 nMsg++;
732 if( pullFlag ){
733 if( !db_exists("SELECT 1 FROM blob WHERE uuid='%b' AND size>=0",
734 &xfer.aToken[1]) ){
735 content_put(0, blob_str(&xfer.aToken[1]), 0);
736 newPhantom = 1;
737 }
738 }
739 }else
740
741
@@ -720,13 +746,17 @@
746 if( xfer.nToken==2
747 && blob_eq(&xfer.aToken[0], "leaf")
748 && blob_is_uuid(&xfer.aToken[1])
749 ){
750 nMsg++;
751 int rid = rid_from_uuid(&xfer.aToken[1], 0);
752 if( pushFlag && rid ){
753 leaf_response(&xfer, rid);
754 }
755 if( pullFlag && rid==0 ){
756 content_put(0, blob_str(&xfer.aToken[1]), 0);
757 newPhantom = 1;
758 }
759 }else
760
761
762 /* push SERVERCODE PRODUCTCODE
@@ -747,10 +777,12 @@
777 zPCode = mprintf("%b", &xfer.aToken[2]);
778 db_set("project-code", zPCode);
779 }
780 cloneFlag = 0;
781 pullFlag = 1;
782 blob_appendf(&send, "pull %s %s\n", zSCode, zPCode);
783 nMsg++;
784 }else
785
786 /* error MESSAGE
787 **
788 ** Report an error
@@ -783,14 +815,16 @@
815 xfer.nDeltaRcvd = 0;
816 xfer.nDanglingFile = 0;
817 nCycle++;
818 go = 0;
819
820 /* If we have received one or more files on this cycle or if
821 ** we have received information that has caused us to create
822 ** new phantoms and we have one or more phantoms, then go for
823 ** another round
824 */
825 if( (xfer.nFileRcvd+xfer.nDeltaRcvd+xfer.nDanglingFile>0 || newPhantom)
826 && db_exists("SELECT 1 FROM phantom")
827 ){
828 go = 1;
829 }
830
831

Keyboard Shortcuts

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