会一些基础的操作了,现在通过一个不太复杂的例子来看看如何进行多人协作。
一般情况下,多人协作需要至少一台机当做服务器,无论是在本机、局域网或互联网上都可以。Git 服务器一般是由管理员搭建的。我们现在将会在局域网内的一台机器上搭建一个 Git 仓库。
设置一个 Git 仓库 非管理员可以跳过本节,无需设置服务器,直接进行多人协作。
先登陆上要设置仓库的机器,然后添加一个 git 用户,供其他用户访问:
为了不用每次都输入密码,以及更高的安全性,可以将要访问的用户的 ssh pub key 放到 /home/git/.ssh/authorized_keys ,一个人一行。
设置 ssh key 想要查看现有的 key,在本地机器(不是服务器)执行 ls -al ~/.ssh
。然后将 authorized_keys 文件或者 .pub 结尾的文件中的内容添加进服务器的 /home/git/.ssh/authorized_keys 中。
如果还没有 pub key,可以新创建一个:
1 2 3 4 5 6 // 替换邮箱$ ssh-keygen -t rsa -b 4096 -C "[email protected] " // 然后输入要保存的文件,一般是直接回车就好Enter file in which to save the key: // 这里是设置一个密码,为了以后不用每次都输入密码,所以留空,直接回车Enter passphrase
创建好之后,就是把 pub key 推送到服务器了。在本地执行:
1 2 // 替换服务器地址$ ssh [email protected] 'mkdir .ssh && chmod 700 .ssh && cat >> ~/.ssh/authorized_keys' < ~/.ssh/i d_rsa.pub
或者将这个 id_rsa.pub 文件发给管理员添加。
禁止 git 用户登录 为了安全性,可以限制 git 用户登录 shell。在服务器上,编辑 /etc/passwd
,找到:
1 git: x: 1001 : 1001 : ,,,:/home/git :/bin/bash
改为:
1 git: x: 1001 : 1001 : ,,,:/home/git :/usr/bin/git-shell
在禁用 git 用户登录后,上面的 key 将无法推送,可以在搜集完 key 后再禁用。
创建仓库 在服务器上,进入放仓库的目录:/home/git
,然后初始化一个仓库:
1 2 3 4 // 自行更换仓库名$ sudo git init --bare name.git // 将仓库的所有者改为 git$ sudo chown -R git:git name.git
到这里,一个简单的 Git 仓库就已经设置完成了。
多人协作 接下来就是激动人心的多人协作了。
现在可以将仓库克隆下来,开始多人协作:
1 2 // 自行更改服务器地址以及仓库名 $ git clone git @192.168 .2.103 :test.git
这样就克隆了一个空的仓库,然后可以在里面开始工作了。
或者,如果你在本地已有了一个仓库,想要链接这个远程仓库,可以设置:
1 2 3 $ git remote add origin git $ git push -u origin master
git remote add
命令可以将服务器地址添加到当前的仓库里,便于将现在的仓库推送到服务器,origin
表示远程服务器。
-u origin master
是指定操作时默认的远程分支。
这样本地仓库就链接上远程仓库,并且将所有版本记录都上传上去了。
多人协作实例 现在将通过一个不太难的例子来演示多人协作。
有两个人,小明和小红。一起共同开发一个小程序,服务器已经设置好了。
克隆仓库 小明将这个仓库克隆下来:
1 $ git clone git @192.168 .2.103 :random.git
将会提示这是一个空仓库,所以小明创建了一个 random.c 文件并开始了编码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #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 , "使用: %s <数字>\n" , argv[0 ]); return EXIT_FAILURE; } int max = atoi(argv[1 ]); int result = random_int(max ); printf ("%d\n" , result); return EXIT_SUCCESS; }
编译一下看看能不能正常运行:
1 2 3 4 5 $ gcc -std=c99 random.c $ ./a .out 使用: ./a .out <数字> $ ./a .out 10 3
提交更改 很好,现在将 random.c 添加到仓库:
然后看看操作的状态是否正确:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ git status 位于分支 master 尚无提交 要提交的变更: (使用 "git rm --cached <文件>..." 以取消暂存) 新文件: random .c 未跟踪的文件: (使用 "git add <文件>..." 以包含要提交的内容) a .out
有个 a.out 文件,这是一个可执行文件。作为编译生成的文件是不应该存储在版本库中的,所以小明创建一个名为 .gitignore 的文件:
再将 .gitignore 添加进版本控制里面:
再次查看状态,已经没有 a.out 文件了:
1 2 3 4 5 6 7 8 9 10 $ git status 位于分支 master 尚无提交 要提交的变更: (使用 "git rm --cached <文件>..." 以取消暂存) 新文件: .gitignore 新文件: random .c
接下来就是提交更改了:
上传更改 将更改提交到仓库后,小明打算上传到服务器上供小红继续开发:
检查历史 现在轮到小红克隆这个版本库了:
1 2 $ git clone git@192 .168.2 .103 :random .git$ cd random
小红想要先看看提交日志,所以她使用了 log
命令:
1 2 3 4 5 6 $ git log commit 3 f59af20a7621f9d670da046da6f805464f6a379 (HEAD -> master , origin/master , origin/HEAD) Author: 小明 <小明@gmail.com> Date : Fri Mar 30 10 :53 :59 2018 +0800 最初版
经过一番检查和调试,小红发现这个随机数生成器从不初始化,每次生成的都是一样的数字。小红进行了一点修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #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 , "使用: %s <数字>\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; }
进过一番调试,每次生成的都不一样了,可以进行提交了。看看需要什么操作:
1 2 3 4 5 6 7 8 9 10 11 $ git status 位于分支 master 您的分支与上游分支 'origin/master ' 一致。 尚未暂存以备提交的变更: (使用 "git add <文件>..." 更新要提交的内容) (使用 "git checkout -- <文件>..." 丢弃工作区的改动) 修改: random.c 修改尚未加入提交(使用 "git add" 和/或 "git commit -a" )
git 已经知道 random.c 被修改了。小红想要通过 diff
命令查看更详细的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $ git diff diff --git a/random .c b/random .c index b831e09..636 b28c 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 ) {@@ -16 ,6 +17 ,7 @@ int main (int argc, char *argv[]) int max = atoi(argv[1 ]); + srand(time(NULL )); int result = random_int(max ); printf ("%d\n" , result);
现在可以添加、提交并且推送到服务器了:
1 2 3 git add random.c git commit -m "初始化随机数" git push
重命名和移动文件 小明打算重构一下,所以决定将所有源码文件移动到 src/
目录下:
1 2 3 4 5 6 7 8 9 10 11 $ mkdir src$ git mv random.c src/$ git status 位于分支 master 您的分支与上游分支 'origin/master' 一致。 要提交的变更: (使用 "git reset HEAD <文件>..." 以取消暂存) 重命名: random.c -> src/random.c $ git commit -m "重构文件"
然后小明决定为项目创建一个 Makefile 和 README 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $ cat Makefile all:: randomCC=gcc CFLAGS=-std=c99 -Wall -Wextra random: $(CC) $(CFLAGS) src/random.c clean: $(RM) *.o random .PHONY: all clean $ cat README 随机数生成 ========== 在给定的最大数内生成随机整数。
然后将这两个文件添加并提交:
1 2 $ git add Makefile README $ git commit -m "添加 Makefile 和 README 文件"
然后小明决定将 random.c 文件重命名为 rand.c :
1 git mv src/random.c src/ rand.c
同时,Makefile 也需要更改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 $ git diff @@ -1,12 +1,12 @@ -all:: random +all:: rand CC=gcc CFLAGS=-std=c99 -Wall -Wextra -random: - $(CC) $(CFLAGS) src/random.c +rand: + $(CC) $(CFLAGS) src/rand.c clean: - $(RM) *.o random + $(RM) *.o rand .PHONY: all clean $ git add Makefile $ git commit -m "重命名文件"
更新存储库(合并) 提交完以后,小明准备推送这些更改到服务器:
1 2 3 4 5 6 7 8 $ git push To 192.168.2.103:random.git ! [rejected] master -> master (fetch first) error: 无法推送一些引用到 '[email protected] :random.git'提示:更新被拒绝,因为远程仓库包含您本地尚不存在的提交。这通常是因为另外 提示:一个仓库已向该引用进行了推送。再次推送前,您可能需要先整合远程变更 提示:(如 'git pull ...')。 提示:详见 'git push --help' 中的 'Note about fast-forwards' 小节。
推送失败,这是因为小红已经先推送了一个版本。小明需要先将小红推送的内容拉取下来(需要添加提交信息):
1 2 3 4 5 6 7 8 9 10 11 $ git pull remote: Counting objects: 3 , done. remote: Compressing objects: 100 % (3 /3 ), done. remote: Total 3 (delta 1 ), reused 0 (delta 0 ) 展开对象中: 100 % (3 /3 ), 完成. 来自 192.168 .2.103 :random 3 f59af2..4 f42481 master -> origin /master 自动合并 src/rand.c Merge made by the 'recursive' strategy. src/rand.c | 2 ++ 1 file changed, 2 insertions(+)
pull
命令是获取服务器上最新的版本,并且会自动与本地的版本合并,并且提交合并。
1 2 3 4 5 6 7 8 9 $ git show commit dd5316ec3e192129a9283eaf38208a15ce0b070f (HEAD - > master)Merge : 5616 d0c 4 f42481Author: 小明 < 小明@gmail .com> Date : Fri Mar 30 12 :35 :15 2018 + 0800 Merge branch 'master' of 192.168 .2 .103 :random 合并小红的版本
show
命令是显示当前的提交。看上去像是合并成功了,并且没有任何问题。
创建标签 小红和小明决定发布这个项目,所以小明创建了一个标签,以便可以更好的访问、引用发布的版本。
1 2 3 4 5 6 7 8 9 10 11 $ git tag -a -m "random v0.1" v0.1 $ git tag --list v0.1 $ git log -1 Merge: 5616 d0c 4 f42481 Author: 小明 <小明@gmail.com> Date : Fri Mar 30 12 :35 :15 2018 +0800 Merge branch 'master ' of 192.168 .2.103 :random 合并小红的版本
现在,这个标签仅在小明的本地库中,接下来就是把所有更改全都推送到服务器上:
1 2 3 4 5 6 7 8 9 $ git push origin tag v0.1 对象计数中: 15 , 完成. Delta compression using up to 8 threads. 压缩对象中: 100 % (12 /12 ), 完成. 写入对象中: 100 % (15 /15 ), 1.70 KiB | 579.00 KiB/s, 完成. Total 15 (delta 1 ), reused 0 (delta 0 ) To 192.168 .2 .103 :random .git * [new tag] v0.1 -> v0.1 $ git push
小红更新仓库以获取 v0.1
标签,并从最新的版本库继续:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ git pull remote: 对象计数中: 15 , 完成. remote: 压缩对象中: 100 % (12 /12 ), 完成. remote: Total 15 (delta 1 ), reused 0 (delta 0 ) 展开对象中: 100 % (15 /15 ), 完成. 来自 192.168 .2 .103 :random 6e6 e562..05 db770 master -> origin/master * [新标签] v0.1 -> v0.1 更新 6e6 e562..05 db770 Fast-forward Makefile | 12 ++++++++++++ README | 4 ++++ random.c => src/rand.c | 0 3 files changed, 16 insertions(+) create mode 100644 Makefile create mode 100644 README rename random.c => src/rand.c (100 %)
解决合并冲突 小红现在决定将一个伪随机数生成器的初始化提取到一个单独的方法中,这样初始化和生成随机数都被封装起来,让未来的修改更加容易。添加 init_rand()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 $ git diff @@ -2,6 +2,11 @@ #include <stdlib.h> #include <time.h> +void init_rand(void) +{ + srand(time(NULL)); +} int random_int(int max) { return rand() % max; @@ -17,7 +22,7 @@ int main(int argc, char *argv[]) int max = atoi(argv[1]); - srand(time(NULL)); + init_rand(); int result = random_int(max); printf("%d\n", result); $ git add src/rand.c $ git commit -m "提取随机生成器的初始化"
与此同时,小明发现 rand()
函数的文档说此方法是一个弱伪随机生成器。所以他决定添加注释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ git diff diff --git a/src/ rand.c b/src/ rand.c index 636 b28c..c18c11e 100644 --- a/src/ rand.c +++ b/src/ rand.c @@ -2 ,6 +2 ,7 @@ #include <stdlib.h> #include <time.h> + int random_int(int max) { return rand() % max; $ git add src/rand.c $ git commit -m "添加注释" $ git push
这时候,小红也准备推送了,但是被拒绝:
1 2 3 4 5 6 7 8 $ git push To 192.168.2.103:random.git ! [rejected] master -> master (fetch first) error: 无法推送一些引用到 '[email protected] :random.git'提示:更新被拒绝,因为远程仓库包含您本地尚不存在的提交。这通常是因为另外 提示:一个仓库已向该引用进行了推送。再次推送前,您可能需要先整合远程变更 提示:(如 'git pull ...')。 提示:详见 'git push --help' 中的 'Note about fast-forwards' 小节。
小明已经推送了一个版本,小红必须先 pull
拉取到最新的版本再合并,才可以继续推送:
1 2 3 4 5 6 7 8 9 10 $ git pull remote: 对象计数中: 4 , 完成. remote: 压缩对象中: 100 % (3 /3 ), 完成. remote: Total 4 (delta 2 ), reused 0 (delta 0 ) 展开对象中: 100 % (4 /4 ), 完成. 来自 192.168 .2.103 :random 05 db770..e20781e master -> origin /master 自动合并 src/rand.c 冲突(内容):合并冲突于 src/rand.c 自动合并失败,修正冲突然后提交修正的结果。
这次合并失败了,Git 无法自动合并两人的更改。提示有冲突,所以小红决定在编辑器中打开 src/rand.c 文件来检查:
1 2 3 4 5 6 7 8 9 <<<<<<< HEAD void init_rand (void ){ srand (time (NULL )); } ======= >>>>>>> e20781e46d20ffcd2b79c5c537823ffc9deaa102
Git 同时将小红的代码(*<<<<<<<* 和 *=======* 之间)和小明的代码(*=======* 和 *>>>>>>>* 之间)都包含了。只有两个代码块分开时自动合并才会成功,因为没有分开,所以这次合并失败了。小红的 init_rand()
函数可以在小明的注释之前,所以小红这样更改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $ git diff diff index fec25cb,c18c11e..0000000 +++ b/src/rand.c @@@ -2 ,11 -2 ,7 +2 ,12 @@@ #include <stdlib.h> #include <time .h> +void init_rand(void ) +{ + srand(time (NULL )); +} + + // 考虑使用更好的随机生成器 int random_int(int max) { return rand() % max;
这样应该就解决完问题了,然后再添加并提交:
1 2 3 $ git add src/rand.c$ git commit -m "合并: init_rand() + 注释" $ git push
push
已经没有任何问题了。