Git Learning Notes
[toc]
Learn Git Branching
This is my notes while learning https://learngitbranching.js.org/
git merge
git merge <br>
this command merge branch br
to current branch
We can still merge main
into bugFix
to ensure each branch contains all the work in the repo
git rebase
(I haven’t found where it is useful)
Rebasing essentially takes a set of commits, “copies” them, and plops them down somewhere else.
While this sounds confusing, the advantage of rebasing is that it can be used to make a nice linear sequence of commits. The commit log / history of the repository will be a lot cleaner if only rebasing is allowed.
We would like to move our work from bugFix directly onto the work from main. That way it would look like these two features were developed sequentially, when in reality they were developed in parallel.
git rebase main
We can continue to move main
to bugFix
with git rebase bugFix
from main
relative refs
With relative refs, you can start somewhere memorable (like the branch bugFix
or HEAD
) and work from there.
Relative commits are powerful, but we will introduce two simple ones here:
- Moving upwards one commit at a time with
^
- Moving upwards a number of times with
~<num>
git checkout main^
git checkout bugFix~1
is the same as git checkout bugFix^
One of the most common ways I use relative refs is to move branches around. You can directly reassign a branch to a commit with the -f
option. So something like:
1 | git branch -f main HEAD~3 |
moves (by force) the main branch to three parents behind HEAD.
Relative refs gave us a concise way to refer to C1
and branch forcing (-f
) gave us a way to quickly move a branch to that location.
reverse changes
git reset
If we want to reset to some previous commit and ignore the commits after it, we can use git reset
git reset --hard <target commit id>
if we git push
here, it will be an error because local HEAD is behind remote counterpart. So we can use git push -f
to push
git revert
If we want to cancel the modification of some commit while saving the modification of the commits after it, we can use git revert
git revert -n <commit id>
if we want to can cel commit id
Then we can have a new commit git commit -m "revert xx"
git cherry-pick
ref: https://www.ruanyifeng.com/blog/2020/04/git-cherry-pick.html
If we want to merge some commits of another branch to current branch, we can use cherry pick.
举例来说,代码仓库有master
和feature
两个分支。
1
2
3 a - b - c - d Master
\
e - f - g Feature
现在将提交f
应用到master
分支。
1
2
3
4
5 # 切换到 master 分支
$ git checkout master
# Cherry pick 操作
$ git cherry-pick f
上面的操作完成以后,代码库就变成了下面的样子。
1
2
3 a - b - c - d - f Master
\
e - f - g Feature
从上面可以看到,master
分支的末尾增加了一个提交f
。
interactive rebase
When the interactive rebase dialog opens, you have the ability to do two things in our educational application:
You can reorder commits simply by changing their order in the UI (via dragging and dropping with the mouse).
You can choose to keep all commits or drop specific ones. When the dialog opens, each commit is set to be included by the
pick
button next to it being active. To drop a commit, toggle off itspick
button.
git rebase -i HEAD~4
and we can reorganize the commits from C2 to C5
a case for cherry pick and interactive rebase
Locally stacked commits
Here’s a development situation that often happens: I’m trying to track down a bug but it is quite elusive. In order to aid in my detective work, I put in a few debug commands and a few print statements.
All of these debugging / print statements are in their own commits. Finally I track down the bug, fix it, and rejoice!
Only problem is that I now need to get my bugFix
back into the main
branch. If I simply fast-forwarded main
, then main
would get all my debug statements which is undesirable. There has to be another way…
We need to tell git to copy only one of the commits over. This is just like the levels earlier on moving work around – we can use the same commands:
git rebase -i
git cherry-pick
Solution:
1 | 1: cherry pick |
make modification to previous commit
Here’s another situation that happens quite commonly. You have some changes (newImage
) and another set of changes (caption
) that are related, so they are stacked on top of each other in your repository (aka one after another).
The tricky thing is that sometimes you need to make a small modification to an earlier commit. In this case, design wants us to change the dimensions of newImage
slightly, even though that commit is way back in our history!!
We will overcome this difficulty by doing the following:
We will re-order the commits so the one we want to change is on top with
git rebase -i
We will
git commit --amend
to make the slight modificationThen we will re-order the commits back to how they were previously with
git rebase -i
Finally, we will move main to this updated part of the tree to finish the level (via the method of your choosing)
Solution: use git rebase -i
1 | revert C3 and C2 |
Solution: use git cherry-pick
1 | git chekout main |
tag
git tag v1 C1
git fetch
git fetch
git fetch
performs two main steps, and two main steps only. It:
- downloads the commits that the remote has but are missing from our local repository, and…
- updates where our remote branches point (for instance,
o/main
)
git fetch
essentially brings our local representation of the remote repository into synchronization with what the actual remote repository looks like (right now).
If you remember from the previous lesson, we said that remote branches reflect the state of the remote repositories since you last talked to those remotes. git fetch
is the way you talk to these remotes! Hopefully the connection between remote branches and git fetch
is apparent now.
git fetch
doesn’t change local state
git fetch
, however, does not change anything about your local state. It will not update your main
branch or change anything about how your file system looks right now.
This is important to understand because a lot of developers think that running git fetch
will make their local work reflect the state of the remote. It may download all the necessary data to do that, but it does not actually change any of your local files.
git pull
Now that we’ve seen how to fetch data from a remote repository with git fetch
, let’s update our work to reflect those changes!
There are actually many ways to do this – once you have new commits available locally, you can incorporate them as if they were just normal commits on other branches. This means you could execute commands like:
git cherry-pick o/main
git rebase o/main
git merge o/main
- etc., etc.
In fact, the workflow of fetching remote changes and then mergingthem is so common that git actually provides a command that does both at once! That command is git pull
.
git pull
conflict of remote and local branch
Solution: rebase
1 | pull remote main and rebase local main with o/main |
Solution: merge
1 | git pull |
merge features
It’s common for developers on big projects to do all their work on feature branches (off of main
) and then integrate that work only once it’s ready. This is similar to the previous lesson (where side branches get pushed to the remote), but here we introduce one more step.
Some developers only push and pull when on the main
branch – that way main
always stays updated to what is on the remote (o/main
).
So for this workflow we combine two things:
- integrating feature branch work onto
main
, and - pushing and pulling from the remote
This level is pretty hefty – here is the general outline to solve:
- There are three feature branches –
side1
side2
andside3
- We want to push each one of these features, in order, to the remote
- The remote has since been updated, so we will need to incorporate that work as well
Solution: rebase
1 | pull C8 from remote repo |
Solution: merge
1 | pull C8 from remote repo |
Some tips in Git
This is my notes when learning git mainly from this resource:
https://www.liaoxuefeng.com/wiki/896043488029600
版本回退
git reflog
记录每一条命令
1 | $ git reflog |
git reset
可以回到某个commit
只是要知道commit id ,可以通过git reflog得到
1 | $ git reset --hard 1094a |
暂存区
git add
保存到暂存区
如果想track变化必须在每次修改之后都add然后commit
撤销修改
git checkout -- <file>
用版本库里的版本替换工作区的版本,无论工作区是修改还是删除
命令git checkout -- readme.txt
意思就是,把readme.txt
文件在工作区的修改全部撤销,这里有两种情况:
一种是readme.txt
自修改后还没有被放到暂存区(还没add),现在,撤销修改就回到和版本库一模一样的状态(上一次add);
一种是readme.txt
已经添加到暂存区后(add但还没commit),又作了修改,现在,撤销修改就回到添加到暂存区后的状态。
总之,就是让这个文件回到最近一次git commit
或~~git add
时的状态。
commit 之后就不太好撤销了, 可以回退到上一个commit
git reset HEAD <file>
可以把暂存区的修改撤销掉(unstage),重新放回工作区
Summary for recall modification
场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout -- file
。
场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD
,就回到了场景1,第二步按场景1操作。
场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节,不过前提是没有推送到远程库。
删除文件
git rm git commit 从版本库删除文件
如果本地也不想要就git rm
1 | $ git rm test.txt |
如果本地还想要,只是从本地版本库删除:
git rm --cached <file>
查看已存放:(这个最有用)
git ls-files
查看还没添加的文件:
git status
添加远程库
git remote add origin xxx.git
要关联一个远程库,使用命令git remote add origin git@server-name:path/repo-name.git
;
关联后,使用命令git push -u origin master
第一次推送master分支的所有内容;
可以添加一个刚创建的repo,或者clone 远程repo
创建合并分支
1 | git checkout -b dev |
解决冲突
两个分支commit了不同的修改又要merge的话,需要手动handle confict
手动修改冲突之后再git add git commit 才算解决
git log –graph –oneline 可以看图
1 | $ git log --graph --pretty=oneline --abbrev-commit |
分支管理
git merge --no-ff -m "merge with no-ff" dev
禁用fast-forward模式的合并
1 | git branch -b dev |
开发中的分支策略
首先,master
分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
那在哪干活呢?干活都在dev
分支上,也就是说,dev
分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev
分支合并到master
上,在master
分支发布1.0版本;
你和你的小伙伴们每个人都在dev
分支上干活,每个人都有自己的分支,时不时地往dev
分支上合并就可以了。
所以,团队合作的分支看起来就像这样:
Bug分支
git stash 储藏当前工作
1 | git stash |
恢复用git stash apply
/git stash apply stash@{0}
,stash内容不删除,如果要删除还需要git stash drop
恢复还可以用git stash pop
,恢复并且删除stash
修复bug流程
master有bug就从master checkout 一条issue branch
1 | $ git checkout master |
修改提交
1 | $ git add readme.txt |
切回master再合并
1 | $ git checkout master |
如果master上的bug 在dev branch 上也有,把4c805e2 fix bug 101
这个提交所做的修改cherry-pick到dev分支
1 | $ git branch |
多人协作
小伙伴git clone
一个repo之后只能看到master branch,要在dev分支开发,需要创建远程origin
的dev
分支到本地,然后add commit push
1 | $ git checkout -b dev origin/dev |
如果我也对同一个文件进行修改并push
1 | $ cat env.txt |
会因为远程dev branch比我更新而被拒绝,需要先git pull合并解决冲突
1 | $ git pull |
git pull
也失败了,原因是没有指定本地dev
分支与远程origin/dev
分支的链接,根据提示,设置dev
和origin/dev
的链接:
1 | $ git branch --set-upstream-to=origin/dev dev |
再pull:
1 | $ git pull |
多人合作summary
- 查看远程库信息,使用
git remote -v
; - 本地新建的分支如果不推送到远程,对其他人就是不可见的;
- 从本地推送分支,使用
git push origin branch-name
,如果推送失败,先用git pull
抓取远程的新提交; - 在本地创建和远程分支对应的分支,使用
git checkout -b branch-name origin/branch-name
,本地和远程分支的名称最好一致; - 建立本地分支和远程分支的关联,使用
git branch --set-upstream branch-name origin/branch-name
; - 从远程抓取分支,使用
git pull
,如果有冲突,要先处理冲突。
标签管理
git tag <tag name> <commit id>
用于新建一个标签,默认为HEAD
,也可以指定一个commit id;
git tag -a -m "blablabla..."
可以指定标签信息;
git tag
可以查看所有标签。
git push origin
/git push origin --tags
推送本地标签;
git tag -d <tag name>
/ git push origin :refs/tags/
删除本地或远程标签
Other Problems Encountered
Git push without password
1.使用文件创建用户名和密码
文件创建在用户主目录下:
1 | touch .git-credentials |
2.添加git config内容
1 | git config --global credential.helper store |
执行此命令后,用户主目录下的.gitconfig文件会多了一项:[credential]
1 | helper = store |
重新git push就不需要用户名密码了。
Another effective way:
Source: https://stackoverflow.com/questions/10032461/git-keeps-asking-me-for-my-ssh-key-passphrase
Once you have started the SSH agent with:
1 eval $(ssh-agent)
You have to add your private key to it:
1 ssh-addThis will ask you your passphrase just once, and then you should be allowed to push, provided that you uploaded the public key to Github.
To save key permanently on macOS:
1 ssh-add -KThis will persist it after you close and re-open it by storing it in user’s keychain.
remove git submodule
just got over the same issue yesterday, after having deleted by hand the entry in .gitmodules (e.g. nano .gitmodules), I had to go with
1 | git rm --cached <pathtomodule> |
which returned a message
1 | rm '<pathtomodule>' |
then I needed a
1 | git commit -m "delete cached modules" |
and validated the submodule deletion.
Rename a branch
Follow the steps below to rename a Local and Remote Git Branch:
Start by switching to the local branch which you want to rename:
1
git checkout <old_name>
Rename the local branch by typing:
1
git branch -m <new_name>
At this point, you have renamed the local branch.
If you’ve already pushed the
<old_name>
branch to the remote repository , perform the next steps to rename the remote branch. Delete the previous one.Push the
<new_name>
local branch and reset the upstream branch:1
git push origin -u <new_name>
Delete the
<old_name>
remote branch:1
git push origin --delete <old_name>
Remove a branch
1 | // delete branch locally |
Pull new branch from remote
Three ways:
#1. git checkout –track
1 | git checkout --track <remote-repo>/<remote-branch> |
if we want to rename local branch:
1 | git checkout --track -b <local-branch> <remote-repo>/<remote-branch> |
#2. git checkout -b
1 | git checkout -b <local-branch> <remote-repo>/<remote-branch> |
#3. git fetch origin
1 | git fetch origin <remote-branch>:<local-branch> |
This will not checkout to new local branch. Local branch is not set to track remote branch either.
So we need to
1 | git branch --set-upstream-to=origin/<remote-branch> <local-branch> |
Compare different commits
You can also compare two arbitrary commits in your repository or its forks on GitHub in a two-dot diff comparison.
To quickly compare two commits or Git Object IDs (OIDs) directly with each other in a two-dot diff comparison on GitHub, edit the URL of your repository’s “Comparing changes” page.
For example, this URL uses the shortened seven-character SHA codes to compare commits c3a414e
and faf7c6f
: https://github.com/github/linguist/compare/c3a414e..faf7c6f
.