Tìm hiểu về một số khái niệm trong Git

Giới thiệu

Đối với các lập trình viên, việc sử dụng công cụ quản lý phiên bản git là điều không thể thiểu trong công việc hằng ngày và là một trong những kĩ năng cơ bản nhất mà ai cũng cần có được. Git có rất nhiều các khái niệm khác nhau nên nếu bạn chỉ là người mới làm quen với git thì đây chính là bài viết dành cho bạn. Bài viết sẽ giải thích tại sao chúng ta nên sử dụng git cũng như một số khái niệm thường gặp khi sử dụng git.

Tại sao cần sử dụng git?

  • Thử tưởng tượng khi làm một project và bạn muốn bổ sung hoặc làm thêm tính năng mới cho project của mình. Tuy nhiên để đảm bảo rằng bạn có thể quay lại sử dụng phần code trước đó bạn đã code trong trường hợp tính năng mới gây lỗi và bạn không nhớ phải xóa những gì để khổi phục lại trạng thái code trước đó thì bạn cần phải thực hiện copy toàn bộ project đó và paste ra đâu đó để lưu trữ rồi mới bắt đầu thực hiện code tính năng mới. Như vậy mỗi lần muốn làm tính năng mới, bạn phải lặp đi lặp lại thao tác trên và rất mất thời gian. Sử dụng Git có thể giải quyết vấn đề này chỉ trong 1 vài dòng lệnh
  • Nếu project bạn đang làm có 2 thành viên cùng làm, mỗi lần một người hoàn thành một tính năng lại phải gửi toàn bộ source code đó thông qua usb, google driver hoặc công cụ lưu trữ online nào đó cho thành viên còn lại có thể download về và paste đè lại lên phần code của người đó. Công việc này cũng mất rất nhiều thời gian và tồn tại nhiều rủi rõ khi paste code chồng lên nhau. Để giải quyết vấn đề này, ta cũng có thể sử dụng git và một remote repository

Các khái niệm cơ bản trong git

1. Repository là gì?

  • Khi sử dụng git, lệnh đầu tiên mà chúng ta thường gõ:
$ git init
  • Lệnh này sẽ tạo ra một thư mục ẩn có tên .git và đây chính là repository (hay kho chứa). Còn phần code hay hay thư mục của project nằm cùng với thư mục .git được gọi là Working Directory. Git sử dụng repository này để lưu trữ, giám sát toàn bộ thông tin về các trạng thái của và bất cứ thay đổi nào với project lúc này sẽ được git lưu trữ lại.
  • Có hai loại repository gồm local repository - là repository nằm trên chính máy tính của chúng ta và remote repository - là repository nằm trên một máy chủ từ xa được cung cấp bởi các nhà phân phối như Github, Gitlab hay Bitbucket, ...

2. Branch là gì?

  • Như đã nói ở trên về vấn đề khi ta muốn thêm một tính năng mới mà đảm bảo vẫn có thể dễ dàng khôi phục lại trạng thái trước đó thì ta có thể sử tạo 1 branch mới nhau sau:
$ git branch <tên-branch>  

hoặc

$ git checkout -b <tên-branch> 
  • Branch mặc định là master
  • Branch mới được tạo ra sẽ chứa toàn bộ trạng thái và những thay đổi đã thực hiện trên project trước khi được tạo
  • Với mỗi repository ta có thể tạo nhiều branch khác nhau và các nhánh này là độc lập với nhau nên khi ta có thay đổi đối với project trên branch này sẽ không ảnh hưởng đến các branch khác
  • Khi tính năng được ta thử nghiệm trên nhánh mới hoàn thiện và đã được kiểm tra đầy đủ, ta có thể tiến hành hợp nhất (đưa những thay đổi của nhánh này gộp vào với nhánh khác) 2 nhánh với nhau bằng
  • Có hai loại branch là local branch - là branch nằm trên máy tính của chúng ta và remote branch - là branch nằm trên máy chủ từ xa

3. Làm thế nào để xóa một branch?

  • Trong trường hợp branch chúng ta tạo ra trước đó không còn cần thiết nữa, ta có thể tiến hành xóa chúng đi bằng cách sử dụng các lệnh như sau:

    • Đối với local branch:
    $ git branch -d <tên-branch>
    

    Với cách xóa trên, nếu branch cần xóa chưa được gộp thay đổi với branch khác sẽ lập tức báo lỗi vào yêu cầu gộp với branch khác trước khi thực hiện xóa bằng lệnh này

    $ git branch -D <tên-branch>
    

    Với cách xóa này thì branch được chỉ định sẽ lập tức bị xóa kể cả trong trường hợp nó chưa được gộp với branch khác

    • Đối với remote branch:
    $ git push --delete <tên-remote> <tên-branch>
    

    hoặc

    $ git push <tên-remote> --delete <tên-branch>
    

Lưu ý: đối với cả loca branch và remote branch ta có thể tiến hành xóa đồng thời nhiều branch bằng cách liệt kê tên các branch cần xóa liền nối tiếp nhau và cách nhau một khoảng trắng

4. Push local branch lên remote server với một tên khác

  • Thông thường khi chúng ta tiến hành push một local branch lên remote server thì tên branch mặc định của remote branch lúc này sẽ là tên của local branch, lệnh push như sau:
$ git push <tên-remote> <tên-branch>
  • Nhưng nếu ta muốn đổi tên của remote branch đó trên server thì ta cần sử dụng lệnh như sau:
$ git push <tên-remote> <tên-branch>:<tên-remote-branch>

5. Phân biệt rebase và merge

  • Khi muốn tiến hành gộp 2 branch lại với nhau, ta có thể sử dụng một trong hai lệnh sau:
$ git merge <tên-branch>

hoặc

$ git rebase <tên-branch>

Sẽ tiến hành gộp branch hiện tại với branch mà ta lựa chọn. Tuy có cùng chức năng là gộp nhánh nhưng cách hoạt động của mergerebase lại khác với nhau, ta có thể so sánh sự khác biệt thông đó như sau:

  • Giả sử ta có 2 branch cần gộp với nhau như hình sau:

  • Đối với sử dụng merge kết quả thu được sẽ như sau: Việc sử dụng merge sẽ tạo ra một commit mới là kết hợp từ 2 commit cuối cùng của 2 nhánh cần gộp vào với nhau Log commit sẽ không bị thay đổi và thứ tự các commit sẽ được sắp xếp theo thời gian tạo commit

  • Đối với sử dụng rebase kết quả thu được sẽ như sau: Rebase sẽ đưa toàn bộ branch Feature lên trên 'đầu' branch master Làm thay đổi lịch sử commit

6. Khác nhau giữa fetch và pull

  • Khi muốn cập nhật các thay đổi từ trên remote server về local repository ta cũng có hai cách để thực hiện điều này như sau:
$ git pull <tên-remote> <tên-remote-branch>

Lệnh này sẽ tiến hành kéo các thay đổi từ trên remote server về local của chúng ta đồng thời tiến hành merge các thay đổi đó ngay

$ git fetch <tên-remote> <tên-remote-branch>

Đối với lệnh fetch, các thay đổi từ remote server sẽ được kéo về máy nhưng không tự động merge vào source code của chúng ta mà chúng ta có thể thực hiện việc này sau khi đã review lại các thay đổi đó trước khi tiến hành merge. Các thay đổi này được đẩy sang một branch khác và ta có thể sử dụng lệnh:

$ git branch -a

Để xem được các branch sau khi fetch đồng thời cũng có thể checkout sang branch đó để xem các thay đổi.

  • Có thể hiểu đơn giản lại sự khác nhau giữa fetchpull như sau:
    • git pull = git fetch + git merge

7. Thế nào là git stash?

  • Trong quá trình chúng làm việc, có những lúc chúng ta đang code dở một chức năng nào đó nhưng bất ngờ ở một chức năng trên branch khác đang có lỗi cần phải sửa gấp và chúng ta muốn lưu lại thay đổi đã làm trên nhánh hiện tại nhưng không muốn thực hiện commit dư thừa thì git stash là lệnh mà chúng ta có thể dùng để giải quyết vấn đề này.
  • git stash cho bạn khả năng lưu lại trạng những thay đổi mà bạn đã tạo ra mà không cần thiết phải commit nó giúp bạn có thể dễ dàng chuyển sang nhánh khác làm việc và sau đó quay lại và tiếp tục những gì bạn đang làm ở nhánh đó.
  • Các lệnh liên quan đến git stash
    • Để lưu được những thay đổi mà không cần commit nó, ta cần thực hiện những lệnh sau:
    $ git add .
    
    Để đưa toàn bộ các thay đổi đó vào trạng tháy staged, sau đó sử dụng lệnh sau để lưu thay đổi đó mà không cần commit:
    $ git stash # hoặc "git stash save"
    
    • Để xem lại các thay đổi đã lưu, ta có thể dùng các lệnh sau:
    $ git stash list
    [email protected]{0}: WIP on <branch-name>: <lastest commit>
    [email protected]{1}: WIP on <branch-name>: <lastest commit>
    [email protected]{2}: WIP on <branch-name>: <lastest commit>
    
    • Để xem lại danh sách các lần đã lưu, trong trường hợp muốn xem nội dung thay đổi thì ta gõ lệnh sau:
    $ git stash list -p
    
    • Hoặc nếu muốn xem cụ thể nội dung thay đổi của một lần lưu cụ thể, ta dùng lệnh:
    $ git stash show "[email protected]{n}"
    # với n là lần lưu tương ứng trong danh sách
    # Lưu ý phần [email protected]{n} phải nằm trong cặp ngoặc đôi
    
    • Để lấy lại thay đổi được lưu trong danh sách trên ta dùng lệnh:
    $ git stash apply "[email protected]{n}"
    
    Hoặc lấy thay đổi gần nhất và xóa lần lưu đó
    $ git stash pop
    
    • Để xóa danh sách các thay đổi đã lưu, ta dùng lệnh:
    $ git stash drop '[email protected]{n}'
    
    Để drop một lần lưu chỉ định hoặc
    $ git stash clear
    
    Để xóa toàn bộ những lần đã lưu

8. Làm thể nào để xóa bỏ trạng thài vài commit gần đây?

Để thưc hiện công việc này chúng ta có thể sử dụng 1 trong 2 lệnh sau:

$ git revert <commit-hash-code>

Lệnh này sẽ tạo ra một commit mới đảo ngược lại những thay đổi trong commit được chỉ định.

$ git reset --hard <commit-hash-code>

Keehnh này sẽ xóa toàn bộ các commit trước đó và đưa branch hiện tại trở về trạng thái của commit-hash-code đã chọn

9. Gộp một vài commit thành một commit duy nhất?

  • Đôi khi trong lúc làm việc, ta thường tạo ra một số commit dư thừa và sau đó muốn gộp chung số commit đó lại với một message rõ ràng hơn về mục đích chung của toàn bộ các commit đó. Để làm được điểu này, ta có thể sử dụng những lệnh sau:
$ git rebase -i <commit-hash-code>

Với commit-hash-code là hash code của commit cuối cùng của nhóm cần gộp hoặc:

$ git rebase -i HEAD~<index>

Với index là số lượng commit cần gồm so với commit cuối cùng. Ngoài ra khi thực hiện việc rebase để gộp commit, ta có các lựa chọn khác như pick|squash|fixup để quyết định kiểu gộp. Cuối cùng ta cũng có thể dùng lệnh sau để gộp commit:

$ git reset --soft <commit-hash-code>
$ git add .
$ git commit -m"New commit"

Với commit-hash-code là mã hash của commit trước đó mà ta muốn gộp lại từ commit cuối cùng đến commit chỉ định.

10. Phân biệt giữa git reset, reset --soft, reset --hard

$ git reset <commit-hash-code>

Sẽ di chuyển HEAD về phía commit được chỉ định nhưng vẫn giữ nguyên trạng thái thay đổi của các file và đồng thời loại bỏ các file đó khỏi trạng thái staged

$ git reset --soft <commit-hash-code>

Tương tư như git reset nhưng toàn bộ các fle vẫn giữ được trạng thái staged.

$ git reset --hard <commit-hard-code>

Tương tự như 2 lệnh trên nhưng toàn bộ sự thay đổi của các file sẽ bị loại bỏ hoàn toàn nên hãy chú ý khi dùng lệnh này để tránh rơi vào trường hợp bao nhiêu công sức đổ xuống sông xuống biển 😄

Thế nào là cherry-pick?

  • Bạn có thể hiểu cherry-pick cũng có một số điểm tương đồng với mergerebase là lấy thay đổi từ một branch này và gộp vào branch khác. Nhưng điểm khác nhau lớn nhất giữa cherry-pickmerge, rebasecherry-pick chỉ gộp một commit được chỉ định từ một nhánh khác vào nhánh hiện tại trong khi mergerebase sẽ gộp toàn bộ các commit lại. Để sử dụng cherry-pick, ta cần xem lại log các commit sau đó lấy mã hash của commit cần được cherry-pickcheckout sang nhánh cần được gộp commit của mã hash kia và thực hiện lệnh:
$ git cherry-pick <commit-hash-code>
  • Một hình ảnh minh họa cho cherry-pick:
    • Giả sử ta muốn lấy commit C từ branch master và gộp vào branch cherry-pick
    • Sau khi thực hiện lệnh cherry-pick như đề cập ở trên, đây sẽ là kết quả ta thu được
    • Như ta có thể thấy commit C từ branch master được gộp vào với branch cherry--pick dưới tên commit là C'

Git flow là gì?

  • Git flow là một quy trình làm việc với git được thiết kế bởi Vincent Driessen. Git flow đưa ra một mô hình phân nhánh giúp hỗ trợ việc quản lý các dự án lớn dễ dàng hơn. Sơ đồ tổng quan:
Các branch trong gitflow:
  • Master branch: là branch dùng cho sản phẩm chính thức. Đây luôn là branch ổn định nhất và nó chưa lịch sử các lần release của dự án
  • Develop branch: là nhánh dùng cho sản phẩm trong quá trình phát triển
  • Feature: mỗi tính năng mới cho sẩn phẩm sẽ được tạo và phát triển trên một branch mới với tên quy ước feature/tên_branch. Các feature này sẽ tạo ra từ develop branch và khi được hoàn thiện sẽ được gộp trở lại với develop branch (Lưu ý: các Feature không được phép gộp trực tiếp với master branch)
  • Release: khi develop branch đã có đủ số tính năng cần thiết để có thể release, ta có thể tạo branch mới với tên quy ước release/tên_version. Branch này sau khi được tạo xong sẽ tiến hành merge nó với đồng thời cả master branchdevelop branch
  • Hotfix branch: khi sản phẩm trên master branch của chúng ta gặp phải trục trặc và cần có bản vá ngay lập tức thì ta sẽ tạo ra hotfix branch. Branch này tương tự như release branch nhưng nó được tạo ra từ master branch thay vì từ develop branch như release (*Chú ý hotfix branch cũng cần được gộp lại với master branch với develop branch)
Các lệnh trong gitflow
  • Để khởi tạo một git-flow cho một project, ta dùng lệnh sau
    $ git flow init
    
    • Lệnh này sẽ tạo ra hai branch ban đầu là master và develop
  • Để bắt đầu một feature ta dùng lệnh
    $ git flow feature start <tên-feature>
    
    • Sẽ tạo ra một branch mới có tên dạng feature/<tên-feature>
  • Sau khi feature đó được thực hiện xong, ta có thể công bố feature đó lên remote server để mọi người cùng có thể cập nhật bằng cách gõ lệnh:
    $ git flow feature publish <tên-feature>
    
  • Để tiến hành gộp branch đó vào develop branch ta dùng lệnh:
    $ git flow feature finish <tên-feature>
    
  • Để tạo một bản release ta dùng lệnh:
    $ git flow release start <verion-no>
    
  • Để tiến hành merge bản release đó vào master branchdevelop branch ta dùng lệnh:
    $ git flow release finish <version-no>
    
  • Để tạo một bản hotfix ta dùng lệnh:
    $ git flow hotfix start <tên-hotfix>
    
  • Sau khi bản hotfix hoàn thiện ta có thể tiến hành merge lại với master branchdevelop branch như sau:
    $git flow hotfix finish <tên-hotfix>
    

Kết luận

Bài viết ở trên chủ yếu giới thiệu cho mọi người về các khái niệm trong git cũng như cách sử dụng nó trong công việc thường ngày của mình một cách hiệu quả hơn. Cám ơn bạn đã theo dõi.