Git快速上手指南

I recently set up an intranet server, and from now on, all projects will use Git for version control. Here, I’ve outlined some common operations for easy reference.

Install Git

Windows: You can download the Git installation package/portable version from the official website. The difference between the installation package and the portable version is that the portable version requires you to manually add GitBinaryPath/bin to the system Path.

Linux (debian): sudo apt-get install git

Initial Setup

After installation, start git.exe (Windows) or just type in the terminal:

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

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

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

Press Enter all the way (or set a password if you prefer), and once done, a .ssh directory will be created in C:\Users\${userName}\ (on Linux, it will be in ~/) containing two files: id_rsa and id_rsa.pub.

id_rsa is the private key, which must not be leaked; id_rsa.pub is the public key, which you can share with anyone.

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

GitHub needs to verify that the commits you push are indeed from you, not someone impersonating you. Since Git supports 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 – you can push from your work computer or your home computer; just add each computer’s key to GitHub, and you can push from any of them.

Create a Local Repository

Start Git Bash, then cd (or use Easy Context Menu to add Git Bash to the system’s right-click menu) to the directory where you want to create the local repository and execute the following command:

1
2
// Initialize a directory
$ git init

Local Commit Files

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

Delete Remote Repository

1
2
3
4
5
// To remove the association of the local repository with a remote 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 (the main branch) but can also be modified (not recommended). -f means to ignore conflicts and force the push.
$ git push ${remote_repo_name} ${branch}
// Pull updates from the remote repository, the 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.
The first method is to use the git 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; repeat adding to the same name with 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 to add multiple remote repository addresses to the same remote repository name:

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

# Edit .git/config and 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 Branch

1
2
3
4
5
6
7
8
9
10
11
// -b means base, which creates a new branch based on the current branch 
$ git branch ${new_branch_name} -b
// Switch branches
$ 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}

The checkout command has three uses: switch branches, discard changes, and check out from history, which will be explained 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 commands
$ 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 on the remote, 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, merging 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 the local and remote branches (or local branches), you can abort the merge to revert the local branch to its state before the merge.

1
$ git merge --abort

Resolve Conflicts

In Git, if there are conflicts in text files, you can directly see the differences while pulling, but if Git is managing binary files, it is less convenient.

If you want to undo the changes of a specific commit ${123456}, you can revert to the previous version and then use:

1
2
3
4
5
$ git reset --hard 123455
# Checkout the files from the commit 123456 to commit 123455
# This is equivalent to directly modifying the files of 123456 based on 123455
$ git checkout 123456 .
# Then, just don't commit the files you don't want to include.

Binary File Conflicts

Text file merges in Git can be easily viewed, but binary files can be cumbersome.

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

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

And to keep the local branch file:

1
$ git checkout --ours git.exe

Then execute:

1
2
3
$ git add git.exe
# If the above was executed with --theirs
$ git commit -m "Merge conflict, keep remote version"

At this point, the local branch is merged with the remote while retaining the remote file version.

Delete Branch

1
2
3
4
5
6
7
8
// Delete local branch
// Note: Before deleting a branch, you must merge it; otherwise, an error will occur (or use -D to force delete)
// 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. If you have exited the Terminal

Run git reflog to see your last commit SHA1

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

You can recreate a branch based on your SHA1, choosing the commit SHA1 of the delete operation.

  1. If you have not exited the Terminal

When you delete a branch, you’ll have the SHA1 value

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

Then use that SHA1 value to restore the deleted branch:

1
$ git branch ${branch_name} ffe7c46

Version Rollback

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

1
2
// Roll back to a specific version
$ git reset --hard version_number

1
2
// Roll back a specific file to a certain version
$ git reset version_number filename

Undo Uncommitted File Changes

Only Undo Local Changes

If you have made modifications to some files in a branch but haven’t added them to the staging area (git add), check the file status using the status command.

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

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

Undo Both Local and Staged Modifications

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

First, unstage:

1
$ git reset ${filename}

Then undo local changes:

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

Quick Undo Changes All at Once

1
$ git checkout HEAD -- ${filename}

Check Out Files from History

Sometimes you may commit a new version but feel that changes to certain files are inappropriate and want to revert some files to a previous version while keeping others at the new version. You can use the checkout command.

1
2
3
4
5
6
7
// View commit history
$ git log
// Check out a file from a historical version to the current version
$ git checkout historical_version_number ${filename}

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

Later, you can use add to commit just like ordinary files.

Rename Local Branch

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

Clone Remote Repository

1
2
3
4
5
// Clone the entire repository
$ git clone ${remote_repo}

// To clone only one branch, use -b to specify the branch name
$ git clone -b ${branch_name} ${remote_repo}

Switch to Remote Branch

Once a remote repository is cloned with git clone, executing git branch will only show:

1
* master

Other branches will not be visible even if there are more in the remote repository.

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

1
$ git branch -va

To 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. You can certainly use other names here. Once again executing git branch -va will show:

And you’re done~

After making changes locally in the dev branch, you can directly push 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

This pulls the master branch from the remote, with --depth specifying the historical version’s depth to retrieve.

Ignore Unnecessary Files for Commit

Sometimes we don’t want to commit everything to the repository (like some compiled binary files); we can use .gitignore to exclude files that should not be committed. On Windows, you cannot create this file directly because it starts with a dot without a filename, so use the command in Git Bash to create it:

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, you can check 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 untracked files have been updated
$ git diff
# View which parts of an untracked file have been updated
$ git diff filename
# View the differences between staged files and the last commit
$ git diff --cached
# View the differences between a staged file and the last commit
$ git diff --cached filename
# View differences between two versions
$ git diff ffd98b291e0caa6c33575c1ef465eae661ce40c9 b8e7b00c02b95b320f14b625663fdecf2d63e74c
# View differences between two versions of a specific file
$ git diff ffd98b291e0caa6c33575c1ef465eae661ce40c9:filename b8e7b00c02b95b320f14b625663fdecf2d63e74c:filename

Diff Between Two Branches

A common use of diff is to compare the working directory, index, HEAD, or the diff between 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 comparing two branches separated by a space will perform a diff on the latest commits of the two branches, equivalent to diffing two commits.
Usage 2: Using two dots between the branch names has the same effect as usage 1 (just looking cool).
Usage 3: Using three dots between the branch names will output the changes on master since topic was first developed.
Note that the .. and ... here should not be confused with those in git rev-list.

Error Handling

Bad Signature

If you encounter the following error while using the git terminal:

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

This is caused by index corruption, which can be handled as follows:

1
2
rm -f .git/index
git reset

For more details, refer to the discussion on stack overflow here: How to resolve “Error: bad index – Fatal: index file corrupt” when using Git

Disable SSL Verification in Git

1
$ git config --global http.sslVerify false

Private Key Permission Too Open Error

Sometimes, when using Git, you might see this prompt (usually when sharing keys across 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 warns that your Git private key’s read/write permissions are too open and pose a security risk (in my case, it’s 644), and the solution is to change the private key (id_rsa) permissions to 600. For Linux read/write permissions, check Knowledge Accumulation of Tools and Environment: Linux File Permission Modification.

1
sudo chmod 600 ~/.ssh/id_rsa

For Windows, the solution is:
Right-click on the .ssh/id_rsa file, go to properties - Security / Advanced, disable inheritance, change the file owner to your own account, and add your own account and SYSTEM with Read and Execute permissions, then save.

Alternatively, use WSL to set permissions with chmod.

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

Update Remote Branch List in Git

1
$ git remote update origin --prune

Submodule

If you use another Git repository within a Git repository, you can handle this inclusion with git module.

If you directly clone another repository B into subdirectory A, there will be similar prompts when you git add:

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.

This is to inform you that you have included another Git repository.

The proper way to handle this is to 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

This will clone B, then go into B’s directory, change the version record to your desired commit, and then add/commit it.

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 in project A will not contain directly uploaded files but will link to the actual source repository of B.

Initial Update Submodule

1
$ git submodule update --init --recursive

Subsequent Updates to Submodule

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

Deleting Submodule

To delete an added submodule, follow these steps:

  1. Delete the submodule’s directory.
  2. Remove the related submodule info from .gitmodules.
  3. Remove the related submodule info from .git/config.
  4. Delete the submodule’s directory from .git/modules.

After performing the above steps, you are good to go. If there are error issues, you can remove the cache:

1
$ git rm --cached submodule_name

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.

Git uses a format called loose objects when saving objects on disk by default. From time to time, Git 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, you will be prompted to run git gc.

So, when you see the above prompt, just run in the terminal:

1
$ git gc

Wait for it to finish.

Pull Branch from Remote

1
2
# View remote branches
$ git branch -r

Method 1:

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

Method 2:

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

Fatal: The Remote End Hung Up Unexpectedly

Increasing postBuffer might help (in bytes):

1
$ git config http.postBuffer 524288000

Enable Case Sensitivity in Git

Git is case insensitive by default on Windows. You can enable it by modifying the configuration:

1
$ git config core.ignorecase false

Set Git Proxy

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

To cancel git proxy:

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

Create Empty Local Repository

1
$ git --bare init

This command creates an empty repository that 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 you encounter the following 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 that the server connectivity is normal and the SSH Key has no issues, you can solve it using the following method.

The reason for this issue is that another Git account has previously used this computer, and passwords were saved. When requesting the remote repository, it defaults to using the saved account, and if that account does not have access permissions, an error will occur.

The solution is to delete the extra credentials from the control panel:

After that, when you re-clone or pull, you will be prompted to enter your Git account and password.

Export Diff Files

Sometimes you need to know which files have been updated between two versions without wanting to know the detailed changes. You can use the git diff command in this way:

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 changed file names, with --name-only being the key parameter.

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

List Commit History

Unlike directly using git log, I just want to simply list each commit record without caring about the author, time, etc.

You can use Git placeholders for that: 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 "add test document"
52b7562 "enable VaRestPlugin"
5e6adc0 "add VaRest/General Lib"
9562bf8 "set GWorld Project to mobile"
3f16e75 "GWorldSlg init"

The --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 just use merge to merge branches, it will carry over all commit information and submission history from the target branch. Sometimes we want a version of the feature to be treated as a single commit instead of a bunch of minor changes resulting in many commits in Git’s history. How can we solve this? By using rebase.

First, let’s describe the rebase process:

  1. Create branch B based on branch A: git checkout -b B
  2. Make changes and commits on branch B
  3. When you want to synchronize finished functionality back with branch A, use rebase to condense the commits

The specific operation is to locate the commit hash from which you branched off branch A on branch B, and then use the command:

1
2
3
4
# Specify the base version to rebase onto
$ git rebase -i 0fee16a1a2f20d299c678e9846ef720c2ca228ad
# Or specify the number of commits to go back from HEAD
$ git rebase -i HEAD~3

This will open a vim editor showing all commits from branch B since the base version to HEAD. By default, they’re all marked as pick, but since we need to condense commits, we should change multiple commits to use the squash command:

  • pick: Keep the commit (abbreviated as: p)
  • reword: Keep the commit but modify the commit message (abbreviated as: r)
  • edit: Keep the commit but stop to make changes to what is being committed (not just the message) (abbreviated as: e)
  • squash: Merge this commit with the previous one (abbreviated as: s)
  • fixup: Merge this commit with the previous one, but do not keep the commit message (abbreviated as: f)
  • exec: Execute a shell command (abbreviated as: x)
  • drop: Discard the commit (abbreviated as: d)

Keep only one pick and change the rest to squash. It will then pop up a commit editing interface where you can comment out the commit messages you do not need using #, then save and exit, and the rebase is complete.

  1. The last step is to switch back to branch A and execute the merge operation: git merge B, so in branch A you will see only one commit.

Clear LFS Cache

After using LFS acceleration for a while, the cache can become excessively large, so you can use the following command to clear 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)

The solution is:

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

Git View Pending Commit File List

Git Diff

AMCR gets the files for: Add/Modify/Copy/Rename and other operations:

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

Using --cached checks the differences after the most recent commit considering the current git add.

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

You can check the specific parameters in the git documentation: –diff-filter.

Git Status

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

1
git status -s

This will show a short list:

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

The first two characters denote the file status, followed by a space; ?? indicates an untracked file. You can view more status codes at git-status.

Undo Commit

1
git reset --soft ^HEAD

Delete Tag on GitHub

1
git push origin --delete tagname

Specify Username and Password during Git Commit

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

Note that LFS also needs to be modified; you can edit the .git/config file.

SSH Key Login Failed

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

The 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 Request to Local

1
git fetch origin pull/58/head:ue5

This means pulling the 58th remote PR to the local ue5 branch.

History Files Supporting LFS

After enabling LFS, you can also track files within historical commits with LFS.

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

Delete Changed Files

To delete all modified files:

1
git clean -xdf

Note: This will delete all untracked files (including those ignored in .gitignore). If you do not want to delete files like those in .gitignore, remove x from the command above.

no hostkey alg

When connecting to the SSH Server, if this prompt appears, it is due to the SSH version. 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 version for the client in engine version 4.27: Replace Windows ssh and rsync from DeltaCopy with new version from cw…

If using an engine version prior to 4.27 and upgrading to MacOS 13+, this issue will occur during remote building.## 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, for repositories pulled using the previous account, the above error message will appear.

The solution is to add all paths to the safe directory:

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

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

Warning: the ECDSA host key

When you encounter this warning while 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 that matches the target IP address.

To resolve this issue, follow these steps:

1. Delete the old key from the known hosts file:
Open the ~/.ssh/known_hosts file with a text editor (Windows users should find the file at %UserProfile%\.ssh\known_hosts) and locate the corresponding entry (refer to the IP address and hostname), then delete the corresponding line.

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

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

Delete that line.

2. Confirm GitHub’s SSH host fingerprint:
Confirm the current valid Transport Layer Security (TLS) public key fingerprint and SSH public key fingerprint 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 prompted “Hi username! You’ve successfully authenticated, but GitHub does not provide shell access.”, then SSH authentication has succeeded, and the issue is resolved.

You should now be able to commit code to GitHub without the warning message appearing.

Pull/push prompts for password every time

You can execute the following command:

1
git config --global credential.helper store

Get the latest commit information for the current repository

If you just 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 the branch and commit information, you can use the following command:

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 depending on the git version, newer versions have branch information by default):

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

SSH key login failure

If the SSH connection prompts the following error:

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

You can modify the SSH configuration on the server (/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 committer of the n~m lines of a file:

1
git blame filename -L n,m

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 【Building】Building requirements 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 records

To clear all history records in a Git repository while keeping only the latest commit, you can follow these steps:

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

    1
    git checkout --orphan latest_branch

    This command creates a new branch without a history.

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

    1
    git add -A
  3. Commit the 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 main:
    Rename the new branch to main.

    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 records in the remote repository.

    1
    git push -f origin main

This will clear all history records, keeping only the latest commit. This operation is irreversible, and all history records will be permanently deleted, so please ensure you really do not need these history records before performing this operation.

Differences Between Windows and Linux Line Endings

Windows uses carriage return line feed (CRLF, \r\n), while Linux uses line feed (LF, \n). If you create or modify a file on Windows and then view it on Linux, Git may think the file has changed due to the difference in line endings.

Solution: You can configure Git to handle line endings. Use the following commands to set Git to automatically convert line endings:

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

Note: After setting this up, I’ve found that changed files do not display in the working directory in real-time. You can delete this configuration in C:\User\[USER_NAME]\.gitconfig.

UPDATE

CheatSheet

Reference Articles

Detailed explanation of Git remote operations
Creating and merging branches
Undoing branch deletion in Git
Reverting changes with git checkout command
Diffing two branches with git diff

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

Scan the QR code on WeChat and follow me.

Title:Git快速上手指南
Author:LIPENGZHA
Publish Date:2016/09/29 22:19
Update Date:2018/08/15 00:57
World Count:16k 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!