Git——Git应用入门
将会介绍以下知识:
- 搭建Git环境和创建Git版本库(init、clone)。
- 文件添加、状态检查、创建注释和查看历史记录。
- 与其他Git版本库交互(pull、push)。
- 解决合并冲突。
- 创建分支列表、列表切换和合并。
- 创建标签。
1、版本控制与Git
版本控制系统(有时也称修订控制)是一种用户可以根据时间追溯项目文件(存放于版本库中)修改历史和属性的工具,它还可以帮助团队成员协作开发。当前流行的版本控制系统可以为每个开发人员提供专属的沙箱,防止他们的工作发生冲突,同时采用冲突合并和同步机制,实现以非阻塞的方式进行高效协作。
像Git这类分布式版本控制系统为每个用户提供专属于其自己的项目历史副本、版本库的副本。Git系统如此高效的原因有以下几个:
- 首先,几乎所有操作都是在用户本机上执行,而且非常灵活;
- 其次,你可以使用多种方式建立版本仓库。版本仓库对于开发来说意味着每个开发人员都有整个项目文件的独立工作区(也称工作目录)。
- Git采用的分支模型支持本地分支,而且分支的发布也非常灵活,用户可以使用分支进行内容切换,还可以在开发过程中将不同工作放置于相互隔离的沙箱中(有可能构建出独立、灵活的主题分支工作流)。
事实上,版本库的整个变更历史都是可以访问的,用户可以撤销或者回退更改过的内容到最后一个工作版本等。当每个修改被提交之后,用户的修改提交记录也被记录下来,因此提交代码修改的用户也就很容易被定位。你还可以比较文件的不同版本,将代码回退到某个用户提交bug报告之前的版本,甚至可以知道哪个版本的变更导致了上述bug。其实,Git主要是通过reflog命令来跟踪分支的变更记录信息并实现回退和覆盖目的的。
Git有一个独特的功能是它支持显式访问暂存区以便创建注释(对项目进行新的修订)。这为用户管理工作区和确定将来的注释信息带来了更多灵活性。
所有这些灵活、强大的特性都是要付出代价的。虽然掌握Git的基本使用非常简单,但是精通Git的使用并不是那么容易。让我们先来回顾一下Git的基本使用。
2、Git简易示例
让我们通过两个开发人员在一个简单项目上使用Git进行协作开发来一步一步地构建一个简单示例。
2.1、创建版本库
某公司准备研发一款新产品。该产品主要的用途是从特定区间内随机获取若干个整数。
该公司指派了两名开发人员负责这个新项目,他们的名字分别是Alice和Bob。两名远程办公的开发人员经过和公司领导协商之后,决定使用C语言开发一套命令行应用来完成该产品的研发,并且使用Git 2.5.0(http://git-scm.com/)进行程序代码版本控制管理。该产品的用途主要是用来进行过程演示的,而且也非常简单。程序代码的细节并不是重点,我们关注的是如何管理代码变更。
团队规模很小,因此他们决定以下图所示的组织结构启动项目:
2.2、创建Git版本库
Alice在项目启动时请求管理员Carol为她创建一个新的版本库以方便团队协作。
命令行示例遵循的是UNIX系统风格,命令行前面的提示信息由“用户名@主机名/文件目录”组成,这样一眼就可以看出由谁执行该命令,属于哪台计算机以及文件目录是什么。上述风格在UNIX环境中很常见(在Linux系统下也是如此)。
carol@server ~$ mkdir -p /srv/git
carol@server ~$ cd /srv/git
carol@server /srv/git$ git init --bare random.git
你还可以使用工具来管理Git的版本库(例如Gitolite),在服务端创建一个版本库也许看上去会稍有不同。通常情况下是使用git init(不带“–bare”参数)命令创建版本库,然后根据特定的URL地址将其推送到服务端,执行上述操作之后,服务端会自动创建一个公共版本库。或者也可以使用带Web接口工具的网站创建该版本库,例如GitHub、Bitbucket和GitLab(可以托管或内部部署)。
2.3、克隆版本库并添加注释
Bob知道项目版本库就绪的消息之后,就开始了编写代码的工作。
因为这是他在本项目中首次使用Git,因此,他在自己的版本库根目录下建立了对应的~/.gitconfig文件,该文件主要是用来帮助他标记日志文件中特定的注释信息:
[user]name = Bob Hackeremail = bob@company.com
现在他需要获取自己的版本库实例:
bob@hostB ~$ git clone https://git.company.com/random
Cloning into random...
Warning: You appear to have cloned an empty repository.
done.
bob@hostB ~$ cd random
bob@hostB random$
Bob注意到Git系统提示说这是一个空的版本库,还没有代码文件,所以他开始编写代码了。他打开了文本编辑器,为本项目创建了第一版代码程序:
#include <stdio.h>
#include <stdlib.h>int random_int(int max)
{return rand() % max;
}int main(int argc, char *argv[])
{if (argc != 2) {fprintf(stderr, "Usage: %s <number>\n", argv[0]);return EXIT_FAILURE;}int max = atoi(argv[1]);int result = random_int(max);printf("%d\n", result);return EXIT_SUCCESS;
}
一般来说,和大部分原始程序实现类似,该程序还非常简陋。不过这是一个很好的开端。在提交代码之前,Bob希望这个程序可以通过编译并能够运行:
bob@hostB random$ gcc –std=c99 random.c
bob@hostB random$ ls –l
total 43
-rwxr-xr-x 1 bob staff 86139 May 29 17:36 a.out
-rw-r--r-- 1 bob staff 331 May 19 17:11 random.c
bob@hostB random$ ./a.out
Usage: ./a.out <number>
bob@hostB random$ ./a.out 10
1
好的!现在把该文件添加到版本库中:
bob@hostB random$ git add random.c
Bob使用“status”命令来确保先前的所有修改都没什么问题:
bob@hostB random$ git status –s
A random.c
?? a.out
Git系统显示了警告信息,因为它不知道该如何处理a.out文件:它既没有在跟踪列表中,也没有在忽略列表中。它是编译过程中生成的可执行体,不应该存放于版本库中。Bob可以暂时不用理会这个提示信息。
现在,向服务端提交(commit)该代码文件:
bob@hostB random$ git commit –a –m "Initial implementation"
[master (root-commit) 2b953b4] Initial implementation
1 file changed, 22 insertions(+)
Create mode 100644 random.c
一般来说,在添加注释信息时,不仅可以使用-m <信息>命令行选项,还可以让Git打开一个文本编辑器完成相关操作。
2.4、发布修改
在完成项目的初始版本之后,Bob准备发布它们(提交到服务器,供团队其他成员访问)。他将自己的工作成果推送到了服务端:
bob@hostB random$ git push
warning: push.default is unset; its implicit value has changed in
Git 2.0 from 'matching' to 'simple'. To squelch this message [...]
To https://git.company.com/random* [new branch] master -> master
bob@hostB random$ git config --global push.default simple
Git会根据用户的网络速度,显示诸如clone、push和fetch操作的具体进度。
2.5、查看历史版本
因为这是Alice第一次在她的个人电脑上使用Git,因此,她必须告诉Git系统如何识别她提交的注释:
alice@hostA ~$ git config --global user.name "Alice Developer"
alice@hostA ~$ git config --global user.email alice@company.com
现在Alice需要建立专属于她自己的版本库实例:
alice@hostA ~$ git clone https://git.company.com/random
Cloning into random...
done.
Alice打算查看一下工作目录:
alice@hostA ~$ cd random
alice@hostA random$ ls –al
total 1
drwxr-xr-x 1 alice staff 0 May 30 16:44 .
drwxr-xr-x 4 alice staff 0 May 30 16:39 ..
drwxr-xr-x 1 alice staff 0 May 30 16:39 .git
-rw-r--r-- 1 alice staff 353 May 30 16:39 random.c
.git目录下包含Alice的版本库的拷贝(克隆),并且这些文件是以Git内部格式存在的,同时还包含一些针对版本库的管理信息。
她希望查看日志的细节信息(查看项目历史记录):
alice@hostA random$ git log
commit 2b953b4e80abfb77bdcd94e74dedeeebf6aba870
Author: Bob Hacker <bob@company.com>
Date: Thu May 29 19:53:54 2015 +0200Initial implementation
在最底层实现中,Git历史版本识别是通过一个SHA-1哈希码实现的,例如2b953b4e80。Git支持多种形式的版本查询,其中就包括SHA-1码精确匹配(最少提供4个字符)。
当Alice决定浏览一遍代码时,她突然发现了一个严重的问题:随机数生成部分一直都没有初始化!她经过一个快速测试发现程序生成的结果都是同一个数。幸运的是,她并不需要修改main()内部的代码,只需要在顶部加入相应的#include引用即可:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>int random_int(int max)
{return rand() % max;
}int main(int argc, char *argv[])
{if (argc != 2) {fprintf(stderr, "Usage: %s <number>\n", argv[0]);return EXIT_FAILURE;}int max = atoi(argv[1]);srand(time(NULL));int result = random_int(max);printf("%d\n", result);return EXIT_SUCCESS;
}
改完代码之后,她运行了几次程序,来确认程序真的可以生成随机数。一切似乎都进行得很顺利。她使用git status命令查看了之前的文件变更:
alice@hostA random$ git status –sM random.c
不必感到大惊小怪。Git知道random.c文件被修改了。然后Alice使用git diff命令再次确认对代码的修改:
alice@hostA random$ git diff
diff --git a/random.c b/random.c
index cc09a47..5e095ce 100644
--- a/random.c
+++ b/random.c
@@ -1,5 +1,6 @@#include <stdio.h>#include <stdlib.h>
+#include <time.h>int random_int(int max)
{
@@ -15,6 +16,7 @@ int main(int argc, char *argv[])int max = atoi(argv[1]);+ srand(time(NULL));int result = random_int(max);printf("%d\n", result);
现在,可以提交变更,然后将它们发布到公共版本库了:
alice@hostA random$ git commit -a -m "Initialize random number generator"
[master db23d0e] Initialize random number generator1 file changed, 2 insertions(+)
alice@hostA random$ git push
To https://git.company.com/random3b16f17..db23d0e master -> masterRenaming and moving files
2.6、重命名、移动文件
Bob接下来的工作是重构工作区目录。他不希望版本库的顶层目录文件太多,所以他决定将所有源代码文件移动到“src/”子目录下:
bob@hostA random$ mkdir src
bob@hostA random$ git mv random.c src/
bob@hostA random$ git status –s
R random.c -> src/random.c
bob@hostA random$ git commit –a –m "Directory structure"
[master 69e0d3d] Directory structure1 file changed, 0 insertions(+), 0 deletions(-)rename random.c => src/random.c (100%)
现在,他为了确保目录重构之后使用“diff”命令输出结果的差异不至于太大,将Git系统配置为始终执行重命名和拷贝的检测:
bob@hostB random$ git config --global diff.renames copies
Bob觉得是时候为项目添加一个合适的Makefile配置文件,以及一个README帮助文件了:
bob@hostA random$ git add README Makefile
bob@hostA random$ git status –s
A Makefile
A README
bob@hostA random$ git commit -a -m "Added Makefile and README"
[master abfeea4] Added Makefile and README
2 files changed, 15 insertions(+)
create mode 100644 Makefile
create mode 100644 README
Bob将“random.c”文件的名字改为“rand.c”:
bob@hostA random$ git mv src/random.c src/rand.c
上述操作当然也需要修改Makefile文件:
bob@hostA random$ git status –sM Makefile
R src/random.c -> src/rand.c
然后,他提交了这些修改。
2.7、更新版本库(合并)
项目文件重组完成之后,Bob打算将这些变更推送到服务端:
bob@hostA random$ git push
$ git push
To https://git.company.com/random! [rejected] master -> master (fetch first)
error: failed to push some refs to 'https://git.company.com/random'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository
pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for
details.
但是Alice同时也在这个项目中工作,而且她已经向服务端推送了自己的代码。Git系统现在不允许Bob推送他对项目代码的变更,因为Alice已经推送了一些内容到master分支上,系统会保护她提交的变更。
Bob使用“pull”命令将服务端版本库的内容与自己的版本库同步(像命令行提示信息建议的那样):
bob@hostB random $ git pull
From https://git.company.com/random+ 3b16f17...db23d0e master -> origin/master
Auto-merging src/rand.c
Merge made by the 'recursive' strategy.src/rand.c | 2 ++1 file changed, 2 insertions(+)
执行“pull”命令之后,Git系统会将服务端版本库中的变更下载到Bob本机,然后自动将它们和Bob本机版本库的变更合并,最后把合并后的变更提交到本机版本库中。
现在万事俱备了:
bob@hostB random$ git show
commit ba5807e44d75285244e1d2eacb1c10cbc5cf3935
Merge: 3b16f17 db23d0e
Author: Bob Hacker <bob@company.com>
Date: Sat May 31 20:43:42 2015 +0200Merge branch 'master' of https://git.company.com/random
合并后的提交也完成了。Git系统可以直接将Alice提交的变更与Bob移动或重命名后的文件合并,是不是很神奇呢?
Bob检查编译(因为自动合并之后并不能绝对保证代码没问题)了一下变更合并后的代码,准备将合并变更后的代码推送到服务端:
bob@hostB random$ git push
To https://git.company.com/randomdb23d0e..ba5807e master -> master
2.8、创建标签
Alice和Bob认为项目可以进行更大范围的发布了。Bob创建了一个标签(tag),以便日后他们方便地访问/引用发布过的预览版本。他为此使用了一个带注释的标签,当然大家一般采用的替代性方案是使用带数字签名的标签,该标签通常会包含一个PGP数字签名(以后的验证需要用到它):
bob@hostB random$ git tag -a -m "random v0.1" v0.1
bob@hostB random$ git tag --list
v0.1
bob@hostB random$ git log -1 --decorate --abbrev-commit
commit ba5807e (HEAD -> master, tag: v0.1, origin/master)
Merge: 3b16f17 db23d0e
Author: Bob Hacker <bob@company.com>
Date: Sat May 31 20:43:42 2015 +0200Merge branch 'master' of https://git.company.com/random
当然,v0.1版的标签如果只放在Bob本地的版本库中是没有什么意义的。接下来他将刚创建的标签推送到服务端:
bob@hostB random$ git push origin tag v0.1
Counting objects: 1, done.
Writing objects: 100% (1/1), 162 bytes, done.
Total 1 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (1/1), done.
To https://git.company.com/random
* [new tag] v0.1 -> v0.1
Alice为了获得这个标签,更新了她的版本库,然后开始了她的日常工作:
alice@hostA random$ git pull
From https://git.company.com/randomf4d9753..be08dee master -> origin/master* [new tag] v0.1 -> v0.1
Updating f4d9753..be08dee
Fast-forwardMakefile | 11 +++++++++++README | 4 ++++random.c => src/rand.c | 03 files changed, 15 insertions(+)create mode 100644 Makefilecreate mode 100644 READMErename random.c => src/rand.c (100%)
2.9、解决合并冲突
Alice认为将生成伪随机数功能修改成一个单独的子程序是个好主意。这样一来,初始化和生成随机数的功能都独立封装起来了,将来需求变更的时候更容易实现一些。她决定给程序添加一个init_rand()函数:
void init_rand(void)
{srand(time(NULL));
}
接下来,编译运行一下代码,看看有没有什么问题:
alice@hostA random$ make
gcc -std=c99 -Wall -Wextra -o rand src/rand.c
alice@hostA random$ ls -F
Makefile rand* README src/
通过编译,程序没什么问题之后就可以提交变更了:
alice@hostA random$ git status –sM src/rand.c
alice@hostA random$ git commit -a -m "Abstract RNG initialization"
[master 26f8e35] Abstract RNG initialization1 files changed, 6 insertions(+), 1 deletion(-)
从提示信息可以看到,提交成功了。
同时,Bob在与rand()函数相关的开发文档中发现,它是用来生成简单的伪随机数的标准函数,可能并不能满足实际需要:
bob@hostB random$ git pull
Already up-to-date.
他决定在提交代码变更的注释中添加对此问题的备注说明:
bob@hostB random$ git status –sM src/rand.c
bob@hostB random$ git diff
diff --git a/src/rand.c b/src/rand.c
index 5e095ce..8fddf5d 100644
--- a/src/rand.c
+++ b/src/rand.c
@@ -2,6 +2,7 @@#include <stdlib.h>#include <time.h>+// TODO: use a better random generatorint random_int(int max){return rand() % max;
他提交代码的变更之后,将它们推送到了服务端:
bob@hostB random$ git commit -m 'Add TODO comment for random_int()'
[master 8c4ceca] Use Add TODO comment for random_int()1 files changed, 1 insertion(+)
bob@hostB random$ git push
To https://git.company.com/randomba5807e..8c4ceca master -> master
因此,当Alice准备推送她的变更到服务端时,Git系统拒绝了该操作:
alice@hostA random$ git push
To https://git.company.com/random! [rejected] master -> master (non-fast-forward)
error: failed to push some refs to 'https://git.company.com/random'
[...]
Bob一定是推送了不少变更到服务端。Alice需要再次从服务端的版本库上下载最新版本的项目文件,然后亲自把自己的变更和Bob的变更合并:
alice@hostA random$ git pull
From https://git.company.com/randomba5807e..8c4ceca master -> origin/master
Auto-merging src/rand.c
CONFLICT (content): Merge conflict in src/rand.c
Automatic merge failed; fix conflicts and then commit the result.
该合并操作并没有像上次那样被顺利执行。Git系统无法自动合并Alice和Bob两人提交的变更。显然,文件变更之间有冲突。Alice决定用文本编辑器打开文件“src/rand.c”一探究竟(她也可以使用图形化的合并工具查看代码差异):
<<<<<<< HEAD
void init_rand(void)
{srand(time(NULL));
}=======
// TODO: use a better random generator
>>>>>>> 8c4ceca59d7402fb24a672c624b7ad816cf04e08
int random_int(int max)
Git系统中现在既有Alice提交的代码变更(在<<<<<<<<HEAD和======== 冲突标记之间),也有Bob提交的代码变更(在========和 >>>>>>>>之间)。我们希望的结果是将两人的代码融为一体。Git系统无法自动合并它们,因为这些代码块并不是独立的。Alice的init_rand()函数可以简单地插入Bob添加的代码注释之前。执行上述操作之后,结果如下:
alice@hostA random$ git diff
diff --cc src/rand.c
index 17ad8ea,8fddf5d..0000000
--- a/src/rand.c
+++ b/src/rand.c
@@@ -2,11 -2,7 +2,12 @@@#include <stdlib.h>#include <time.h>+void init_rand(void)+{+ srand(time(NULL));+}++ // TODO: use a better random generatorint random_int(int max){return rand() % max;
这样冲突应该就可以解决了。Alice重新编译运行了一下程序,然后提交了这一变更:
alice@hostA random$ git status –s
UU src/rand.c
alice@hostA random$ git commit -a -m 'Merge: init_rand() + TODO'
[master 493e222] Merge: init_rand() + TODO
然后她尝试将该变更推送到服务端版本库:
alice@hostA random$ git push
To https://git.company.com/random8c4ceca..493e222 master -> master
大功告成!
2.10、添加和移除文件
Bob打算给项目添加一个和版权声明有关的COPYRIGHT文件,当然还打算添加一个记录软件新特性的文件(不过还没有创建),所以他使用批处理命令添加了工作区中的所有文件到版本库中:
bob@hostB random$ git add –v
add 'COPYRIGHT'
add 'COPYRIGHT~'
因为Bob没有配置的忽略模式,因此作为备份文件的“COPYRIGHT~”也被提交到了版本库。接下来我们移除该文件:
bob@hostB random$ git status -s
A COPYRIGHT
A COPYRIGHT~
bob@hostB random$ git rm COPYRIGHT~
error: 'COPYRIGHT~' has changes staged in the index
(use --cached to keep the file, or -f to force removal)
bob@hostB random$ git rm -f COPYRIGHT~
rm 'COPYRIGHT~'
检查一下文件状态,然后提交变更:
bob@hostB random$ git status –s
A COPYRIGHT
bob@hostB random$ git commit -a -m 'Added COPYRIGHT'
[master ca3cdd6] Added COPYRIGHT1 files changed, 2 insertions(+), 0 deletions(-)create mode 100644 COPYRIGHT
2.11、撤销对单个文件的修改
百无聊赖之际,Bob决定调整一下文件rand.c的代码缩进格式,使之符合统一的命名规范。
bob@hostB random$ indent src/rand.c
他统计了一下该文件源代码的变更记录:
bob@hostB random$ git diff --statsrc/rand.c | 40 ++++++++++++++++++++++------------------1 files changed, 22 insertions(+), 18 deletions(-)
样式调整的变更太多了(对于如此小的文件来说),在合并时可能会出问题。Bob冷静了一下,然后撤销了对文件rand.c的样式调整:
bob@hostB random$ git status –sM src/rand.c
bob@hostB random$ git checkout -- src/rand.c
bob@hostB random$ git status -s
如果你不记得如何回退一个特定类型的变更或者更新某个已提交的变更(使用不带“-a”参数的命令“git commit”),执行“git status”命令(不带“-s”参数)后的输出结果会包含如下所示的帮助信息:
bob@hostB random$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
#
# 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: src/rand.c
2.12、创建新分支
Alice注意到代码中取模运算返回给定区间内随机数的分布并不是均匀的,因为大部分情况下返回的都是较小的数。她打算修复这个问题。为了将相关的开发工作和代码中的其他变更隔离,她决定在自己的版本库中创建一个新分支,然后切换到该分支:
alice@hostA random$ git checkout -b better-random
Switched to a new branch 'better-random'
alice@hostA random$ git branch
* better-randommaster
除了使用“git checkout –b better-random”命令创建一个新分支,然后使用命令切换到该分支之外,她还可以首先使用“git branch better-random”命令创建一个新分支,然后使用“git checkout better-random”命令切换到该分支。
她决定使用RAND_MAX常量控制rand()函数生成随机数的范围。相关的代码修改如下:
alice@hostA random$ git diff
diff --git a/src/rand.c b/src/rand.c
index 2125b0d..5ded9bb 100644
--- a/src/rand.c
+++ b/src/rand.c
@@ -10,7 +10,7 @@ void init_rand(void)// TODO: use a better random generatorint random_int(int max){
- return rand() % max;
+ return rand()*max / RAND_MAX;}int main(int argc, char *argv[])
她提交了上述代码变更,然后将它们推送到了服务端,她知道上述推送操作可以顺利执行,因为这些操作都是在她的私有分支上进行的:
alice@hostA random$ git commit -a -m 'random_int: use rescaling'
[better-random bb71a80] random_int: use rescaling1 files changed, 1 insertion(+), 1 deletion(-)
alice@hostA random$ git push
fatal: The current branch better-random has no upstream branch.
To push the current branch and set the remote as upstream, usegit push --set-upstream origin better-random
通过上述信息可以知道,Git系统希望Alice为她新创建的分支(它采用的推送策略是simple模式)在远程版本库中添加对应的上游分支,这样可以让分支推送到远程分支的目标更明确。
alice@hostA random$ git push --set-upstream origin better-random
To https://git.company.com/random* [new branch] better-random -> better-random
如果她希望更直观地管理她自己的分支结构并且只对自己可见,那么她需要配置好与服务端的相关映射,或者使用诸如Gitolite之类Git版本库管理软件来管理自己的分支。
2.13、合并分支(无冲突)
与此同时,在默认的主分支下,Bob打算推送自己给项目添加COPYRIGHT文件的变更:
bob@hostB random$ git push
To https://git.company.com/random! [rejected] master -> master (non-fast-forward)
[…]
出现上述错误提示是因为Alice当时正在忙着将初始化生成随机数的部分代码封装为一个子程序(解决合并冲突),她首先向服务端版本库推送了自己的变更:
bob@hostB random$ git pull
From https://git.company.com/random8c4ceca..493e222 master -> origin/master* [new branch] better-random -> origin/better-random
Merge made by 'recursive' strategy.src/rand.c | 7 ++++++-1 file changed, 6 insertions(+), 1 deletion(-)
Git系统可以轻松地将Alice推送的变更合并,但是现在出现了一个新分支。接下来我们来看看具体细节。为了节省篇幅,下文只显示和该新分支“better- random”相关的内容:
bob@hostB random$ git log HEAD..origin/better-random
commit bb71a804f9686c4bada861b3fcd3cfb5600d2a47
Author: Alice Developer <alice@company.com>Date: Sun Jun 1 03:02:09 2015 +0200random_int: use rescaling
有趣的是,Bob希望从服务端获取Alice创建的新分支然后合并到自己的版本库的默认分支下(该新分支在远程版本库上已经存在):
bob@hostB random$ git merge origin/better-random
Merge made by the 'recursive' strategy.src/rand.c | 2 +-1 file changed, 1 insertion(+), 1 deletion(-)
2.14、撤销未发布的合并
Bob意识到何时将该特性添加到主分支中应该由Alice来做决定。他打算撤销上述合并操作。因为这些变更还没有发布,因此只需要简单地将自己的本地主分支回退到上一次提交的变更状态即可:
bob@hostB random$ $ git reset --hard @{1}
HEAD is now at 3915cef Merge branch 'master' of https://git.company.com/
random
本示例演示了使用日志引用(reflog)机制实现变更回退操作。另外一种解决方案是使用“HEAD^”代替“@{1}”,这样也可以实现同样的效果。
相关文章:

Git——Git应用入门
将会介绍以下知识: 搭建Git环境和创建Git版本库(init、clone)。文件添加、状态检查、创建注释和查看历史记录。与其他Git版本库交互(pull、push)。解决合并冲突。创建分支列表、列表切换和合并。创建标签。 1、版本控…...
【SpringBoot】Redisson 分布式锁注解和 @Transactional 注解一起使用问题
一、前言 平时使用切面去加分布式锁,是先开启事务还是先尝试获得锁?这两者有啥区别? 业务中怎么控制切面的顺序?切面的顺序对事务的影响怎么避免? 下面程序分析: OverrideTransactionalpublic ReceiveH5…...
Druid数据库连接池框架
1.Druid概述 Druid 是一个开源的数据库连接池框架,用于管理和优化数据库连接的使用。它提供了高效的、可扩展的连接池管理,可以用于 Java 应用程序连接到关系型数据库。 之前有了解过 C3P0 数据库连接池,所谓数据库连接池就是重复利用连接数据…...
Python项目打包
Python项目如何打包? 本指南总结了Python项目打包的最佳实践,主要涉及代码的打包和分发,以及环境和依赖的管理。 0. 一般项目清单 源代码(可使用git托管)数据包(可使用DVC托管)Docker环境镜像…...

ASUS(华硕) B760M-AYW WIFI D4_解决wifi不能使用
1、最近新购买了一套 diy电脑主机,选用的是 ASUS B760M-AYW WIFI D4电脑主板 win10 系统,到货后 发现右下角电脑图标处及网络适配器中 没有wifi选项 首先 在官网和旗舰店客服处,确认了 该主板 有集成wifi模块,鲨鱼鳍天线未安装…...

Postgresql数据库运维统计信息
如果需要使用以下运维信息,需要如下几步 修改postgresql.conf文件 #shared_preload_libraries # (change requires restart)shared_preload_libraries pg_stat_statements重启数据库创建扩展 CREATE EXTENSION IF NOT EXISTS pg_stat_statements;1. 统计信息…...

Python3基础
导包 在 python 用 import 或者 from...import 来导入相应的模块。 将整个模块(somemodule)导入,格式为: import somemodule 从某个模块中导入某个函数,格式为: from somemodule import somefunction 从某个模块中导入多个函数,格式为&#…...

【性能测试】服务器常用的性能指标总结,一文概全...
目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 压测过程中&#…...
Vue学习笔记-Vuex基本使用
基本使用 初始化数据、配置actions、mutations,操作文件/store/index.js //index.js文件用于创建Vuex中最为核心的store对象 import Vue from vue import Vuex from vuex Vue.use(Vuex) //actions对象用于响应组件中的动作,专门负责业务逻辑 const actions {//函数…...

vue3中的customRef创建一个自定义的 ref对象
customRef 创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制 小案例: 自定义 ref 实现 debounce <template><div style"font-size: 14px;"><input v-model"text" placeholder"搜索关键字"/><…...

动态规划学习——子序列问题
目录 编辑 一,最长定差子序列 1.题目 2,题目接口 3,解题思路及其代码 一,最长定差子序列 1.题目 给你一个整数数组 arr 和一个整数 difference,请你找出并返回 arr 中最长等差子序列的长度,该子序列…...
使用 COPY 加速 PostgreSQL 批量插入
文章目录 1.copy命令介紹2.copy vs insert的优势3.测量性能4.结论 1.copy命令介紹 PostgreSQL 中的命令COPY是执行批量插入和数据迁移的强大工具。它允许快速有效地将大量数据插入表中。 COPY命令为批量插入和数据迁移提供了更简单且更具成本效益的解决方案。 可以避免使用诸…...

plotneuralnet和netron结合绘制模型架构图
plotneuralnet和netron结合绘制模型架构图 一、plotneuralnet 本身的操作 模型结构图的可视化,能直观展示模型的结构以及各个模块之间的关系。最近借助plotneuralnet python库(windows版)绘制了一个网络结构图,有一些经验和心得…...
MYSQL 中如何导出数据?
文章目录 前言MySQL 导出数据使用 SELECT ... INTO OUTFILE 语句导出数据SELECT ... INTO OUTFILE 语句有以下属性:导出表作为原始数据导出SQL格式的数据将数据表及数据库拷贝至其他主机 后言 前言 hello world欢迎来到前端的新世界 😜当前文章系列专栏:…...

GPT-4惨遭削弱,偷懒摸鱼绝不多写一行代码,OpenAI已介入调查
GPT-4再次遭网友“群攻”,原因是“懒”得离谱! 有网友想在Android系统开发一个能够与OpenAI API实时交互的应用。 于是把方法示例链接发给GPT-4,让它参考用Kotlin语言编写代码: 没成想,和GPT-4一来二去沟通半天,GPT-4死活给不出…...

CSS特效020:涌动的弹簧效果
CSS常用示例100专栏目录 本专栏记录的是经常使用的CSS示例与技巧,主要包含CSS布局,CSS特效,CSS花边信息三部分内容。其中CSS布局主要是列出一些常用的CSS布局信息点,CSS特效主要是一些动画示例,CSS花边是描述了一些CSS…...
系列五、Spring整合MyBatis不忽略mapper接口同目录的xxxMapper.xml
一、概述 默认情况下maven要求我们将xml配置、properties配置等都放在resources目录下,如果我们强行将其放在java目录,即将xxxMapper.xml和xxxMapper接口放在同一个目录下,那么默认情况下maven打包时会将这个xxxMapper.xml文件忽略掉…...
第454题.四数相加II
力扣题目链接 给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足: 0 < i, j, k, l < nnums1[i] nums2[j] nums3[k] nums4[l] 0 分析: 当需要判断一个元素是…...

RabbitMQ消息队列
简介 MQ(message queue),从字面意思上看就个 FIFO 先入先出的队列,只不过队列中存放的内容是 message 而已,它是一种具有接收数据、存储数据、发送数据等功能的技术服务。 作用:流量削峰、应用解耦、异步处理。 生产者将消息发送…...

ModBus电表与RS485电表有哪些区别?
在能源计量领域,ModBus电表和RS485电表是两种常见的设备,它们都具有监测和记录电能数据的功能。然而,它们之间存在一些区别,比如通信协议、连接方式、数据格式等等参数的区别有哪些? ModBus电表和RS485电表都是用于电能…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...

Reasoning over Uncertain Text by Generative Large Language Models
https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...

2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...