AmendHub

jcs

/

amend

/

amendments

/

21

patch: Initial progress on patch applying

Unify on "patch" instead of "diff" terminology for export/apply

jcs made amendment 21 9 months ago
--- amend.h Mon Oct 18 13:09:14 2021 +++ amend.h Thu Oct 28 13:51:14 2021 @@ -33,7 +33,7 @@ #define REPO_MENU_ID 131 #define REPO_MENU_ADD_FILE_ID 1 #define REPO_MENU_REVERT_FILE_ID 2 -#define REPO_MENU_APPLY_DIFF_ID 3 +#define REPO_MENU_APPLY_PATCH_ID 3 #define COMMIT_MENU_ID 132 #define COMMIT_MENU_EXPORT_ID 1 --- browser.c Mon Oct 18 13:09:06 2021 +++ browser.c Thu Oct 28 16:12:25 2021 @@ -22,6 +22,7 @@ #include "browser.h" #include "committer.h" #include "diff.h" +#include "patch.h" #include "repo.h" #include "tetab.h" #include "util.h" @@ -68,10 +69,13 @@ browser_idle(struct browser *browser) browser_revert_file(browser); browser->state = BROWSER_STATE_IDLE; break; - case BROWSER_STATE_EXPORT_DIFF: - browser_export_diff(browser); + case BROWSER_STATE_EXPORT_PATCH: + browser_export_patch(browser); browser->state = BROWSER_STATE_IDLE; break; + case BROWSER_STATE_APPLY_PATCH: + browser_apply_patch(browser); + browser->state = BROWSER_STATE_IDLE; } } @@ -368,7 +372,7 @@ browser_revert_file(struct browser *browser) } void -browser_export_diff(struct browser *browser) +browser_export_patch(struct browser *browser) { Point pt = { 75, 100 }; Cell selected = { 0 }; @@ -382,14 +386,27 @@ browser_export_diff(struct browser *browser) len = sizeof(Ptr); LGetCell(&commit, &len, selected, browser->commit_list); - SFPutFile(pt, "\pSave diff as:", NULL, NULL, &reply); + SFPutFile(pt, "\pSave patch as:", NULL, NULL, &reply); if (!reply.good) return; - repo_export_diff(browser->repo, commit, reply.vRefNum, reply.fName); + repo_export_patch(browser->repo, commit, reply.vRefNum, reply.fName); } void +browser_apply_patch(struct browser *browser) +{ + Point pt = { 75, 100 }; + SFReply reply; + + SFGetFile(pt, NULL, NULL, -1, NULL, NULL, &reply); + if (!reply.good) + return; + + patch_process(browser->repo, reply.fName, reply.vRefNum); +} + +void browser_update_menu(struct browser *browser) { size_t vlines; @@ -418,7 +435,7 @@ browser_update_menu(struct browser *browser) EnableItem(repo_menu, REPO_MENU_ADD_FILE_ID); EnableItem(repo_menu, REPO_MENU_REVERT_FILE_ID); - EnableItem(repo_menu, REPO_MENU_APPLY_DIFF_ID); + EnableItem(repo_menu, REPO_MENU_APPLY_PATCH_ID); if (LGetSelect(true, &cell, browser->commit_list)) EnableItem(commit_menu, COMMIT_MENU_EXPORT_ID); --- browser.h Mon Oct 18 13:09:23 2021 +++ browser.h Thu Oct 28 14:05:02 2021 @@ -31,7 +31,8 @@ enum { BROWSER_STATE_COMMITTER_DO_DIFF, BROWSER_STATE_WAITING_FOR_COMMITTER, BROWSER_STATE_REVERT_FILE, - BROWSER_STATE_EXPORT_DIFF + BROWSER_STATE_EXPORT_PATCH, + BROWSER_STATE_APPLY_PATCH }; struct browser { @@ -56,10 +57,11 @@ void browser_update_menu(struct browser *browser); void browser_update(struct browser *browser, EventRecord *event); void browser_show_commit(struct browser *browser, struct repo_commit *commit); -void browser_export_diff(struct browser *browser); +void browser_export_patch(struct browser *browser); short browser_selected_file_ids(struct browser *browser, short **selected_files); void browser_mouse_down(struct browser *browser, EventRecord *event); +void browser_apply_patch(struct browser *browser); pascal void commit_list_ldef(short message, Boolean selected, Rect *cellRect, Cell theCell, short dataOffset, short dataLen, --- main.c Mon Oct 18 09:22:19 2021 +++ main.c Thu Oct 28 14:22:47 2021 @@ -257,7 +257,9 @@ handle_menu(long menu_id) if (cur_browser) cur_browser->state = BROWSER_STATE_REVERT_FILE; break; - case REPO_MENU_APPLY_DIFF_ID: + case REPO_MENU_APPLY_PATCH_ID: + if (cur_browser) + cur_browser->state = BROWSER_STATE_APPLY_PATCH; break; } break; @@ -265,7 +267,7 @@ handle_menu(long menu_id) switch (LoWord(menu_id)) { case COMMIT_MENU_EXPORT_ID: if (cur_browser) - cur_browser->state = BROWSER_STATE_EXPORT_DIFF; + cur_browser->state = BROWSER_STATE_EXPORT_PATCH; break; } break; @@ -287,7 +289,7 @@ update_menu(void) DisableItem(repo_menu, REPO_MENU_ADD_FILE_ID); DisableItem(repo_menu, REPO_MENU_REVERT_FILE_ID); - DisableItem(repo_menu, REPO_MENU_APPLY_DIFF_ID); + DisableItem(repo_menu, REPO_MENU_APPLY_PATCH_ID); DisableItem(commit_menu, COMMIT_MENU_EXPORT_ID); } --- patch.c Thu Oct 28 17:48:58 2021 +++ patch.c Thu Oct 28 17:48:58 2021 @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2021 joshua stein <jcs@jcs.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdio.h> +#include <string.h> + +#include "patch.h" +#include "util.h" + +static short patch_state; +static char patch_err[128]; + +enum { + PATCH_STATE_HEADER_FROM, + PATCH_STATE_TO, + PATCH_STATE_CHUNK_HEADER, + PATCH_STATE_CONTEXT +}; + +short patch_open_source_file(struct repo *repo, char *filename); +short patch_open_temp_dest_file(struct repo *repo, char *filename); + + +short +patch_open_source_file(struct repo *repo, char *filename) +{ + short error, ret, i; + Str255 pfilename; + + for (i = 0; i < repo->nfiles; i++) { + if (strcmp(repo->files[i]->filename, filename) != 0) + continue; + + memcpy(&pfilename, filename, sizeof(pfilename)); + CtoPstr(pfilename); + + error = FSOpen(pfilename, repo->vrefnum, &ret); + if (error && error == 1234) { + /* TODO: what does it return if file isn't there? */ + error = Create(pfilename, repo->vrefnum, + repo->files[i]->creator, repo->files[i]->type); + if (error && error != dupFNErr) + err(1, "Failed to create %s: %d", filename, error); + error = FSOpen(pfilename, repo->vrefnum, &ret); + } + + if (error) + err(1, "Failed to open %s: %d", filename, error); + + return ret; + } + + return -1; +} + +short +patch_open_temp_dest_file(struct repo *repo, char *filename) +{ + short error, ret; + char tmpfile[256]; + + if (sprintf(tmpfile, "%s.tmp", filename) > sizeof(tmpfile)) + err(1, "sprintf overflow!"); + + CtoPstr(tmpfile); + + error = Create(tmpfile, repo->vrefnum, AMEND_CREATOR, 'TEXT'); + if (error && error != dupFNErr) + err(1, "Failed to create %s: %d", PtoCstr(tmpfile), error); + error = FSOpen(tmpfile, repo->vrefnum, &ret); + if (error) + err(1, "Failed to open %s: %d", PtoCstr(tmpfile), error); + + return ret; +} + +short +patch_process(struct repo *repo, Str255 filename, short vrefnum) +{ + char tofilename[256] = { 0 }; + char buf[1024]; + size_t i; + long patch_size; + short linenum = 0, error, ret; + short linelen, patch_frefnum = -1, source_frefnum = -1, + dest_frefnum = -1; + char *line; + + error = FSOpen(filename, vrefnum, &patch_frefnum); + if (error) + err(1, "Failed to open patch %s: %d", PtoCstr(filename), error); + + error = GetEOF(patch_frefnum, &patch_size); + if (error) + err(1, "Failed to get size of patch %s: %d", PtoCstr(filename), + error); + + patch_state = PATCH_STATE_HEADER_FROM; + + for (i = 0; i < patch_size; i += linelen) { + linelen = FSReadLine(patch_frefnum, buf, sizeof(buf) - 1); + if (linelen < 0) + break; + buf[linelen] = '\0'; + linenum++; + line = buf; + + switch (patch_state) { + case PATCH_STATE_HEADER_FROM: + if (strncmp(line, "--- ", 4) != 0) + break; + patch_state = PATCH_STATE_TO; + break; + case PATCH_STATE_TO: + if (strncmp(line, "+++ ", 4) != 0) { + sprintf(patch_err, "Expected '+++ ' on line %d", linenum); + ret = -1; + goto patch_done; + } + line += 4; + linelen -= 4; + + tofilename[0] = '\0'; + for (i = 0; i < linelen; i++) { + if (line[i] == '\0' || line[i] == '\t') { + memcpy(&tofilename, line, i); + tofilename[i + 1] = '\0'; + break; + } + } + if (tofilename[0] == '\0') { + sprintf(patch_err, "Failed to parse filename after +++ " + "on line %d", linenum); + ret = -1; + goto patch_done; + } + + source_frefnum = patch_open_source_file(repo, tofilename); + if (source_frefnum == -1) { + ret = -1; + goto patch_done; + } + + dest_frefnum = patch_open_temp_dest_file(repo, tofilename); + if (dest_frefnum == -1) { + ret = -1; + goto patch_done; + } + + patch_state = PATCH_STATE_CHUNK_HEADER; + break; + case PATCH_STATE_CHUNK_HEADER: { + short source_line, source_delta, dest_line, dest_delta, count; + + if (strncmp(line, "@@ ", 3) != 0) { + sprintf(patch_err, "Expected '@@ ' on line %d", linenum); + ret = -1; + goto patch_done; + } + if (sscanf(line, "@@ %d,%d %d,%d @@%n", + &source_line, &source_delta, &dest_line, &dest_delta, + &count) != 4 || count < 1) { + sprintf(patch_err, "Malformed '@@ ' on line %d", linenum); + ret = -1; + goto patch_done; + } + break; + } + default: + err(1, "Invalid patch state %d", patch_state); + } + } + +patch_done: + if (patch_frefnum > -1) + FSClose(patch_frefnum); + if (source_frefnum > -1) + FSClose(source_frefnum); + if (dest_frefnum > -1) + FSClose(dest_frefnum); + + return ret; +} --- patch.h Thu Oct 28 14:37:57 2021 +++ patch.h Thu Oct 28 14:37:57 2021 @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2021 joshua stein <jcs@jcs.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "repo.h" + +short patch_process(struct repo *repo, Str255 filename, short vrefnum); --- repo.c Mon Oct 18 17:12:17 2021 +++ repo.c Thu Oct 28 13:54:06 2021 @@ -744,7 +744,7 @@ repo_diff_file(struct repo *repo, struct repo_file *fi } void -repo_export_diff(struct repo *repo, struct repo_commit *commit, +repo_export_patch(struct repo *repo, struct repo_commit *commit, short vrefnum, Str255 filename) { Handle diffh; --- repo.h Mon Oct 18 15:08:25 2021 +++ repo.h Thu Oct 28 13:53:59 2021 @@ -85,7 +85,7 @@ struct repo_file *repo_add_file(struct repo *repo); short repo_diff_file(struct repo *repo, struct repo_file *file); short repo_checkout_file(struct repo *repo, struct repo_file *file, short vrefnum, Str255 filename); -void repo_export_diff(struct repo *repo, struct repo_commit *commit, +void repo_export_patch(struct repo *repo, struct repo_commit *commit, short vrefnum, Str255 filename); void repo_commit(struct repo *repo, short *files, short nfiles, short adds, short subs, Handle log, short loglen, Handle diff, unsigned long difflen); --- util.c Mon Oct 18 13:14:58 2021 +++ util.c Thu Oct 28 17:01:17 2021 @@ -35,14 +35,9 @@ static TEHandle track_control_te = NULL; void vwarn(short alert_func, const char *format, va_list ap); -void -err_init(void) -{ - if (!(err_str = NewHandle(ERROR_STRING_SIZE))) { - SysBeep(20); - ExitToShell(); - } -} +/* + * Memory functions + */ void * xmalloc(size_t size) @@ -119,7 +114,42 @@ xstrdup(const char *str) return cp; } +/* + * String functions + */ + +short +getline(char *str, size_t len, char **ret) +{ + short i; + + for (i = 0; i < len; i++) { + if (str[i] == '\r' || i == len - 1) { + if (*ret == NULL) + *ret = xmalloc(i + 1); + memcpy(*ret, str, i + 1); + (*ret)[i] = '\0'; + return i + 1; + } + } + + return 0; +} + +/* + * BSD err(3) and warn(3) functions, must call err_init() before using + */ + void +err_init(void) +{ + if (!(err_str = NewHandle(ERROR_STRING_SIZE))) { + SysBeep(20); + ExitToShell(); + } +} + +void vwarn(short alert_func, const char *format, va_list ap) { size_t len; @@ -196,6 +226,10 @@ note(const char *format, ...) va_end(ap); } +/* + * Error checking wrappers for Mac toolkit functions + */ + Handle xNewHandle(unsigned long size) { @@ -233,6 +267,10 @@ xGetString(short id) } /* + * Filesystem utilities + */ + +/* * With SFGetFile, when the user has a folder selected and clicks Open, * File Manager will change to the folder and expect the user to select a * file in it. This triggers sfHookOpenFolder, but when the user double- @@ -415,6 +453,39 @@ is_dir(char *path) return false; } + +/* read a \r-terminated line or the final non-line bytes of an open file */ +OSErr +FSReadLine(short frefnum, char *buf, size_t buflen) +{ + char tbuf; + size_t pos, fsize, rlen = 1, total_read = 0; + short error, found = -1, i; + + GetFPos(frefnum, &pos); + GetEOF(frefnum, &fsize); + + for (; pos <= fsize; pos++) { + if (total_read > buflen) + return -1; + + error = FSRead(frefnum, &rlen, &tbuf); + if (error) + return -1; + + if (tbuf == '\r') + return total_read; + + buf[total_read++] = tbuf; + } + + /* nothing found until the end of the file */ + return total_read; +} + +/* + * General Mac-specific GUI functions + */ short FontHeight(short font_id, short size) --- util.h Mon Oct 18 13:09:59 2021 +++ util.h Thu Oct 28 17:01:09 2021 @@ -53,7 +53,6 @@ typedef struct stat { unsigned char st_flags; }; -void err_init(void); void *xmalloc(size_t); void *xmalloczero(size_t); void *xcalloc(size_t, size_t); @@ -61,14 +60,17 @@ void *xrealloc(void *src, size_t size); void *xreallocarray(void *, size_t, size_t); char *xstrdup(const char *); +short getline(char *str, size_t len, char **ret); +/* from strnatcmp.c */ +int strnatcmp(char const *a, char const *b); +int strnatcasecmp(char const *a, char const *b); + +void err_init(void); void warnx(const char *format, ...); void warn(const char *format, ...); void err(short retcode, const char *format, ...); void note(const char *format, ...); -int strnatcmp(char const *a, char const *b); -int strnatcasecmp(char const *a, char const *b); - Handle xNewHandle(unsigned long size); Handle xGetResource(ResType type, short id); StringHandle xGetString(short id); @@ -83,6 +85,7 @@ pascal Boolean open_dialog_filter(DialogPtr theDialog, EventRecord *theEvent, short *itemHit); bool is_dir(char *path); short stat(const char *path, struct stat *sb); +OSErr FSReadLine(short frefnum, char *buf, size_t buflen); short FontHeight(short font_id, short size); void DrawGrowIconOnly(WindowPtr win);