[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

Screen Shot 2021-01-14 at 11.24.31

Screen Shot 2021-01-14 at 11.24.41

Screen Shot 2021-01-14 at 11.24.57

We can still merge main into bugFix to ensure each branch contains all the work in the repo

Screen Shot 2021-01-14 at 11.33.42

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.

Screen Shot 2021-01-14 at 11.45.31

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

Screen Shot 2021-01-15 at 17.36.56

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>

Screen Shot 2021-01-14 at 12.14.06

git checkout main^

Screen Shot 2021-01-14 at 12.14.33

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.

Screen Shot 2021-01-14 at 12.25.29

Relative refs gave us a concise way to refer to C1and branch forcing (-f) gave us a way to quickly move a branch to that location.

Screen Shot 2021-01-14 at 12.25.52

reverse changes

Ref: https://blog.csdn.net/yxlshk/article/details/79944535

git reset

img

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.

举例来说,代码仓库有masterfeature两个分支。

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 pickbutton next to it being active. To drop a commit, toggle off its pick button.

Screen Shot 2021-01-15 at 17.19.09

git rebase -i HEAD~4 and we can reorganize the commits from C2 to C5

Screen Shot 2021-01-15 at 17.20.13

Screen Shot 2021-01-15 at 17.20.54

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

Screen Shot 2021-01-15 at 17.31.01

Solution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#1: cherry pick

gti checkout C1
git cherry-pick C4

#2: interactive rebase

#only leave C3
git rebase -i HEAD~3

git checkout main

#move main to bugFix
git rebase bugFix

Screen Shot 2021-01-15 at 17.28.55

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 modification

  • Then 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)

Screen Shot 2021-01-15 at 17.55.33

Solution: use git rebase -i

1
2
3
4
5
6
7
8
9
10
11
# revert C3 and C2
git rebase -i

# make modification
git commit --amend

# revert C3 and C2 again
git rebase -i

# rebase to caption from main
git rebase caption mian

Screen Shot 2021-01-15 at 18.18.15

Solution: use git cherry-pick

1
2
3
4
5
6
7
8
9
git chekout main

# get C2 in another branch to modify
git cherry-pick C2

git commit --amend

# attach C3 to new C2
git cherry-pick C3

Screen Shot 2021-01-15 at 18.16.41

tag

git tag v1 C1

Screen Shot 2021-01-15 at 18.26.00

git fetch

Screen Shot 2021-01-15 at 20.21.33

git fetch

Screen Shot 2021-01-15 at 20.21.45

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.

Screen Shot 2021-01-15 at 20.37.13

git pull

Screen Shot 2021-01-15 at 20.36.40

conflict of remote and local branch

Screen Shot 2021-01-15 at 21.06.45

Solution: rebase

1
2
3
4
# pull remote main and rebase local main with o/main
git pull --rebase

git push

Screen Shot 2021-01-15 at 21.09.33

Solution: merge

1
2
git pull
git push

Screen Shot 2021-01-15 at 21.13.05

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

Screen Shot 2021-01-16 at 20.07.43

This level is pretty hefty – here is the general outline to solve:

  • There are three feature branches – side1 side2 and side3
  • 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

Screen Shot 2021-01-16 at 12.03.34

Solution: rebase

1
2
3
4
5
6
7
8
9
10
11
12
# pull C8 from remote repo
git checkout main
git pull

# merge feature branch to main
git rebase main side1
git rebase side1 side2
git rebase side2 side3

# move main to side3 and push
git rebase side3 main
git push

Solution: merge

1
2
3
4
5
6
7
8
9
10
# pull C8 from remote repo
git checkout main
git pull

# merge feature branch to main
git merge side1
git merge side2
git merge side3

git push

Screen Shot 2021-01-16 at 20.58.53

Some tips in Git

This is my notes when learning git mainly from this resource:

https://www.liaoxuefeng.com/wiki/896043488029600

版本回退

git reflog 记录每一条命令
1
2
3
4
5
$ git reflog
e475afc HEAD@{1}: reset: moving to HEAD^
1094adb (HEAD -> master) HEAD@{2}: commit: append GPL
e475afc HEAD@{3}: commit: add distributed
eaadf4e HEAD@{4}: commit (initial): wrote a readme file
git reset 可以回到某个commit

只是要知道commit id ,可以通过git reflog得到

1
2
3
$ git reset --hard 1094a 

HEAD is now at 83b0afe append GPL

暂存区

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
2
3
4
5
6
7
$ git rm test.txt
rm 'test.txt'

$ git commit -m "remove test.txt"
[master d46f35e] remove test.txt
1 file changed, 1 deletion(-)
delete mode 100644 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
2
3
4
5
6
7
git checkout -b dev
# ...make changes
git checkout master
git merge dev

# delete dev branch
git branch -d dev

解决冲突

两个分支commit了不同的修改又要merge的话,需要手动handle confict

手动修改冲突之后再git add git commit 才算解决

git log –graph –oneline 可以看图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git log --graph --pretty=oneline --abbrev-commit
* cf810e4 (HEAD -> master) conflict fixed
|\
| * 14096d0 (feature1) AND simple
* | 5dc6824 & simple
|/
* b17d20e branch test
* d46f35e (origin/master) remove test.txt
* b84166e add test.txt
* 519219b git tracks changes
* e43a48b understand how stage works
* 1094adb append GPL
* e475afc add distributed
* eaadf4e wrote a readme file

分支管理

git merge --no-ff -m "merge with no-ff" dev 禁用fast-forward模式的合并
1
2
3
4
5
6
7
8
9
10
11
12
git branch -b dev
//do some change and commit
git checkout master
git merge --no-ff -m "..." dev

$ git log --graph --pretty=oneline --abbrev-commit
* e1e9c68 (HEAD -> master) merge with no-ff
|\
| * f52c633 (dev) add merge
|/
* cf810e4 conflict fixed
...

开发中的分支策略

首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;

那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;

你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。

所以,团队合作的分支看起来就像这样:

Bug分支

git stash 储藏当前工作
1
2
3
4
5
6
git stash

// ...do other things

$ git stash list
stash@{0}: WIP on dev: f52c633 add merge

恢复用git stash apply/git stash apply stash@{0},stash内容不删除,如果要删除还需要git stash drop

恢复还可以用git stash pop,恢复并且删除stash

修复bug流程

master有bug就从master checkout 一条issue branch

1
2
3
4
5
6
7
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.
(use "git push" to publish your local commits)

$ git checkout -b issue-101
Switched to a new branch 'issue-101'

修改提交

1
2
3
4
$ git add readme.txt 
$ git commit -m "fix bug 101"
[issue-101 4c805e2] fix bug 101
1 file changed, 1 insertion(+), 1 deletion(-)

切回master再合并

1
2
3
4
5
6
7
8
9
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.
(use "git push" to publish your local commits)

$ git merge --no-ff -m "merged bug fix 101" issue-101
Merge made by the 'recursive' strategy.
readme.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

如果master上的bug 在dev branch 上也有,把4c805e2 fix bug 101这个提交所做的修改cherry-pick到dev分支

1
2
3
4
5
6
$ git branch
* dev
master
$ git cherry-pick 4c805e2
[master 1d4b803] fix bug 101
1 file changed, 1 insertion(+), 1 deletion(-)

多人协作

小伙伴git clone 一个repo之后只能看到master branch,要在dev分支开发,需要创建远程origindev分支到本地,然后add commit push

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ git checkout -b dev origin/dev

$ git add env.txt

$ git commit -m "add env"
[dev 7a5e5dd] add env
1 file changed, 1 insertion(+)
create mode 100644 env.txt

$ git push origin dev
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 308 bytes | 308.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To github.com:michaelliao/learngit.git
f52c633..7a5e5dd dev -> dev

如果我也对同一个文件进行修改并push

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ cat env.txt
env

$ git add env.txt

$ git commit -m "add new env"
[dev 7bd91f1] add new env
1 file changed, 1 insertion(+)
create mode 100644 env.txt

$ git push origin dev
To github.com:michaelliao/learngit.git
! [rejected] dev -> dev (non-fast-forward)
error: failed to push some refs to 'git@github.com:michaelliao/learngit.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

会因为远程dev branch比我更新而被拒绝,需要先git pull合并解决冲突

1
2
3
4
5
6
7
8
9
10
$ git pull
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details.

git pull <remote> <branch>

If you wish to set tracking information for this branch you can do so with:

git branch --set-upstream-to=origin/<branch> dev

git pull也失败了,原因是没有指定本地dev分支与远程origin/dev分支的链接,根据提示,设置devorigin/dev的链接:

1
2
$ git branch --set-upstream-to=origin/dev dev
Branch 'dev' set up to track remote branch 'dev' from 'origin'.

再pull:

1
2
3
4
$ git pull
Auto-merging env.txt
CONFLICT (add/add): Merge conflict in env.txt
Automatic merge failed; fix conflicts and then commit the result.
多人合作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
2
3
touch .git-credentials
vim .git-credentials
https://{username}:{password}@github.com

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)
  1. You have to add your private key to it:

    1
    ssh-add

    This will ask you your passphrase just once, and then you should be allowed to push, provided that you uploaded the public key to Github.

  2. To save key permanently on macOS:

    1
    ssh-add -K  

    This 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:

  1. Start by switching to the local branch which you want to rename:

    1
    git checkout <old_name>
  2. 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.

  3. Push the <new_name> local branch and reset the upstream branch:

    1
    git push origin -u <new_name>
  4. Delete the <old_name> remote branch:

    1
    git push origin --delete <old_name>

Remove a branch

1
2
3
4
5
6
7
8
// delete branch locally
git branch -d localBranchName

//use -D if the branch is not fully merged
git branch -D localBranchName

// delete branch remotely
git push origin --delete remoteBranchName

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.