-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathDEVELOPMENT
1242 lines (1061 loc) Β· 61.1 KB
/
DEVELOPMENT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
+--+
|ki|
+--+
Python package `ki` is a command-line interface for the version control and
editing of ``.anki2`` collections as git repositories of markdown files.
Rather than providing an interactive UI like the Anki desktop client, `ki` aims
to allow natural editing *in the filesystem*.
In general, the purpose of `ki` is to allow users to work on large, complex
Anki decks in exactly the same way they work on large, complex software
projects.
TODO
----
- Add implementations to test function sigs in `tests/test_ki.py`. (DONE)
- Add CLI tests that actually call the subcommands. (DONE)
- Add `shell-doctest` tests for CLI subcommands. (NO)
- Add implementations of `clone`, `pull`, and `push`. (DONE)
- Make sure `push` acquires lock on SQLite3 file before overwriting. (DONE)
- Make clone work on empty directories to match `git` behavior. (DONE)
- Test utilities. (DONE)
- Handle file deletion. (DONE)
- Try doing one file per deck, and see if this is faster. (NO)
- Handle merge conflicts better. (DONE)
- Test that uncommitted changes do not get pushed. (DONE)
- Test that repeated push-pull on up-to-date repo is no-op/idempotent. (DONE)
- Test hashes and backups get committed or ignored. (DONE)
- Test that parser handles leading '#' characters (currently doesn't).
- Test warnings are captured.
- Test that the existence of a backup file with an up-to-date checksum doesn't
prevent us from pushing.
- Remove commit call that deletes .ki/initial (DONE)
- Test that the sequence PULL -> EDIT -> PUSH -> PULL yields a no-op on the
last PULL.
- Test ``get_fetch_head_sha``. (NO)
- Test that .ki/ is in gitignore, and doesn't get committed.
- Test that CLONE -> EDIT -> COMMIT -> PULL -> PUSH results in a nontrivial
PUSH.
- Test that CLONE -> PULL NONTRIVIAL -> PUSH results in trivial PUSH.
- Test that ephemeral clones are maximally shallow.
- Test that the 'Initial commit' created on nontrivial PUSH ops is squashed
into the merge commit or at least given a better name (renaming is probably
the better option, because merges may not always succeed).
- Write a 'Getting started' section with an example repository on github that
users can clone and pull into their collections with ki (requires submodule
support, which is not yet implemented).
- Test that cloning deck repos from github and adding as submodules works as
expected.
- Write a finite state machine test with the following operations as rules:
1. clone
2. pull
3. push
4. edit a field
5. add a note
6. delete a note
7. the three above operations, but using the anki API
8. changed the note type of a card
9. add an image
10. all the above operations, but with subdecks
- Separate tests into quick and slow. (NO)
- Try working with a Japanese language deck. (DONE)
- Test that the media folder isn't created outside the repository. (NO)
- Test that actual merge conflicts are handled nicely.
- Test that ki cleans up its tempdirs unless a debug flag is set. There is no
need to backup the ephemeral repos because those commits can be checked-out.
- Use terminology other than FETCH_HEAD, because this no longer represents the
git ref FETCH_HEAD. (DONE)
- Make notemap a namedtuple, dataclass, or Namespace (figure out which is
best). (DONE)
- Test warnings are displayed for duplicate notes.
- Test pure note deletion doesn't raise deprecation warning.
- Test that push does not work if there are uncommitted changes. (NO)
- Test that notes get renamed and their nids modified when being added.
- Test that pull squashes initial commit and merge if and only if there were no
merge conflicts.
- Test that deleting individual notes from files containing multiple notes
works as intended. (NO)
- Test that push works when adding notes and with changes to the working tree
or untracked files (uncommitted stuff). (DONE)
- Test that clone cleans up directory if it aborts due to invalid DB path.
- Test the media folder is cloned, and that a warning is printed if it isn't
found.
- Add a section in docs that explains what a `ki` repository is.
- Try pdoc instead of pdoc3.
- Convert path manipulation code to pathlib. (DONE)
- Profile code. (DONE)
- Use NestedText instead of markdown. (NO)
- Add asciinema gif for simple workflow. (DONE)
- Test that strange targetdirs are handled well.
- Test branches work as expected.
- Test that `apy` parser handles decks that don't exist (it doesn't, currrently).
- Remove all beautiful soup usage. (DONE)
- Test decknames with leadings dots and resulting collisions from stripping
dots.
- Handle problem of notes with cards in distinct decks by symlinking notes,
making the choice of where the ground-truth note lives arbitrarily (perhaps
by choosing the deck with shortest name). (DONE)
- Test that bug is squashed: slugify filters all characters from filename
causes files to not be put in appropriate directory. (More likely without
allowing UTF-8). (DONE)
- Add this for html detection https://stackoverflow.com/a/70585604 (DONE)
- Do fieldnames have to be distinct within a model? (DONE)
- Consider writing a short vim plugin to fix syntax highlighting:
https://stackoverflow.com/a/34645680
- Fix the following issue: since the location of card files within a ki
repository has semantic significance, i.e. determines which deck they are in,
it is important that we preserve the information of where symlinks live.
However, when we are pushing, it is possible that because of the
`Path.resolve()` calls littered everywhere, we are instead seeing that all
cards are in a single deck, because they all resolve to the location of the
note file corresponding to the first cid processed in the clone operation. A
test should be written for this.
- There is a long time before any output when adding japanese-core-2000 as a
submodule, committing, and then pushing. (DONE)
- Test that the stuff that checks out HEAD to avoid pushing working changes
does so for the submodules as well. What if submodules have working changes?
Does this push them? (DONE)
- Investigate faster way to create ephemeral directories:
https://stackoverflow.com/questions/28157815/make-local-duplicate-of-local-git-repo
(DONE)
- Add a full suite of submodule tests. (DONE)
- Test that adding completely foreign models works as expected. (DONE)
- Add tests for handling of Anki's `Default` deck. Anki does some weird magic
with the `Default` deck. If it has subdecks, but no cards directly belong to
`Default`, then it appears to be removed from the tree. Ki should remove it
as well during pushes if necessary, so that after a `pull()` or `push()` the
directory tree structure *always* matches the deck tree structure. See e.g.
`fn hide_default_deck` and `fn remove_single_deck` in
`rslib/src/decks/tree.rs`.
- Test that the directory within which a card resides is meaningful. So moving
a card should change its deck. This will require removing the `deck` field
from the grammar, which is good. We can just replace spaces with hyphens in
any decks we upload to github, and it will still look reasonably nice. (DONE)
- Make DB error shorter. Should just say 'database is locked'.
- Refactor error message display logic. (DONE)
- Test that models are not duplicated unnecessarily (see bug #14). (DONE)
- Start using GitHub issues.
- Serious bug: push logic is broken. The following sequence is an MWE:
CLONE
delete a note
PUSH
add note back inside anki
PULL
delete note again
PUSH
Before this last PUSH, the note exists inside anki. We've deleted it in our
repo, and committed the deletion, so when we push, the note should be gone
inside anki. But this is not what happens. Instead, it says `up to date`.
This is because `diff_repos()` takes the diff between the commit of our last
PUSH, at which point the note had been deleted, and the current HEAD, at
which point the note is *also* deleted. This is WRONG. What is the actual
diff we want? We want the diff between the REMOTE and the HEAD, and the
REMOTE is just the Anki collection as it sits on disk. Updates may have been
made to it since the last PUSH, and if we pull those changes, and then revert
them, these reversions will not "take", because there is no change between
the state at last PUSH and the state at current HEAD. And updates aren't
rejected because we just PULLed. So in order for the diff to be correct, we
must get a temp repo reset to LAST PUSH COMMIT REF, and then pull the remote
into that. Then our temp repo will have exactly the same state as the remote,
and we can diff that against the current HEAD. (DONE)
- Test that '.ki/no_submodules_tree' does not, itself, have a '.ki/' directory.
(NO)
- Test that deleting a deck directory deletes the deck, i.e. there isn't an
empty folder left in the Anki client after pushing.
- Test that changing models but leaving cards untouched doesn't result in 'up
to date.'.
- Test that deleting notetypes from models file actually removes them.
- Consider making each model a file, and then making `_models` a directory.
Then we can safely symlink all the models in deck directories to the
top-level `_models/` directory, and then just resolve the symlinks when we
need to push a submodule to github.
- There is a bug where pushes are not no-ops when they should be as a result of
having submodules whose repository name is different from the deck name.
Specifically, if you have add a submodule called `a` but its top-level deck
is called `b`, then once pushed, every push will be nontrivial, because it
will look like a path rename. (DONE)
- Ki fails to pull deletions. If we:
CLONE
Delete a note in Anki
PULL
The note is still there in the repo.
The problem is that `git merge --strategy-option=theirs` doesn't actually use
`theirs` for everything. To do that, we'd need `git merge --strategy=theirs`,
but there is no such thing. See
https://stackoverflow.com/questions/173919/is-there-a-theirs-version-of-git-merge-s-ours
for a workaround.
- Add test for symlink handling.
- Tags should be on separate lines so that git can merge them nicely. See
https://github.com/ankitects/anki/issues/1018#issuecomment-1153998635
- Try to see if there is a way to test if non-notetype changes result in a
syncable database.
- Test that media is deduplicated.
- Write some more comprehensive unit tests for `is_anki_note()`.
16-April-2022
-------------
Merge write_deck() and write_decks(). (DONE)
19-April-2022
-------------
Remove ki qualified import from tests to make code copying easier. (DONE)
Finish refactoring tests. (DONE)
20-April-2022
-------------
Move types, errors, maybes, and generic functions into separate files. (DONE)
Fix bug where reassigned nid differs from old nid in commit msg. (DONE)
Fix imports in tests to reflect new repository structure. (DONE)
Remove all duplicated constant declarations. (DONE)
Split tests into integration/unit. (DONE)
26-April-2022
-------------
Write tests for all ki functions. (DONE)
28-April-2022
-------------
Achieve 100% test coverage. (DONE)
Make sure all warnings and errors are actually displayed at the command line. (DONE)
Media writes. (DONE)
29-April-2022
-------------
Create a `.media` directory recursively in each deck directory. (DONE)
Fix logic in `write_repository()` so that we use the deck tree to construct a
list of relevant colnotes instead of using a global list of all nids to
construct a mapping from deck names to lists of colnotes. (DONE)
Add test for above that checks that a `models.json` file and a `.media`
directory are created for every deck, even if all its cards are contained in
subdecks.
Add media files back to collection in during `push()` calls. (DONE)
Remove submodule update calls. (DONE)
Add test to make sure that pushing new media files that have the same filenames
as existing media files in the collection doesn't break paths in note fields.
Persistent note ids across pulls.
Triage
------
Ghostwrite hypothesis tests (NO)
Command line output (DONE)
Test backlog (DONE)
Submodules (DONE)
Modifying note types (DONE)
Changing note types (DONE)
Pulling in remote changes in submodules without losing review data
Generating HTML
Media (DONE)
Distillation
------------
The complexity of this program has gotten out of hand. An effort should be made
to distill functionality into the smallest form possible. No custom exceptions,
no functional or maybe modules, no pretty output, no halo, no profiling,
nothing. No checking, no safety, nothing. Paths are always assumed to exist and
targets are always assumed to be valid.
Subdeck duplication
-------------------
Suppose we have a deck A. We clone our collection. Then, from inside Anki, we
create a new deck B, and move A *inside* B. Then we pull.
We expect to see A *moved* inside B, and only one top-level deck, which is B.
Instead, we have a copy of A at the top-level, *and* a copy of A inside B.
The current pull architecture, ignoring submodules, is that allowing unrelated
histories, we merge a fresh clone of the database `remote_repo` into a checkout
of the repository at the revision of the last successful `ki push`, resolving
merge conflicts in favor of `theirs`, i.e. the remote.
The issue with this is that because we have unrelated histories, `git` cannot
'see' that the A deck at top-level that doesn't exist in the remote repo is the
same deck that now appears inside B. It assumes these are distinct entities,
and keeps them both during the merge.
The solution is to not perform a merge at all. Instead, we annihilate all the
contents of the staging repository, which is checked-out at revision of last
`ki push`, and then replace those contents with the raw contents of the remote
repo. Then we commit those. These will simply look like edits to `git`, and it
cannot cause a merge conflict because we are not merging anything.
Hardening media symlinks
------------------------
Do this in the relevant `_media/` directory:
shopt -s globstar && sed -i '' **/*
Media deduplication
-------------------
Currently, a copy of every media file is put in each ancestor of each deck
containing a card where that note appears. It turns out that this is very
wasteful, and really, we should symlink all the descendant media files to the
`_media/` directory in the top-level, i.e. the root of the repository. The only
reason why these copies in each deck exist is so that you can turn a deck into
a git submodule and then push it to GitHub without having to manually mess
around with making sure the right media is included. Unfortunately, Git will
not follow symlinks, and we'd need a pre-commit hook to make it do that (and it
will be difficult to write one that is cross-platform).
Alternatively, we can use hard links. These can be created on all platforms
with `os.link()`. And Git will follow hard links, which is good.
On second thought, the above will not work, because we don't want a ton of hard
links in submodules, because we will still get duplicate files. So what we
really want is hard links in the root of whatever submodule we're pushing, and
symlinks in all its descendants. The only way is to symlink all media to the
media directory of that deck's immediate parent, and then have these nested
symlinks point all the way up the tree to the root media directory. Then we
will need a way to `harden` a deck directory to prepare convert it into a
submodule/collaborative deck. This will just convert all the symlinks in the
root directory of that deck into hardlinks, preserving all the lower symlinks.
This may need to be another command, called `ki harden`.
Symlink naming
--------------
Only one symlink should be created per deck. Also, the card name should have
the ordinal tacked-on if it's a cloze-type notetype. Observe in the output
below that this is not what happens:
(anki) user@computer:~$ ki clone ~/.local/share/Anki2/User\ 2/collection.anki2
Cloning.
Found .anki2 file at /home/user/.local/share/Anki2/User 2/collection.anki2
Computed md5sum: ea94e3fbbfa8a239f8131e8965492f2c
Cloning into /home/user/collection...
Notes: 100%|βββββββββββββββββββββββββββββββββββββ| 1/1 [00:00<00:00, 446.35it/s]
HTML: 100%|ββββββββββββββββββββββββββββββββββββββ| 1/1 [00:00<00:00, 275.71it/s]
Media: 100%|βββββββββββββββββββββββββββββββββββββ| 1/1 [00:00<00:00, 538.70it/s]
Decks: 0%| | 0/5 [00:00<?, ?it/s]2022-06-15 07:38:31.207 | DEBUG | ki:write_decks:1237 - Pointing link /home/user/collection/Default/B/sample_cloze-ol.md -> ../../Default/A/sample.md
2022-06-15 07:38:31.208 | DEBUG | ki:write_decks:1237 - Pointing link /home/user/collection/Default/B/sample_cloze-ol_1.md -> ../../Default/A/sample.md
2022-06-15 07:38:31.209 | DEBUG | ki:write_decks:1237 - Pointing link /home/user/collection/Default/C/sample_cloze-ol.md -> ../../Default/A/sample.md
2022-06-15 07:38:31.209 | DEBUG | ki:write_decks:1237 - Pointing link /home/user/collection/Default/C/sample_cloze-ol_1.md -> ../../Default/A/sample.md
2022-06-15 07:38:31.209 | DEBUG | ki:write_decks:1237 - Pointing link /home/user/collection/Default/C/sample_cloze-ol_2.md -> ../../Default/A/sample.md
2022-06-15 07:38:31.210 | DEBUG | ki:write_decks:1237 - Pointing link /home/user/collection/Default/C/sample_cloze-ol_3.md -> ../../Default/A/sample.md
2022-06-15 07:38:31.210 | DEBUG | ki:write_decks:1237 - Pointing link /home/user/collection/Default/C/sample_cloze-ol_4.md -> ../../Default/A/sample.md
Decks: 100%|βββββββββββββββββββββββββββββββββββββ| 5/5 [00:00<00:00, 685.57it/s]
Wrote md5sum to /home/user/collection/.ki/hashes
Done.
The collection cloned above has been adding for testing purposes. See
`tests/data/collections/symlinks.anki2`.
Mapping database changes to submodules
--------------------------------------
To fix #18, one option is to use git diff to save a patch for the diff from
HEAD~1 to HEAD. Then for each extant submodule, filter the patches relevant to
that repo, ltrim the paths so they begin at the root of the submodule, and
apply them to the submodules directly. If submodules have been renamed since
the last push, then we can construct a map to the new names. The cases we must
treat are directories that have been converted into submodules, submodules that
have been converted into directories, and submodules that have been renamed. As
a special case of the second, we have submodules that have been deleted. This
is the most common use case. Most users will not be creating shared decks.
Git's default behavior works well for this case. For the case of converting
directories into submodules, we simply check if any extant submodules have the
exact same path as a directory with changes. If so, we use patches. This case
is covered by the fix described above. If submodules have been renamed, well,
we only worry about this case, actually we choose to not worry about this case.
Git's default behavior will be to do whatever it does when you pull changes to
a directory which has been renamed. We should prioritize using ort over
subtree.
To fix the 'not our ref' error during submodule fetch, we must do the
following:
cd <sm>/ && git log --oneline --graph && git branch temp && \
git checkout main && git merge --strategy-option=theirs temp
Won't fix
---------
pdoc3 doesn't generate docstrings for click commands
Clone doesn't work on ankiweb deck URLs
Comments aren't supported in notes
Implicitly adding new notetypes is not allowed, currently they cannot be added
from ki at all, and in the future it should probably require explicitly
creating a template/model file
Can't map back from HTML to plaintext/markdown while preserving changes made in
Anki. Editing the files directly is not the main use case. Nice serialization
and ability to put notes on github is. Mapping back from HTML is impossible
in the general case, and it adds needless complexity.
Pulling remote changes from GitHub decks while preserving review data
---------------------------------------------------------------------
Consider the following scenario:
- User A clones their collection into a ki repository. All the nids in the note
files are the nids generated by their Anki installation.
- User A converts their `C* Algebras` deck into a submodule in their
repository, and pushes this repo to GitHub.
- User B clones their collection, and then clones User A's `C-star-algebras`
GitHub repo into their ki repository as a submodule. New nids are generated
for all the notes in this deck, since User B's Anki installation does not
recognize the nids generated by User A's Anki installation, which are still
listed in all the note files on GitHub. These new nids are committed to the
submodule by ki.
- User B studies these notes, accumulating review data.
- User A pushes commits to their `C-star-algebras` GitHub repo, which contain
important corrections to errata in the deck.
- User B wants these corrections, so they run `git pull` within their submodule
inside their ki repository. Their branch has diverged from the remote, since
ki automatically committed the nid reassignments. The merge likely succeeds
because the line where the nid is defined within each note will not have
changed within User A's local copy of the repository, since they're still
using the same nid for that note.
- User B corrects some errata in their local copy of the `C* Algebras` deck
from within Anki. They want to push their corrections back to User A's GitHub
repo. They make a pull request with their corrections, but the diff is
*HUGE*, because the nid for every single note has been changed. This is not
ideal, because they will have to manually go in and remove the nid changes,
perhaps with some git wizardry. In particular, they would have to construct a
branch without the nid reassignment commit.
After thinking about the problem at length, this does not seem to be a
critically high priority issue. Although annoying, it will not cause the
collaboration workflow to be prohibitively cumbersome.
It should still be fixed long-term, however. One possible solution is replacing
the `nid` field within the note grammar with a `uuid` field. There would exist
a `.ki/manifest.yaml` file, which would map uuids to nids. This manifest file
would be unique to each user/Anki installation, whereas the uuid for that
particular note would be somethat that is unique across all users studying that
note (e.g. if it resided in a collaborative deck hosted on GitHub). During a
clone operation, a uuid would be generated for each note, perhaps seeded with a
hash of the note's content. Then the manifest file would be generated, which,
as we mentioned above, would simply map uuids to nids. The uuids would live in
the note files, and then there would be no need to worry about different
people's nids when merging pull requests on GitHub. Everyone would have the
same uuid for a given note. We now have a very simple condition for when an nid
must be regenerated: this must be done whenever we parse a uuid from a note
that does not exist in the manifest yet.
On the deleted notes repo
-------------------------
This is only used to examine the diff between the last push commit ref and the
current HEAD. This diff is used to see which files were deleted, and to be able
to read these files even though they were removed from the repository. This is
done with some reset --hard magic. We want to read these deleted files to
figure out what nids were extant at the last push commit ref. But this is a
very bad way to do this. A simpler thing is to just store the nids that are
currently extant every time we push in a file called ``.ki/last_push_nids``.
Then we simply read these and take the diff against the currently extant nids,
and see which ones were deleted, and remove them with an ``a.col`` call.
This is slow though, because you must read every note on every ki command.
Better to use git to get the diffs, and to do that, we need to use a deleted
notes repo.
Deck names, field names, notetype names
---------------------------------------
ANKI SPECIAL CHARS: #/^
BAD FIELDNAME CHARS:
:
{
}
"
(leading and trailing whitespace and special chars)
See: notetype/fields.rs:53
BAD FIELDCONTENT CHARS (NOTE: This is HTML, so newlines are represented as <br> tags):
(ascii control)
(newline)
(tab)
See: notes/mod.rs:186
BAD DECKCOMPONENT CHARS:
(ascii control)
"
See: decks/name.rs:175
BAD TAG CHARS:
(ascii control)
"
(space)
(\u3000)
See: tags/register.rs:124
On pushing new notes
--------------------
We must use nids as the filename so that edits to fields don't require edits to
the filenames as well. The only other alternative so far is doing
one-file-per-deck.
So what happens when you add a new note?
- Create new md file.
- Add content, add an nid or leave it blank.
- Call push()
- Ki asks anki to generate a new nid for this note.
- Changed are pushed to collection.
- Ki stashes unstaged and staged files
(git stash -u --keep-index && git reset --hard HEAD).
- Ki renames file to reflect new nid and changes nid in file.
- If a new nid must be generated, we first delete the file containing
that note.
- Then we regenerate the file (may contain multiple notes) from a
representation (list of notemaps) saved during push() call.
- In order to regenerate the file, we must keep track of which
notes we generated new nids for.
- For these notes, we must edit the ``notemap`` to have the correct
``nid``, and then serialize all the notemaps for that file and
regenerate the markdown.
- The order of notes in a file is deterministic, and the filename
generated is the nid of the first note.
- Ki commits these changes.
- Ki recovers the stashed files (git stash pop).
So what happens when you modify an existing nid?
BAD THINGS! YOU LOSE YOUR REVIEW DATA!!
We'll add warning when the nid is generated not to mess with it.
If the user copies the text of a note and doesn't remove the ki-generated nid
stuff, then ki will use some variant of edit distance to figure out which is
the original.
If the user changes the nid of an existing note to something else, then on the
next push, ki will treat it like a new note and generate a new nid for it. The
review/learning data will be lost (can be recovered by reverting to an earlier
commit and pushing again).
We are effectively reimplementing functionality that git can already handle.
The `nid` is to anki what a `filename` is to git.
So the ``apy`` spec for a note looks like this:
# Note
nid: 1645010162168
model: Basic
tags:
markdown: false
## Front
a
## Back
b
And our modified spec looks similar to this:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@~~~ki~~~@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@ # WARNING: DO NOT EDIT DATA IN THIS SECTION!
@@@
@@@ nid: 1645010162168
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@~~~ki~~~@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
---
# Note
model: Basic
tags:
markdown: false
## Front
a
## Back
b
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Notes
-----
To debug gitpython commands:
export GIT_PYTHON_TRACE=full
Bugs
----
1. Collection is not open.
Issue was that we were trying to interact with anki note objects after
closing apy Anki object.
(anki39) user@host:~$ ki clone ~/.local/share/Anki2/User\ 1/collection.anki2
Traceback (most recent call last):
File "/home/user/conda/envs/anki39/bin/ki", line 33, in <module>
sys.exit(load_entry_point('ki', 'console_scripts', 'ki')())
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1128, in __call__
return self.main(*args, **kwargs)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1053, in main
rv = self.invoke(ctx)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1659, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1395, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 754, in invoke
return __callback(*args, **kwargs)
File "/home/user/pkgs/ki/ki/__init__.py", line 77, in clone
_clone(collection, directory)
File "/home/user/pkgs/ki/ki/__init__.py", line 130, in _clone
note_file.write(str(note))
File "/home/user/pkgs/ki/ki/note.py", line 18, in __repr__
apy_note_repr: str = super().__repr__()
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/apy/note.py", line 44, in __repr__
lines += [f'deck: {self.get_deck()}']
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/apy/note.py", line 318, in get_deck
return self.a.col.decks.name(self.n.cards()[0].did)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/anki/notes.py", line 123, in cards
return [self.col.getCard(id) for id in self.card_ids()]
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/anki/notes.py", line 126, in card_ids
return self.col.card_ids_of_note(self.id)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/anki/collection.py", line 390, in card_ids_of_note
return [CardId(id) for id in self._backend.cards_of_note(note_id)]
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/anki/_backend/generated.py", line 361, in cards_of_note
output.ParseFromString(self._run_command(2, 11, input))
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/anki/_backend/__init__.py", line 131, in _run_command
raise backend_exception_to_pylib(err)
anki.errors.InvalidInput: CollectionNotOpen
2. Remote not deleted.
(anki39) user@host:~/collection$ ki pull
/home/user/conda/envs/anki39/lib/python3.9/site-packages/bs4/__init__.py:337: MarkupResemblesLocatorWarning: "." looks like a directory name, not markup. You may want to open a file found in this directory and pass the filehandle into Beautiful Soup.
warnings.warn(
/home/user/conda/envs/anki39/lib/python3.9/site-packages/bs4/__init__.py:337: MarkupResemblesLocatorWarning: "/" looks like a directory name, not markup. You may want to open a file found in this directory and pass the filehandle into Beautiful Soup.
warnings.warn(
Traceback (most recent call last):
File "/home/user/conda/envs/anki39/bin/ki", line 33, in <module>
sys.exit(load_entry_point('ki', 'console_scripts', 'ki')())
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1128, in __call__
return self.main(*args, **kwargs)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1053, in main
rv = self.invoke(ctx)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1659, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1395, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 754, in invoke
return __callback(*args, **kwargs)
File "<@beartype(ki.pull) at 0x7f343f93adc0>", line 10, in pull
File "/home/user/pkgs/ki/ki/__init__.py", line 188, in pull
_ = repo.create_remote("origin", os.path.join(ephem, ".git"))
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/git/repo/base.py", line 458, in create_remote
return Remote.create(self, name, url, **kwargs)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/git/remote.py", line 680, in create
repo.git.remote(scmd, name, Git.polish_url(url), **kwargs)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/git/cmd.py", line 638, in <lambda>
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/git/cmd.py", line 1183, in _call_process
return self.execute(call, **exec_kwargs)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/git/cmd.py", line 983, in execute
raise GitCommandError(redacted_command, status, stderr_value, stdout_value)
git.exc.GitCommandError: Cmd('git') failed due to: exit code(3)
cmdline: git remote add origin /tmp/tmpuand3wz0/ki/remote/1625c6643169d7c6b7b8d13d654645d5/.git
stderr: 'error: remote origin already exists.'
3. Note names not generated in fixed order. Caused merge conflict for every note.
4. We loop over all note files 4 times, but we should not do this at all. We
should ask git which files have changed since the last pull, and only update
those.
5. KeyError in push:
Traceback (most recent call last):
File "/home/user/conda/envs/anki39/bin/ki", line 33, in <module>
sys.exit(load_entry_point('ki', 'console_scripts', 'ki')())
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1128, in __call__
return self.main(*args, **kwargs)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1053, in main
rv = self.invoke(ctx)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1659, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1395, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 754, in invoke
return __callback(*args, **kwargs)
File "<@beartype(ki.push) at 0x7f28fab90f70>", line 10, in push
File "/home/user/pkgs/ki/ki/__init__.py", line 299, in push
nid = int(notemap["nid"])
KeyError: 'nid'
6. Doesn't handle when users make up new note IDs:
# Add new file.
shutil.copyfile(NOTE_2, os.path.basename(NOTE_2))
# Commit.
os.chdir("../")
repo = git.Repo(REPODIR)
repo.git.add(all=True)
repo.index.commit("Added 'e'.")
# Push changes.
os.chdir(REPODIR)
> push(runner)
tests/test_ki.py:278:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
<@beartype(tests.test_ki.push) at 0x7fa4345f8a60>:30: in push
???
tests/test_ki.py:74: in push
res = runner.invoke(ki.ki, ["push"], standalone_mode=False, catch_exceptions=False)
../../conda/envs/anki39/lib/python3.9/site-packages/click/testing.py:408: in invoke
return_value = cli.main(args=args or (), prog_name=prog_name, **extra)
../../conda/envs/anki39/lib/python3.9/site-packages/click/core.py:1053: in main
rv = self.invoke(ctx)
../../conda/envs/anki39/lib/python3.9/site-packages/click/core.py:1659: in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
../../conda/envs/anki39/lib/python3.9/site-packages/click/core.py:1395: in invoke
return ctx.invoke(self.callback, **ctx.params)
../../conda/envs/anki39/lib/python3.9/site-packages/click/core.py:754: in invoke
return __callback(*args, **kwargs)
<@beartype(ki.push) at 0x7fa4345ea940>:10: in push
???
ki/__init__.py:273: in push
note: Note = Note(a, a.col.get_note(nid))
../../conda/envs/anki39/lib/python3.9/site-packages/anki/collection.py:332: in get_note
return Note(self, id=id)
../../conda/envs/anki39/lib/python3.9/site-packages/anki/notes.py:46: in __init__
self.load()
../../conda/envs/anki39/lib/python3.9/site-packages/anki/notes.py:52: in load
note = self.col._backend.get_note(self.id)
../../conda/envs/anki39/lib/python3.9/site-packages/anki/_backend/generated.py:327: in get_note
output.ParseFromString(self._run_command(2, 5, input))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <anki._backend.RustBackend object at 0x7fa43459ba90>, service = 2, method = 5, input = nid: 123412341234
def _run_command(self, service: int, method: int, input: Any) -> bytes:
input_bytes = input.SerializeToString()
try:
return self._backend.command(service, method, input_bytes)
except Exception as e:
err_bytes = bytes(e.args[0])
err = backend_pb2.BackendError()
err.ParseFromString(err_bytes)
> raise backend_exception_to_pylib(err)
E anki.errors.NotFoundError
7. Doesn't say 'up to date.' when pushing right after a nontrivial pull:
(anki39) user@host:~/collection$ ki pull
Found .anki2 file at '/home/user/.local/share/Anki2/test2/collection.anki2'
Computed md5sum: 3aebb4b7c59e24b8f000739c6f6cb72a
Wrote md5sum to '/tmp/tmphpeuc9t4/ki/remote/3aebb4b7c59e24b8f000739c6f6cb72a/.ki/hashes'
Cloning into '/tmp/tmphpeuc9t4/ki/remote/3aebb4b7c59e24b8f000739c6f6cb72a'...
100%|βββββββββββββββββββββββββββββββββ| 3/3 [00:00<00:00, 1698.10it/s]
Merge made by the 'ort' strategy.
note1645298206334.md | 11 +++++++++++
1 file changed, 11 insertions(+)
create mode 100644 note1645298206334.md
From /tmp/tmphpeuc9t4/ki/remote/3aebb4b7c59e24b8f000739c6f6cb72a/
* branch main -> FETCH_HEAD
* [new branch] main -> anki/main
Updating 986fee6..51c4d9c
Fast-forward
note1645298206334.md | 11 +++++++++++
1 file changed, 11 insertions(+)
create mode 100644 note1645298206334.md
From /tmp/tmp_699r57b/ki/local/3aebb4b7c59e24b8f000739c6f6cb72a/
* branch main -> FETCH_HEAD
* [new branch] main -> anki/main
(anki39) user@host:~/collection$ ls
note1645010162168.md note1645222430007.md note1645298206334.md
(anki39) user@host:~/collection$ ki push
ki push: nontrivial push.
100%|ββββββββββββββββββββββββββββββββββ| 2/2 [00:00<00:00, 621.75it/s]
Database was modified.
8. Unknown issue on pulling nontrivial change from live collection:
(anki39) user@host:~/collection$ ki pull
Found .anki2 file at '/home/user/.local/share/Anki2/test2/collection.anki2'
Computed md5sum: 658fa0b356262c2c42077bde3a526eda
Wrote md5sum to '/tmp/tmp13847e9i/ki/remote/658fa0b356262c2c42077bde3a526eda/.ki/hashes'
Cloning into '/tmp/tmp13847e9i/ki/remote/658fa0b356262c2c42077bde3a526eda'...
100%|βββββββββββββββββββββββββββββββββ| 4/4 [00:00<00:00, 2145.15it/s]
From /tmp/tmp13847e9i/ki/remote/658fa0b356262c2c42077bde3a526eda/
* branch main -> FETCH_HEAD
* [new branch] main -> anki/main
Your local changes to the following files would be overwritten by merge:
note1645010162168.md
Traceback (most recent call last):
File "/home/user/conda/envs/anki39/bin/ki", line 33, in <module>
sys.exit(load_entry_point('ki', 'console_scripts', 'ki')())
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1128, in __call__
return self.main(*args, **kwargs)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1053, in main
rv = self.invoke(ctx)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1659, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1395, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 754, in invoke
return __callback(*args, **kwargs)
File "<@beartype(ki.pull) at 0x7f09fc045ee0>", line 10, in pull
File "/home/user/pkgs/ki/ki/__init__.py", line 228, in pull
assert p.returncode == 0
AssertionError
9. Pull when Anki is open should print a prettier error message:
(anki39) user@host:~/collection$ ki pull
Traceback (most recent call last):
File "/home/user/conda/envs/anki39/bin/ki", line 33, in <module>
sys.exit(load_entry_point('ki', 'console_scripts', 'ki')())
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1128, in __call__
return self.main(*args, **kwargs)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1053, in main
rv = self.invoke(ctx)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1659, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1395, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 754, in invoke
return __callback(*args, **kwargs)
File "<@beartype(ki.pull) at 0x7ff2bea48ee0>", line 10, in pull
File "/home/user/pkgs/ki/ki/__init__.py", line 174, in pull
con = lock(collection)
File "<@beartype(ki.lock) at 0x7ff2bea55dc0>", line 30, in lock
File "/home/user/pkgs/ki/ki/__init__.py", line 538, in lock
con.execute("BEGIN EXCLUSIVE")
sqlite3.OperationalError: database is locked
10. Note parse errors should not be silent.
11. Deletion of 'anki' git remote and write to hashes file should be in a
'finally' block. In particular, they should happen if we get a merge conflict:
(anki39) user@host:~/collection$ ki pull
2022-02-20 14:55:14.448 | DEBUG | ki:pull:235 - fetch_head_sha: 74c98c22a54485fd6f7648592076dfd6c5ac1a4a
2022-02-20 14:55:14.453 | DEBUG | ki:pull:237 -
diff --git a/note1645010162168.md b/note1645010162168.md
index e6f8464..d924148 100644
--- a/note1645010162168.md
+++ b/note1645010162168.md
@@ -20,4 +20,4 @@ eee
888
999
999
-888
+111
2022-02-20 14:55:14.453 | DEBUG | ki:pull:238 -
Auto-merging note1645010162168.md
Merge made by the 'ort' strategy.
note1645010162168.md | 1 +
1 file changed, 1 insertion(+)
From /tmp/tmpm88m07j6/ki/remote/96d99f27d75f7715a71f26ed6f282b0c/
* branch main -> FETCH_HEAD
* [new branch] main -> anki/main
2022-02-20 14:55:14.488 | DEBUG | ki:pull:261 -
diff --git a/note1645010162168.md b/note1645010162168.md
index d924148..1b9c51b 100644
--- a/note1645010162168.md
+++ b/note1645010162168.md
@@ -21,3 +21,4 @@ eee
999
999
111
+222
2022-02-20 14:55:14.488 | DEBUG | ki:pull:262 -
Traceback (most recent call last):
File "/home/user/conda/envs/anki39/bin/ki", line 33, in <module>
sys.exit(load_entry_point('ki', 'console_scripts', 'ki')())
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1128, in __call__
return self.main(*args, **kwargs)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1053, in main
rv = self.invoke(ctx)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1659, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 1395, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/click/core.py", line 754, in invoke
return __callback(*args, **kwargs)
File "<@beartype(ki.pull) at 0x7f9e7dc9c160>", line 10, in pull
File "/home/user/pkgs/ki/ki/__init__.py", line 270, in pull
fetch_head_remote = repo.create_remote(REMOTE_NAME, fetch_head_remote_path)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/git/repo/base.py", line 458, in create_remote
return Remote.create(self, name, url, **kwargs)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/git/remote.py", line 680, in create
repo.git.remote(scmd, name, Git.polish_url(url), **kwargs)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/git/cmd.py", line 638, in <lambda>
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/git/cmd.py", line 1183, in _call_process
return self.execute(call, **exec_kwargs)
File "/home/user/conda/envs/anki39/lib/python3.9/site-packages/git/cmd.py", line 983, in execute
raise GitCommandError(redacted_command, status, stderr_value, stdout_value)
git.exc.GitCommandError: Cmd('git') failed due to: exit code(3)
cmdline: git remote add anki /tmp/tmpuglbuhux/ki/local/96d99f27d75f7715a71f26ed6f282b0c/.git
stderr: 'error: remote anki already exists.'
12. Nid reassignment in submodules doesn't get committed.
13. KeyError can be raised within `update_note()` when field names are wrong
for notetype.
self = <anki.notes.Note object at 0x7f45f510b0a0>, key = Text
def _field_index(self, key: str) -> int:
try:
> return self._fmap[key][0]
E KeyError: Text
../../conda/envs/anki/lib/python3.9/site-packages/anki/notes.py:150: KeyError
The above exception was the direct cause of the following exception:
capfd = <_pytest.capture.CaptureFixture object at 0x7f45f510bee0>
def test_display_fields_health_warning_catches_missing_clozes(capfd):
col = open_collection(get_col_file())
note = col.get_note(set(col.find_notes("")).pop())
field = "data"
fields = {"Text": field, "Back Extra": ""}
flatnote = FlatNote("title", 0, "Cloze", "Default", [], False, fields)
clz: ki.NotetypeDict = col.models.by_name("Cloze")
cloze: ki.Notetype = ki.parse_notetype_dict(clz)
notetype: ki.Notetype = ki.parse_notetype_dict(note.note_type())
> res: OkErr = ki.update_note(note, flatnote, notetype, cloze)
tests/test_ki.py:1310:
ki/safe.py:101: in decorated
result = func(*args, **kwargs)
<@beartype(ki.update_note) at 0x7f45f520c790>:88: in update_note
???
ki/__init__.py:1201: in update_note
note[key] = plain_to_html(field)
../../conda/envs/anki/lib/python3.9/site-packages/anki/notes.py:158: in __setitem__
self.fields[self._field_index(key)] = value
self = <anki.notes.Note object at 0x7f45f510b0a0>, key = Text
def _field_index(self, key: str) -> int:
try:
return self._fmap[key][0]
except Exception as exc:
> raise KeyError(key) from exc
E KeyError: Text
../../conda/envs/anki/lib/python3.9/site-packages/anki/notes.py:152: KeyError
14. This should indicate that Anki might just have been left open.
Unexpected SQLite3 error while attempting to acquire lock on file:
'/home/user/.local/share/Anki2/User 2/collection.anki2':
A 'sqlite3.DatabaseError' was raised with error message: 'database
is locked'. This may indicate that either the database file at the
location specified above is corrupted, or the config file at
'.ki/config' is pointing to the wrong location. (The latter may occur
in the unlikely event that the collection file in the Anki data
directory has been accidentally overwritten.)
15. Models are duplicated when they shouldn't be:
(Try hashing).
(anki) user@computer:~/collection/Default$ ki push
Pushing to '/home/user/.local/share/Anki2/User 2/collection.anki2'
Computed md5sum: 742bf0303a945bd41bf3f2fa40ff0e20