Git Quick Start Guide

Git快速上手指南

Our company recently set up an intranet server. From now on, all projects will use Git for version control. I’m documenting some common operations here for easy reference.

Install Git

Windows: Git installer/portable version can be downloaded from the official website. The difference between the installer and the portable version is that the portable version requires you to manually add GitBinaryPath/bin to the system’s Path.

Linux(debian): sudo apt-get install git

First-time Setup

After installation, launch git.exe (Windows), or directly enter in the terminal for Linux:

1
2
$ git config --global user.name "Your Name"
$ git config --global user.email "email@example.com"

Generate SSH Key (replace the email address with your own email address):

1
$ ssh-keygen -t rsa -C "youremail@example.com"

Then press Enter all the way (or set your own password). After execution, a .ssh directory will be created under C:\Users\${userName}\ (or ~/ in Linux), containing two files: id_rsa and id_rsa.pub.

id_rsa is the private key and must not be leaked. id_rsa.pub is the public key and can be safely shared with anyone.

Then add your SSH Key to your account on the remote Git (GitHub, GitLab).

Because GitHub needs to identify that the commits you push are indeed from you, not someone impersonating you, and Git supports the SSH protocol, GitHub can confirm that only you can push as long as it knows your public key.

Of course, GitHub allows you to add multiple Keys. Suppose you have several computers, and you commit sometimes at work and sometimes at home; as long as you add the Key from each computer to GitHub, you can push to GitHub from any of them.

Create Local Repository

Launch Git Bash and cd (or use Easy Context Menu to add GitBash to the system right-click menu) to the directory where you want to create a local repository and execute the following command:

1
2
// Initialize a directory
$ git init

Commit Files Locally

1
2
3
$ git add ${file_name}
// commit
$ git commit -m "commit message"

Add Remote Repository

1
2
3
4
5
// Add a remote repository (${repo_name} can be customized)
$ git remote add ${remote_repo_name} git@192.168.9.3:ayconanw/test-ue4.git

// example
$ git remote add origin git@192.168.9.3:ayconanw/test-ue4.git

Remove Remote Repository

1
2
3
4
5
// If you want to remove the remote repository associated with the local repository, you can execute the following command
$ git remote rm ${remote_repo_name}

// example
$ git remote rm origin

Push to Remote Repository

1
2
3
4
// ${branch} defaults to master (main branch) and can be modified (not recommended). -f forces submission ignoring conflicts.
$ git push ${remote_repo_name} ${branch}
// Pull updates from the remote repository, parameters are the same as above
$ git pull ${remote_repo_name} ${branch}

Push to Multiple Remote Repositories

There are two ways to push to two or more remote repositories in one step.
The first method is to use Git’s set-url --add command:

1
2
3
4
# Add the first remote repository
git remote add blog git@github.com:imzlp/blog-source.git
# Add the second remote repository. To add multiple URLs to the same remote name, use the set-url --add parameter
git remote set-url --add blog git@git.coding.net:visoinsmile/blog.git

The second method is to directly edit the .git/config file and add multiple remote repository addresses under the same remote repository name:

1
2
3
4
5
6
7
8
$ nano .git/config

# Edit .git/config, fill in multiple repository addresses
[remote "blog"]
url = git@github.com:imzlp/blog-source.git
url = git@git.coding.net:visoinsmile/blog.git

$ git push blog

Create New Branch

1
2
3
4
5
6
7
8
9
10
11
// -b means base, which is to create a new branch based on the current branch
$ git branch ${new_branch_name} -b
// Switch branch
$ git checkout ${new_branch_name}

// You can also use the git checkout command with the -b parameter to create and switch
$ git checkout -b ${new_branch_name}

// Equivalent to the following two commands:
$ git branch ${new_branch_name}
$ git checkout ${new_branch_name}

checkout has three uses: switching branches, discarding changes, and checking out from historical versions, which will be covered later.

1
2
3
4
5
# Create a local branch based on the remote origin/level
$ git checkout -b level origin/level
# The above command is equivalent to the following two
$ git checkout -b origin/level
$ git branch -m level

Push Local Current Branch to Remote Branch

1
2
// If the ${new_branch_name} branch does not exist remotely, it will be created automatically
$ git push ${remote_repo_name} ${new_branch_name}

Merge Branches

1
2
3
4
5
// First, switch back to the branch you want to merge into, for example, merge the dev branch into the master branch
$ git branch master
// Merge the dev branch into the current branch
$ git merge dev
// After merging, you can delete the dev branch

Abort Merge

If there are conflicts between local and remote branches (or local branches), you can abort the merge to revert the local branch to its pre-merge state.

1
$ git merge --abort

Resolve Conflicts

In Git, if there are conflicts in text files, you can directly diff them when pulling, but it’s not as convenient if Git manages binary files.

If you want to undo changes from a local commit ${123456}, you can revert to a historical version, then use:

1
2
3
4
5
$ git reset --hard 123455
# Check out the files committed in 123456 into the 123455 commit
# This is equivalent to directly applying the file modifications from 123456 on top of 123455
$ git checkout 123456 .
# Then `reset`/`checkout` the files you don't want to commit

Binary File Conflicts

In Git, text file merges are directly visible, but binary files can be quite tricky.

If we want to keep the remote branch’s git.exe, we can execute the following command:

1
2
# Keep the remote version of git.exe
$ git checkout --theirs git.exe

The command to keep the local branch’s file is:

1
$ git checkout --ours git.exe

Then execute:

1
2
3
$ git add git.exe
# Assuming --theirs was executed above
$ git commit -m "Merge conflicts, keep remote version"

At this point, the local repository is merged with the remote, and the remote file version is preserved during the merge.

Delete Branch

1
2
3
4
5
6
7
8
// Delete local branch
// Note: Merge the branch before deleting it, otherwise an error will occur (or use -D to force deletion)
// error: The branch 'dev' is not fully merged.
// If you are sure you want to delete it, run 'git branch -D dev'.
$ git branch -D dev

// Delete remote branch
$ git push ${repo_name} --delete ${remote_branch_name}

Undo Deleted Branch

Two scenarios

  1. Terminal already exited

git reflog to view the commit SHA1 value of your last commit

1
$ git branch ${branch_name} ${SHA1}

You can create a branch based on your SHA1 value. For this commit, you can choose the commit SHA1 of the branch deletion operation.

  1. Terminal not exited

The SHA1 value will be displayed when deleting a branch.

1
2
$ git branch -d dev
Deleted branch dev (was ffe7c46).

Then you can use this SHA1 value to restore the deleted branch.

1
$ git branch ${branch_name} ffe7c46

Revert Version

1
2
// View all committed versions
$ git log --pretty=oneline

1
2
// Revert to a specific version
$ git reset --hard 版本号

1
2
// Revert a specific file to a specific version
$ git reset 版本号 filename

Undo Uncommitted File Changes

Undo Local Changes Only

Suppose we have made some modifications to files within a branch, and they have not yet been staged (git add).

After modifying the file, use the status command to check the file status.

Git prompts us that for files not added to the staging area, you can use git checkout -- to quickly undo local changes.

1
2
// Quickly undo local changes
$ git checkout -- ${filename}

Undo Both Local and Staged Changes

So, for files already added to the staging area, how do you undo local changes? First, use the status command to check the file status:

You can unstage first:

1
$ git reset ${filename}

Then undo local changes:

1
2
// Quickly undo local changes
$ git checkout -- ${filename}

One-step Quick Undo of Changes

1
$ git checkout HEAD -- ${filename}

Check Out Files from Historical Versions

Sometimes we commit new versions, but feel that some file changes are not appropriate. If we want to revert certain files to previous versions while keeping the rest in the new version’s state, we can use the checkout command.

1
2
3
4
5
6
7
// View historical committed versions
$ git log
// Check out files from historical versions to the current version
$ git checkout 历史版本号 ${filename}

// Check out file.txt from version bde5455fd58079f66f10d1526a579cda2be38190 to the current version
$ git checkout bde5455fd58079f66f10d1526a579cda2be38190 file.txt

Subsequently, you can commit them using add just like regular files.

Rename Local Branch

1
git branch -m ${CurrentBranchName} ${NewCurrentBranchName}

Clone Remote Repository

1
2
3
4
5
// Clone all branches of the repository
$ git clone ${remote_repo}

// To clone only one branch, you can specify the branch name with -b
$ git clone -b ${branch_name} ${remote_repo}

Switch to Remote Branch

After git cloneing a remote repository, running git branch will only show:

1
* master

You won’t see other branches, even if there are other branches on the remote repository.

You can use git branch -va to first list local/remote branches:

1
$ git branch -va

Switch to the remote origin/master branch:

1
$ git checkout remotes/origin/master

Further operations are required:

1
2
$ git checkout -b ${remote_branch_to_local_branch_name}
// e.g. git checkout -b dev

-b means base, creating a new branch named dev based on the current branch. Of course, other names can also be used here. Then, executing git branch -va will show:

And you’re all set~

After modifying the dev branch locally, you can directly push it to the remote dev branch:

1
$ git push origin dev

Clone Specific Branch from Remote Repository

1
$ git clone -b ${OriginRepoBranchName} ${OriginRepoAddr}

For example:

1
git clone -b master git@github.com:imzlp/blog-source.git --depth=2

That is, pull the master branch from the remote repository, and --depth specifies the depth of historical versions to pull.

Ignore Unnecessary Files for Committing

Sometimes we don’t want to commit everything to the repository (e.g., some compiled binary files). We can use .gitignore to exclude files that don’t need to be committed.
On Windows, we cannot directly create this file because it starts with a dot and has no filename. Therefore, it needs to be created using a command in GitBash:

1
$ touch .gitignore

Then edit .gitignore and add the files/directories you want to exclude.

1
2
3
4
5
6
# Exclude the public folder in the current directory
public/
# Exclude all .exe files
*.exe
# Exclude all .txt files
*.txt

For more information about .gitignore, see here — Ignoring Special Files - Git Tutorial

Compare Differences Between Versions

1
2
3
4
5
6
7
8
9
10
11
12
# View which parts of unstaged files have been updated
$ git diff
# View which parts of a specific unstaged file have been updated
$ git diff filename
# View the differences between staged files and the last committed version
$ git diff –cached
# View the differences between a specific staged file and the last committed version
$ git diff –cached filename
# View the differences between two specific versions
$ git diff ffd98b291e0caa6c33575c1ef465eae661ce40c9 b8e7b00c02b95b320f14b625663fdecf2d63e74c
# View the differences between a specific file in two specific versions
$ git diff ffd98b291e0caa6c33575c1ef465eae661ce40c9:filename b8e7b00c02b95b320f14b625663fdecf2d63e74c:filename

Diff Between Two Branches

Common diff usage involves comparing the working directory, index, HEAD, or diffing two commits. In addition to these uses, diff can also compare two branches, as follows:

1
2
3
$ git diff topic master (1)
$ git diff topic..master (2)
$ git diff topic...master (3)

Usage 1: Directly specifying two branch names separated by a space will directly diff the latest commits on those two branches, effectively diffing two commits.
Usage 2: Branch names separated by two dots, works the same as Usage 1 (a cool trick).
Usage 3: Branch names separated by three dots will output changes on the master branch since topic and master were developed separately.
It should be noted that .. and ... here should not be confused with .. and ... in git rev-list.

Error Handling

bad signature

If the following error occurs when using the git terminal:

1
2
3
$ git status
error: bad signature
fatal: index file corrupt

This is caused by index corruption and can be resolved in the following way:

1
2
rm -f .git/index
git reset

For more details, see the discussion on Stack Overflow: How to resolve “Error: bad index – Fatal: index file corrupt” when using Git

Disable Git SSL Verification

1
$ git config --global http.sslVerify false

Private Key Permissions Too Open Error

When using Git, you might sometimes encounter this prompt (usually when sharing private keys between machines).

1
2
3
4
5
6
7
8
9
10
11
12
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/root/.ssh/id_rsa' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/root/.ssh/id_rsa": bad permissions
Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

This indicates that your Git private key’s read/write permissions are too open, posing a security risk (mine is 644 here). The solution is to change the read/write permissions of the private key (id_rsa) to 600. For Linux read/write permissions, see Collection of Tools and Environment Knowledge: Linux File Permission Modification.

1
sudo chmod 600 ~/.ssh/id_rsa

Solution for Windows:
Right-click on the .ssh/id_rsa file, go to Properties -> Security -> Advanced, disable inheritance. Change the file owner to your own account, then add your own account and SYSTEM with Read & Execute permissions, then save.

Alternatively, use WSL to set permissions with chmod.

Reference article: Windows SSH: Permissions for ‘private-key’ are too open

Git Update Remote Branch List

1
$ git remote update origin --prune

submodule

If another Git repository is used within a Git repository, git module can be used to handle this inclusion relationship.

If you directly clone repository B into repository A as a subdirectory, when you git add, you will see a prompt similar to the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
hint: You've added another git repository inside your current repository.
hint: Clones of the outer repository will not contain the contents of
hint: the embedded repository and will not know how to obtain it.
hint: If you meant to add a submodule, use:
hint:
hint: git submodule add <url> Plugins/VaRest
hint:
hint: If you added this path by mistake, you can remove it from the
hint: index with:
hint:
hint: git rm --cached Plugins/VaRest
hint:
hint: See "git help submodule" for more information.

The purpose is to tell you that you’ve included another Git repository.

The correct way to handle this is to first delete the previously cloned B, and then use git submodule to clone B:

1
2
3
4
5
6
7
8
9
10
$ git submodule add git@github.com:ufna/VaRest.git Plugins/VaRest
Cloning into 'C:/Users/imzlp/Documents/Unreal Projects/GWorld/Plugins/VaRest'...
remote: Enumerating objects: 123, done.
remote: Counting objects: 100% (123/123), done.
remote: Compressing objects: 100% (81/81), done.
remote: Total 1796 (delta 74), reused 83 (delta 42), pack-reused 1673
Receiving objects: 100% (1796/1796), 624.66 KiB | 44.00 KiB/s, done.
Resolving deltas: 100% (955/955), done.
warning: LF will be replaced by CRLF in .gitmodules.
The file will have its original line endings in your working directory

It will clone B, then you enter B’s directory, change the version record to your desired commit, and then add/commit.

1
2
3
4
5
6
$ git status
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)

modified: Plugins/VaRest (new commits)

Then push to the remote repository. In the remote repository, the B directory within project A will not be directly uploaded files, but rather a link to the real source repository of B.

Initial Submodule Update

1
$ git submodule update --init --recursive

Subsequent Submodule Update

1
2
3
$ git submodule foreach git fetch
# Or
$ git submodule update --remote

Delete Submodule

Deleting an added submodule requires the following steps:

  1. Delete the submodule directory
  2. Delete information about the submodule from .gitmodules
  3. Delete information about the submodule from .git/config
  4. Delete the submodule directory under .git/modules

After completing the above steps, it should be done. However, if there are errors, you can delete the cache:

1
$ git rm --cached 子模块名称

Update All Submodules

1
git submodule foreach git pull origin master

git gc

If you see the following prompt when pulling:

1
2
Auto packing the repository for optimum performance. You may also
run "git help gc" manually. See "git help gc" for more information.

Because the default format Git uses to save objects to disk is called the loose object format. Git occasionally packs these objects into a binary file called a packfile to save space and improve efficiency. When there are too many loose objects in the repository, it will prompt you to run git gc.

So, when the above prompt appears, just run in the terminal:

1
$ git gc

And it’s done after execution.

Pull Branch from Remote

1
2
# View remote branches
$ git branch -r

Method one:

1
2
# Pull remote branch and create local branch
$ git checkout -b ${LocalBranchName} origin/${RemoteBranchName}

Method two:

1
$ git fetch origin ${RemoteBranchName}:${LocalBranchName}

fatal: The remote end hung up unexpectedly

You can increase the postBuffer (in bytes):

1
$ git config http.postBuffer 524288000

Enable Git Case Sensitivity

Git on Windows is case-insensitive by default; this can be achieved by modifying the configuration:

1
$ git config core.ignorecase false

Git Proxy Settings

1
2
git config --global http.proxy 'socks5://127.0.0.1:1080'
git config --global https.proxy 'socks5://127.0.0.1:1080'

Disable Git proxy:

1
2
git config --global --unset http.proxy
git config --global --unset https.proxy

Create Local Empty Repository

1
$ git --bare init

This command creates an empty repository, which can be added as a remote repository in other projects.


Early EOF Error

1
2
3
4
5
6
7
8
user@USER ~
$ git clone -v git://192.168.8.5/butterfly025.git
Cloning into 'butterfly025'...
remote: Counting objects: 4846, done.
remote: Compressing objects: 100% (3256/3256), done.
fatal: read error: Invalid argument, 255.05 MiB | 1.35 MiB/s
fatal: early EOF
fatal: index-pack failed

Solution: Disable core.compression:

1
git config --global core.compression 0

Repository Not Found

If prompted when pulling:

1
2
3
$ git.exe pull--progress-v--no-rebase "origin"
remote: The project you were looking for could not be found.
fatal: repository "https://192.168.2.223/gyvr/GWorldS1g. git/' not found

After confirming normal server connectivity and no issues with the SSH Key, you can use the following method to resolve it.

The reason for this issue is that this computer had previously used other Git accounts and saved their passwords. When requesting a remote repository, it will default to using the saved password for an account. If the account requesting the server does not have access rights to the repository, this error will occur.

The solution is to delete redundant credentials in the Control Panel:

Afterward, cloning or pulling again will prompt for the Git username and password.

Export Diff Files

Sometimes you need to know which file information has been updated between two versions, which can be done using the git diff command. However, if you don’t want to know the specific details of the changes, but just need to know which files were modified, you can use the following method:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ git diff COMMIT_HASH_1...COMMENT_HASH_2 --name-only > diff.txt 
Config/DefaultEngine.ini
Content/Core/BP_GameMode.uasset
Content/Maps/GameMap.umap
Content/Maps/GameMap_BuiltData.uasset
Content/Maps/LoginMap.umap
Content/Maps/LoginMap_BuiltData.uasset
Content/Pak/Cube.uasset
Content/Pak/Mats/BasicShapeMaterial_Blue.uasset
Content/Pak/Mats/BasicShapeMaterial_White.uasset
Content/Pak/Mats/BasicShapeMaterial_Yellow.uasset
Content/UI/MountPak.uasset
GWorld.uproject

This will list all modified filenames. The key point is the --name-only parameter.

PS: COMMIT_HASH_1...COMMENT_HASH_2 must have the older version first and the newer version second.

List Commits

Unlike a direct git log, I only want to simply list each commit record without caring about the committer, time, and so on.

You can use Git’s placeholders: Git Basics - Viewing the Commit History

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git log --pretty=format:"%h \"%s\""
fee8b06 "fix word error"
84a2a8b "Base v0.0.1"
91a9991 "set default map"
5e2499e "add download pak test"
cbcbb9a "add UI"
ca34d8f "fix bugs"
589cf38 "fix bug"
9e9ca9b "add CreateFileByBytes"
64d3b94 "添加测试文档"
52b7562 "enable VaRestPlugin"
5e6adc0 "add VaRest/General Lib"
9562bf8 "set GWorld Project to mobile"
3f16e75 "GWorldSlg init"

--pretty=format: supports more options:

Option Description of Output
%H Commit hash
%h Abbreviated commit hash
%T Tree hash
%t Abbreviated tree hash
%P Parent hashes
%p Abbreviated parent hashes
%an Author name
%ae Author email
%ad Author date (format respects the –date=option)
%ar Author date, relative
%cn Committer name
%ce Committer email
%cd Committer date
%cr Committer date, relative
%s Subject

rebase

If you only use merge to combine branches, all commit information and history from the target branch will be merged. Sometimes we want a feature version to be a single commit, rather than committing every small change. This results in many meaningless commits in Git’s history. How to solve this? By using rebase.

First, let’s talk about the rebase process:

  1. Create branch B based on branch A: git checkout -b B
  2. Then freely modify and commit on branch B.
  3. When the feature on branch B is almost complete and you want to synchronize it back to branch A, you can use rebase to fold commits.

The specific operation is:

On branch B, find the hash information of the base version commit from which branch A was cut, and then use the following command:

1
2
3
4
# Specify the base version to rebase to
$ git rebase -i 0fee16a1a2f20d299c678e9846ef720c2ca228ad
# Or specify to look up a few commits from the current HEAD
$ git rebase -i HEAD~3

At this point, a vim editing interface will pop up, listing all commits in branch B from the base version to HEAD. By default, all are pick, but because we need to fold commits, we need to combine multiple commits using Git’s squash command:

  • pick: Keep this commit (abbreviation: p)
  • reword: Keep this commit, but I need to modify its commit message (abbreviation: r)
  • edit: Keep this commit, but I want to stop and modify this commit (not just the message) (abbreviation: e)
  • squash: Combine this commit with the previous commit (abbreviation: s)
  • fixup: Combine this commit with the previous commit, but I don’t want to keep its commit message (abbreviation: f)
  • exec: Execute shell command (abbreviation: x)
  • drop: I want to discard this commit (abbreviation: d)

Just keep one pick and replace the others with squash. Then a commit editing interface will pop up, where you can comment out unwanted commit messages using #, then save and exit. The rebase is then complete.

  1. The final step is to switch back to branch A and perform a merge operation: git merge B. After the merge, you will only see one commit on branch A.

Clean LFS Cache

When using LFS acceleration for a while, the cache can become exceptionally large. You can use the following command to clean it:

1
git lfs prune

error:cannot lock ref

1
2
3
error: cannot lock ref 'refs/remotes/origin/SN/feature/Terrain_CDLod': is at 076e430ca921e6a02b6c2a431609090e635f8ea8 but expected 48b8b9ebd04ab04a7053d3aca304beca46da16f6
From http://git.code.oa.com/RStudioEngine/UnrealEngine
! 48b8b9ebd04..076e430ca92 SN/feature/Terrain_CDLod -> origin/SN/feature/Terrain_CDLod (unable to update local ref)

Solution:

1
2
$ git update-ref -d refs/remotes/origin/SN/feature/Terrain_CDLod
$ git pull -f

Git View Staged Files List

git diff

AMCR retrieves files from four operations: Add/Modify/Copy/Rename:

1
git diff --cached --name-only --relative --diff-filter=AMCR

Using -cached will detect the differences between files after the current git add and the most recent commit.

1
2
3
4
$ git diff --cached --name-only --relative --diff-filter=AMCRX
Maps/Login.umap
PreCommitChecker.py
Texture/texture1.uasset

Specific parameters can also be found in Git’s parameters: –diff-filter.

git status

Using git diff can only compare between versions or files after git add, but it cannot retrieve untracked files.

1
git status -s

Will show a short list:

1
2
3
 M Assets/Scene/BaseMaterials/4X4_MRA.uasset
?? AAA.txt
?? PreCommitChecker.py

The first two characters indicate the file status, followed by a space. ?? indicates the file is untracked. More status codes can be found in git-status.

Undo Commit

1
git reset --soft ^HEAD

Delete Tag on GitHub

1
git push origin --delete tagname

Specify Account Password when Committing with Git

1
git remote set-url origin https://username:password@github.com/username/repo_name.git

Note that LFS also needs modification; you can edit the .git/config file.

SSH Login Failed with Key

You can modify Mac’s SSH configuration (/etc/ssh/sshd_config):

1
2
3
RSAAuthentication yes
PubkeyAuthentication yes
StrictModes no

Then reload the configuration:

1
2
$ sudo launchctl unload /System/Library/LaunchDaemons/ssh.plist
$ sudo launchctl load -w /System/Library/LaunchDaemons/ssh.plist

error: cannot lock ref

1
error: cannot lock ref 'refs/remotes/origin/SN/feature/avinchen/Develop': is at 651a8bcf620db8e6bbc6c874e0949ff6c09bb37f but expected f0f49ccce3abfe552e2301eca5711741a390af6b

Solution:

1
2
$ git update-ref -d refs/remotes/origin/SN/feature/avinchen/Develop
$ git pull origin

Or force a full pull:

1
$ git pull -p

Pull LFS Files

1
2
3
git lfs fetch
# or
git lfs pull

Pull Pull Request to Local

1
git fetch origin pull/58/head:ue5

This means pulling the remote pull request #58 to the local ue5 branch.

Historical Files Support LFS

After enabling LFS, files in historical commits can also be tracked by LFS.

1
2
# Use current gitattributes configuration
git lfs migrate import --everything

Delete Changed Files

Delete all changed files:

1
git clean -xdf

Note: This will delete all untracked files (including files ignored in .gitignore). If you don’t want to delete files in .gitignore, remove x from the above command;

no hostkey alg

If this message appears when connecting to an SSH Server, it’s due to an SSH version mismatch: the remote SSHD version is too high, while the local SSH version is too low.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
OpenSSH_5.1p1, OpenSSL 0.9.8i 15 Sep 2008
debug2: ssh_connect: needpriv 0
debug1: Connecting to 10.64.222.108 [10.64.222.108] port 2222.
debug1: Connection established.
debug2: key_type_from_name: unknown key type '-----BEGIN'
debug2: key_type_from_name: unknown key type '-----END'
debug1: identity file G:/UnrealProjects/Client/Build/NotForLicensees/SSHKeys/10.64.222.108/buildmachine/RemoteToolChainPrivate.key type -1
debug1: Remote protocol version 2.0, remote software version OpenSSH_9.0
debug1: match: OpenSSH_9.0 pat OpenSSH*
debug1: Enabling compatibility mode for protocol 2.0
debug1: Local version string SSH-2.0-OpenSSH_5.1
debug2: fd 3 setting O_NONBLOCK
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug2: kex_parse_kexinit: diffie-hellman-group-exchange-sha256,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1
debug2: kex_parse_kexinit: ssh-rsa
debug2: kex_parse_kexinit: aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,arcfour128,arcfour256,arcfour,aes192-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se,aes128-ctr,aes192-ctr,aes256-ctr
debug2: kex_parse_kexinit: aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,arcfour128,arcfour256,arcfour,aes192-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se,aes128-ctr,aes192-ctr,aes256-ctr
debug2: kex_parse_kexinit: hmac-md5,hmac-sha1,umac-64@openssh.com,hmac-ripemd160,hmac-ripemd160@openssh.com,hmac-sha1-96,hmac-md5-96
debug2: kex_parse_kexinit: hmac-md5,hmac-sha1,umac-64@openssh.com,hmac-ripemd160,hmac-ripemd160@openssh.com,hmac-sha1-96,hmac-md5-96
debug2: kex_parse_kexinit: none,zlib@openssh.com,zlib
debug2: kex_parse_kexinit: none,zlib@openssh.com,zlib
debug2: kex_parse_kexinit:
debug2: kex_parse_kexinit:
debug2: kex_parse_kexinit: first_kex_follows 0
debug2: kex_parse_kexinit: reserved 0
debug2: kex_parse_kexinit: sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256
debug2: kex_parse_kexinit: rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp256,ssh-ed25519
debug2: kex_parse_kexinit: chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com
debug2: kex_parse_kexinit: chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com
debug2: kex_parse_kexinit: umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1
debug2: kex_parse_kexinit: umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1
debug2: kex_parse_kexinit: none,zlib@openssh.com
debug2: kex_parse_kexinit: none,zlib@openssh.com
debug2: kex_parse_kexinit:
debug2: kex_parse_kexinit:
debug2: kex_parse_kexinit: first_kex_follows 0
debug2: kex_parse_kexinit: reserved 0
debug2: mac_setup: found hmac-sha1
debug1: kex: server->client aes128-ctr hmac-sha1 none
debug2: mac_setup: found hmac-sha1
debug1: kex: client->server aes128-ctr hmac-sha1 none
no hostkey alg

UE updated the SSH client version in the 4.27 engine: Replace Windows ssh and rsync from DeltaCopy with new version from cw…

If using an engine version prior to 4.27 and upgrading to MacOS13+, this problem may occur during remote builds.## detected dubious ownership in repository at

1
2
3
4
5
6
7
8
9
$ git remote set-url origin https://git.woa.com/xxxx/UnrealEngine.git
fatal: detected dubious ownership in repository at 'D:/agent/workspace/MasterPackage/SOURCE/Engine'
'D:/agent/workspace/MasterPackage/SOURCE/Engine' is owned by:
'S-1-5-21-1333135361-625243220-14044502-1020188'
but the current user is:
'S-1-5-21-1333135361-625243220-14044502-1052754'
To add an exception for this directory, call:

git config --global --add safe.directory D:/agent/workspace/MasterPackage/SOURCE/Engine

If you switch to a new account, you might encounter the error above for repositories pulled with the previous account.

Solution: Add all paths to the safe directory:

1
git config --global --add safe.directory "*"

Note: This command should be executed in the new account.

Warning: the ECDSA host key

When you encounter this warning when committing to GitHub: Warning: the ECDSA host key for 'github.com' differs from the key for the IP address '192.30.255.112', it means your SSH client has authenticated another host matching the target IP address.

To resolve this issue, follow these steps:

1. Delete the old key from the known_hosts file:
Use a text editor to open the ~/.ssh/known_hosts file (Windows users can find it at %UserProfile%\.ssh\known_hosts) and locate the corresponding entry (referencing the IP address and hostname), then delete that line of text.

For example, in the known_hosts file, find an entry like this:

1
github.com,192.30.255.112 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hX ...

Delete this line.

2. Confirm GitHub’s SSH host fingerprints:
Confirm the currently valid Transport Layer Security (TLS) public key fingerprints and SSH public key fingerprints from the GitHub official documentation.

3. Reconnect via SSH and add the new host key:
In the terminal or command prompt, execute the following command:

1
ssh -T -oStrictHostKeyChecking=accept-new git@github.com

This will automatically add the new github.com host key.

If you see “Hi username! You’ve successfully authenticated, but GitHub does not provide shell access.”, it means the SSH authentication was successful, and the issue is resolved.

Now, you should be able to commit code to GitHub normally, and the warning message will no longer appear.

pull/push always prompts for password

You can execute the following command:

1
git config --global credential.helper store

Get the latest commit information for the current repository

If you only want to get the latest commit ID, you can use the following command:

1
2
$ git rev-parse HEAD
5c342b5145720c8cd4642e50774990db1c4294c3

If you want to see branch and commit information, you can use the command below:

1
2
$ git log -1 --pretty=oneline
5c342b5145720c8cd4642e50774990db1c4294c3 (HEAD -> master, origin/master, origin/HEAD) AUTO: Commit 58ca381 - (by UGit)

The -1 parameter lists only the latest commit.

If you add --decorate, you will see branch information (also depends on the git version; newer versions include branch information by default):

1
2
$ git log -1 --pretty=oneline --decorate
2d7b647a0f7330e3470ebe05fe039d9107624479 (HEAD -> dev, origin/dev) copy file

SSH key login failed

If the SSH connection prompts the following error:

1
lipengzha@192.168.31.55: Permission denied (publickey,password,keyboard-interactive).

You can modify the Server-side SSH configuration (/etc/ssh/sshd_config):

1
2
RSAAuthentication yes
PubkeyAuthentication yes

Then restart the SSHD service.

View the last modifier of a specific line of code

To view the last person who modified lines n to m of a file:

1
git blame filename -L n,m

It will output:

1
2
e74836f557b (lipengzha 2024-01-10 09:43:02 +0800 541)                                   TArray<FString> FoundShaderLibs = UFlibShaderCodeLibraryHelper::FindCookedShaderLibByPlatform(PlatformName,SavePath,false);

1
git blame -L 541,541 --incremental --minimal Plugins\\HotPatcher\\HotPatcher\\Source\\HotPatcherCore\\Private\\CreatePatch\\PatcherProxy.cpp

Output:

1
2
3
4
5
6
7
8
9
10
11
12
e74836f557bc5ddf01de3f5f8d3b797107d9a767 541 541 1
author lipengzha
author-mail <lipengzha@gmail.com>
author-time 1704850982
author-tz +0800
committer lipengzha
committer-mail <lipengzha@gmail.com>
committer-time 1704850982
committer-tz +0800
summary 【构建】构建需求总单
previous 00ea9b4597dad3fe867b8af8747d12d10be105e2 Plugins/HotPatcher/HotPatcher/Source/HotPatcherCore/Private/CreatePatch/PatcherProxy.cpp
filename Plugins/HotPatcher/HotPatcher/Source/HotPatcherCore/Private/CreatePatch/PatcherProxy.cpp

Clear all history

To clear all history in a Git repository and keep only the latest commit, follow these steps:

  1. Create a new branch:
    First, ensure you are in a clean working directory (no uncommitted changes), then create a new branch.

    1
    git checkout --orphan latest_branch

    This command creates a new branch with no history.

  2. Add all files:
    Add all files to the new branch.

    1
    git add -A
  3. Commit changes:
    Commit all files.

    1
    git commit -am "Initial commit with latest state"
  4. Delete the old branch:
    Switch back to the main branch and delete the old branch.

    1
    git branch -D main
  5. Rename the new branch to the main branch:
    Rename the new branch to the main branch.

    1
    git branch -m main
  6. Force push to the remote repository:
    Finally, force push the changes to the remote repository. Note: This will overwrite all history in the remote repository.

    1
    git push -f origin main

This will clear all history, keeping only the latest commit. This operation is irreversible, and all historical records will be permanently deleted. Therefore, before performing this operation, make sure you genuinely do not need this history.

Windows and Linux newline character differences

Windows uses carriage return and line feed (CRLF, \r\n), while Linux uses line feed (LF, \n). If you create or modify files on Windows and then view them on Linux, Git might detect changes due to the different newline characters.

Solution: You can configure Git’s handling of newline characters. Use the following commands to set Git to automatically convert newline characters:

1
2
git config --global core.autocrlf true  # Windows
git config --global core.autocrlf input # Linux
  • Use the .gitattributes file to specify newline character handling for specific files, for example:
1
`* text=auto

Note, after I set this, I found that changed files did not display in the working directory in real-time. You can remove this configuration in C:\User\[USER_NAME]\.gitconfig.

Batch convert end-of-line characters

I wrote a python script to achieve this: git_eol_normalize.py

1
python3 git_eol_normalize.py --repo_Dir REPO_ROOT_DIR --eol LF

You can modify the list of text formats in g_text_exts as needed.

Pull TAG to local branch

  1. Fetch tags from remote (git fetch --tags)
  2. View tag list (git tag)
  3. Pull remote tag to local branch (git checkout -b LOCAL_BRANCH ORIGIN_TAG_NAME)

Package commit history

Sometimes we want to synchronize some commit history between two machines, but there is no public repository accessible.

In this case, you can use git bundle to package specific commit history:

1
2
3
4
5
# Package the latest commit
git bundle create create_last_commit.bundle HEAD~..HEAD

# Package the last two commit histories
git bundle create create_last_2commit.bundle HEAD~2..HEAD

Another machine can then execute git pull with the bundle file:

1
git pull create_last_commit.bundle

This will restore the commit history on the other machine.

subtree

Sometimes we want to put the code of one repository into another repository, but not using the submodule method. In this case, we can use subtree.

First, you already have a main repository, and you want to put another repository into a directory of the main repository: for example, the main repository MainProj, and you want to put HotPatcher into the Plugins directory.

Required operations:

  1. In the main repository, add a remote for the sub-repository (git remote add lib-hotpatcher git@github.com:hxhb/HotPatcherUE.git)
  2. Add the sub-repository to the sub-directory using git subtree add (git subtree add --prefix=Plugins/HotPatcher lib-hotpatcher master --squash)

The above command will pull the master branch of the lib-hotpatcher remote repository into the Plugins/HotPatcher directory and create two commit records:

1
git subtree add --prefix=libs/my-library library-remote main --squash

Parameter explanation:

  • --prefix=libs/my-library: Specifies the path of the subdirectory where the code will be placed. If the directory does not exist, Git will create it automatically.
  • library-remote: The alias of the remote repository added in the previous step.
  • main: The branch name of the library-project repository you want to pull (could be main or master).
  • --squash: (Optional but highly recommended) This parameter compresses all historical records of the sub-repository into a single commit. This keeps your main repository’s history clean; otherwise, hundreds or thousands of commits from the sub-repository would be merged in.

How to pull updates from the sub-repository later:

1
git subtree pull --prefix=libs/my-library library-remote main --squash

How to push changes made in the main repository to the subdirectory back to the sub-repository (Push)?

This is an advanced usage. If you modify the code in the libs/my-library directory within main-project and want to contribute these changes back to library-project, you can do this:

1
git subtree push --prefix=libs/my-library library-remote main

UPDATE

CheatSheet

Reference Articles

Git Remote Operation Details
Creating and Merging Branches
Git Undo Branch Deletion
git checkout command to undo changes
git diff - diff two branches

The article is finished. If you have any questions, please comment and communicate.

Scan the QR code on WeChat and follow me.

Title:Git Quick Start Guide
Author:LIPENGZHA
Publish Date:2016/09/29 22:19
Update Date:2018/08/15 00:57
Word Count:19k Words
Link:https://en.imzlp.com/posts/53696/
License: CC BY-NC-SA 4.0
Reprinting of the full article is prohibited.
Your donation will encourage me to keep creating!