This part is not 101 stuff, but rather various "tricks" that you may or may not have to (or want to) use. If you arrived at this point for the first time, I strongly advise you to skip this part entirely, so that you do not suffer from information overload. However, if you happen to have questions on a specific use of Git and it is actually answered below, please enjoy! (And if you have a question that is unanswered, please [let me know](mailto:mathias.malandain@inria.fr) and I might write an answer down here for everyone to enjoy.)
# Branch management: `git fetch` and `git fetch --prune` {#git-fetch}
{: .box-error}
**TODO**
As we have seen numerous times before, there are not a lot of Git commands that actually have your computer interact with the repo. The one you will be using a lot will be `git pull`, which is actually `git fetch` followed by `git merge`: the former actually fetches the changes from the repo, and the latter merges these changes with your current working copy.
Note, however, that `git fetch`, used by itself, will do more than just fetch the contents of the branch you are working on:
{: .box-success}
Command `git fetch` is used to fetch the contents of the repo (or *remote*), including branches and tags previously unknown by your computer.
You do not have to understand exactly what the first 5 lines mean: essentially, there was content on the repo that was not on my computer, so that Git downloaded it for me. (This content was packed beforehand, so as to reduce the volume of data to be transferred, and unpacked on my machine after the transfer itself.)
The last three lines are more interesting:
* I am told that new content was fetched from the Inria GitLab repo.
* In particular, a branch that I did not know of, called `readme-structure-change`, now exists. This branch now exists in my working copy: a `git switch` to this branch will be lightning fast, because my computer holds all necessary data to switch to this branch.
* In a similar fashion, my computer did not know about tag `checkpoint`; now it does.
{: .box-info}
"New content fetched from the repo? Well," you may ask, "where else could it come from? Duh!" First, please do not duh me, thank you, and second, a Git project may very well have several remotes associated to it. Our current use (with GitLab, Github and others) relies on a central repo, but nothing technically prevents you from collaborating on a Git project with several people in a decentralized network... apart from the fact that it is pretty difficult, messy, and no one does that anymore.
Note, however, that `git fetch` may bring you new content, but that it will not delete content that does not exist anymore on the repo.
That is, unless you ask.
{: .box-success}
Command `git fetch --prune` deletes references that do not exist anymore on the repo before it performs the actual fetching. Such references include branches that were deleted from the repo -- hopefully after they were merged to active branches -- but not tags: option `--prune-tags` also has to be provided for deleting tags. (Alternatively, one may edit the `~/.gitconfig` file so that one line reads `fetch.pruneTags=true`: this tells Git to also prune tags when `git fetch --prune` is invoked.)
There is a subtle nuance here that you might have missed: pruning will delete references to branches *that were deleted from the repo*. Branches that never existed on the repo will *not* be subject to pruning, so that, if you are working on a local branch, running `git fetch --prune` will not delete it. Only remote-tracking branches that were deleted on the remote. All in all, running this command should not hurt.
# Cleaning up your local copy {#cleanup}
...
...
@@ -23,7 +60,7 @@ All hail `git clean`.
* Add the `-d` option to do this recursively: all subfolders of the current folders will be cleaned up as well.
* Add the `-X` option (uppercase `X`) to only remove files ignored by Git.
* The lowercase `x` has a different meaning: `-x` will remove every untracked file, ignored or not. Say goodbye to any local file that you were holding dear.
* Add the `-n` option for a dry run, so that you just know what would be removed without it.
* Add the `-n` option for a dry run, so that you just know what would be removed without it. If you are OK with these changes, you may now run the same command without this option.
{: .box-warning}
The `-f`, or `--force`, option is here because one of the Git configuration variables might very well prevent you from cleaning enything without forcing. This is one of the rare instances when it is kind of OK to use `-f` in a Git command, because if misused, it will only have an impact on *yourself*. **When using pretty much any Git command other than `git clean`, do not ever write `-f`, unless you are 110% sure that you know what you are doing.**
...
...
@@ -37,10 +74,44 @@ These are the most used options for `git clean`; the rest is [here](https://git-
* In most (in not all) cases, **you should do a dry run *before* the actual cleanup by adding the `-n` option**.
</div>
# Rebasing (how to make history linear without rewriting it) {#rebase}
# Rebasing (how to keep up with a source branch) {#rebase}
There are already a lot of very good resources about `git rebase`, probably because it is a command that seems pretty frightening at first, but is actually a very powerful and beautiful command. This is why I will only get to the "why" and the 101 here, before providing you a few links. Also, this is kind of a "pure Git" command, which is not exactly the scope of this tutorial.
{: .box-info}
**Rebasing** consists in taking a sequence of commits and moving them somewhere else in the graph of the project.
...okay, well, that's... pretty vague. Could I provide an example? Yeah, sure. Here is the most common use of rebasing:
* You created a local `feature` branch from a source branch (let's say `main`), and pushed a few commits on it.
* Meanwhile, other commits were pushed to the `main` branch. Some of these commits probably change files that you also modified on the `feature` branch.
* You know that you will go on working on `feature`, and that other people will go on pushing stuff on `main`. The longer it goes, the harder merging your branch in `main` will be.
<divclass="box-success"markdown="1">
By rebasing, you can have your local branch "look like" it originates from the latest commit on the source branch, and during this rebasing process, you will be able to fix conflicts directly on your local branch. This means two things:
* First, if you make a mistake when fixing conflicts, the outcome will be in your working copy, not on the shared source branch. In other words, if you break something, you are not breaking it *for everyone*.
* Second, if you rebase on a regular basis, you will have fewer and simpler conflicts to handle each time, and the chances that you actually break your code will be way slimmer!
</div>
This is the most common "why". The "how" is not that difficult:
1. Check that you committed all your changes on the `feature` branch.
2. Update the source branch; if it is `main`, you may run `git switch main` followed by `git pull`, for example. (The use of `git pull` here makes step 1 basically mandatory; otherwise, uncommitted changes are moved to the `main` branch when you switch to it, and `git pull` will force you to merge these changes with the contents of `main`.)
3. Switch back to `feature` and run `git rebase main`. (Step 2 was used to make `main` point to the latest commit. If you skip step 2, you may rebase on an older commit... which was not the goal, was it?)
If conflicts arise at some point during the rebasing process, Git will tell you which files contain conflicts. In these files, you will be able to see the usual conflict markers (`<<<<<<<<`, `========`, `>>>>>>>>`), and you can solve these conflicts as you see fit. Once it is done, you can `git add` the files to tell Git that you solved all conflicts in them, and run `git rebase --continue`.
Finally, be reassured: if anything goes wrong in the process, you can just run `git rebase --abort` and everything will be back to the initial state.
Oh, also, before we forget: rebasing is great, but for local branches only.
{: .box-error}
**TODO**
**Do not rebase a branch that was pushed to the repo:** if you do so, you will then have to force push, that is, to rewrite the public history of the repo, and this is a very bad practice. Not only is it generally deemed disrespectful and unsafe, but it will also cause you a lot of problems when anyone then tries to interact with the branch from their working copy.
{: .box-error}
**TODO:** Add references
# Interactive rebasing (how to clean up your mess before pushing) {#irebase}