Skip to content

Instantly share code, notes, and snippets.

@urob
Last active March 29, 2024 23:18
Show Gist options
  • Save urob/68a1e206b2356a01b876ed02d3f542c7 to your computer and use it in GitHub Desktop.
Save urob/68a1e206b2356a01b876ed02d3f542c7 to your computer and use it in GitHub Desktop.
Maintaining a personal ZMK fork

A cookbook approach to maintaining a personal ZMK fork

This is a brief introduction to the tools needed to maintain a personal fork of ZMK (or QMK or really whatever). It covers:

  1. the initial setup
  2. updating your fork with the latest ZMK features
  3. merging PRs into your fork
  4. deleting PRs from your fork
  5. resetting your fork
  6. using your fork to compile with Github Actions
  7. appendix: glossary

Throughout I am using examples from ZMK but nothing is specific to ZMK (other than the compiling stuff). I am deliberately simplifying things, only covering what's absolutely essential for maintaining a personal ZMK fork for non-developers.1

Setup

First, create your personal fork of the official ZMK repository. To do so, navigate to https://github.com/zmkfirmware/zmk and click "fork" in the upper right corner. For now your fork lives online ("remotely") on Github at https://github.com/your_username/zmk. For example mine is https://github.com/urob/zmk.

To work with your fork, next create a local "clone" of your fork on your computer. Navigate to the parent working directory in which you want to create the clone, then type:

git clone https://github.com/your_username/zmk --single-branch

The --single-branch argument is optional. It tells git to only download the main branch of your fork, which helps keeping the local clone clean.

Updating your ZMK fork with the latest ZMK

Eventually, our fork will grow out of date. To keep it updated with the latest ZMK features, we first have to register the official ZMK repo with our local clone:

git remote add -t main upstream https://github.com/zmkfirmware/zmk

Here the -t main argument is optional, it tells git to only register the "main" branch of the official ZMK repo. upstream is an alias that we will use to interact with the official ZMK repo. It can be anything, but "upstream" is customary for the source repository.

FYI, when we created our local clone, git automatically created another alias, origin, that points to our own ZMK fork on Github.

Now that we have registered the "upstream" ZMK repo, we can use it to update our own ZMK fork:

git fetch upstream
git rebase upstream/main
git push --force

The "fetch" command simply checks what's new with the upstream branch. The "rebase" command resets our local ZMK branch to the latest upstream state and then re-adds everything that we had done in our branch. Hopefully, everything goes smoothly and there are no merge conflicts. Finally, the "push" command pushes the changes we just made in our local repo back to our remote fork on Github (the --force argument is needed here due to the way git rebase works).

Merging PRs (and other remotes) into our fork

First, we have to locate and register the remote repository that contains the branch that we want to merge. We can use the same command that we used above to register the "upstream" ZMK repo. For example, for ftc's "mouse PR" branch:

git remote add -t mouse-ftc mouse-stuff https://github.com/ftc/zmk

Again, the -t mouse-ftc is optional. Here I used it to only register the "mouse-ftc" branch (which is the one that contains the mouse PR). mouse-stuff is again an arbitrary alias -- choose what makes sense to you.2

Once we registered the remote, we can merge it into our own local clone:

git fetch mouse-stuff
git merge mouse-stuff/mouse-ftc --squash
git commit -m "Mouse emulation support"
git push

The already familiar git fetch now checks what's new with the "mouse-stuff" repository ("mouse-stuff" is the alias we chose above). git merge does the actual merging into our local clone. The --squash argument tells git to bundle all the commits in the source repo iinto a single one instead of adding them one-by-one. It's a matter of personal taste, but I like it, because it keeps my git history clean and allows for easy removal of a PR (see below). Next we commit all the merged changes with git commit using a commit message of "Mouse emulation support" (can be anything). Finally, we push these local changes back to our fork on Github.

Another example

This process can be repeated to merge as many remote branches as you like. To give another example, to add the "fix-mod-morph" PR and the "positional-hold-tap-on-release" PR, we would first register the source remote:

git remote add urob https://github.com/urob/zmk

I haven't used the -t argument this time, because I am planning to merge two branches from this repo. Once the remote is registered, we can merge the two branches and push the changes back to our remote fork with:

git fetch urob
git merge urob/fix-mod-morph --squash
git commit -m "Fix mod-morph"
git merge urob/positional-hold-tap-on-release --squash
git commit -m "On-release property for positional hold taps"
git push

Merge conflicts

Resolving merge conflicts is beyond the scope of this little introduction. While some merge conflicts are simple formatting issues, major merge conflicts require digging through the source code and understanding what's actually going on in order to resolve them correctly. Fortunately, in my experience, most PRs merge cleanly. And if not, there is often already someone who resolved the merge conflict (like ftc for the "mouse PR"), and one can just merge their branch instead.

Deleting PRs (and other commits)

Suppose at some point we decide that we no longer need the "Fix mod-morph" PR (perhaps because it has been merged into the official ZMK repo). If you followed my advice above, the entire PR is a single commit in our repo. To delete it, run:

git rebase upstream/main -i

This is almost the same command that we used above to update our repo with the latest ZMK features. As above, it first resets our local branch to the state of the upstream branch (or, more accurately, to the state it was in the last time we ran git fetch upstream). But then, because we have specified the -i argument, instead of re-applying all our local changes, it opens a text editor and lets us choose interactively what we want to do. The editor lists all our commits that we have made on top of upstream. It should look similar to this:

pick 2efdd3ce Mouse emulation support
pick d3eb8c3d Fix mod-morph
pick d3768f3a On-release property for positional hold taps

To delete one of those commits, replace "pick" by "d" and then save and exit the editor. For example, to delete the "Fix mod-morph" PR, we would do

pick 2efdd3ce Mouse emulation support
d d3eb8c3d Fix mod-morph
pick d3768f3a On-release property for positional hold taps

Once we are done, we can push back the changes to our remote fork on Github with:

git push --force

Resetting our fork

Suppose we have done something weird and screwed up our local clone. If we haven't pushed our changes back to our remote fork on Github, we can simply reset our local clone to the state of our remote fork on Github by running:

git reset --hard origin/main

Alternatively, we could reset our repo to the state of the official ZMK repo by running:

git fetch upstream
git reset --hard upstream/main
git push --force

If we only want to undo some of our commits, but have already pushed them to our remote, we can use the interactive rebase command from above to first delete the offending commits from our local repo:

git rebase upstream/main -i

Once we fixed our local repo, we can then push back the repaired repo to our remote fork on Github:

git push --force

Using our own fork to compile with Github actions

Now that you have your shiny own personalized ZMK fork, you want to use it to build the firmware. You could just embrace the full experience, install the toolchain, and compile locally. But perhaps you want to save the full experience for another day. In this case, you can still use your own repo to build with Github Actions by replacing the west.yml file in your zmk-config repo (not the same as your new zmk repo!) with this:

manifest:
  remotes:
    - name: some_name
      url-base: https://github.com/yourusername
  projects:
    - name: zmk
      remote: some_name
      revision: main
      import: app/west.yml
  self:
    path: config

Conclusion

That's it. You now know how to update your fork with the latest official features, how to merge PRs into your fork or delete them, and how to reset your repo. Happy building!

Glossary

Okay, I get it, there's a lot of terms flying around. This is what some of them mean:

Note that there is no automatic syncronization in git. To make our local clone aware of new stuff happening in any remote, we use git fetch. To actually apply changes from a (fetched) remote to our local clone, we use git merge and git rebase (there's also git pull and git cherry-pick). And to push back changes from our local clone to origin (or other remotes with write-access), we use git push.

Footnotes

  1. This means that I am leaving out many core concepts such as git add, git pull, git branch (and checkout), git stash, git submodule and git subtree, git cherry-pick, resolving merge-conflicts, viewing diffs and logfiles, etc. The reason is that there are already tons of great introductions to git. The problem is that git is complex, which can make even the best introductions a bit daunting, especially for people who only want to combine a couple of PRs without developing their own code (if you develop your own code, you probably don't need help using git). Hence the idea for this "cookbook approach".

  2. Personally I like to use remote aliases based on the maintainers username. But for the purpose of this guide, I wanted to emphasize that it is the alias that it used when interacting with the repo, not the username. Hence, my choice of "mouse-stuff" as opposed to "ftc".

@urob
Copy link
Author

urob commented Jan 26, 2024

remote add appears to work correctly:

$ git remote add -t mouse-ftc mouse-stuff https://github.com/ftc/zmk
error: remote mouse-stuff already exists.

$ git fetch mouse-stuff

$ git merge mouse-stuff/mouse-pr --squash
merge: mouse-stuff/mouse-pr - not something we can merge

Try

git merge mouse-stuff/mouse-ftc --squash

The branch mouse-pr does not exist in ftc's repo -- the correct branch name is mouse-ftc (the same one you registered with remote add two commands prior)

@fsqonbdk
Copy link

fsqonbdk commented Jan 26, 2024

Yes, thank you! I swear I can read! 😆

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment