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 Hacker
  email = 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 +0200

    Initial 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 –s
 M 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 generator
 1 file changed, 2 insertions(+)
alice@hostA random$ git push
To https://git.company.com/random
   3b16f17..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 structure
 1 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 –s
 M 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 +0200

    Merge branch 'master' of https://git.company.com/random

合并后的提交也完成了。Git系统可以直接将Alice提交的变更与Bob移动或重命名后的文件合并,是不是很神奇呢?

Bob检查编译(因为自动合并之后并不能绝对保证代码没问题)了一下变更合并后的代码,准备将合并变更后的代码推送到服务端:

bob@hostB random$ git push
To https://git.company.com/random
   db23d0e..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 +0200

    Merge 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/random
   f4d9753..be08dee  master     -> origin/master
 * [new tag]         v0.1       -> v0.1
Updating  f4d9753..be08dee
Fast-forward
 Makefile               | 11 +++++++++++
 README                 |  4 ++++
 random.c => src/rand.c |  0
 3 files changed, 15 insertions(+)
 create mode 100644 Makefile
 create mode 100644 README
 rename 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 –s
 M src/rand.c
alice@hostA random$ git commit -a -m "Abstract RNG initialization"
[master 26f8e35] Abstract RNG initialization
 1 files changed, 6 insertions(+), 1 deletion(-)

从提示信息可以看到,提交成功了。

同时,Bob在与rand()函数相关的开发文档中发现,它是用来生成简单的伪随机数的标准函数,可能并不能满足实际需要:

bob@hostB random$ git pull
Already up-to-date.

他决定在提交代码变更的注释中添加对此问题的备注说明:

bob@hostB random$ git status –s
 M 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 generator
 int 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/random
   ba5807e..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/random
   ba5807e..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 generator
  int 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/random
   8c4ceca..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 COPYRIGHT
 1 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 --stat
 src/rand.c |    40 ++++++++++++++++++++++------------------
 1 files changed, 22 insertions(+), 18 deletions(-)

样式调整的变更太多了(对于如此小的文件来说),在合并时可能会出问题。Bob冷静了一下,然后撤销了对文件rand.c的样式调整:

bob@hostB random$ git status –s
 M 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-random
  master

除了使用“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 generator
 int 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 rescaling
 1 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, use

   git 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/random
   8c4ceca..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 +0200

    random_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}”,这样也可以实现同样的效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/195541.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

sqli-labs靶场详解(less17-less22)

目录 less-17 less-18 less-19 less-20 less-21 less-22 less-17 修改密码关卡 服务器后端 账号密码都存在数据库中 使用UPDATE进行修改密码 尝试username处 尝试好久尝试不出来应该是对用户名进行了过滤 于是对password进行注入 判断注入点 passwdadmin 报错&#xff1a…

vue3中的customRef创建一个自定义的 ref对象

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

智能优化算法应用:基于郊狼算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于郊狼算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于郊狼算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.郊狼算法4.实验参数设定5.算法结果6.参考文献7.MATLAB…

深思:C与C++相互调用问题

背景 上周&#xff0c;偶然看到同事愁眉苦脸的样子&#xff0c;便善意咨询了下发生了什么。简单沟通下&#xff0c;才知道他遇到了一个工程编译的问题&#xff0c;一直无法编译通过&#xff0c;困扰了他快一天时间。出于个人的求知欲和知识的渴望&#xff0c;我便主动与他一同分…

PHP中关于func_get_args()方法

首先呢这个函数出现的是比较早的,大致应该是PHP4出现的, func_get_args — 返回一个包含函数参数列表的数组 说明 func_get_args(): array 获取函数参数列表的数组。 该函数可以配合 func_get_arg() 和 func_num_args() 一起使用&#xff0c;从而使得用户自定义函数可以接…

基于C#实现块状链表

在数据结构的世界里&#xff0c;我们会认识各种各样的数据结构&#xff0c;每一种数据结构都能解决相应领域的问题&#xff0c;当然每个数据结构&#xff0c;有他的优点&#xff0c;必然就有它的缺点&#xff0c;那么如何创造一种数据结构来将某两种数据结构进行扬长避短&#…

【数据结构/C++】栈和队列_循环队列

牺牲一个存储单元来判断队满。 #include<iostream> using namespace std; // 循环队列 #define MaxSize 10 typedef int ElemType; typedef struct {ElemType data[MaxSize];int front, rear; } SqQueue; // 初始化队列 void InitQueue(SqQueue &Q) {// 判断队空 Q.…

微服务知识大杂烩

1.什么是微服务? 微服务(Microservices)是一种软件架构风格,将一个大型应用程序划分为一组小型、自治且松耦合的服务。每个微服务负责执行特定的业务功能,并通过轻量级通信机制(如HTTP)相互协作。每个微服务可以独立开发、部署和扩展,使得应用程序更加灵活、可伸缩和可…

Milvus入门手册1.0

一、window环境搭建&#xff08;单机&#xff09; 1、docker安装 略 2、milvus安装 参考文档&#xff1a;https://milvus.io/docs/install_standalone-docker.md tips: &#xff08;1&#xff09;compose.yaml下载比较慢&#xff0c;可以在网络上找一份。 &#xff08;2&…

2023-11-26 LeetCode每日一题(统计子串中的唯一字符)

2023-11-26每日一题 一、题目编号 828. 统计子串中的唯一字符二、题目链接 点击跳转到题目位置 三、题目描述 我们定义了一个函数 countUniqueChars(s) 来统计字符串 s 中的唯一字符&#xff0c;并返回唯一字符的个数。 例如&#xff1a;s “LEETCODE” &#xff0c;则其…

Python字典合并

合并两个有部分key相同的字典&#xff0c;相同key保留两个字典中对应key的较大值。 (笔记模板由python脚本于2023年11月27日 18:12:15创建&#xff0c;本篇笔记适合熟悉Python字典的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Fr…

通过预定义颜色查找表上色_vtkLookupTable_vtkColorTransferFunction

开发环境&#xff1a; Windows 11 家庭中文版Microsoft Visual Studio Community 2019VTK-9.3.0.rc0vtk-example demo解决问题&#xff1a;通过颜色查找表给vtkPlaneSource上色 第一种技术是使用预定义颜色的查找表vtkLookupTable。这包括创建一个查找表并为其分配一组已命名的…

攻关眼科难题!第一届爱尔眼科-四川大学科研基金完成立项

当前我国眼科患者数量不断增长&#xff0c;人民群众对高质量的眼健康的需要不断攀升&#xff0c;而目前国内眼科医疗资源远不能满足需求&#xff0c;疑难眼病诊疗能力及学术科研体系建设仍有较大进步空间。基于此&#xff0c;爱尔眼科携手四川大学共同设立爱尔眼科-四川大学科研…

js moment时间范围拿到中间间隔时间

2023.11.27今天我学习了如何对只返回的开始时间和结束时间做处理&#xff0c;比如后端返回了&#xff1a; [time:{start:202301,end:202311}] 我们需要把中间的间隔渲染出来。 [202301,202302,202303,202304,202305,202306,202307,202308,202309,202310,202311] 利用moment…

搭建你自己的网盘-个人云存储的终极解决方案-nextcloud AIO(二)

今天接着上篇&#xff0c;我们继续来玩nextcloud AIO. 当我们看到这个页面的时候&#xff0c;则证明AIO已经安装好了&#xff0c;登录账号和密码在图上已经标注了。点击open your nextcloud 即可跳转到我们的域名的登录页。 输入用户名和密码后登录即可。 打开前台页面&#x…

【Dockerfile】将自己的项目构建成镜像部署运行

目录 1.Dockerfile 2.镜像结构 3.Dockerfile语法 4.构建Java项目 5.基于Java8构建项目 1.Dockerfile 常见的镜像在DockerHub就能找到&#xff0c;但是我们自己写的项目就必须自己构建镜像了。 而要自定义镜像&#xff0c;就必须先了解镜像的结构才行。 2.镜像结构 镜…

element table滚动条失效

问题描述:给el-table限制高度之后滚动条没了 给看看咋设置的&#xff1a; <el-table:data"tableData"style"width: 100%;"ref"table"max-height"400"sort-change"changeSort">对比了老半天找不出问题&#xff0c;最后…

上门预约互联网干洗店洗鞋店小程序开发

很多时候可能大家的衣服鞋子需要干洗&#xff0c;但又不想出门送去店里&#xff0c;大家就可以使用手机线上下单预约取货&#xff0c;会有专门的人上门来取衣服&#xff0c;让你能够轻松的进行洗护。 闪站侠洗衣洗鞋小程序&#xff0c;提供了足不出户就能预约人员上门去 衣送洗…

解决ansible批量加入新IP涉及known_hosts报错的问题

我们把一批新的IP加入到ansible的hosts文件&#xff0c;比如/etc/ansible/hosts&#xff0c;往往会有这样的提示&#xff0c; 因为本机的~/.ssh/known_hosts文件中并有fingerprint key串&#xff0c;使用ssh连接目标主机时&#xff0c;一般会提示是否将key字符串加入到~/.ssh/…

Java多线程核心技术二-synchronzied同步方法

1 概述 关键字synchronzied保障了原子性、可见性和有序性。 非线程安全问题会在多个线程对同一个对象中的同一个实例变量进行并发访问时发生&#xff0c;产生的后果就是“脏读”&#xff0c;也就是读取到的数据其实是被更改过的。而线程安全是指获取的实例变量的值是经过同步处…