---
# System prepended metadata

title: M01W02_Git&GitHub for Version Control

---

# Git & GitHub for Version Control
## 1. Khái niệm VCS và Git

:::info
Version Control System là hệ thống theo dõi và lưu lại các thay đổi của tập tin theo thời gian. VCS giúp ghi lại ai thực hiện thay đổi, thay đổi gì và khi nào.
:::

![image](https://hackmd.io/_uploads/BylEmHLPxg.png)

![image](https://hackmd.io/_uploads/r12PmrUPxl.png)


## 2. Một số cấu hình ban đầu

![image](https://hackmd.io/_uploads/ByYf2SMDxl.png)

:::info
Các câu lệnh này đều có thể làm trên **Terminal** sau khi các bạn tải **Git** về (kể cả **Terminal** trong **VSCode**) nhưng mình sẽ làm trên **Git Bash** :+1: 
Bạn có thể dùng lệnh git ----version để kiểm tra xem đã cài đặt thành công chưa (nó hiển thị tên version hiện tại)
:::

```
git config -l
=> Dùng lệnh này để hiển thị cấu hình hiện tại
=> git config [--scope] [option name] [value]
Dùng lệnh này để cấu hình Git. 
Scope bao gồm:
--system: áp dụng với tất cả user
--global: áp dụng với mọi repo của user hiện tại
--local: chỉ áp dụng với repo hiện tại
```

**Ví dụ**: Đây là file **.gitconfig** sau khi các bạn làm phần cấu hình cơ bản theo ảnh trên, kèm một số alias khác các bạn có thể cài đặt theo ảnh.


![image](https://hackmd.io/_uploads/BJtd87Qwlx.png)

![image](https://hackmd.io/_uploads/SJy8sGmvex.png)

Xem trực tiếp trong file thì hiển thị dễ nhìn hơn xem trong Terminal.
## 3. Một số lệnh để quản lí file
### 3.1. Các câu lệnh help

Dùng khi bạn không hỏi được ChatGPT.

```
Chọn một trong số lệnh dưới đây:
git help
git --help
man git-[lệnh]
```

![image](https://hackmd.io/_uploads/rk8G4UfPlg.png)



### 3.2. Lệnh cd (change directory)

Dùng để chuyển qua lại giữa các thư mục (folder) trên máy tính thông qua **Terminal (hoặc Git Bash)**. Chỉ chuyển qua được các folder con của folder hiện tại, hoặc về folder cha với lệnh: 

```
 Lệnh cd .. chuyển về folder cha ngay trước đó.
```

**Ví dụ**: Khi chọn một folder nào đó, bạn nhấn chuột phải chọn **Open in Terminal** hoặc **Open Git Bash here** thì

![image](https://hackmd.io/_uploads/r1u4bLzwee.png)

hoặc

![image](https://hackmd.io/_uploads/Skqz-UMwlg.png)

![image](https://hackmd.io/_uploads/HJ4sW8fvgl.png)

### 3.3. Lệnh clear

Đơn giản để xóa bớt các câu lệnh để màn hình Terminal "sạch" hơn. Nhưng không có nghĩa là delete hết các câu lệnh trước đó, ta có thể truy cập lại thông qua phím mũi tên lên xuống.

![image](https://hackmd.io/_uploads/HJGa-8fwee.png)

### 3.4. Lệnh dir và ls

Đây là các câu lệnh dùng để hiển thị danh sách tập tin, folder bên trong folder hiện tại.

```
Lệnh dir (directory): dành cho Windows (CMD, Powershell), không hỗ trợ nhiều lựa chọn
Lệnh ls (list): dành cho Linux/MacOS và Git Bash, tùy chỉnh mạnh, tiện cho dùng nâng cao

ls              # Liệt kê cơ bản
ls -l           # Dạng chi tiết (kích thước, thời gian, quyền)
ls -a           # Hiện cả file ẩn (.git, .env)
ls -lh          # Kích thước dạng dễ đọc (KB, MB)
```
![image](https://hackmd.io/_uploads/ryLqtIMDee.png)

### 3.5. Lệnh mkdir

Tạo ra một thư mục mới.

```
mkdir [tên thư mục]
Nếu tên thư mục có chứa nhiều dấu cách thì viết trong dấu ' '
```

![image](https://hackmd.io/_uploads/BkkacLMDex.png)

![image](https://hackmd.io/_uploads/S1C0c8MDex.png)

### 3.6. Lệnh touch

Tạo file mới.

```
touch [tên file kèm phần file extension]
Nếu chứa nhiều dấu cách thì viết trong dấu ' '
```
![image](https://hackmd.io/_uploads/Sk59A8Mvgx.png)

![image](https://hackmd.io/_uploads/r1RFRUMPxl.png)

### 3.7. Lệnh echo

Xuất một nội dung gì đó. Tương tự lệnh cout trong C++ hay print trong Python. Có thể dùng kèm > để ghi vào file.

```
echo [nội dung muốn in] > [chỗ muốn in ra] 
=> Đây là ghi đè.
echo [nội dung muốn in] >> [chỗ muốn in ra]
=> Đây là ghi newline.
```
![image](https://hackmd.io/_uploads/Bk4A1DfPge.png)

![image](https://hackmd.io/_uploads/r1b-eDMvxe.png)

![image](https://hackmd.io/_uploads/H1dXWvzvgl.png)

![image](https://hackmd.io/_uploads/HyTEbwMDxx.png)

Ta cũng có thể tạo file mới bằng lệnh echo. Nếu phần [nội dung in] không có gì thì nó là file rỗng.

![image](https://hackmd.io/_uploads/HJIabPzPlg.png)

![image](https://hackmd.io/_uploads/r1CkzDGvel.png)

### 3.8. Lệnh cat

In và ghi nội dung vào file. Hơi khác so với echo.

```
cat [tên file]
=> Đây để đọc file.
cat > [tên file]
=> Đây để ghi nội dung vào file. Terminal sẽ xuống dòng chờ mình điền nội dung.
```
![image](https://hackmd.io/_uploads/BkTHvwGvxe.png)

![image](https://hackmd.io/_uploads/S1fDPPzwxe.png)

:::info
Nhớ ấn Ctrl + D để báo hiệu kết thúc việc nhập.
:::

### 3.9. Lệnh diff

So sánh sự khác biệt giữa 2 file.

```
diff file1 file2
```

![image](https://hackmd.io/_uploads/SJ5-2vfDle.png)

### 3.10. Lệnh rm (remove)

Remove tiếng việt có nghĩa là xóa.

```
rm [tên file]
rm -d [tên folder]
=> Chỉ dùng để xóa folder rỗng
rm -r [tên folder]
=> Xóa được cả rỗng và có dữ liệu
```
![image](https://hackmd.io/_uploads/Sy5_euMDeg.png)

Xóa hết 2 file, dùng lệnh ls xem thử thì chỉ còn file sample.txt. 

![image](https://hackmd.io/_uploads/Hy0QG_GPxl.png)

## 4. Quy trình làm việc Git cơ bản
### 4.1. Một số câu lệnh hay dùng

```
Nhấn phím q hoặc Ctrl + C để thoát khỏi một lệnh (tùy trường hợp)
git add [tên file] thêm vào staging area
git add . : thêm tất cả file vào staging area
git commit -m "nội dung" : tạo commit lên repo, kèm câu comment
git status: hiển thị trạng thái kho lưu trữ
git diff: so sánh với commit cuối cùng
git log: hiển thị lịch sử các commit
pwd: hiển thị đường dẫn tuyệt đối của thư mục hiện tại đang trỏ tới trong terminal
```

### 4.2. Khởi tạo và sao chép Repository

![image](https://hackmd.io/_uploads/H1CTi77wel.png)

Để xem thư mục ẩn **.git/** thì dùng lệnh **ls -a** đã giới thiệu ở mục trước.

Có 2 cách:
- Khởi tạo repo ở máy local rồi liên kết với github (repository online có tên trùng hay không trùng với cái local cũng được)
- Clone về từ github

![image](https://hackmd.io/_uploads/HyUytV7weg.png)

Sau khi tạo repo trên github thì có hiện lệnh để: tạo repo và liên kết / tạo liên kết với repo có sẵn trên local. 

Nếu clone một dự án về thì chú ý đường link là HTTPS hay SSH, SSH thì yêu cầu key nên nó đảm bảo security hơn.

Nếu tự tạo mới, thì bước tiếp theo là copy các lệnh theo trên github vào. Có thể đổi tên chỗ branch thành main, nếu có thay đổi thì chỗ push cũng phải đổi lại thành main.

```
git branch -M main
git push -u origin main
```

### 4.3. Theo dõi và lưu trữ thay đổi

![image](https://hackmd.io/_uploads/B1zg0wQwxe.png)

![image](https://hackmd.io/_uploads/SJ0-RDQDgg.png)

Kết hợp 2 ảnh để hiểu thông tin nha. 

**Ví dụ**:

![image](https://hackmd.io/_uploads/HyoQIOQDxe.png)

Ban đầu, chưa có file nào thì Terminal sẽ hiện trạng thái như trên.

![image](https://hackmd.io/_uploads/By8AD_Qwxe.png)

Khi tạo file mới thì lệnh git status trả về như trên ảnh, báo là untracked file.

![image](https://hackmd.io/_uploads/HkRntu7wel.png)

Sau khi dùng lệnh git add (bỏ qua mấy cái warning nha) thì nó hiện status là changes to be committed. Hiện file này đang ở staging area.

![image](https://hackmd.io/_uploads/BJX3qOXwle.png)

Đây là màn hình hiển thị và status sau khi bạn commit nó lên repo.

![image](https://hackmd.io/_uploads/BkaI3_7vlx.png)

Nếu file README có thay đổi, hoặc có file mới tạo ra thì lệnh **git add .** sẽ hiển thị như trên.

### 4.4. Xem lịch sử commit

![image](https://hackmd.io/_uploads/Hyp7ytXwxe.png)

![image](https://hackmd.io/_uploads/Hygket7wel.png)

Khi dùng lệnh git log nó sẽ hiển thị như trên.

Có một cách ngắn gọn hơn để xem:

```
git log --oneline
```

![image](https://hackmd.io/_uploads/rJ13kKmvee.png)

Đây là format riêng của Dr. Hà ;> có hiển thị trong mục 1, phần alias.

![image](https://hackmd.io/_uploads/Bk4kUY7wxl.png)

Ngoài ra nếu muốn hiển thị commit trực quan hơn thì ta có thể sử dụng **Git Lens**. Tìm trong kho extension của VSCode. Có thể restore, revert commit trong cửa sổ này luôn.

![image](https://hackmd.io/_uploads/HkQRalBDeg.png)

Hoặc có thể commit tại source control (extension của VSCode). Với chữ ADD 4 là chỗ comment.

![image](https://hackmd.io/_uploads/BJ3QWZSvxe.png)

### 4.5. Hoàn tác thay đổi

![image](https://hackmd.io/_uploads/SJDwP-BPee.png)

```
Các câu lệnh git reset (công dụng khác với restore)
git reset --soft <commit ID> 
=> Chuyển HEAD về vị trí commit, trạng thái của stage và tất cả sự thay đổi giữ nguyên.
git reset <commit ID> 
=> Chuyển HEAD về vị trí commit, giữ sự thay đổi của file nhưng loại bỏ các thay đổi stage.
git reset --hard <commit ID>
=> Chuyển HEAD về vị trí commit, loại bỏ tất cả sự thay đổi của file, stage.
```

| Tiêu chí                                     | `git reset`                                      | `git restore`                                 |
| -------------------------------------------- | ------------------------------------------------ | --------------------------------------------- |
| **Tác động đến HEAD**                     | Có thể di chuyển HEAD về commit trước hoặc sau | Không thay đổi HEAD                         |
| **Tác động đến staging area**             | Có thể bỏ staged file (`git reset <file>`)     | Có thể bỏ staged file (`--staged`)          |
| **Tác động đến working directory (file)** | Có thể xóa file (`--hard`)                     | Phục hồi file về trạng thái từ commit       |
| **Chủ yếu dùng để**                       | Điều khiển commit, staging, HEAD                 | Phục hồi nội dung file                        |
| **Có thể làm mất commit?**                | Có (với `--hard`, `--mixed`)                   | Không                                       |
| **Thay thế `checkout` cũ?**               | Một phần                                      | Được tạo ra để thay thế `checkout` cho file |


**Ví dụ**: Giả sử đây là file ở trạng thái modified nhưng chưa staged (tức là đã từng commit trước đây, nhưng giờ mình đang sửa đổi file đó) 

![image](https://hackmd.io/_uploads/rJwf_2Hvgx.png)

Để quay lại commit trước đó ta dùng **git restore**. File sẽ mất dòng missing context.

![image](https://hackmd.io/_uploads/SkdOdhSwxe.png)

Tiếp tục, nếu ta đã lỡ add file vào staged. Để bỏ staged thì dùng **git restore --staged**. Khi đó file về trạng thái modified (vì nó đã từng được sửa nên không thành untracked file).

![image](https://hackmd.io/_uploads/SJW4j3Svxx.png)

Khi chọn **git commit --amend**, nó sẽ hiển thị lên khung cửa sổ để chỉnh sửa.

![image](https://hackmd.io/_uploads/H1Tjj3Swxg.png)

Cuối cùng, hãy bàn về **git revert**. Lệnh này tạo ra một commit mới có tác dụng đảo ngược (undo) lại một commit trước đó, mà **KHÔNG** xóa commit cũ khỏi lịch sử.

```
Giả sử đang có 3 commit A -> B -> C
Bạn muốn quay về B. Thì dùng lệnh git revert C (mã commit).
=> Git tạo ra commit D mới, commit này có tác dụng undo C
Các commit sau cùng: A -> B -> C -> D
```
:::info
Lưu ý khi dùng git revert. Revert không làm mất commit cũ, nên có thể làm rối nếu revert nhiều lần → xem kỹ lại bằng git log. HEAD vẫn tiếp tục di chuyển, và sẽ ở commit undo.
:::

### 4.6. Làm việc với remote repository

![image](https://hackmd.io/_uploads/SyXQ86BPlg.png)

#### 4.6.1. Remote Repository là gì?

**Remote repository** là một bản sao của repository Git được lưu trữ trên một máy chủ từ xa (remote server), thay vì máy cục bộ của bạn. Nó là nơi bạn push code lên và pull code về để chia sẻ, cộng tác với người khác.

Có **2 loại remote repository**: một loại tạo trên **GitHub**, một loại tự tạo nội bộ (dùng làm server nội bộ). Repo ta tạo từ mục 3.2. là repo thường, đây là sự khác biệt của nó.

| Đặc điểm                      | Repository thông thường (`git init`)       | Bare Repository (`git init --bare`)         |
|------------------------------|---------------------------------------------|---------------------------------------------|
| 📁 Có working directory       | ✅ Có                                        | ❌ Không                                     |
| 📦 Cấu trúc thư mục           | Có thư mục `.git/` bên trong project        | Chính toàn bộ repo là `.git`                |
| 👨‍💻 Có thể chỉnh sửa mã nguồn | ✅ Có                                        | ❌ Không (chỉ chứa metadata Git)             |
| 📜 Cho phép `git add/commit` | ✅ Có                                        | ❌ Không                                     |
| 🧭 Mục đích sử dụng chính     | Làm việc cục bộ, phát triển dự án          | Làm repo trung tâm để clone/push/pull       |
| 🔗 Dùng làm remote repo       | ❌ Không nên                                 | ✅ Phù hợp                                   |
| 🔄 Cho phép push từ nơi khác | Không an toàn                               | ✅ An toàn, vì không có working tree         |
| 🗂 Được dùng trong GitHub     | ❌ Không                                     | ✅ Có — GitHub lưu trữ dạng bare repo        |

**So sánh công dụng chính**:
Repository thông thường:
- Phát triển cá nhân
- Tạo và quản lý mã nguồn
- Chạy project trực tiếp trong thư mục
```
my-project/
├── .git/
├── main.py
└── README.md
```
Bare Repository:
- Là nơi để các lập trình viên khác **push/pull** (giống như GitHub, Git server nội bộ)
- Dùng để **triển khai hệ thống Git server**
- Bắt buộc dùng khi không cần hoặc không nên có working directory

```
my-project.git/
├── HEAD
├── config
├── objects/
├── refs/
└── ...
```

#### 4.6.2. So sánh với bare repository của GitHub

| Đặc điểm                       | `git init --bare` (local server)                          | Repo trên GitHub                                               |
| ------------------------------ | --------------------------------------------------------- | -------------------------------------------------------------- |
| 📦 Là loại repo gì?            | **Bare repository** (chỉ metadata Git, không source code) | **Bare repository**, quản lý bởi GitHub                        |
| 📁 Có working directory không? | ❌ Không                                                   | ❌ Không (chỉ lưu metadata, không làm việc trực tiếp trên code) |
| 📍 Lưu trữ ở đâu?              | Trên máy cục bộ hoặc server riêng                         | Trên cloud (GitHub.com)                                        |
| 🌐 Truy cập qua                | File path (SSH/local): `/home/git/repo.git`               | HTTPS hoặc SSH: `https://github.com/user/repo.git`             |
| 👥 Hỗ trợ quản lý người dùng   | Thủ công (set quyền bằng hệ điều hành)                    | Có sẵn giao diện quản lý collaborator, tổ chức                 |
| 🔒 Tích hợp bảo mật            | Tùy cấu hình hệ thống                                     | Mặc định có xác thực, 2FA, quyền riêng tư                      |
| 🚀 Có giao diện web?           | ❌ Không                                                   | ✅ Có (xem code, commit, pull request, issue, v.v.)             |
| 🔧 Tích hợp công cụ khác       | Phải cấu hình thủ công (CI/CD, backup)                    | Có sẵn tích hợp CI/CD, Actions, Projects, Discussions, ...     |

#### 4.6.3. Cách tạo bare repository nội bộ

```
git init --bare: tạo một bare repo, gọi là central repo
git clone [repo name][clone name]: sao chép và liên kết repo name
git fetch: lấy các thông tin về commit mới từ central repo
git pull: lấy dữ liệu từ central về local repo
git push: đẩy các commit từ local đến central
```
![image](https://hackmd.io/_uploads/ryMrzNLPle.png)

Tạo bare repo là 1 folder tên CentralRepo.

![image](https://hackmd.io/_uploads/ryYhMNIvlx.png)

Clone từ cái central về folder local tên Dev1.

![image](https://hackmd.io/_uploads/r1dC7VLDgl.png)

Sau đó nên config lại folder Dev1, cho biết mình là người chỉnh sửa v.v

:::info
Nên nhớ rằng khi **git push**, thì dữ liệu sẽ được nén và mã hóa bằng mã **SHA-1** bên trong bare repo, do đó ta sẽ không mở folder đó ra và xem trực tiếp được dữ liệu bên trong. Dữ liệu thực chất được lưu trữ trong ``objects/``.
:::

#### 4.6.4. Git fetch và git pull

| Lệnh        | Làm gì?                                                                                                      | Có cập nhật code local không? | Khi nào dùng?                                                      |
| ----------- | ------------------------------------------------------------------------------------------------------------ | ----------------------------- | ------------------------------------------------------------------ |
| `git fetch` | Lấy **metadata** và **commit mới** từ remote (central), nhưng **không hợp nhất (merge)** vào nhánh hiện tại. | ❌ Không                       | Khi bạn muốn **xem trước** có gì mới từ remote trước khi hợp nhất. |
| `git pull`  | Là `git fetch` + `git merge` (hoặc `rebase`) → Tải về và **áp dụng ngay** các commit mới.                    | ✅ Có                          | Khi bạn muốn cập nhật **ngay lập tức** nhánh local theo remote.    |

Khác nhau ở chỗ merge và không merge với nhánh hiện tại. Tùy theo nhu cầu sử dụng mà ta chọn lựa.

#### 4.6.5. Xử lí xung đột trong Git

Đây là trường hợp xảy ra xung đột, khi Dev2 **không cập nhật sự thay đổi ở central repo bằng git pull mà lại git push lên đó một version khác**.

![image](https://hackmd.io/_uploads/rJc8XLDDxe.png)

![image](https://hackmd.io/_uploads/HJf-VIwvlx.png)

Sau đó khi dùng git pull, Git Bash thông báo nó sẽ cố gắng auto-merge để xử lí xung đột...nhưng failed, vì vậy yêu cầu mình tự sửa (fix conflicts and then commit) rồi mới commit.

![image](https://hackmd.io/_uploads/ry1Yr8Pvee.png)

Khi đó để sửa file thì ta mở file đó ra, Git sẽ hiển thị sự xung đột ngay trong file. Đoạn từ chỗ Head trở đi chính là đoạn cần sửa. Dấu === ngăn cách phần trên là ở local của ta, phần dưới là ở central repo lúc đó.

![image](https://hackmd.io/_uploads/ByKjUIwvge.png)

Thì sửa là xóa đoạn đó đi rồi viết lại thôi á :+1: rồi commit, rồi push lên. Đây là một tình huống, còn nhiều tình huống khác thì hỏi **ChatGPT**.

:::info
Nếu ta dùng lệnh **git fetch** thay cho **git pull** thì sẽ an toàn hơn vì khi đó nó chỉ lấy dữ liệu về chứ chưa **merge** vào nhánh local. Khi đó sẽ kiểm tra được bằng git status là nhánh mình có up-to-date chưa, để biết trước sẽ xảy ra lỗi.
:::

#### 4.6.6. Các lệnh khác

![image](https://hackmd.io/_uploads/S1zGqrIwgl.png)

Chi tiết hỏi ChatGPT nha tui mệt quá. Các lệnh này là để tương tác với repo trên **GitHub**.

## 5. Quản lí nhánh và lịch sử
### 5.1. Khái niệm nhánh (Branch)

![image](https://hackmd.io/_uploads/B1hMl8Pvxe.png)

### 5.2. Câu lệnh git checkout

```
git checkout [commit-hash]
```
Dùng để chuyển trạng thái của working directory (thư mục làm việc) và staging area về đúng thời điểm của commit đó, nghĩa là:

- Bạn có thể xem lại mã nguồn như tại thời điểm commit đó.
- Git sẽ thay đổi tất cả file trong thư mục làm việc của bạn sao cho khớp với snapshot của commit đó.
- **Không thay đổi lịch sử commit**, mà chỉ “quay lại” để bạn xem hoặc thao tác tạm thời tại trạng thái đó.

Giả sử đang có lịch sử commit như này

![image](https://hackmd.io/_uploads/BkziMqdPgl.png)

Sau khi dùng lệnh git checkout với mã commit như trên thì ta có màn hình như sau:

![image](https://hackmd.io/_uploads/rkVfQ5Ovxe.png)

Có thể hiểu dạng như là đưa **HEAD** về lại bất kì commit nào. Kể cả khi các bạn commit mới nhánh đây thì vẫn được bình thường, **HEAD** vẫn sẽ trỏ đến commit mới. Để làm việc an toàn hơn thì ta có khái niệm nhánh.

:::info
Lưu ý khi ở trạng thái **"detached HEAD"**:
- Bạn có thể chỉnh sửa code, build, test thoải mái.
- Nhưng nếu bạn commit, thì commit đó sẽ không gắn với nhánh nào.
- Nếu bạn không tạo nhánh mới, commit đó có thể bị mất sau này.
:::

### 5.3. Tạo, chuyển và gộp nhánh
#### 5.3.1. Tạo và chuyển nhánh

![image](https://hackmd.io/_uploads/r1KS1puPex.png)

feature/login trên ảnh là tên nhánh á.

![image](https://hackmd.io/_uploads/HJEArfqvlg.png)

```
git branch [branch's name]: tạo nhánh
git branch: xem tất cả nhánh local
git branch -a: xem cả nhánh local + remote
git checkout [branch's name]: chuyển nhánh
```
![image](https://hackmd.io/_uploads/H1dDXT_vee.png)

Để ý thấy nếu đang đứng nhánh nào thì nó tô xanh nhánh đó, và đổi cả phần trong dấu ngoặc () thành tên nhánh.

Khi ta làm việc với nhánh mới và push lên thì có vấn đề. Ta phải dùng thêm câu lệnh khác. **Lý do:** Git không biết "push lên đâu" vì nhánh mới chưa được liên kết với một nhánh từ xa (upstream) nào.

```
git push --set-upstream origin [branch's name]
```

![image](https://hackmd.io/_uploads/SykLrT_Deg.png)

Đây là sự khác biệt.

| Lệnh                                      | Khi nào cần                                         | Chức năng chính                            |
| ----------------------------------------- | --------------------------------------------------- | ------------------------------------------ |
| `git push`                                | Khi nhánh local đã gắn với remote branch (upstream) | Đẩy thay đổi                               |
| `git push --set-upstream origin [branch]` | Khi tạo nhánh mới chưa được theo dõi                | Đẩy nhánh lên remote và thiết lập tracking |
| Với bare repo                             | Nếu clone từ bare thì push bình thường được         | Vì Git tự thiết lập upstream khi clone     |

:::info
Mặc định khi liên kết với remote repo, nhánh main ở máy local "tương đương" với nhánh main ở remote repo. Tức là khi bạn push từ nhánh main local lên remote repo thì nó thay đổi nhánh main ở remote repo đó. Còn nếu push nhánh khác không phải nhánh main thì nó không ảnh hưởng. Vì vậy mới cần câu lệnh set upstream để liên kết với nhánh khác main trên remote repo.
Nên nhớ rằng nhánh main là nhánh quan trọng nhất, là nhánh chốt cuối cùng, dùng để deploy sản phẩm sau cùng.
Có một cách để tự động hóa việc này (không cần dùng lệnh set upstream)
:::

![image](https://hackmd.io/_uploads/HyHGEAdDgg.png)

#### 5.3.2. Vấn đề 1

![image](https://hackmd.io/_uploads/BkgSSCODgl.png)

Ở trường hợp trên, ta tạo file **f3.txt** ở nhánh master, nhưng chưa commit. Và ngay sau đó ta chuyển sang nhánh 1, dùng lệnh ls để xem list file, và **f3.txt** cũng ở trong nhánh 1. **Do đâu?**

- Vì Git coi **f3.txt** là thay đổi chưa được commit (uncommitted change).
- Khi bạn chuyển sang nhanh_1, Git giữ nguyên file đó trong thư mục làm việc, vì nhanh_1 không có thay đổi gì mâu thuẫn với **f3.txt**

=> Git nghĩ bạn vẫn đang làm dở, nên không xóa mất file của bạn

#### 5.3.3. Vấn đề 2

![image](https://hackmd.io/_uploads/rkLpi0uDgx.png)

Giả sử ta **pull** (hoặc **fetch**) từ remote repo về, với điều kiện là trên remote repo nó có các nhánh khác ngoài master. Dùng lệnh git branch để xem các nhánh, thì nó không hiển thị nhánh 1 dù đã pull. **Do đâu?**

**Git** sẽ tải về tất cả các nhánh từ xa (remote branches) nhưng không tự tạo local branch tương ứng cho chúng. Điều này nghĩa là:

- Nhánh **nhanh_1** đã được fetch từ remote và lưu ở dạng origin/nhanh_1
- Nhưng local repo của bạn chưa có nhánh nhanh_1

Sau khi dùng lệnh checkout, Git sẽ tự tạo local branch nhanh_1, gắn nó với remote branch origin/nhanh_1 (tạo "tracking") và chuyển bạn sang nhánh đó luôn. Từ đó thì nhánh 1 sẽ hiện lên trong local, mang các file như trên remote repo.

#### 5.3.4. Gộp nhánh (Merge)

Giả sử có các nhánh như sau:

![image](https://hackmd.io/_uploads/rJlf219Pgl.png)

Khi dùng lệnh merge nhánh B vào nhánh A (đứng ở nhánh A và dùng merge B) thì nó sẽ thành như sau:

![image](https://hackmd.io/_uploads/HkbShy5vgl.png)

Để dễ hiểu, xem như file nhánh chính là:

```
A1
A2
A3
A4
```
File nhánh B là:

```
A1
A2
B1
B2
```

Khi merge lại nó sẽ gặp conflict như này

```
A1
A2
<<<<<HEAD
A3
A4
====
B1
B2
>>>>>
```

Tại vì cùng dòng đó trong một file mà lại có 2 cách ghi khác nhau, Git đưa cho mình lựa chọn. Sau khi sửa xong thì git add rồi commit, rồi mới push lên remote repo.

:::info
Merge thường không conflict. Conflict chỉ xảy ra khi cả hai:
- Cùng sửa một chỗ
- Không cùng hướng (khác nội dung)

Với trường hợp nhánh A sửa file A, nhánh B sửa file B thì merge lại không ảnh hưởng gì nhau.
:::

:::info
**Merge để làm gì?** Thường trong các dự án, mỗi branch sẽ là mỗi feature, khi đó merge là để áp dụng thay đổi trên mỗi feature đó vào nhánh main - nơi được chọn để deploy và chia sẻ với team. Với mỗi thay đổi, thì sẽ có một người khác test thử branch đó ok để merge vào chưa (giải quyết conflict và test).
:::

#### 5.3.5. Gộp nhánh trên GitHub

![image](https://hackmd.io/_uploads/SyxwgZ9Pgg.png)

Khi bạn push 1 branch mới lên GitHub, nó sẽ có nút **Compare & pull request**. 

![image](https://hackmd.io/_uploads/rJd6xWcvge.png)

Nó tương đương với việc merge branch đó vào main, nhưng có tương tác với team hơn là dùng lệnh merge ở local.

![image](https://hackmd.io/_uploads/ByqSW-cPlg.png)


Bạn để ý dòng ... **wants to merge 1 commit into main**. Nó thông báo với team là bạn định merge, và để team kiểm tra commit đó.

![image](https://hackmd.io/_uploads/r12FyM9vxe.png)

Có 3 loại merge như trên:
- **Create a merge commit**: merge thông thường, gồm đủ các commit
- **Squash and merge**: gom lại tất cả commit thành một commit duy nhất rồi merge, cho gọn
- **Rebase and merge**: theo dạng rebase, sẽ giải thích sau

### 5.4. Quản lí nhánh

![image](https://hackmd.io/_uploads/S1Q84zcwel.png)

:::info
Note thêm: Khi đi làm công ty, vì có quá nhiều branch, nên branch nào đã được phản ánh trong nhánh main rồi thì người ta sẽ xóa nó đi bớt.
Khi dùng lệnh xóa nhánh **git branch -d** bình thường thì nó chỉ xóa ở local. Khi đó ta cần xóa thêm lần nữa ở remote repo bằng câu lệnh git push origin --delete...như trên ảnh.
:::

### 5.5. Tái cơ sở cho một nhánh (Rebase)

![image](https://hackmd.io/_uploads/BkBg2Mqvll.png)

```
git rebase [branch's name]
Khi gặp conflict, nếu muốn bỏ cái commit bị conflict luôn thì ta dùng:
git rebase --skip
Ngược lại, nếu ta đã sửa conflict, muốn tiếp tục rebase thì dùng:
git rebase --continue
```

Giả sử ban đầu có các nhánh

![image](https://hackmd.io/_uploads/Byi8JQcwel.png)

Sau khi rebase thì nó sẽ thành

![image](https://hackmd.io/_uploads/BkTdJmqDgx.png)

Giống ví dụ ở mục **5.3.4.** Nó sẽ gặp conflict :v: Sửa lỗi và add, commit lại rồi dùng **git rebase --continue** như trong hướng dẫn.

![image](https://hackmd.io/_uploads/S1wnym5wxl.png)

Kết quả git log sau cùng: Nhánh A gồm A1 -> A4, còn nhánh B gồm A1 -> B2.

## 6. Một số skill khác
### 6.1. Cấu hình file .gitignore (chỉ định file, folder không nên theo dõi bởi Git)

Tác dụng của **.gitignore**: 
Ngăn Git thêm những file không cần thiết vào staging area.
Giúp repo sạch sẽ, tránh commit:
- File tạm (*.log, *.tmp)
- File biên dịch (*.class, *.exe)
- File cá nhân/máy (.env, node_modules/, __pycache__/)
- File cấu hình IDE (.vscode/, .idea/)

Bước 1: Khởi tạo repository (git init rồi cd vào folder đó)
Bước 2: Tạo file .gitignore (bằng bất kì lệnh nào)
Bước 3: Ghi vào file

| Cách viết            | Ý nghĩa                                           |
|----------------------|---------------------------------------------------|
| `*.log`              | Bỏ qua mọi file có đuôi `.log`                    |
| `/config.py`         | Chỉ bỏ qua `config.py` ở thư mục gốc              |
| `build/`             | Bỏ qua toàn bộ thư mục `build/`                   |
| `!important.txt`     | Không bỏ qua `important.txt` (dù match pattern)   |
| `**/temp/*.txt`      | Bỏ qua mọi file `.txt` trong các thư mục `temp/`  |


Ví dụ cho dự án Python: 

```
# Bỏ qua thư mục môi trường ảo
venv/
env/

# File tạm Python
__pycache__/
*.py[cod]

# File cấu hình cá nhân
.env
*.log

# File Jupyter
*.ipynb_checkpoints

# VS Code config
.vscode/

# File hệ điều hành
.DS_Store
Thumbs.db

```
:::info
Nếu file đã bị add rồi thì .gitignore sẽ không có tác dụng.
Có thể tự động tạo .gitignore trên GitHub.
:::

### 6.2. Tagging - Đánh dấu phiên bản

![image](https://hackmd.io/_uploads/ByR_qzvPgx.png)

Đây là giao diện sau khi push tag lên GitHub.

![image](https://hackmd.io/_uploads/B1xTRMwvgl.png)

### 6.3. Fork

Fork trong GitHub là hành động sao chép một repo về tài khoản của bạn, tạo thành một bản độc lập nhưng giữ nguyên commit history của repo gốc. Nó giống như bạn tạo một nhánh phát triển riêng tư cho repo đó. Bạn có toàn quyền chỉnh sửa repo đã fork, nhưng giống như bản copy, bạn không thể update hay đồng bộ với repo gốc. Nếu có thay đổi bạn phải tự pull về.


