git操作

一、安装Git

sudo apt-get install git
输入git查看说明:

jw@pc:~$ git
usage: git [--version] [--help] [-C <path>] [-c <name>=<value>]
           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
           [-p | --paginate | --no-pager] [--no-replace-objects] [--bare]
           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
           <command> [<args>]

这些是各种场合常见的 Git 命令:

开始一个工作区(参见:git help tutorial)
   clone      克隆仓库到一个新目录
   init       创建一个空的 Git 仓库或重新初始化一个已存在的仓库

在当前变更上工作(参见:git help everyday)
   add        添加文件内容至索引
   mv         移动或重命名一个文件、目录或符号链接
   reset      重置当前 HEAD 到指定状态
   rm         从工作区和索引中删除文件

检查历史和状态(参见:git help revisions)
   bisect     通过二分查找定位引入 bug 的提交
   grep       输出和模式匹配的行
   log        显示提交日志
   show       显示各种类型的对象
   status     显示工作区状态

扩展、标记和调校您的历史记录
   branch     列出、创建或删除分支
   checkout   切换分支或恢复工作区文件
   commit     记录变更到仓库
   diff       显示提交之间、提交和工作区之间等的差异
   merge      合并两个或更多开发历史
   rebase     在另一个分支上重新应用提交
   tag        创建、列出、删除或校验一个 GPG 签名的标签对象

协同(参见:git help workflows)
   fetch      从另外一个仓库下载对象和引用
   pull       获取并整合另外的仓库或一个本地分支
   push       更新远程引用和相关的对象

命令 'git help -a''git help -g' 显示可用的子命令和一些概念帮助。
查看 'git help <命令>''git help <概念>' 以获取给定子命令或概念的
帮助。

二、创建版本库repository

创建一个版本库非常简单,首先,选择一个合适的地方,创建一个空仓库:不需要提前创建空文件夹

jw@pc:myrepository$ git init myrepository
已初始化空的 Git 仓库于 /home/jw/myrepository/.git/
jw@pc:~$ cd myrepository/
jw@pc:myrepository$ la
.git

2.1初始化用户名和邮箱

$ git config --list # 查看用户名和邮箱
$  git config --global user.name "输入你的用户名"
$  git config --global user.email "输入你的邮箱"

2.2修改用户名和邮箱

$  git config --global --replace-all user.email "输入你的邮箱" 
$  git config --global --replace-all user.name "输入你的用户名"
$  git config --list # 修改后再查看是否修改成功

三、熟悉.git的文件夹目录

jw@pc:myrepository$ la .git/
branches  config  description  HEAD  hooks  info  objects  refs
.git/
├── branches
├── config
├── description
├── HEAD
├── hooks
│   ├── applypatch-msg.sample
│   ├── commit-msg.sample
│   ├── fsmonitor-watchman.sample
│   ├── post-update.sample
│   ├── pre-applypatch.sample
│   ├── pre-commit.sample
│   ├── prepare-commit-msg.sample
│   ├── pre-push.sample
│   ├── pre-rebase.sample
│   ├── pre-receive.sample
│   └── update.sample
├── info
│   └── exclude
├── objects
│   ├── info
│   └── pack
└── refs
    ├── heads
    └── tags
  • branches
    对于新版的git,该文件夹已经不用了,但还留着。
    对于分支的管理,新版的git使用ref/heads来管理了
  • objects

项目中你看到的每个东西都是一个object,实际上object有4种:commit、tree、blob、tag

经过 add 命令后,object目录下多了一个文件夹 58/c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c

jw@pc:myrepository$ echo 111 > a.txt
jw@pc:myrepository$ ls
a.txt
jw@pc:myrepository$ la .git/objects/
info  pack
jw@pc:myrepository$ git add .
jw@pc:myrepository$ la .git/objects/
58  info  pack
jw@pc:myrepository$ la .git/objects/58/
c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c
#xxxx是遗传16进制的值,由目录名和里面的文件名前2字符组成
#可以打印object的文件内容
jw@pc:myrepository$ git cat-file -p 58c9
111
#可以看到当前object的类型,此时是blob
jw@pc:myrepository$ git cat-file -t 58c9
blob

接下来执行 commit 操作,此时会发现 objects 目录下多了两个文件夹:

jw@pc:myrepository$ git commit a.txt -m test1
[master (根提交) b89f15c] test1
 1 file changed, 1 insertion(+)
 create mode 100644 a.txt
jw@pc:myrepository$ la .git/objects/
58  b8  f2  info  pack
jw@pc:myrepository$ la .git/objects/b8/
9f15c012e18f62a1ab6f8d9fa9d85fc0c555c3
jw@pc:myrepository$ la .git/objects/f2/
53233a1a0e59f33115daca3fa494eaa20758d8

先查看b89f的文件内容:

jw@pc:myrepository$ git cat-file -t b89f
commit
# 该object是一个commit类型
jw@pc:myrepository$ git cat-file -p b89f
tree f253233a1a0e59f33115daca3fa494eaa20758d8
author Jan0510 <814862021@qq.com> 1592031008 +0800
committer Jan0510 <814862021@qq.com> 1592031008 +0800

test1
# 该boject的内容有什么?
# tree HASH值,指向了一个object
# 作者信息
# commit时的附加信息

通过以上的信息可以知道,实际上 commit 命令是构建了1个 tree,再通过 tree 后面的HASH值,可以找到该tree:

jw@pc:myrepository$ git cat-file -t f253233a1a0e59f33115daca3fa494eaa20758d8
tree
jw@pc:myrepository$ git cat-file -p f253233a1a0e59f33115daca3fa494eaa20758d8
100644 blob 58c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c	a.txt

  • 小结:commit 指向 tree ,tree 指向 blob

  • refs
    该目录用于存放软链接,有4个文件夹

jw@pc:myrepository$ la .git/refs/
heads  tags  remotes  stash
  • refs/heads
    该文件夹存储的是所有的本地branch文件,每一个本地branch文件中,存储的是一个哈希值,每一次commit都是生成一个对应的哈希值,然后用这次新生成的哈希值,替换掉原来这个branch文件中的哈希值。

换句话说,branch 文件中的哈希值都指向最新的 commit

jw@pc:myrepository$ la .git/refs/heads/
master
# 本地仓库只有1个分支叫master
jw@pc:myrepository$ cat .git/refs/heads/master 
b89f15c012e18f62a1ab6f8d9fa9d85fc0c555c3

  • refs/stash
    该文件夹是使用git stash命令时,会将生成的git对象的哈希值存储到stash文件里,更进一步说stash文件中,就存了一个40位的哈希值。对应的git对象在objects文件夹里。使用git stash pop时,会先去stash文件中,找到该哈希值(或者也可以认为是指针),然后到对应的objects文件夹下,查找对应的git对象,将其中的数据取出来,转换成我们能看懂的代码

  • refs/remotes
    存储最近一次push到远程仓库的 commit

  • refs/tags
    (首先要与branch区分开来,tags本质就是branch,与branch不同的是tag是不会有改动的,是历史版本记录)
    tags 存储一系列历史版本的最后一次 commit,也就是存在记录。

  • hooks
    这个目录存放一些shell脚本,可以设置特定的git命令后触发相应的脚本;

在搭建gitweb系统或其他git托管系统会经常用到hook script。

  • info
    包含git仓库的一些信息
  • logs
    保存所有更改的引用记录,继续打开logs文件夹,有refs文件夹和HEAD文件
  • logs/HEAD文件
    主要记录每次的变更操作,所有类型的变更都会记录的

git reflog命令查询出的列表就是HEAD中存储的列表

  • logs/refs
    继续打开refs文件夹,可以看到heads文件夹、remotes文件夹和stash文件。

  • HEAD
    该文件中存放的是一个软链接,指向了正在工作的branch,而branch位于refs目录下:

jw@pc:myrepository$ cat .git/HEAD 
ref: refs/heads/master
jw@pc:myrepository$ cat .git/refs/heads/master 
b89f15c012e18f62a1ab6f8d9fa9d85fc0c555c3

  • Index
    存储缓冲区(stage)的内容,内容包括它指向的文件的时间戳、文件名、HASH值等。

想要查看index文件的内容,不能直接使用cat命令打印,会出现乱码,而需要使用ls-files --stage

jw@pc:myrepository$ git ls-files --stage
100644 58c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c 0	a.txt

四、git提交时的操作流程

  • 修改文件a(位于workplace)
  • 将文件推入stage(暂存区)git add a
  • 修改文件b(位于workplace)
  • 将文件推入stage(暂存区)git add b
  • 将stage中的文件一次提交到local repository(本地仓库)git commit . -m "1"
  • 编写commit msg(就是说明我做了什么修改,主题/内容/其他)

五、git恢复时的操作流程

1、想要将workplace恢复到之前的某个状态(前提是之前那个状态被commit过),怎么操作?

这是最常用的版本回退操作,只要曾经被commit过的内容,都可以从local repository再拷到workplace!

同时,index/log 的内容也会恢复到对应状态,也就是说,恢复后再使用git log就会失去一部分日志,可以改用git reflog查看日志。

使用HEAD指针恢复

在git中,HEAD指向最新的一次commit,HEAD~1表示回退1个版本HEAD~2表示回退2个版本

jw@pc:myrepository$ git reset --hard HEAD~1
HEAD 现在位于 8426369 555edit
jw@pc:myrepository$ git reset --hard HEAD~1
HEAD 现在位于 aae8e6e sdasaddada
jw@pc:myrepository$ git reset --hard HEAD~1
HEAD 现在位于 b89f15c test1
jw@pc:myrepository$ git reset --hard HEAD~1

使用HASH码恢复

每次commit都会对应一个HASH码,可以直接恢复到指定的commit阶段

jw@pc:myrepository$ git reset --hard 84263698191414dd2d5fdb483772d50c147dec2f
HEAD 现在位于 8426369 555edit

使用场景:

我有三次commit:当前最新版本是v3
git commit -m v1
git commit -m v2
git commit -m v3
假如我想要恢复到v1,那么我可以

git reset HEAD~2
回退2个版本,此时workplace就会恢复到v1了

别急,再执行git log会发现,v2v3的记录消失了!若想再从v1恢复到v3怎么办?

若v3的commit码还在,直接执行
git reset [commit of v3]
若v3的commit码已经忘记了,也可以执行
git reflog
查看commit v3 时记录的HASH码,该命令查看的是logs/HEAD文件,保留所有历史记录
  • 小结:使用HASH码可以恢复到指定的commit记录

2、add 操作后悔了,怎么办?

这种情况也是常见的,因为每次修改workpalce后,都需要先add再commit。而add的东西都会被记录,因此撤销add也是有必要的。

在 add 的时候我们可能会:

  • 选错了文件,那么要撤销index名单
# 首先查看index文件的内容,说明正在跟踪哪些文件的修改
jw@pc:myrepository$ git ls-files -s
100644 8d47907939dced20dfe17c0a30c347923c5cb8e2 0	a.txt
# 创建了1个新文件,并add到index中,跟踪其修改情况
jw@pc:myrepository$ echo 1 >> b.txt
jw@pc:myrepository$ ls
a.txt  b.txt
jw@pc:myrepository$ git add b.txt 
# 可以看到,add了新文件后,index的表项增加1条
jw@pc:myrepository$ git ls-files -s
100644 8d47907939dced20dfe17c0a30c347923c5cb8e2 0	a.txt
100644 d00491fd7e5bb6fa28c517a0bb32b8b506539d4d 0	b.txt
# 但是我发现我add错了文件,需要将其从index中拿出来
jw@pc:myrepository$ git reset b.txt
jw@pc:myrepository$ git ls-files -s
100644 8d47907939dced20dfe17c0a30c347923c5cb8e2 0	a.txt
# 将b.txt文件从index中去除的前提时,add b.txt 之后,没有执行commit
# 若是执行了commit,那么就不是这么处理了,而是参考第1条,使用HEAD或者HASH码恢复

  • 突然还想再修改一下,即保存workpalce的最新修改,但是要求不改动stage区,那么要撤销 add 操作
# 上面我已经修改了a.txt的内容,并且执行了add操作
jw@pc:myrepository$ git status
位于分支 master
要提交的变更:
  (使用 "git reset HEAD <文件>..." 以取消暂存)

	修改:     a.txt
# 从status可以看出,git提示我们要存在提交的变更,可以commit了哦,或者可以reset取消之前add的东西
# 因为我后悔add操作了,那么接下来我就取消暂存
jw@pc:myrepository$ git reset HEAD a.txt
重置后取消暂存的变更:
M	a.txt
# reset成功了,再打印status看看
jw@pc:myrepository$ git status
位于分支 master
尚未暂存以备提交的变更:
  (使用 "git add <文件>..." 更新要提交的内容)
  (使用 "git checkout -- <文件>..." 丢弃工作区的改动)

	修改:     a.txt

修改尚未加入提交(使用 "git add" 和/或 "git commit -a"# 可以看出,上次add的修改不见了,git提示我们,workplace中的修改还没有add进stage哦,得add之后才能commit哦。

  • 突然觉得不需要修改,让workplace与stage保持一致,那么要撤销add操作,丢弃workplace的修改
    该操作不可逆,慎用!因为workplace所做的修改会找不回来了
jw@pc:myrepository$ git checkout -- a.txt
# 此时workplace就恢复到上一次add时的样子,之前workplace的修改都丢失了

小结:

  • 从stage到workplace是git checkout -- [file]
  • 从repository到stage是git reset HEAD [file]

3、commit操作注释写错了

两种情况:
1.已经将代码push到远程仓库
2.还没将代码push到远程仓库,还在本地的仓库中

-还没将代码push到远程仓库,还在本地的仓库中

修改最后一次注释:
git commit --amend

然后会弹出vim编辑窗口,修改之前的注释

修改之前的某次注释:
step1:
git rebase -i HEAD~2

最后的数字2指的是显示到倒数第几次 比如这个输入的2就会显示倒数的两次注释(最上面两行)
在这里插入图片描述

step2:

你想修改哪条注释 就把哪条注释前面的pick换成edit。方法就是上面说的编辑方式:i—编辑,把pick换成edit—Esc—:wq.

step3:

然后:(接下来的步骤Terminal会提示)
git commit --amend

step4:

修改注释,保存并退出后,输入:
git rebase --continue

在这里插入图片描述

六、删除文件

  • 确定要删除
# workplace删除文件
jw@pc:myrepository$ rm b.txt 
# 告知git删除文件,git add b.txt也是一样的效果
jw@pc:myrepository$ git rm b.txt 
rm 'b.txt'
# 提交到本地仓库
jw@pc:myrepository$ git commit -a -m rmtest
[master af40984] rmtest
 1 file changed, 1 deletion(-)
 delete mode 100644 b.txt
  • 删错了…可以尝试从stage、repository中恢复
# ------------从repository恢复------------------
# 添加一个新文件b.txt
jw@pc:myrepository$ echo 111 >> b.txt
jw@pc:myrepository$ git add -A
# commit到repository
jw@pc:myrepository$ git commit -a -m addfile
[master c8a5f43] addfile
 1 file changed, 1 insertion(+)
 create mode 100644 b.txt
 # 删除文件
jw@pc:myrepository$ rm b.txt 
# 这里没有用git rm,用add效果也一样
jw@pc:myrepository$ git add -A
# commit到repository
jw@pc:myrepository$ git commit -a -m rmfile
[master 1fd4bff] rmfile
 1 file changed, 1 deletion(-)
 delete mode 100644 b.txt
# 删除完成,现在尝试从repository中恢复,先将repository中的b.txt拉到stage
jw@pc:myrepository$ git reset HEAD~1 b.txt
重置后取消暂存的变更:
D	b.txt
# ------------从stage中恢复------------------
# 再从stage中拉到workplace
jw@pc:myrepository$ git checkout -- b.txt
# 查看workplace,可以看到文件b.txt又恢复了
jw@pc:myrepository$ ls
a.txt  b.txt
jw@pc:myrepository$ cat b.txt 
111

七、查看文件之间的差异

  • workpalce与repository
    git diff [file]
  • workplace与stage
    git diff HEAD
  • stage与repository
    git diff --cached 或 git diff --staged
  • repository中的分支之间的差异
    git diff <分支名1> <分支名2>
    git diff <分支名1> <分支名2> [file]

八、远程仓库

注册一个GitHub账号,就可以免费获得Git远程仓库。

由于你的本地Git仓库和GitHub仓库之间的传输是通过SSH加密的,所以,需要一点设置:

第1步:创建SSH Key。在用户主目录下,看看有没有.ssh目录

如果有,再看看这个目录下有没有id_rsaid_rsa.pub这两个文件,如果已经有了,可直接跳到下一步。如果没有,创建SSH Key:

jw@pc:~$ ssh-keygen -t rsa -C "814862021@qq.com"
# 把邮件地址换成你自己的邮件地址,然后一路回车,使用默认值即可,由于这个Key也不是用于军事目的,所以也无需设置密码。
Generating public/private rsa key pair.
Enter file in which to save the key (/home/jw/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/jw/.ssh/id_rsa.
Your public key has been saved in /home/jw/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:GY5iweBSmRdDhmOcZjZmdVqPIrkJX2pmtmNqf9zTy3E 814862021@qq.com
The key's randomart image is:
+---[RSA 2048]----+
| .oB*.o          |
| o/=.= o         |
|oB++* . o        |
| + * o o o       |
|  X o . S        |
| = o .           |
|  + . . .. E     |
| + . o o..o      |
|o ...   .o.      |
+----[SHA256]-----+

如果一切顺利的话,可以在用户主目录里找到.ssh目录,里面有id_rsaid_rsa.pub两个文件,这两个就是SSH Key的秘钥对,id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人。

第2步:登陆GitHub,打开“Account settings”,“SSH Keys”页面:

然后,点“Add SSH Key”,填上任意Title,在Key文本框里粘贴id_rsa.pub文件的全部内容。然后提交key就可以了。

从本地仓库链接到远程仓库

现在的情景是,你已经在本地创建了一个Git仓库后,又想在GitHub创建一个Git仓库,并且让这两个仓库进行远程同步,这样,GitHub上的仓库既可以作为备份,又可以让其他人通过该仓库来协作,真是一举多得。

第3步:登陆GitHub,然后,在右上角找到“Create a new repository”按钮,创建一个新的仓库

在Repository name填入learngit,其他保持默认设置,点击“Create repository”按钮,就成功地创建了一个新的Git仓库!

根据github的提示,有2种方式:

若没有本地仓库则需要先创建一个本地仓库

echo "# learngit" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin https://github.com/Jan0510/learngit.git
git push -u origin master

若已经有本地仓库了,则直接添加远程仓库

git remote add origin https://github.com/Jan0510/learngit.git
git push -u origin master

添加后,远程库的名字就是origin,这是Git默认的叫法,也可以改成别的,但是origin这个名字一看就知道是远程库。

jw@pc:~$ cd myrepository/
jw@pc:myrepository$ git remote add origin https://github.com/Jan0510/learngit.git

下一步,就可以把本地库的所有内容推送到远程库上:

jw@pc:myrepository$ git push -u origin master
Username for 'https://github.com': Jan0510
Password for 'https://Jan0510@github.com': 
对象计数中: 18, 完成.
Delta compression using up to 4 threads.
压缩对象中: 100% (11/11), 完成.
写入对象中: 100% (18/18), 1.36 KiB | 347.00 KiB/s, 完成.
Total 18 (delta 4), reused 0 (delta 0)
remote: Resolving deltas: 100% (4/4), done.
To https://github.com/Jan0510/learngit.git
 * [new branch]      master -> master
分支 'master' 设置为跟踪来自 'origin' 的远程分支 'master'

此后,每次本地提交后,只要有必要,就可以使用命令git push origin master推送最新修改;

从远程仓库拉代码到本地仓库

假设从零开发

那么最好的方式是先创建远程库,然后,从远程库克隆。

首先,登陆GitHub,创建一个新的仓库,名字叫gitskills

我们勾选Initialize this repository with a README,这样GitHub会自动为我们创建一个README.md文件。创建完毕后,可以看到README.md文件

现在,远程库已经准备好了,下一步是用命令git clone克隆一个本地库:

jw@pc:~$ git clone git@github.com:Jan0510/gitskills-.git
正克隆到 'gitskills-'...
The authenticity of host 'github.com (52.74.223.119)' can't be established.
RSA key fingerprint is SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8.
Are you sure you want to continue connecting (yes/no)? y
Please type 'yes' or 'no': yes
Warning: Permanently added 'github.com,52.74.223.119' (RSA) to the list of known hosts.
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
接收对象中: 100% (3/3), 完成.

进入gitskills目录看看,已经有README.md文件了:

$ cd gitskills-
$ ls
README.md

假设已经在本地建立好目录和准备好其他本地文件

git init
git checkout -b main # 创建仓库后先创建一个main分支
git remote -v	# 若什么都没有,则和远程已断联系,拉不了代码也推不了代码
git remote add origin git@github.com:xxxxxxxxxxxxxxxxxxxxxxxxxxxx
git remote -v	# 这次可以看到有对应关系了
git pull origin main # 将远程仓库origin的main分支拉取到本地的当前分支下

push操作与上面的其他情况大同小异,不再赘述。

九、分支管理

创建与合并

截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master分支。HEAD严格来说不是指向提交,而是指向master,master才是指向提交的,所以,HEAD指向的就是当前分支。

首先,我们创建dev分支,然后切换到dev分支:

jw@pc:gitskills-$ git checkout -b dev
切换到一个新分支 'dev'

然后,用git branch命令查看当前分支:

jw@pc:gitskills-$ git branch
* dev
  master

然后,我们就可以在dev分支上正常提交,比如对readme.txt做个修改,加上一行:

jw@pc:gitskills-$ cat README.md 
# gitskills-jw@pc:gitskills-$ echo 123 >> README.md 
jw@pc:gitskills-$ git add README.md 
jw@pc:gitskills-$ git commit -m 123
[dev 94f4e3b] 123
 1 file changed, 1 insertion(+), 1 deletion(-)

现在,dev分支的工作完成,我们就可以切换回master分支:

jw@pc:gitskills-$ git checkout master
切换到分支 'master'
您的分支与上游分支 'origin/master' 一致。
# 切换回master分支后,再查看一个readme.txt文件,刚才添加的内容不见了!因为那个提交是在dev分支上
jw@pc:gitskills-$ cat README.md 
# gitskills-jw@pc:gitskills-$ 

我们把dev分支的工作成果合并到master分支上:

jw@pc:gitskills-$ git merge dev
更新 1c3d60c..94f4e3b
Fast-forward
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
# 合并完成后,就可以放心地删除dev分支了:
jw@pc:gitskills-$ git branch -d dev
已删除分支 dev(曾为 94f4e3b)。
jw@pc:gitskills-$ git branch
* master

解决冲突

当多个分支合并时,难免会发生冲突的情况:

  • 例如分支A和分支B都修改了同一个文件,那么2个分支合并时,这个文件必然产生冲突,就需要手动编辑,解决冲突后再合并。
# 当前分支只有1个,现在开始创建2个分支,并修改同一个文件README.md 
jw@pc:gitskills-$ git branch
* master
jw@pc:gitskills-$ git checkout -b A
切换到一个新分支 'A'
jw@pc:gitskills-$ echo Creating a new branch is quick AND simple. > README.md 
jw@pc:gitskills-$ cat README.md 
Creating a new branch is quick AND simple.
jw@pc:gitskills-$ git add README.md
jw@pc:gitskills-$ git commit -m AND
[A 77f5257] AND
 1 file changed, 1 insertion(+), 1 deletion(-)
# 先切换到master分支,因为master分支的README.md 还是原样没有修改
jw@pc:gitskills-$ git checkout master
切换到分支 'master'
您的分支领先 'origin/master' 共 1 个提交。
  (使用 "git push" 来发布您的本地提交)
# 创建、修改、add、commit
jw@pc:gitskills-$ git checkout -b B
切换到一个新分支 'B'
jw@pc:gitskills-$ echo "Creating a new branch is quick & simple." > README.md 
jw@pc:gitskills-$ git add -A
jw@pc:gitskills-$ git commit -a -m "&"
[B 4f2da15] &
 1 file changed, 1 insertion(+), 1 deletion(-)
# A和B都修改完了,并且都commit到本地仓库了
# 接下来回到master分支,将A、B的内容合并到一起
jw@pc:gitskills-$ git checkout master
切换到分支 'master'
您的分支领先 'origin/master' 共 1 个提交。
  (使用 "git push" 来发布您的本地提交)
# 合并分支A,顺利完成
jw@pc:gitskills-$ git merge A
更新 94f4e3b..77f5257
Fast-forward
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
# 合并分支B,git提示冲突,因为都修改了同一个文件
jw@pc:gitskills-$ git merge B
自动合并 README.md
冲突(内容):合并冲突于 README.md
自动合并失败,修正冲突然后提交修正的结果。
#git status查看冲突信息
jw@pc:gitskills-$ git status
位于分支 master
您的分支领先 'origin/master' 共 2 个提交。
  (使用 "git push" 来发布您的本地提交)

您有尚未合并的路径。
  (解决冲突并运行 "git commit")
  (使用 "git merge --abort" 终止合并)

未合并的路径:
  (使用 "git add <文件>..." 标记解决方案)

	双方修改:   README.md

修改尚未加入提交(使用 "git add" 和/或 "git commit -a"# 打印冲突的文件,看看A、B分别修改了什么,然后才能手动编辑,修改冲突
jw@pc:gitskills-$ cat README.md 
<<<<<<< HEAD
Creating a new branch is quick AND simple.
=======
Creating a new branch is quick & simple.
>>>>>>> B
# 修改后使用add则告知git已经解决冲突了
jw@pc:gitskills-$ git add README.md 
jw@pc:gitskills-$ git status
位于分支 master
您的分支领先 'origin/master' 共 2 个提交。
  (使用 "git push" 来发布您的本地提交)

所有冲突已解决但您仍处于合并中。
  (使用 "git commit" 结束合并)

要提交的变更:

	修改:     README.md
# add后就是commit了
jw@pc:gitskills-$ git commit -m AandB
[master 48ab814] AandB
# 查看分支内容提交的时间线
jw@pc:gitskills-$ git log --graph
*   commit 48ab8144f79f73fb284389f0983461f586c1e885 (HEAD -> master)
|\  Merge: 77f5257 4f2da15
| | Author: Jan0510 <814862021@qq.com>
| | Date:   Sun Jun 14 16:11:56 2020 +0800
| | 
| |     AandB
| | 
| * commit 4f2da15e7e9aaab9f9719fcb13a81638acb0c67d (B)
| | Author: Jan0510 <814862021@qq.com>
| | Date:   Sun Jun 14 16:05:55 2020 +0800
| | 
| |     &
| | 
* | commit 77f52578d68298c9ecff8dd59019329beb52bd7b (A)
|/  Author: Jan0510 <814862021@qq.com>
|   Date:   Sun Jun 14 16:01:08 2020 +0800
|   
|       AND
| 
* commit 94f4e3b5e9dad67c7bdbf0b23cd2aa817707aaee
| Author: Jan0510 <814862021@qq.com>
| Date:   Sun Jun 14 15:36:30 2020 +0800
| 

# 合并之后可以选择删除分支
jw@pc:gitskills-$ git branch
  A
  B
* master
jw@pc:gitskills-$ git branch -d A
已删除分支 A(曾为 77f5257)。
jw@pc:gitskills-$ git branch -d B
已删除分支 B(曾为 4f2da15)。

  • 但是我们并不能保证一次修改就完美解决bug了,所以,在产生冲突之后,不要急于修改冲突的文件,而是先commit一份修改前的版本进本地仓库
    这样一来,如果我们手动编辑解决不了冲突,甚至还写出了更多bug时 ,还能回退到最初的模样!
jw@pc:gitskills-$ git merge B
自动合并 README.md
冲突(内容):合并冲突于 README.md
自动合并失败,修正冲突然后提交修正的结果。

# 合并时产生冲突,查看冲突的文件内容
jw@pc:gitskills-$ cat README.md 
<<<<<<< HEAD
111
=======
333
>>>>>>> B
# 不做修改,先commit一份再说
jw@pc:gitskills-$ git commit -am before_edit
[master 6a17543] before_edit

分支策略

在实际开发中,我们应该按照几个基本原则进行分支管理:

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

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

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

所以,团队合作的分支看起来就像这样:
在这里插入图片描述

  • 下面我们实战一下--no-ff方式的git merge
# 首先,仍然创建并切换dev分支:
jw@pc:gitskills-$ git checkout -b dev
切换到一个新分支 'dev'
# 修改文件并提交
jw@pc:gitskills-$ echo test_no_ff > README.md  
jw@pc:gitskills-$ git commit -am "add merge"
[dev fa5775b] add merge
 1 file changed, 1 insertion(+), 5 deletions(-)
# 现在切换会master分支
jw@pc:gitskills-$ git checkout master
切换到分支 'master'
您的分支领先 'origin/master' 共 8 个提交。
  (使用 "git push" 来发布您的本地提交)
# 合并分支,但是使用了参数:--no-ff,表明强制关闭Fast forward
# 有什么作用呢?就是合并分支时,也会记录为一次commit,以后可以在log中查看
jw@pc:gitskills-$ git merge --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
 README.md | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)
# 查看log,可以发现merge时的commit记录
jw@pc:gitskills-$ git log
commit 6940b9308def6d2e3f02e40f35a28b9a9a05480d (HEAD -> master)
Merge: 6a17543 fa5775b
Author: Jan0510 <814862021@qq.com>
Date:   Sun Jun 14 18:18:07 2020 +0800

    merge with no-ff

commit fa5775bfb0240678d5c843d1c29162788c2293ee (dev)
Author: Jan0510 <814862021@qq.com>
Date:   Sun Jun 14 18:16:19 2020 +0800

    add merge

可以看到,不使用Fast forward模式,merge后就像这样:
在这里插入图片描述

  • 小结
    合并分支时,加上–no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward合并就看不出来曾经做过合并。

bug处理的流程

每个bug都可以通过一个新的临时分支来修复,修复后,合并分支,然后将临时分支删除。

当你接到一个修复一个代号101的bug的任务时,很自然地,你想创建一个分支issue-101来修复它,但是,等等,当前正在dev上进行的工作还没有提交!!

并不是你不想提交,而是工作只进行到一半,还没法提交,预计完成还需1天时间。但是,必须在两个小时内修复该bug,怎么办?

stash

幸好,Git还提供了一个stash功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:

jw@pc:gitskills-$ git checkout master
M	README.md
A	a.txt
A	b.txt
切换到分支 'master'
您的分支领先 'origin/master' 共 10 个提交。
  (使用 "git push" 来发布您的本地提交)

# 可以看出,在使用stash之前,workplace中存在着一些与bug不相关文件
jw@pc:gitskills-$ git stash
保存工作目录和索引状态 WIP on master: 6940b93 merge with no-ff
jw@pc:gitskills-$ git status
位于分支 master
您的分支领先 'origin/master' 共 10 个提交。
  (使用 "git push" 来发布您的本地提交)

无文件要提交,干净的工作区

# 工作区是干净的,刚才的工作现场存到哪去了?用`git stash list`命令看看:
jw@pc:gitskills-$ git stash list
stash@{0}: WIP on master: 6940b93 merge with no-ff

# 你可以多次stash,恢复的时候,先用`git stash list`查看,然后恢复指定的stash,用命令:
git stash apply stash@{0}

工作现场还在,Git把stash内容存在某个地方了,但是需要恢复一下,有两个办法:

一是用git stash apply恢复,但是恢复后,stash内容并不删除,你需要用git stash drop来删除;

另一种方式是用git stash pop,恢复的同时把stash内容也删了;

新建bug分支

首先确定要在哪个分支上修复bug,假定需要在master分支上修复,就从master创建临时分支

# 在master上新建一个分支,用来修复bug
jw@pc:gitskills-$ git checkout -b issue-101
切换到一个新分支 'issue-101'
jw@pc:gitskills-$ ls
README.md
# 修复bug
jw@pc:gitskills-$ echo no bug > README.md

# 提交修复bug的相关文件 
jw@pc:gitskills-$ git commit -am "fix bug101"
[issue-101 e39fea3] fix bug101
 1 file changed, 1 insertion(+), 1 deletion(-)
jw@pc:gitskills-$ git checkout master
切换到分支 'master'
您的分支领先 'origin/master' 共 10 个提交。
  (使用 "git push" 来发布您的本地提交)
# 切换回master分支,将刚刚的修复文件合并回主分支
jw@pc:gitskills-$ git merge --no-ff -m "merge bug101-fixed" issue-101 
Merge made by the 'recursive' strategy.
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

master分支上修复了bug后,我们要想一想,dev分支是早期从master分支分出来的,所以,这个bug其实在当前dev分支上也存在。

同样的bug,要在dev上修复,我们只需要把[issue-101 e39fea3]这个提交所做的修改“复制”到dev分支。

为了方便操作,Git专门提供了一个cherry-pick命令,让我们能复制一个特定的提交到当前分支:

jw@pc:gitskills-$ git branch
  A
  B
  dev
  issue-101
* master
jw@pc:gitskills-$ git checkout dev
切换到分支 'dev'
jw@pc:gitskills-$ git cherry-pick e39fea3
[dev 09995e2] fix bug101
 Date: Sun Jun 14 18:49:26 2020 +0800
 1 file changed, 1 insertion(+), 1 deletion(-)
jw@pc:gitskills-$ cat README.md 
no bug

Git自动给dev分支做了一次提交,注意这次提交的commit是[dev 09995e2]

  • 小结
    在master分支上修复的bug,想要合并到当前dev分支,可以用git cherry-pick <commit>命令,把bug提交的修改“复制”到当前分支,避免重复劳动。

多人协作

本地与远程的追踪关系(tracking)

在某些场合,Git会自动在本地分支与远程分支之间,建立一种追踪关系(tracking)。比如,在git clone的时候,所有本地分支默认与远程主机的同名分支,建立追踪关系,也就是说,本地的master分支自动"追踪"origin/master分支。

# 查看本地与远程分支的对应关系
$ git branch -av

remote

为了便于管理,Git要求每个远程主机都必须指定一个主机名。git remote命令就用于管理主机名。

不带选项的时候,git remote命令列出所有远程主机。

# 要查看远程库的信息,用git remote:
jw@pc:gitskills-$ git remote
origin
jw@pc:gitskills-$ git remote -v
origin	git@github.com:Jan0510/gitskills-.git (fetch)
origin	git@github.com:Jan0510/gitskills-.git (push)
# 上面显示了可以抓取和推送的origin的地址。如果没有推送权限,就看不到push的地址。
# 主机名缺省时是origin,如果想用其他的主机名,需要用git clone命令的-o选项指定。
jw@pc:gitskills-$ git clone -o <主机名> <版本库的网址>
# git remote show命令加上主机名,可以查看该主机的详细信息。
jw@pc:gitskills-$ git remote show <主机名>

clone

克隆仓库。该命令会在本地主机生成一个目录,与远程主机的版本库同名。如果要指定不同的目录名,可以将目录名作为git clone命令的第二个参数。

git clone <版本库的网址>
git clone <版本库的网址> <本地目录名>

fetch

同步远程分支到本地(因为一开始是在本地看不到远程分支的,或者版本落后于远程主机),一旦远程主机的版本库有了更新(Git术语叫做commit),需要将这些更新取回本地,这时就要用到git fetch命令。

git fetch命令通常用来查看其他人的进程,因为它取回的代码对你本地的开发代码没有影响。所取回的更新,在本地主机上要用"远程主机名/分支名"的形式读取。
比如origin主机的master,就要用origin/master读取。

取回远程主机的更新以后,可以在它的基础上,使用git checkout命令创建一个新的分支,使用该新分支在本地肆意修改。

# 同步远程分支到本地,使用后,本地就能看到远程分支origin/master
# 但数据不会实时同步,即使不小心改动了也不会影响远程主机上的内容
git fetch origin master
# 查看远程分支
$ git branch -r
origin/master
# 查看所有分支
$ git branch -a
* master
  remotes/origin/master
# 在远程分支的基础上创建一个可以肆意修改的分支
git checkout -b newBrach origin/master

如果是别人更新了远程分支的内容,我们取回本地后想把别人更新的内容同步到本地的主分支上,可以使用merge,在本地分支上合并远程分支。
$ git merge origin/master

pull

git pull命令的作用是,取回远程主机某个分支的更新,再与本地的指定分支合并,其实就相当于fetch+merge

$ git pull <远程主机名> <远程分支名>
$ git pull origin next
# 将远程分支更新到当前分支(可能得先使用git checkout <branch>切换到对应分支)
# 相当于下面的命令
# 同步origin/next到本地
$ git fetch origin next
# 将origin/next合并到当前分支(可能得先使用git checkout <branch>切换到对应分支)
$ git merge origin/next

push

推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上:

git push origin master
如果要推送其他分支,比如dev,就改成:
git push origin dev

但是,并不是一定要把本地分支往远程推送,那么,哪些分支需要推送,哪些不需要呢?

  • master分支是主分支,因此要时刻与远程同步;

  • dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;

  • bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;

  • feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。

clone

抓取分支

默认情况下,你的小伙伴只能看到本地的master分支。不信可以用git branch命令看看

jw@pc:~$ git clone git@github.com:Jan0510/gitskills-.git
正克隆到 'gitskills-'...
The authenticity of host 'github.com (52.74.223.119)' can't be established.
RSA key fingerprint is SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8.
Are you sure you want to continue connecting (yes/no)? y
Please type 'yes' or 'no': yes
Warning: Permanently added 'github.com,52.74.223.119' (RSA) to the list of known hosts.
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
接收对象中: 100% (3/3), 完成.
jw@pc:~$ git branch
* master

现在,你的小伙伴要在dev分支上开发,就必须创建远程origin的dev分支到本地,于是他用这个命令创建本地dev分支:

git checkout -b dev origin/dev

现在,他就可以在dev上继续修改,然后,时不时地把dev分支push到远程:

多人协作的流程

多人协作时,大家都会往master和dev分支上推送各自的修改。

因此,多人协作的工作模式通常是这样:

  • 首先,可以试图用git push origin <branch-name>推送自己的修改;

  • 如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图先把文件拉回本地;
    如果git pull提示no tracking information,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to <branch-name> origin/<branch-name>

  • 如果合并有冲突,在本地解决冲突,并在本地提交;

  • 没有冲突或者解决掉冲突后,再用git push origin <branch-name>推送就能成功!

注意:本地和远程分支的名称最好一致

十、打标签

Git有commit,为什么还要引入tag?

“请把上周一的那个版本打包发布,commit号是6a5819e…”

“一串乱七八糟的数字不好找!”

如果换一个办法:

“请把上周一的那个版本打包发布,版本号是v1.2”

“好的,按照tag v1.2查找commit就行!”

所以,tag就是一个让人容易记住的有意义的名字,它跟某个commit绑在一起。

创建标签

标签总是和某个commit挂钩。如果这个commit既出现在master分支,又出现在dev分支,那么在这两个分支上都可以看到这个标签。

# 为当前分支打上标签,没有指定的commit,那么默认就是HEAD指向的分支的最新的commit
git tag v1.0
# 为特定的commit打上标签
git tag v0.9 f52c633
# 还可以给表情添加说明msg
git tag -a v0.1 -m "version 0.1 released" f52c633

# 查看标签的说明
git show v0.1

操作标签

如果标签打错了,也可以删除:

git tag -d v0.1

标签不会随着push而传到远程,如果要推送某个标签到远程,使用命令git push origin <tagname>

# 推送标签 v1.0到远程
git push origin v1.0
# 一次性推送全部标签
git push origin --tags

如果标签已经推送到远程,要删除远程标签就麻烦一点,先从本地删除,然后,从远程删除。

git tag -d v0.9
git push origin :refs/tags/v0.9
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值