Total Pageviews

Monday, 23 March 2026

git-practical-application

 一些工作上遇到git操作的问题的优雅解决方案。

 

实际场景下使用git的一些简单总结.

只总结一些高频常用操作,不涉及一些骚操作.

Question1

公司内部有代码仓库和 github 仓库邮箱不一致。例如已经全局配置了公司内的信息

git config --global user.name "xxx"
git config --global user.email "xxx@tencent.com"

使用该配置推送到自己的 github 仓库后,那里是看不到 contribute 记录的

解决:用户.ssh 目录下新建 config 文件,配置参考如下

# tencent
   Host 变量
   HostName 变量
   PreferredAuthentications publickey
   IdentityFile ~/.ssh/tencent_id_rsa

# github
   Host github.com
   HostName github.com
   PreferredAuthentications publickey
   IdentityFile ~/.ssh/id_rsa

IdentityFile 指定生成的每个账号 ssh key 文件路径 Host git 服务器 host Hostname 自己定义。git@xxx

Question2

分支开发亢余 commit 信息处理。例如自己有分支上一个小阶段 commit 一个东西,但是在合 master 的时候这些是不被允许的,需要处理

git log // 查看commit记录

例如,如下。需要将 3 条 step x 记录合并成一条提交。我们找到需要合并的最前那条 commit 记录的前一条(此处是 9b37084eec4e045fc0cdf218a846ec2e43a2812f

git rebase -i 9b37084eec4e045fc0cdf218a846ec2e43a2812f
pick c67c886 step1
pick 0df8493 step2
pick 1ae6ab8 step 3

# Rebase 9b37084..1ae6ab8 onto 9b37084 (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.

注释已经写得很清楚。vim 下修改

pick c67c886 step1
s 0df8493 step2
s 1ae6ab8 step 3

保存退出,重新输入新的 commit 纪录再保存退出,这 3 条既可以合并成一条记录。(注意,不能修改master上已有的commit记录;由于重写了分支记录,推到远程分支必须push -f覆盖远程)

Question3

如何优雅合并主干,遇到冲突如何处理。

合主干,假设之前在 feature/something 上开发

git checkout master
git pull --rebase orgin master
git merge --no-ff feature/something // 用非fast-forward进行合并,这样git网络比较清晰
这里使用pull --rebase而不是pull的原因是因为,如果直接使用pull有可能产生多余多余的Merge master into xxx这样无用的commit信息

如果遇到了冲突。(通常命令行和 vscode 都会有提示)

解决冲突后

git add .
git rebase --continue
合并主干完成

对于提 merge request 的

在 feature/something 上开发

git pull --rebase origin master 若冲突,解决后

git add .
git rebase --continue
git push -f origin feature/something # 由于进行了rebase,必须使用-f覆盖远程,只适用于当前分支是自己一个人开发的情况

提 merge request

实际情况 master 也有可能是 dev,看团队习惯

(同理,所有分支上使用过rebase的,推送到远程分支必须使用-f)

个人的最佳实践简单总结就是:分支拉取master更新永远使用rebase,合并到master使用merge --no-ff

Question4 feature 分支上开发到一半。遇到其它问题从 master 分支上切新分支处理

可以git stash push暂存 然后 git stash pop。但是个人更喜欢commit一个tmp。最后合并的时候把commit信息重写

Question5

写了一会代码,发现自己是在本地 master 上直接写并且已经 commit 了几次了,本来应该是分支的。

git reset --soft 回退到的commit Question6 错误的 merge 后需要修复,这里分两种情况:

1、master 本地刚合了 feature 分支代码,但是没有推上远程。分支上的多次 commit 时间很久了,插在 master 上很多了。然后需要撤销合并。需要还原 merge 前的 master

// git log信息如下,需要撤销test分支过来的两次合并,test合并测试1 test合并测试2。
commit a0674976b94d17465eb63e799a334dd12a5ab553 (HEAD -> master)
Merge: 1f8c9ac 69e88d3
Date:   Wed Nov 7 15:56:39 2018 +0800

    Merge branch 'test'

commit 1f8c9aceb78677c2dc8169f463382fc64ec8a514
Date:   Wed Nov 7 15:56:30 2018 +0800

    master上的

commit 69e88d3fdd64547e34e248b7b77a612cae3dbe37 (test)
Date:   Wed Nov 7 15:56:05 2018 +0800

    test合并测试2

commit 1e9761a4b233137c8640129d74894d51dcb38a78
Date:   Wed Nov 7 15:55:56 2018 +0800

    test合并测试1

可见 a0674976b94d17465eb63e799a334dd12a5ab553 是合并过来的 commit 记录,我们直接

git reset --hard 1f8c9aceb78677c2dc8169f463382fc64ec8a514 即可。即回退到合并分支的上一条状态。之前我以为这样 test 合并测试那两条还在。实际上它们也在 commit 记录上没了。这种操作只适用于本地没有上远程的时候有用。

2、master 合并分支的代码已经推到了远程,需要撤销这次的提交

已经推到远程的不能像本地那么做,因为会把别人后来的代码弄没了。需要使用 revert 命令。还是上面的例子

git revert -m 1 a0674976b94d17465eb63e799a334dd12a5ab553 这里是放弃到合并的这次提交。注意 revert 是会生成一次新的 commit 记录,而不是把历史中的问题 commit 从 log 中清除

如果冲突,解决后

git add .
git revert --continue

重新推上远程,这样不会影响别人的提交,只是把自己的问题 commit 去除掉

Question7

代码误上 master 和 6 的情况 2 类似

Question8

仓库上一个文件需要删除

git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch 文件路径' \
--prune-empty --tag-name-filter cat -- --all
## 远程所有该文件记录删掉

git push origin -f --all 注意:需要master也要-f,很多风险 提交敏感文件还是小心为妙

Question9

提交了敏感文件。。就是类似于8

Question10

追加提交。把这次的提交追加到上次的 commit。适合分支使用,push 的时候使用-f

git add . git commit —amend

Question11

将一个已经添加到git中的文件ignore掉

1、先在.gitignore中添加规则

2、git rm --cache — file

revert 实用姿势

revert 反提交 //将 head^^^到 head 范围内的提交反转 git revert head^^^..head (3 次) Todo 查看 revert 一些回滚某几个分支的做法 Or git rebase -I xxx

撤销某一个文件的修改,还没有 add 的 git checkout [file] 撤销某一个文件的 add git reset HEAD [file]

Cherry-pick 将 feature 分支的某个提交合进 develop。sha 值是不一样的 git checkout develop`` git log feature git cherry-pick <C 的 SHA1>

from  https://github.com/flytam/git-practical-application

-----

 

Git and GitHub Cheat Sheet

Git is the free and open source distributed version control system that's responsible for everything GitHub related that happens locally on your computer. This cheat sheet features the most important and commonly used Git commands for easy reference.

INSTALLATION & GUIS🔽

With platform specific installers for Git, GitHub also provides the ease of staying up-to-date with the latest releases of the command line tool while providing a graphical user interface for day-to-day interaction, review, and repository synchronization.

GitHub for Windows

https://windows.github.com

GitHub for Mac

https://mac.github.com

For Linux and Solaris platforms

the latest release is available on the official Git web site.

Git for All Platforms

http://git-scm.com

SETUP🔽

Configuring user information used across all local repositories

git config --global user.name “[firstname lastname]”

set a name that is identifiable for credit when review version history

git config --global user.email “[valid-email]”

set an email address that will be associated with each history marker

git config --global color.ui auto

set automatic command line coloring for Git for easy reviewing

SETUP & INIT🔽

Configuring user information, initializing and cloning repositories

git init

initialize an existing directory as a Git repository

git clone [url]

retrieve an entire repository from a hosted location via URL

STAGE & SNAPSHOT🔽

Working with snapshots and the Git staging area

git status

show modified files in working directory, staged for your next commit

git add [file]

add a file as it looks now to your next commit (stage)

git reset [file]

unstage a file while retaining the changes in working directory

git diff

diff of what is changed but not staged

git diff --staged

diff of what is staged but not yet committed

git commit -m “[descriptive message]”

commit your staged content as a new commit snapshot

BRANCH & MERGE🔽

Isolating work in branches, changing context, and integrating changes

git branch

list your branches. a * will appear next to the currently active branch

git branch [branch-name]

create a new branch at the current commit

git checkout

switch to another branch and check it out into your working directory

git merge [branch]

merge the specified branch’s history into the current one

git log

show all commits in the current branch’s history

INSPECT & COMPARE🔽

Examining logs, diffs and object information

git log

show the commit history for the currently active branch

git log branchB..branchA

show the commits on branchA that are not on branchB

git log --follow [file]

show the commits that changed file, even across renames

git diff branchB...branchA

show the diff of what is in branchA that is not in branchB

git show [SHA]

show any object in Git in human-readable format

TRACKING PATH CHANGES🔽

Versioning file removes and path changes

git rm [file]

delete the file from project and stage the removal for commit

git mv [existing-path] [new-path]

change an existing file path and stage the move

git log --stat -M

show all commit logs with indication of any paths that moved

IGNORING PATTERNS🔽

Preventing unintentional staging or commiting of files

logs/
*.notes
pattern*/

Save a file with desired patterns as .gitignore with either direct string matches or wildcard globs.

git config --global core.excludesfile [file]

system wide ignore pattern for all local repositories

SHARE & UPDATE🔽

Retrieving updates from another repository and updating local repos

git remote add [alias] [url]

add a git URL as an alias

git fetch [alias]

fetch down all the branches from that Git remote

git merge [alias]/[branch]

merge a remote branch into your current branch to bring it up to date

git push [alias] [branch]

Transmit local branch commits to the remote repository branch

git pull

Fetch and merge any commits from the tracking remote branch

REWRITE HISTORY🔽

Rewriting branches, updating commits and clearing history

git rebase [branch]

apply any commits of current branch ahead of specified one

git reset --hard [commit]

clear staging area, rewrite working tree from specified commit

TEMPORARY COMMITS🔽

Temporarily store modified, tracked files in order to change branches

git stash

Save modified and staged changes

git stash list

ist stack-order of stashed file changes

git stash pop

write working from top of stash stack

git stash drop

discard the changes from top of stash stack

 ----------------------------------------------------------------

 

git Cheat Sheet

by Ben Nadel for future Ben Nadel

The git API is so vast and so esoteric, I can barely manage to keep a fraction of one-percent of it in my head. As such, I wanted to outline a few of the commands that I commonly (and sometimes uncommonly) reach for. This way, when I inevitably get stuck and my brain fails me, I have something that I can refer back to.

Future Ben, you are welcome!

Table of Contents

Use Cases

I want to show the status of the current branch.

The status command shows differences between the working tree, the index, and head commit.

git status

I want to create a new branch that is based on the current branch.

In general, you want to implement new features in short-lived "feature branches" so that changes can be isolated. You can use the checkout command to create a new branch based on the current branch:

git checkout master

# Creates a new branch, `my-feature`, based on `master`.
git checkout -b my-feature

I want to checkout the previous branch that I was on.

In some of the git commands, the - token refers to the "last branch" that you had checked-out. This makes it very easy to jump back-and-forth between two branches:

git checkout master
git checkout my-feature

# At this point, the `-` refers to the `master` branch.
git checkout -

# At this point, the `-` refers to the `my-feature` branch.
git checkout -

The - token can also be used to merge-in the last branch that you had checked-out:

git checkout my-feature
# ... changes to the working tree (your file system).
git add .
git commit -m "Awesome updates."
git checkout master

# At this point, the `-` refers to the `my-feature` branch.
git merge -

The - token can also be used to cherry-pick the most recent commit of the last branch that you had checked-out:

git checkout my-feature
# ... changes to the working tree (your file system).
git add .
git commit -m "Minor tweaks."

git checkout master

# At this point, the `-` refers to the `my-feature` branch.
git cherry-pick -

I want to list the files that have been modified in the current working tree.

By default, when you call git diff, you see all of the content that has been modified in the current working tree (and not yet staged). However, you can use the --stat modifier to simply list the files that have been modified:

git diff --stat

I want to view the changes that were made in a given commit.

When show is given a branch name, it will default to head - the last or most-recent commit on the given branch:

git checkout master

# Outputs the changes made to the `head` commit of the current (`master`)
# branch.
git show

# Outputs the changes made to the `head` commit of the `my-feature` branch.
git show my-feature

You can also use the show command to target a specific commit that is not the head commit. This can be done with a specific commit hash; or, a relative commit operator like ~:

# Outputs the changes made in the commit with the given hash.
git show 19e771

# Outputs the changes made in in a previous commit of the current (`master`)
# branch.
git show head~ # Show changes in first parent.
git show head~~ # Show changes in first parent's first parent.
git show head~~~ # Show changes in first parent's first parent's first parent.

# Outputs the changes made in a previous commit of the `my-feature` branch.
git show my-feature~
git show my-feature~~
git show my-feature~~~

I want to list the files that were changed in a given commit.

Just as with git diff, you can limit the output of the git show command using the --stat modifier. This will list the files that were changed in the given commit:

# Outputs the list of files changed in the commit with the given hash.
git show 19e771 --stat

I want to view the changes that were made across multiple commits.

While the show command can show you changes in a given commit, you can use the diff command to show changes across multiple commits:

git checkout master

# Outputs the changes between `head~` and `head` of the current branch. If
# only one commit is provided, other commit is assumed to be `head`.
git diff head~

# Outputs the changes between the first commit and the second commit.
git diff head~~~..head~~

And, since branch names are really just aliases for commits, you can use a branch name in order to show the changes between one branch and your branch:

git checkout my-feature

# At this point, the following are equivalent and output the changes between
# the `head` commit of the `master` branch and the `head` commit of the
# `my-feature` branch.
git diff master
git diff master..head
git diff master..my-feature

I want to view the changes that were made in a given file.

By default, the show command shows all of the changes in a given commit. You can limit the scope of the output by using the -- modifier and identifying a filepath:

# Outputs the changes made to the `README.md` file in the `head` commit of the
# `my-feature` branch.
git show my-feature -- README.md

# Outputs the changes made to the `README.md` file in the `19e771` commit.
git show 19e771 -- README.md

I want to view the contents of a file in a given commit.

By default, the show command shows you the changes made to a file in a given commit. However, if you want to view the entire contents of a file as defined at that time of a given commit, regardless of the changes made in that particular commit, you can use the : modifier to identify a filepath:

# Outputs the contents of the `README.md` file as defined in the `head` commit
# of the `my-feature` branch.
git show my-feature:README.md

# Outputs the contents of the `README.md` file as defined in the `19e771`
# commit.
git show 19e771:README.md

I want to open the contents of a file in a given commit in my editor.

Since you're working on the command-line, the output of any git-command can be piped into another command. As such, you can use the show command to open a previous commit's file-content in your editor or viewer of choice:

# Opens the `README.md` file from the `head` commit of the `my-feature` branch
# in the Sublime Text editor.
git show my-feature:README.md | subl

# Opens the `README.md` file from the `19e771` commit in the `less` viewer.
git show 19e771:README.md | less

I want to copy a file from a given commit into my current working tree.

Normally, the checkout command will update the entire working tree to point to a given commit. However, you can use the -- modifier to copy (or checkout) a single file from the given commit into your working tree:

git checkout my-feature

# While staying on the `my-feature` branch, copy the `README.md` file from
# the `master` branch into the current working tree. This will overwrite the
# current version of `README.md` in your working tree.
git checkout master -- README.md

I want to copy the last commit from another branch into my branch.

When you don't want to merge a branch into your current working tree, you can use the cherry-pick command to copy specific commit-changes into your working tree. Doing so creates a new commit on top of the current branch:

git checkout master

# Copy the `head` commit-changes of the `my-feature` branch onto the `master`
# branch. This will create a new `head` commit on `master`.
git cherry-pick my-feature

I want to copy an earlier commit from the current branch to the head.

Sometimes, after you understand why reverted code was breaking, you want to bring the reverted code back into play and then fix it. You could use the revert command in order to "revert the revert"; but, such terminology is unattractive. As such, you can cherry-pick the reverted commit to bring it back into the head where you can then fix it and commit it:

git checkout master

# Assuming that `head~~~` and `19e771` are the same commit, the following are
# equivalent and will copy the changes in `19e771` to the `head` of the 
# current branch (as a new commit).
git cherry-pick head~~~
git cherry-pick 19e771

I want to update the files in the current commit.

If you want to make changes to a commit after you've already committed the changes in your current working tree, you can use the --amend modifier. This will add any staged changes to the existing commit.

git commit -m "Woot, finally finished!"

# Oops, you forgot a change. Edit the file and stage it.
# ... changes to the working tree (your file system).
git add oops.txt

# Adds the currently-staged changes (oops.txt) to the current commit, giving
# you a chance to update the commit message.
git commit --amend

I want to edit the current commit message.

In addition to adding files to the current commit, the --amend modifier can also be used to change the current commit message:

git add .
git commit -m "This is greet."

# Oh noes! You misspelled "great". You can edit the current commit message:
git commit --amend -m "This is great."

Note that if you omit the -m message portion of this command, you will be able to edit the commit message in your configured editor.

I want to copy master into my feature branch.

At first, you may be tempted to simply merge your master branch into your feature branch, but doing so will create an unattactive, non-intuitive, Frankensteinian commit tree. Instead, you should rebase your feature branch on master. This will ensure that your feature commits are cleanly colocated in the commit tree and align more closely with a human mental model:

git checkout my-feature

# This will unwind the commits specific to the `my-feature` branch, pull in
# the missing `master` commits, and then replay your `my-feature` commits.
git rebase master

Once your my-feature branch has been rebased on master, you could then, if you wanted to, perform a --ff-only merge ("fast forward only") of your feature branch back into master:

git checkout my-feature
git rebase master

# Fast-forward merge of `my-feature` changes into `master`, which means there
# is no creation of a "merge commit" - your `my-features` changes are simply
# added to the top of `master`.
git checkout master
git merge --ff-only my-feature

That said, when you're working on a team where everyone uses a different git workflow, you will definitely want a "merge commit". This way, multi-commit merges can be easily reverted. To force a "merge commit", you can use the --no-ff modifier ("no fast forward"):

# Get the `my-feature` branch ready for merge.
git checkout my-feature
git rebase master

# Merge the `my-feature` branch into `master` creating a merge-commit.
git checkout master
git merge --no-ff my-feature

Now, if the merge needs to be reverted, you can simply revert the "merge commit" and all commits associated with the merge will be reverted.

I want to revert the merge of my feature branch into master.

If you performed a --ff-only merge of your feature branch into master, there's no "easy" solution. You either have to reset the branch to an earlier commit (rewriting history); or, you have to revert the individual commits in the merge.

If, however, you performed a --no-ff merge ("no fast forward") that created a "merge commit", all you have to do is revert the merge commit:

git checkout master

# Merge the feature branch in, creating a "merge commit".
git merge --no-ff my-feature

# On noes! You didn't mean to merge that in. Assuming that the "merge commit"
# is now the `head` of `master`, you can revert back to the commit's fist
# parent, the `master` branch: -m 1. And, since `head` and `master` are the
# same commit, the following are equivalent:
git revert -m 1 head
git revert -m 1 master

I want to extract changes that I accidentally made to master.

Sometimes, after you've finished working on your feature branch, you execute git checkout master, only find that you've been accidentally working on master the whole time (error: "Already on 'master'"). To fix this, you can checkout a new branch and reset your master branch:

git checkout master
# > error: Already on 'master'

# While on the `master` branch, create the `my-feature` branch as a copy of
# the `master` branch. This way, your `my-feature` branch will contain all of # your recent changes.
git checkout -b my-feature

# Now that your changes are safely isolated, get back into your `master`
# branch and `reset` it with the `--hard` modifier so that your local index
# and file system will match the remote copy.
git checkout master
git reset --hard origin/master

I want to undo the changes that I've made to my branch.

If you've edited some files and then change your mind about keeping those edits, you can reset the branch using the --hard modifier. This will update the working tree - your file structure - to match the structure of the last commit on the branch (head).

Caution: You will lose data when using the --hard option.

git checkout my-feature
# ... changes to the working tree (your file system).
git add .

# Remove the file from staging AND remove the changes from the file system.
git reset --hard

If you call git reset without the --hard option, it will reset the staging to match the head of the branch, but it will leave your file system in place. As such, you will be left with "unstaged changes" that can be modified and re-committed.

I want to remove unpublished changes from my branch.

If you've committed changes to the local copy of a remote (ie, published) branch, but you want to undo those changes, you can reset the local branch to match the remote branch:

git checkout my-feature

# Update the remote copy of the `my-feature` branch in order to make sure that
# you are working with the most up-to-date remote content.
git fetch origin my-feature

# Now, reset the local copy of `my-feature` to match the published copy. This
# will update your index and your local file system to match the published
# version of `my-feature`.
git reset --hard origin/my-feature

I want to see which branches have already been merged into master.

From any branch, you can locate the merged-in branches (that can be safely deleted) by using the --merged modifier:

git checkout master

# List all of the local branches that have been merged into `master`. This
# command will be relative to the branch that you have checked-out.
git branch --merged

I want to see which branches have not yet been merged into master.

From any branch, you can locate the unmerged branches by using the --no-merged modifier:

git checkout master

# List all of the local branches that have NOT YET been merged into `master`.
# This command will be relative the branch you have checked-out.
git branch --no-merged

I want to delete my feature branch.

After you're merged your feature branch into master, you can delete your feature branch using the branch command:

# Merge your `my-feature` branch into `master` creating a "merge commit."
git checkout master
git merge --no-ff my-feature

# Safely delete the merged-in `my-feature` branch. The `-d` modifier will
# error-out if the given branch has not yet been merged into the current
# branch.
git branch -d my-feature

If you want to abandon a feature branch, you can use the -D modifier to force-delete it even if it has not yet been merged into master:

git checkout master

# Force-delete the `my-feature` branch even though it has not been merged
# into the `master` branch.
git branch -D my-feature

I want to delete a remote branch.

When you delete a branch using git branch -d, it deletes your local copy; but, it doesn't delete the remote copy from your origin (ex, GitHub). To delete the remote copy, you have to push the branch using the : prefix:

git checkout master

# Safely delete the local copy of your `my-feature` branch. The `-d` modifier
# will error-out if the given branch has not been fully-merged into `master`.
git branch -d my-feature

# Delete the remote copy of the `my-feature` branch from the origin. The `:`
# prefix sends this through as a "delete" command for the given branch.
git push origin :my-feature

I want to update master because my push was rejected.

If you've committed changes to master but you forgot to pull recent changes from the remote master branch, your next push will be rejected with an error that looks like, "Updates were rejected because the tip of your current branch is behind its remote counterpart". To fix this, you can use the --rebase modifier:

git checkout master
git merge --no-ff my-feature

# Oh noes! You forgot to pull in the latest remote copy of `master` before you
# merged your `my-feature` commits. No problem, just `--rebase` your local
# `master` on the remote branch. This will move your local changes to the tip
# of the `master` branch.
git pull --rebase 

# Now that you've pulled-in the remote changes, you should be able to push
# your updated `master` branch.
git push origin master

I want to remove a file from my staging area.

If you accidentally added too many files to the staging area (in preparation for a git commit), you can use the rm --cached command to remove them from the staging area but keep them in the working tree:

git add .

# Oh noes! You didn't mean to add all of the files to the staging area. You
# can remove some of the staged files using the `--cached` modifier:
git rm --cached secrets.config

If you accidentally added an entire directory to the staging area, you can add the -r modifier to recursively apply the rm command:

git add .

# Oh noes! You didn't mean to add the ./config directory. You can recursively
# remove it with the `-r` modifier:
git rm --cached -r config/.

When you rm files using --cached, they will remain in your working tree and will become "unstaged" changes.

I want to squash several commits into one (or more) commits.

Your commit history is a representation or your personality. It is a manifestation of your self-respect and the respect you have for your team. As such, you will often need to rewrite your feature branch's history before merging it into master. This allows you to get rid of intermediary commit messages like, "still working on it." and "Meh, missed a bug.". To do this, you can perform an "interactive rebase".

The "interactive rebase" gives you an opportunity to indicate how intermediary commits should be rearranged. Some commits can be "squashed" (combined) together. Others can omitted (remove). And others can be edited. When performing an interactive rebase, you have to tell git which commit to use as the starting point. If you're on an up-to-date feature branch, the starting point should be master.

# Create the `my-feature` branch based on `master`.
git checkout master
git checkout -b my-feature

# ... changes to the working tree (your file system).
git add .
git commit -m "Getting close."

# ... changes to the working tree (your file system).
git add .
git commit -m "Missed a bug."

# ... changes to the working tree (your file system).
git add .
git commit -m "Uggggg! Why is this so hard?"

# ... changes to the working tree (your file system).
git add .
git commit -m "Woot, finally got this working."

# At this point, your commit history is sloppy and would bring much shame on
# your family if it ended-up in `master`. As such, you need to squash the
# commits down into a single commit using an interactive rebase. Here, you're
# telling `git` to use the `master` commit as the starting point:
git rebase -i master

As this point, git will open up an editor that outlines the various commits and asks you how you want to rearrange them. It should look something like this, with the commits listed in ascending order (oldest first):

pick 27fb3d2 Getting close.
pick e8214df Missed a bug.
pick ce5ed14 Uggggg! Why is this so hard?
pick f7ee6ab Woot, finally got this working.

# Rebase b0fced..f7ee6ab onto b0fced (4 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit

At this point, you can identify the later commits as needing to be squashed (s) down into the oldest commit (the one you are picking):

pick 27fb3d2 Getting close.
s e8214df Missed a bug.
s ce5ed14 Uggggg! Why is this so hard?
s f7ee6ab Woot, finally got this working.

Once saved, git will prompt you to provide a cleaner commit message. And, once provided, your four shameful commits will be squashed down into a single, cohesive, meaningful commit.

I want to squash several commits into one commit without using rebase.

In the vast majority of cases, if your git workflow is clean and true and your feature branch is short-lived, an interactive rebase should be straightforward and pain-free. However, once you make the mistake of periodically merging master into your feature branch, you are inviting a world of hurt. In such a case, you can use the merge command with the --squash modifier as an escape hatch.

When you run git merge --squash, you copy the file-changes from one branch into another branch without actually copying the commit meta-data. Instead, the changes are brought-over as staged changes on top of the current branch. At that point, you can commit all the staged changes as a single commit.

Assuming your my-feature branch needs to be "fixed", you can use the following workflow:

# Assuming that the `my-feature` branch is the branch that needs to be fixed,
# start off my renaming the branch as a backup (the `-m` modifier performs a
# rename):
git checkout my-feature
git branch -m my-feature-backup

# Now, checkout the `master` branch and use it to create a new, clean
# `my-feature` branch starting point.
git checkout master
git checkout -b my-feature

# At this point, your `my-feature` branch and your `master` branch should be
# identical. Now, you can "squash merge" your old feature branch into the new
# and clean `my-feature` branch:
git merge --squash my-feature-backup

# All the file-changes should now be in the `my-feature` branch as staged
# edits ready to be committed.
git commit -m "This feature is done and awesome."

# Delete the old backup branch as it is no longer needed. You will have to
# force delete (`-D`) it since it was never merged into `master`.
git branch -D my-feature-backup

ASIDE: You should almost never need to do this. If you find yourself having to do this a lot; or, you find yourself dealing with a lot of "conflict resolution", you need to reevaluate your git workflow. Chances are, your feature branches are living way too long.

I want to temporarily set-aside my feature work.

The life of a developer is typically "interrupt driven". As such, there is often a need to briefly set aside your current work in order to attend to more pressing matters. In such a case, it is tempting to use git stash and git stash pop to store pending changes. But, do not do this. Stashing code requires unnecessary mental overhead. Instead, simply commit the changes to your current feature branch and then perform an interactive rebase later on in order to clean up your commits:

# Oh noes! Something urgent just came up - commit your changes to your feature
# branch and then go attend to the more pressing work.
git add .
git commit -m "Saving current work - jumping to more urgent matters."

git checkout master

Now, you never have to remember where those pending changes are. This guarantees that you won't lose your work.

If you were working directly on master when urgent matters came up, you can still avoid having to use git stash. To keep your work, simply checkout a new branch and the commit your pending changes to that new branch:

# Oh noes! Something urgent just came up - checkout a new branch. This will
# move all of your current work (staged and unstaged) over to the new branch.
git checkout -b temp-work

# Commit any unstaged changes.
git add .
git commit -m "Saving current work - jumping to more urgent matters."

git checkout master

Now, your master branch should be back in a pristine state and your temp-work branch can be continued later.

I want to keep my changes during conflict resolution.

If your Git workflow is healthy - meaning that you have short-lived feature branches - conflicts should be very few and far between. In fact, I would assert that getting caught-up in frequent conflicts is an indication that something more fundamental to your workflow is primed for optimization.

That said, conflicts do happen. And, if you want to resolve a conflict by selecting "your version" of a file, you can use git checkout --theirs in a merge conflict, a cherry-pick conflict, and a rebase conflict.

In a merge conflict, --theirs indicates the branch being merged into the current context:

git checkout master
git merge --no-ff my-feature

# Oh noes! There is a conflict in "code.js". To keep your version of the
# code.js file, you can check-it-out using --theirs and the file path:
git checkout --theirs code.js
git add .
git merge --continue

Similarly, in a cherry-pick conflict, --theirs indicates the branch being cherry-picked into the current context:

git checkout master
git cherry-pick --no-ff my-feature

# Oh noes! There is a conflict in "code.js". To keep your version of the
# code.js file, you can check-it-out using --theirs and the file path:
git checkout --theirs code.js
git add .
git merge --continue

In a rebase conflict, --theirs indicates the branch that is being replayed on top of the current context (See Aside):

git checkout my-feature
git rebase master

# Oh noes! There is a conflict in "code.js". To keep your version of the
# code.js file, you can check-it-out using --theirs and the file path:
git checkout --theirs code.js
git add .
git rebase --continue

ASIDE: Using --theirs in a rebase can seem confusing because you are already in "your" feature branch. As such, it would seem logical that your version of the code would be targeted with --ours, not --theirs. However, a rebase operates kind of like a series of cherry-pick operations. You can think of a rebase as doing the following:

  • Check-out an earlier, common commit between your feature branch and the target branch.
  • Cherry-pick your feature branch commits onto the earlier commit.
  • Replace your feature branch with this temporary branch

With this mental model, "your" version - targeted using --theirs - is the version being cherry-picked into the "current context" (the temporary branch).

I want to find the commit that deleted a file.

To find a deleted file, you can use the git log command and filter the results based on file status and path patterns. In this case, you want to use --diff-filter=D which limits the results to deleted files. And, since the files in question have been deleted, the path pattern must come after the final -- delimiter:

# Find the commit that deleted the file "projects.js".
git log --diff-filter=D --name-only -- wwwroot/app/projects.js

The --name-only option includes statistics about the commit (which files were changed); but, limits the file meta-data to include file paths only.

Of course, since you are looking for a delete file, you may not remember the exact file path of the deleted file. In that case, you can use pattern matching on the file path:

# Find the commit that deleted the file "projects.js" that resided somewhere
# in the "app" directory. The double-asterisk matches across directories.
git log --diff-filter=D --name-only -- "wwwroot/app**/projects.js"

# You can even use pattern matching on the file name itself if you can't
# remember exactly what it was named.
git log --diff-filter=D --name-only -- "wwwroot/**/*project*.js"

The default output of the git log command can be a bit verbose since it outputs the commit message along with the deleted files. To minify the output, you can use the --oneline and --pretty options:

# Find the commit that deleted the file "projects.js", but only show a one-line
# commit message and the list of files.
git log --diff-filter=D --name-only --oneline -- "wwwroot/app**/projects.js"

# To get slightly easier-to-read output, you can use the `--pretty` option with
# some specialized formatting (instead of the `--oneline` option). This
# includes the abbreviated hash, the author, the relative date, and the
# subject line. And, includes some human-friendly line-breaks.
git log --diff-filter=D --name-only --pretty=format:"%Cgreen%h - %an, %ar : %s" -- "wwwroot/app**/projects.js"

I want to find the commit that deleted a file that contained a piece of code.

To find a deleted file that contained a given piece of code, you can use the git log command and filter based on pattern matching within the diff. In this case, you want to use --diff-filter=D which limits the results to deleted files.

You can use the -S option to match on a string literal:

# Find the commit that deleted a file that contained the code 'isProcessed'.
git log -S 'isProcessed' --diff-filter=D --name-only

# To limit the search to the "app" directory, you can include a file path after
# the final `--` delimiter.
git log -S 'isProcessed' --diff-filter=D --name-only -- wwwroot/app/

# To make the search case-insensitive, include the -i option.
git log -S 'isprocessed' -i --diff-filter=D --name-only

You can use the -G option to match on a Regular Expression pattern instead of a string literal:

# Find the commit that deleted a file that contained the code `isProcessed`,
# matching on strict word-boundaries.
git log -G '\bisProcessed\b' --diff-filter=D --name-only

# To make the search case-insensitive, include the `-i` option.
git log -G '\bisprocessed\b' -i --diff-filter=D --name-only

The default output of the git log command can be a bit verbose since it outputs the commit message along with the deleted files. To minify the output, you can use the --oneline and --pretty options:

# Find the commit that deleted a file that contained the code `isProcessed`,
# matching on strict word-boundaries.
git log -G '\bisProcessed\b' --diff-filter=D --name-only --oneline

# To get slightly easier-to-read output, you can use the `--pretty` option with
# some specialized formatting (instead of the `--oneline` option). This
# includes the abbreviated hash, the author, the relative date, and the
# subject line. And, includes some human-friendly line-breaks.
git log -G '\bisProcessed\b' --diff-filter=D --name-only --pretty=format:"%Cgreen%h - %an, %ar : %s"

Once you locate the commit that appears to contain your code, you can use the git show command to view the contents of the commit delta:

# Find the commit that deleted a file that contained the code `isProcessed`,
# matching on strict word-boundaries.
git log -G '\bisProcessed\b' --diff-filter=D --name-only --oneline

# The above `log` action listed commit `aef037`. Use `git show` to output the
# changes made in the given commit.
git show aef037
from  https://github.com/bennadel/git-cheat-sheet
--------------------------------------------------------

Git常用命令速览


首先上张图:

配置默认信息:

git config --global user.name "[username]"
git config --global user.email "[email_address]"

初始化版本库

# 将当前目录初始化为 git 版本库
git init
# 重命名当前分支为 main
git branch -M main

推荐使用 main 作为主分支名 参见: 链接

设置远程分支

git remote add origin [repo_url]

暂存更改

git add .

提交更改

git commit -m "[commit_message]"

发布 main 分支

git push -u origin main

较为常用的命令
一、修改与提交操作
查看当前版本库状态

git status

暂存更改

git add .

提交更改

git commit -m "[commit_message]"

修改最后一次提交

运行以下命令后按i进入编辑模式

git commit --amend

二、分支操作
新建分支

git branch [new_branch]

切换分支

git checkout [banch_name]
git checkout HEAD^
git checkout HEAD~3

删除本地分支

git branch -d [branch_name]

新建并切换到指定分支

git checkout -b [branch_name]

合并分支

合并指定分支到当前分支

git merge [branch_name]

迁移分支

把当前分支迁移到目标分支的后面

git rebase [branch_name]

迁移完了后注意 master 等分支的当前引用位置,若目标分支是 maste、main 等主分支, 建议将主分支也 rebase 到最新的更改

三、撤销操作
软撤销

仅仅撤销上一个 commit 不修改你改动了的文件

git reset --soft HEAD^

若只是想修改 commit 信息,可以先运行下列命令,然后输入 i 进入编辑模式,并修改第一行 commit 信息

git commit --amend

硬撤销

撤销 commit 的同时, 删除你对文件做出的所有改动, 还原到上一个 commit 的状态

说人话就是: 不懂的话千万别乱用 否则一下午写的代码全白给

git reset --hard HEAD^ # 撤销上一个提交
git reset --hard HEAD~3 # 撤销前三个提交

如果实在是不小心误删, 可以先查看撤销记录, 找到要还原的记录的 commitID , 再通过 commitID 还原:

git reflog
git reset --hard [commitID]

然后再根据需求撤销当前 commmit
四、查看提交历史

git log # 查看git日志(按q退出)
git show (commitID) # 查看上一次(或者指定id的)提交记录
git log -p [file_name] # 查看指定文件
git blame [file_name] # 以列表形式查看指定文件提交历史

附:可用来查阅的 git 命令集

// 初始化一个 git 版本库
git init
// 查看版本库的状态
git status
// 全局默认信息配置
git config --global user.email "[email_addres]"
// 全局默认信息配置
git config --global user.name "[username]"
// 将所有修改添加到暂存区
git add .
// Ant 风格添加修改
git add *
// 将以 Hello 结尾的文件的所有修改添加到暂存区
git add *Hello
// 将所有以 Hello 开头的文件的修改添加到暂存区
git add Hello*
// 将以 Hello 开头且其后仅跟一位字符的文件的修改提交到暂存区
git add Hello?
// 提交更改并附带有意义的信息
git commit -m "comment"
// 为本地版本库设置远程分支
git remote add origin [repo_url]
// 克隆远程版本库到本地( git 协议最快)
git clone [repo_url]
// 查看当前版本库所有分支
git branch
// 创建 [branch_name] 分支
git branch [branch_name]
// 删除 [branch_name] 分支
git branch -d [branch_name]
// 强制删除 [branch_name] 分支
git branch -D [branch_name]
// 丢弃工作区的修改退回原始状态
git checkout -- [file_name]
// 切换到一个已经存在的分支
git checkout [branch_name]
// 创建 [branch_name] 分支,并切换到 [branch_name] 分支
git checkout -b [branch_name]
// 拉取远程分支的最新更改到本地
git pull
// 将本地 [branch_name] 分支推送到远程的 [branch_name] 分支
git push -u origin [branch_name]
// 切换到 [branch_name] 分支并将本地 [branch_name] 分支推送到远程
git push -u origin [branch_name]
// 将标签 v1.0 推送到远程版本库
git push -u origin v1.0
// 一次性推送全部标签
git push -u origin --tags
// 将 [branch_name] 分支合并到 [branch_name] 分支
git merge [branch_name]
// 禁用 Fast forward 合并 [branch_name] 分支
git merge --no-ff -m "comment" [branch_name]
// 冻结当前的分支修改
git stash
// 列出所有工作现场存储
git stash list
// 查看 commit 信息
git log
// 将 commit 信息简化成一行显示
git log --pretty=oneline
// 图形化展示分支情况
git log --graph --pretty=oneline --abbrev-commit
// 退回到上一个版本, HEAD 表示当前版本
git reset --hard HEAD^
// 退回到上两个版本
git reset --hard HEAD^^
//退回到上 100 个版本
git reset --hard HEAD~100
// 退回到指定版本
git reset --hard [commitID]
// 丢弃已经添加到暂存区的修改
git reset HEAD [file_name]
// 在当前版本 HEAD 上打一个名称为 [tag_name] 的标签
git tag [tag_name]
// 查看所有标签,列表出所有的标签名
git tag
// 为目标 commit 打上 [tag_name] 标签
git tag [tag_name] [commitID]
// 为 commit 打上标签
git tag -a [tag_name] -m "[commit_msg]" [commitID]
// 查看标签的信息,包括文字说明
git tag [tag_name]
// 删除标签 [tag_name]
git tag -d [tag_name]
// 在文件未提交前查看文件被修改的部分
git diff [file_name]
// 从 git 版本库中删除文件(同时从文件系统中删除文件)
git rm file
// 查看之前版本的 commitID
git reflog
// 查看指定标签的提交信息
git show [tag_name]

 

No comments:

Post a Comment