←⌂

4 — Bad head

Created: 16 Feb 2026, Modified: 17 Feb 2026

Git / onedrive

“Corrupted” repository inside onedrive

This morning when I wanted to commit to this site’s repository I was greeted with the dreaded fatal: bad object HEAD.

[pm@pos plu5.github.io]$ git status
fatal: bad object HEAD
[pm@pos plu5.github.io]$ git branch
  dev
* master
[pm@pos plu5.github.io]$ cat .git/HEAD
ref: refs/heads/master
[pm@pos plu5.github.io]$ git log
fatal: bad object HEAD
[pm@pos plu5.github.io]$ git fsck --full
Checking ref database: 100% (1/1), done.
bad sha1 file: .git/objects/09/8be16a9bc6de71ce453b9fd00b1b82869343be-pos-safeBackup-0001
bad sha1 file: .git/objects/d2/ec924ed3c50d2407d9918c8f5c282b26d13a91-pos-safeBackup-0001
Checking object directories: 100% (256/256), done.
Checking objects: 100% (116/116), done.
error: refs/heads/master: invalid sha1 pointer 098be16a9bc6de71ce453b9fd00b1b82869343be
error: refs/remotes/origin/HEAD: invalid sha1 pointer 098be16a9bc6de71ce453b9fd00b1b82869343be
error: refs/remotes/origin/master: invalid sha1 pointer 098be16a9bc6de71ce453b9fd00b1b82869343be
error: HEAD: invalid sha1 pointer 098be16a9bc6de71ce453b9fd00b1b82869343be
error: HEAD: invalid reflog entry d2ec924ed3c50d2407d9918c8f5c282b26d13a91
error: HEAD: invalid reflog entry d2ec924ed3c50d2407d9918c8f5c282b26d13a91
error: HEAD: invalid reflog entry 098be16a9bc6de71ce453b9fd00b1b82869343be
error: refs/heads/master: invalid reflog entry d2ec924ed3c50d2407d9918c8f5c282b26d13a91
error: refs/heads/master: invalid reflog entry d2ec924ed3c50d2407d9918c8f5c282b26d13a91
error: refs/heads/master: invalid reflog entry 098be16a9bc6de71ce453b9fd00b1b82869343be
error: refs/remotes/origin/master: invalid reflog entry 098be16a9bc6de71ce453b9fd00b1b82869343be
dangling blob bb58b7aea9aa0846a8dfac153535829264c71de0
dangling blob cfb02a8ed2ee45fccd18fd309704e0804ea8aca9
dangling blob 7aa55addbcf5557af06acc97253b6f7d37f751ed

These safeBackup files look familiar; they are generated by onedrive. I think it is when a local file is not on remote and it has no record of its creation, so it renames it locally in order to “delete” it, I have also had it happen when it thinks remote is more recent but it will involve overwriting local changes, so it renames the existing file and downloads the remote one. I don’t have the log anymore to see what happened, because I guess it happened in a previous session without my notice. It seems to have happened yesterday when we had a lot of power outages.

Renaming these files back fixed it and the repository was back to normal. I first verified that the files do not exist:

[pm@pos plu5.github.io]$ file .git/objects/09/8be16a9bc6de71ce453b9fd00b1b82869343be
.git/objects/09/8be16a9bc6de71ce453b9fd00b1b82869343be: cannot open `.git/objects/09/8be16a9bc6de71ce453b9fd00b1b82869343be' (No such file or directory)
[pm@pos plu5.github.io]$ file .git/objects/d2/ec924ed3c50d2407d9918c8f5c282b26d13a91
.git/objects/d2/ec924ed3c50d2407d9918c8f5c282b26d13a91: cannot open `.git/objects/d2/ec924ed3c50d2407d9918c8f5c282b26d13a91' (No such file or directory)
[pm@pos plu5.github.io]$ t='.git/objects/09/8be16a9bc6de71ce453b9fd00b1b82869343be'; mv "${t}-pos-safeBackup-0001" "$t"
[pm@pos plu5.github.io]$ t='.git/objects/d2/ec924ed3c50d2407d9918c8f5c282b26d13a91'; mv "${t}-pos-safeBackup-0001" "$t"

It’s either a bad idea to put a git repository in onedrive or at least one should exclude .git, which I was aware of but didn’t want to do out of fear that related codepaths would be bugged some time in future; it is a repository maintained by just one person thanklessly, and often things break, sometimes due to Microsoft changing things on their end.

Another option is to use local_first, either run with --local-first or add local_first = "true" to ~/.config/onedrive/config, which I had also considered doing before but likewise was hesitant to diverge too much from default options.

Let’s see what it does with this value. If it doesn’t go into completely different codepaths, and is only used in a simple conditional when deciding which version of a file to prioritise, then it could be fairly risk-free.

It’s only used in one place, but it’s indeed two different codepaths for syncing with local first vs “normal sync process”.

Local first:

  1. performDatabaseConsistencyAndIntegrityCheck [A]
  2. if monitor, processInotifyEvents
  3. scanLocalFilesystemPathForNewData [B]
  4. if monitor, processInotifyEvents
  5. syncOneDriveAccountToLocalDisk [C]
  6. if monitor, processInotifyEvents
  7. if resync, set resync to false

Normal:

  1. syncOneDriveAccountToLocalDisk [C]
  2. if monitor, processInotifyEvents
  3. performDatabaseConsistencyAndIntegrityCheck [A]
  4. if monitor, processInotifyEvents
  5. if not download_only,
    1. scanLocalFilesystemPathForNewData [B]
    2. if monitor, processInotifyEvents
    3. if not force_children_scan,
      1. syncOneDriveAccountToLocalDisk [C]
      2. if monitor, processInotifyEvents
  6. if resync, set resync to false

but maybe since local-first is simpler, it’s less risky overall? All the functions that it calls are in the normal path too.

but maybe it’s better to stay mirrored with remote,

[no, it is mirrored either way, only changes the order ^]

I go with “indecision”

I set it in the end.

To restart it:

  1. pkill onedrive
  2. verify in the log that it cleanly shut down
  3. onedrive -m > ~/OneDrive-log.txt 2>&1 & disown
  4. verify all is well in the log

2 and 4 optional out of an abundance of caution.

Dotfiles

generate-devlog

Created a function to create a devlog file easily. I create it like 4.md (number determined automatically) and rename later when I decide on a title.

braille.el

Erase

Finally introduced erasing, which was easy. It’s the same logic as drawing (see how placing down a dot works from devlog 1), except NOT+ANDing the bits instead of ORing the bits, i.e. d & ~dot-bit instead of d | dot-bit.

 (new-dot-value
  (if erase
      (if d (logand d (lognot dot-bit)) 0)
    (if d (logior d dot-bit) dot-bit)))

and now instead of doing (+ #x2800 new-dot-value) always, I do this to give the possibility to use space instead of the blank grid character:

 (c (if (and erase (= 0 new-dot-value))
        (braille-empty-char)
      (+ #x2800 new-dot-value)))

It’s probably redundant to check if erase here. If it’s 0, it must have been an erase.

Now it’s starting to get pretty fun.

 ⠀⠀⠀⠀⠀⣀⣀⣀⠤⠒⠒⠤⠤⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 ⠀⠀⡠⠒⠉⠀⠀⠀⠀⡀⠀⠀⡆⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 ⠀⢰⠁⠀⠀⠀⠀⠀⠀⢸⠀⠀⡇⠀⢠⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 ⠀⡎⢄⠀⡆⢀⠀⣀⡀⢸⡄⠀⠁⠠⠊⢠⣾⠏⠹⣗⡄⠀⠀⠀⠀⢠⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 ⢰⠁⠘⢼⠤⣧⡣⠋⠉⠈⠈⠀⠂⠀⡔⣾⢃⣴⣼⣿⡹⣆⠠⣀⠀⠀⡇⠀⢀⣀⠀⠀⠀⠀⠀⠀
 ⡎⠀⣀⠇⠀⠀⠀⠀⣀⠀⠀⠀⠀⢀⣿⣿⣾⡾⠿⠻⣷⣏⠆⠀⠀⠀⠃⠄⢨⠈⠆⠀⠀⠀⠀⠀
 ⠱⣀⠈⠀⠀⢀⠤⠊⠁⠀⠀⠀⢀⢎⣽⣿⣧⣤⣤⣾⣿⢿⣀⡀⠀⠀⠀⠸⠀⠀⠈⠒⠦⢤⣄⡀
 ⠀⠀⠉⠉⠉⠁⠀⠀⠀⠀⢀⣔⣿⡟⠿⢿⣾⣿⢟⣿⣿⡎⢆⠑⠀⠀⠀⠀⠀⠀⡆⠀⠀⠀⠀⠉
 ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⠿⠛⢡⣾⠟⡕⠁⠈⡆⠀⠀⠀⠀⢀⠰⠁⠀⠀⠀⠀⠀
 ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⡇⣹⠽⡯⠚⠀⠀⠀⠁⠐⠐⠀⠀⠈⠀⠀⠀⠀⠀⠀⠀
 ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠉⢇⢠⠰⠐⢣⠀⡀⠀⢠⠀⠀⠀⠀⠀⠀⠀⠀⠀
 ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠹⡀⠐⡇⠀⠀⢆⣸⠀⠀⠀⠀⠀⠀⠀⠀⠀
 ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠇⠀⠀⠂⠀⠀⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀
 ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀

I also discovered while doing this that 0 is truthy in elisp.

(and 0 (message "hi"))  ; → "hi"

It’s redundant to handle the case d (braille delta) is 0 in the if, but doesn’t affect the calculation here since doing 0 | dot-bit is equivalent to just dot-bit, and 0 & ~dot-bit is equivalent to just 0.

Canvas convenience functions and keybindings

I wanted to bind the function to create a canvas to C-c n

It reminds me of when I had first started to use Emacs well over a decade ago, the pain when you first try to bind a key. We pretty quickly learn to just use kbd. Here it doesn’t work, possibly because I’m quoting the list? I don’t really want to have to change to wrapping in list and cons.

(define-minor-mode braille-mode
  "Toggles global braille-mode.
Lets you draw in the buffer with braille dots using your mouse."
  :global t
  :lighter " ⣿"
  :keymap
  '(([down-mouse-1] . braille-mouse-draw)
    ([mouse-1] . ignore)
    ([C-down-mouse-1] . braille-mouse-erase)
    ([C-mouse-1] . ignore)
    ([M-mouse-1] . undo)
    ([M-down-mouse-1] . ignore)
    ([M-S-mouse-1] . redo)
    ([(control ?c) ?n] . braille-create-canvas-at-point)
    ([(control ?c) (control ?n)] . braille-create-canvas-at-point-without-asking)))

The canvas creation functions now use a defcustom for the default size:

(defcustom braille-default-canvas-size "50x20"
  "Canvas size in the format WxH to use as default dimensions.
Used in `braille-create-canvas-at-point-without-asking' and in
`braille-create-canvas-at-point' as the default value.
The unit of W and H is number of characters."
  :type 'string
  :group 'braille)

Improper for it to be a string instead of a cons or list? Meh, it’s easier that way. And I’ve got a clear user-error when the format is wrong so it shouldn’t confuse anyone too much.

braille-create-canvas-at-point-without-asking does just what it says on the tin. The implementation is just calling braille-create-canvas-at-point with the default size, so we can create canvases (somewhat) conveniently with C-c C-n without being nagged about the size each time.

When I first started working on braille.el, the default size I used was 40x10, now we’ve upgraded to 50x20 it seems… I do not want the default to be too large to not have too many users for whom it is broken by default because they use word wrap + their window width is too narrow to avoid the canvas wrapping.

Convert spacing

And lastly, a function to convert the spacing in region between actual space and braille empty grid, for which I consulted this 2019 SE answer by Drew. It decides whether it needs to convert to or from spaces based on whether there is a braille blank grid character in the region.

(defun braille-convert-spacing-in-region (beg end)
  "Convert characters in region from braille blank grid to space or vice versa.
If a braille blank grid character is found in region, convert braille
blank grid characters to spaces, otherwise convert spaces to braille
blank grid characters."
  (interactive "*r")
  (save-restriction
    (narrow-to-region beg end)
    (let* ((grd (char-to-string braille-base))
           (spc " ")
           (old (if (save-excursion (search-forward grd nil t 1)) grd spc))
           (new (if (eq old grd) spc grd)))
      (goto-char (point-min))
      (while (search-forward old nil t 1)
        (replace-match new))
      (message "Converted [%s] to [%s] in region %s to %s" old new beg end))))

When there is no region, it seems to convert from point until the end of the buffer.

Part of me is concerned because there is no easy way to tell whether your spaces are in fact braille blank grids (describe-char, but the user needs to realise something’s off with their spaces first!). I hope the message will be sufficient warning. And the reason I used square brackets and no quotes is no matter which type of quotes I use it shows up as ‘ ‘ which I dislike (two left single quotation marks).

Now that I think about it, since the user can’t easily tell the difference between the characters, we need to change the message to something more descriptive:

 (message "Converted [%s] (%s) to [%s] (%s) in region %s to %s" old
          (if (eq old spc) "space" "braille blank grid") new
          (if (eq new spc) "space" "braille blank grid") beg end)

but now maybe the function needs to be rewritten to do it less stupidly ^^”

Also adding something like this to the create canvas function so that it’s clear which spacing character was used.

 (message "Created %s canvas with %s character" size
          (if (eq c ?\s) "space" "braille blank grid"))

I guess the more proper way is to write a function that gives human description from a given character, or if it doesn’t know it return it as is, so that we don’t report wrongly that it’s braille blank grid if somehow it isn’t.

Hey there is actually a function char-to-name. But it seems to be pretty recent so maybe not worth depending on just for this.

char-to-name is a native-comp-function in ‘mule-cmds.el’.
(char-to-name CHAR)
Return the Unicode name for CHAR, if it has one, else nil.
Return nil if CHAR is not a character.
Probably introduced at or before Emacs version 30.1.

Just going to write:

(defun braille-char-name-or-char (c)
  "Return description for space and blank braille grid if c is one of them.
Otherwise, return c as a string."
  (cond
   ((equal c ?\s) "space")
   ((equal c braille-base) "blank braille grid")
   (t (char-to-string c))))

Note

… and here I got stuck for a while because I tried to use eq and couldn’t understand why (eq " " " ") → nil. eq is for identity, equal equality.

Canvas message:

 (message "Created %s canvas with %s character" size
          (braille-char-name-or-char c))

Conversion message:

 (message "Converted [%s] (%s) to [%s] (%s) in region %s to %s"
          old (braille-char-name-or-char (string-to-char old))
          new (braille-char-name-or-char (string-to-char new)) beg end)

yes now there is the extra complication that the conversion function has to use strings so we have to convert them to characters first to get the name. but that should be good enough surely.™

Improve this page / Leave a message.

←⌂ / ←3 — Braille display concerns /