git: 3bbe81a675 - main - git: add FAQ

Warner Losh imp at FreeBSD.org
Thu Mar 18 21:59:03 UTC 2021


The branch main has been updated by imp:

URL: https://cgit.FreeBSD.org/doc/commit/?id=3bbe81a6753dfcd17b49dd4e770581babd6e1f72

commit 3bbe81a6753dfcd17b49dd4e770581babd6e1f72
Author:     Warner Losh <imp at FreeBSD.org>
AuthorDate: 2021-03-18 21:55:16 +0000
Commit:     Warner Losh <imp at FreeBSD.org>
CommitDate: 2021-03-18 21:55:16 +0000

    git: add FAQ
    
    Add in the FAQ from the github docs I did for the transition.
    
    Submissions from: rpokala@, jrm@, Marc Branchaud and "mirror176".
---
 .../en/articles/committers-guide/_index.adoc       | 410 +++++++++++++++++++++
 1 file changed, 410 insertions(+)

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 <new-branch-name>
+
+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
 


More information about the dev-commits-doc-all mailing list