+++ title = 'My git workflow' date = 2023-11-12T19:10:04+01:00 draft = false +++ ## TL;DR (if you use nix) [git module](https://codeberg.org/jacekpoz/niksos/src/branch/master/modules/cli/git.nix) (uses home-manager) and [the config](https://codeberg.org/jacekpoz/niksos/src/branch/master/hosts/niks/configuration.nix#L323-L330) [ssh module](https://codeberg.org/jacekpoz/niksos/src/branch/master/modules/services/ssh.nix) and [the config](https://codeberg.org/jacekpoz/niksos/src/branch/master/hosts/niks/configuration.nix#L434-L472) --- Over the past few months I helped a few people learn git so I decided to put this together instead of explaining my workflow each time. Hopefully this can serve as a simple git guide to get started quickly or as a reference to come back to if you forget something. I don't use any git wrappers, the cli is enough for me. If you know of a good one let me know, I might try it and add it here. --- I aliased `git` to `g` in my [zsh config](https://codeberg.org/jacekpoz/niksos/src/branch/master/modules/cli/zsh.nix#L31), which is what I use in all the examples below. First the basics: - `g init` initializes the repo in `.`; make sure you create a new one for your repo, - `g clone forge:username/repo` will download that repository into `./repo`, - `g remote add origin forge:username/repo` is how you connect the local repo to your preferred git forge (more in [remotes](#remotes)), - `g add .` adds all the changes you made so far (more in [adding](#adding)), - `g commit -m "commit message"` creates a commit with whatever you added prior (more in [commits](#commits)), - `g push` pushes all the commits to the forge (it ignores added but uncommited changes), - `g status` shows you the current status (added files, unpushed commites, etc.) and handy commands on how to change it. If you run all these right now you'll get an error akin to this: ``` user@example.com: Permission denied (publickey). fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. ``` which means you need to generate and setup an ssh key. ## SSH key `ssh-keygen -t ed25519 -f ~/.ssh/` is what I usually do. Some people might recommend making a single key for all git forges, I keep them separate, you do you. After running the above you'll be asked to enter a passphrase or leave it empty for no passphrase. I recommend **always** setting a strong passphrase (could be generated with [KeePassXC](https://keepassxc.org)) and adding it to a password manager like the aforementioned. Copying and pasting that passphrase isn't a big deal, having your unlocked key stolen is. After doing that you'll have 2 files, `~/.ssh/` (the private key) and `~/.ssh/.pub` (the public key). As the name suggests, **DO NOT** share the first one with anyone, it must be kept secure. If that leaks and isn't protected by a password, or the password is cracked, whoever has the file can modify the git history as you. Now you want to add your public key to your preferred git forge. Here's how to do that on [github](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account), [gitlab](https://docs.gitlab.com/ee/user/ssh.html#add-an-ssh-key-to-your-gitlab-account) and [codeberg](https://docs.codeberg.org/security/ssh-key/#add-the-ssh-key-to-codeberg) (which should work for all forgejo instances). All of these instructions are pretty similar and you should be able to figure it out after the first time. What we need to do now is tell git which ssh key to use when connecting to our preferred forge. ## Remotes On my machine I alias all the remotes in my ssh config which both makes them shorter and allows me to add the ssh key to that forge. The nix config is at the top but if you don't use nix this is what you add for each remote to either `/etc/ssh/ssh_config` or (preferably) `~/.ssh/config`: ``` Host example HostName git.example.com User git IdentityFile /path/to/private_key ``` For an example, this is what I'd do for codeberg: ``` Host codeberg HostName codeberg.org User git IdentityFile ~/.ssh/codeberg ``` and change my git remote: `g remote set-url origin codeberg:nickname/repo` (if you added it already). Now you should finally be able to push your changes! That wasn't so bad, was it? This workflow might not be ideal to you but it's simple and convenient enough for me and I think it should be alright for new users. The purpose of this post isn't to be a general git guide, it's a reference I (and hopefully others) can go back to. For a general git guide that goes much more in depth than this I can't recommend the [official git book](https://git-scm.com/book/en/v2) enough. This post covers a very small part of the second chapter of that book. If you also want a (maybe not so quick) reference for various git subcommands check their manpages, e.g. `man git-add` for `g add`. Now, if all you want to do is push a few commits to a repo, this should be enough and you can stop reading. If you want to learn interactive adding, reverting your changes, signing your commits with a GPG key and more, read on! ## Adding `g add` accepts a [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming)#Unix-like) meaning that you can e.g. `g add *.c` to add all files ending with `.c` in the current directory. Passing a single file name of course also works. `g add -p` gives you access to the interactive mode, which displays hunks (a part of the changes) that you can add `y`, discard `n`, split `s` or manually edit `e` into smaller hunks. It also accepts a glob pattern, just like the normal `g add`. The interactive mode might offer more functionality but I found these 4 options to be enough for almost anything. ## Commits `g commit` without any flags will let you edit the commit message in your editor of choice. It can be set with either `g config --global core.editor "my_editor"` or with the environment variable `GIT_EDITOR`. If you're on nix and use home-manager check my git module. `g log` gives you all commits in the current repo. Each commit description starts with something like `commit b155c8912bb2347e030e723a2b37f1a60cb0fe15`. That big mess of characters is the commit hash, its identifier which you will use to refer to that commit. Usually you can type the first few characters of that hash and it should also work. When in doubt use your shell's autocompletion (most likely using the tab key). `g log` has *a lot* of flags, either read its manpage or toy around with the help of autocompletion. If you want to see the state of your repo at a specific commit use `g checkout`. Now that we know how to view and refer to a specific commit, we can learn how to revert the changes made in that commit. The terminology on this one is really confusing, I have to check it almost every time I need to revert a commit: - `g reset --hard commit_hash` will remove that commit and **remove all the changes in that commit locally** — to get them back you have to rewrite them by hand; most of the time not necessary, - `g revert commit_hash` will create a revert commit — the exact opposite of the one you're reverting, bringing you back to the state before it, - `g reset --soft commit_hash` will remove that commit but keep its changes, allowing you to make further changes, add and commit them again; in practice this is the one I use the most. To ammend further changes to the last commit you can add the changes and use `g commit --ammend`. It's probably a faster equivalent to `g reset --soft`, I just happen to have learned the former first and got used to it. If you want to sign your commits with a GPG key, verifying that you're actually their author, you want to make git aware of your public key. To do that, run `g config --global user.signingkey my_public_gpg_key` and install a pinentry frontend. I use the qt one and can recommend it. If you have no idea what I'm talking about, skip this section. This isn't necessary and your contributions most likely won't be rejected because your commits aren't signed. --- If you got stuck on some part, find this too hard or have any suggestions for this post, feel free to contact me. All my public contact information is on the sidebar at the top of all pages, I'd be more than happy to help you out. :-)