jcs
/amend
/amendments
/21
patch: Initial progress on patch applying
Unify on "patch" instead of "diff" terminology for export/apply
jcs made amendment 21 over 3 years 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);