Mac 下的 SVN 客户端 Versions 团队开发提交冲突 bug 的解决方案:
1。目前我用的 Versions 的版本为 v1.0.6,官方已经出到 v1.1 了,
Macx 上面也有 v1.0.9 破解版下载,或许新版本有将这个 bug 修复掉~
2。Mac 下面还有很多其他的项目版本控制软件:
SmartSVN,Cornerstone,sourceTree(git),SCPlugin(紧密集成于Finder),
3。Windows 下面的 TotoriseSVN 据闻比较出色,可以用 crossover 或 wine 搭配之~
4。采用一定的措施来弥补当前版本 Versions 的 bug,参见四篇博文(第二篇是mvp):
http://marshal.easymorse.com/archives/4030
http://www.logicaltrinkets.com/wordpress/?p=178
http://renxiangzyq.iteye.com/blog/850762
http://alexrezit.42qu.com/10280223
5。或许可以出一个小工具来快捷处理 project.pbxproj 文件~
(功能:彼此间的差异不覆盖掉,做合并处理)
不多废话,使用说明、注意事项、基本的测试说明,注释,一应俱全,需要的就拿去吧~
VersionsPatch.java
package org.bruce.versions.patch.entry;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.LinkedList;
import java.util.List;
/**
* @author Bruce Yang
* Versions 多人提交 bug 的补丁~
*
* 注意事项:
* 各成员须遵循:
* 除了变动不大且公用的文件(如 AppDelegate.mm 文件),
* 不宜在未和他人联系的情况下擅自修改以及提交由他人负责的代码文件~
*
* 使用说明:
* 0。适用范围:仅限于处理 project.pbxproj 文件上面出现的冲突问题~
* 1。设置本地工程的目录
* 将 PROJECT_DIR 设置为从 svn 服务器 checkout 下来的 XCode 工程的根目录~
* 注意:根目录下面必须包含 .xcodeproj 文件(新建 XCode 工程时默认即是这般)~
* 2。养成好习惯,commit 前都先 update 一下~
* 3。在提交劳动成果的时候,如果发现在 project.pbxproj 文件上面出现冲突,运行该程序后再做提交~
*
* 测试:
* 我这里做过几次粗略的测试,没什么问题。不过,安全性还是有待各人自行进行检验~
* 义务发放源码,各人使用须自担风险~
*/
public class VersionsPatch {
public static final String PROJECT_DIR = "/Users/bruce/SVN/SvnTest";
/**
* 检查传入的文件目录是否为项目文件夹~
* @param dirProject
* @return
*/
public static boolean isProjectDir(String strProjectDir) {
File dirProject = new File(strProjectDir);
if(dirProject.exists() && dirProject.isDirectory()) {
for(File fileItem : dirProject.listFiles()) {
if(fileItem.getName().endsWith(".xcodeproj")) {
return true;
}
}
}
System.out.println("不是项目文件夹~");
return false;
}
/**
* 修正 xcode 项目的资源索引数据文件~
* @param filePbxproj
*/
public static List<String> repairPbxprojFile(File filePbxproj) {
/**
* 将 project.pbxproj 文件的内容读成一个行字符串列表~
* 与此同时,移除 Versions 的标记行(正是这些标记行导致 XCode 工程文件被损坏而打不开)~
*/
List<String> listStrToWriteBack = new LinkedList<String>();
List<String> listStrToReturn = new LinkedList<String>();
try {
FileReader fr = new FileReader(filePbxproj);
BufferedReader br = new BufferedReader(fr);
// 1.读~
String strLine = null;
// int i = 1;
do {
strLine = br.readLine();
if(strLine != null) {
if(strLine.startsWith("<<<<<<<") ||
strLine.startsWith("=======") ||
strLine.startsWith(">>>>>>>")) {
listStrToReturn.add(strLine);
} else {
listStrToWriteBack.add(strLine);
}
// System.out.println(i + strLine);
// i ++;
}
} while (strLine != null);
br.close();
// 2.写~
FileWriter fw = new FileWriter(filePbxproj);
BufferedWriter bw = new BufferedWriter(fw);
for(String strLineItem : listStrToWriteBack) {
bw.write(strLineItem + '\n');
}
bw.flush();
bw.close();
} catch (Exception e) {
e.printStackTrace();
}
return listStrToReturn;
}
/**
* @param args
*/
public static void main(String[] args) {
// 0.做即时 log~
StringBuffer sbReport = new StringBuffer("执行结果报告:\n");
// 1.检查是否为项目文件夹~
if(!isProjectDir(PROJECT_DIR)) {
sbReport.append("1.检查是否为项目文件夹:\n" + PROJECT_DIR
+ "\n不是一个有效的 XCode 工程文件夹~\n\n");
return;
}
sbReport.append("1.检查是否为项目文件夹:\n" + PROJECT_DIR
+ "\n是一个有效的 XCode 工程文件夹~\n\n");
// 2.找到 .xcodeproj
File fileXcodeproj = null;
for(File fileItem : new File(PROJECT_DIR).listFiles()) {
if(fileItem.getName().endsWith(".xcodeproj")) {
fileXcodeproj = fileItem;
sbReport.append("2.找到 \"*.xcodeproj\" 文件:\n"
+ fileItem.getAbsolutePath() + "\n\n");
break;
}
}
// 3.遍历 .xcodeproj 包下面的子文件、子目录~
// 遍历的过程中不能执行删除操作,找个数组将要删除的文件先装起来再说~
List<File> listFilesToDelete = new LinkedList<File>();
// 被标识为冲突的 project.pbxproj 文件,也要将其内部的一些行给清除掉~
File filePbxproj = null;
for(File fileItem : fileXcodeproj.listFiles()) {
if(fileItem.getName().equals("project.pbxproj")) {
sbReport.append("3.找到 \"project.pbxproj\" 文件:\n"
+ fileItem.getAbsolutePath() + "\n\n");
// conflicted "project.pbxproj"~
filePbxproj = fileItem;
} else if(fileItem.getName().startsWith("project.pbxproj")) {
// project.pbxproj.r163,project.pbxproj.r173,project.pbxproj.mine,etc~
listFilesToDelete.add(fileItem);
}
}
// 4.执行删除~
sbReport.append("4.执行删除 \"project.pbxproj.*\" 文件的操作:\n");
for(File fileItem : listFilesToDelete) {
if(!fileItem.delete()) {
System.err.println("删除过程中出现故障,返回~");
return;
}
sbReport.append("删除文件:\"" + fileItem.getAbsolutePath() + "\"\n");
}
sbReport.append('\n');
// 5.执行修改操作~
sbReport.append("5.执行修改 \"project.pbxproj\" 文件的操作:\n");
List<String> listDeletedLine = repairPbxprojFile(filePbxproj);
int iRowCount = 0;
for(String strLineItem : listDeletedLine) {
sbReport.append("删除行:\"" + strLineItem + "\"\n");
iRowCount += 1;
}
sbReport.append("统计:\"project.pbxproj\" 文件中总共有" + iRowCount + "行被删除~\n");
sbReport.append("报告完毕!");
// 6.显示 log~
new EchoJf(sbReport);
}
}
EchoJf.java
package org.bruce.versions.patch.entry;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Toolkit;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
/**
* @author Bruce Yang
*
*/
public class EchoJf extends JFrame {
private static final long serialVersionUID = -6578080569933048958L;
/**
* @param sbLog
*/
public EchoJf(StringBuffer sbLog) {
// 标题~
this.setTitle("执行结果报告");
// 尺寸(480p)~
this.setSize(720, 480);
// 居中~
setComponentBoundsToCenterScreen(this);
// 添加组件,显示 log~
this.setLayout(new BorderLayout());
JTextArea jta = new JTextArea();
JScrollPane jsp = new JScrollPane();
jsp.setViewportView(jta);
jta.setLineWrap(true);
jta.setText(sbLog.toString());
jta.setEditable(false);
this.add(jsp, BorderLayout.CENTER);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
/**
* @param component
*/
public static void setComponentBoundsToCenterScreen(Component component) {
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
int x = (int)(screen.getWidth() - component.getWidth()) / 2;
int y = (int)(screen.getHeight() - component.getHeight()) / 2;
component.setBounds(x, y, component.getWidth(), component.getHeight());
}
}