Redmine 的 Repository Controls plugin(續)

在正式把手邊的專案移上 redmine + svn 之前,
我還是花了點時間把這個 plugin 做改善,
資料庫也從 sqlite 換到了 mysql 上,
也把 redmine 從 ports 的版本直接換成官方 svn trunk 裡的版本。

換上 svn trunk 的版本後我馬上發現到一件事,
就是新版的 redmine 有所謂的 multi-role 機制,
舉例來說一個 project 的 member 可以同時是 Manager 和 Developer,
這讓我想通了為什麼這個 plugin 的作者會以 role 的 position 來做優先順序的判斷,
舉例來說 Manager 的 position 是 3 而 Developer 的 position 是 4,
那麼檔案庫存取權限就是看 Manager 的為準;
雖然我也思考過是否有更聰明更複雜的判斷方法,
不過還是以單一 role 來判斷最不容易導致混亂,
也是唯一最貼近 svn 原本 authz 運作模式的實現方法,
更何況這是每 checkout 一個檔案就要跑一次的動作,
SQL 的查詢次數和複雜度也不便設計得太誇張,
不然光是 checkout 和 update 就可以慢到會等死人的程度。

不過利用 mod_perl2 掛 access/auth/authz 的 handler 有一個問題,
那就是跑到 auth 這個 step 的時候必須先發送一個 AUTH_REQUIRED 給 browser (或 svn client),
這樣 browser 才會跳出輸入帳號密碼的 dialog (svn client 也才會要求輸入密碼),
加上 HTTP 協定並不是 keep alive 的東西,
我很難去判斷這是第幾次發送 AUTH_REQUIRED 目前這個 client,
所以如果 user 希望用帳號密碼完全空白的 anonymous 模式存取檔案庫,
那就只會不斷的重複被詢問帳號密碼而不是被直接放行;
關於這點我最後的解決方式就是把存取的 URL 拆成 http:// 和 https:// 兩種協定,
perl module 也是一分為二,
走 http:// 的就是完全不需要打帳號密碼一律視為 anonymous 的存取,
走 https:// 的就視為一定要輸入帳號密碼的 members,
這樣做想想其實也還蠻合理的,
畢竟吃 LDAP 帳號密碼的這種架構下允許 members 走 http:// 實在太恐怖。

下面是分離出來給 anonymous 存取用的 perl module:

下面這個是給 members 登入後才能存取用的 perl module:

註解方面我就沒有時間去慢慢改慢慢加了,
反正目前稍微測試了一下是可以在 virtual host 的 / 為起點的設定下正常運作,
而原作者的設計是假設檔案庫一定不是在 / 下面而是在某層目錄之下,
這個部分我也保留了原作者用 regular expression 的處理方式,
所以如果不是掛在 / 的話能不能正常運作我也不知道。


如果你的 svn 位址不是在 / 開始 (如 http://svn.xxx.xxx/repos 而不是 http://svn.xxx.xxx/),
請自行修改 get_project_identifier() 及 get_requested_path() 中 if($location ne "/") { ... } 的部分。
可以仿照 else { ... } 內的邏輯實作一份。
因為我的檔案庫掛在根目錄,
所以對於不是掛在根目錄的 case 我是直接使用原作者有問題的實作。

NOTE: (for users who don't understand Tranditional Chinese)
If your SVN repositories aren't located on the root directory of your website, you MUST re-implement some codes in get_project_identifier() and get_requested_path().

Since my SVN repositories are located on the root directory, I can only implement and test this case.
I know that I can generalize these codes in order to handle all cases, but I don't have enough time and environments.
Thus, I only publish my modifications in my blog rather than Redmine's forum.

至於那個 SQL 的寫法,
就我讀過的書來說如果寫 SELECT xxx FROM a, b, c 是所謂的 CROSS JOIN,
而且我記得 CROSS JOIN 的 performace 會比較差 (但實際測試似乎都會被 MySQL 的 optimizer 處理到沒差多少),
所以我還是直接寫成 SELECT xxx FROM a INNER JOIN b INNER JOIN c 的形式,
原作者用的是前者的形式而且條件式的部分一律使用 WHERE 不使用 ON (那種寫法確實也沒地方能擺 ON),
我並不是 SQL 的專家所以也沒有在研究這個的,
不過之前讀過一本 O'Reilly 的 SQL 之美學似乎作者是比較支持前者的寫法,
到底哪種好哪種不好可能就要問資管畢業的夭壽仔了 (他好像跑去當兵都找不到人),
我是覺得可讀性來說用 INNER JOIN + ON 的方式可以一層一層看出設計者的目的,
會比較容易理解就是了;
原作者寫的 SQL 我是沒有動它,
所以 code 裡會同時看到兩種寫法。

另外還發現一個小 bug,
就是如果編輯已經設定好的 role 權限,
然後把 read / write 的權限通通都取消的話,
儲存以後會發現權限會保持原狀而不會變成 None (新增的時候兩個都不勾倒是會正常),
這個部分要修改一下 controller 裡的 edit method:


2009-12-23 Update
根據機八林餅幹的說法,
SELECT xxx FROM a, b, c 這種寫法 MySQL 會做 cache,
單次跑出來是數據會差不多,
但是跑多幾次就會發現這種寫法會比較快。

2010-01-27 Update
今天發現就算用這個改寫過的 RedmineRepoControl.pm,
在 MPM=worker 的模式下用 svn copy 還是會爆炸。
gdb 也攔不到原因只會看到直接 program exited with code 02 跳掉,
所以 svn 會說 connection truncated。
目前懶得追了,
只確定是某次 DBI->connect 呼叫下去就爛掉。
跟以前遇到的狀況一樣只要把 apache 改回 MPM=prefork 就會正常。
Redmine 官方的 Redmine.pm 也有遇到一樣的問題:
http://www.redmine.org/boards/2/topics/7593
最新的一篇是 4 個月前了,
大概是沒救了。