--- title: Project management 2. Git continued marp: true paginate: true theme: buutti --- # Project management 2. Git continued ## Branches * A Git repository can have multiple ***branches*** * By default, a Git repository has only one branch * named usually `main` or `master` * Using multiple branches makes it possible to work on new features step by step in their own ***feature branches*** * Meanwhile, the `main` branch is kept clean and in a working state * Only when the feature is completed, the branch is merged into the `main` branch! ![bg right:30% width: 60%](imgs/git-branches.png) ### Active branch * Even though your local repository can have multiple branches, only one of them is *active* at a given time * `git status` tells you firsthand which branch you are on: ``` On branch main ``` ## `git branch` & `git checkout` * You can create a new branch with `git branch ` * The new branch is not empty: it contains a copy of the code of the branch you executed this command in * ***Note:*** `git branch ` does not make the branch active! * To make the branch active, a.k.a "move" to the branch: * `git checkout `. * For example, to move back to the main branch, use `git checkout main` ### Local vs. remote branches * `git branch ` only creates a ***local*** branch * When you try to push changes from a new local branch for the first time, Git nags you that a matching remote branch doesn't yet exist * Git tells you how to create the remote branch: * `git push --set-upstream origin ` * Afterwards, `git push` pushes the changes to the matching remote branch ### Extra branch commands * Handy command: `git checkout -b ` * It's a shorthand for `git branch ` + `git checkout ` * Get a list of local branches with `git branch` * ...and all branches (incl. the remote ones) with `git branch -a` * Delete a local branch with `git branch -d ` * Remote branch can be deleted in the GitLab/etc website * ...or with `git push origin -d ` * ***Note:*** If deleted remote branches still show up in `git branch -a`, you can use the command `git remote prune origin` to remove them from the list. ## `git merge` * When a feature is done (and all the broken things fixed), you want to apply, i.e., ***merge*** your changes from the feature branch to the `main` branch 1) First, checkout the `main` branch with `git checkout main` 2) Then, do a `git pull` so you have the newest version of the `main` branch * (Someone else might have done changes to it while you were working on your feature!) 3) Then, merge your local feature branch to your local main branch with `git merge ` * It applies changes from `` to the ***current active branch*** * This is where ***conflicts*** might happen (more about them [later](#conflicts)) 4) Then, push your changes to remote with `git push` ### Merging with a pull/merge request * The aforementioned procedure isn't what we usually do, though! * `main` branch should be ***protected*** so we can't merge our new code there directly * Instead, we do the *inverse*. 1) First, checkout the `main` branch with `git checkout main` 2) Then, do a `git pull` so you have the newest version of the `main` branch 3) Then, ***checkout back to the feature branch***. 4) Merge your local `main` branch to your local `featurebranch` with `git merge main` 5) Then, create a ***merge request*** on GitLab (it's ***pull request*** on GitHub—they're the same thing, just named from different points of view.) ### Simplifying merging further * ***Pro tip:*** If you're working on your feature branch, you can merge changes from the `main` branch without changing branches altogether. 1) Update your local main branch with ```git fetch origin main:main``` 2) Merge your local `main` branch to your local `featurebranch` with `git merge main` 3) Then, create a merge request/pull request * Much easier this way! * But what is the merge request, anyway? ### Merge request/pull request * Merge/pull request is a formal process for merging your *remote* feature branch straight to the remote `main` branch * This adds a layer of protection to the `main` branch: no direct merging to the `main` branch! * Additionally, a ***code review*** can happen at this stage: * If a code maintainer thinks your merge request needs some modifications, you can make changes to the feature branch accordingly * A merge request is branch-specific, so when you push your changes to your feature branch, the merge request is automatically updated as well. * If this procedure is followed strictly, no bad code should get into the main branch! ### GitHub pull request UI ![w:900px](imgs/github-pullreq.png) * UI of a new pull request (*Pull requests > New pull request*) can be confusing... * *base* is the branch you're merging, *compare* is the branch you're merging into. ## Exercise 1. Pushing onwards Continue the exercise from [Git Basics](1-git-basics) or create a new repository for these exercises. 1) Create a new branch (with a name `new-feature`, for instance) in your local repository. 2) Checkout the branch, make some changes to `GitTest.md` there, and push the changes to GitLab. 3) Then, merge the changes from your `new-feature` branch to the `main` branch by using a) `git merge` from command line b) a merge request in GitLab ## GitLens * To make the Git workflow easier, install the GitLens extension to VS code * It helps in managing conflicts, comparing branches or commits * Install it from the Extensions panel (access with ***CTRL+SHIFT+X***) * Adds many new views to the source control tab
* For example: * Commits * Repositories * File History * Line History * Branches * Remotes
![](imgs/gitlens.png)
## Conflicts * Sometimes two people have made changes in the same lines of code! * This leads to a ***conflict***. * Let's assume we're trying to merge changes from `featurebranch` to `main`. * If a conflict happens, the merge does not conclude automatically. * Instead, we need to ***fix all the conflicts*** by hand and then ***conclude the merge*** with some commands. * Conflicting lines of code are framed by some `<<<< garbage ==== symbols >>>>` we don't yet understand * Before we can conclude the merge, we need to get rid of the garbage. ### Garbage explained ```c# <<<<<<< HEAD:Player.cs if (Input.anyKey) { return true; } ======= if (Input.anyKey) return true; >>>>>>> featureBranch:Player.cs ``` * ***Current change*** is between `<<<<<<<` and `=======` * old code that was already in `main` * ***Incoming change*** is between `>>>>>>>` and `=======` * new code coming from `featurebranch` * Use your text editor to choose which (or some combination of both) you want to preserve ### VS Code tools * VS Code gives us tools to make conflict resolution a relatively quick process * Choose your preference: * ***Accept Current Change*** (old code is preserved, new code is removed) * ***Accept Incoming Change*** (new code is preserved, old code is removed) * ***Accept Both Changes*** (both are preserved) ![bg right width:95%](imgs/vscode-conflicts.png) ### After resolving the conflict * After resolving all conflicting files, use `git add ` to add them to the commit * Then use `git commit` to apply changes (without a message! no `-m`) * Close the automatically opened `COMMITMSG` file. * This finishes the merge. * Then just `git push` to apply changes in the remote repository * ***Note:*** If VS code is not configured properly as the Git's text editor, and you encounter an error, run `git config core.editor code --wait` ### Example Git workflow with branches ![w:1200px](imgs/merge-workflow.png) ## Exercise 2. Fixing conflicts Continue in the Exercise 1 repository. 1) Create a new branch in your local repository, but do not checkout it just yet. 2) On the `main` branch, make some changes to `GitTest.md`, and **add & commit**. 3) Then, checkout the new feature branch. 4) Make some ***other*** changes to GitTest.md to the same line as before, **add & commit**. 5) Then, merge the changes from the `main` branch to your feature branch by using `git merge` 6) Fix the ensuing conflicts, add & commit & push. ## Undoing * Git doesn't have a general "undo" command * If you make a mistake, it is very case-specific what you need to do to fix it * See [undo options here](https://docs.gitlab.com/ee/topics/git/numerous_undo_possibilities_in_git/) * Also, [ohshitgit.com](https://ohshitgit.com/) ## `git log` & `git checkout ` * Use `git log` to see the commit history * Or `git log --oneline` for a more concise version * Press ***Q*** to quit the log view. * The newest changes are seen on top * On the left side of the commit message you see the ***hashcode*** of the commit * Use `git checkout ` to "time travel" into the commit * ***Note:*** If you have GitLens, check the Commits view in the Source control tab to see the commit history. * ### Reverting one file to a previous state * Sometimes you want to revert just one file to its previous state * You can use `git checkout -- ` to time travel just that file to how it was in that commit * For this, you of course need to figure out the commit hash you want to return to * Find that out in one of the following methods: * `git log --oneline` * VS Code: *Source control > File history* * Check commits from GitHub/GitLab ## Exercise 3. Branching team effort Work as a group for this assignment. Continue Exercise 2 from [Git basics](1-git-basics). 1) Every group member: create your own individual branch from the `main` branch 2) Then, make some changes to the `GitTest.md` file. * Do not tell other group members what you're going to change! :D 3) Add new files as well, at least one file per group member. 4) Then, merge the changes back to the `main` branch. Fix ensuing conflicts, if any appear. ## Reading * [Pro Git book](https://git-scm.com/book/en/v2) * [Oh shit Git](https://ohshitgit.com/) * [Undo possibilities](https://docs.gitlab.com/ee/topics/git/numerous_undo_possibilities_in_git/) ## Very extra: `git rebase` * `git merge` creates a new commit for the merge process * Sometimes that's undesirable, so an alternative is to use `git rebase` * Unlike merge, rebase applies changes from the rebased branch ***one commit at a time*** * Whenever there's a conflict: 1) After fixing the conflict, add the conflicting file with `git add ` 2) Then continue the rebase process with `git rebase --continue` 3) If you want to disregard a conflicting commit, use `git rebase --skip` 4) If you get cold feet, you can cancel the rebase with `git rebase --abort` ## Very extra: Interactive `git rebase -i` * `git rebase` also has a hidden ***interactive*** mode used with `git rebase -i` * This is a true swiss army knife of a Git tool for rewriting Git history * It can: * Squash commits into one * Cherry-pick only certain commits * Reword commit messages * And more! * See [Hackernoon: Beginner’s Guide to Interactive Rebasing](https://hackernoon.com/beginners-guide-to-interactive-rebasing-346a3f9c3a6d) ## Very very extra: Git submodules * To add external code to your project from someone else's repository, Git has a neat system called *submodules* * To add a submodule to your project, use `git submodule add ` * To remove a submodule, use `git rm ` * If you clone a project with submodules, you need to run `git submodule update --init --recursive` once. * ***Note:*** If you don't want submodules to appear in the Source control tab of VS Code, go to settings and disable the *Git: Detect Submodules* setting.