001package org.vishia.gitGui; 002 003import java.io.File; 004import java.io.FileNotFoundException; 005import java.io.IOException; 006import java.text.SimpleDateFormat; 007import java.util.Date; 008import java.util.LinkedList; 009import java.util.List; 010import java.util.Map; 011 012import org.vishia.cmd.CmdExecuter; 013import org.vishia.cmd.JZtxtcmdFilepath; 014import org.vishia.gral.base.GralButton; 015import org.vishia.gral.base.GralGraphicTimeOrder; 016import org.vishia.gral.base.GralTable; 017import org.vishia.gral.base.GralTextBox; 018import org.vishia.gral.base.GralTextField; 019import org.vishia.gral.base.GralWindow; 020import org.vishia.gral.ifc.GralTableLine_ifc; 021import org.vishia.gral.ifc.GralUserAction; 022import org.vishia.gral.ifc.GralWindow_ifc; 023import org.vishia.util.DataAccess; 024import org.vishia.util.Debugutil; 025import org.vishia.util.FileList; 026import org.vishia.util.FilePath; 027import org.vishia.util.FileSystem; 028import org.vishia.util.KeyCode; 029import org.vishia.util.StringPartAppend; 030 031 032/**A Graphical User Interface for basic working in git with remote repository and preserving time stamp of the files. 033 * <ul> 034 * <li>Show a working tree with remote repository (option -git-dir on any git command call). 035 * <li>Lists all changed files from working tree and between any revisions. 036 * <li>supports commit: via a text file for the commit text (edit with any standard editor) and commit button. 037 * On any commit creates a new "_filelist.lst" via {@link FileList} which contains the time stamp of all files. 038 * <li>restore any file from any older version: select version, select file, right mouse "restore". (git checkout) 039 * with restoring the original time stamp. 040 * </ul> 041 * @author Hartmut Schorrig 042 * 043 */ 044public class GitGui 045{ 046 047 048 049 /**Version, history and license 050 * <ul> 051 * <li>2018-10-28 Hartmut new textfield for cmd, shows the last automatically cmd, enables assembled cmd for git and common cmd. 052 * <li>2018-10-27 Hartmut new {@link #openNewFileSelector(String, RevisionEntry)} not ready yet. 053 * <li>2018-10-27 Hartmut chg uses <code>diff --name-status</diff> instead <code>diff --name-only</diff> 054 * to build the input for the #wdgTableFiles to show different files. The table has an additional left row for the kind of difference 055 * adequate the output of <code>diff --name-status</diff>. 056 * <li>2018-10-10 Hartmut new {@link #guiRepository(JZtxtcmdFilepath)} as start operation. It checks whether "name.gitRepository" is given 057 * and copies "name.gitignore" to ".gitignore" and uses "name.filelist" as filelist. It supports more as one component on one working dir. 058 * <li>2018-10-10 Hartmut new {@link #getFilePathRepository(File)}. It is invoked in jzTc to get the opened repository by this GUI for add command. 059 * <li>2017-05-10 Hartmut bugfix {@link CmdExecuter#setCharsetForOutput(String)} to UTF-8 because git outputs cmd output in UTF-8 060 * <li>2016-12-02 Hartmut chg GitGui some improvements. 061 * <li>2016-09-23 Hartmut GitGui: ContextMenu in file table 062 * <li>2016-08-18 Hartmut this version is able to use to view the repository versions, the changed files per version, the changed file to the working tree. 063 * It supports view diff with invocation of an external tool. It is the first productive version. But yet with some specific settings yet now. 064 * TODO: read a config. Document it. Show the git command line for any action. 065 * <li>2016-08-24 Hartmut created 066 * </ul> 067 * 068 * 069 * <b>Copyright/Copyleft</b>: 070 * For this source the LGPL Lesser General Public License, 071 * published by the Free Software Foundation is valid. 072 * It means: 073 * <ol> 074 * <li> You can use this source without any restriction for any desired purpose. 075 * <li> You can redistribute copies of this source to everybody. 076 * <li> Every user of this source, also the user of redistribute copies 077 * with or without payment, must accept this license for further using. 078 * <li> But the LPGL is not appropriate for a whole software product, 079 * if this source is only a part of them. It means, the user 080 * must publish this part of source, 081 * but don't need to publish the whole source of the own product. 082 * <li> You can study and modify (improve) this source 083 * for own using or for redistribution, but you have to license the 084 * modified sources likewise under this LGPL Lesser General Public License. 085 * You mustn't delete this Copyright/Copyleft inscription in this source file. 086 * </ol> 087 * If you are intent to use this sources without publishing its usage, you can get 088 * a second license subscribing a special contract with the author. 089 * 090 * @author Hartmut Schorrig = hartmut.schorrig@vishia.de 091 * 092 * 093 */ 094 public final String sVersion = "2018-10-28"; 095 096 097 static class RevisionEntry 098 { 099 final String revisionHash; 100 String treeHash; 101 String parentHash; 102 String author; 103 Date dateAuthor; 104 String committer; 105 Date dateCommit; 106 String commitTitle; 107 StringBuilder commitText = new StringBuilder(200); 108 109 RevisionEntry(String hash) { revisionHash = hash; } 110 111 } 112 113 114 115 static class Settings 116 { 117 118 File dirTemp1 = new File("t:/git_tmp1"); 119 File dirTemp2 = new File("t:/git_tmp2"); 120 } 121 122 123 /**Action for open the commit text. 124 * 125 */ 126 GralUserAction actionOpenCommitText = new GralUserAction("actionTableOpenCommitText") 127 { @Override public boolean exec(int actionCode, org.vishia.gral.ifc.GralWidget_ifc widgd, Object... params) { 128 String[] args ={"cmd.exe", "/C", "edit", ".gitCommit"}; 129 wdgCmd.setText("cmd.exe /C edit .gitCommit"); 130 gitCmd.addCmd(args, null, listOut, null, workingDir, null); 131 wdgCommit.setText("do commit"); 132 return true; 133 } }; 134 135 136 /**This code snippet is executed after the 'git diff' command for 2 revisions are executed. 137 * It is used as last argument of {@link CmdExecuter#execute(String[], boolean, String, List, List, org.vishia.cmd.CmdExecuter.ExecuteAfterFinish)} 138 * and prepares the {@link #wdgTableFiles}. 139 */ 140 CmdExecuter.ExecuteAfterFinish exec_CommitDone = new CmdExecuter.ExecuteAfterFinish() 141 { @Override 142 public void exec(int errorcode, Appendable out, Appendable err) 143 { if(errorcode ==0) { 144 FileSystem.writeFile("\n", sWorkingDir + "/.gitcommit"); 145 wdgCommit.setText("commit done"); 146 } else { 147 wdgCommit.setText("commit error"); 148 } 149 } 150 }; 151 152 153 154 /**Action for do commi. 155 * 156 */ 157 GralUserAction actionCommit = new GralUserAction("actionCommit") 158 { @Override public boolean exec(int actionCode, org.vishia.gral.ifc.GralWidget_ifc widgd, Object... params) { 159 File gitCommit = new File(sWorkingDir, ".gitCommit"); 160 if(gitCommit.length() > 3) { 161 try{ FileList.list(sWorkingDir, "*", GitGui.this.sFileList); 162 } catch(Exception exc){ 163 wdgInfo.setText("_filelist.lst problem: " + exc.getMessage()); 164 } 165 String sGitCmd = "git"; 166 if(! sGitDir.startsWith(sWorkingDir)) { 167 sGitCmd += " '--git-dir=" + sGitDir + "'"; 168 } 169 sGitCmd += " commit -a -F .gitcommit"; 170 wdgCmd.setText(sGitCmd); 171 String[] args ={exepath.gitsh_exe, "-x", "-c", sGitCmd}; 172 gitCmd.addCmd(args, null, listOut, null, workingDir, exec_CommitDone); 173 } else { 174 wdgCommit.setText("do commit text?"); 175 } 176 return true; 177 } }; 178 179 180 /**Action for show the version table for the given file. 181 * 182 */ 183 GralUserAction actionRefresh = new GralUserAction("actionRefresh") 184 { @Override public boolean exec(int actionCode, org.vishia.gral.ifc.GralWidget_ifc widgd, Object... params) { 185 if(KeyCode.isControlFunctionMouseUpOrMenu(actionCode)){ 186 startLog(null); return true; 187 } //if; 188 return false; 189 } }; 190 191 192 193 194 GralUserAction actionTableLineVersion = new GralUserAction("actionTablelineVersion") 195 { @Override public boolean exec(int actionCode, org.vishia.gral.ifc.GralWidget_ifc widgd, Object... params) { 196 if(actionCode == KeyCode.userSelect) { 197 @SuppressWarnings("unchecked") 198 GralTable<RevisionEntry>.TableLineData line = (GralTable<RevisionEntry>.TableLineData) params[0]; 199 200 showLog_startRevisionDiff4FileTable(line); 201 } 202 return true; 203 } 204 }; 205 206 207 /**This code snippet is executed after the 'git diff' command for 2 revisions are executed. 208 * It is used as last argument of {@link CmdExecuter#execute(String[], boolean, String, List, List, org.vishia.cmd.CmdExecuter.ExecuteAfterFinish)} 209 * and prepares the {@link #wdgTableFiles}. 210 */ 211 CmdExecuter.ExecuteAfterFinish exec_fillRevisionTable = new CmdExecuter.ExecuteAfterFinish() 212 { @Override 213 public void exec(int errorcode, Appendable out, Appendable err) 214 { fillRevisionTable(); 215 } 216 }; 217 218 219 /**This code snippet is executed after the 'git diff' command for 2 revisions are executed. 220 * It is used as last argument of {@link CmdExecuter#execute(String[], boolean, String, List, List, org.vishia.cmd.CmdExecuter.ExecuteAfterFinish)} 221 * and prepares the {@link #wdgTableFiles}. 222 */ 223 CmdExecuter.ExecuteAfterFinish exec_fillFileTable4Revision = new CmdExecuter.ExecuteAfterFinish() 224 { @Override 225 public void exec(int errorcode, Appendable out, Appendable err) 226 { fillFileTable4Revision(); 227 } 228 }; 229 230 231 /**This code snippet is executed after the 'git diff' command for 2 revisions are executed. 232 * It is used as last argument of {@link CmdExecuter#execute(String[], boolean, String, List, List, org.vishia.cmd.CmdExecuter.ExecuteAfterFinish)} 233 * and prepares the {@link #wdgTableFiles}. 234 */ 235 CmdExecuter.ExecuteAfterFinish exec_ShowStatus = new CmdExecuter.ExecuteAfterFinish() 236 { @Override 237 public void exec(int errorcode, Appendable out, Appendable err) 238 { wdgInfo.setText(gitOut); //The status info 239 gitOut.buffer().setLength(0); //prepare for the next command. 240 gitOut.assign(gitOut.buffer()); //to reset positions to the changed gitOut.buffer() 241 } 242 }; 243 244 245 /**Action for mouse double to start view diff. 246 * 247 */ 248 GralUserAction actionTableFile = new GralUserAction("actionTableFile") 249 { @Override public boolean exec(int actionCode, org.vishia.gral.ifc.GralWidget_ifc widgd, Object... params) { 250 @SuppressWarnings("unchecked") 251 GralTable<RevisionEntry>.TableLineData line = (GralTable<RevisionEntry>.TableLineData)params[0]; //it is the table line. 252 253 String sFile = line.getKey(); //"org/vishia/inspcPC/InspCmd.java"; 254 switch(actionCode) { 255 case (KeyCode.ctrl | KeyCode.enter): 256 case KeyCode.mouse1Double: startDiffView(sFile, GitGui.this.currentEntry, GitGui.this.cmpEntry); return true; 257 case (KeyCode.ctrl | 's'): startLog(sFile); return true; 258 default: return false; 259 } //switch; 260 261 } }; 262 263 264 /**Action for mouse double to start view diff. 265 * 266 */ 267 GralUserAction actionRestore = new GralUserAction("actionRestore") 268 { @Override public boolean exec(int actionCode, org.vishia.gral.ifc.GralWidget_ifc widgd, Object... params) { 269 @SuppressWarnings("unchecked") 270 GralTable<RevisionEntry> table = (GralTable<RevisionEntry>)widgd; //it is the table line. 271 if(KeyCode.isControlFunctionMouseUpOrMenu(actionCode)){ 272 GralTable<RevisionEntry>.TableLineData line = table.getLineMousePressed(); //(GralTable<RevisionEntry>.TableLineData)params[0]; //it is the table line. 273 String sFile = line.getKey(); 274 restoreFile(sFile); 275 return true; 276 } //if; 277 return false; 278 } }; 279 280 281 /**Action for mouse double to start view diff. 282 * 283 */ 284 GralUserAction actionTableFileDiffView = new GralUserAction("actionTableFileDiffView") 285 { @Override public boolean exec(int actionCode, org.vishia.gral.ifc.GralWidget_ifc widgd, Object... params) { 286 @SuppressWarnings("unchecked") 287 GralTable<RevisionEntry> table = (GralTable<RevisionEntry>)widgd; //it is the table line. 288 289 GralTable<RevisionEntry>.TableLineData lineCurr = table.getCurrentLine(); //(GralTable<RevisionEntry>.TableLineData)params[0]; //it is the table line. 290 GralTable<RevisionEntry>.TableLineData line = table.getLineMousePressed(); //(GralTable<RevisionEntry>.TableLineData)params[0]; //it is the table line. 291 if(KeyCode.isControlFunctionMouseUpOrMenu(actionCode)){ 292 String sFile = line.getKey(); 293 startDiffView(sFile, GitGui.this.currentEntry, GitGui.this.cmpEntry); return true; 294 } //if; 295 return false; 296 } }; 297 298 299 GralUserAction actionTableFileRenMove = new GralUserAction("actionTableFileRenMove") 300 { @Override public boolean exec(int actionCode, org.vishia.gral.ifc.GralWidget_ifc widgd, Object... params) { 301 @SuppressWarnings("unchecked") 302 GralTable<RevisionEntry> table = (GralTable<RevisionEntry>)widgd; //it is the table line. 303 304 GralTable<RevisionEntry>.TableLineData lineCurr = table.getCurrentLine(); //(GralTable<RevisionEntry>.TableLineData)params[0]; //it is the table line. 305 GralTable<RevisionEntry>.TableLineData line = table.getLineMousePressed(); //(GralTable<RevisionEntry>.TableLineData)params[0]; //it is the table line. 306 if(KeyCode.isControlFunctionMouseUpOrMenu(actionCode)){ 307 String sFile = line.getKey(); 308 openNewFileSelector(sFile, GitGui.this.currentEntry); return true; 309 } //if; 310 return false; 311 } }; 312 313 314 /**Action for diff view of the current file to the workspace.*/ 315 GralUserAction actionDiffCurrWork = new GralUserAction("actionCurrFileDiffView") 316 { @Override public boolean exec(int actionCode, org.vishia.gral.ifc.GralWidget_ifc widgd, Object... params) { 317 if(KeyCode.isControlFunctionMouseUpOrMenu(actionCode)){ 318 GralTable<RevisionEntry>.TableLineData line = wdgTableVersion.getCurrentLine(); 319 RevisionEntry revision = line.getData(); 320 startDiffView(sLocalFile, null, revision); return true; 321 } //if; 322 return false; 323 } }; 324 325 326 /**Action for diff view of the current file between revisions.*/ 327 GralUserAction actionFileDiffRev = new GralUserAction("actionFileDiffRev") 328 { @Override public boolean exec(int actionCode, org.vishia.gral.ifc.GralWidget_ifc widgd, Object... params) { 329 if(KeyCode.isControlFunctionMouseUpOrMenu(actionCode)){ 330 GralTable<RevisionEntry>.TableLineData line = wdgTableVersion.getCurrentLine(); 331 RevisionEntry revision = line.getData(); 332 startDiffView(sLocalFile, null, revision); return true; 333 } //if; 334 return false; 335 } }; 336 337 338 /**Action for show the version table for the given file. 339 * 340 */ 341 GralUserAction actionTableFileLog = new GralUserAction("actionTableFileLog") 342 { @Override public boolean exec(int actionCode, org.vishia.gral.ifc.GralWidget_ifc widgd, Object... params) { 343 @SuppressWarnings("unchecked") 344 GralTable<RevisionEntry> table = (GralTable<RevisionEntry>)widgd; //it is the table line. 345 GralTable<RevisionEntry>.TableLineData lineCurr = table.getCurrentLine(); //(GralTable<RevisionEntry>.TableLineData)params[0]; //it is the table line. 346 GralTable<RevisionEntry>.TableLineData line = table.getLineMousePressed(); //(GralTable<RevisionEntry>.TableLineData)params[0]; //it is the table line. 347 if(KeyCode.isControlFunctionMouseUpOrMenu(actionCode)){ 348 String sFile = line.getKey(); //working tree has key "*" 349 startLog(sFile); return true; 350 } //if; 351 return false; 352 } }; 353 354 355 GralUserAction actionExecCmd = new GralUserAction("actionExecCmd") 356 { @Override public boolean exec(int actionCode, org.vishia.gral.ifc.GralWidget_ifc widgd, Object... params) { 357 if(KeyCode.isControlFunctionMouseUpOrMenu(actionCode)){ 358 String cmd = wdgCmd.getText(); 359 File cmdDir = workingDir; 360 int posDir = cmd.indexOf('>'); 361 if(posDir >0) { 362 cmdDir = new File(cmd.substring(0, posDir)); 363 cmd = cmd.substring(posDir+1); 364 } 365 String[] args = cmd.split(" "); 366 if(args[0].equals("git")) { 367 String[] args2 = args; 368 args = new String[4]; 369 args[0] = exepath.gitsh_exe; 370 args[1] = "-x"; 371 args[2] = "-c"; 372 args[3] = cmd; 373 } 374 gitOut.clear(); 375 gitCmd.addCmd(args, null, listOut, null, cmdDir, execShowListOut); 376 377 } //if; 378 return false; 379 } }; 380 381 /**Action for show the version table for the given file. 382 * 383 */ 384 GralUserAction actionAdd = new GralUserAction("actionAdd") 385 { @Override public boolean exec(int actionCode, org.vishia.gral.ifc.GralWidget_ifc widgd, Object... params) { 386 if(KeyCode.isControlFunctionMouseUpOrMenu(actionCode)){ 387 addFileFromSelection(); return true; 388 } //if; 389 return false; 390 } }; 391 392 393 394 /**Action for show the version table for the given file. 395 * 396 */ 397 GralUserAction actionMove = new GralUserAction("actionMove") 398 { @Override public boolean exec(int actionCode, org.vishia.gral.ifc.GralWidget_ifc widgd, Object... params) { 399 if(KeyCode.isControlFunctionMouseUpOrMenu(actionCode)){ 400 moveFileListToSelection(); return true; 401 } //if; 402 return false; 403 } }; 404 405 406 407 408 CmdExecuter.ExecuteAfterFinish execShowListOut = new CmdExecuter.ExecuteAfterFinish() 409 { @Override 410 public void exec(int errorcode, Appendable out, Appendable err) 411 { //if(errorcode ==0) { 412 wdgInfo.setText("cmd ouptut:\n"); 413 try { wdgInfo.append(gitOut); 414 } catch (IOException e) { } 415 } }; 416 417 418 /**The paths to the executables. */ 419 GitGuiPaths exepath; 420 421 /**The only one opened git repository. null if nothing is open or more as one is open. 422 * 423 */ 424 static File filePathRepository; 425 426 Settings settings = new Settings(); 427 428 String sTypeOfImplementation = "SWT"; //default 429 430 431 GralWindow window = new GralWindow("0+60, 0+90", "GitGui", "Git vishia", GralWindow_ifc.windResizeable | GralWindow_ifc.windRemoveOnClose); 432 433 GralTextField wdgCmd = new GralTextField("@2-2,0..-7=cmd", GralTextField.Type.editable); 434 435 GralButton wdgBtnCmd = new GralButton("@0..2, -6..0 = cmdExec", "exec", this.actionExecCmd); 436 437 438 GralTable<RevisionEntry> wdgTableVersion = new GralTable<>("@3..-30,0..-40=git-versions", new int[] {2, 10, 0, -10}); 439 440 GralTable<String> wdgTableFiles = new GralTable<>("@3..-30,-40..0=git-files", new int[] {3,20,0}); 441 442 GralTextBox wdgInfo = new GralTextBox("@-30..0, 0..-20=info"); 443 444 GralButton wdgBtnDiffCurrWork = new GralButton("@-29..-27, -18..-2 = diffCurrWork", "diff current file to workspace", this.actionDiffCurrWork); 445 446 GralButton wdgBtnDiffCurrFile = new GralButton("@-26..-24, -18..-2 = diffCurrFile", "diff current file", this.actionFileDiffRev); 447 448 GralButton wdgBtnAdd = new GralButton("@-18+2, -18..-8 = add", "add", this.actionAdd); 449 450 GralButton wdgBtnMove = new GralButton("@-15+2, -18..-8 = move", "mv", this.actionMove); 451 452 GralButton wdgRefresh = new GralButton("@-12+2, -18..-8 = refresh", "refresh", this.actionRefresh); 453 454 GralButton wdgCommitText = new GralButton("@-9+2, -18..-2 = commitText", "commit-text", this.actionOpenCommitText); 455 456 GralButton wdgCommit = new GralButton("@-6+2, -18..-2 = commit", "do commit", this.actionCommit); 457 458 /**If set to true, the {@link #cmdThread} should be aborted. 459 * 460 */ 461 boolean bCmdThreadClose; 462 463 CmdExecuter gitCmd = new CmdExecuter(); 464 465 466 /**Destination for output of all command line invocations. 467 * This buffer will be cleared and filled with the git command, and then parsed to present the result. 468 */ 469 StringPartAppend gitOut = new StringPartAppend(); 470 471 /**The {@link CmdExecuter#execute(String[], boolean, String, List, List, org.vishia.cmd.CmdExecuter.ExecuteAfterFinish)} 472 * needs a list of appendable, that is it.*/ 473 List<Appendable> listOut = new LinkedList<Appendable>(); 474 { listOut.add(gitOut); } 475 476 /**Stored arguments from {@link #startLog(String, String, String)}. */ 477 String sGitDir, sWorkingDir; //, sLocalFile; 478 479 File workingDir; 480 481 String sFileList = "_filelist.lst"; 482 483 /**If given the file which's log and diff should be shown. 484 * Elsewhere null. set in {@link #startLog(String)} 485 */ 486 String sLocalFile; 487 488 489 /**The presentation of the time stamps. */ 490 SimpleDateFormat dateFormat = new SimpleDateFormat("yy-MM-dd HH:mm"); 491 492 493 494 /**The current line and the line before, to the earlier commit. The Predecessor is not the parent in any case, not on branch and merge points. */ 495 //GralTable<RevisionEntry>.TableLineData currentLine, cmpLine; 496 GralTableLine_ifc<RevisionEntry> currentLine, cmpLine; 497 498 /**The current entry and the entry before, to the earlier commit. The Predecessor is not the parent in any case, not on branch and merge points. */ 499 RevisionEntry currentEntry, cmpEntry; 500 501 502 public static void main(String[] args){ 503 GitGui main = new GitGui(args); 504 //only test 505 main.startLog("D:/GitArchive/D/vishia/srcJava_vishiaBase/.git", "D:/GitArchive/D/vishia/srcJava_vishiaBase", "org/vishia/util/CalculatorExpr.java"); 506 main.doSomethinginMainthreadTillCloseWindow(); 507 } 508 509 510 public GitGui(String[] args) { 511 if(args !=null && args.length >=1){ 512 sTypeOfImplementation = args[0]; 513 } 514 initializeCmd(); 515 if(!settings.dirTemp1.exists()) { settings.dirTemp1.mkdirs(); } 516 if(!settings.dirTemp2.exists()) { settings.dirTemp2.mkdirs(); } 517 wdgTableVersion.specifyActionOnLineSelected(actionTableLineVersion); 518 wdgTableFiles.specifyActionChange("actionTableFile", actionTableFile, null); 519 window.specifyActionOnCloseWindow(actionOnCloseWindow); 520 window.create(sTypeOfImplementation, 'B', null, initGraphic); 521 cmdThread.start(); 522 } 523 524 525 GralGraphicTimeOrder initGraphic = new GralGraphicTimeOrder("init") 526 { @Override protected void executeOrder() { 527 wdgTableFiles.addContextMenuEntryGthread(1, "diffView", "Diff view [Mouse double], [ctrl-Enter]", actionTableFileDiffView); 528 wdgTableFiles.addContextMenuEntryGthread(1, "rename/move", "rename/move", actionTableFileRenMove); 529 wdgTableFiles.addContextMenuEntryGthread(1, "restore", "Restore this file", actionRestore); 530 wdgTableFiles.addContextMenuEntryGthread(1, "show Log for File", "Show log for this file [ctrl-s]", actionTableFileLog); 531 } 532 }; 533 534 535 536 537 GralUserAction actionOnCloseWindow = new GralUserAction("") 538 { @Override public boolean exec(int actionCode, org.vishia.gral.ifc.GralWidget_ifc widgd, Object... params) { 539 GitGui.this.gitCmd.close(); 540 bCmdThreadClose = true; 541 return true; 542 } 543 }; 544 545 546 @Override public void finalize() 547 { 548 } 549 550 void initializeCmd() { 551 gitCmd.setCharsetForOutput("UTF-8"); //git outputs in UTF-8 552 Map<String, String> env = gitCmd.environment(); 553 env.put("HOMEPATH", "\\vishia\\HOME"); 554 env.put("HOMEDRIVE", "D:"); 555 String sPath = env.get("PATH"); 556 if(sPath ==null) { sPath = env.get("Path"); } 557 if(sPath ==null) { sPath = env.get("path"); } 558 sPath = "D:\\Programs\\Gitcmd\\bin;" + sPath; 559 env.put("PATH", sPath); 560 } 561 562 563 public void doSomethinginMainthreadTillCloseWindow() 564 { int ix = 0; 565 while(! window.isGraphicDisposed()){ 566 try{ Thread.sleep(1000);} 567 catch (InterruptedException e) { } 568 } 569 570 } 571 572 573 574 575 576 /**Search the base dir and the repository for a given path/to/file. 577 * @param startFile Any file path. Especially a ".git" directory or ".gitRepository*" file. In that case the local file path will be set to null, 578 * because the whole repository is given. 579 * A ".gitRepository*" file contains the path to the extern .git directory. 580 * @param dst maybe null, elsewhere new String[2]. 581 * dst[0] will be filled with the absolute path of the .git directory. 582 * dst[1] will be filled with the absolute path to the basic directory for the working tree where .git or .gitRepository were found. 583 * dst[2] will be filled with the local file path or null if the .git or .gitRepositiory is the startFile. 584 * @param dstMap maybe null, if not null it is a container maybe from JZcmd variables or an empty Map. 585 * @param nameRepository if dstMap !=null, the name of the key (variable) for the repository path. 586 * @param nameBasedir if dstMap !=null, the name of the key (variable) for the base directory. 587 * @param nameLocalFile if dstMap !=null, the name of the key (variable) for the local file. 588 * That variables will be set. nameLocalFile will be set to null if .git or .gitRepository is the startFile. 589 * @return null on success or an error message 590 */ 591 public static String searchRepository(File startFile, String[] dst, Map<String, DataAccess.Variable<Object>> dstMap 592 , String nameRepository, String nameBasedir, String nameLocalFile) 593 { File fRepo = null; 594 File currDir; 595 String ret = null; 596 if(startFile.isDirectory()) { 597 currDir = startFile; 598 fRepo = new File(startFile, ".git"); //only to check whether it exists 599 if(!fRepo.exists()){ 600 fRepo = new File(startFile, ".gitRepository"); 601 if(!fRepo.exists()) { //if startFile as currdir contains .git or .gitRepository, it's okay 602 currDir = null; //not okay, startFile is any directory maybe inside a working tree 603 fRepo = null; 604 } 605 } 606 } else { //startFile is a file. 607 String fName = startFile.getName(); 608 if(fName.equals(".git") || fName.endsWith(".gitRepository")) { 609 fRepo = startFile; 610 currDir = startFile.getParentFile(); 611 } else { 612 currDir = null; //startFile is not a directory, search in parents. 613 } 614 } 615 if(currDir == null) { //startFile is not a directory, or it is a directory which does not contain .git or .gitRepository 616 currDir = startFile.getParentFile(); 617 //search whether a .git or .gitRepository exists and change to parent dir till it is found. 618 do{ 619 fRepo = new File(currDir, ".git"); //only to check whether it exists 620 if(!fRepo.exists()){ 621 fRepo = new File(currDir, ".gitRepository"); 622 if(!fRepo.exists()){ 623 fRepo = null; 624 try{ 625 currDir = FileSystem.getDirectory(currDir); //NOTE: currDir.getParent() is not successfully on relative dir "." 626 } catch(FileNotFoundException exc){ currDir = null;} 627 } 628 } 629 } while(fRepo ==null && currDir !=null); 630 } 631 // 632 //currdir and fRepo should be set, or not: 633 if(fRepo == null){ 634 ret = "searchRepository - .git or .gitRepository not found ;" + startFile.getAbsolutePath(); 635 } else { 636 assert(currDir !=null); //set together with fRepo 637 String sBaseDir = FileSystem.normalizePath(currDir).toString(); 638 //dst.put(bzrdir, sBzrDir); 639 String sRepository; 640 if(fRepo.getName().endsWith(".gitRepository")){ //File with link to repository 641 filePathRepository = fRepo; 642 sRepository = FileSystem.readFile(fRepo).trim(); 643 } else { 644 sRepository = sBaseDir; 645 } 646 CharSequence sFilePath = FileSystem.normalizePath(startFile); 647 String sLocalFilePath; 648 if(currDir == startFile) { 649 sLocalFilePath = null; 650 } else { 651 sLocalFilePath = sFilePath.subSequence(sBaseDir.length()+1, sFilePath.length()).toString(); 652 if(sLocalFilePath.length() == 0 || sLocalFilePath.equals(".git") || sLocalFilePath.endsWith(".gitRepository")){ 653 sLocalFilePath = null; //no local file. 654 } 655 } 656 if(dst !=null) { 657 dst[0] = sRepository; 658 dst[1] = sBaseDir; 659 dst[2] = sLocalFilePath; 660 } 661 if(dstMap !=null) { 662 try { 663 DataAccess.createOrReplaceVariable(dstMap, nameBasedir, 'S', sBaseDir, true); 664 DataAccess.createOrReplaceVariable(dstMap, nameRepository, 'S', sRepository, true); 665 DataAccess.createOrReplaceVariable(dstMap, nameLocalFile, 'S', sLocalFilePath, true); 666 } catch(Exception exc) { 667 ret = "searchRepository - repository found, but write problems for dst Map,"; 668 } 669 } 670 } 671 return ret; 672 } 673 674 675 676 /**Returns the opened repository or repoository linking file or searches the next .git or .gitRepository file 677 * in the parent dir. It is to return the correct repository linking file for the opened GUI 678 * to add something with Fcmd and jzTc 679 * @param dir The dir where a file should be handled. 680 * @return The opened filePathRepository only if the dir is in the same file tree. 681 * Elsewhere it searches .git or .gitRepository in this dir and parents. 682 */ 683 public static File getFilePathRepository(File startFileSearchRepository) { 684 if(filePathRepository !=null) { 685 String sFilePathRepository = FileSystem.getCanonicalPath(filePathRepository.getParentFile()); 686 String sstartFileSearchRepository = FileSystem.getCanonicalPath(startFileSearchRepository); 687 if(sstartFileSearchRepository.startsWith(sFilePathRepository)) { 688 return filePathRepository; //same or sub dir as filePathRepository: Proper. 689 } 690 } 691 //else: 692 return FileSystem.searchInParent(startFileSearchRepository, ".gitRepository", ".git"); //search either .gitRepository or .git 693 } 694 695 696 697 698 /**Searches the git repository and the root of the working tree and opens the window for the git gui. 699 * This routine can be invoked from any Java program without additional conditions. 700 * @param srcFile Any file to start searching the root of the working tree and the git repository inside this tree. 701 * If it is a directory and that directory contains either ".git" or ".gitRepository", it is the root of the working tree. 702 * Elsewhere the root will be searched backward to the root of the file system. 703 * It means you can start this routine with any srcFile inside the working tree. 704 * This file will be used as additional argument for example to show the history of that file. 705 */ 706 public static void guiRepository(GitGuiPaths exepath, JZtxtcmdFilepath repoFile) 707 { 708 File srcFile = null; 709 try { 710 //copy to the working version of .gitignore 711 File fIgnore = new File(repoFile.absname() + ".gitignore"); 712 if(fIgnore.exists()) { 713 File fIgnore2 = new File(repoFile.absdir() + "/.gitignore"); 714 FileSystem.copyFile(fIgnore, fIgnore2); 715 } 716 srcFile = new File(repoFile.absfile().toString()); 717 } catch (NoSuchFieldException | IOException e) { System.err.println(e.getMessage());} 718 String[] paths = new String[3]; 719 String error = searchRepository(srcFile, paths, null, null, null, null); 720 if(error !=null) { System.err.println(error); } 721 else { 722 GitGui main = new GitGui(null); 723 main.exepath = exepath; 724 main.sFileList = repoFile.name() + ".filelist"; 725 main.startLog(paths[0], paths[1], paths[2]); 726 } 727 } 728 729 730 /**Searches the git repository and the root of the working tree and opens the window for the git gui. 731 * This routine can be invoked from any Java program without additional conditions. 732 * @param srcFile Any file to start searching the root of the working tree and the git repository inside this tree. 733 * If it is a directory and that directory contains either ".git" or ".gitRepository", it is the root of the working tree. 734 * Elsewhere the root will be searched backward to the root of the file system. 735 * It means you can start this routine with any srcFile inside the working tree. 736 * This file will be used as additional argument for example to show the history of that file. 737 */ 738 public static void showLog(File srcFile) 739 { 740 String[] paths = new String[3]; 741 String error = searchRepository(srcFile, paths, null, null, null, null); 742 if(error !=null) { System.err.println(error); } 743 else { 744 GitGui main = new GitGui(null); 745 main.startLog(paths[0], paths[1], paths[2]); 746 } 747 } 748 749 750 751 752 public void startLog(String sGitDir, String sWorkingDir, String sLocalFile) { 753 this.sGitDir = sGitDir; 754 this.sWorkingDir = sWorkingDir; 755 this.workingDir = new File(sWorkingDir); 756 startLog(sLocalFile); 757 } 758 759 /** 760 * @param sLocalFile "*" for all files, else "path/in/loacal/tree/file.ext" 761 */ 762 void startLog(String sLocalFile) { 763 //this.sLocalFile = sLocalFile; 764 String sPathShow; 765 this.sLocalFile = sLocalFile; 766 if(sLocalFile !=null) { 767 sPathShow = sGitDir + " : " + sLocalFile; 768 wdgBtnDiffCurrFile.setVisible(true); 769 wdgBtnDiffCurrWork.setVisible(true); 770 } else { 771 sPathShow = sGitDir; 772 wdgBtnDiffCurrFile.setVisible(false); 773 wdgBtnDiffCurrWork.setVisible(false); 774 } 775 window.setTitle("Git " + sPathShow); 776 777 wdgTableVersion.clearTable(); 778 wdgTableVersion.addLine("*", new String[] {"", "", "wait for prepairing log", ""}, null); 779 gitOut.buffer().setLength(0); 780 gitOut.assign(gitOut.buffer()); //to reset positions to the changed gitOut.buffer() 781 String sGitCmd = "git"; 782 if(! sGitDir.startsWith(sWorkingDir)) { 783 sGitCmd += " '--git-dir=" + sGitDir + "'"; 784 } 785 sGitCmd += " log --date=iso '--pretty=raw'"; 786 if(sLocalFile !=null && sLocalFile.length() >0) { 787 sGitCmd += " -- '" + sLocalFile + "'"; 788 } 789 wdgCmd.setText(sGitCmd); 790 String[] args ={exepath.gitsh_exe, "-x", "-c", sGitCmd}; 791 gitCmd.clearCmdQueue(); 792 gitCmd.abortCmd(); 793 gitOut.buffer().setLength(0); 794 gitOut.assign(gitOut.buffer()); //to reset positions to the changed gitOut.buffer() 795 gitCmd.addCmd(args, null, listOut, null, workingDir, exec_fillRevisionTable); 796 synchronized(cmdThread) { cmdThread.notify(); } 797 //int error = cmd.execute(args, null, gitOut, null); 798 //fillRevisionTable(); 799 } 800 801 802 803 804 /**Fills the {@link #wdgTableVersion} with the gotten output after git log command. This routine is invoked as {@link #execAfterCmdLog}. 805 * 806 */ 807 void fillRevisionTable() { 808 gitOut.firstlineMaxpart(); 809 String lineTexts[] = new String[4]; 810 boolean contCommits = true; 811 wdgTableVersion.clearTable(); 812 lineTexts[0] = ""; 813 lineTexts[1] = "*"; 814 lineTexts[2] = "--working area --"; 815 lineTexts[3] = ""; 816 wdgTableVersion.addLine("*", lineTexts, null); 817 RevisionEntry entryLast = null; 818 do { 819 if(gitOut.scanStart().scan("commit ").scanOk()) { 820 contCommits = false; //set to true on the next "commit " line. 821 String hash = gitOut.getCurrentPart().toString(); 822 RevisionEntry entry = new RevisionEntry(hash); 823 try { 824 boolean cont = true; 825 while (cont && gitOut.nextlineMaxpart().found()) { 826 if(gitOut.scanStart().scan("tree ").scanOk()) { 827 entry.treeHash = gitOut.getCurrentPart().toString(); 828 } 829 else if(gitOut.scanStart().scan("parent ").scanOk()) { 830 entry.parentHash = gitOut.getCurrentPart().toString(); 831 } 832 else if(gitOut.scanStart().scan("author ").scanOk()) { 833 gitOut.lento('<'); 834 entry.author = gitOut.getCurrentPart().toString(); 835 gitOut.fromEnd().seekPosBack(16).lento(' '); 836 if(gitOut.scan().scanInteger().scanOk()) { 837 entry.dateAuthor = new Date(1000*gitOut.scan().getLastScannedIntegerNumber()); 838 } 839 } else if(gitOut.scanStart().scan("committer ").scanOk()) { 840 gitOut.lento('<'); 841 entry.committer = gitOut.getCurrentPart().toString(); 842 gitOut.fromEnd().seekPosBack(16).lento(' '); 843 if(gitOut.scan().scanInteger().scanOk()) { 844 entry.dateCommit = new Date(1000*gitOut.scan().getLastScannedIntegerNumber()); 845 } 846 } else { 847 do { 848 if(gitOut.scanStart().scan("commit ").scanOk()) { 849 gitOut.seekBegin(); 850 cont = false; 851 contCommits = true; 852 break; 853 } else { 854 CharSequence commitTextline = gitOut.getCurrentPart(); 855 if(entry.commitTitle == null && commitTextline.length() >6){ 856 entry.commitTitle = commitTextline.toString(); //first line with at least 6 character: Use as title. 857 } 858 entry.commitText.append(commitTextline).append('\n'); 859 } 860 } while(cont && (gitOut.nextlineMaxpart().found())); 861 } 862 }//while lines of one commit 863 }catch(Exception exc) { 864 Debugutil.stop(); 865 } 866 if(entry.dateAuthor !=null) { 867 String cL = "A"; 868 if(entryLast !=null && entryLast.parentHash !=null && entryLast.parentHash.equals(entry.revisionHash)){ cL = "B"; } 869 lineTexts[0] = cL; 870 lineTexts[1] = dateFormat.format(entry.dateAuthor); 871 lineTexts[2] = entry.commitTitle; 872 lineTexts[3] = entry.author; 873 GralTableLine_ifc<RevisionEntry> line = wdgTableVersion.addLine(hash, lineTexts, entry); 874 line.repaint(); 875 } 876 entryLast = entry; 877 } // 878 else { 879 contCommits = gitOut.nextlineMaxpart().found(); 880 } 881 } while(contCommits); 882 //wdgTableVersion. 883 wdgTableVersion.repaint(); 884 } 885 886 887 /**Starts the command 'git diff' to fill the {@link #wdgTableFiles} for the selected revision. 888 * 889 * @param line 890 */ 891 void showLog_startRevisionDiff4FileTable(GralTable<RevisionEntry>.TableLineData line) { 892 if(line == null){ 893 return; 894 } 895 // 896 //abort the current cmd for diff view (or any other) 897 gitCmd.clearCmdQueue(); 898 gitCmd.abortCmd(); 899 gitOut.buffer().setLength(0); 900 gitOut.assign(gitOut.buffer()); //to reset positions to the changed gitOut.buffer() 901 // 902 // 903 GitGui.this.currentLine = line; 904 GralTable<RevisionEntry> table = line.getTable(); 905 List<GralTableLine_ifc<RevisionEntry>> markedLines = table.getMarkedLines(1); 906 GitGui.this.cmpLine = line.nextSibling(); 907 if(markedLines !=null && markedLines.size() >0) { 908 if(markedLines.size() ==1) { 909 cmpLine = markedLines.get(0); 910 } 911 else { 912 System.err.println("more as one line marked"); 913 } 914 } 915 GitGui.this.currentEntry = line.data; 916 GitGui.this.cmpEntry = cmpLine == null ? null : cmpLine.getUserData(); 917 String sGitCmd = "git"; 918 if(! sGitDir.startsWith(sWorkingDir)) { 919 sGitCmd += " '--git-dir=" + sGitDir + "'"; 920 } 921 if(currentEntry ==null) { 922 String[] args ={exepath.gitsh_exe, "-x", "-c", sGitCmd + " status"}; 923 gitCmd.addCmd(args, null, listOut, null, workingDir, exec_ShowStatus); 924 wdgInfo.setText("(working area)"); 925 try{ 926 wdgInfo.append("\n"); 927 } catch(IOException exc){} 928 sGitCmd += " diff --name-status HEAD"; 929 } else { 930 wdgInfo.setText(currentEntry.revisionHash); 931 try{ 932 wdgInfo.append("=>").append(currentEntry.parentHash).append(" @").append(currentEntry.treeHash).append("\n"); 933 if(cmpEntry !=null) { 934 wdgInfo.append(cmpEntry.revisionHash).append("=>").append(cmpEntry.parentHash).append(" @").append(cmpEntry.treeHash).append("\n"); 935 } 936 wdgInfo.append(currentEntry.commitText); 937 } catch(IOException exc){} 938 sGitCmd += " diff --name-status " + currentEntry.parentHash + ".." + currentEntry.revisionHash; 939 } 940 wdgCmd.setText(sGitCmd); 941 String[] args ={exepath.gitsh_exe, "-x", "-c", sGitCmd}; 942 // 943 gitCmd.addCmd(args, null, listOut, null, workingDir, exec_fillFileTable4Revision); 944 synchronized(cmdThread) { cmdThread.notify(); } 945 } 946 947 948 949 void restoreFile(String sFile) { 950 String sGitCmd2 = "git '--git-dir=" + sGitDir + "' checkout " + cmpEntry.revisionHash + " -- " + this.sFileList; /// 951 wdgCmd.setText(sGitCmd2); 952 String[] args2 ={exepath.gitsh_exe, "-x", "-c", sGitCmd2}; 953 gitCmd.addCmd(args2, null, listOut, null, workingDir, null); 954 955 String sGitCmd = "git '--git-dir=" + sGitDir + "' checkout " + cmpEntry.revisionHash + " -- " + sFile; 956 String[] args ={exepath.gitsh_exe, "-x", "-c", sGitCmd}; 957 gitCmd.addCmd(args, null, listOut, null, workingDir, 958 new CmdExecuter.ExecuteAfterFinish() 959 { @Override 960 public void exec(int errorcode, Appendable out, Appendable err) 961 { try {FileList.touch(sWorkingDir, GitGui.this.sFileList, sFile, null); 962 } catch (IOException exc) { 963 System.err.println("IOexception for File.touch()"); 964 } 965 } 966 } 967 ); 968 969 970 971 // 972 973 // 974 } 975 976 977 978 void addFileFromSelection() { 979 int pos = wdgInfo.getCursorPos(); 980 String text = wdgInfo.getText(); 981 982 String sGitCmd = "git"; 983 if(! sGitDir.startsWith(sWorkingDir)) { 984 sGitCmd += " '--git-dir=" + sGitDir + "'"; 985 } 986 sGitCmd += " add "; 987 wdgCmd.setText(sGitCmd); 988 989 } 990 991 992 void moveFileListToSelection() { 993 int pos = wdgInfo.getCursorPos(); 994 String text = wdgInfo.getText(); 995 996 997 998 999 GralTable<String>.TableLineData line = wdgTableFiles.getCurrentLine(); 1000 int posSep = sGitDir.lastIndexOf('/'); 1001 assert(sGitDir.substring(posSep).equals("/.git")); 1002 String sGitCmd = sGitDir.substring(0, posSep) + ">git mv "; 1003 sGitCmd += line.getCellText(2) + "/" + line.getCellText(1) + " "; 1004 1005 wdgCmd.setText(sGitCmd); 1006 1007 1008 1009 1010 } 1011 1012 1013 /**Gets the file from repository and writes to tmp directory, starts diff tool 1014 * @param sFile The local path of the file 1015 * @param currRev maybe null, then compare with working tree, elsewhere a selected revision 1016 * @param cmpRev the selected revision to compare. 1017 */ 1018 void startDiffView(String sFile, RevisionEntry currRev, RevisionEntry cmpRev) { 1019 String sFile1 = settings.dirTemp1.getAbsolutePath() + "/" + sFile; 1020 String sFile2 = settings.dirTemp2.getAbsolutePath() + "/" + sFile; 1021 if(currRev == null) { 1022 sFile1 = sWorkingDir + "/" + sFile; 1023 } else { 1024 String sGitCmd = "git '--git-dir=" + sGitDir + "' checkout " + currRev.revisionHash + " -- " + sFile; 1025 String[] args ={exepath.gitsh_exe, "-x", "-c", sGitCmd}; 1026 gitCmd.addCmd(args, null, listOut, null, settings.dirTemp1, null); 1027 } 1028 if(cmpRev == null) { 1029 sFile2 = sFile1; 1030 } 1031 else { 1032 String sGitCmd = "git '--git-dir=" + sGitDir + "' checkout " + cmpRev.revisionHash + " -- " + sFile; 1033 wdgCmd.setText(settings.dirTemp2 + ">" + sGitCmd); 1034 String[] args ={exepath.gitsh_exe, "-x", "-c", sGitCmd}; 1035 gitCmd.addCmd(args, null, listOut, null, settings.dirTemp2, null); 1036 } 1037 sFile1 = sFile1.replace('/', '\\'); 1038 sFile2 = sFile2.replace('/', '\\'); 1039 String sCmdDiff = "cmd.exe /C start " + exepath.diff_exe + " " + sFile1 + " " + sFile2; 1040 String[] cmdDiffView = CmdExecuter.splitArgs(sCmdDiff); 1041 gitCmd.addCmd(cmdDiffView, null, listOut, null, null, null); 1042 synchronized(cmdThread) { cmdThread.notify(); } 1043 } 1044 1045 1046 1047 1048 void openNewFileSelector(String sFile, RevisionEntry currRev) { 1049 //wdgViewFiles.openDialog(FileRemote.fromFile(workingDir), "files", false, null); 1050 } 1051 1052 1053 1054 1055 /**Fills the file table with the gotten output after git diff name-status command. This routine is invoked as {@link #exec_fillFileTable4Revision}. 1056 * 1057 */ 1058 void fillFileTable4Revision() { 1059 wdgTableFiles.clearTable(); 1060 GralTableLine_ifc<String> line = wdgTableFiles.addLine("*", new String[] {"!","(all files)",""}, "*"); 1061 wdgTableFiles.repaint(); 1062 gitOut.firstlineMaxpart(); 1063 do { 1064 String sLine = gitOut.getCurrentPart().toString(); 1065 //the line starts with the status character, then '\t', then the file path. 1066 if(!sLine.startsWith("+ git") && sLine.length() >2) { 1067 String[] col = new String[3]; 1068 int posSlash = sLine.lastIndexOf('/'); 1069 col[0] = sLine.substring(0,1); 1070 if(posSlash >0) { 1071 col[1] = sLine.substring(posSlash+1); 1072 col[2] = sLine.substring(2, posSlash); 1073 } else { 1074 col[1] = sLine.substring(2); 1075 col[2] = ""; 1076 } 1077 String key = sLine.substring(2); //without "M\t" (sign of change from <code>diff --name-status</code> output ) 1078 line = wdgTableFiles.addLine(key, col, sLine); 1079 line.repaint(); 1080 } 1081 } while(gitOut.nextlineMaxpart().found()); 1082 } 1083 1084 1085 1086 Thread cmdThread = new Thread("gitGui-Cmd") { 1087 @Override public void run() { 1088 do { 1089 gitCmd.executeCmdQueue(true); 1090 try { 1091 synchronized(this){ wait(1000); } 1092 } catch (InterruptedException e) { } 1093 } while (!bCmdThreadClose); 1094 } 1095 }; 1096 1097 1098 1099 1100}