Cookies之Git命令

配置

gitlib秘钥配置

需要自己生成一个秘钥

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

然后将公钥(默认在~/.ssh/id_rsa.pub)放到网站的个人配置页面,里面有SSH keys添加子页:

基本配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
git config --global user.name "你的名字"
git config --global user.email "你的邮箱"
# windows下载代码编码格式,不会进行转换,提交不转换(默认安装会转换成windows)
git config --global core.autocrlf false
# 拒绝提交包含混合换行符的文件
git config --global core.safecrlf true
# 文件名大小写敏感,有人又不需要大小写敏感,因为windows是大小写不敏感的
git config --global core.ignorecase true
# 避免git gui中的中文乱码
git config --global gui.encoding utf-8
# 避免git status显示的中文文件名乱码
git config --global core.quotepath off
# 彩色显示
git config --global color.ui true
# windows支持长路径,要不然代码路径太长会报错
git config --global core.longpaths true
# windows默认下载git代码,没有显示链接link属性问题
git config --global core.symlinks true
# 为查看历史配置一个别名
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"

别名效果示例:

1
2
3
4
$ git lg
* 1d14eae - (HEAD, origin/master, master) specification v1 (23 minutes ago) <你的名字>
* 9f7c372 - add README (2 hours ago) <你的名字>
*

回车换行的修改

git add 提示会对回车换行做修改

1
2
3
$ git add .gitattributes
warning: LF will be replaced by CRLF in .gitattributes.
The file will have its original line endings in your working directory.

回车换行有两个配置,一个是AutoCRLF:

  1. 提交时转换为LF,检出时转换为CRLF

    1
    git config --global core.autocrlf true
  2. 提交时转换为LF,检出时不转换

    1
    git config --global core.autocrlf input
  3. 提交检出均不转换

    1
    git config --global core.autocrlf false

二是SafeCRLF:

  1. 拒绝提交包含混合换行符的文件
    1
    git config --global core.safecrlf true
  1. 允许提交包含混合换行符的文件

    1
    git config --global core.safecrlf false
  2. 提交包含混合换行符的文件时给出警告

    1
    git config --global core.safecrlf warn

入门命令

创建仓库

1
2
3
$ git init
$ ls -a
./ ../ .git/

提交一个文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ echo i have a dream > readme.txt
$ git add readme.txt #添加
$ git status #查看状态
# On branch master
#
# Initial commit
#
# Changes to be committed:
# (use "git rm --cached <file>..." to unstage)
#
# new file: readme.txt
#
$ git commit -m "first blood" #提交文件
[master (root-commit) 9d88046] first blood
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 readme.txt
$ git status #再次查看状态
# On branch master
nothing to commit (working directory clean)

修改文件后提交

1
2
3
4
5
6
7
8
9
10
$ echo "second line" >> readme.txt
$ git status
# On branch master
# Changed but not updated:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: readme.txt
#
no changes added to commit (use "git add" and/or "git commit -a")

查看有什么不一样:

1
2
3
4
5
6
7
8
$ git diff readme.txt
diff --git a/readme.txt b/readme.txt
index a23bf50..896de85 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1 +1,2 @@
i have a dream
+second line

再次提交改动:

1
2
3
4
5
6
7
$ git commit -m "second blood" -a
[master 615b81b] second blood
1 files changed, 1 insertions(+), 0 deletions(-)
$ git status
# On branch master
nothing to commit (working directory clean)

查看修改记录

常规查看:

1
2
3
4
5
6
7
8
9
10
11
12
$ git log
commit 615b81b6814eb5441cb304db5c4f11052ce7c654
Author: 你的名字 <你的邮箱>
Date: Tue Mar 20 00:59:39 2018 +0800
second blood
commit 9d8804680b7b2d9dfe95db84012bd21546271c68
Author: 你的名字 <你的邮箱>
Date: Tue Mar 20 00:56:10 2018 +0800
first blood

单行模式查看:

1
2
3
# git log --pretty=oneline
615b81b6814eb5441cb304db5c4f11052ce7c654 second blood
9d8804680b7b2d9dfe95db84012bd21546271c68 first blood

分支

提交到一个新分支

当前分支情况,只有master:

1
2
$ git branch
* master

想要提交commit到newBranch1分支(如果这个分支之前不存在,会自动创建),可以:

1
2
3
$ git push origin master:newBranch1
...
* [new branch] master -> newBranch1

删除分支

1
2
3
4
5
6
7
8
9
$ git branch
newBranch1
* master
$ git branch -d newBranch1
Deleted branch newBranch1 (was 1d14eae).
$ git branch
* master

从分支克隆

加入-b参数,可以克隆某个分支的数据:

1
git clone -b newBranch1 git@你的服务器:路径/版本.git

在这样一个只有分支newBranch1的本地仓库中,你的所有操作都默认在此分支:

1
2
3
$ git status
On branch newBranch1
Your branch is up to date with 'origin/newBranch1'.

1
2
3
$ git commit -m "优化性能"
[newBranch1 ec0177719] 优化性能
7 files changed, 403 insertions(+), 37 deletions(-)
1
2
3
4
$ git push
...
To git@你的服务器:路径/版本.git
b71ecced5..ec0177719 newBranch1 -> newBranch1

切换到分支再提交

切换分支到newBranch1(如果这个分支之前不存在,会自动创建):

1
$ git checkout -b newBranch1

默认是在master上创建分支,可以这里指定从其他地方创建,例如 orign/newBranch1 。

这个checkout -b操作,也可以展开为创建/切换两个步骤:

1
2
3
4
5
6
7
8
9
10
11
12
$ git branch newBranch1 # 创建分支
$ git branch # 查看当前默认分支情况
* master
newBranch1
$ git checkout newBranch1 # 切换分支
已经位于 'newBranch1'
$ git branch
master
* newBranch1

当然,切换分支前,拉取master的最新改动是一个好习惯,上面的步骤可以改进为:

1
2
3
git checkout master #切换到master分支
git pull origin master #更新master代码
git checkout -b newBranch1 master #从master新建,并切换到新分支

对于已存在分区,多人更改的时候,你可以拉取分支newBranch1下最新改动:

1
git pull origin newBranch1

现在你可以正常add, commit了。
最后将commit推送到分支newBranch1

1
git push origin newBranch1

可以随时切换会到其他分支,例如,切换回master分支:

1
2
$ git checkout master
Switched to branch 'master'

回滚

放弃本地更改,强制更新

先获取远程内容,这时候没有合并:

1
git fetch --all

再把HEAD指向刚刚获取的最新内容:

1
git reset --hard origin/master

回到历史某版本

1
2
3
4
5
6
7
8
9
10
11
12
$ git reset --hard 9d88
HEAD is now at 9d88046 first blood
$ cat readme.txt
i have a dream
$ git log
commit 9d8804680b7b2d9dfe95db84012bd21546271c68
Author: 你的名字 <你的邮箱>
Date: Tue Mar 20 00:56:10 2018 +0800
first blood

撤销回滚到历史版本,回到最新版本

需要记得之前最新版本的commit ID:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ git reset --hard 615b
HEAD is now at 615b81b second blood
$ cat readme.txt
i have a dream
second line
$ git log
commit 615b81b6814eb5441cb304db5c4f11052ce7c654
Author: 你的名字 <你的邮箱>
Date: Tue Mar 20 00:59:39 2018 +0800
second blood
commit 9d8804680b7b2d9dfe95db84012bd21546271c68
Author: 你的名字 <你的邮箱>
Date: Tue Mar 20 00:56:10 2018 +0800
first blood

撤销工作区的修改

1
2
3
4
5
6
7
8
9
10
11
12
13
$ echo third >> readme.txt
$ cat readme.txt
i have a dream
second line
third
$ git checkout -- readme.txt
或者
$ git reset --hard HEAD
$ cat readme.txt
i have a dream
second line

撤销暂存区修改

1
2
3
4
5
6
7
8
9
$ echo third >> readme.txt
$ git add readme.txt
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: readme.txt
#

上面已经放入暂存区了,下面撤销:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ git reset HEAD readme.txt #先撤销暂存区修改
Unstaged changes after reset:
M readme.txt
$ git status
# On branch master
# Changed but not updated:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: readme.txt
#
no changes added to commit (use "git add" and/or "git commit -a")
$ git checkout -- readme.txt #再撤销工作区修改
$ git status
# On branch master
nothing to commit (working directory clean)

撤销Commit

比如我现在commit了,但是并没有push:

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
$ git rm readme.txt
rm 'readme.txt'
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# deleted: readme.txt
$ git commit -m "delete all"
[master b88b45e] delete all
1 files changed, 0 insertions(+), 2 deletions(-)
delete mode 100644 readme.txt
$ git log
commit b88b45e4cd4fa3750199d43b1f156f1175384e7f
Author: 你的名字 <你的邮箱>
Date: Tue Mar 20 01:19:19 2018 +0800
delete all
commit 615b81b6814eb5441cb304db5c4f11052ce7c654
Author: 你的名字 <你的邮箱>
Date: Tue Mar 20 00:59:39 2018 +0800
second blood
commit 9d8804680b7b2d9dfe95db84012bd21546271c68
Author: 你的名字 <你的邮箱>
Date: Tue Mar 20 00:56:10 2018 +0800
first blood

找要撤销步骤的commit ID,上述为 b88b45e4cd4fa3750199d43b1f156f1175384e7f
一种撤销是,只撤销commit命令,不对修改的代码进行撤销:

1
git reset b88b45e4cd4fa3750199d43b1f156f1175384e7f

另一种撤销是,既撤销commit,还要将代码撤销(代码恢复到commit前的对应版本):

1
git reset --hard b88b45e4cd4fa3750199d43b1f156f1175384e7f

对于已经commit的,需要git reset,如果只是放入了暂存区,就不需要git reset,后面还可以加入:

1
git checkout 改动文件

这样确保“改动文件”在工作区的内容为最新。

删除文件

工作区、暂存区、分支上同时删除

1
git rm 路径

暂存区、分支上删除,但工作区保留(其实就是不希望某文件被版本控制,和.gitignore一个作用,对于已经在分支上的.gitignore无效)

1
git rm --cached 路径

如果递归删除结合reset,可以清除本地的所有改动:

1
2
git rm --cached -r .
git reset --hard

合并

合并newBranch1到当前分支

1
2
3
4
5
6
$ git merge newBranch1
Updating 9f7c372..1d14eae
Fast-forward
...readme.xlsx" | Bin 0 -> 16048 bytes
6 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 "readme.xlsx"

使用mergetool处理冲突

1
2
3
4
5
6
7
8
9
10
11
$ git merge
Auto-merging .gitattributes
CONFLICT (content): Merge conflict in .gitattributes
Automatic merge failed; fix conflicts and then commit the result.
$ git status
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: .gitattributes

用mergetool来人工处理冲突:

1
2
3
4
5
6
7
8
$ git mergetool --tool-help
'git mergetool --tool=<tool>' may be set to one of the following:
tortoisemerge
vimdiff
vimdiff2
vimdiff3
$ git mergetool --tool=tortoisemerge

或者

1
2
3
4
5
6
7
8
9
10
11
$ git mergetool
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc codecompare emerge vimdiff
refs/remotes/origin/master is not a valid attribute name: .gitattributes:36
Merging:
.gitattributes
Normal merge conflict for '.gitattributes':
{local}: modified file
{remote}: modified file
Hit return to start merge resolution tool (tortoisemerge):

手动处理二进制冲突

两种选择,要么保留欲合并分支的修改:

1
git checkout --theirs filename

要么保留自己的修改:

1
git checkout --ours filename

然后提交更改:

1
2
git add filename
git commit

放弃本地修改

单文件放弃:

1
git checkout [oldversion可选] -- 文件地址

整体放弃

1
2
git reset --hard
git pull

完全以自己的内容为准(危险)

比如,你pull的时候,发现有冲突:

1
2
3
4
5
$ git pull
warning: Cannot merge binary files: /lib64/libutils.so (HEAD vs. 9ac05b78ec9555ef16ab50efe1f31e69617d2be8)
Auto-merging /lib64/libutils.so
CONFLICT (content): Merge conflict in /lib64/libutils.so
Automatic merge failed; fix conflicts and then commit the result.

你如果只想保留本地修改,忽略远端修改,可以

1
2
$ git merge -s ours origin/master
Merge made by the 'ours' strategy.

注意
这动作非常危险,如果你push了你的提交,会导致别人的修改被丢弃。

##保留本地修改
$ git fetch origin

危险,会导致你没有PULL的别人提交的部分在库上也被丢弃。
$ git merge -s ours origin/master
Merge made by the ‘ours’ strategy.

如果你不小心提交了,导致别人的提交丢失,可以参考rebase示例

放弃合并

1
2
$ git merge --abort
$ git reset --merge

跨版本合并

例如合并3.0.9代码到3.0.10。

  1. 工作目录目前在3.0.10,把3.0.10下的所有文件都commit,并push。
  2. 添加3.0.9为远程分支并取回内容

    1
    2
    git remote add 3.0.9 git@你的服务器:路径/3.0.9.git
    git fetch 3.0.9
  3. checkout 3.0.9的master分支到本地,给分支取名 3.0.9-master

    1
    git checkout -b 3.0.9-master 3.0.9/master
  4. 切换到3.0.10的master分支

    1
    git checkout master
  5. 将 3.0.9-master 合入到3.0.10的当前工作目录

    1
    git merge 3.0.9-master --allow-unrelated-histories
  6. 期间会有合并失败的,需要先解决冲突。

  7. 提交代码

    1
    2
    $ git commit -m "merge sip3.0.9"
    [master cfe833e6f] merge sip3.0.9
    1
    $ git push -u origin master

从其他版本的某个分支挑选代码合入当前版本

  1. 在预合入的分支下,添加远程分支,将其他版本的某个分支加入进来。

    1
    (master)$ git remote add -m 其他版本的分支名字 本地名字 git@你的服务器:路径/其他版本.git
  2. 更新代码。

    1
    (master)$ git fetch 本地名字
  3. 把远程分支中要合并的commit Id挑选过来

    1
    (master)$ git cherry-pick e93ca048978f480c0258df2f2d7f0af67cb4f23e

    或者用名称也可以:

    1
    2
    3
    (master)$ git cherry-pick 本地名字/其他版本的分支名字
    ...
    9 files changed(改动了9个文件), 125 insertions(+)(有125行新增代码), 7 deletions(-)(有7行删除代码)
  4. 提交代码,这里我提交到一个新分支

    1
    2
    3
    4
    (master)$ git checkout -b newBranch1
    Switched to a new branch 'newBranch1'
    (newBranch1)$ git push origin newBranch1

rebase示例

merge和rebase都是用于合并代码,差异在于历史记录看起来不一样。
我之前使用 git merge -s ours origin/master 把自己的内容强制merge并push到了master分支,导致我修改期间别人的提交丢失,现在,我准备将别人丢失的部分,重新加入到master分支里面。我这里使用rebase操作,把历史也处理一下。

  1. 先把我最新在master上的提叫checkout下来,名称叫mylast,这个版本丢失了其他人的提交的。

    1
    2
    (master)$ git checkout 6ac5d94fdae15a17108cbdc1700656f1d8b3ad70 -b mylast
    Switched to a new branch 'mylast'
  2. 把别人的最后一次正确提交checkout下来,取名叫skipPoint

    1
    2
    3
    4
    5
    (mylast)$ git checkout 9ac05b78ec9555ef16ab50efe1f31e69617d2be8 -b skipPoint
    Switched to a new branch 'skipPoint'
    (skipPoint)$ git checkout mylast
    Switched to branch 'mylast'
  3. rebase操作,把mylast上的提交取消掉,并将其保存为patch,然后mylast会更新为skipPoint,然后再把mylast原本的path应用到更新后的mylast上。

    1
    2
    3
    4
    5
    6
    7
    (mylast)$ git rebase skipPoint
    First, rewinding head to replay your work on top of it
    ...
    Resolve all conflicts manually, mark them as resolved with
    "git add/rm <conflicted_files>", then run "git rebase --continue".
    You can instead skip this commit: run "git rebase --skip".
    To abort and get back to the state before "git rebase", run "git rebase --abort".
  4. rebase过程中会有冲突,我们需要处理冲突。
    例如:

    1
    2
    3
    4
    5
    (mylast|REBASE 1/1)$ git rm /lib64/libutils.so
    rm '/lib64/libutils.so'
    (mylast|REBASE 1/1)$ git add /lib64/libutils.so
    add '/lib64/libutils.so'
  5. 所有冲突处理完,可以继续rebase。

    1
    (mylast|REBASE 1/1)$ git rebase --continue
  6. 对于一些遗漏的commit,佟麟阁cherry-pick合入。

    1
    (mylast|REBASE 1/1)$ git cherry-pick 92578fc27d978ca092f8773e2a866f79c2e554c3
  7. 提交

    1
    (mylast|CHERRY-PICKING)$ git commit -m "confit"
  8. 替换master为mylast分支
    删除原本的master分支

    1
    2
    (mylast)$ git branch -D master
    Deleted branch master (was 1dea1c102).

    或者,把原本的master重命名:

    1
    (mylast)$ git branch -m master
  9. 推送覆盖到master
    gitlib禁止了覆盖master,需要在 “设置”/“版本库”/“保护分支”下临时去掉保护。

    1
    2
    3
    4
    5
    (master|CHERRY-PICKING)$ git push -f --set-upstream origin master
    Counting objects: 202, done.
    To git@你的服务器:路径/版本.git
    + 1dea1c102...2c7ecba76 master -> master (forced update)
    Branch 'master' set up to track remote branch 'master' from 'origin'.