// p4.e // // Wed, 08/16/2000 06:31:02 // // Purpose: // // This extension is designed to interact with the perforce // version control system. A problem with most VCS systems is // that in the typical multi-programmer environment files are // stored locally as Read Only files. This is only a problem in // the sense that the programmer must explicitly lock the file // before editing it. This extension handles typical a check out // with edit, differences with current tip revisions and current // revision history. // // Usage: // // Compile and load this extension. The only assumptions made by // this extensions are: // // 1) The VCS system is set up for Multi-Programmer lock // use with IDs and the local files are stored in read // only format when not checked out (a requirement for // most systems). // 2) The VCS system is configured to automatically find // it's log files based on the directory in which the // file being edited resides. // // If 1) is not true, you do not need this extension. If 4) is // not true, then you will need to modify the cfg file for the // system in use to make this true. // // Details: // // The basic thread of each command is to save the current editing // environment, perform the command and restore the system. In // order: // // The current point, mark, buffer and filename are stored. // If the file is modified, it is saved. A temporary buffer // is created to maintain the window space of the buffer (for // the current window only). At the end of this, the current // process buffer, error_point and grep variables are saved. // The reason the the process save is made is to facilitate // changes required as a result of compiler or grep searches // without interrupting the current search. In addition, any // concurrent processes are suspended. // // Once the command is complete, the process buffers are // restored, the new file is re-read and placed in the saved // window and the concurrent shell if any are restored. The // buffer point and mark are restored and the grep/compile // state is reset. // // The commands are not bound to any keys. These are not really // keystroke operations and were forced to the A-X command line. // If you need them bound to keys, go right ahead. // // The modifications to this code are dedicated to the public domain // with the caveat that Lugaru is welcome to use this within their // distribution source code which is supplied with Epsilon. Use it // any way you want for whatever purposes. // // // John Kercheval // 4002 NE 204th St // Lake Forest Park, WA 98155-1606 // INTERNET: kercheval@bigfoot.com // // // Modification History: // // J. Kercheval Wed, 08/16/2000 05:40:09 Creation from VCS extension // #include #include #define BOOLEAN int #define TRUE 1 #define FALSE 0 #define _A_NORMAL 0x00 // Normal file - No read/write restrictions #define _A_RDONLY 0x01 // Read only file #define _A_HIDDEN 0x02 // Hidden file #define _A_SYSTEM 0x04 // System file #define _A_VOLID 0x08 // Volume ID file #define _A_SUBDIR 0x10 // Subdirectory #define _A_ARCH 0x20 // Archive file #define PROCESS_BUFFER "process" #define PROCESS_RESTORE_BUFFER "-P4OldProcess-" #define PROCESS_RESULT_BUFFER "p4-result" char _P4Buffer[FNAMELEN]; // current buffer name char _P4Filename[FNAMELEN]; // current buffer associated file name char _P4EditPath[FNAMELEN]; // the path of the current epsilon session char _P4TargetPath[FNAMELEN]; // the path of the current buffer char *_P4TempBuf; // a temp buffer to maintain the current window int _P4Point; // current buffer point int _P4Mark; // current buffer mark BOOLEAN _P4Translate; // current buffer is translating int _P4ProcessErrorSpot; // error_spot in process buffer int _P4ProcessPoint; // point for process buffer int _P4ProcessMark; // mark for process buffer BOOLEAN _P4ProcessHadErrors; // _had_errors in process buffer BOOLEAN _P4ProcessLastWasGrep; // _last_was_grep in process buffer BOOLEAN _P4RestoreProcessBuffer; // true if the process buffer is stored BOOLEAN _P4RestartConcurrent; // true if process buffer was concurrent #define PROMPT_LENGTH 2*FNAMELEN //////////////////////////////////////////////////////////////////////////// // // IsFile() returns FALSE if the buffer does not have an associated // filename // // BOOLEAN IsFile(f) char *f; { return strlen(f); } //////////////////////////////////////////////////////////////////////////// // // IsWritable() returns FALSE if the file is write only and TRUE // otherwise // // BOOLEAN IsWritable(f) char *f; { struct file_info fi; // // validate a valid parameter // if (!IsFile(f)) { return FALSE; } // // check the file attribute, if it doesn't exist then it is writable // if (!check_file(f, &fi)) { return TRUE; } if (fi.attr & _A_RDONLY && !(fi.attr & _A_SUBDIR)) { return FALSE; } else { return TRUE; } } //////////////////////////////////////////////////////////////////////////// // // showResults will display the p4 result buffer appropriately // // void showResults(buf) char *buf; { quiet_set_bookmark(); bufname = buf; point = 0; locate_window(buf, ""); } //////////////////////////////////////////////////////////////////////////// // // P4ProcessSave() save any current process buffer. VERY few // changes to do_push(). Just copy it over on updates and reedit // changes in. // // P4ProcessSave() { save_var bufname; // // save the process buffer if there is one currently around // _P4RestoreProcessBuffer = FALSE; if (exist(PROCESS_BUFFER)) { _P4RestoreProcessBuffer = TRUE; _P4ProcessHadErrors = _had_errors; _P4ProcessLastWasGrep = _last_was_grep; _P4ProcessPoint = point; _P4ProcessMark = mark; if (spot_to_buffer(error_spot) > 0) { _P4ProcessErrorSpot = *error_spot; } else { _P4ProcessErrorSpot = 0; } _P4RestartConcurrent = another_process() ? TRUE : FALSE; try_exit_concurrent(); delete_buffer(PROCESS_RESTORE_BUFFER); bufname = PROCESS_BUFFER; change_buffer_name(PROCESS_RESTORE_BUFFER); } } //////////////////////////////////////////////////////////////////////////// // // P4ProcessRestore() restore the current process buffer // // P4ProcessRestore() { save_var bufname; // // move the process buffer to the results buffer // if (exist(PROCESS_BUFFER)) { delete_buffer(PROCESS_RESULT_BUFFER); bufname = PROCESS_BUFFER; change_buffer_name(PROCESS_RESULT_BUFFER); } // // restore the previous process buffer // if (_P4RestoreProcessBuffer) { delete_buffer(PROCESS_BUFFER); bufname = PROCESS_RESTORE_BUFFER; change_buffer_name(PROCESS_BUFFER); if (_P4RestartConcurrent) { maybe_restart_concurrent(); } // // re-establish the error spot // point = _P4ProcessErrorSpot <= size() ? _P4ProcessErrorSpot : size(); set_error_spot(); // // re-establish the point and the mark // point = _P4ProcessPoint <= size() ? _P4ProcessPoint : size(); mark = _P4ProcessMark <= size() ? _P4ProcessMark : size(); // // reset correct state // _had_errors = _P4ProcessHadErrors; _last_was_grep = _P4ProcessLastWasGrep; } iter = 0; has_arg = 0; } //////////////////////////////////////////////////////////////////////////// // // P4DoPush() performs a simpler and kinder do_push. This push does // not modify the current directory for Epsilon at all (the variable // start_process_in_buffer_directory is completely ignored). This // function will also not abort on error. // // int P4DoPush(cmdline, cap, show) char *cmdline; // cap nonzero means capture output, show means show it to user { // but show -1 means don't wait for keystroke char *s, dir[FNAMELEN], old[FNAMELEN]; int result, tbuf; if (!(has_feature & FEAT_MULT_CONCUR) && !another_process()) save_var restart_concurrent = 0; if (opsys == OS_DOS && !is_gui && !is_win32) try_exit_concurrent(); if (is_unix == IS_UNIX_XWIN && !*cmdline) cmdline = push_cmd_unix_interactive; iter = 1; before_push(); getcd(old); get_buffer_directory(dir); chdir(dir); note(cmdline); result = shell("", cmdline, cap ? bufnum_to_name(tbuf = tmp_buf()) : ""); chdir(old); if (cap) { process_captured(tbuf, cmdline); buf_delete(tbuf); } else if (cmdline[0] && show != -1 && !is_gui && !is_win32 && !is_unix) { if (!char_avail()) { s = "Press any key to return to Epsilon"; term_write(0, screen_lines - 1, s, strlen(s), color_class after_exiting, 1); term_position(strlen(s), screen_lines - 1); } getkey(); } after_push(); if (cmdline[0] && cap && show) { locate_window(PROCBUF, ""); point = size(); } maybe_restart_concurrent(); if (cmdline[0] && result) say("Process returned %d", result); else check_dates(0); return result; } //////////////////////////////////////////////////////////////////////////// // // P4Start() validates a buffer and saves the current buffer state // // BOOLEAN P4Start() { char *str; // // save the current buffer state and file // _P4Point = point; _P4Mark = mark; _P4Translate = translation_type; strcpy(_P4Buffer, bufname); strcpy(_P4Filename, filename); if (modified && IsWritable(filename)) { do_save_file(0, 1, 1); } // // obtain the current edit path and change to target path // getcd(_P4EditPath); str = get_tail(filename, TRUE); strcpy(_P4TargetPath, filename); _P4TargetPath[strlen(filename) - strlen(str)] = '\0'; chdir(_P4TargetPath); // // create a temporary buffer and use it to maintain the window // _P4TempBuf = temp_buf(); to_buffer(_P4TempBuf); // // remove the current file buffer // delete_buffer(_P4Buffer); // // tuck away the old existing process buffer if it exists // P4ProcessSave(); return TRUE; } //////////////////////////////////////////////////////////////////////////// // // P4End() restores a buffer // // BOOLEAN P4End() { // // restore the target buffers vital stats // find_in_other_buf(_P4Filename, _P4Translate); point = _P4Point <= size() ? _P4Point : size(); mark = _P4Mark <= size() ? _P4Mark : size(); chdir(_P4EditPath); delete_buffer(_P4TempBuf); // // save the current process and restore the previous one // P4ProcessRestore(); return TRUE; } //////////////////////////////////////////////////////////////////////////// // // P4Submit takes a command string and passes it to perforce // // void P4Submit(cmdString, showResult) char *cmdString; BOOLEAN showResult; { char prompt[PROMPT_LENGTH]; char filename_nodir[FNAMELEN]; int result = 0; // // obtain the relative filename Create the command line // strcpy(filename_nodir, get_tail(filename, 0)); sprintf(prompt, cmdString, filename_nodir); // // perform the Check Out // P4Start(); result = P4DoPush(prompt, TRUE, FALSE); P4End(); // // view the results // if (result || showResult) { showResults(PROCESS_RESULT_BUFFER); } } //////////////////////////////////////////////////////////////////////////// // // Perforce commands // command p4Add() { P4Submit("p4 add %s", FALSE); } command p4Edit() { P4Submit("p4 edit %s", FALSE); } command get() { P4Submit("p4 edit %s", FALSE); } command getl() { P4Submit("p4 edit %s", FALSE); } command p4Delete() { P4Submit("p4 delete %s", FALSE); } command p4Diff() { P4Submit("p4 diff %s", TRUE); } command vdiff() { P4Submit("p4 diff %s", TRUE); } command p4FileLog() { P4Submit("p4 filelog %s", TRUE); } command vlog() { P4Submit("p4 filelog %s", TRUE); } command p4Lock() { P4Submit("p4 lock %s", FALSE); } command p4Revert() { P4Submit("p4 revert %s", FALSE); } command p4Sync() { P4Submit("p4 sync %s", FALSE); } //////////////////////////////////////////////////////////////////////////// // // replace_in_readonly_hook() and replace_in_existing_hook() will // branch the file if it is readonly. This is the simplest error // handling and does not require user intervention if the file can // not be locked or the log files are not currently available. This // is also the most general solution. // // new_p4_replace_in_readonly_hook(old_readonly) { // // silence the compiler warning // old_readonly = old_readonly; // // get the file for use // P4Submit("p4 edit %s", FALSE); } REPLACE_FUNC("p4", "replace_in_readonly_hook") new_p4_replace_in_existing_hook(old_readonly) { // // silence the compiler warning // old_readonly = old_readonly; // // get the file for use // P4Submit("p4 edit %s", FALSE); } REPLACE_FUNC("p4", "replace_in_existing_hook")