HBuilderX技巧:Git插件,手把手教你扩展git命令

官方的git插件,只是列出了常用的git命令,并不涵盖所有。

遇到缺失的怎么办?

小编教您如何扩展git命令

git插件配置入口在哪里?

如下图片所示,点击最后一个【插件配置】,即打开git插件配置文件package.json

未分类

解析GIT命令菜单

{
        "id":"GIT_TAG",                          // Git命令ID
        "name":"git tag",                         // 外部命令显示的名称
        "command":["git", "tag"],             // 命令,参数使用逗号分割即可
        "workingDir":"${projectDir}",       // 工作目录,一般两个参数 ${projectDir}: 项目目录;${file}:文件 workingDir也可以省略
        "key":"",            // 快捷键
        "showInParentMenu":false,       // 是否显示在上一级菜单中,默认false
        "onDidSaveExecution": false,    // 是否保存时执行,默认false
        "type":"terminal"                        // 默认terminal
      }

如上解说,只需要配置一个id、name、command,就可以组成一个新的命令

特别说明:

比如切换分支、创建tag、创建分支,都需要输入啊,别担心。

在命令中加上${userInput},就可以调起一个输入框。如下

"command":["git", "tag", "${userInput:请输入tag名称}"]

示例: git tag示例

比如git tag是一个比较重要的操作,官方没提供,那我们自己手动加上吧

例子1: 打tag标签

{
        "id":"GIT_TAG_CREATE",
        "name":"git tag 打标签",
        "command":["git", "tag", "${userInput:请输入tag名称}"],
        "workingDir":"${projectDir}",
        "key":"",
        "showInParentMenu":false,
        "onDidSaveExecution": false,
        "type":"terminal"
      }

例子2:git show查看标签信息

{
        "id":"GIT_SHOW",
        "name":"git show 查看标签信息",
        "command":["git", "show","${userInput:请输入要查看的tag名称}"],
        "workingDir":"${projectDir}",
        "key":"",
        "showInParentMenu":false,
        "onDidSaveExecution": false,
        "type":"terminal"
      }

例子3:git tag 查看标签列表

{
        "id":"GIT_TAG_LIST",
        "name":"git tag 查看标签列表",
        "command":["git", "tag"],
        "workingDir":"${projectDir}",
        "key":"",
        "showInParentMenu":false,
        "onDidSaveExecution": false,
        "type":"terminal"
      },

例子4: git reset 回退到指定版本

{
        "id":"GIT_RESET_COMMIT",
        "name":"git reset 回退到指定版本",
        "command":["git", "reset", "--hard", "${userInput:请输入commit id}"],
        "workingDir":"${projectDir}",
        "key":"",
        "showInParentMenu":false,
        "onDidSaveExecution": false,
        "type":"terminal"
      }

注意事项

编辑插件配置后,并不能马上生效,需要重启软件。
请大家将自己修改后的配置文件,另存一份,以防更新后丢失。

git配置文件

小编将自己的git配置文件,放到github上了,请大家关注

https://github.com/yi-heng/MyConfig/blob/master/HBuilderX/git%E6%8F%92%E4%BB%B6%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6.json

git ssh https 踩坑记 —- 域账号密码更新

前几天突然通知要更新公司的域账号密码,然后git pull就一直报

fatal: Authentication failed for ‘https://git ...

很奇怪的是,有一个项目git pull push都是好的。

上网查了下用以下命令可以看到

git remote get-url origin

好的项目是用了ssh,不好的项目用的是https,https把旧的密码存到电脑里了,所以一直登陆不上

解决方法1–修改密码:

// 清除账户密码缓存
git config --system --unset credential.helper
// 设置账户密码永久保存
git config --global credential.helper store
// 拉代码,重新输入账号密码登陆一次,以后再也不用再输账号密码
git pull

解决方法2–改用ssh方式认证:

// 去除https源
git remote remove origin
// 添加ssh远端源
git remote add origin git@git.xxx.cn:xxx/xxx.git

ps: 值得注意的是更换origin后,分支要和远端重新关联一下

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

git常用命令速查表

先进入项目文件夹)通过命令 git init 把这个目录变成git可以管理的仓库

git init

把文件添加到版本库中,使用命令 git add .添加到暂存区里面去,不要忘记后面的小数点“.”,意为添加文件夹下的所有文件

git add .

用命令 git commit告诉Git,把文件提交到仓库。引号内为提交说明

git commit -m 'first commit'

关联到远程库

git remote add origin 你的远程库地址

如:

git remote add origin https://github.com/cade8800/ionic-demo.git

获取远程库与本地同步合并(如果远程库不为空必须做这一步,否则后面的提交会失败)

git pull --rebase origin master

把本地库的内容推送到远程,使用 git push命令,实际上是把当前分支master推送到远程。执行此命令后会要求输入用户名、密码,验证通过后即开始上传。

git push -u origin master

状态查询命令

git status

未分类

git 几个好用的命令

git checkout . 撤销工作区修改而未提交的内容(注意:新增的文件不能修改,需要手动删除)

git fetch origin master 将远程仓库的master分支下载到本地当前branch中

git log -p master ..origin/master 比较本地的master分支和origin/master分支的差别

git merge origin/master 合并代码

git push origin feature:master 将本地feature代码推到远端master

注意:

git pull == git fetch + git merge 会出现远程代码直接覆盖本地代码而不报冲突的情况, 推荐上面先git fetch 再对比差异

一巴掌拍平Git中的各种概念

0x000 导读

讲git的文章很多,但是大部分都是一个套路,讲概念,讲命令,太多的概念和命令总是让人有一种稀里糊涂的感觉,讲的很对,但似乎没能讲透,没有醍醐灌顶的感觉,大概是我的悟性差吧。所以这几天一直在做各种git的实验,并且阅读了一些博客、文档、资料,综合下来整理出了这么一篇文章。注意:

  • 本篇文章旨在说明我对git的理解,只是一家之言,聊以分享。
  • 本片文章不是一篇命令教程,或者概念说明,需要一定的git使用经验和踩坑经验。
  • 为了阅读方便,commitID只保留4位

0x001 总结[提前]

这是一篇比较乱七八糟的文章,不从传统出发,尝试用自己的思想去理解git这一神奇的工具。以前我觉得git是命运石之门,我们在不同的时间线(分支)上跳跃,所有的事件都必须且只能发生在时间线上。但是现在我觉得git是无限的可能性的集合,一个事件可以引申出无限的可能性。而我们要做的是用工具(branch、tag、reset、rebase、merge….)来组织这些可能性,从而构成一个有序的、向前发展的历史,引导整个历史的发展,构建可以走向未来的工程。

0x002 存档和读档

  • 存档

其实吧,版本就是存档,就是游戏中的存档,我们不断的推进项目,完成一个又一个任务,然后中途不断的存档,就形成了版本迭代。而版本多了,自然就需要管理了,自然就有了版本管理系统。

在游戏中,存档可以手动存档,也可以到指定点存档,也可以自动定场景存档。在游戏中,存档之后形成的东西叫做档案,而在git中,叫做commit。我们可以使用git add+git commit完成一个档案的创建,或者说版本的创建。

一个commit拥有许多的属性,比如ID、Message、Date、Author:等等,这些信息都有助于我们了解这个版本,就像游戏中的存档会以关卡名/图片之类的信息来提示你这个存档所代表的进度,比如使用git log可以获取以下信息:

commit 4963 (HEAD -> master)
Author: **********
Date:   Thu Jan 10 15:22:12 2019 +0800

    版本H

commit 1a42
Author: **********
Date:   Thu Jan 10 15:25:01 2019 +0800

    版本G

commit 931b
Author: **********
Date:   Thu Jan 10 15:24:50 2019 +0800

    版本F
....
  • 读档

既然有存档,那就有读档。游戏中直接选择一个档案就行了,那git中呢?如果有可视化操作工具,那我们直接点击也是可以的,但现在不使用工具,而使用命令行,该如何呢。读取一个存档说白了在git中就是读取一个commit而已,所以我们可以使用git checkout和git reset两个命令来做到,那如何指定版本呢?前面提到的commit属性中的ID可以帮我们实现这个目标。

环境说明:我在仓库中git commit了8个,每个commit都添加了一个从a-h的文件,并在commit信息中添加版本标记

未分类

使用git checkout切到版本A,可以发现,此时只有文件a

$ git checkout 401e
Note: checking out '401e'.

...

HEAD is now at 401e1b6 版本A

$ ls
README.md       a.txt

使用git reset切换到版本G,可以发现,此时有了a-g几个文件了

$ git reset 1a42
Unstaged changes after reset:
D       b.txt
D       c.txt
D       d.txt
D       e.txt
D       f.txt
D       g.txt

$ git stash
Saved working directory and index state WIP on (no branch): 1a4268d 版本G

l$ ls
README.md       a.txt           b.txt           c.txt           d.txt           e.txt           f.txt           g.txt
  • 总结:
    我们通过commit的ID属性配合其他命令来达到了任意读档的目的,可以在各种版本中随意穿梭,快乐的很啊。而读档的姿势其实还有很多,但不外乎是对commit操作方式的不同,在git中,我觉得commit 才是构成整个版本管理的基本栗子。每一个commit都是独立的个体,虽然和其他commit存在着关联,但是依旧是独立的,而我们在commit构成节点序列中来回移动和操作,就可以达到所有对版本管理的目的。

0x003 别名系统

在上一个章节中,我们已经可以依靠一些命令和commit ID做到在版本中自由的穿梭,但是却带来一个问题,那就是commit ID的记忆难度。commit ID是hash值,尽管git支持只提供前几位就能匹配到hash,并且也提供了commit message来说明commit,但是依旧存在commit的辨识和记忆问题。而这个问题,可以通过别名系统来解决。

所谓的别名系统,其实是我自己归纳的概念,指的其实就是HEAD、branch、tag这三个东西。在我看来,这三个东西都是一样的东西,都是别名,也就是标记一个commit的东西而已,只是在行为表现上有一些区别。

1. HEAD

一个仓库只有一个HEAD,指向你当前所在的commit。如果你创建了一个commit,HEAD将会指向这个新的commit。也可以通过命令,强制HEAD指向某个commit,比如reset、checkout。也就是不论你在哪个commit之上,那么HEAD就在哪儿,或者说,其实你能在哪个commit,是通过修改HEAD指向的commit实现的。

通过修改HEAD在各个版本之间旋转跳跃

未分类

2. branch

一开始我觉得这个东西才是git的核心,因为创建项目的时候,我们就处于master分支之上,并且我们在工作中,往往也是基于分支工作的。但是后来发现,分支在本质上毫无意义,并不需要真的基于branch去工作,基于commit就行了。而branch只是提供了一个方式来管理这些commit。branch和HEAD相同点是随着新的commit的创建,branch指向的commit会不断更新,当然前提是你需要在这个branch所在的commit上创建新的commit。而branch和HEAD的不同点在于HEAD只能有一个,branch可以有多个。

实验一:用branch来实现切换版本

目前的库情况

$ git log --pretty=oneline
1a42 (HEAD) 版本G
931b 版本F
071d 版本E
0caa 版本D
7855 版本C
1295 版本B
401e 版本A

为版本A-G分别创建一个分支

$ git checkout 1a42 -b G
Switched to a new branch 'G'
$ git checkout 931b -b F
Switched to a new branch 'F'
$ git checkout 071d -b E
Switched to a new branch 'E'
$ git checkout 0caa -b D
Switched to a new branch 'D'
$ git checkout 7855 -b C
Switched to a new branch 'C'
$ git checkout 1295 -b B
Switched to a new branch 'B'
$ git checkout 401e -b A
Switched to a new branch 'A'

$ git log --pretty=oneline
1a42 (HEAD -> G) 版本G
931b (F) 版本F
071d (E) 版本E
0caa (D) 版本D
7855 (C) 版本C
1295 (B) 版本B
401e (A) 版本A

接下来就可以换一种方式在版本之间跳跃了,并且不需要记住或者查询冗长的
commit ID

$ git checkout A
Switched to branch 'A'
$ git checkout B
Switched to branch 'B'
$ git checkout C
Switched to branch 'C'
$ git checkout E
Switched to branch 'E'
$ git checkout F
Switched to branch 'F'
$ git checkout G
Switched to branch 'G'

实验二:分支跟随新的commit

当前库的情况,注意:这里的HEAD -> G表示HEAD指向了branch G,而branch G指向了版本G

$ git log --pretty=oneline
1a42 (HEAD -> G) 版本G
931b (F) 版本F
071d (E) 版本E
0caa (D) 版本D
7855 (C) 版本C
1295 (B) 版本B
401e (A) 版本A

添加一个文件,创建一个commit

$ echo 'h'> h.txt
$ git add h.txt
$ git commit -m '版本H'
[G d346d27] 版本H
1 file changed, 1 insertion(+)
create mode 100644 h.txt

此时查看log,可以看到HEAD和G都指向了版本H,就是所谓的branch跟着commit动,但是它真的是跟着commit动吗?

$ git log --pretty=oneline
d346 (HEAD -> G) 版本H
1a42 版本G
931b (F) 版本F
071d (E) 版本E
0caa (D) 版本D
7855 (C) 版本C
1295 (B) 版本B
401e (A) 版本A

实验三:分支跟着啥动

将HEAD指向版本G的commit,而不是分支G,也就是使用git checkout commitID,而不是使用git checkout branchName,可以看到,此时HEAD不指向G,而是HEAD和G同时指向了版本H的commit。

$ git checkout d346 # 版本 H 的 commitID
$ git log --pretty=oneline
d346 (HEAD, G) 版本H
1a42 版本G
931b (F) 版本F
071d (E) 版本E
0caa (D) 版本D
7855 (C) 版本C
1295 (B) 版本B
401e (A) 版本A

继续创建一个commit,可以看到,这个时候分支G不再跟着commit移动了,所以,只有在HEAD指向branch的时候,branch才会向前移动,也就是只要HEAD来到branch身边,branch就会跟着HEAD跑。

$ echo 'i'> i.txt
$ git add i.txt
$ git commit -m "版本I"
[detached HEAD 2e836eb] 版本I
1 file changed, 1 insertion(+)
create mode 100644 i.txt
$ git log --pretty=oneline
2e83 (HEAD) 版本I
d346 (G) 版本H
1a42 版本G
931b (F) 版本F
071d (E) 版本E
0caa (D) 版本D
7855 (C) 版本C
1295 (B) 版本B
401e (A) 版本A

3. tag

tag是比较特殊的一个别名类型,他无法移动,或者说不推荐移动。一旦一个tag和指向某个coimmit,就不希望它移动,因为tag就是用来标记这个commit的,他是一个孤独而忠诚的守望者,而不像branch,花间游龙似的浪子。

现在库的情况

$ git log --pretty=oneline
1a42 (HEAD, G) 版本G
931b (F) 版本F
071d (E) 版本E
0caa (D) 版本D
7855 (C) 版本C
1295 (B) 版本B
401e (A) 版本A

为每个版本添加一个tag,为了区别分支名,统统加了个T

$ git tag TA A
$ git tag TB B
$ git tag TC C
$ git tag TD D
$ git tag TE E
$ git tag TF F
$ git tag TG G

$ git log --pretty=oneline
1a42 (HEAD, tag: G, G) 版本G
931b (tag: TF, F) 版本F
071d (tag: TE, E) 版本E
0caa (tag: TD, D) 版本D
7855 (tag: TC, C) 版本C
1295 (tag: TB, B) 版本B
401e (tag: TA, A) 版本A

现在又多了一种旋转跳跃的方式了

$ git checkout TA
Previous HEAD position was 1a4268d 版本G
HEAD is now at 401e1b6 版本A
$ git checkout TB
Previous HEAD position was 401e1b6 版本A
HEAD is now at 1295260 版本B
$ git checkout TC
Previous HEAD position was 1295260 版本B
HEAD is now at 7855905 版本C
$ git checkout TD
Previous HEAD position was 7855905 版本C
HEAD is now at 0caa2b7 版本D
$ git checkout TE
Previous HEAD position was 0caa2b7 版本D
HEAD is now at 071d00a 版本E
$ git checkout TF
Previous HEAD position was 071d00a 版本E
HEAD is now at 931b3c9 版本F
$ git checkout TG
Previous HEAD position was 931b3c9 版本F
HEAD is now at 1a4268d 版本G
  • 总结
    所以,不管是HEAD、tag、branch,都是一种别名,除了行为表现上的差别,没有太大的不同,特别是branch和tag,不过都只是提供了一种管理commit的方式。

0x004 分叉

在上一章节中,我们揭开了别名系统的红盖头,这一章,我们就开始探索一下分叉的神秘。

和游戏中的存档一样,有时候一个游戏有许多的选择,这些选择指向了不同的结果。而作为游戏玩家,我们希望能够走完所有的选择,以探索更多的游戏乐趣。所以我们会在做选择的时候存档,而当我们走完一个选择,就会读取这个存档,继续往另一个选择探索。这个时候,就产生了两个不同的剧情走向,这就是分叉。

在git中,其实我们可以有无数的选择,每一个commit可以创建无数的commit,就会引申出无数的可能。

我们遇到了一个抉择,所以需要创建版本,暂时称为版本X吧

$ git log --pretty=oneline
2cae (HEAD) 版本X
....

然后我们选择了走Y,并且沿着Y1一直走到Y3,这是尽头

$ git log --pretty=oneline
d2e0 (HEAD) 版本Y3
4ca8 版本Y2
fcff 版本Y1
2cae 版本X
...

接着我们返回X,并选择另一个选择Z,从Z1走到Z3

$ git checkout 2cae # 切到`版本X`
$ git log --pretty=oneline
16ff (HEAD) 版本Z3
0ca5 版本Z2
b4a7 版本Z1
2cae 版本X
...
  • 总结

可以看到,我们顺着两个选择一直往下发展,在这发展的过程中,我们完全没有用到tag和branch,也就是为了印证commit 是构成 git 世界的基本栗子这一说明。

从git log中,我们看不到了Y走向,那Y真的消失了吗?不是的,我们依旧可以通过Y的commit ID来寻回Y的记录。当然为了方便在YZ中切换,我们可以使用branch来标记一下YZ两个走向,这样就形成了YZ两个branch了,也就是分叉!

那那些没有被branch或者tag标记的commit呢?他们会消失吗?会,也不会。不会是因为不被标记的commit将变成dangling commit,我称之为游离的commit,是git中最孤独的存在,只要我们知道commitID,就会可唤回它。但是很大的可能是我们永远不会记得这么一个不被引用的commit,所以我呼吁,善待每一个commit。会是因为还是可能会被回收的,看这里,git 也有 gc。

0x003 合并

和游戏的存档不同的是,git中的版本可以合并,也就是说我可以在分支Y中做完任务Y1、Y2、Y3,然后分支Z中完成任务Z1、Z2、Z3,然后合并这两个分支,结果回到了X,但是却完成了Y1-y3、Z1-Z3,并拿到了神器Y和Z,这让boss怎么活?

实验一:使用merge合并commit

创建版本O

$ echo O >> o.txt
$ git add o.txt
$ git commit -m '版本O'
[detached HEAD 478fa6d] 版本O
1 file changed, 1 insertion(+)
create mode 100644 o.txt

基于版本O创建版本P1

$ echo P >>p1.txt
$ git add p1.txt
$ git commit -m '版本P1'
[detached HEAD a3ab178] 版本P1
1 file changed, 1 insertion(+)
create mode 100644 p1.txt
$ git log --pretty=oneline
a3ab (HEAD) 版本P1
478f 版本O

基于版本O创建版本P2

$ git checkout 478f # 版本O 的 commitID
$ echo p2 >> p2.txt
$ git add p2.txt
$ git commit -m '版本P2'
[detached HEAD cbccf52] 版本P2
1 file changed, 1 insertion(+)
create mode 100644 p2.txt
$ git log --pretty=oneline
cbcc (HEAD) 版本P2
478f 版本O

合并版本P1到版本P2

$ git merge a3ab # 版本P1 的 commitID
$ git log --pretty=oneline
656a (HEAD) Merge commit 'a3ab' into HEAD
cbcc 版本P2
a3ab 版本P1
478f 版本O

实验三:使用rebase合并

切换到版本P2,在版本P2中使用rebase

$ git checkout cbcc # 版本P2 的 commitID
....
HEAD is now at cbccf52 版本P2
$ git rebase a3ab # 版本P1 的 commitID
First, rewinding head to replay your work on top of it...
Applying: 版本P2
$ git log --pretty=oneline
3bd7 (HEAD) 版本P2
a3ab 版本P1
478f 版本O

实验四:使用cherry-pick合并

切换到版本O,新建版本P3

$ echo 'p3'>> p3.txt
$ git add p3.txt
$ git commig -m '版本P3'
git: 'commig' is not a git command. See 'git --help'.

The most similar command is
        commit
$ git commit -m '版本P3'
[detached HEAD ae09e94] 版本P3
1 file changed, 1 insertion(+)
create mode 100644 p3.txt
$ git log --pretty=oneline
ae09 (HEAD) 版本P3
478f 版本O

切换到版本P2中使用cherry-pick合并版本P3的东西

$ git checkout 3bd7 # 版本P2 的commitID
...
HEAD is now at 3bd7820 版本P2
$ git cherry-pick ae09 # 版本P3 的 commitID
[detached HEAD f9dfba2] 版本P3
Date: Sat Jan 12 11:35:27 2019 +0800
1 file changed, 1 insertion(+)
create mode 100644 p3.txt
$ git log --pretty=oneline
f9df (HEAD) 版本P3
3bd7 版本P2
a3ab 版本P1
478f 版本O
  • 注意:合并中的冲突解决
    合并的过程中可能会出现冲突,比如同时拿到神器P1、P2,但是在P1中卖掉了O之前拿到的装备S,而在P2中则为S镶上了宝石,那么合并之后要怎么处理?是卖掉S?还是保留镶宝石的S?还是镶了宝石再卖掉?深井冰啊!我不要面子的啊…
    所以这里就涉及到了合并的冲突解决,这里不再赘述,不是我要讲的内容。

0x005 变基

这个名词让人想入菲菲啊,每次项目新成员加入,总是会提醒他们注意要变基….

这里不去说merge和rebase的爱恨情仇,只说一些rebase的操作,用rebase来整理commit。

上面说到commit 是构成 git 世界的基本栗子,所以,我们需要掌握一些栗子的操作方式

  • 查看commit,可以使用git log,如果需要寻回忘记的commit,可以使用reflog来尝试看看是否能够找到
$ git log --pretty=oneline
68de (HEAD -> X) Merge branches 'Y' and 'Z' into X
16ff (Z) 版本Z3
...
$ git reflog
23e799e (HEAD) HEAD@{0}: rebase -i (pick): 版本Z
01a10d6 HEAD@{1}: rebase -i (squash): 版本Y
a15dd72 HEAD@{2}: rebase -i (squash): # This is a combination of 2 commits.
b6f2ea3 HEAD@{3}: rebase -i (start): checkout 1004
f4c4ccc HEAD@{4}: rebase -i (abort): updating HEAD
...
  • 创建

创建使用git add+git commit就行了

$ echo error > error.txt
$ git add error.txt
$ git commit -m '一个错误的版本'
[X bc90774] 一个错误的版本
1 file changed, 1 insertion(+)
create mode 100644 error.txt
$ git log --pretty=oneline
bc90 (HEAD -> X) 一个错误的版本
68de Merge branches 'Y' and 'Z' into X
...
  • 更新上一个commit,直接使用git commit –amend
$ echo error2 >> error.txt
$ git add error.txt
$ git commit --amend
// 这里将打开一个vim窗口
一个错误的版本

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Fri Jan 11 17:21:18 2019 +0800
#
# On branch X
# Changes to be committed:
#       new file:   error.txt
#
// 保存退出之后输出
[X d5c4487] 一个错误的版本
Date: Fri Jan 11 17:21:18 2019 +0800
1 file changed, 2 insertions(+)
create mode 100644 error.txt
  • 要更新历史中的commit也是可以做到的,例如需要在版本X中加入文件x1.txt,要使用交互式模式
git rebase -i 2e83 # 指向 版本X 的前一个 commit

此时将打开一个交互式窗口

pick 913b571 版本X
pick 0eca5e3 版本Y1
pick 33a9ca3 版本Y2
pick b95b3ca 版本Y3
pick 839c481 版本Z1
pick 6fb6cb3 版本Z2
pick c28d3e0 版本Z3
...

将版本X前面的pick改为e或者edit,保存,然后退出,这个时候,仓库将会回到版本X的状态,并输出

Stopped at 913b571...  版本X
You can amend the commit now, with

git commit --amend

Once you are satisfied with your changes, run

git rebase --continue

添加文件x1

$ echo x1 > x1.txt
$ git add x*
$ git commit --amend
// 打开交互式窗口可以修改 commit message
$ git rebase --comtinue
Successfully rebased and updated detached HEAD.

此时又回会到原本的版本并且多出了文件x1,就像是在版本X中就已经加入一样

  • 插入一个新的commit,上面的栗子中不使用–amend,就会在X和Y1之间插入一个新的commit
$ git rebase -i 2e83
// 交互式窗口,吧`pick`改为`e`
$ echo x2 > x2.txt
$ git add x2.txt
$ git commit -m '插入一个版本X2'
[detached HEAD 1b4821f] 插入一个版本X2
1 file changed, 1 insertion(+)
create mode 100644 x2.txt
$ git rebase --continue
Successfully rebased and updated detached HEAD.
$ git log --pretty=oneline
30a5 (HEAD) 版本Z3
4b00 版本Z2
cc1d 版本Z1
595e 版本Y3
4456 版本Y2
b6f2 版本Y1
1b48 插入一个版本X2
1004 版本X
  • 删除

删除一个分支可以使用交互式rebase,命令:git rebase -i commitID,这里的commitID必须是你要删除的commit的前一个commit。

$ git rebase -i 68de

此时将会打开一个vim窗口

pick bf2c542 版本Y1
pick 588feec 版本Y2
pick 1b2ae37 版本Y3
pick 38f7cf3 版本Z1
pick 080e442 版本Z2
pick 206a7ae 版本Z3
pick 6b01f70 一个错误的版本

# Rebase 2caeda3..6b01f70 onto 2caeda3 (7 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
...

要删除一个错误的版本,将前面的pick改为d或者drop

d 6b01f70 一个错误的版本

保存退出,输出

Successfully rebased and updated refs/heads/X.

合并多个commit,比如合并Z1-Z3,打开交互式窗口之后,将Z2、Z3的pick改为s

$ git rebase -i 100468330c7819173760938d9e6d4b02f37ba001
// 打开了交互式窗口
pick bf2c542 版本Y1
pick 588feec 版本Y2
pick 1b2ae37 版本Y3
pick 38f7cf3 版本Z1
s 080e442 版本Z2
s 206a7ae 版本Z3

保存退出以后,又打开交互式窗口,显示要合并的commit的message,这里可以修改commit。

# This is a combination of 3 commits.
# This is the 1st commit message:

版本Z1

# This is the commit message #2:

版本Z2

# This is the commit message #3:

版本Z3

这里修改为Z,保存,退出,输出,可以看到,Z1-Z3消失了,取而代之的是Z,对Y1-Y3做操作

detached HEAD f4c4ccc] 版本Z
Date: Fri Jan 11 16:27:00 2019 +0800
1 file changed, 3 insertions(+)
create mode 100644 z.txt
Successfully rebased and updated detached HEAD.
$ git log --pretty=oneline
f4c4 (HEAD) 版本Z
595e 版本Y3
4456 版本Y2
b6f2 版本Y1

$ git rebase -i 1004
[detached HEAD 01a10d6] 版本Y
Date: Fri Jan 11 16:24:37 2019 +0800
1 file changed, 3 insertions(+)
create mode 100644 y.txt
Successfully rebased and updated detached HEAD.
$ git rebase --continue

重新排序commit顺序,比如重排版本Y和版本Z,交换一下顺序就好了

$ git log --pretty=oneline
23e7 (HEAD) 版本Z
01a1 版本Y
$ git rebase -i 1b48

这时候打开交互式窗口,显示

pick a1942a3 版本Y
pick eeabc6c 版本Z

将它交换顺序,保存,退出

pick eeabc6c 版本Z
pick a1942a3 版本Y

查看结果

Successfully rebased and updated detached HEAD.
$ git log --pretty=oneline
a194 (HEAD) 版本Y
eeab 版本Z

如何快速掌握git命令

Git 是程序员工作必备的技能之一,本文将从基本概念、常见操作、快捷技巧三个方面进行介绍,总结工作中常用的 Git 命令,使你能够快速脱离 Git 可视化工具,掌握 Git。

1. 基本概念

1.1 4个区

  • 工作区
  • 暂存区
  • 本地仓库
  • 远程仓库

1.2 5种状态

  • 未修改:对应区为工作区
  • 已修改:对应区为工作区
  • 已暂存:对应区为暂存区
  • 已提交:对应区为本地仓库
  • 已推送:对应区为远程仓库

平时我们修改和未修改的代码都属于工作区,git add 即把已修改的代码暂时存储,代码进入暂存区,git commit 即把暂存的代码提交到本地仓库,产生一条提交记录,git push 即将本地仓库推送到远程仓库,代码实现远程共享。

2. 常见操作

2.1 本地初始化

主要有两种方法,一种是 clone 项目,另一种是本地创建再连接到远程项目。下面以 github 为例:

  • clone 项目
git clone git@github.com:userName/projectName.git
  • 本地创建再连接
cd projectName   // 进入项目文件夹
git init         // 初始化
git remote add origin git@github.com:userName/projectName.git  // 连接到远程 github 项目

第二种方法要首先确保远程仓库已经创建。

2.2 分支创建和提交

通常,代码都在分支上进行开发,分支开发的命令几乎是 Git 中最常用的命令,下面罗列了分支开发中的常用命令并加以说明。

git checkout -b project-dev   // 创建 project-dev 分支
git branch                    // 查看分支
git status                    // 显示工作区和暂存区的状态
git add .                     // 将工作区的所有变化提交到暂存区
git commit -m 'message'       // 将暂存区提交到本地仓库
git checkout dev              // 切换到 dev 分支
git merge project-dev         // 将 project-dev 合并到 dev 分支
git pull                      // 拉取仓库仓库代码
git push origin dev           // 提交本地仓库代码
git branch -d zproject-dev    // 删除 project-dev 分支

切换分支时,记得将代码提交到本地仓库,避免未提交的代码一同切换。

2.3 撤销更改

实际工作中,经常会出现提交错误的情况,所以撤销更改的操作要牢记于心。处于不同状态下的撤销更改是不同的:

  • 已修改,未暂存
# 方法1:撤销所有修改
git checkout .
# 方法2:撤销所有修改
git reset --hard
  • 已暂存,未提交,即 git add 错误
# 撤销指定的文件,保留更改
git reset <file>
# 撤销所有的文件,保留更改
git reset
# 撤销所有的文件,不保留更改
git reset --hard
  • 已提交,未推送,即 git commit 错误文件
# 撤销 commit 操作,保留文件变化
git reset HEAD~1
# 撤销 commit 操作,删除变化
git reset --hard HEAD~1
  • 已推送,即 git push 错误文件
# 退回到指定版本
git reset --hard <commit_id>
# 强制覆盖远程分支
git push -f

2.4 指定版本修复

线上代码出现问题,需要紧急修复,而 dev 分支上正在开发下一个版本,此时我们就要新开分支,来进行代码修复,并合并到线上分支。

git branch -a                                   // 查看所有分支
git checkout -b repair_dev origin/repair_dev    // 创建远程分支 repair_dev,并切换到 repair_dev分支
...                                             // 在分支上进行修改提交
git checkout dev                                // 切换回 dev 分支
git merge repair_dev                            // 合并分支 repair_dev
git branch -D repair_dev                        // 删除分支

3. 快捷技巧

从常用操作中可以看出,在 git 的使用过程中,部分命令的使用非常频繁,例如

git status
git add .
git commit -m msg

每次的重复输入操作很繁琐,为此 git 提供别名 alias 的设置,使用 alias 别名设置可以简化我们的操作

3.1 alias

git config --global alias.ci commit

设置之后,以后提交到本地仓库就简写为

git ci -m msg

看起来是不是简短了很多?但这种方法每次只能简化一个命令,主要是用来简化参数。

3.2 自定义指令

我们可以通过配置相应文件,来简化我们的命令,打开 git 安装目录下的 etc/bash.bashrc,在最后一行添加

source /etc/git-completion.bash

对 git-completion.bash 文件进行引用,保存之后再打开 /etc/git-completion.bash ,添加属于自己的自定义指令,例如我配的自定义指令

alias ci='git add . && git commit'
alias project='cd d:/first/second/third/project'

配置好之后重启 git bash ,此时输入 project 就可以直接进入项目路径,提交代码则为

project
ci -m msg

是不是更简短了?我们可以通过在 git-completion.bash 定义各种指令,定制出一套属于自己的指令。

3.3 结合 VS Code

除此之外,我们还可以将自定义指令和 VS Code 的快捷指令结合,例如 VS Code 可以在 git bash 通过 code 命令打开,我们就可以设置自定义指令为

alias project='cd d:/first/second/third/project' && code ./
alias projectpull='cd d:/first/second/third/project' && code ./ && git pull

此时我们输入 project 即可以进入相应项目目录,并在同时在 VS Code 打开了该项目。输入 projectpull 即在前面的基础上对代码进行拉取

除了结合VS Code,还可以和一些全局 npm 包或者 node 脚本一起配合使用。

4. 总结

本文主要从基本概念、常见操作、快捷技巧三个方面对 Git 进行讲解,掌握后基本满足工作需要。但 Git 的使用远不只有这三方面,例如使用 Git 钩子在代码提交前进行检查、项目中 Git 提交信息的规范等,以后将慢慢介绍。

Git 常用命令集

个人使用git的一些总结,一下常用命令的互相搭配使用,能非常完美的处理好日常遇到的99%的情况(不敢说100%,得留点余地)。
还有一些使用频率非常低的指令没有记录,至少普通开发者非常不常用,以后可能会补充。

话不多说
首先需要先下载git这个还是要说一下的

1. 常用命令

1.1 最常用/版本对比/其他

git status 查看当前分支状态
git reflog 查看每一次的命令,都做过什么
git log 查看此分支完整的提交记录,回车继续看,q停止
git log --oneline 查看此分支简略的提交记录,只展示提交号和提交信息
git show 查看最近一次提交的具体代码变化
git show <提交ID> 查看某次提交的具体代码变化
git diff 查看当前代码add后,会add哪些内容
git diff --staged 查看现在commit提交后,会提交哪些内容
git diff HEAD 查看当前代码add并提交后,会提交哪些内容
git log --oneline --graph 图形化展示合并历史

1.2 初始化基本操作

git init 初始化仓库,默认为master分支
git add -A 提交全部文件修改到缓存区
git add <具体某个文件路径+全名> 提交某些文件到缓存区
git commit -m "<注释>" 提交代码到本地仓库,并写提交注释
git commit –a -m "<新注释>" 更改上次提交的注释

1.3 分支操作

git branch 查看本地所有分支
git branch -r 查看远程所有分支
git branch -a 查看本地和远程所有分支
git merge <分支名> 合并分支
git merge --abort 合并分支出现冲突时,取消合并,一切回到合并前的状态
git branch <新分支名> 基于当前分支,新建一个分支
git checkout --orphan <新分支名> 新建一个空分支(会保留之前分支的所有文件)
git branch -D <分支名> 删除本地某个分支
git push <远程库名> :<分支名> 删除远程某个分支
git branch <新分支名称> <提交ID> 从提交历史恢复某个删掉的某个分支
git branch -m <原分支名> <新分支名> 分支更名
git checkout <分支名> 切换到本地某个分支
git checkout <远程库名>/<分支名> 切换到线上某个分支
git checkout -b <新分支名> 把基于当前分支新建分支,并切换为这个分支

1.4 推拉操作(push和pull)

git pull <远程仓库名> <远程分支名> 拉取远程仓库的分支与本地当前分支合并
git pull <远程仓库名> <远程分支名>:<本地分支名> 拉取远程仓库的分支与本地某个分支合并
git push <远程仓库名> <本地分支名> 推送本地某个分支到远程与其对应的分支上,远程没有则会给远程库新建分支
git push <远程仓库名> <本地分支名>:<远程分支名> 推送本地分支到远程某个分支
git push <远程仓库名> :<远程分支名> 删除远程某个分支
git push -f <同上的命令> 强制提交,覆盖线上
git fetch 获取线上最新版信息记录,不合并,用法和pull类似(一个特殊的操作,比如多人开发分支,其他人提交后,自己看分支的时候,可能还知不道线上的分支已经比自己的新了,需要这个指令,来获取一下线上的最新版本,会拉取,但不会合并,存在本地仓库中)

1.5 查看具体文件版本区别(git diff)

git diff <文件名> 对比最近提交,和近近次提交的区别,不加文件名,则为整体对比
git diff HEAD^ -- <文件名> 同上
git diff HEAD~<一个数字> -- <文件名> 上次提交和前第几次提交作对比
git diff <提交ID> <文件名> 上次提交和某次提交作对比
git diff <文件名> 工作区与暂存区比较
git diff HEAD <文件名> 工作区与最近提交比较
git diff <本地分支名> <文件名> 当前分支的文件与其他分支的文件进行比较

1.6 回滚操作

注意,当回滚代码时,resetrevert都可以使用,下面是两个指令的区别
reset:真实硬性回滚,目标版本后面的提交记录全部丢失了
revert:同样回滚,但实际这个回滚操作,算是一个提交,目标版本后面的提交记录也全部都有,而且会多一次提交,就是这次revert

--hard的功能:不加他,文件修改会保留,都会处于add之前的状态;加上他,文件修改会被删除,丢失掉

git <reset/revert> <--hard?> HEAD^ 回退到上次提交
git <reset/revert> <--hard?> <提交记录> 回退到之前的某次提交
git <reset/revert> <--hard?> <某个分支> 回退到此分支的提交状态,相当于复制分支过来
git reset <文件完整路径+完整名> 把add过的某个文件撤销到未add的状态
git reset 把add过的所有文件撤销到未add的状态
git checkout <文件完整路径+完整名> 一个还没有add的文件,撤销修改
git checkout . 还没有add的所有文件,撤销修改

1.7 远程库和分支的链接操作

git remote -v 查看对应的远程仓库地址
git clone <远程仓库地址链接> 克隆某个远程库,默认库名为origin
git clone <远程仓库地址链接> <库名> 克隆某个远程库,并自定库名
git remote remove <远程仓库名> 和远程的某个仓库解除绑定关系
git remote add <远程仓库名> <远程仓库地址链接> 和某个远程仓库建立绑定关系
git push --set-upstream <远程仓库名> <远程分支名> 当前分支和远程某个分支建立绑定关系

1.8 储藏操作(stash)

经常有这样的事情发生,当你正在进行项目中某一部分的工作,里面的东西处于一个比较杂乱的状态。
而你想转到其他分支上进行一些工作。
问题是,你不想提交进行了一半的工作,否则以后你无法回到这个工作点。
解决这个问题的办法就是git stash命令。

git stash save <注释信息> 当前分支提交到储藏,插到储藏序列的最前面(不包括新建文件)
git stash save -u <注释信息> 同上,会包括新建文件,功能更强大
git stash list 查看所有储藏中的工作
git stash drop <储藏的名称> 删除对应的某个储藏
git stash pop 取出储藏中最后存入的工作状态进行恢复,会删除储藏
git stash pop <储藏对应的数字> 取出储藏中对应的工作状态进行恢复,会删除储藏
git stash apply 取出储藏中最后存入的工作状态进行恢复,注意,不会删除储藏
git stash apply <储藏的名称> 取出储藏中对应的工作状态进行恢复,注意,不会删除储藏
git stash branch <新分支名> <储藏的名称> 从储藏中新建分支,会删除储藏
git stash clear 清空所有储藏中的工作

1.9 标签操作(tag)

tag 是什么,虽然整个开发过程,每个提交都对应一个提交ID,回滚也比较方便,但总有一些重大意义的提交,比如从v0.91升级到了v1.0,这个v1.0是必须要单独摘出来保存的,否则万一出现回滚事故,导致提交记录丢失呢?
虽然可以单独新建分支保存,但分支的功能并不是用来做这个的。冗余的分支在开发时也容易产生误操作。

所以tag的用处就来了,他可以把某次提交后的项目,整个单独复制拎出来,命名为一个tag(可以以项目版本或打tag的时间来命名),可以写注释,存在tag列表中,与分支隔离开,除非专门命令操作,否则只要项目存在,他们就不会受到影响。

同时,可以使用tag快速回滚项目、基于tag新建分支等,在项目整体的管理上非常方便。

git tag 列出本仓库中所有tag列表
git tag <tag名>' 基于最近的一次提交打tag(轻量tag,不推荐)
git tag -a <tag名> -m '<此tag的注释>' 基于最近的一次提交打tag
git tag -a <tag名> <提交ID> -m '<此tag的注释>' 基于某次提交打tag
git show <tag名> 查看此tag的tag信息
git tag -d <tag名> 删除本地某个tag
git push origin :refs/tags/<tag名> 删除远程仓库中的tag
git push <远程仓库名> <tag名>' 推送某个tag至某个远程库
git push <远程仓库名> --tags' 推送所有tag至某个远程库
git checkout -b <新分支名> <tag名>' 基于某个tag新建分支

2. .gitignore文件

在使用Git的过程中,有的文件比如日志、临时文件、编译的中间文件等不要提交到代码仓库,这时就要设置相应的忽略规则,来忽略这些文件的提交。

没错就是.gitignore文件了。

此文件需要放在.git(默认是隐藏文件,是git的本地仓库)同一目录下

写法如下:

node_modules/
dist/
*.map
npm-debug.log*
yarn-debug.log*
yarn-error.log*

这样,当git add的时候,上面定义忽略的那些文件,就会被git忽略,发生了变化git也不会管。

然而有时候,是会出现意外的。

比如,git已经提交过了,这是后突然说,这里面有个文件需要被忽略,然后我把这个文件写到了.gitignore文件中。。。无效!这个文件仍然没有被忽略!

这是就需要如下命令,来删除此文件并忽略

git rm -r --cached <文件路径+文件全名> 在git库中删除文件并停止追踪,但实际文件还保留,只是忽略掉。

由此,方可。

3. 生成SSH公钥

绑定一个远程仓库,有两种方式:

  • 使用http链接的方式
  • 使用ssh加密链接的方式

如果远程仓库为公开仓库,则两者在使用上,区别不大;
但如果是私密仓库,则http方式,需要输入用户名和密码登录后,才可建立联系,但这样一来,需要把私密仓库的账号和密码暴露,即使添加了项目团队成员,如果成员的账号密码暴露,任何人在任何设备上,登录账号密码,至少都能对项目进行拉取操作,所以就有了下面更合适的方式—ssh

ssh的话,则只需要本地设备,输入一条git指令,生成一对公钥和私钥,然后把公钥的内容,复制添加到远程库的设置中,让远程库认识此设备,就想当于用户填写了账号和密码,好处是,这种方法在远程库的添加上,只认设备不认账户,这样只用保证设备是安全的,仓库就是安全的。

下面为本地设备生成ssh

输入ssh-keygen来生成ssh
window默认存放在C盘/用户/<用户名称>/.ssh文件夹中
Mac默认存放在硬盘/用户/<用户名称>/.ssh文件夹中
其中两个文件,id_rsa.pub为公钥,需要打开复制其中的内容,粘贴到需要的网站中(另一个为私钥,切记保管好)

4. 设置用户名、邮箱、登录账户

git config user.name 查看设置的用户名
git config user.email 查看设置的用户邮箱
git config --global user.name <用户名> 设置用户名
git config --global user.email <邮箱> 设置用户邮箱

修改用户登录信息

  • window

控制面板 -> 用户账户 -> 管理 Windows 凭据,即可看到普通凭据中有git的账户信息,可编辑更改

  • Mac

钥匙串访问.app -> 右上角搜索git,即可看到,可编辑更改

git设置ssh访问

生成新的ssh密钥

进入根目录的.ssh里,没有也行,生成文件名时会生成目录的

cd ~/.ssh

生成文件名和文件位置

ssh-keygen -t rsa -C "github" -f id_rsa_github

指定生成的文件名是id_rsa_github
指定备注是github
密码一般为空即可
此时的~/.ssh目录下会有:

id_rsa_github 私钥文件

id_rsa_github.pub 公钥文件
再把key加到ssh-agent里

Start the ssh-agent in the background.

eval "$(ssh-agent -s)"
Agent pid 59566

If you‘re using macOS Sierra 10.12.2 or later, you will need to modify your ~/.ssh/config file to automatically load keys into the ssh-agent and store passphrases in your keychain.

Host *
AddKeysToAgent yes
UseKeychain yes
IdentityFile ~/.ssh/id_rsa

ssh-add -K ~/.ssh/id_rsa

再把.pub里的加到github的ssh key里
注意:生成的文件名是什么,加到config里的file名字就用自己生成的,ssh-add的也是

此时git clone git://地址 就OK了

参考地址
https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/#generating-a-new-ssh-key

Jenkins和Git稀疏结账

您可以使用Git的稀疏结账功能。 请注意,Git仍将整个存储库克隆到本地磁盘。 然而,这并不是太糟糕,因为它是压缩的。

1.在Jenkins中创建一个新作业,在源代码管理部分设置Git存储库。
2.建立项目。 这会将整个存储库克隆到本地磁盘。
3.打开项目的工作区文件夹,删除除.git文件夹之外的所有内容。
4.打开项目工作区文件夹的Git shell。 启用稀疏检查:

git config core.sparsecheckout true

5.更新工作树:

git read-tree -mu HEAD

6.在.git / info文件夹中创建sparse-checkout文件。 添加要签出到该文件的子文件夹的路径,如下所示(注意尾部斜杠):

folder/to/include/

7.再次构建项目。 这次只有一个子文件夹应出现在工作区文件夹中。