2007/05/23

Subversion 最佳實務

Subversion 最佳實務 [轉貼自愛德華日誌]

這裡提供一組快速的指導原則,讓你能在每天的軟體開發工作中發揮 Subversion 的最大效用。
採用建全的儲存庫配置

儲存庫的目錄配置有許多方式。由於 branches 和 tags 只是一般的目錄,你必須在配置儲存庫的結構時,把它們考慮進去。

Subversion 官方文件建議採用專案根目錄的觀念,來代表專案在儲存庫中的掛載點。專案根目錄裡面僅包含三個子目錄:/trunk, /branches, 及 /tags。一個儲存庫裡面可包含一個或多個專案根目錄。

參考書籍: Choosing a Repository Layout.
以邏輯變更集(changesets)作為提交單位

當你將修改提交到儲存庫時,要確認你的更動反應的是單一的目的:修正一個特定的臭蟲、增加一個新的特性、或是一些特定的工作。你的提交將會產生一個版本號碼,這號碼在往後可用來作為該次變更的 "名稱"。你可以在臭蟲資料庫中記錄這個號碼,或把它拿來作為 svn merge (合併) 的參數。

參考書藉:"Subversion and Changesets" sidebar, within chapter 4.
善用事例追蹤軟體 (issue-tracker)

盡可能試著建立 Subversion 與 issue-tracker 資料庫之間的雙向連結:

* 盡可能在每個提交的登錄訊息 (log message) 中參引到明確的事例編號
* 在事例中附加訊息時 (描述進度,或是關閉事例),註記相應於此事例變更作業的版本號碼

手動追蹤合併

當提交合併的結果時,切記寫下解釋合併了些什麼的描述性資訊,像是:

將 /branches/foobranch 版號 3490:4120 合併至 /trunk。

參考書藉:Tracking merges manually, and Merging a whole branch to another.
了解混合版本的工作複本

存放工作複本的目錄及檔案可以處於不同的工作版號中:這種特意的設計讓你可以混編不同的新舊版號。但是有些事實是你必須注意的:

1. 每次的提交,都會讓你的工作複本包含不同的版號。你剛提交的變更是位於 HEAD 版號中,而其他的部份則屬於之前的版號。
2. 有許多的情況不允許提交:
* 若是某檔案或目錄沒有包含在 HEAD 的工作版號中,那你便不能提交其刪除動作。
* 若是某目錄沒有包含在 HEAD 的工作版號中,那你便不能提交其變更屬性的動作。
3. svn 更新 (update) 指令可以幫你取回整個工作版號的複本,而這也是第 2 個問題常用的解決方案。

參考書籍:The limitation of mixed revisions.
處理較大的檔案時要有耐心

Subversion 的優點之一是:在設計上,它並沒有對處理檔案的大小做限制。檔案以 "流(streamily)" 的形式在 Subversion client 及 server 兩端收送,而網路兩端只使用了少許的、定量的記憶體。

當然,有許多實務上的問題需要考量。雖然沒有必要擔心幾 K 位元組大小左右的檔案 (像是一般的原始檔),提交較大的檔案會花費大量的時間及空間 (像是幾十或幾百 mega 位元組大小的檔案)。

首先,記得 Subversion 的工作複本會在 .svn/text-base/ 目錄內儲存了所有接受版本控管檔案的初始複本(pristine copy)。這表示你的工作複本至少會占用原本資料集的兩倍磁碟空間。此外,Subversion client 會採用 (目前無法調整的) 演算法來提交檔案:

* 將檔案複製到 .svn/tmp/ (這會花一些時間,而暫存檔也會占用額外的磁碟空間)
* 對暫存檔與初始複本 (pristine copy) 進行二進位的差異比對,若是新增加的檔案則會拿暫存檔與空檔比對。(這可能會花很長的時間運算,即使最終只有很少量的資料會傳送到網路的另一端)
* 將差異送到伺服器,並將暫存檔移入 .svn/text-base/

因此雖然在理論上沒有檔案大小的限制,你還是得注意,當 client 程式在蹣跚處置較大檔案時,你得要耐著性子等待。然而,你可以放心的是,不像在 CVS,較大的檔案不會讓你的伺服器掛點,或影響到其他使用者。
因應那些不認得 copies/renames 動作的指令

在檔案或目錄複製或更名後,Subversion 儲存庫會追蹤歷程記錄。不幸的,在 Subversion 1.0 裡,善用這項特性的 client 端指令只有 svn log。許多其他指令 (例如 svn diff 及 svn cat) 應該要能自動追蹤更名歷程,但卻還沒這麼做。

面對這種情況,基本的因應之道是使用 'svn log -v' 指令找出舊版的檔案路徑。

例如,假設你在版號 200 中將 /trunk 複製到 /branches/mybranch,並在後續的版號中提交了對 /branches/mybranch/foo.c 的修改。現在你想比較該檔的 80 及 250 版號。

如果你有該分支的工作複本,並執行 svn diff -r80:250 foo.c,你會看到在版號 80 裡面並不存在 /branches/mybranch/foo.c 的錯誤訊息。補救方法是在你的分支或檔案上執行 svn log -v,以得知它在版號 200 之前稱為 /trunk/foo.c,接著就可以直接比較兩個 URL 了:

$ svn diff http://.../trunk/foo.c@80 http://.../branches/mybranch/foo.c@200
確定建立分支的時機

這是經常被爭論的話題,事實上這應該視軟體專案文化而定,而不是一套事先訂定的、放諸四海皆準的政策。我們將討論比較常見的三種型式:
從不分支的系統

(常用於還沒有可執行程式的初始專案)

* 使用者直接將每天的變更提交到 /trunk 中。
* 當某使用者開始提交一連串的複雜變更時,偶爾會造成 /trunk 的破壞 (無法編譯,或不能通過功能測試)。

優點:易於遵字的政策。新進開發人員進入門檻較低。沒有人需要學習如何操作分支及合併。

缺點:開發過程混亂,程式碼可能在任何時間產生不穩固的狀況。

附註:這種開發模式在 Subversion 中比 CVS 裡風險少一點點。因為 Subversion 的提交是單元式的 (atomic),即使當某人正在提交變更時,你也不可能僅取出 (checkout) 或更新 (update) 部分提交的變更。
總是分支的系統

(常用於傾向重度管理及監督的專案)

* 每個使用者在他自己所建立的分支上進行開發工作。
* 當實作完成後,某人 (原來的程式設計師,同儕,或經理) 複審所有私有分支的改變後,再將變更合併至 /trunk。

優點:可以保證 /trunk 在任何時間都相當穩固。

缺點:程式開發工作在此人為因素下與其他人分隔開來,可能產生更多不必要的合併衝突,而需要使用者進行許多額外的合併工作。
當需要時才分支的系統

(這是 Subversion 專案所採用的型式)

* 使用者將每天的工作提交到 trunk 上
* 規則 1:/trunk 必須在任何時間都能編譯,並通過回歸測試,違反此規則的提交者將在眾人面前無地自容。
* 規則 2:單一的提交 (changeset) 不應太大,免得同儕難以複審。
* 規則 3:若是規則 1 與規則 2 衝突 (例如,無法進行一連串的小提交同時保持 trunk 的穩固),那使用者就應建立一個分支,再於該分支上進行一連串的變更集小提交。這可以讓同儕順利進行複審,又保持 /trunk 的穩固性。

優點:可保證 /trunk 在任何時間都很穩固,也不用常常進行煩人的分支及合併。

缺點:會稍微增加使用者每天的工作負擔,他們在每次的提交前必須進行編譯及測試。

2007/04/23

Javascript - Regular Express

Regular Express 簡介

範例:

檢查日期格式 1900/1/12:/^\d{4}\/\d{1,2}\/\d{1,2}$/

檢查信用卡號 xxxx-xxxx-xxxx-xxxx:/^\d{4}-\d{4}-\d{4}-\d{4}$/

檢查身分證號碼 A000000000 : /^[A-Z]\d{9}$/

檢查IP格式 : /^\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b$/

檢查僅能輸入英文數字格式 : /^\w{1,}$/

2007/04/09

Windows - 最佳化虛擬記憶體

如果要讓 Windows 選擇最佳的分頁檔大小,請按一下 [系統管理大小]。
建議的最小值為您電腦上 RAM 的 1.5 倍,而最大值則為這個數字的 3 倍。
例如,假設您有 256 MB 的 RAM,則最小值應為 384 MB,最大值應為 1152 MB。

2007/04/03

判斷檔案的 ContentType/MIME-Type

import javax.activation.MimetypesFileTypeMap;
import java.io.File;

class GetContentType {
public static void main(String args[]) {
File f = new File("gumby.gif");
System.out.println("Mime Type of " + f.getName() + " is " +
new MimetypesFileTypeMap().getContentType(f));
// "Mime Type of gumby.gif is image/gif"
}
}

[註]有可能會mapping不到資料,可以查詢j2ee api : javax.activation.MimetypesFileTypeMap。MIME types

2007/03/28

DataBase - 存取檔案至資料庫 (mssql2000)

環境:
DB : mssql 2000
Table schema :
id int,
fileContent image,
path varchar(255);

從檔案加入至資料庫

code:

try {
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
Connection con = DriverManager.getConnection("jdbc:sqlserver://127.0.0.1:1433", "cf-intermediate",
"cf-intermediate");

File file = new File("C:/MyEclipse5.1/workspace/myJava/abc.doc");
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(file));

// 將檔案讀入位元陣列
ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
byte[] bytes = new byte[1];

while (bufferedInputStream.read(bytes) != -1) {
arrayOutputStream.write(bytes);
}
arrayOutputStream.close();
bufferedInputStream.close();
bytes = arrayOutputStream.toByteArray();

System.out.println("bytes="+bytes.length);

PreparedStatement preState = con.prepareStatement("insert into erpglm_append values(?,?,?)");
preState.setBytes(1, bytes);
preState.setString(2, file.getPath());
preState.setString(3, "N");

preState.execute();
con.close();
} catch (Exception e) {
e.printStackTrace();
}


從資料庫取得資料並存成檔案:

try {
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
Connection con = DriverManager.getConnection("jdbc:sqlserver://127.0.0.1:1433", "cf-intermediate",
"cf-intermediate");

String sql = "select fileContent from erpglm_append where id=1";
Statement stat = con.createStatement();
ResultSet rs = stat.executeQuery(sql);
byte[] bytes = null;
while (rs.next()) {
bytes = (byte[]) rs.getObject("fileContent");
}
rs.close();
stat.close();
con.close();

System.out.println("bytes="+bytes.length);

BufferedInputStream bufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(bytes));
File file = new File("C:/MyEclipse5.1/workspace/myJava/abc1.doc");
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(file));
byte[] data = new byte[1];
while (bufferedInputStream.read(data) != -1) {
bufferedOutputStream.write(data);
}
bufferedOutputStream.flush();
bufferedInputStream.close();
bufferedOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
}

2007/03/26

XDoclet - sql-type 用法

/**
* @hibernate.property name="chtName" not-null="true"
* @hibernate.column name="chtName" sql-type="nvarchar(50)"
*/

欄位長度均由sql-type中給定

2007/03/23

彈跳視窗

1.使彈跳視窗為全螢幕:
window.open("2.html", "page2", "fullscreen=1");

2.使彈跳視窗最大化:
var param = "hight="+screen.availHeight+",width="+screen.availWidth;
window.open("2.html", "page2", param);

3.使彈跳視窗設定座標:
window.open("2.html", "page2", "top=0,left=0");

NGINX SSL/設定檔案

#user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #...