--- tags: devtools2021 --- # Develop and Collaborate (Day1 - Morning) ## Install Git and Create Github Repository (15 mins) Git is an Open Source Distributed Version Control System. In short, it is a **content tracker** which allows you to track any changes in all your files: - Many developers can have a local copy and add code in parallel without conflicts - Developers can see version histories and unroll to previous "states" if project requirements change over time - Developers can "branch out" and work on different versions of the project at the same time The diagram below illustrates how Git works: ![](https://i.imgur.com/E2jBho9.png) * **Local Workspace** (or working directory) refers to the current working folder in your computer. It can contain any files that you have. * **Staging area** is a temp area containing the files that are going to be a part of the **next commit**, which lets git know what changes in the file are going to occur for the next commit. > Imagine a box. You can put stuff into the box. You can take stuff out of the box. This box is the staging area of Git. * **Repository**: Repositories in GIT contain a collection of files of various different versions of a Project. "Local" repository is in your computer, and remote repository is situated in some other server. > In other words, it is a database containing all the information needed to retain and manage the revisions and history of a project Repositories are stored in the working directory, specifically the . git/ subdirectory inside the Working Directory (usually hidden, you have to enable view on hidden files). Here's a sample screenshot inside ~/Desktop/FundamentalDevTools as the root working directory of all these `.js` files. ![](https://i.imgur.com/e16p8b9.png) Don't worry, we will get to try this out ourselves very soon. ## Create a remote repository on Github Finally, you can store your files in the "cloud" (we call this remote repository / server). This is what Github is for. > GitHub, Inc. is a provider of Internet hosting for software development and version control using Git. There are other alternatives as well, such as [BitBucket](https://bitbucket.org/product) and [GitLab](https://about.gitlab.com). It comes down to preference. **Create an account** on Github.com if you haven't already, and then **create a new repository** called `FundamentalDevelopmentTools`. Follow [this](https://docs.github.com/en/get-started/quickstart/create-a-repo) instruction up to Step 6. Here's some suggested settings: ![](https://i.imgur.com/1hrYN3l.png) * Select `.gitignore` template as anything that sounds familiar to you. We will edit this later. * You can tick "add a README file" so we can write some summary for others who visit our repo * Choose your repo to be "public" Click "Create repository" and you shall see something like this: ![](https://i.imgur.com/Z7QNJzX.png) You have just create a **remote repository** with 1 commit: the addition of these two files `.gitignore` and `README.md` into this remote repository. ## The very basics of Git (50 minutes) The next step to do is to **install Git** in your machines. You can download it from [here](https://git-scm.com/downloads). You can test if it is installed successfully using the command: ```bash= $ git --version ``` Output: `git version 2.24.3 (Apple Git-128)` ### Init Return to VSCode and create a new local git repository using the command: ```bash= $ git init ``` The git init command adds a local Git repository to the project. ### Staging changes Now we need to **add** the file `server.js`: > **Note**: This is the file we created from the [previous handout](https://hackmd.io/rg6-84vXQwqsoNWu-VQSwA#Install-Visual-Studio-Code-15-mins). ```js // server.js const http = require("http"); const host = '0.0.0.0'; const port = 8000; const requestListener = function (req, res) { res.writeHead(200); res.end("My first server!"); }; const server = http.createServer(requestListener); server.listen(port, host, () => { console.log(`Server is running on http://${host}:${port}`); }); ``` ```bash= $ git add server.js ``` This puts `server.js` to the **staging area** mentioned in the previous notes. In case you want to add multiple files you can use: ```bash= $ git add file1 file2 file3 ``` ### Committing changes Use the following command to commit the file: ```bash= $ git commit -m "feat: Add simple HTTP server to repo" ``` You can pretty much write any commit message that you want, but there are certain preferred **standards**, here's a [summary](https://www.freecodecamp.org/news/writing-good-commit-messages-a-practical-guide/): 1. *Specify* the type of commit: * **feat**: The new feature you're adding to a particular application * **fix**: A bug fix * **style**: Feature and updates related to styling * **refactor**: Refactoring a specific section of the codebase * **test**: Everything related to testing * **docs**: Everything related to documentation * **chore**: Regular code maintenance.[ You can also use emojis to represent commit types] 2. *Separate* the subject from the body with a blank line 1. Your commit message should not contain any **whitespace** **errors** 1. **Remove** unnecessary punctuation marks 1. Do not end the subject line with a **period** 1. **Capitalize** the subject line and each paragraph 1. Use the **imperative** mood in the subject line 1. Use the **body** to explain what changes you have made and why you made them. 1. **Do not assume** the reviewer understands what the original problem was, ensure you add it. 1. **Do** **not** **think** your code is self-explanatory 1. **Follow** the commit convention defined by your team ### Add remote repository We cannot immediately **push** because we have not specified the remote repository to push into. Here's a screenshot of the output so far: ![](https://i.imgur.com/AQQj8NN.png) We need to **add** remote repository first: ```bash= git remote add origin <repository url> ``` where `<repository url>` can be found from github remote repo we created previously (click the copy button) ![](https://i.imgur.com/Ku3A4Dx.png) Now simply push to the remote **branch** (`master` in this case), but it can be also `main` depending on your setting. ```shell= git push -u origin <branch> ``` This pushes the code from the **master** branch in the **local** repository to the **master** branch in the **remote** repository. ### Error Handling: Fail to Push At this point, you will be met with the error: ![](https://i.imgur.com/IJWjXCq.png) This is because the **remote** branch contains `.gitignore` and `README.md`, both of which we don't have locally. We can fix this by **pulling** remote contents first *before* we push. ### Pulling changes from remote repository Type the command: ```shell= git pull origin master ``` This is used to **pull** the **latest** **changes** from the remote repository into the local repository. The remote repository code is updated continuously by various developers, hence git pull is necessary. ### Error Handling: Fail to Pull There's yet another error when you attempt to pull because there was no **common commits**. ![](https://i.imgur.com/4MZS7TT.png) This is because the local repo and remote GitHub repo "started their lives independently". We want to allow them to be merged but we need to tell git to allow this by including the --allow-unrelated-histories option. ```shell= git pull origin master --allow-unrelated-histories ``` You will be prompted with the `vim` editor (or other commit editor like `nano`) to write your commit message. Write something like `feat: Merge local repo and remote repo`, and once you quit the editor you will see a success message: ![](https://i.imgur.com/YzU1lsQ.png) > If you don't know how to navigate `vim`, see [here](https://coderwall.com/p/adv71w/basic-vim-commands-for-getting-started) You can now see that you have the two files `.gitignore` and `README.md` in your local workspace too: ![](https://i.imgur.com/uJVIO34.png) ### Push to remote repository Now we want to add `server.js` to the remote repository so that others can `pull` this new change. We can retype the command: ```shell= git push -u origin master ``` You will be **prompted** with username and password to your Github account. This NOT the ordinary username and password you use to **log in** to your github account online. You need to generate a **personal access token**. Follow [this guide](https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token) closely. Once you have generated the token, **paste it** to the password prompt. Ensure that you store this token somewhere securely. In order for you to not **repeatedly** key in username-password each time you `push` to this remote repo, you can **tell your system** to enable auto storage of github credentials (automatically stored the next time you're prompt to key in username-password) using this command: ```shell= git config --global credential.helper store ``` There are many other alternatives than just using the personal access token, such as using [SSH authentication](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh). ### Checking status Use the following command to see the status: ```bash= $ git status ``` This can be used to find out information regarding what files are modified and what files are there in the staging area. This can come in very handy to see current changes, untracked files, etc. ## Exercise: Adding files to remote repository (10 minutes) Create a new file in `DevTools` folder called `html.js`: ```javascript= // Sample server that serves direct HTML const http = require("http"); const host = '0.0.0.0'; const port = 8000; const requestListener = function (req, res) { res.setHeader("Content-Type", "text/html"); res.writeHead(200); res.end(`<html><body><h1>This is HTML</h1></body></html>`); }; const server = http.createServer(requestListener); server.listen(port, host, () => { console.log(`Server is running on http://${host}:${port}`); }); ``` and create another file called `index.html`: ```javascript= <!DOCTYPE html> <head> <title>My Website</title> <style> *, html { margin: 0; padding: 0; border: 0; } html { width: 100%; height: 100%; } body { width: 100%; height: 100%; position: relative; background-color: rgb(236, 152, 42); } .center { width: 100%; height: 50%; margin: 0; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: white; font-family: "Trebuchet MS", Helvetica, sans-serif; text-align: center; } h1 { font-size: 144px; } p { font-size: 64px; } </style> </head> <body> <div class="center"> <h1>Hello Again!</h1> <p>This is served from a file</p> </div> </body> </html> ``` Save both files and run the command `node html.js`, go to http://0.0.0.0:8000 on your web browser and you should see something like this: ![](https://i.imgur.com/t3ydzIi.png) Now **add** and **commit** these two files: `html.js` and `index.js` to your remote repository as an exercise (10 minutes). You can use `git status` to see the current state: ![](https://i.imgur.com/Biq7G8z.png) You should see both files added in your remote repository as follows: ![](https://i.imgur.com/2AatErz.png) ## Making Changes and Fixing errors (50 minutes) ### Editing Tracked Files Suppose you want to change the message sent in `server.js` into: ```java= const requestListener = function (req, res) { res.writeHead(200); res.end("My first server! Hello world!"); }; ``` When you **save** and check `status`, you will see some changes because `server.js` is tracked and part of the local repository. ![](https://i.imgur.com/IBXQ1hY.png) You can then proceed with `add`, `commit`, then `push` as usual. Your remote should look like this by now. Git keeps track of every commit in terms of **hash values** as shown on the right. ![](https://i.imgur.com/51ncnf4.png) ### Restoring changes If you have modified the file but have yet to add or commit them, you can simply type `git restore <filename>` to remove the changes to match the current local repository's state. ### Unstaging changes You can use `git reset` to unstage everything that you have **added** but not yet committed: ![](https://i.imgur.com/cl7bOvR.png) ### Undoing commits There are many options to do this, but suppose you just want to undo the **last** commit while keeping the changes then you can do: ``` git reset HEAD~1 ``` > HEAD is just a pointer to the latest commit. When you do a git reset HEAD~1, you tell Git to move the HEAD pointer back **one** commit. But (unless you use --hard) you leave your files as they were. Your changes are still there. If you want to **permanently** remove your last change then you can do: ``` git reset --hard HEAD~1 ``` ### Reverting to previous commits You can use the command `git log` to view all previous commits and hash values: ![](https://i.imgur.com/SC8kJEs.png) > press q to quit the console Also, try this *magic*: `git log --graph --all --oneline --decorate` for better output. Suppose we don't want the change from where we added the simple HTTP server + HTML file, we can use the command: ``` git revert <commit hash> ``` Both `index.html` and `html.js` will be deleted, but the new message `"My first server! Hello world!"` in server.js **stays** intact. ![](https://i.imgur.com/XpA0eyX.png) ==It's important to understand that git revert undoes a **SINGLE** commit—it does not "revert" back to the previous state of a project by removing all subsequent commits.== > If you want to remove ALL subsequent commits from then onwards, let's say in the above example, we want to remove BOTH the last two commits `8ae156` and `ec97d9`, then you can do: > ``` > git reset --hard HEAD~2 > ``` At this stage, our local repository is **ahead** of the remote repository by 1 commit because we simply **revert** and not **push** yet. ![](https://i.imgur.com/RwQBaQP.png) The effect of **revert**: We only have `server.js` because the commit that added `index.html` and `html.js` was REVERTED. ### Rebasing Another basic and useful tool is to **rebase**, which gives us plenty of options to decide what to do with many complicated commits in the past. Make a couple of redundant commits one by one (line 2, then line 3 comment) as follows: ![](https://i.imgur.com/gJ5WAC2.png) ![](https://i.imgur.com/SJ3129j.png) This results in the following output in `git log`. Right now the local repository is 3 commits **ahead** of the remote one. ![](https://i.imgur.com/m651MAC.png) Then type `git rebase -i HEAD~5` and you will see a list of the past 5 commits: ![](https://i.imgur.com/J9cht6l.png) You can choose to drop some commits and leave the rest by changing `pick` to `drop`. Change the **third** line "Revert" commit to "drop": ![](https://i.imgur.com/O9pIi4J.png) This results in both `index.html` and `html.js` being **back** into our local repository because we **dropped** the **revert** commit. We keep the last two commits that added comments intact in `server.js`. ### Merge Conflict What if there are **conflicting** choices? To witness conflicts first hand, attempt to remote the **second last** commit, while keeping the last one intact: ![](https://i.imgur.com/hv04JzQ.png) This results in a **merge conflict**, as shown: ![](https://i.imgur.com/t8yPRNo.png) Your file `server.js` also contains weird things that starts with <<<<<<< and ends with >>>>>>: ![](https://i.imgur.com/igvjIXS.png) This is because there are **two conflicts**: 1. Removing the second last commit removes the line `//test`,, which results in empty space (line 3 shown) 2. However, the last commit **expects** that `//test` is there, and that t`//test again` is added *after*. We can now **manually choose** which version to keep by doing: * Option 1: Keeping line 3 (delete 2, 4-8) * Option 2: Keeping line 5-7, (delete 2-4, 8) Suppose we go with Option 2, we will end up with: ![](https://i.imgur.com/xBialDW.png) After that, as per the conflict message, we need to **add back** `server.js` and run `git rebase --continue`: ![](https://i.imgur.com/L3FZP5A.png) This **rebase** still **deletes** the second last commit, and **keep** the last one (you fix the conflict manually). Once the rebase and conflict merge is fixed, **push** the changes to your remote repository. You should have this in your remote commit history: ![](https://i.imgur.com/4yYUTE0.png) ## Exercise: Undo commits (10 mins) Undo the last commit "test: add MORE comments in server.js" that you have pushed to the remote, and update the remote. You should have the following commit history in your remote: ![](https://i.imgur.com/DAiRuCD.png) and `server.js` does not contain the extra comments anymore: ![](https://i.imgur.com/5uQMsOV.png) If you'd like to do deeper reading, you can visit [here](https://www.atlassian.com/git/tutorials/undoing-changes/git-revert). It is **dangerous** to mess with commands like`reset` without being fully conscious when using them. > For example, **reverting** has two important advantages over **resetting**. First, it doesn’t change the project **history**, which makes it a “safe” operation for commits that have already been published to a shared repository. ## Collaboration (20 mins) In order to collaborate with your teammates, you can add a **collaborator** in the remote repository so that your teammate can have access to it. Go to **settings** in your remote repo page and click on Settings, then click on Manage Access: ![](https://i.imgur.com/DhNWvIN.png) Now you are free to add a few other people to collaborate together. As an exercise, add someone in this class as your collaborator. An email will be sent and now both of you can manage this repository together. ### Branching It is a common practice to *develop* in multiple **branches**. Right now there's only one branch which is either called the **master** branch or the **main** branch, depending on your setting. Suppose we want to work on the backend and frontend using the same starter code in parallel. We need to create a new branch for this using the `checkout` command with the `-b` parameter: ``` git checkout -b frontend git checkout -b backend ``` Now you are at the `backend` branch, and there exists three **local** branches in total: - main/master (remote has this too) - frontend (doesn't exist in remote) - backend (doesnt exist in remote) You can type the command `git branch` to show all local branches or `git branch -r` to show all remote branches. The one with the asterisk indicates the placement of the HEAD: ![](https://i.imgur.com/uDmoOXj.png) Now create a file called `backend.js` and type some comments: ![](https://i.imgur.com/qxOCNcX.png) Then, `add` and `commit` the file. ![](https://i.imgur.com/9K3GZrt.png) You can push this branch to remote using the command: ``` git push -u origin <branchname> ``` ![](https://i.imgur.com/G1Qkmmz.png) This results in two branches existing in our remote: ![](https://i.imgur.com/zmBuBDL.png) Now switch to the `frontend` branch using the command `git checkout frontend`, and create a file called `frontend.js`: ![](https://i.imgur.com/GYOPLd9.png) Notice that we no longer have `backend.js` because that file **only belongs to the backend branch**. ![](https://i.imgur.com/Y4RbD7p.png) ==What happens now if we push to master? Why is that so?== ![](https://i.imgur.com/6aivbxC.png) Then push the `frontend` branch to remote. ![](https://i.imgur.com/SdHW7ZZ.png) At this point, this is the graph of our commits with all three branches existing in **both local and remote repository**, and **HEAD** at `frontend` branch. ![](https://i.imgur.com/IIS6g2u.png) We can change the HEAD (i.e: where we are looking at) using the aforementioned command: `git checkout <branch>`. ### Merging branch In order to **merge** the workings of `frontend` and `backend` branches, you need to `checkout` to the **destination** branch, e.g: `master` and merge using `git merge <branch>` command: ![](https://i.imgur.com/vROGnvm.png) As a result, the **master (local)** branch is pointing to the commit where we added `backend.js`. The local master is no longer "behind" the backend branch. This is also called a **fast forwarding** (no extra commits, just simply moving the HEAD). > You can do `git merge backend --no-ff` to prevent fast forwarding and create a new commit instead. ![](https://i.imgur.com/H2OIuI1.png) The interesting thing that happens is when we try to merge the `frontend` to master. This results in the creation of a new commit: ![](https://i.imgur.com/pdAeX4g.png) ![](https://i.imgur.com/sqCPOCX.png) The final step to do after this is to simply update the `remote master`: `git push -u origin master`. ## Exercise: Handling branch merge conflicts (10 mins) Suppose we change `backend.js` in `backend` branch by adding a comment in the second line: ![](https://i.imgur.com/khkALkS.png) And then push the changes to remote `backend`: ![](https://i.imgur.com/3GoS1BW.png) Afterwards, change to `master` branch and edit `backend.js`: ![](https://i.imgur.com/IheYSxD.png) Similarly, push the changes to remote `master`: ![](https://i.imgur.com/GB8yCPp.png) When you try to `merge` the `backend` branch to `master` branch local repositories, this results in a merge conflict: ![](https://i.imgur.com/A3E1U5D.png) ![](https://i.imgur.com/sxazXyM.png) Fix this merge conflict by end by accepting either line 2 **or** 4-5 (and deleting the rest), and update the remote master. **Study the `git log --graph --oneline` output**. The graph should look like this: ![](https://i.imgur.com/p2a24NW.png) This marks the end of our morning session. The next step to do is to **deploy** the server code so clients can have access to it. Please proceed to [this handout](https://hackmd.io/@Crimsonlycans/rkhvAZHZY) after the lunch break.