git: add FAQ
Add in the FAQ from the github docs I did for the transition. Submissions from: rpokala@, jrm@, Marc Branchaud and "mirror176".
This commit is contained in:
parent
28d4e1377a
commit
3bbe81a675
1 changed files with 410 additions and 0 deletions
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue