文章目录
Git管理本地代码
安装Git
ubuntu上的安装
$ sudo apt install git
安装正常输入git会显示帮助信息,或查询版本
$ git --version
git version 2.7.4
Git初始化
进入一个你需要管理版本的项目的目录,可以是一个已经存在的项目目录,或者是一个空的新建项目的目录,执行以下命令:
$ git init
则在项目目录下创建一个.git目录,隐藏目录,存放git相关内容
~/src/.git$ ls
branches COMMIT_EDITMSG config description HEAD hooks index info logs objects refs
查看当前的状态
$ git status
位于分支 master
初始提交
未跟踪的文件:
(使用 "git add <文件>..." 以包含要提交的内容)
LinkList.cpp
LinkList.h
LinkList.o
lttest.cpp
lttest.o
makefile
tm
提交为空,但是存在尚未跟踪的文件(使用 "git add" 建立跟踪)
添加跟踪
列出项目的文件,颜色为红色,表示没有被git跟踪,如果需要对某个文件或多个文件的修改情况进行跟踪,就需要用git add把其添加到工作区
$ git add LinkList.h
$ git status
位于分支 master
初始提交
要提交的变更:
(使用 "git rm --cached <文件>..." 以取消暂存)
新文件: LinkList.h
未跟踪的文件:
(使用 "git add <文件>..." 以包含要提交的内容)
LinkList.cpp
LinkList.o
lttest.cpp
lttest.o
makefile
tm
被跟踪的文件添加到工作区,颜色为绿色。
跟踪工作区文件修改
如果这是在文件中加了一行数字“11111111111”
$ git status
位于分支 master
初始提交
要提交的变更:
(使用 "git rm --cached <文件>..." 以取消暂存)
新文件: LinkList.h
尚未暂存以备提交的变更:
(使用 "git add <文件>..." 更新要提交的内容)
(使用 "git checkout -- <文件>..." 丢弃工作区的改动)
修改: LinkList.h
未跟踪的文件:
(使用 "git add <文件>..." 以包含要提交的内容)
LinkList.cpp
查看修改的细节
可以提交修改、放弃修改、放弃暂存,通过git diff查看修改的内容。
$ git diff
diff --git a/LinkList.h b/LinkList.h
index fd78947..be0175c 100644
--- a/LinkList.h
+++ b/LinkList.h
@@ -1,6 +1,6 @@
#include <stdio.h>
#include<stdlib.h>
-
+111111111111111111
//自定义数据类型
绿色,前有加号表示改动的地方。
提交修改的内容到仓库
$ git commit LinkList.h -m "增加了一行数字"
[master (根提交) 3e14eae] 增加了一行数字
1 file changed, 23 insertions(+)
create mode 100644 LinkList.h
-m 用于mark你做的变动,备忘录的作用,非常重要,需简明扼要。
查看变更记录
$ git log
commit 3e14eaeea1ab0253683200fb755b055a2867432a
Author: gsl371 <youremail@here>
Date: Sun Feb 4 21:10:20 2018 +0800
增加了一行数字
版本回退
再修改一次,提交后的状态。
$ git log
commit 003272447618762684a652fb828b6ad82ecd2987
Author: gsl371 <ouremail@here>
Date: Sun Feb 4 21:19:07 2018 +0800
又增加了一行22222
commit 3e14eaeea1ab0253683200fb755b055a2867432a
Author: gsl371 <ouremail@here>
Date: Sun Feb 4 21:10:20 2018 +0800
增加了一行数字
查看简要信息
$ git log --pretty=oneline
003272447618762684a652fb828b6ad82ecd2987 又增加了一行22222
3e14eaeea1ab0253683200fb755b055a2867432a 增加了一行数字
如果要回退到第一次修改后
git reset --hard 3e14eaeea1ab0253683200fb755b055a2867432a
HEAD 现在位于 3e14eae 增加了一行数字
进入文件,可以看到第二次修改没有了。
后悔药
如果不想回退了,返回回退前的状态,git reflog查看head
$ git reflog
3e14eae HEAD@{0}: reset: moving to 3e14eaeea1ab0253683200fb755b055a2867432a
0032724 HEAD@{1}: commit: 又增加了一行22222
3e14eae HEAD@{2}: commit (initial): 增加了一行数字
git reset --hard 查到的head号
git reset --hard 0032724
HEAD 现在位于 0032724 又增加了一行22222
远程仓库的使用
注册帐号
先注册github账号,由于你的本地Git仓库和github仓库之间的传输是通过SSH加密的,所以需要一点设置:
生成ssh key
第一步:创建SSH Key。在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa和id_rsa.pub这两个文件,如果有的话,直接跳过此如下命令,如果没有的话,打开命令行,输入如下命令:
$ ssh-keygen -t rsa -b 4096 -C "youremail@here.com"
Generating public/private rsa key pair.
Enter file in which to save the key (/home/gsl/.ssh/id_rsa):
Created directory '/home/gsl/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
邮箱是你注册github时使用的邮箱,执行的结果生成~/.ssh目录,其中含id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人。
把ssh公钥传到github
第二步:登录github,打开your profile->personal setting->SSH and GPG key->new ssh key,填上任意title,在Key文本框里黏贴id_rsa.pub文件的内容
创建远程库
echo "# condition-var" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add condition-var https://github.com/gsl371/condition-var.git
git push -u condition-var master
git init 用git初始化一个项目,即本地git管理的操作,见上。
git add README.md 添加一个本项目的说明文件,方便以后查看。
git commit -m "first commit"本次提交的注释。
以上都是本地操作,如果已经做过,可以跳过。
git remote add condition-var https://github.com/gsl371/condition-var.git在远程创建了一个condition-var的仓库 网址是https://github.com/gsl371/condition-var.git
最后一步,把本地的git推送到github 作为master。
以下是执行结果。
Username for 'https://github.com': gsl371
Password for 'https://gsl371@github.com':
对象计数中: 6, 完成.
Delta compression using up to 8 threads.
压缩对象中: 100% (6/6), 完成.
写入对象中: 100% (6/6), 27.26 KiB | 0 bytes/s, 完成.
Total 6 (delta 0), reused 0 (delta 0)
To https://github.com/gsl371/condition-var.git
* [new branch] master -> master
分支 master 设置为跟踪来自 condition-var 的远程分支 master。
推送一个已经存在的项目,后两句即可。
git remote add origin https://github.com/gsl371/condition-var.git
git push -u origin master
由于远程库是空的,我们第一次推送master分支时,加上了 –u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。推送成功后,可以立刻在github页面中看到远程库的内容已经和本地一模一样了。
以后有了变更,只要本地作了提交,就可以通过如下命令:
git push origin master
搭建git服务器
搭建环境
创建你自己的共享 Git 服务器非常简单,而且在很多情况下,遇到的这点麻烦是完全值得的。不仅仅是因为它保证你有权限查看自己的代码,它还可以通过扩展为 Git 的使用敞开了一扇大门,例如个人 Git 钩子、无限制的数据存储、和持续集成与分发(CI & CD)。
如果你知道如何使用 Git 和 SSH,那么你已经知道怎么创建一个 Git 服务器了。Git 的设计方式,就是让你在创建或者 clone 一个仓库的时候,就完成了一半服务器的搭建。然后允许用 SSH 访问仓库,而且任何有权限访问的人都可以使用你的仓库作为 clone 的新仓库的基础。
但是,这是一个小的点对点环境(ad-hoc)。按照一些方案你可以创建一些带有同样的功能的设计优良的 Git 服务器,同时有更好的拓展性。
首要之事:确认你的用户们,现在的用户以及之后的用户都要考虑。如果你是唯一的用户那么没有任何改动的必要。但是如果你试图邀请其他的代码贡献者使用,那么你应该允许一个专门的分享系统用户给你的开发者们。
假定你有一个可用的服务器(如果没有,这不成问题,Git 会帮忙解决,CentOS 的 树莓派 3 是个不错的开始),首先,必须安装了git,然后第一步就是只允许使用 SSH 密钥认证的 SSH 登录。这比使用密码登录安全得多,因为这可以免于暴力破解,也可以通过直接删除用户密钥而禁用用户。
创建用户
一旦你启用了 SSH 密钥认证,创建 gituser 用户。这是给你的所有授权的用户们的公共用户:
$ su -c 'adduser gituser'
然后切换到刚创建的 gituser 用户,创建一个 ~/.ssh 的框架,并设置好合适的权限。这很重要,如果权限设置得太开放会使自己所保护的 SSH 没有意义。
$ su - gituser
$ mkdir .ssh && chmod 700 .ssh
$ touch .ssh/authorized_keys
$ chmod 600 .ssh/authorized_keys
authorized_keys 文件里包含所有你的开发者们的 SSH 公钥,你开放权限允许他们可以在你的 Git 项目上工作。他们必须创建他们自己的 SSH 密钥对然后把他们的公钥给你。复制公钥到 gituser 用户下的 authorized_keys 文件中。例如,为一个叫 Bob 的开发者,执行以下命令:
$ cat ~/path/to/id_rsa.bob.pub >> /home/gituser/.ssh/authorized_keys
或者在客户端
$ ssh-copy-id -i ~/.ssh/id_rsa.pub gituser@192.168.122.24
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/gsl/.ssh/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
gituser@192.168.122.24's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh 'gituser@192.168.122.24'"
and check to make sure that only the key(s) you wanted were added.
只要开发者 Bob 有私钥并且把相对应的公钥给你,Bob 就可以用 gituser 用户访问服务器。
但是,你并不是想让你的开发者们能使用服务器,即使只是以 gituser 的身份访问。你只是想给他们访问 Git 仓库的权限。因为这个特殊的原因,Git 提供了一个限制的 shell,准确的说是 git-shell。以 root 身份执行以下命令,把 git-shell 添加到你的系统中,然后设置成 gituser 用户的默认 shell。
# grep git-shell /etc/shells || su -c "echo `which git-shell` >> /etc/shells"
# su -c 'usermod -s git-shell gituser'
现在 gituser 用户只能使用 SSH 来 push 或者 pull Git 仓库,并且无法使用任何一个可以登录的 shell。你应该把你自己添加到和 gituser 一样的组中,在我们的样例服务器中这个组的名字也是 gituser。
举个例子:
# usermod -a -G gituser gsl
创建一个仓库
仅剩下的一步就是创建一个 Git 仓库。因为没有人能在服务器上直接与 Git 交互(也就是说,你之后不能 SSH 到服务器然后直接操作这个仓库),所以创建一个空的仓库 。如果你想使用这个放在服务器上的仓库来完成工作,你可以从它的所在处 clone 下来,然后在你的 home 目录下进行工作。
严格地讲,你不是必须创建这个空的仓库;它和一个正常的仓库一样工作。但是,一个空的仓库没有工作分支(working tree) (也就是说,使用 checkout 并没有任何分支显示)。这很重要,因为不允许远程使用者们 push 到一个有效的分支上(如果你正在 dev 分支工作然后突然有人把一些变更 push 到你的工作分支,你会有怎么样的感受?)。因为一个空的仓库可以没有有效的分支,所以这不会成为一个问题。
你可以把这个仓库放到任何你想放的地方,只要你想要放开权限给用户和用户组,让他们可以在仓库下工作。千万不要保存目录到比如说一个用户的 home 目录下,因为那里有严格的权限限制。保存到一个常规的共享地址,例如 /opt 或者 /usr/local/share。
以 root 身份创建一个空的仓库:
# git init --bare /opt/jupiter.git
# chown -R gituser:gituser /opt/jupiter.git
# chmod -R 770 /opt/jupiter.git
从客户端使用git服务
现在任何一个用户,只要他被认证为 gituser 或者在 gituser 组中,就可以从 jupiter.git 库中读取或者写入。在本地机器尝试以下操作:
$ git clone gituser@example.com:/opt/jupiter.git
Cloning into 'jupiter.clone'...
Warning: you appear to have cloned an empty repository.
谨记:开发者们一定要把他们的 SSH 公钥加入到 gituser 用户下的 authorized_keys 文件里,或者说,如果他们有服务器上的用户(如果你给了他们用户),那么他们的用户必须属于 gituser 用户组。
Git 钩子
运行你自己的 Git 服务器最赞的一件事之一就是可以使用 Git 钩子。Git 托管服务有时提供一个钩子类的接口,但是他们并不会给你真正的 Git 钩子来让你访问文件系统。Git 钩子是一个脚本,它将在一个 Git 过程的某些点运行;钩子可以运行在当一个仓库即将接收一个 commit 时、或者接受一个 commit 之后,或者即将接收一次 push 时,或者一次 push 之后等等。
这是一个简单的系统:任何放在 .git/hooks 目录下的脚本、使用标准的命名体系,就可按设计好的时间运行。一个脚本是否应该被运行取决于它的名字; pre-push 脚本在 push 之前运行,post-receive 脚本在接受 commit 之后运行等等。这或多或少的可以从名字上看出来。
脚本可以用任何语言写;如果在你的系统上有可以执行的脚本语言,例如输出 ‘hello world’ ,那么你就可以这个语言来写 Git 钩子脚本。Git 默认带了一些例子,但是并不有启用。
想要动手试一个?这很简单。如果你没有现成的 Git 仓库,首先创建一个 Git 仓库:
$ mkdir jupiter
$ cd jupiter
$ git init .
然后写一个 “hello world” 的 Git 钩子。因为我为了支持老旧系统而使用 tsch,所以我仍然用它作为我的脚本语言,你可以自由的使用自己喜欢的语言(Bash,Python,Ruby,Perl,Rust,Swift,Go):
$ echo "#\!/bin/tcsh" > .git/hooks/post-commit
$ echo "echo 'POST-COMMIT SCRIPT TRIGGERED'" >> ~/jupiter/.git/hooks/post-commit
$ chmod +x ~/jupiter/.git/hooks/post-commit
现在测试它的输出:
$ echo "hello world" > foo.txt
$ git add foo.txt
$ git commit -m 'first commit'
! POST-COMMIT SCRIPT TRIGGERED
[master (root-commit) c8678e0] first commit
1 file changed, 1 insertion(+)
create mode 100644 foo.txt
现在你已经实现了:你的第一个有功能的 Git 钩子。
有名的 push-to-web 钩子
Git 钩子最流行的用法就是自动 push 更改的代码到一个正在使用中的产品级 Web 服务器目录下。这是摆脱 FTP 的很好的方式,对于正在使用的产品保留完整的版本控制,整合并自动化内容的发布。
如果操作正确,网站发布工作会像以前一样很好的完成,而且在某种程度上,很精准。Git 真的好棒。我不知道谁最初想到这个主意,但是我是从 Emacs 和 Git 方面的专家,IBM 的 Bill von Hagen 那里第一次听到它的。他的文章包含关于这个过程的权威介绍:Git 改变了分布式网页开发的游戏规则。
Git 变量
每一个 Git 钩子都有一系列不同的变量对应触发钩子的不同 Git 行为。你需不需要这些变量,主要取决于你写的程序。如果你只是需要一个当某人 push 代码时候的通用邮件通知,那么你就不需要什么特殊的东西,甚至也不需要编写额外的脚本,因为已经有现成的适合你的样例脚本。如果你想在邮件里查看 commit 信息和 commit 的作者,那么你的脚本就会变得相对麻烦些。
Git 钩子并不是被用户直接执行,所以要弄清楚如何收集可能会混淆的重要信息。事实上,Git 钩子脚本类似于其他的脚本,像 BASH、Python、C++ 等等一样从标准输入读取参数。不同的是,我们不会给它提供这个输入,所以,你在使用的时候,需要知道可能的输入参数。
在写 Git 钩子之前,看一下 Git 在你的项目目录下 .git/hooks 目录中提供的一些例子。举个例子,在这个 pre-push.sample 文件里,注释部分说明了如下内容:
# $1 -- 即将 push 的远程仓库的名字
# $2 -- 即将 push 的远程仓库的 URL
# 如果 push 的时候,并没有一个命名的远程仓库,那么这两个参数将会一样。
#
# 提交的信息将以下列形式按行发送给标准输入
# <local ref> <local sha1> <remote ref> <remote sha1>
并不是所有的例子都是这么清晰,而且关于钩子获取变量的文档依旧缺乏(除非你去读 Git 的源码)。但是,如果你有疑问,你可以从线上其他用户的尝试中学习,或者你只是写一些基本的脚本,比如 echo $1, $2, $3 等等。
分支检测示例
我发现,对于生产环境来说有一个共同的需求,就是需要一个只有在特定分支被修改之后,才会触发事件的钩子。以下就是如何跟踪分支的示例。
首先,Git 钩子本身是不受版本控制的。 Git 并不会跟踪它自己的钩子,因为对于钩子来说,它是 Git 的一部分,而不是你仓库的一部分。所以,Git 钩子可以监控你的 Git 服务器上的一个空仓库的 commit 记录和 push 记录,而不是你本地仓库的一部分。
我们来写一个 post-receive(也就是说,在 commit 被接受之后触发)钩子。第一步就是需要确定分支名:
#!/bin/tcsh
foreach arg ( $< )
set argv = ( $arg )
set refname = $1
end
这个 for 循环用来读入第一个参数 $1 ,然后循环用第二个参数 $2 去覆盖它,然后用第三个参数 $3 再这样。在 Bash 中有一个更好的方法,使用 read 命令,并且把值放入数组里。但是,这里是 tcsh,并且变量的顺序可以预测的,所以,这个方法也是可行的。
当我们有了 commit 记录的 refname,我们就能使用 Git 去找到这个分支的供人看的名字:
set branch = `git rev-parse --symbolic --abbrev-ref $refname`
echo $branch #DEBUG
然后把这个分支名和我们想要触发的事件的分支名关键字进行比较:
if ( "$branch" == "master" ) then
echo "Branch detected: master"
git \
--work-tree=/path/to/where/you/want/to/copy/stuff/to \
checkout -f $branch || echo "master fail"
else if ( "$branch" == "dev" ) then
echo "Branch detected: dev"
Git \
--work-tree=/path/to/where/you/want/to/copy/stuff/to \
checkout -f $branch || echo "dev fail"
else
echo "Your push was successful."
echo "Private branch detected. No action triggered."
endif
给这个脚本分配可执行权限:
$ chmod +x ~/jupiter/.git/hooks/post-receive
现在,当一个用户提交到服务器的 master 分支,那些代码就会被复制到一个生产环境的目录,提交到 dev 分支则会被复制到另外的地方,其他分支将不会触发这些操作。
同时,创造一个 pre-commit 脚本也很简单。比如,判断一个用户是否在他们不该 push 的分支上 push 代码,或者对 commit 信息进行解析等等。
Git 钩子也可以变得复杂,而且它们因为 Git 的工作流的抽象层次不同而变得难以理解,但是它们确实是一个强大的系统,让你能够在你的 Git 基础设施上针对所有的行为进行对应的操作。如果你是一个 Git 重度用户,或者一个全职 Git 管理员,那么 Git 钩子是值得学习的,只有当你熟悉这个过程,你才能真正掌握它。
在我们这个系列下一篇也是最后一篇文章中,我们将会学习如何使用 Git 来管理非文本的二进制数据,比如音频和图片。
Git基本常用命令
命令 | 功能 |
---|---|
mkdir | XX (创建一个空目录 XX指目录名) |
pwd | 显示当前目录的路径。 |
git init | 把当前的目录变成可以管理的git仓库,生成隐藏.git文件。 |
git add XX | 把xx文件添加到暂存区去。 |
git commit –m “XX” | 提交文件 –m 后面的是注释。 |
git status | 查看仓库状态 |
git diff XX | 查看XX文件修改了那些内容 |
git log | 查看历史记录 |
git reset --hard HEAD^ 或者 git reset --hard HEAD~ | 回退到上一个版本(如果想回退到100个版本,使用git reset –hard HEAD~100 ) |
cat XX | 查看XX文件内容 |
git reflog | 查看历史记录的版本号id |
git checkout – XX | 把XX文件在工作区的修改全部撤销。 |
git rm XX | 删除XX文件 |
git remote add origin <网址> | 关联一个远程库 |
git push –u(第一次要用-u 以后不需要) origin master | 把当前master分支推送到远程库 |
git clone <网址> | 从远程库中克隆 |
git checkout –b dev | 创建dev分支 并切换到dev分支上 |
git branch | 查看当前所有的分支 |
git checkout master | 切换回master分支 |
git merge dev | 在当前的分支上合并dev分支 |
git branch –d dev | 删除dev分支 |
git branch name | 创建分支 |
git stash | 把当前的工作隐藏起来 等以后恢复现场后继续工作 |
git stash list | 查看所有被隐藏的文件列表 |
git stash apply | 恢复被隐藏的文件,但是内容不删除 |
git stash drop | 删除文件 |
git stash pop | 恢复文件的同时 也删除文件 |
git remote | 查看远程库的信息 |
git remote –v | 查看远程库的详细信息 |
git push origin master | Git会把master分支推送到远程库对应的远程分支上 |