一、 前言
相信很多人接触并认识到git是因为Github
多人协同的场景、或者多个设备上开发的场景中, 一个远程的仓库作为”server”就十分必要了
Github正是为用户提供了这样的一个”server”
这一篇中我们跳出本地仓库, 学习和远程仓库的交互操作
二、 了解远程仓库
git是一个开源的分布式版本控制系统
虽然我们时常会设置一个中央服务器来托管代码, 但是抛开服务器的自身性质, 提供服务的远程仓库和本地仓库, 它们的本质是一样的——你可以建立一个本地的”远程库”, 进行和真正的”远程库”基本一样的操作
裸(bare)仓库简介
其实一般的远程仓库, 和本地仓库还是有一定区别的, 它们往往是bare
的:
1
git init --bare (仓库名)
我们一般称之为”裸仓库”
被设计成远端的中心仓库, 这样的仓库没有工作树, 故大部分本地仓库的命令不适用于裸仓库
除了被砍掉工作树, “裸仓库”对于远程更新的行为(如push), 有更弱的防线
它成为了一个纯粹的仓库, 你并不能在其中部署生产环境, 那里只能托管代码
下面是bare仓库的根目录结构, 不同仓库有工作区, 所以我们可以查看和编辑代码, 但这里没有
我们push
过去的代码放在objects/pack/
目录下、以打包后的文件.idx&.pack
的形式存在着
如果我们在服务器自建一个普通的仓库, 本地对其push
操作后, 会收到远程仓库的拒绝, 它会提示你
1
2
3
4
5
6
7
8
9
10
11
12
13
14
refusing to update checked out branch: refs/heads/master
By default, updating the current branch in a non-bare repository
is denied, because it will make the index and work tree inconsistent
with what you pushed, and will require 'git reset --hard' to match
the work tree to HEAD.
remote: error:
You can set 'receive.denyCurrentBranch' configuration variable to
'ignore' or 'warn' in the remote repository to allow pushing into
its current branch; however, this is not recommended unless you
arranged to update its work tree to match what you pushed in some
other way.
remote: error:
To squelch this message and still keep the default behaviour, set
'receive.denyCurrentBranch' configuration variable to 'refuse'.
总结成两句话:
- 默认情况下, 将当前的分支在一个
non-bare
的仓库中更新, 这样的操作会被拒绝 - 解决办法是将远程仓库的
receive.denyCurrentBranch
设置为ignore
或者warn
, 但不推荐这样做
总结成一点就是: 推荐将远程仓库初始化为裸(bare)仓库
三、 基本操作
3.1 clone(克隆远程库)
这个操作将在当前目录中创建远程库的文件夹
1
git clone [仓库地址] (本地仓库名)
- 仓库地址可以是http协议、git协议(
git://
)、也可以是ssh协议(例如root@server:/path/to/repo.git
)、本地文件协议等等 - 本地仓库名是可选的, 若不指定, 本地仓库名取决于远程仓库
3.2 remote(管理远程库)
在进行远程仓库的操作前, 我们需要给当前的操作配置”源”, 帮助git找到当前仓库的远程源
- 如果当前库是
clone
得到的, 则被clone
的地址就自动加到了源的列表中、并命名为origin
origin
是默认的名字、也是大家约定俗成的命名方式
添加远程库
1
git remote add <源名称> <源url>
删除现有远程库
1
git remote remove <源名称>
重命名现有远程库
1
git rename <旧名称> <新名称>
查看现有远程库
1
git remote [-v]
-v
选项即--verbose
, 显示更详细的信息(包含url信息)
获取源url的方法, 除了git remote -v
, 我们还可以使用
1
2
# 查
git remote get-url [--path] [--all] <源名称>
不加选项时默认返回fetch
源, 可以用--push
获得push
源, 但一般情况下二者是一致的
与之对应的, 是修改源url的方法:
1
2
3
4
5
6
# 改
git remote set-url [--push] <源名称> <新url> [<旧url>]
# 增
git remote set-url --add [--push] <源名称> <新url>
# 删
git remote set-url --delete [--push] <源名称> <要删除的url>
3.3 fetch(获取远程库更新)
这个命令将远程仓库的最新内容拉到本地, 若不指定分支名, 则获取所有分支的更新
1
git fetch <源名称> [<远程分支名>]
取回所有远程分支的fetch命令实际上在本地创建了本地远程分支
, 这种分支的命名默认采用源名称/远程分支名
的方式, 例如origin/master
注意: 取回单个分支时不会创建本地远程分支
我们也可以在fetch单个分支时, 对其重命名
1
git fetch <源名称> <远程分支名>:<本地分支名>
在进行fetch
操作后, 我们会得到一个FETCH_HEAD
, 它不完全对应于本地的HEAD
, 因为本地的HEAD
始终指向当前分支, 而我们进行的fetch操作不一定会将其HEAD
所指的分支取回
- 如果没有显式的指定远程分支, 则远程分支的master将作为默认的
FETCH_HEAD
- 如果指定了远程分支, 就将这个远程分支作为
FETCH_HEAD
一个用例: 将本地仓库回滚至远程仓库的上一个提交版本
1
git reset FETCH_HEAD^
另外, 如果要指定远程仓库的分支, 采用源名称/远程分支名
的组合方式, 例如origin/master
一个用例: 将远程仓库的主干分支合并到当前分支
1
git merge origin/master
另一个用例: 以远程仓库的主干分支为基础创建新的分支
1
git checkout -b newBranch origin/master
3.4 pull
pull从远程库拉取更新, 并与本地库合并
- 简而言之:
pull = fetch + merge
- 更具体一些:
git pull = git fetch + git merge FETCH_HEAD
git pull
使用给定的参数运行git fetch
, 并调用git merge
把FETCH_HEAD指向的版本与本地进行合并
使用方法
1
git pull <源名称> <远程分支>[:<本地分支>]
如果是与当前分支合并, 则冒号后面的部分可以省略
可以建立远程和本地、分支之间的追踪关系
1
git branch --set-upstream <本地分支> <源名称>/<远程分支>
例如:
1
git branch --set-upstream master origin/master
如果当前分支的远程追踪有多个主机, 那么源名称还是必需的
1
git pull [源名称]
如果当前分支的远程追踪只有一个, 就可以直接省略后面的所有部分
1
git pull
3.5 push
push与pull相对, 将本地仓库的分支推送到远程仓库的分支
push的用法与pull相似, 比较完整的push形式:
1
git push <源名称> <本地分支>:<远程分支>
将本地分支push到远程仓库的同名分支
1
git push <源名称> <本地分支>
如果省略本地分支, 则会清空远程分支(等同于使用空的分支覆盖远程分支)
1
2
3
git push <源名称> :<远程分支> #不推荐
# 等同于:
git push <源名称> --delete <远程分支>
如果当前分支和远程分支有唯一的追踪关系, 就可以使用简化命令
1
git push
如果有多个源, 则需要指定源名称; 或者采取以下的指定方法, 将某个源设置为默认, 在设置后就可以直接使用简化后的命令
1
git push -u [源名称]
以上的git-push
命令总结一下:
- push需要的基本信息有:
源名称
、本地分支
、远程分支
- 如果有唯一的追踪关系, 则不用任何指定
- 如果有唯一的源/设定了默认源, 则不用指定
源名称
- 如果
本地分支
为当前分支, 则无需指定本地分支
- 如果
远程分支名
与本地分支名
相同, 则无需指定远程分支
注意
- 在
git2.0
及以上的版本中, 不带任何参数的git push
采取simple
的推送方式, 这种方式只推送当前分支 - 而在更早的版本中, 它采用
matching
的推送方式, 会推送所有有对应远程分支的本地分支
可以通过以下命令, 修改推送方式:
1
2
git config --global push.default matching
git config --global push.default simple
除了simple
和matching
, 还有一种更强硬的方式all
, 这种方式会将本地的所有分支推送到远程主机
1
git push --all [源名称]
如果远程源的版本比本地更新, 就会出现推送失败的情况; 这种情况通常先使用git-pull
更新本地仓库, 如果要进行强制推送, 可以使用force
选项(不推荐)
1
git push --force ...
3.6 使用建议
不同的场景, 各种命令都可以进行不同程度的简化, 但在真正熟悉它们之前, 我建议按照最完整、也是最稳妥的格式使用这些命令; 在需要进阶之后, 再去简化命令
- 一方面, 它们的使用频度不大, 多打一些参数代价极小
- 另一方面, 完整的格式和参数, 可以帮助我们更好地理解和感知命令
- 加之, 减少记忆量, 对入门友好
- 此外, 明确的指定能够预防错误和混乱, 无论是git还是我们的大脑
下面是建议的使用方式:
1
2
3
4
5
6
7
8
9
10
11
# 拉取全部分支的更新
git fetch <源名称>
# 拉取特定分支的更新
git fetch <源名称> <远程分支名>
# 将指定源的指定远程分支, 拉取并合并到指定的本地分支
git pull <源名称> <远程分支>:<本地分支>
# 将指定的本地分支, 推送到指定源的指定远程分支
git push <源名称> <本地分支>:<远程分支>