This section provides a conversational explanation of how the repository actually stores and revisions file trees. It's not critical knowledge for a programmer using the Subversion Filesystem API, but most people probably still want to know what's going on “under the hood” of the repository.
Suppose we have a new project, at revision 1, looking like this (using CVS syntax):
prompt$ svn checkout myproj U myproj/ U myproj/B U myproj/A U myproj/A/fish U myproj/A/fish/tuna prompt$
Only the file tuna is a regular file, everything else in myproj is a directory.
Let's see what this looks like as an abstract data structure in the repository, and how that structure works in various operations (such as update, commit, and branch).
In the diagrams that follow, lines represent parent-to-child connections in a directory hierarchy. Boxes are "nodes". A node is either a file or a directory – a letter in the upper left indicates which kind. A file node has a byte-string for its content, whereas directory nodes have a list of dir_entries, each pointing to another node.
Parent-child links go both ways (i.e., a child knows who all its parents are), but a node's name is stored only in its parent, because a node with multiple parents may have different names in different parents.
At the top of the repository is an array of revision numbers, stretching off to infinity. Since the project is at revision 1, only index 1 points to anything; it points to the root node of revision 1 of the project:
( myproj's revision array ) ______________________________________________________ |___1_______2________3________4________5_________6_____... | | ___|_____ |D | | | | A | /* Two dir_entries, `A' and `B'. */ | \ | | B \ | |__/___\__| / \ | \ | \ ___|___ ___\____ |D | |D | | | | | | | | fish | /* One dir_entry, `fish'. */ |_______| |___\____| \ \ ___\____ |D | | | | tuna | /* One dir_entry, `tuna'. */ |___\____| \ \ ___\____ |F | | | | | /* (Contents of tuna not shown.) */ |________|
What happens when we modify tuna and commit? First, we make a new tuna node, containing the latest text. The new node is not connected to anything yet, it's just hanging out there in space:
________ |F | | | | | |________|
Next, we create a new revision of its parent directory:
________ |D | | | | tuna | |___\____| \ \ ___\____ |F | | | | | |________|
We continue up the line, creating a new revision of the next parent directory:
________ |D | | | | fish | |___\____| \ \ ___\____ |D | | | | tuna | |___\____| \ \ ___\____ |F | | | | | |________|
Now it gets more tricky: we need to create a new revision of the root directory. This new root directory needs an entry to point to the “new” directory A, but directory B hasn't changed at all. Therefore, our new root directory also has an entry that still points to the old directory B node!
______________________________________________________ |___1_______2________3________4________5_________6_____... | | ___|_____ ________ |D | |D | | | | | | A | | A | | \ | | \ | | B \ | | B \ | |__/___\__| |__/___\_| / \ / \ | ___\_____________/ \ | / \ \ ___|__/ ___\____ ___\____ |D | |D | |D | | | | | | | | | | fish | | fish | |_______| |___\____| |___\____| \ \ \ \ ___\____ ___\____ |D | |D | | | | | | tuna | | tuna | |___\____| |___\____| \ \ \ \ ___\____ ___\____ |F | |F | | | | | | | | | |________| |________|
Finally, after all our new nodes are written, we finish the “bubble up” process by linking this new tree to the next available revision in the history array. In this case, the new tree becomes revision 2 in the repository.
______________________________________________________ |___1_______2________3________4________5_________6_____... | \ | \__________ ___|_____ __\_____ |D | |D | | | | | | A | | A | | \ | | \ | | B \ | | B \ | |__/___\__| |__/___\_| / \ / \ | ___\_____________/ \ | / \ \ ___|__/ ___\____ ___\____ |D | |D | |D | | | | | | | | | | fish | | fish | |_______| |___\____| |___\____| \ \ \ \ ___\____ ___\____ |D | |D | | | | | | tuna | | tuna | |___\____| |___\____| \ \ \ \ ___\____ ___\____ |F | |F | | | | | | | | | |________| |________|
Generalizing on this example, you can now see that each “revision” in the repository history represents a root node of a unique tree (and an atomic commit to the whole filesystem.) There are many trees in the repository, and many of them share nodes.
Many nice behaviors come from this model:
Easy reads. If a filesystem reader wants to locate revision X of file foo.c, it need only traverse the repository's history, locate revision X's root node, then walk down the tree to foo.c.
Writers don't interfere with readers. Writers can continue to create new nodes, bubbling their way up to the top, and concurrent readers cannot see the work in progress. The new tree only becomes visible to readers after the writer makes its final “l(fā)ink” to the repository's history.
File structure is versioned. Unlike CVS, the very structure of each tree is being saved from revision to revision. File and directory renames, additions, and deletions are part of the repository's history.
Let's demonstrate the last point by renaming the tuna to book.
We start by creating a new parent “fish” directory, except that this parent directory has a different dir_entry, one which points the same old file node, but has a different name:
______________________________________________________ |___1_______2________3________4________5_________6_____... | \ | \__________ ___|_____ __\_____ |D | |D | | | | | | A | | A | | \ | | \ | | B \ | | B \ | |__/___\__| |__/___\_| / \ / \ | ___\_____________/ \ | / \ \ ___|__/ ___\____ ___\____ |D | |D | |D | | | | | | | | | | fish | | fish | |_______| |___\____| |___\____| \ \ \ \ ___\____ ___\____ ________ |D | |D | |D | | | | | | | | tuna | | tuna | | book | |___\____| |___\____| |_/______| \ \ / \ \ / ___\____ ___\____ / |F | |F | | | | | | | | | |________| |________|
From here, we finish with the bubble-up process. We make new parent directories up to the top, culminating in a new root directory with two dir_entries (one points to the old “B” directory node we've had all along, the other to the new revision of “A”), and finally link the new tree to the history as revision 3:
______________________________________________________ |___1_______2________3________4________5_________6_____... | \ \_________________ | \__________ \ ___|_____ __\_____ __\_____ |D | |D | |D | | | | | | | | A | | A | | A | | \ | | \ | | \ | | B \ | | B \ | | B \ | |__/___\__| |__/___\_| |__/___\_| / ___________________/_____\_________/ \ | / ___\_____________/ \ \ | / / \ \ \ ___|/_/ ___\____ ___\____ _____\__ |D | |D | |D | |D | | | | | | | | | | | | fish | | fish | | fish | |_______| |___\____| |___\____| |___\____| \ \ \ \ \ \ ___\____ ___\____ ___\____ |D | |D | |D | | | | | | | | tuna | | tuna | | book | |___\____| |___\____| |_/______| \ \ / \ \ / ___\____ ___\____ / |F | |F | | | | | | | | | |________| |________|
For our last example, we'll demonstrate the way “tags” and “branches” are implemented in the repository.
In a nutshell, they're one and the same thing. Because nodes are so easily shared, we simply create a new directory entry that points to an existing directory node. It's an extremely cheap way of copying a tree; we call this new entry a clone, or more colloquially, a “cheap copy”.
Let's go back to our original tree, assuming that we're at revision 6 to begin with:
______________________________________________________ ...___6_______7________8________9________10_________11_____... | | ___|_____ |D | | | | A | | \ | | B \ | |__/___\__| / \ | \ | \ ___|___ ___\____ |D | |D | | | | | | | | fish | |_______| |___\____| \ \ ___\____ |D | | | | tuna | |___\____| \ \ ___\____ |F | | | | | |________|
Let's “tag” directory A. To make the clone, we create a new dir_entry T in our root, pointing to A's node:
______________________________________________________ |___6_______7________8________9________10_________11_____... | \ | \ ___|_____ __\______ |D | |D | | | | | | A | | A | | \ | | | | | B \ | | B | T | |__/___\__| |_/__|__|_| / \ / | | | ___\__/ / / | / \ / / ___|__/ ___\__/_ / |D | |D | | | | | | | | fish | |_______| |___\____| \ \ ___\____ |D | | | | tuna | |___\____| \ \ ___\____ |F | | | | | |________|
Now we're all set. In the future, the contents of directories A and B may change quite a lot. However, assuming we never make any changes to directory T, it will always point to a particular pristine revision of directory A at some point in time. Thus, T is a tag.
(In theory, we can use some kind of authorization system to prevent anyone from writing to directory T. In practice, a well-laid out repository should encourage “tag directories” to live in one place, so that it's clear to all users that they're not meant to change.)
However, if we do decide to allow commits in directory T, and now our repository tree increments to revision 8, then T becomes a branch. Specifically, it's a branch of directory A which shares history with A up to a certain point, and then “broke off” from the main line at revision 8.
Copyright ? 2000-2006 Collab.Net. All rights reserved.
This software is licensed as described in the file COPYING, which you should have received as part of this distribution. The terms are also available at http://subversion.tigris.org/license-1.html. If newer versions of this license are posted there, you may use a newer version instead, at your option.
track:http://subversion.tigris.org/design.html#server.fs.struct.bubble-up
從 httpd Apache web服務(wù)器獲取的資源只限于由 htpasswd 創(chuàng)建的文件中所列出的用戶使用. 該程序只能用于用戶名保存在平面文件的情況下. 要使用DBM數(shù)據(jù)庫(kù),請(qǐng)參看 dbmmanage.
htpasswd 的加密密碼既可以使用針對(duì)Apache修改的MD5版本, 也可以使用系統(tǒng)的crypt()程序. 由 htpasswd 管理的文件可能同時(shí)包含兩種類型的密碼; 一些用戶記錄可使用MD5加密的密碼, 而另一些用戶記錄可在同一文件中使用通過(guò) crypt()加密的密碼.
該手冊(cè)頁(yè)只列出了命令行的參數(shù). 要獲得關(guān)于在 httpd 中配置用戶認(rèn)證的必要指令的詳細(xì)情況, 可參看Apache手冊(cè),它是Apache發(fā)布版本的一部分,你也可以在 <URL:http://www.apache.org/>上找到它. ?
htpasswd -c /home/doe/public_html/.htpasswd jane
htpasswd -mb /usr/web/.htpasswd-all jones Pwd4Steve
建議不要使用-b選項(xiàng),因?yàn)槭褂迷撨x項(xiàng)時(shí)未加密的密碼出現(xiàn)在了命令行上. ?
使用的MD5算法是Apache軟件特有的; 使用它加密的密碼對(duì)于其他Web服務(wù)器來(lái)說(shuō)是不可用的.
用戶名限制在255個(gè)字節(jié)以內(nèi),而且不能包含字符':'. q.SH SEE ALSO(另見(jiàn)) httpd(8) 以及隨發(fā)布版本所帶的support/SHA1下的腳本.
第二步,修改apache的配置文件
接著打開(kāi)Apache HTTP Server 的 httpd.conf(在
#LoadModule dav_module modules/mod_dav.so
#LoadModule dav_fs_module modules/mod_dav_fs.so
把這兩行最前面的 '#' 字符刪除,然后把下面這行加到這些 LoadModule 指令的后面:
LoadModule dav_svn_module modules/mod_dav_svn.so
LoadModule authz_svn_module modules/mod_authz_svn.so
用記事本打開(kāi) Apache HTTP Server 的 httpd.conf,在文件最后面加上以下幾行:
<Location /svn>
??? DAV svn
??? SVNParentPath d:/svn
</Location>
其中d:/svn為你要建的倉(cāng)庫(kù)的父目錄(必須為本地目錄),最好用雙引號(hào)引一下.
第三步,創(chuàng)建倉(cāng)庫(kù)
根據(jù)apache的配置文件的設(shè)置建立目錄:d:/svn.然后我們就可以在下面建立倉(cāng)庫(kù)了,創(chuàng)建方法如下:
c:\>svnadmin create d:/svn/repository(svn大寫(xiě)的時(shí)候居然驗(yàn)證訪問(wèn)被拒絕)
命令執(zhí)行成功之后,會(huì)在d:/svn/repository下面生成很多的文件和目錄
第四步,使用
重新啟動(dòng)Apache Server
在瀏覽器中輸入網(wǎng)址:
http://localhost/svn/repository/
這時(shí)候,看到頁(yè)面顯示:
Revision 0: /
Powered by Subversion version 1.3.2 (r19776).
安裝成功,接下來(lái)將進(jìn)行進(jìn)一步的設(shè)置。
二、配置
1.? 以兩種方式運(yùn)行
(1) 以apache方式運(yùn)行
? svn使用apache的權(quán)限管理對(duì)檔案庫(kù)及目錄進(jìn)行讀寫(xiě)管理。
??htpasswd的調(diào)用:
??? 1)用cmd進(jìn)入到Apache的bin目錄下,直接開(kāi)始調(diào)用
??? 2)把htpasswd添加到系統(tǒng)變量里.
??
? 建立保存用戶名和密碼的文件
? htpasswd -c? svn-auth-filename? username1
? 然后,會(huì)提示輸入密碼,這里就加入了一個(gè)用戶了。然后再加一個(gè)用戶:
? htpasswd -m ?/svn-auth-filename username2
?
? 建立控制用戶訪問(wèn)權(quán)限的文件svn-access-filename
?[svn1:/]?????????????? //這表示,倉(cāng)庫(kù)svn1的根目錄下的訪問(wèn)權(quán)限
?harry = rw???????????? // svn1倉(cāng)庫(kù)harry用戶具有讀和寫(xiě)權(quán)限
?sally = r?????????????? // svn1倉(cāng)庫(kù)sally用戶具有讀權(quán)限
?[svn2:/]?????????????? //svn2倉(cāng)庫(kù)根目錄下的訪問(wèn)權(quán)限
?harry = r?????????????? // harry用戶在svn2倉(cāng)庫(kù)根目錄下只有讀權(quán)限
?sally =?????????????? // sally用戶在 svn2倉(cāng)庫(kù)根目錄下無(wú)任何權(quán)限
?[svn2:/src]?????????? //svn2倉(cāng)庫(kù)下src目錄的訪問(wèn)權(quán)限
?harry=rw????????????
?sally=r
?[/]????????????????? // 這個(gè)表示在所有倉(cāng)庫(kù)的根目錄下
?* = r?????????????? // 這個(gè)表示對(duì)所有的用戶都具有讀權(quán)限
?[groups]?????????????? // 這個(gè)表示群組設(shè)置
?svn1-developers = harry, sally?????????? // 這個(gè)表示某群組里的成員
?svn2-developers = sally
?[svn1:/]???????????
?@svn1-developers = rw?????? // 如果在前面加上@符號(hào),則表示這是個(gè)群組權(quán)限設(shè)置
?
然后修改httpd.conf配置:
<Location /svn>
DAV svn
SVNParentPath /svn
AuthType Basic
AuthName "Subversion repository"(注意引號(hào))
AuthUserFile? svn-auth-filename (保存用戶名和密碼)
Require valid-user
AuthzSVNAccessFile? svn-access-filename(保存用戶訪問(wèn)的權(quán)限策略)
</Location>
(注:)
svn-access-filename,svn-auth-filename 文件直接可以放到C:\Program Files\Apache Group\Apache2\下,這也是默認(rèn)的目錄,置于其他目錄下,啟動(dòng)apache server出現(xiàn)錯(cuò)錯(cuò)誤。
AuthUserFile文件需要用htpasswd工具創(chuàng)建、維護(hù);AuthzSVNAccessFile 文件可以用Subversion創(chuàng)建倉(cāng)庫(kù)以后,生成的conf文件夾下面的authz文件加以修改。
如果已經(jīng)有一個(gè)apache服務(wù),希望兩個(gè)apache服務(wù)同時(shí)運(yùn)行,則修改其中一個(gè)的服務(wù)端口。
修改httpd.conf中的一行: Listen 80 將80改成其他不用的端口號(hào)
啟動(dòng)apahce就可以通過(guò)
http://svnmachine-ip/svn/svn1
http://svnmachine-ip/svn/svn2
這兩個(gè)URL來(lái)訪問(wèn)倉(cāng)庫(kù)了,當(dāng)然,要受權(quán)限的限制,必須是合法用戶才能訪問(wèn)且具有相應(yīng)的權(quán)限
(2) svnserve方式運(yùn)行
這種方式的運(yùn)行又可以分為以下兩種(這和vsftp有些相似)
1) standalone mode
直接運(yùn)行 #svnserve –d
運(yùn)行 lsof -i :3690可以看到SVN服務(wù)器已經(jīng)在運(yùn)行
2) xinetd mode
在/etc/xinetd.d/下生成svnserve文件,內(nèi)容如下
service svnserve
{
disable = no
socket_type = stream
protocol = tcp
wait = no
user = svnpub? (最好建一個(gè)這樣的用戶管理svn)
server = /usr/local/bin/svnserve
server_args = -i
}
編輯 /etc/services 檔,加入底下兩行:
svnserve 3690/tcp # Subversion svnserve
svnserve 3690/udp # Subversion svnserve
重啟xinetd服務(wù),運(yùn)行 netstat -a|grep svnserve 可以看到SVN服務(wù)器已經(jīng)在運(yùn)行
默認(rèn)下客戶可以以匿名方式通過(guò)svn://方式任意訪問(wèn)檔案庫(kù),為了限制其權(quán)限,比如只允許讀操作,可以通過(guò)修改檔案庫(kù)conf子目錄中的svnseve.conf文件來(lái)實(shí)現(xiàn)。
#vi /svn1/conf/svnseve.conf
將下面三行前的#去掉
anon-access = none??????????? //則匿名用戶不可以通過(guò)svn://方式訪問(wèn)檔案庫(kù)
auth-access = write????
password-db = passwd????????? //使用passwd中的用戶名和密碼進(jìn)行認(rèn)證,有一點(diǎn)不好的是這里面的密碼輸?shù)氖敲魑?br />authz-db = authz????????????? //使用authz中的權(quán)限控制策略,寫(xiě)法如同apache中的svn-access-filename
如果想更強(qiáng)的安全性可以使用svn+ssh://訪問(wèn)機(jī)制。
首當(dāng)用戶通過(guò)svn+ssh://訪問(wèn)時(shí),服務(wù)器會(huì)自動(dòng)啟動(dòng)ssh認(rèn)證機(jī)制,要求用戶輸入密碼,對(duì)于window用戶來(lái)說(shuō)還需要安裝第三方軟件openssh,才可以采用這種機(jī)制。
2.? 將代碼導(dǎo)入項(xiàng)目倉(cāng)庫(kù)。在要導(dǎo)入倉(cāng)庫(kù)的目錄上面點(diǎn)擊右鍵,點(diǎn)擊TortoiseSVN項(xiàng)的子菜單Import…在URl里面填寫(xiě)http://服務(wù)器IP/svn/project,這里project就是剛剛建立的項(xiàng)目倉(cāng)庫(kù),
在下面填入注釋,點(diǎn)擊OK,彈出提示輸入用戶名密碼對(duì)話框,填入flmn和password,就可以把代碼導(dǎo)入倉(cāng)庫(kù)了。剛剛導(dǎo)入的文件并沒(méi)有顯示被svn管理,現(xiàn)在把這個(gè)目錄里文件都刪除,再在右鍵菜單里選擇SVN Checkout…就可以導(dǎo)出代碼,再看這個(gè)目錄,就有個(gè)小圖標(biāo)在文件夾圖標(biāo)下面了。
3.? 對(duì)于其它操作,和CVS差不多,這里就不贅述了。可以參考TortoiseSVN幫助文件。
|