凌云的博客

行胜于言

跟我一起学Git (九) 远程代码库

分类:Git| 发布时间:2014-04-28 07:34:00


代码库概念

Bare和Development代码库

Bare代码库

Bare代码库是没有工作目录的也不应该用来做普通的开发,可以通过git clone --bare选项来创建bare代码库

Development代码库

我们之前讨论的都是Development代码库,用于日常的开发,它会维护当前分支并在工作目录签出当前分支。

代码库克隆

git clone命令通常用于根据你指定的original代码库来创建一个新的代码库。 Git不会复制original代码库的所有内容。 在普通的git clone中,original代码库的本地开发分支(refs/heads/)变成新的代码库的的远程追踪分支(refs/remotes/)。 original代码库的远程追踪分支会被忽略。 original代码库的Tag会被复制,而hooks,配置文件,reflog,stash不会被克隆。

我们可以通过以下命令创建public_html的一个新克隆

$ git clone pub_html my_website

类似的,git clone也可以用于从网站上复制一个克隆

$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git

默认情况下,每一个新的克隆会维持一个“链接”回它的父代码库(original代码库), 保存在remote称为origin,但是original代码库并不知道这个。

Remotes

你正在工作的代码库称为本地或者当前代码库,而你用于交换文件的代码库称为远程(remote)代码库。 可以使用git remote来创建,删除,操作和查看一个远程代码库的配置。 所有这些都保存在配置文件.git/config中

除了git clone外还有以下命令和远程代码库有关

  • git fetch 从远程代码库返回objects和对应的元数据
  • git pull 类似于git fetch同时会合并到对应的本地分支
  • git push 发送对应的objects和元数据到远程代码库中
  • git ls-remotes 查看和远程代码库的引用

追踪分支

当你克隆一个代码库后,你可以在你的本地代码库保留修改而不用通知upstream。 甚至你可以创建一个test分支而不用知道upstream也用一个test分支。 在克隆时,Git会根据original代码库的每个topic分支创建一个remote tracking分支。

引用其他代码库

为了定位你的代码库和其他代码库,你定义了remote。 remote是一个保存在.git/config的命名实体。 它由两部分组成:第一部分是远程代码库的地址,第二部分是refspec, 用于定义一个ref如何从一个命名空间映射到另一个命名空间。

引用远程代码库

Git支持以下几种地址格式

本地文件
/path/to/repo.git
file:///path/to/repo.git

上述两种格式有细微差别,前一种通过硬连接共享对象,而后一种直接复制对象。

Git native protocol
git://example.com/path/to/repo.git
git://example.com/~user/path/to/repo.git
SSH connection
ssh:///[user@]example.com[:port]/path/to/repo.git
ssh:///[user@]example.com/path/to/repo.git
ssh:///[user@]example.com/~user2/path/to/repo.git
ssh:///[user@]example.com/~/path/to/repo.git

Git也支持scp-like格式的SSH

[user@]example.com:/path/to/repo.git
[user@]example.com:~user/path/to/repo.git
[user@]example.com:path/to/repo.git
HTTP和HTTPS
http://example.com/path/to/repo.git
https://example.com/path/to/repo.git
rsync协议
rsync://example.com/path/to/repo.git

refspec

refspec的语法如下:

[+]source:destination

如果指定+表示忽略fast-forward安全检查

一个典型的refspec如下:

+refs/heads/*:refs/remotes/remote/*

使用远程代码库的例子

假设我们要将下面的代码库作为远程代码库

$ git init
Initialized empty Git repository in /home/cjl/public/.git/
$ touch index.html
$ git add index.html
$ git commit -m"Add index.html"
[master (root-commit) c42944c] Add index.html
0 files changed
create mode 100644 index.html

创建权威代码库(Creating an Authoritative Repository)

$ mkdir /tmp/Depot
$ cd /tmp/Depot/
$ git clone --bare ~/public public.git
Cloning into bare repository 'public.git'...
done. 
$ ls -aF public.git/
./  ../  HEAD  config  description  hooks/  info/  objects/  packed-refs  refs/
$ ls -aF ~/public/
./  ../  .git/  index.html
$ ls -aF ~/public/.git
./   COMMIT_EDITMSG  config       hooks/  info/  objects/
../  HEAD            description  index   logs/  refs/

因为使用了--bare选项来克隆,因此目标代码库是bare代码库, bare代码库是没有工作目录的,public.git目录相当于publib/.git目录。

设置你自己的origin远程代码库

$ cd ~/public/
$ cat .git/config
[core]
    repositoryformatversion = 0
    filemode = false
    bare = false
    logallrefupdates = true
    symlinks = false
    ignorecase = true
    hideDotFiles = dotGitOnly

$ git remote add origin /tmp/Depot/public
$ cat .git/config
[core]
    repositoryformatversion = 0
    filemode = false
    bare = false
    logallrefupdates = true
    symlinks = false
    ignorecase = true
    hideDotFiles = dotGitOnly
[remote "origin"]
    url = /tmp/Depot/public
    fetch = +refs/heads/*:refs/remotes/origin/*

现在git remote在我们的配置文件中创建了一个称为origin的remote节。 origin并没有什么特殊的含义,你可以指定其他的名称。

现在让我们来完成origin remote的设置

$ git branch -a
* master
$ git remote update
Fetching origin
From /tmp/Depot/public
* [new branch]      master     -> origin/master
$ git branch -a
* master
  remotes/origin/master

在你的代码库中进行开发

$ git show-branch -a
* [master] Add index.html
 ! [origin/master] Add index.html
 --
*+ [master] Add index.html
$ cat > fuzzy.txt
Fuzzy Wuzzy was a bear
Fuzzy Wuzzy had no hair
Fuzzy Wuzzy wasn't very fuzzy,
Was he? 
$ git add fuzzy.txt
$ git commit -m"Add a hairy poem."
[master 752a7ff] Add a hairy poem. 
1 file changed, 4 insertions(+)
create mode 100644 fuzzy.txt
$ git show-branch -a
* [master] Add a hairy poem. 
 ! [origin/master] Add index.html
--
*  [master] Add a hairy poem.
*+ [origin/master] Add index.html

现在你的代码库比/tmp/Depot/public中的代码库多了一个提交

推送你的修改(Pushing Your Changes)

$ git push
fatal: The current branch master has no upstream branch. 
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin master

发现git提交需要先设置upstream

$ git push --set-upstream origin master
Counting objects: 4, done. 
Delta compression using up to 4 threads. 
Compressing objects: 100% (3/3), done. 
Writing objects: 100% (3/3), 345 bytes, done. 
Total 3 (delta 0), reused 0 (delta 0)
To /tmp/Depot/public
c42944c..752a7ff  master -> master
Branch master set up to track remote branch master from origin. 
$ cat .git/config
[core]
    repositoryformatversion = 0
    filemode = false
    bare = false
    logallrefupdates = true
    symlinks = false
    ignorecase = true
    hideDotFiles = dotGitOnly
[remote "origin"]
    url = /tmp/Depot/public
    fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
    remote = origin
    merge = refs/heads/master

git push的输出显示,Git已经将修改发送到名为origin的远程代码库中了。 除此之外Git还做了额外的操作:它也会将这些修改添加到你的代码库的origin/master分支上

$ git show-branch -a
* [master] Add a hairy poem.
 ! [origin/master] Add a hairy poem.
--
*+ [master] Add a hairy poem. 

添加一个新的开发者

$ mkdir /tmp/bob
$ cd /tmp/bob/
$ git clone /tmp/Depot/public.git
Cloning into 'public'...
done.
$ ls
public
$ cd public/
$ ls
fuzzy.txt  index.html
$ git branch
* master
$ git log -1
commit 752a7ff4cbe8844f0ee949932a77340cbb1e8e70
Author: Jianlong Chen <jianlong99@gmail.com>
Date:   Sat Feb 8 15:54:41 2014 +0800

    Add a hairy poem. 

可以看到,克隆已经成功,另外由于由于Bob的代码库是从从远程代码库克隆过来的, 它已经设置好了默认的地址

$ git remote show origin
* remote origin
Fetch URL: /tmp/Depot/public.git
Push  URL: /tmp/Depot/public.git
HEAD branch: master
Remote branch:
 master tracked
Local branch configured for 'git pull':
 master merges with remote master
Local ref configured for 'git push':
 master pushes to master (up to date)

* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master

现在Bob作出了自己的修改

$ git diff
diff --git a/fuzzy.txt b/fuzzy.txt
index 4281f49..bbe4737 100644
--- a/fuzzy.txt
+++ b/fuzzy.txt
@@ -1,4 +1,4 @@
Fuzzy Wuzzy was a bear
Fuzzy Wuzzy had no hair
Fuzzy Wuzzy wasn't very fuzzy,
-Was he?
+Wuzzy?^M
$ git commit fuzzy.txt
[master 0eebdf8] Make the name pun complete!
1 file changed, 1 insertion(+), 1 deletion(-)
$ git push
Counting objects: 5, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 325 bytes, done.
Total 3 (delta 1), reused 0 (delta 0)
To /tmp/Depot/public.git
   752a7ff..0eebdf8  master -> master

获取代码库的更新

现在假设Bob休假了,而你在获取Bob的更新之前作出了以下修改

$ cd ~/public/
$ git diff
diff --git a/index.html b/index.html
index e69de29..dd4d4cc 100644
--- a/index.html
+++ b/index.html
@@ -0,0 +1,5 @@
+<html>^M
+<body>^M
+My website is alive!^M
+</body>^M
+</html>^M
$ git push
To /tmp/Depot/public
! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to '/tmp/Depot/public
'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
hint: before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

新版本的Git要求你的代码库不能比远程代码库旧。 git pull其实是分为两步的,第一步是fetch(相当于git fetch),第二步是合并或者rebase。

添加和删除远程分支

可以通过使用只有source ref的refspec来创建远程分支

$ git checkout -b foo
Switched to a new branch 'foo'

$ git push origin foo
Total 0 (delta 0), reused 0 (delta 0)
To /tmp/Depot/public
* [new branch]      foo -> foo

可以通过只有destination ref的refspec来删除远程分支

$ git push origin :foo
To /tmp/Depot/public
- [deleted]         foo

配置远程代码库(Remote Configuration)

有3种方法配置远程代码库: 一种是使用git remote,另一种是git config,最后一种是直接编辑.git/config文件。

git remote

$ git remote fasfdfa
error: Unknown subcommand: fasfdfa
usage: git remote [-v | --verbose]
  or: git remote add [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mir
ror=<fetch|push>] <name> <url>
  or: git remote rename <old> <new>
  or: git remote remove <name>
  or: git remote set-head <name> (-a | -d | <branch>)
  or: git remote [-v | --verbose] show [-n] <name>
  or: git remote prune [-n | --dry-run] <name>
  or: git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)..
.]
  or: git remote set-branches [--add] <name> <branch>...
  or: git remote set-url [--push] <name> <newurl> [<oldurl>]
  or: git remote set-url --add <name> <newurl>
  or: git remote set-url --delete <name> <url>
  
  -v, --verbose       be verbose; must be placed before a subcommand

git config

$ git config remote.publish.url 'ssh://git.example.org/pub/repo.git'
$ git config remote.publish.push '+refs/heads/*:refs/heads/*'

这会创建一个远程名称为publish的push refspec