FaYunYm

本文章将简要介绍Windows下Git及其基本操作,并结合用C++实现单链表开发实例学会使用Git进行版本控制和项目开发。

关于Git

什么是Git和GitHub

GitHub是世界上最大的代码托管平台、远程仓库,同性交友网站,它利用Git进行版本控制。
Git是世界上最流行的分布式版本控制工具,简单来说就是当你在项目开发版本迭代的过程中,可以利用Git方便地回溯到任何一个历史版本,而不需要对历史文件进行任何存储和备份。当你试图利用word进行版本控制并与导师愉快交流:
20201014181757
此外,Git也是团队合作的工具,使用Git能够记录谁在哪个时间对项目的哪一部分进行了修改,进行跟踪,保证团队成员的同步和协作开发。

使用Git

可以通过下面的方法使用Git:

利用Git Bash

Git Bash是方便在windows下使用git命令的模拟终端(windows自带的cmd功能太弱):
20201014185345

vscode中使用Git

两种方法:
20201014185036

Git项目开发流程演练

Git基本操作

Git的基本操作如下:
20201014190331
Git的原理可以参考孟宁老师的文章:
五⼤场景玩转 Git,只要这一篇就够了!
下面通过一个简单的实例来熟悉这些基本操作:使用C++实现单链表,通过Git来进行版本控制,用GitHub作为远程仓库(Remote)。示例所用仓库地址放在最后。
我们使用Git Bash进行命令行操作,使用vscode作为默认编辑器。

单人版本控制

假设现在只有你一个人在独立开发。

Git config

首先在本地利用Git Bash进行Git的用户配置:

$ git config --global user.email "Lanjun1998@163.com"    #使用你的邮箱作为用户标识
$ git config --global user.name "FayunYm" #英文名
$ git config --global core.editor "code -w"   #使用vscode作为默认编辑器,你可以使用code -h命令查看w参数的意思

配置成功后可通过以下命令查看用户(--global)配置:

$ git config --global --list

或通过vscode查看或更改配置:

$ git config --global -e

20201014203652

git clone

然后在GitHub上创建仓库命名为Link-List:
20201014204143
然后将其克隆到本地仓库。但在这之前需要取得clone权限:
获取git clone权限
使用clone指令将GitHub仓库的内容克隆至本地:

$ git clone git@github.com:LanjunC/Link-List.git    #SSH地址

20201014204758
生成了Link-List文件夹:
20201014205215

git add 和git commit

通过vscode打开Link-List文件夹,将其作为工作区,编辑README并保存:
20201014210029
接着使用git add指令将文件添加至暂存区index,表示已经暂存了更改,文件已经被纳入追踪了,但是还未纳入本地仓库的版本控制中。你可以随时add新的更改到index中:

$ git add README.md # add指定文件如README.md
$ git add . # 所有文件

若你觉得这次的更改已经进行的差不多了,则使用commit将暂存的文件提交到本地仓库,生成可以被追踪的版本:

$ git commit

需要编辑此次commit的日志,方便以后查看:
20201014211647
也可以直接通过以下命令实现commit和提交日志:

$ git commit -m "init README"

.gitignore, git status

接下来进行单链表的实现,先加入头文件link_list.h以及实现link_list.cpp,以及测试用例test.cpp:

// link_list.h --单链表头文件
#ifndef LINK_LIST_NODE_H_
#define LINK_LIST_NODE_H_

typedef int DATA_TYPE;

//单链表结点
struct Node {
	DATA_TYPE data;
	Node *next;
};

class LinkList
{
private:
	Node *head;
	int size;
public:
	LinkList();
	~LinkList();
};
#endif
//link_list.cpp --单链表的实现
#include <iostream>
#include"link_list.h"

LinkList::LinkList()
{
	head = new Node;
	head->data = 0;
	head->next = NULL;
	size = 0;
}

LinkList::~LinkList()
{
	delete head;
}
// test.cpp --测试用例
#include "link_list.h"

int main() {
    return 0;
}

如果我们在本地工作区进行编译调试,此时会在文件夹下生成.vscode文件夹,但我们并不希望将此文件夹纳入版本管理,而且我们以后在遇到实际项目时会附带很多的插件以及cmake管理文件等,我们也不希望纳入版本控制,这时我们可以在文件夹下创建.gitignore文件夹:
20201016191344
此时.vscode文件夹就不再纳入版本控制,我们可以使用git status命令查看当前工作区状态,在其他时刻你也可以随时查看工作区状态以便掌握情况:

$ git status

20201016191507
可以看到待add的项目中并没有.vscode。此时就可以add并commit啦。

git diff, 文件恢复

继续实现单链表,添加2个成员函数用来建立和摧毁单链表:

// link_list.h --单链表头文件

...

class LinkList {

...

    int CreateLinkList(int size);
    int BYELinkList();
};

...
//link_list.cpp --单链表的实现
...

using namespace std;

...

int LinkList::CreateLinkList(int n)
{
	if (n<0) {
		printf("error\n");
		return -1;
	}
	Node *ptemp = NULL;
	Node *pnew = NULL;
	
	this->size = n;
	ptemp = this->head;
	for(int i =0 ; i<n ; i++)
	{
		pnew = new Node;
		pnew->next = NULL;
		cout << "输入第" << i+1 << "个节点值" << endl;
		cin >> pnew->data;
		ptemp->next = pnew;
		ptemp = pnew;
	}
	cout << "创建完成" << endl;
	return 0;
}

int LinkList::BYELinkList()
{
	Node *ptemp;
	if (this->head == NULL) {
		cout << "链表原本就为空" << endl;
		return -1;
	}
	while (this->head)
	{
		ptemp = head->next;
		free(head);
		head = ptemp;
	}
	cout << "销毁链表完成" << endl;
	return 0;
}
// test.cpp --测试用例
#include "link_list.h"

int main() {
    LinkList list;
    list.CreateLinkList(5);
    return 0;
}

为了方便地看到改动的地方,我们可以用git diff指令查看改动前后的差异:

$ git diff  #比对所有差异
$ git diff test.cpp #比对特定文件的差异如test.cpp
$ git diff --staged #如果文件已经add到暂存区,则需要用此指令比对staged(即已经纳入暂存区的)文件

20201016195247
若我们做了大量错误的更改,还未add,想快速回到修改前的状态,则我们可以使用git checkout 签出到修改前状态:

$ git checkout -- FILE #一定要加--
$ git checkout -- .

回到了修改前状态:
20201016200658
如果我们已经add,但还未commit,可以先使用下面的命令将暂存的文件移除,再签出:

git reset HEAD filename #取消将文件添加到暂存区

做完修改后,我们就可以add并commit了。可以使用如下带-a的命令对已经纳入版本控制的文件一口气完成add和commit,但新建的文件未纳入版本控制则不能:

$ git commit -am "develop CreateLinkList and BYELinkList"

提交日志,版本回退

git可以让你根据需要回到你想回到的版本,这里用了我们前面多次commit所产生的日志,使用git log查看日志:

$ git log
$ git log --oneline #查看简单版本的日志
$ git log --graph ##以图的形式

20201016201656
HEAD可以理解为实现版本控制的指针,他始终指向当前所在的版本。
使用如下命令回退到需要的版本:

$ git reset --hard HEAD^    #HEAD回退到上一次提交,即回退到上一个版本
$ git reset --hard HEAD^^  #回退两个版本
$ git reset --hard [commit-id]  #上图每次commit前面的字符串即为对应commit-id。到达对应的版本
$ git reset --hard HEAD~2 #回退2个版本

回退一个版本后工作区恢复到上个版本:
20201016203232
此时的log:
20201016203255
但是当前的log无法显示将来的更改,那么怎么回到将来的更改呢,答案是使用git reflog查看所有的日志:
20201016203402
就能根据对应哈希值回到未来的版本。

分支

使用git的分支功能,在分支上所进行的更改不会影响到其他分支,因此分支的引入使得团队协作更为方便。此外主分支的程序在运转中时,可以通过创建分支,在分支上修改bug、增加功能,再并入的主分支的方式防止干扰主分支的运转。

建立分支,切换分支

$ git branch [name]   #创建特定名称的分支
$ git branch  #查看分支
$ git checkout [branch] #切换到某一分支
$ git checkout -b [branch] #建立特定名称的分支并切换到那

建立分支testbranch并checkout到该分支:
20201016203930

git rebase

我们在testbranch分支上继续进行单链表实现。

// link_list.h --单链表头文件
#ifndef LINK_LIST_NODE_H_
#define LINK_LIST_NODE_H_

typedef int DATA_TYPE;

//单链表结点
struct Node {
	DATA_TYPE data;
	Node *next;
};

class LinkList
{
private:
	Node *head;
	int size;
public:
	LinkList();
	~LinkList();
	int CreateLinkList(int size);
	int BYELinkList();
	int TravalLinkList();
	int GetLen();
	bool IsEmply();
};
#endif
//link_list.cpp --单链表的实现
#include <iostream>
#include "link_list.h"

using namespace std;

LinkList::LinkList()
{
	head = new Node;
	head->data = 0;
	head->next = NULL;
	size = 0;
}

LinkList::~LinkList()
{
	delete head;
}

int LinkList::CreateLinkList(int n)
{
	if (n<0) {
		printf("error\n");
		return -1;
	}
	Node *ptemp = NULL;
	Node *pnew = NULL;
	
	this->size = n;
	ptemp = this->head;
	for(int i =0 ; i<n ; i++)
	{
		pnew = new Node;
		pnew->next = NULL;
		cout << "输入第" << i+1 << "个节点值" << endl;
		cin >> pnew->data;
		ptemp->next = pnew;
		ptemp = pnew;
	}
	cout << "创建完成" << endl;
	return 0;
}

int LinkList::BYELinkList()
{
	Node *ptemp;
	if (this->head == NULL) {
		cout << "链表原本就为空" << endl;
		return -1;
	}
	while (this->head)
	{
		ptemp = head->next;
		free(head);
		head = ptemp;
	}
	cout << "销毁链表完成" << endl;
	return 0;
}

int LinkList::TravalLinkList()
{
	Node *ptemp = this->head->next;
	if (this->head == NULL) {
		cout << "链表为空" << endl;
		return -1;
	}
	while(ptemp)
	{
		cout << ptemp->data << "->";
		ptemp = ptemp->next;
	}
	cout <<"NULL"<< endl;
	return 0;
}

int LinkList::GetLen()
{
	return this->size;
}

bool LinkList::IsEmply()
{
	if (this->head == NULL) {
		return true;
	}
	else{
		return false;
	}
}
// test.cpp --测试用例
#include "link_list.h"

int main() {
    LinkList list;
    list.CreateLinkList(5);
    list.TravalLinkList();
    return 0;
}

我们在分支上,每次增加一个成员函数并进行相应更改,都进行一次commit,接着修改了一些代码,再进行commit,因此从建立分支开始总共进行了4次commit:
20201016213024
为了使得我们的日志简单干净,我们常常需要在提交前进行一个叫rebase的操作,该操作可以合并某些提交,使得最终的提交日志更为简洁,更易阅读。

$ git rebase -i [start] [end] #后面一般省略,-i表示--interactive,即弹出交互式的界面让用户编辑

rebase从start指示的地方一直到end指示的地方,我们常常省略end,默认end为当前版本。
我们使用如下指令合并这四次提交:

$ git rebase -i HEAD~4  #注意这里的数字

我们只保留最新的一次提交,将其他几次提交合并到此,因此我们删除前三行提交:
20201016213626
由于我们这几次commit进行了很多了增删,因此文件内容会发生冲突,你可以根据需要决定最终保留什么文本,冲突的指示如下:
20201016214436
解决冲突后,我们要进行一次add,继续rebase:

$ git rebase --continue

20201016214915
rebase成功后commit,完成。

git merge

完成分支上的修改后,我们就可以把分支上的新内容并入主分支main了。
我们切换到main主分支,使用如下指令将testbrain并入:

$ git merge --no-ff testbrain   #no-ff参数表示关闭快速合并

此时查看版本日志,可以看到历史上有支路的产生和合并:
20201016215136

简单的多人开发

现在来模拟一下简单的多人开发。
我们首先使用push命令将我们修改的内容更新到远端仓库:

$ git push

可以看到远端仓库已被更新:
20201016235307

接着我们假设和Bob一起进行单链表的开发,你和Bob分别进行单链表的插入和删除的成员方法的实现和测试。Bob和你一样,在本地进行更新,并将新内容push到远端仓库,并且Bob比你先一步完成,将内容push到GitHub,因此现在的情况是远端仓库的内容领先于本地,此时本地不知道远端仓库已经进行了更新。
待你完成你的任务后,你想将内容push到远端:
20201017002231
Git会提示push失败,原因就是远端领先于本地,你push的内容和远端内容发生了冲突。因此你首先要做的是将最新的内容pull(拉取)到本地,这样你就能在本地尝试解决冲突:

$ git pull

20201017002411
解决完毕后commit,再次push就能成功啦!(我们最后还在本地修复了一些小bug)
日常开发中我们要养成先pull再push的习惯。
至此,整个过程的提交日志如下所显示:
20201017003503

其他

git help

使用如下指令可以获取特定指令的详细说明:

$ git help [指令名如clone]

pull和push详解

实际开发中参与人数众多,分支数量大,版本网络复杂,需要使用更复杂的pull和push指令。
git clone,push,pull,fetch命令详解
实际开发中还会涉及Fork和Pull Request,这里就不讲解了。

一点心得体会

本人以前有简单学习Git,但对于Git的理解始终是模糊的,碰巧这次孟宁老师亲自教授Git的使用,因此这一次也一鼓作气好好地学习了一下Git并写下这篇Blog,才发现一旦学会了Git的指令和原理,会对实际开发产生巨大的帮助。我们能够把更多的精力集中在代码本身而不是琐碎的版本控制上,帮助我们成倍地提升效率。
接下来还需要结合实际中的项目,对Git的多人协作版本控制进行更深入的了解才行。

其他参考资料

孟宁老师通过五大场景深入浅出讲解Git
万门大学视频讲解Git
Pro Git中文手册
GitHub入门与实践电子书
本次示例所用仓库

分类:

技术点:

相关文章: