diff --git a/documentation/content/en/articles/committers-guide/_index.adoc b/documentation/content/en/articles/committers-guide/_index.adoc index 9069eb8f42..e4ec27cb4c 100644 --- a/documentation/content/en/articles/committers-guide/_index.adoc +++ b/documentation/content/en/articles/committers-guide/_index.adoc @@ -1624,6 +1624,416 @@ author recommends that your upstream github repo remain the default push location so that you only push things into FreeBSD you intend to by making it explicit. +[[git-faq]] +=== Git FAQ + +This section provides a number of targeted answers to questions that are likely to come up often for users, developer and integrators. + +Note, we use the common convention of having the origin for the FreeBSD repo being 'freebsd' rather than the default 'origin' to allow +people to use that for their own development and to minimize "whoopse" pushes to source of truth. + +==== Users + +===== How do I track -current and -stable with only one copy of the repo? + +**Q:** Although disk space is not a huge issue, it's more efficient to use +only one copy of the repository. With SVN mirroring, I could checkout +multiple trees from the same repo. How do I do this with Git? + +**A:** You can use Git worktrees. There's a number of ways to do this, +but the simplest way is to use a clone to track -current, and a +worktree to track stable releases. While using a 'bare repository' +has been put forward as a way to cope, it's more complicated and will not +be documented here. + +First, you need to clone the FreeBSD repository, shown here cloning into +`freebsd-current` to reduce confusion. $URL is whatever mirror works +best for you: +[source,shell] +.... +% git clone -o freebsd --config remote.freebsd.fetch='+refs/notes/*:refs/notes/*' $URL freebsd-current +.... +then once that's cloned, you can simply create a worktree from it: +[source,shell] +.... +% cd freebsd-current +% git worktree add ../freebsd-stable-12 stable/12 +.... +this will checkout `stable/12` into a directory named `freebsd-stable-12` +that's a peer to the `freebsd-current` directory. Once created, it's updated +very similarly to how you might expect: +[source,shell] +.... +% cd freebsd-current +% git checkout main +% git pull --ff-only +# changes from upstream now local and current tree updated +% cd ../freebsd-stable-12 +% git merge --ff-only freebsd/stable/12 +# now your stable/12 is up to date too +.... +I recommend using `--ff-only` because it's safer and you avoid +accidentally getting into a 'merge nightmare' where you have an extra +change in your tree, forcing a complicated merge rather than a simple +one. + +Here's https://adventurist.me/posts/00296 a good writeup that goes into more detail. + +==== Developers + +===== Ooops! I committed to `main` instead of a branch. + +**Q:** From time to time, I goof up and commit to main instead of to a +branch. What do I do? + +**A:** First, don't panic. + +Second, don't push. In fact, you can fix almost anything if you +haven't pushed. All the answers in this section assume no push +has happened. + +The following answer assumes you committed to `main` and want to +create a branch called `issue`: +[source,shell] +.... +% git branch issue # Create the 'issue' branch +% git reset --hard freebsd/main # Reset 'main' back to the official tip +% git checkout issue # Back to where you were +.... + +===== Ooops! I committed something to the wrong branch! + +**Q:** I was working on feature on the `wilma` branch, but +accidentally committed a change relevant to the `fred` branch +in 'wilma'. What do I do? + +**A:** The answer is similar to the previous one, but with +cherry picking. This assumes there's only one commit on wilma, +but will generalize to more complicated situations. It also +assumes that it's the last commit on wilma (hence using wilma +in the `git cherry-pick` command), but that too can be generalized. + +[source,shell] +.... +# We're on branch wilma +% git checkout fred # move to fred branch +% git cherry-pick wilma # copy the misplaced commit +% git checkout wilma # go back to wilma branch +% git reset --hard HEAD^ # move what wilma refers to back 1 commit +.... +Git experts would first rewind the wilma branch by 1 commit, switch over to +fred and then use `git reflog` to see what that 1 deleted commit was and +cherry-pick it over. + +**Q:** But what if I want to commit a few changes to `main`, but +keep the rest in `wilma` for some reason? + +**A:** The same technique above also works if you are wanting to +'land' parts of the branch you are working on into `main` before the +rest of the branch is ready (say you noticed an unrelated typo, or +fixed an incidental bug). You can cherry pick those changes into main, +then push to the parent repo. Once you've done that, cleanup couldn't +be simpler: just `git rebase -i`. Git will notice you've done +this and skip the common changes automatically (even if you had to +change the commit message or tweak the commit slightly). There's no +need to switch back to wilma to adjust it: just rebase! + +**Q:** I want to split off some changes from branch `wilma` into branch `fred` + +**A:** The more general answer would be the same as the +previous. You'd checkout/create the `fred` branch, cherry pick the +changes you want from `wilma` one at a time, then rebase `wilma` to +remove those changes you cherry picked. `git rebase -i main wilma` +will toss you into an editor, and remove the `pick` lines that +correspond to the commits you copied to `fred`. If all goes well, +and there are no conflicts, you're done. If not, you'll need to +resolve the conflicts as you go. + +The other way to do this would be to checkout `wilma` and then create +the branch `fred` to point to the same point in the tree. You can then +`git rebase -i` both these branches, selecting the changes you want in +`fred` or `wilma` by retaining the pick likes, and deleting the rest +from the editor. Some people would create a tag/branch called +`pre-split` before starting in case something goes wrong in the split, +you can undo it with the following sequence: +[source,shell] +.... +% git checkout pre-split # Go back +% git branch -D fred # delete the fred branch +% git checkout -B wilma # reset the wilma branch +% git branch -d pre-split # Pretend it didn't happen +.... +the last step is optional. If you are going to try again to +split, you'd omit it. + +**Q:** But I did things as I read along and didn't see your advice at +the end to create a branch, and now `fred` and `wilma` are all +screwed up. How do I find what `wilma` was before I started. I don't +know how many times I moved things around. + +**A:** All is not lost. You can figure out it, so long as it hasn't +been too long, or too many commits (hundreds). + +So I created a wilma branch and committed a couple of things to it, then +decided I wanted to split it into fred and wilma. Nothing weird +happened when I did that, but let's say it did. The way to look at +what you've done is with the `git reflog`: +[source,shell] +.... +% git reflog +6ff9c25 (HEAD -> wilma) HEAD@{0}: rebase -i (finish): returning to refs/heads/wilma +6ff9c25 (HEAD -> wilma) HEAD@{1}: rebase -i (start): checkout main +869cbd3 HEAD@{2}: rebase -i (start): checkout wilma +a6a5094 (fred) HEAD@{3}: rebase -i (finish): returning to refs/heads/fred +a6a5094 (fred) HEAD@{4}: rebase -i (pick): Encourage contributions +1ccd109 (freebsd/main, main) HEAD@{5}: rebase -i (start): checkout main +869cbd3 HEAD@{6}: rebase -i (start): checkout fred +869cbd3 HEAD@{7}: checkout: moving from wilma to fred +869cbd3 HEAD@{8}: commit: Encourage contributions +... +% +.... + +Here we see the changes I've made. You can use it to figure out where +things when wrong. I'll just point out a few things here. The first +one is that HEAD@{X} is a 'commitish' thing, so you can use that as an +argument to a command. Though if that command commits anything to the +repo, the X numbers change. You can also use the hash (first column) +as well. + +Next 'Encourage contributions' was the last commit I did to `wilma` +before I decided to split things up. You can also see the same hash is +there when I created the `fred` branch to do that. I started by +rebasing `fred` and you see the 'start', each step, and the 'finish' +for that process. While we don't need it here, you can figure out +exactly what happened. Fortunately, to fix this, you can follow the +prior answer's steps, but with the hash `869cbd3` instead of +`pre-split`. While that set of a bit verbose, it's easy to remember +since you're doing one thing at a time. You can also stack: +[source,shell] +.... +% git checkout -B wilma 869cbd3 +% git branch -D fred +.... +and you are ready to try again. The 'checkout -B' with the hash combines +checking out and creating a branch for it. The -B instead of -b forces +the movement of a pre-existing branch. Either way works, which is what's +great (and awful) about Git. One reason I tend to use `git checkout -B xxxx hash` +instead of checking out the hash, and then creating / moving the branch +is purely to avoid the slightly distressing message about detached heads: +[source,shell] +.... +% git checkout 869cbd3 +M faq.md +Note: checking out '869cbd3'. + +You are in 'detached HEAD' state. You can look around, make experimental +changes and commit them, and you can discard any commits you make in this +state without impacting any branches by performing another checkout. + +If you want to create a new branch to retain commits you create, you may +do so (now or later) by using -b with the checkout command again. Example: + + git checkout -b + +HEAD is now at 869cbd3 Encourage contributions +% git checkout -B wilma +.... +this produces the same effect, but I have to read a lot more and severed heads +aren't an image I like to contemplate. + +===== Ooops! I did a 'git pull' and it created a merge commit, what do I do? + +**Q:** I was on autopilot and did a 'git pull' for my development tree and +that created a merge commit on the mainline. How do I recover? + +**A:** This can happen when you invoke the pull with your development branch +checked out. + +Right after the pull, you will have the new merge commit checked out. Git +supports a `HEAD^#` syntax to examine the parents of a merge commit: +[source,shell] +.... +git log --oneline HEAD^1 # Look at the first parent's commits +git log --oneline HEAD^2 # Look at the second parent's commits +.... +From those logs, you can easily identify which commit is your development +work. Then you simply reset your branch to the corresponding `HEAD^#`: +[source,shell] +.... +git reset --hard HEAD^2 +.... + +**Q:** But I also need to fix my 'main' branch. How do I do that? + +**A:** Git keeps track of the remote repository branches in a `freebsd/` +namespace. To fix your 'main' branch, just make it point to the remote's +'main': +[source,shell] +.... +git branch -f main freebsd/main +.... +There's nothing magical about branches in git: they are just labels on a DAG +that are automatically moved forward by making commits. So the above works +because you're just moving a label. There's no metadata about the branch +that needs to be preserved due to this. + +===== Mixing and matching branches + +**Q:** So I have two branches `worker` and `async` that I'd like to combine into one branch called `feature` +while maintaining the commits in both. + +**A:** This is a job for cherry pick. + +[source,shell] +.... +% git checkout worker +% git checkout -b feature # create a new branch +% git cherry-pick main..async # bring in the changes +.... +You now have one branch called `feature`. This branch combines commits +from both branches. You can further curate it with `git rebase`. + +**Q:** OK Wise Guy. That was too easy. I have a branch called `driver` and I'd like +to break it up into `kernel` and `userland` so I can evolve them separately and commit +each branch as it becomes ready. + +**A:** This takes a little bit of prep work, but `git rebase` will do the heavy +lifting here. + +[source,shell] +.... +% git checkout driver # Checkout the driver +% git checkout -b kernel # Create kernel branch +% git checkout -b userland # Create userland branch +.... +Now you have two identical branches. So, it's time to separate out the commits. +We'll assume first that all the commits in `driver` go into either the `kernel` +or the `userland` branch, but not both. + +[source,shell] +.... +% git rebase -i main kernel +.... +and just include the changes you want (with a 'p' or 'pick' line) and +just delete the commits you don't (this sounds scary, but if worse +comes to worse, you can throw this all away and start over with the +`driver` branch since you've not yet moved it). + +[source,shell] +.... +% git rebase -i main userland +.... +and do the same thing you did with the `kernel` branch. + +**Q:** Oh great! I followed the above and forgot a commit in the `kernel` branch. +How do I recover? + +**A:** You can use the `driver` branch to find the hash of the commit is missing and +cherry pick it. +[source,shell] +.... +% git checkout kernel +% git log driver +% git cherry-pick $HASH +.... + +**Q:** OK. I have the same situation as the above, but my commits are all mixed up. I need +parts of one commit to go to one branch and the rest to go to the other. In fact, I have +several. Your rebase method to select sounds tricky. + +**A:** In this situation, you'd be better off to curate the original branch to separate +out the commits, and then use the above method to split the branch. + +So let's assume that there's just one commit with a clean tree. You +can either use `git rebase` with an `edit` line, or you can use this +with the commit on the tip. The steps are the same either way. The +first thing we need to do is to back up one commit while leaving the +changes uncommitted in the tree: +[source,shell] +.... +% git reset HEAD^ +.... +Note: Do not, repeat do not, add `--hard` here since that also removes the changes from your tree. + +Now, if you are lucky, the change needing to be split up falls entirely along file lines. In that +case you can just do the usual `git add` for the files in each group than do a `git commit`. Note: +when you do this, you'll lose the commit message when you do the reset, so if you need it for +some reason, you should save a copy (though `git log $HASH` can recover it). + +If you are not lucky, you'll need to split apart files. There's another tool to do that which you +can apply one file at a time. +[source,shell] +.... +git add -i foo/bar.c +.... +will step through the diffs, prompting you, one at time, whether to include or exclude the hunk. +Once you're done, `git commit` and you'll have the remainder in your tree. You can run it +multiple times as well, and even over multiple files (though I find it easier to do one file at a time +and use the `git rebase -i` to fold the related commits together). + +==== Cloning and Mirroring + +**Q:** I'd like to mirror the entire git repo, how do I do that? + +**A:** If all you want to do is mirror, then +[source,shell] +.... +% git clone --mirror $URL +.... +will do the trick. However, there are two disadvantages to this if you +want to use it for anything other than a mirror you'll reclone. + +First, this is a 'bare repo' which has the repository database, but no +checked out worktree. This is great for mirroring, but terrible for day to +day work. There's a number of ways around this with 'git worktree': +[source,shell] +.... +% git clone --mirror https://cgit-beta.freebsd.org/ports.git ports.git +% cd ports.git +% git worktree add ../ports main +% git worktree add ../quarterly branches/2020Q4 +% cd ../ports +.... +But if you aren't using your mirror for further local clones, then it's a poor match. + +The second disadvantage is that Git normally rewrites the refs (branch name, tags, etc) +from upstream so that your local refs can evolve independently of +upstream. This means that you'll lose changes if you are committing to +this repo on anything other than private project branches. + +**Q:** So what can I do instead? + +**A:** Well, you can stuff all of the upstream repo's refs into a private +namespace in your local repo. Git clones everything via a 'refspec' and +the default refspec is: +[source,shell] +.... + fetch = +refs/heads/*:refs/remotes/freebsd/* +.... +which says just fetch the branch refs. + +However, the FreeBSD repo has a number of other things in it. To see +those, you can add explicit refspecs for each ref namespace, or you +can fetch everything. To setup your repo to do that: +[source,shell] +.... +git config --add remote.freebsd.fetch '+refs/*:refs/freebsd/*' +.... +which will put everything in the upstream repo into your local repo's +'res/freebsd/' namespace. Please note, that this +also grabs all the unconverted vendor branches and the number of refs +associated with them is quite large. + +You'll need to refer to these 'refs' with their full name because they +aren't in and of Git's regular namespaces. +[source,shell] +.... +git log refs/freebsd/vendor/zlib/1.2.10 +.... +would look at the log for the vendor branch for zlib starting at 1.2.10. + + [[subversion-primer]] == Subversion Primer