# Git ## Installation ### Install Git on Ubuntu or Debian ```bash sudo apt install -y git ``` ### Set up ssh keys for remote instance Make sure that the ssh keys are password-protected (On remote instance) ```bash # Ensure .ssh folder has been created mkdir -p ~/.ssh ``` (on home machine) ```bash # Copy over ssh key (it's password protected) scp ~/.ssh/id_rsa <USERNAME>@<IP_ADDRESS>://home/<USERNAME>/.ssh scp ~/.ssh/id_rsa.pub <USERNAME>@<IP_ADDRESS>://home/<USERNAME>/.ssh ``` ## Usage tips ### [Push only a single commit to remote branch](https://www.dennisrobinson.name/blog/push-only-one-commit-with-git/) Useful for triggering CI to run on each commit, instead of only on the last commit pushed in a batch Syntax: `git push <remote> <commit>:<remote-branch>` ```bash git push origin 2dc2b7e3:master ``` If you need to also create the remote branch while pushing only one commit to it: ```bash git push origin ac1420e:refs/heads/<mybranch> ``` ### Use `delta` as git diff pager when viewing PR diffs ```bash gh pr checkout 1835 PAGER=delta gh pr diff DD PAGER=delta gh pr diff # Using DD alias in common ``` ### [Recover git message if commit fails for some reason](https://unix.stackexchange.com/questions/590224/is-git-commit-message-recoverable-if-committing-fails-for-some-reason) Useful if e.g. you forget to plug in your security key for [[GPG]] signing ```bash cat "$(git rev-parse --git-dir)/COMMIT_EDITMSG)" ``` ### [Reorder the last two commits on the current branch](https://stackoverflow.com/questions/33388210/how-to-reorder-last-two-commits-in-git) Run ```bash git rebase -i HEAD~2 ``` Then change e.g. ```text pick f4648aee My first commit pick 00adf09a My second commit ``` to ```text pick 00adf09a My second commit pick f4648aee My first commit ``` Save, resolve any conflicts, and done! ### Using fixup commits to implement PR review feedback (for PRs on [[GitHub]]) **Creating the fixup! commit** - `git commit --fixup=<commit>`: creates a "fixup!" commit which changes the content of `<commit>` but leaves its log message untouched. - `git commit --fixup=amend:<commit>`: is similar but creates an "amend!" commit which also replaces the log message of `<commit>` with the log message of the "amend!" commit. - `git commit --fixup=reword:<commit>`: creates an "amend!" commit which replaces the log message of `<commit>` with its own log message but makes no changes to the content of `<commit>`. **Squashing fixup commits into their 'parent' commits** ```bash grbi --autosquash HEAD~6 grbi --autosquash 25ea746^ ``` **Rationale** - "these kind of tiny commits, esp. in response to PR review, i like to make fixup commits. like `git commit --fixup=HEAD~3` or w/e." - "it makes the same commit as before, but when you rebase git will automatically squash those fixup commits into the parent commit" - "it's also nicer than just rebasing in-place and force-pushing, since then reviewers on github can't see the separate change. but that's also only because github sucks lol" **More info**: - (Blog post) [GIT tip : Keep your branch clean with fixup and autosquash](https://fle.github.io/git-tip-keep-your-branch-clean-with-fixup-and-autosquash.html) ### [Stash untracked files](https://stackoverflow.com/questions/835501/how-do-you-stash-an-untracked-file) ```bash git stash --include-untracked # Shorthand git stash -u ``` ### Create and checkout branch in one command ```bash git checkout -b <branch_name> ``` - The [[Oh My Zsh]] git plugin has `gcb` aliased to the above ### Modify a specific commit (not the one at `HEAD`) https://stackoverflow.com/questions/1186535/how-to-modify-a-specified-commit 1. Stash changes `git stash` 2. `git rebase --interactive 'bbc643cd^'` Note the caret ^ at the end of the command, because you need actually to rebase back to [the commit __before__ the one you wish to modify](https://stackoverflow.com/questions/1955985/). 3. In the default editor, modify `pick` to `edit` in the line mentioning `bbc643cd`. 4. `bbc643cd` is your last commit and you can now make your changes and amend it: - `git stash apply` - `git add .``git commit --amend` 5. Continue the rebase: `git rebase --continue` ### Reset last commit back into staged changes: ``` git reset --soft HEAD~1 ``` ### Partially merge a PR to see better diffs (e.g. if an early commit moved a file) - DO NOT cherry pick the commits - Go to the feature branch, checkout the last commit you want to merge - Create a temp branch for it - Switch to master - Merge the temp branch into master - Copy the feature branch into a fresh branch (same commits but different PR diff) - On Github, replace the old PR with a PR from the fresh feature branch - As an example, this occurred with Eutykhia #18 ### Revert a commit not at `HEAD`, but don't commit https://stackoverflow.com/questions/2318777/undo-a-particular-commit-in-git-thats-been-pushed-to-remote-repos ```bash git revert -n d09b3810 ``` - `-n` switch prevents it from committing ### Creating and pushing tags for minor releases https://stackoverflow.com/questions/5195859/how-do-you-push-a-tag-to-a-remote-repository-using-git ```bash git tag v0.4.1 git push origin v0.4.1 ``` **NOTE:** For larger releases, it is better to create the tag / release using the UI on [[GitHub]], as you can auto generate a large part of the documentation. ```bash # Push all tags git push --tags ``` - I recommend not using or training others to use `git push --tags` as it can be very very difficult to get rid of bad tags when your co-workers are trained to push all tags, as people continue to push the old bad tags they have locally every time they want to push a new tag. Because of this, I will only every advise someone to use git push origin <tag_name> now. ### Don't change date when rebasing #### Author date vs commit date from the [Pro Git Book](https://git-scm.com/book/en/v2/Git-Basics-Viewing-the-Commit-History): >- The author is the person who originally wrote the work, >- whereas the committer is the person who last applied the work. #### Preserving the author date https://stackoverflow.com/a/2976598 ```bash git rebase --committer-date-is-author-date master ``` #### `--committer-date-is-author-date` vs `--ignore-date` From [git am docs](https://git-scm.com/docs/git-am): - `--committer-date-is-author-date` "_[...]allows the user to lie about the committer date by using the same value as the author date_" while - `--ignore-date` "_[...]allows the user to lie about the author date by using the same value as the committer date_". In this case, the one we want is `--committer-date-is-author-date`. ### [Get name of default branch](https://stackoverflow.com/questions/28666357/git-how-to-get-default-branch) (`master`, `main`) (e.g. for usage in scripts) ```bash git remote show upstream | grep "HEAD branch" | sed 's/.*: //' ``` ### [Git pull and overwrite files](https://stackoverflow.com/a/8888015) Not actually git pull, but it's a reasonable way to think about it ```bash # Git fetch git fetch <remote> git reset --hard <remote>/<branch> ``` ## Debugging ### `git commit --fixup <TAB>` completes with files instead of commits - Also applies to use via a `gcf` alias **Notes** Had problems with this with the `brew` installation of `[email protected]`. Originally I thought the problem might have been with [[Oh My Zsh]] but extensive searches through the git history (and bisecting) produced no changes in behavior. I thought I traced the problem to the `_git_commit ()` function in `/opt/homebrew/Cellar/git/2.44.0/share/zsh/site-functions/git-completion.bash`, but was not able to fix it satisfactorily. Multiple tweaks and iterations with an AI made some progress - I was able to substitute `__git_complete_refs` (or something like that) in the `--fixup` branch with a `git log ...` command which prints the commit hash and commit name. However, I could not get the tabbing behavior to work the way I needed, in particular, you can see the commit name while tabbing through, but only the commit hash is left behind. Ended up just uninstalling the [[Homebrew]] `[email protected]` entirely and going with the preinstalled macOS version of git. So as of 2024-11-06, the macOS `[email protected]` appears to work correctly. ```bash $ git --version git version 2.39.3 (Apple Git-146) ``` The `git-completion.bash` file for the macOS version of `git` can be found at `/Applications/Xcode.app/Contents/Developer/usr/share/git-core/git-completion.bash`, in case further inspection is needed. Let's hope that the issue in `[email protected]` eventually gets fixed so I can upgrade.