From 2744b2344dc42fa2a1ddf17f4818975cd48f6d42 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 11 Apr 2005 23:46:50 -0700 Subject: Start of early patch applicator tools for git. I looked a bit at my old BK tools for the same thing, but they were just so horrid in many ways that I largely rewrote it all and these tools do things a bit differently. Instead of aggressively piping data from one process to another (which was clever but very hard to follow), this first just splits out the mbox into many smaller email files, and then does some scripts on these temporary files. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3c518ba --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +CC=gcc +CFLAGS=-Wall -O2 +HOME=$(shell echo $$HOME) + +PROGRAMS=mailsplit mailinfo +SCRIPTS= + +all: $(PROGRAMS) + +install: $(PROGRAMS) $(SCRIPTS) + cp -f $(PROGRAMS) $(SCRIPTS) $(HOME)/bin/ + +clean: + rm -f $(PROGRAMS) *.o diff --git a/mailinfo.c b/mailinfo.c new file mode 100644 index 0000000..1ca554e --- /dev/null +++ b/mailinfo.c @@ -0,0 +1,300 @@ +/* + * Another stupid program, this one parsing the headers of an + * email to figure out authorship and subject + */ +#include +#include +#include +#include + +static FILE *cmitmsg, *patchfile; + +static char line[1000]; +static char name[1000]; +static char email[1000]; +static char subject[1000]; + +static char *sanity_check(char *name, char *email) +{ + int len = strlen(name); + if (len < 3 || len > 60) + return email; + if (strchr(name, '@') || strchr(name, '<') || strchr(name, '>')) + return email; + return name; +} + +static int handle_from(char *line) +{ + char *at = strchr(line, '@'); + char *dst; + + if (!at) + return 0; + + /* + * If we already have one email, don't take any confusing lines + */ + if (*email && strchr(at+1, '@')) + return 0; + + while (at > line) { + char c = at[-1]; + if (isspace(c) || c == '<') + break; + at--; + } + dst = email; + for (;;) { + unsigned char c = *at; + if (!c || c == '>' || isspace(c)) + break; + *at++ = ' '; + *dst++ = c; + } + *dst++ = 0; + + at = line + strlen(line); + while (at > line) { + unsigned char c = *--at; + if (isalnum(c)) + break; + *at = 0; + } + + at = line; + for (;;) { + unsigned char c = *at; + if (!c) + break; + if (isalnum(c)) + break; + at++; + } + + at = sanity_check(at, email); + + strcpy(name, at); + return 1; +} + +static void handle_subject(char *line) +{ + strcpy(subject, line); +} + +static void add_subject_line(char *line) +{ + while (isspace(*line)) + line++; + *--line = ' '; + strcat(subject, line); +} + +static void check_line(char *line, int len) +{ + static int cont = -1; + if (!memcmp(line, "From:", 5) && isspace(line[5])) { + handle_from(line+6); + cont = 0; + return; + } + if (!memcmp(line, "Subject:", 8) && isspace(line[8])) { + handle_subject(line+9); + cont = 1; + return; + } + if (isspace(*line)) { + switch (cont) { + case 0: + fprintf(stderr, "I don't do 'From:' line continuations\n"); + break; + case 1: + add_subject_line(line); + return; + default: + break; + } + } + cont = -1; +} + +static char * cleanup_subject(char *subject) +{ + for (;;) { + char *p; + int len, remove; + switch (*subject) { + case 'r': case 'R': + if (!memcmp("e:", subject+1, 2)) { + subject +=3; + continue; + } + break; + case ' ': case '\t': case ':': + subject++; + continue; + + case '[': + p = strchr(subject, ']'); + if (!p) { + subject++; + continue; + } + len = strlen(p); + remove = p - subject; + if (remove <= len *2) { + subject = p+1; + continue; + } + break; + } + return subject; + } +} + +static void cleanup_space(char *buf) +{ + unsigned char c; + while ((c = *buf) != 0) { + buf++; + if (isspace(c)) { + buf[-1] = ' '; + c = *buf; + while (isspace(c)) { + int len = strlen(buf); + memmove(buf, buf+1, len); + c = *buf; + } + } + } +} + +/* + * Hacky hacky. This depends not only on -p1, but on + * filenames not having some special characters in them, + * like tilde. + */ +static void show_filename(char *line) +{ + int len; + char *name = strchr(line, '/'); + + if (!name || !isspace(*line)) + return; + name++; + len = 0; + for (;;) { + unsigned char c = name[len]; + switch (c) { + default: + len++; + continue; + + case 0: case ' ': + case '\t': case '\n': + break; + + case '~': + break; + } + break; + } + /* remove ".orig" from the end - common patch behaviour */ + if (len > 5 && !memcmp(name+len-5, ".orig", 5)) + len -=5; + if (!len) + return; + printf("filename: %.*s\n", len, name); +} + +static void handle_rest(void) +{ + char *sub = cleanup_subject(subject); + cleanup_space(name); + cleanup_space(email); + cleanup_space(sub); + printf("Author: %s\nEmail: %s\nSubject: %s\n\n", name, email, sub); + FILE *out = cmitmsg; + + do { + /* Track filename information from the patch.. */ + if (!memcmp("---", line, 3)) { + out = patchfile; + show_filename(line+3); + } + + if (!memcmp("+++", line, 3)) + show_filename(line+3); + + fputs(line, out); + } while (fgets(line, sizeof(line), stdin) != NULL); + + if (out == cmitmsg) { + fprintf(stderr, "No patch found\n"); + exit(1); + } + + fclose(cmitmsg); + fclose(patchfile); +} + +static int eatspace(char *line) +{ + int len = strlen(line); + while (len > 0 && isspace(line[len-1])) + line[--len] = 0; + return len; +} + +static void handle_body(void) +{ + int has_from = 0; + + /* First line of body can be a From: */ + while (fgets(line, sizeof(line), stdin) != NULL) { + int len = eatspace(line); + if (!len) + continue; + if (!memcmp("From:", line, 5) && isspace(line[5])) { + if (!has_from && handle_from(line+6)) { + has_from = 1; + continue; + } + } + line[len] = '\n'; + handle_rest(); + break; + } +} + +static void usage(void) +{ + fprintf(stderr, "mailinfo msg-file path-file < email\n"); + exit(1); +} + +int main(int argc, char ** argv) +{ + if (argc != 3) + usage(); + cmitmsg = fopen(argv[1], "w"); + if (!cmitmsg) { + perror(argv[1]); + exit(1); + } + patchfile = fopen(argv[2], "w"); + if (!patchfile) { + perror(argv[2]); + exit(1); + } + while (fgets(line, sizeof(line), stdin) != NULL) { + int len = eatspace(line); + if (!len) { + handle_body(); + break; + } + check_line(line, len); + } + return 0; +} diff --git a/mailsplit.c b/mailsplit.c new file mode 100644 index 0000000..9379fbc --- /dev/null +++ b/mailsplit.c @@ -0,0 +1,144 @@ +/* + * Totally braindamaged mbox splitter program. + * + * It just splits a mbox into a list of files: "0001" "0002" .. + * so you can process them further from there. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int usage(void) +{ + fprintf(stderr, "mailsplit \n"); + exit(1); +} + +static int linelen(const char *map, unsigned long size) +{ + int len = 0, c; + + do { + c = *map; + map++; + size--; + len++; + } while (size && c != '\n'); + return len; +} + +static int is_from_line(const char *line, int len) +{ + const char *colon; + + if (len < 20 || memcmp("From ", line, 5)) + return 0; + + colon = line + len - 2; + line += 5; + for (;;) { + if (colon < line) + return 0; + if (*--colon == ':') + break; + } + + if (!isdigit(colon[-4]) || + !isdigit(colon[-2]) || + !isdigit(colon[-1]) || + !isdigit(colon[ 1]) || + !isdigit(colon[ 2])) + return 0; + + /* year */ + if (strtol(colon+3, NULL, 10) <= 90) + return 0; + + /* Ok, close enough */ + return 1; +} + +static int parse_email(const void *map, unsigned long size) +{ + unsigned long offset; + + if (size < 6 || memcmp("From ", map, 5)) + goto corrupt; + + /* Make sure we don't trigger on this first line */ + map++; size--; offset=1; + + /* + * Search for a line beginning with "From ", and + * having smething that looks like a date format. + */ + do { + int len = linelen(map, size); + if (is_from_line(map, len)) + return offset; + map += len; + size -= len; + offset += len; + } while (size); + return offset; + +corrupt: + fprintf(stderr, "corrupt mailbox\n"); + exit(1); +} + +int main(int argc, char **argv) +{ + int fd, nr; + struct stat st; + unsigned long size; + void *map; + + if (argc != 3) + usage(); + fd = open(argv[1], O_RDONLY); + if (fd < 0) { + perror(argv[1]); + exit(1); + } + if (chdir(argv[2]) < 0) + usage(); + if (fstat(fd, &st) < 0) { + perror("stat"); + exit(1); + } + size = st.st_size; + map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (-1 == (int)(long)map) { + perror("mmap"); + exit(1); + } + close(fd); + nr = 0; + do { + char name[10]; + unsigned long len = parse_email(map, size); + assert(len <= size); + sprintf(name, "%04d", ++nr); + fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd < 0) { + perror(name); + exit(1); + } + if (write(fd, map, len) != len) { + perror("write"); + exit(1); + } + close(fd); + map += len; + size -= len; + } while (size > 0); + return 0; +} -- cgit v0.10.2-6-g49f6 From 853916ff7f1a5b34bd2728fe81059e6270bac134 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 12 Apr 2005 01:40:20 -0700 Subject: Add "applypatch" and "dotest" scripts to tie it all together. This should be getting it all pretty close to a working setup. diff --git a/Makefile b/Makefile index 3c518ba..eca3a5d 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ CFLAGS=-Wall -O2 HOME=$(shell echo $$HOME) PROGRAMS=mailsplit mailinfo -SCRIPTS= +SCRIPTS=dotest applypatch all: $(PROGRAMS) diff --git a/applypatch b/applypatch new file mode 100755 index 0000000..2791d91 --- /dev/null +++ b/applypatch @@ -0,0 +1,35 @@ +#!/bin/sh +## +## applypatch takes four file arguments, and uses those to +## apply the unpacked patch (surprise surprise) that they +## represent to the current tree. +## +## The arguments are: +## $1 - file with commit message +## $2 - file with the actual patch +## $3 - file with list of filenames the patch touches +## $4 - "info" file with Author, email and subject +## +MSGFILE=$1 +PATCHFILE=$2 +FILES=$3 +INFO=$4 +export AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' .dotest/info)" +export AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' .dotest/info)" +export SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' .dotest/info)" + +echo +echo Applying $SUBJECT +echo + +(echo "[PATCH] $SUBJECT" ; echo ; cat $MSGFILE ) > .dotest/final-commit + +check-files $(cat $FILES) || exit 1 +patch -u --no-backup-if-mismatch -f -p1 --fuzz=0 --input=$PATCHFILE || exit 1 +update-cache --add --remove $(cat $FILES) || exit 1 +tree=$(write-tree) || exit 1 +echo Wrote tree $tree +commit=$(commit-tree $tree -p $(cat .git/HEAD) < .dotest/final-commit) || exit 1 +echo Committed: $commit +echo $commit > .git/HEAD + diff --git a/dotest b/dotest new file mode 100755 index 0000000..7d2c16b --- /dev/null +++ b/dotest @@ -0,0 +1,17 @@ +#!/bin/sh +## +## "dotest" is my stupid name for my patch-application script, which +## I never got around to renaming after I tested it. We're now on the +## second generation of scripts, still called "dotest". +## +## You give it a mbox-format collection of emails, and it will try to +## apply them to the kernel using "applypatch" +## +rm -rf .dotest +mkdir .dotest +mailsplit $1 .dotest || exit 1 +for i in .dotest/* +do + mailinfo .dotest/msg .dotest/patch .dotest/file < $i > .dotest/info || exit 1 + applypatch .dotest/msg .dotest/patch .dotest/file .dotest/info || exit 1 +done diff --git a/mailinfo.c b/mailinfo.c index 1ca554e..c1dcac1 100644 --- a/mailinfo.c +++ b/mailinfo.c @@ -7,7 +7,7 @@ #include #include -static FILE *cmitmsg, *patchfile; +static FILE *cmitmsg, *patchfile, *filelist; static char line[1000]; static char name[1000]; @@ -195,6 +195,7 @@ static void show_filename(char *line) case '\t': case '\n': break; + /* patch tends to special-case these things.. */ case '~': break; } @@ -205,7 +206,7 @@ static void show_filename(char *line) len -=5; if (!len) return; - printf("filename: %.*s\n", len, name); + fprintf(filelist, "%.*s\n", len, name); } static void handle_rest(void) @@ -270,13 +271,13 @@ static void handle_body(void) static void usage(void) { - fprintf(stderr, "mailinfo msg-file path-file < email\n"); + fprintf(stderr, "mailinfo msg-file path-file filelist-file < email\n"); exit(1); } int main(int argc, char ** argv) { - if (argc != 3) + if (argc != 4) usage(); cmitmsg = fopen(argv[1], "w"); if (!cmitmsg) { @@ -288,6 +289,11 @@ int main(int argc, char ** argv) perror(argv[2]); exit(1); } + filelist = fopen(argv[3], "w"); + if (!filelist) { + perror(argv[3]); + exit(1); + } while (fgets(line, sizeof(line), stdin) != NULL) { int len = eatspace(line); if (!len) { -- cgit v0.10.2-6-g49f6 -- cgit v0.10.2-6-g49f6 From 61096819949d9c7f6e7032ec949daba35cf57226 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 18 Apr 2005 17:40:32 -0700 Subject: Add quotes around the subject line that we print out as being applied. My brain just flipped when it tried to read the "Applying" as part of the explanation of the patch, and the sentence didn't make any sense. The quotes make it clear what's going on. diff --git a/applypatch b/applypatch index 2791d91..9799cf9 100755 --- a/applypatch +++ b/applypatch @@ -19,7 +19,7 @@ export AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' .dotest/info)" export SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' .dotest/info)" echo -echo Applying $SUBJECT +echo Applying "'$SUBJECT'" echo (echo "[PATCH] $SUBJECT" ; echo ; cat $MSGFILE ) > .dotest/final-commit -- cgit v0.10.2-6-g49f6 From ad4e9ce4f9b34c4e6dd941b3244bcf56a3e3d62d Mon Sep 17 00:00:00 2001 From: James Bottomley Date: Wed, 20 Apr 2005 08:23:00 -0700 Subject: [PATCH] make dotest more amenable to commit message editing This makes "dotest" a lot nicer to sue, especially for people who were used to editing the commit comments after-the-fact in BK, which git doesn't apply. he syntax is dotest [-q] mailbox [signoff] so the command line operates exactly as you're used to. If you supply the -q it will query before applying (I also added the [a]pply all the rest option). If the signoff file is absent, no signoff line gets added. There's also one addition in this: a checkout-cache line. I added that for poor saps like me whose laptop takes minutes to checkout a full build tree, so I can run dotest in a directory with no checked out files. diff --git a/applypatch b/applypatch index 9799cf9..397e4a0 100755 --- a/applypatch +++ b/applypatch @@ -9,27 +9,61 @@ ## $2 - file with the actual patch ## $3 - file with list of filenames the patch touches ## $4 - "info" file with Author, email and subject +## $5 - optional file containing signoff to add ## +signoff="$5" +final=.dotest/final-commit +## +## If this file exists, we ask before applying +## +query_apply=.dotest/.query_apply MSGFILE=$1 PATCHFILE=$2 FILES=$3 INFO=$4 +EDIT=${VISUAL:-$EDITOR} +EDIT=${EDIT:-vi} + export AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' .dotest/info)" export AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' .dotest/info)" export SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' .dotest/info)" +if [ -n "$signoff" -a -f "$signoff" ]; then + cat $signoff >> $MSGFILE +fi + +(echo "[PATCH] $SUBJECT" ; echo ; cat $MSGFILE ) > $final + +f=0 +[ -f $query_apply ] || f=1 + +while [ $f -eq 0 ]; do + echo "Commit Body is:" + echo "--------------------------" + cat $final + echo "--------------------------" + echo -n "Apply? [y]es/[n]o/[e]dit/[a]ccept all " + read reply + case $reply in + y|Y) f=1;; + n|N) exit 2;; # special value to tell dotest to keep going + e|E) $EDIT $final;; + a|A) rm -f $query_apply + f=1;; + esac +done + echo echo Applying "'$SUBJECT'" echo -(echo "[PATCH] $SUBJECT" ; echo ; cat $MSGFILE ) > .dotest/final-commit - check-files $(cat $FILES) || exit 1 +checkout-cache -q $(cat $FILES) || exit 1 patch -u --no-backup-if-mismatch -f -p1 --fuzz=0 --input=$PATCHFILE || exit 1 update-cache --add --remove $(cat $FILES) || exit 1 tree=$(write-tree) || exit 1 echo Wrote tree $tree -commit=$(commit-tree $tree -p $(cat .git/HEAD) < .dotest/final-commit) || exit 1 +commit=$(commit-tree $tree -p $(cat .git/HEAD) < $final) || exit 1 echo Committed: $commit echo $commit > .git/HEAD diff --git a/dotest b/dotest index 7d2c16b..a3e3d35 100755 --- a/dotest +++ b/dotest @@ -7,11 +7,26 @@ ## You give it a mbox-format collection of emails, and it will try to ## apply them to the kernel using "applypatch" ## +## dotest [ -q ] mail_archive [Signoff_file] +## rm -rf .dotest mkdir .dotest +case $1 in + + -q) touch .dotest/.query_apply + shift;; +esac mailsplit $1 .dotest || exit 1 for i in .dotest/* do mailinfo .dotest/msg .dotest/patch .dotest/file < $i > .dotest/info || exit 1 - applypatch .dotest/msg .dotest/patch .dotest/file .dotest/info || exit 1 + applypatch .dotest/msg .dotest/patch .dotest/file .dotest/info "$2" + ret=$? + if [ $ret -ne 0 ]; then + # 2 is a special exit code from applypatch to indicate that + # the patch wasn't applied, but continue anyway + [ $ret -ne 2 ] && exit $ret + fi done +# return to pristine +rm -fr .dotest -- cgit v0.10.2-6-g49f6 From 50eb31d10390f638be2a99f3b064afcd090c08ea Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Fri, 29 Apr 2005 14:56:18 -0700 Subject: Fix up "applypatch" for the big git rename Add "git-" prefixes to git commands. diff --git a/applypatch b/applypatch index 397e4a0..6e21409 100755 --- a/applypatch +++ b/applypatch @@ -57,13 +57,12 @@ echo echo Applying "'$SUBJECT'" echo -check-files $(cat $FILES) || exit 1 -checkout-cache -q $(cat $FILES) || exit 1 +git-check-files $(cat $FILES) || exit 1 +git-checkout-cache -q $(cat $FILES) || exit 1 patch -u --no-backup-if-mismatch -f -p1 --fuzz=0 --input=$PATCHFILE || exit 1 -update-cache --add --remove $(cat $FILES) || exit 1 -tree=$(write-tree) || exit 1 +git-update-cache --add --remove $(cat $FILES) || exit 1 +tree=$(git-write-tree) || exit 1 echo Wrote tree $tree -commit=$(commit-tree $tree -p $(cat .git/HEAD) < $final) || exit 1 +commit=$(git-commit-tree $tree -p $(cat .git/HEAD) < $final) || exit 1 echo Committed: $commit echo $commit > .git/HEAD - -- cgit v0.10.2-6-g49f6 From 8c7b86632c2e54ed98abf922101ee177de75e0eb Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 30 Apr 2005 10:58:41 -0700 Subject: Add "stripspace" program to clean up email commentary Remove multiple empty lines, and empty lines at beginning and end. diff --git a/Makefile b/Makefile index eca3a5d..b5be0c9 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ CC=gcc CFLAGS=-Wall -O2 HOME=$(shell echo $$HOME) -PROGRAMS=mailsplit mailinfo +PROGRAMS=mailsplit mailinfo stripspace SCRIPTS=dotest applypatch all: $(PROGRAMS) diff --git a/applypatch b/applypatch index 6e21409..97274a1 100755 --- a/applypatch +++ b/applypatch @@ -32,7 +32,7 @@ if [ -n "$signoff" -a -f "$signoff" ]; then cat $signoff >> $MSGFILE fi -(echo "[PATCH] $SUBJECT" ; echo ; cat $MSGFILE ) > $final +(echo "[PATCH] $SUBJECT" ; if [ -s $MSGFILE ]; then echo ; cat $MSGFILE; fi ) > $final f=0 [ -f $query_apply ] || f=1 diff --git a/dotest b/dotest index a3e3d35..ccc1f03 100755 --- a/dotest +++ b/dotest @@ -20,7 +20,8 @@ mailsplit $1 .dotest || exit 1 for i in .dotest/* do mailinfo .dotest/msg .dotest/patch .dotest/file < $i > .dotest/info || exit 1 - applypatch .dotest/msg .dotest/patch .dotest/file .dotest/info "$2" + stripspace < .dotest/msg > .dotest/msg-clean + applypatch .dotest/msg-clean .dotest/patch .dotest/file .dotest/info "$2" ret=$? if [ $ret -ne 0 ]; then # 2 is a special exit code from applypatch to indicate that diff --git a/stripspace.c b/stripspace.c new file mode 100644 index 0000000..96cd0a8 --- /dev/null +++ b/stripspace.c @@ -0,0 +1,48 @@ +#include +#include +#include + +/* + * Remove empty lines from the beginning and end. + * + * Turn multiple consecutive empty lines into just one + * empty line. + */ +static void cleanup(char *line) +{ + int len = strlen(line); + + if (len > 1 && line[len-1] == '\n') { + do { + unsigned char c = line[len-2]; + if (!isspace(c)) + break; + line[len-2] = '\n'; + len--; + line[len] = 0; + } while (len > 1); + } +} + +int main(int argc, char **argv) +{ + int empties = -1; + char line[1024]; + + while (fgets(line, sizeof(line), stdin)) { + cleanup(line); + + /* Not just an empty line? */ + if (line[0] != '\n') { + if (empties > 0) + putchar('\n'); + empties = 0; + fputs(line, stdout); + continue; + } + if (empties < 0) + continue; + empties++; + } + return 0; +} -- cgit v0.10.2-6-g49f6 From 62c1f6b4afc68f6448d5ce94b00380405aa101eb Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 1 May 2005 21:42:53 -0700 Subject: Get AUTHOR_DATE from the email Date: line Now that git does pretty reliable date parsing, we might as well get the date from the email itself. Of course, it's still questionable whether the date on the email is all that relevant, but it's certainly no worse than taking the commit date. diff --git a/applypatch b/applypatch index 97274a1..5007201 100755 --- a/applypatch +++ b/applypatch @@ -26,6 +26,7 @@ EDIT=${EDIT:-vi} export AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' .dotest/info)" export AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' .dotest/info)" +export AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' .dotest/info)" export SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' .dotest/info)" if [ -n "$signoff" -a -f "$signoff" ]; then diff --git a/mailinfo.c b/mailinfo.c index c1dcac1..13c1e95 100644 --- a/mailinfo.c +++ b/mailinfo.c @@ -10,6 +10,7 @@ static FILE *cmitmsg, *patchfile, *filelist; static char line[1000]; +static char date[1000]; static char name[1000]; static char email[1000]; static char subject[1000]; @@ -78,6 +79,11 @@ static int handle_from(char *line) return 1; } +static void handle_date(char *line) +{ + strcpy(date, line); +} + static void handle_subject(char *line) { strcpy(subject, line); @@ -99,6 +105,11 @@ static void check_line(char *line, int len) cont = 0; return; } + if (!memcmp(line, "Date:", 5) && isspace(line[5])) { + handle_date(line+6); + cont = 0; + return; + } if (!memcmp(line, "Subject:", 8) && isspace(line[8])) { handle_subject(line+9); cont = 1; @@ -107,7 +118,7 @@ static void check_line(char *line, int len) if (isspace(*line)) { switch (cont) { case 0: - fprintf(stderr, "I don't do 'From:' line continuations\n"); + fprintf(stderr, "I don't do 'Date:' or 'From:' line continuations\n"); break; case 1: add_subject_line(line); @@ -213,9 +224,10 @@ static void handle_rest(void) { char *sub = cleanup_subject(subject); cleanup_space(name); + cleanup_space(date); cleanup_space(email); cleanup_space(sub); - printf("Author: %s\nEmail: %s\nSubject: %s\n\n", name, email, sub); + printf("Author: %s\nEmail: %s\nSubject: %s\nDate: %s\n\n", name, email, sub, date); FILE *out = cmitmsg; do { -- cgit v0.10.2-6-g49f6 From c9049d41b8d91a198e4642b266e50a506024f0aa Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 17 May 2005 09:35:01 -0700 Subject: Update applypatch to use new GIT_-prefix environment variables. Avoid the warnings from newer git versions. diff --git a/applypatch b/applypatch index 5007201..160931d 100755 --- a/applypatch +++ b/applypatch @@ -24,9 +24,9 @@ INFO=$4 EDIT=${VISUAL:-$EDITOR} EDIT=${EDIT:-vi} -export AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' .dotest/info)" -export AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' .dotest/info)" -export AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' .dotest/info)" +export GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' .dotest/info)" +export GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' .dotest/info)" +export GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' .dotest/info)" export SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' .dotest/info)" if [ -n "$signoff" -a -f "$signoff" ]; then -- cgit v0.10.2-6-g49f6 From 6a9853cd4587c9dd16553b429c6d4a9d8111a396 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 17 May 2005 17:15:56 -0700 Subject: Make "applypatch" use the "-E" flag to patch. Always remove empty files, regardless of how the diff showed them to have become empty. diff --git a/applypatch b/applypatch index 160931d..9c191ae 100755 --- a/applypatch +++ b/applypatch @@ -60,7 +60,7 @@ echo git-check-files $(cat $FILES) || exit 1 git-checkout-cache -q $(cat $FILES) || exit 1 -patch -u --no-backup-if-mismatch -f -p1 --fuzz=0 --input=$PATCHFILE || exit 1 +patch -E -u --no-backup-if-mismatch -f -p1 --fuzz=0 --input=$PATCHFILE || exit 1 git-update-cache --add --remove $(cat $FILES) || exit 1 tree=$(git-write-tree) || exit 1 echo Wrote tree $tree -- cgit v0.10.2-6-g49f6 From 3e91311ae750af9bf2e3517b1e701288ac3066b9 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 24 May 2005 01:07:31 -0700 Subject: Add "cvs2git" program to convert a CVS archive into a GIT archive It's very hacky, and it needs lots of work, but it seems to have converted Peter's "syslinux" archive successfully. Whether the end result is correct or not is to be seen. Tons of work still to do: do name conversion properly, and do tags etc. And testing. Lots of testing. diff --git a/Makefile b/Makefile index b5be0c9..9998682 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ CC=gcc CFLAGS=-Wall -O2 HOME=$(shell echo $$HOME) -PROGRAMS=mailsplit mailinfo stripspace +PROGRAMS=mailsplit mailinfo stripspace cvs2git SCRIPTS=dotest applypatch all: $(PROGRAMS) diff --git a/cvs2git.c b/cvs2git.c new file mode 100644 index 0000000..c18f904 --- /dev/null +++ b/cvs2git.c @@ -0,0 +1,255 @@ +/* + * cvs2git + * + * Copyright (C) Linus Torvalds 2005 + */ + +#include +#include +#include +#include +#include + +static int verbose = 0; + +/* + * This is a really stupid program that takes cvsps output, and + * generates a a long _shell_script_ that will create the GIT archive + * from it. + * + * You've been warned. I told you it was stupid. + * + * NOTE NOTE NOTE! In order to do branches correctly, this needs + * the fixed cvsps that has the "Ancestor branch" tag output. + * Hopefully David Mansfield will update his distribution soon + * enough (he's the one who wrote the patch, so at least we don't + * have to figt maintainer issues ;) + */ +enum state { + Header, + Log, + Members +}; + +static char *rcsdir; + +static char date[100]; +static char author[100]; +static char branch[100]; +static char ancestor[100]; +static char tag[100]; +static char log[32768]; +static int loglen = 0; +static int initial_commit = 1; + +static void lookup_author(char *n, char **name, char **email) +{ + /* + * FIXME!!! I'm lazy and stupid. + * + * This could be something like + * + * printf("lookup_author '%s'\n", n); + * *name = "$author_name"; + * *email = "$author_email"; + * + * and that would allow the script to do its own + * lookups at run-time. + */ + *name = n; + *email = n; +} + +static void prepare_commit(void) +{ + char *author_name, *author_email; + char *src_branch; + + lookup_author(author, &author_name, &author_email); + + printf("export GIT_COMMITTER_NAME=%s\n", author_name); + printf("export GIT_COMMITTER_EMAIL=%s\n", author_email); + + printf("export GIT_AUTHOR_NAME=%s\n", author_name); + printf("export GIT_AUTHOR_EMAIL=%s\n", author_email); + + printf("export GIT_AUTHOR_DATE='%s'\n", date); + + if (initial_commit) + return; + + src_branch = *ancestor ? ancestor : branch; + if (!strcmp(src_branch, "HEAD")) + src_branch = "master"; + printf("ln -sf refs/heads/'%s' .git/HEAD\n", src_branch); + printf("git-read-tree -m HEAD\n"); + printf("git-checkout-cache -f -u -a\n"); +} + +static void commit(void) +{ + const char *cmit_parent = initial_commit ? "" : "-p HEAD"; + + printf("tree=$(git-write-tree)\n"); + printf("cat > .cmitmsg < .git/HEAD\n"); + + *date = 0; + *author = 0; + *branch = 0; + *ancestor = 0; + *tag = 0; + loglen = 0; + + initial_commit = 0; +} + +static void get_rcs_name(char *rcspathname, char *name, char *dir) +{ + sprintf(rcspathname, "%s/%s,v", rcsdir, name); + if (!access(rcspathname, R_OK)) + return; + + sprintf(rcspathname, "%s/Attic/%s,v", rcsdir, name); + if (!access(rcspathname, R_OK)) + return; + + if (dir) { + sprintf(rcspathname, "%s/%.*s/Attic/%s,v", rcsdir, (int)(dir - name), name, dir+1); + if (!access(rcspathname, R_OK)) + return; + } + fprintf(stderr, "Unable to find RCS file for %s\n", name); + exit(1); +} + +static void update_file(char *line) +{ + static char rcspathname[4096]; + char *name, *version; + char *dir; + + while (isspace(*line)) + line++; + name = line; + line = strchr(line, ':'); + if (!line) + return; + *line++ = 0; + line = strchr(line, '>'); + if (!line) + return; + *line++ = 0; + version = line; + line = strchr(line, '('); + if (line) { /* "(DEAD)" */ + printf("git-update-cache --force-remove '%s'\n", name); + return; + } + + dir = strrchr(name, '/'); + if (dir) + printf("mkdir -p %.*s\n", (int)(dir - name), name); + + get_rcs_name(rcspathname, name, dir); + + printf("co -p -r%s '%s' > '%s'\n", version, rcspathname, name); + printf("git-update-cache --add -- '%s'\n", name); +} + +struct hdrentry { + const char *name; + char *dest; +} hdrs[] = { + { "Date:", date }, + { "Author:", author }, + { "Branch:", branch }, + { "Ancestor branch:", ancestor }, + { "Tag:", tag }, + { "Log:", NULL }, + { NULL, NULL } +}; + +int main(int argc, char **argv) +{ + static char line[1000]; + enum state state = Header; + + rcsdir = getenv("RCSDIR"); + if (!rcsdir) { + fprintf(stderr, "I need an $RCSDIR\n"); + exit(1); + } + + printf("[ -d .git ] && exit 1\n"); + printf("git-init-db\n"); + printf("mkdir -p .git/refs/heads\n"); + printf("mkdir -p .git/refs/tags\n"); + printf("ln -sf refs/heads/master .git/HEAD\n"); + + while (fgets(line, sizeof(line), stdin) != NULL) { + int linelen = strlen(line); + + while (linelen && isspace(line[linelen-1])) + line[--linelen] = 0; + + switch (state) { + struct hdrentry *entry; + + case Header: + if (verbose) + printf("# H: %s\n", line); + for (entry = hdrs ; entry->name ; entry++) { + int len = strlen(entry->name); + char *val; + + if (memcmp(entry->name, line, len)) + continue; + if (!entry->dest) { + state = Log; + break; + } + val = line + len; + linelen -= len; + while (isspace(*val)) { + val++; + linelen--; + } + memcpy(entry->dest, val, linelen+1); + break; + } + continue; + + case Log: + if (verbose) + printf("# L: %s\n", line); + if (!strcmp(line, "Members:")) { + while (loglen && isspace(log[loglen-1])) + log[--loglen] = 0; + prepare_commit(); + state = Members; + continue; + } + + if (loglen + linelen + 5 > sizeof(log)) + continue; + memcpy(log + loglen, line, linelen); + loglen += linelen; + log[loglen++] = '\n'; + continue; + + case Members: + if (verbose) + printf("# M: %s\n", line); + if (!linelen) { + commit(); + state = Header; + continue; + } + update_file(line); + continue; + } + } + return 0; +} -- cgit v0.10.2-6-g49f6 From e69a19f784b3ff19efc8ab765166e877fffb052e Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 24 May 2005 09:06:51 -0700 Subject: Fix cvs2git branch handling .. and tell 'co' to shut up about the rcs noise. This still leaves some branch issues up in the air: it looks like cvsps has some questionable originating branch information, but I don't know whether that's a cvsps bug or an actual bug in the syslinux archive I'm using to test. I'll let David Mansfield answer my questions about CVS. I'm a total idiot when it comes to branches under CVS ("I'm pure!"). diff --git a/cvs2git.c b/cvs2git.c index c18f904..2ac53ea 100644 --- a/cvs2git.c +++ b/cvs2git.c @@ -82,18 +82,24 @@ static void prepare_commit(void) if (!strcmp(src_branch, "HEAD")) src_branch = "master"; printf("ln -sf refs/heads/'%s' .git/HEAD\n", src_branch); - printf("git-read-tree -m HEAD\n"); + printf("git-read-tree -m HEAD || exit 1\n"); printf("git-checkout-cache -f -u -a\n"); } static void commit(void) { const char *cmit_parent = initial_commit ? "" : "-p HEAD"; + const char *dst_branch; printf("tree=$(git-write-tree)\n"); printf("cat > .cmitmsg < .git/HEAD\n"); + + dst_branch = branch; + if (!strcmp(dst_branch, "HEAD")) + dst_branch = "master"; + + printf("echo $commit > .git/refs/heads/'%s'\n", dst_branch); *date = 0; *author = 0; @@ -154,7 +160,7 @@ static void update_file(char *line) get_rcs_name(rcspathname, name, dir); - printf("co -p -r%s '%s' > '%s'\n", version, rcspathname, name); + printf("co -q -p -r%s '%s' > '%s'\n", version, rcspathname, name); printf("git-update-cache --add -- '%s'\n", name); } -- cgit v0.10.2-6-g49f6 From e16c03b5495432579b864c279833d522ea463e5f Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 24 May 2005 09:36:33 -0700 Subject: cvs2git: escape < .cmitmsg < .cmitmsg < Date: Tue, 24 May 2005 09:59:55 -0700 Subject: cvs2git: add support for GIT_COMMITTER_DATE Of course, this requires a version of git that cares.. diff --git a/cvs2git.c b/cvs2git.c index 462a873..b63b8bc 100644 --- a/cvs2git.c +++ b/cvs2git.c @@ -69,10 +69,10 @@ static void prepare_commit(void) printf("export GIT_COMMITTER_NAME=%s\n", author_name); printf("export GIT_COMMITTER_EMAIL=%s\n", author_email); + printf("export GIT_COMMITTER_DATE='%s'\n", date); printf("export GIT_AUTHOR_NAME=%s\n", author_name); printf("export GIT_AUTHOR_EMAIL=%s\n", author_email); - printf("export GIT_AUTHOR_DATE='%s'\n", date); if (initial_commit) -- cgit v0.10.2-6-g49f6 From 1db0819a9cb30234678df73a99b82cb96babeca5 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 24 May 2005 10:15:42 -0700 Subject: cvs2git: set timezone info to UTC, the way CVS does This should also mean that the conversion is now completely defined by the CVS tree, and that two people doing a cvs2git conversion on the same base will always get the same results regardless of when or in what timezone they do it. diff --git a/cvs2git.c b/cvs2git.c index b63b8bc..8a76734 100644 --- a/cvs2git.c +++ b/cvs2git.c @@ -69,11 +69,11 @@ static void prepare_commit(void) printf("export GIT_COMMITTER_NAME=%s\n", author_name); printf("export GIT_COMMITTER_EMAIL=%s\n", author_email); - printf("export GIT_COMMITTER_DATE='%s'\n", date); + printf("export GIT_COMMITTER_DATE='+0000 %s'\n", date); printf("export GIT_AUTHOR_NAME=%s\n", author_name); printf("export GIT_AUTHOR_EMAIL=%s\n", author_email); - printf("export GIT_AUTHOR_DATE='%s'\n", date); + printf("export GIT_AUTHOR_DATE='+0000 %s'\n", date); if (initial_commit) return; -- cgit v0.10.2-6-g49f6 From d2b6f7c2fdd4012402b61b0a0a333b8b2011e8bc Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 24 May 2005 12:57:12 -0700 Subject: cvs2git: fix character quoting We need to quote backslash and backtick too. And inform the user about our progress, since converting a big archive can take time. Doing the full mutt history took just under eight minutes. diff --git a/cvs2git.c b/cvs2git.c index 8a76734..1adc918 100644 --- a/cvs2git.c +++ b/cvs2git.c @@ -114,6 +114,8 @@ static void commit(void) switch (c) { case '$': + case '\\': + case '`': putchar('\\'); break; case 0 ... 31: @@ -133,6 +135,8 @@ static void commit(void) printf("echo $commit > .git/refs/heads/'%s'\n", dst_branch); + printf("echo 'Committed (to %s):' ; cat .cmitmsg; echo\n", dst_branch); + *date = 0; *author = 0; *branch = 0; -- cgit v0.10.2-6-g49f6 From aff9f97a4fd98e34b0dfde06dacd9dbd82de0b58 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 30 May 2005 21:00:09 -0700 Subject: cvs2git: use CVS (rather than RCS) to extract the different file versions. This allows you to do the conversion (although slowly) from a remote repository, and besides, it's one less thing to worry about when you don't need to look up the CVS Attic directories etc. diff --git a/cvs2git.c b/cvs2git.c index 1adc918..06dd74b 100644 --- a/cvs2git.c +++ b/cvs2git.c @@ -24,6 +24,17 @@ static int verbose = 0; * Hopefully David Mansfield will update his distribution soon * enough (he's the one who wrote the patch, so at least we don't * have to figt maintainer issues ;) + * + * Usage: + * + * TZ=UTC cvsps -A | + * cvs2git --cvsroot=[root] --module=[module] > script + * + * Creates a shell script that will generate the .git archive of + * the names CVS repository. + * + * IMPORTANT NOTE ABOUT "cvsps"! This requires version 2.1 or better, + * and the "TZ=UTC" and the "-A" flag is required for sane results! */ enum state { Header, @@ -31,7 +42,8 @@ enum state { Members }; -static char *rcsdir; +static const char *cvsroot; +static const char *cvsmodule; static char date[100]; static char author[100]; @@ -147,28 +159,8 @@ static void commit(void) initial_commit = 0; } -static void get_rcs_name(char *rcspathname, char *name, char *dir) -{ - sprintf(rcspathname, "%s/%s,v", rcsdir, name); - if (!access(rcspathname, R_OK)) - return; - - sprintf(rcspathname, "%s/Attic/%s,v", rcsdir, name); - if (!access(rcspathname, R_OK)) - return; - - if (dir) { - sprintf(rcspathname, "%s/%.*s/Attic/%s,v", rcsdir, (int)(dir - name), name, dir+1); - if (!access(rcspathname, R_OK)) - return; - } - fprintf(stderr, "Unable to find RCS file for %s\n", name); - exit(1); -} - static void update_file(char *line) { - static char rcspathname[4096]; char *name, *version; char *dir; @@ -194,9 +186,7 @@ static void update_file(char *line) if (dir) printf("mkdir -p %.*s\n", (int)(dir - name), name); - get_rcs_name(rcspathname, name, dir); - - printf("co -q -p -r%s '%s' > '%s'\n", version, rcspathname, name); + printf("cvs -q -d %s checkout -r%s -p '%s/%s' > '%s'\n", cvsroot, version, cvsmodule, name, name); printf("git-update-cache --add -- '%s'\n", name); } @@ -217,10 +207,30 @@ int main(int argc, char **argv) { static char line[1000]; enum state state = Header; + int i; + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (!memcmp(arg, "--cvsroot=", 10)) { + cvsroot = arg + 10; + continue; + } + if (!memcmp(arg, "--module=", 9)) { + cvsmodule = arg+9; + continue; + } + if (!strcmp(arg, "-v")) { + verbose = 1; + continue; + } + } + + + if (!cvsroot) + cvsroot = getenv("CVSROOT"); - rcsdir = getenv("RCSDIR"); - if (!rcsdir) { - fprintf(stderr, "I need an $RCSDIR\n"); + if (!cvsmodule || !cvsroot) { + fprintf(stderr, "I need a CVSROOT and module name\n"); exit(1); } -- cgit v0.10.2-6-g49f6 From 97b865ba7954046072900a95571a308307ed8a00 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 5 Jun 2005 14:09:03 -0700 Subject: applypatch: use the new git-apply to apply patches Let's test it with some real-world horror schenarios. I'm crazy, I know. diff --git a/applypatch b/applypatch index 9c191ae..6db3588 100755 --- a/applypatch +++ b/applypatch @@ -58,10 +58,7 @@ echo echo Applying "'$SUBJECT'" echo -git-check-files $(cat $FILES) || exit 1 -git-checkout-cache -q $(cat $FILES) || exit 1 -patch -E -u --no-backup-if-mismatch -f -p1 --fuzz=0 --input=$PATCHFILE || exit 1 -git-update-cache --add --remove $(cat $FILES) || exit 1 +git-apply $PATCHFILE || exit 1 tree=$(git-write-tree) || exit 1 echo Wrote tree $tree commit=$(git-commit-tree $tree -p $(cat .git/HEAD) < $final) || exit 1 -- cgit v0.10.2-6-g49f6 From f48516746fada1405e476f04bc855bf91412ed46 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 5 Jun 2005 14:12:53 -0700 Subject: applypatch: use "--index" to actually make git-apply write the changes to the index file. diff --git a/applypatch b/applypatch index 6db3588..789917f 100755 --- a/applypatch +++ b/applypatch @@ -58,7 +58,7 @@ echo echo Applying "'$SUBJECT'" echo -git-apply $PATCHFILE || exit 1 +git-apply --index $PATCHFILE || exit 1 tree=$(git-write-tree) || exit 1 echo Wrote tree $tree commit=$(git-commit-tree $tree -p $(cat .git/HEAD) < $final) || exit 1 -- cgit v0.10.2-6-g49f6 From a196d8d4238f582b6441e8e0c56764679c266231 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 23 Jun 2005 09:40:23 -0700 Subject: Avoid doing the "filelist" thing, since "git-apply" picks up the files automatically ..and git-apply does a lot better job at it anyway. Also, we break the comment/diff on a line that starts with "diff -", not just on the "---" line. Especially for git diffs, we actually want that line in the diff. (We should probably also break on "Index: ..." followed by "=====") diff --git a/applypatch b/applypatch index 789917f..5a3a44b 100755 --- a/applypatch +++ b/applypatch @@ -7,11 +7,10 @@ ## The arguments are: ## $1 - file with commit message ## $2 - file with the actual patch -## $3 - file with list of filenames the patch touches -## $4 - "info" file with Author, email and subject -## $5 - optional file containing signoff to add +## $3 - "info" file with Author, email and subject +## $4 - optional file containing signoff to add ## -signoff="$5" +signoff="$4" final=.dotest/final-commit ## ## If this file exists, we ask before applying @@ -19,8 +18,7 @@ final=.dotest/final-commit query_apply=.dotest/.query_apply MSGFILE=$1 PATCHFILE=$2 -FILES=$3 -INFO=$4 +INFO=$3 EDIT=${VISUAL:-$EDITOR} EDIT=${EDIT:-vi} diff --git a/dotest b/dotest index ccc1f03..c6f44e1c 100755 --- a/dotest +++ b/dotest @@ -19,9 +19,9 @@ esac mailsplit $1 .dotest || exit 1 for i in .dotest/* do - mailinfo .dotest/msg .dotest/patch .dotest/file < $i > .dotest/info || exit 1 + mailinfo .dotest/msg .dotest/patch < $i > .dotest/info || exit 1 stripspace < .dotest/msg > .dotest/msg-clean - applypatch .dotest/msg-clean .dotest/patch .dotest/file .dotest/info "$2" + applypatch .dotest/msg-clean .dotest/patch .dotest/info "$2" ret=$? if [ $ret -ne 0 ]; then # 2 is a special exit code from applypatch to indicate that diff --git a/mailinfo.c b/mailinfo.c index 13c1e95..6c4d589 100644 --- a/mailinfo.c +++ b/mailinfo.c @@ -7,7 +7,7 @@ #include #include -static FILE *cmitmsg, *patchfile, *filelist; +static FILE *cmitmsg, *patchfile; static char line[1000]; static char date[1000]; @@ -181,45 +181,6 @@ static void cleanup_space(char *buf) } } -/* - * Hacky hacky. This depends not only on -p1, but on - * filenames not having some special characters in them, - * like tilde. - */ -static void show_filename(char *line) -{ - int len; - char *name = strchr(line, '/'); - - if (!name || !isspace(*line)) - return; - name++; - len = 0; - for (;;) { - unsigned char c = name[len]; - switch (c) { - default: - len++; - continue; - - case 0: case ' ': - case '\t': case '\n': - break; - - /* patch tends to special-case these things.. */ - case '~': - break; - } - break; - } - /* remove ".orig" from the end - common patch behaviour */ - if (len > 5 && !memcmp(name+len-5, ".orig", 5)) - len -=5; - if (!len) - return; - fprintf(filelist, "%.*s\n", len, name); -} - static void handle_rest(void) { char *sub = cleanup_subject(subject); @@ -231,14 +192,9 @@ static void handle_rest(void) FILE *out = cmitmsg; do { - /* Track filename information from the patch.. */ - if (!memcmp("---", line, 3)) { + if (!memcmp("diff -", line, 6) || + !memcmp("---", line, 3)) out = patchfile; - show_filename(line+3); - } - - if (!memcmp("+++", line, 3)) - show_filename(line+3); fputs(line, out); } while (fgets(line, sizeof(line), stdin) != NULL); @@ -283,13 +239,13 @@ static void handle_body(void) static void usage(void) { - fprintf(stderr, "mailinfo msg-file path-file filelist-file < email\n"); + fprintf(stderr, "mailinfo msg-file path-file < email\n"); exit(1); } int main(int argc, char ** argv) { - if (argc != 4) + if (argc != 3) usage(); cmitmsg = fopen(argv[1], "w"); if (!cmitmsg) { @@ -301,11 +257,6 @@ int main(int argc, char ** argv) perror(argv[2]); exit(1); } - filelist = fopen(argv[3], "w"); - if (!filelist) { - perror(argv[3]); - exit(1); - } while (fgets(line, sizeof(line), stdin) != NULL) { int len = eatspace(line); if (!len) { -- cgit v0.10.2-6-g49f6 From fc7ef1e8ae8c19c06a4fa3d574c13d793e66186a Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 23 Jun 2005 11:00:23 -0700 Subject: Add "Index: " to the list of things that start a patch This way we don't get it in the commit message, even if the patch had been generated by cogito (or CVS, ugh) and people didn't add the proper "---" marker. diff --git a/mailinfo.c b/mailinfo.c index 6c4d589..ae279bf 100644 --- a/mailinfo.c +++ b/mailinfo.c @@ -193,7 +193,8 @@ static void handle_rest(void) do { if (!memcmp("diff -", line, 6) || - !memcmp("---", line, 3)) + !memcmp("---", line, 3) || + !memcmp("Index: ", line, 7)) out = patchfile; fputs(line, out); -- cgit v0.10.2-6-g49f6 From c5f7674a97e621bfab5544165098b4860ee6e247 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 16 Jul 2005 10:05:26 -0700 Subject: Prepare git-tools for merging into the main git archive Rename into a "tools" subdirectory, and change name of "dotest" to "applymbox". Remove stripspace (which was already copied into git) and cvs2git (which was likewise already copied into git, and then replaced by a much better perl version). All of this was brought on by Ryan Anderson shaming me into it. Thanks. I guess. diff --git a/Makefile b/Makefile deleted file mode 100644 index 9998682..0000000 --- a/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -CC=gcc -CFLAGS=-Wall -O2 -HOME=$(shell echo $$HOME) - -PROGRAMS=mailsplit mailinfo stripspace cvs2git -SCRIPTS=dotest applypatch - -all: $(PROGRAMS) - -install: $(PROGRAMS) $(SCRIPTS) - cp -f $(PROGRAMS) $(SCRIPTS) $(HOME)/bin/ - -clean: - rm -f $(PROGRAMS) *.o diff --git a/applypatch b/applypatch deleted file mode 100755 index 5a3a44b..0000000 --- a/applypatch +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/sh -## -## applypatch takes four file arguments, and uses those to -## apply the unpacked patch (surprise surprise) that they -## represent to the current tree. -## -## The arguments are: -## $1 - file with commit message -## $2 - file with the actual patch -## $3 - "info" file with Author, email and subject -## $4 - optional file containing signoff to add -## -signoff="$4" -final=.dotest/final-commit -## -## If this file exists, we ask before applying -## -query_apply=.dotest/.query_apply -MSGFILE=$1 -PATCHFILE=$2 -INFO=$3 -EDIT=${VISUAL:-$EDITOR} -EDIT=${EDIT:-vi} - -export GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' .dotest/info)" -export GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' .dotest/info)" -export GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' .dotest/info)" -export SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' .dotest/info)" - -if [ -n "$signoff" -a -f "$signoff" ]; then - cat $signoff >> $MSGFILE -fi - -(echo "[PATCH] $SUBJECT" ; if [ -s $MSGFILE ]; then echo ; cat $MSGFILE; fi ) > $final - -f=0 -[ -f $query_apply ] || f=1 - -while [ $f -eq 0 ]; do - echo "Commit Body is:" - echo "--------------------------" - cat $final - echo "--------------------------" - echo -n "Apply? [y]es/[n]o/[e]dit/[a]ccept all " - read reply - case $reply in - y|Y) f=1;; - n|N) exit 2;; # special value to tell dotest to keep going - e|E) $EDIT $final;; - a|A) rm -f $query_apply - f=1;; - esac -done - -echo -echo Applying "'$SUBJECT'" -echo - -git-apply --index $PATCHFILE || exit 1 -tree=$(git-write-tree) || exit 1 -echo Wrote tree $tree -commit=$(git-commit-tree $tree -p $(cat .git/HEAD) < $final) || exit 1 -echo Committed: $commit -echo $commit > .git/HEAD diff --git a/cvs2git.c b/cvs2git.c deleted file mode 100644 index 06dd74b..0000000 --- a/cvs2git.c +++ /dev/null @@ -1,307 +0,0 @@ -/* - * cvs2git - * - * Copyright (C) Linus Torvalds 2005 - */ - -#include -#include -#include -#include -#include - -static int verbose = 0; - -/* - * This is a really stupid program that takes cvsps output, and - * generates a a long _shell_script_ that will create the GIT archive - * from it. - * - * You've been warned. I told you it was stupid. - * - * NOTE NOTE NOTE! In order to do branches correctly, this needs - * the fixed cvsps that has the "Ancestor branch" tag output. - * Hopefully David Mansfield will update his distribution soon - * enough (he's the one who wrote the patch, so at least we don't - * have to figt maintainer issues ;) - * - * Usage: - * - * TZ=UTC cvsps -A | - * cvs2git --cvsroot=[root] --module=[module] > script - * - * Creates a shell script that will generate the .git archive of - * the names CVS repository. - * - * IMPORTANT NOTE ABOUT "cvsps"! This requires version 2.1 or better, - * and the "TZ=UTC" and the "-A" flag is required for sane results! - */ -enum state { - Header, - Log, - Members -}; - -static const char *cvsroot; -static const char *cvsmodule; - -static char date[100]; -static char author[100]; -static char branch[100]; -static char ancestor[100]; -static char tag[100]; -static char log[32768]; -static int loglen = 0; -static int initial_commit = 1; - -static void lookup_author(char *n, char **name, char **email) -{ - /* - * FIXME!!! I'm lazy and stupid. - * - * This could be something like - * - * printf("lookup_author '%s'\n", n); - * *name = "$author_name"; - * *email = "$author_email"; - * - * and that would allow the script to do its own - * lookups at run-time. - */ - *name = n; - *email = n; -} - -static void prepare_commit(void) -{ - char *author_name, *author_email; - char *src_branch; - - lookup_author(author, &author_name, &author_email); - - printf("export GIT_COMMITTER_NAME=%s\n", author_name); - printf("export GIT_COMMITTER_EMAIL=%s\n", author_email); - printf("export GIT_COMMITTER_DATE='+0000 %s'\n", date); - - printf("export GIT_AUTHOR_NAME=%s\n", author_name); - printf("export GIT_AUTHOR_EMAIL=%s\n", author_email); - printf("export GIT_AUTHOR_DATE='+0000 %s'\n", date); - - if (initial_commit) - return; - - src_branch = *ancestor ? ancestor : branch; - if (!strcmp(src_branch, "HEAD")) - src_branch = "master"; - printf("ln -sf refs/heads/'%s' .git/HEAD\n", src_branch); - - /* - * Even if cvsps claims an ancestor, we'll let the new - * branch name take precedence if it already exists - */ - if (*ancestor) { - src_branch = branch; - if (!strcmp(src_branch, "HEAD")) - src_branch = "master"; - printf("[ -e .git/refs/heads/'%s' ] && ln -sf refs/heads/'%s' .git/HEAD\n", - src_branch, src_branch); - } - - printf("git-read-tree -m HEAD || exit 1\n"); - printf("git-checkout-cache -f -u -a\n"); -} - -static void commit(void) -{ - const char *cmit_parent = initial_commit ? "" : "-p HEAD"; - const char *dst_branch; - int i; - - printf("tree=$(git-write-tree)\n"); - printf("cat > .cmitmsg < .git/refs/heads/'%s'\n", dst_branch); - - printf("echo 'Committed (to %s):' ; cat .cmitmsg; echo\n", dst_branch); - - *date = 0; - *author = 0; - *branch = 0; - *ancestor = 0; - *tag = 0; - loglen = 0; - - initial_commit = 0; -} - -static void update_file(char *line) -{ - char *name, *version; - char *dir; - - while (isspace(*line)) - line++; - name = line; - line = strchr(line, ':'); - if (!line) - return; - *line++ = 0; - line = strchr(line, '>'); - if (!line) - return; - *line++ = 0; - version = line; - line = strchr(line, '('); - if (line) { /* "(DEAD)" */ - printf("git-update-cache --force-remove '%s'\n", name); - return; - } - - dir = strrchr(name, '/'); - if (dir) - printf("mkdir -p %.*s\n", (int)(dir - name), name); - - printf("cvs -q -d %s checkout -r%s -p '%s/%s' > '%s'\n", cvsroot, version, cvsmodule, name, name); - printf("git-update-cache --add -- '%s'\n", name); -} - -struct hdrentry { - const char *name; - char *dest; -} hdrs[] = { - { "Date:", date }, - { "Author:", author }, - { "Branch:", branch }, - { "Ancestor branch:", ancestor }, - { "Tag:", tag }, - { "Log:", NULL }, - { NULL, NULL } -}; - -int main(int argc, char **argv) -{ - static char line[1000]; - enum state state = Header; - int i; - - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (!memcmp(arg, "--cvsroot=", 10)) { - cvsroot = arg + 10; - continue; - } - if (!memcmp(arg, "--module=", 9)) { - cvsmodule = arg+9; - continue; - } - if (!strcmp(arg, "-v")) { - verbose = 1; - continue; - } - } - - - if (!cvsroot) - cvsroot = getenv("CVSROOT"); - - if (!cvsmodule || !cvsroot) { - fprintf(stderr, "I need a CVSROOT and module name\n"); - exit(1); - } - - printf("[ -d .git ] && exit 1\n"); - printf("git-init-db\n"); - printf("mkdir -p .git/refs/heads\n"); - printf("mkdir -p .git/refs/tags\n"); - printf("ln -sf refs/heads/master .git/HEAD\n"); - - while (fgets(line, sizeof(line), stdin) != NULL) { - int linelen = strlen(line); - - while (linelen && isspace(line[linelen-1])) - line[--linelen] = 0; - - switch (state) { - struct hdrentry *entry; - - case Header: - if (verbose) - printf("# H: %s\n", line); - for (entry = hdrs ; entry->name ; entry++) { - int len = strlen(entry->name); - char *val; - - if (memcmp(entry->name, line, len)) - continue; - if (!entry->dest) { - state = Log; - break; - } - val = line + len; - linelen -= len; - while (isspace(*val)) { - val++; - linelen--; - } - memcpy(entry->dest, val, linelen+1); - break; - } - continue; - - case Log: - if (verbose) - printf("# L: %s\n", line); - if (!strcmp(line, "Members:")) { - while (loglen && isspace(log[loglen-1])) - log[--loglen] = 0; - prepare_commit(); - state = Members; - continue; - } - - if (loglen + linelen + 5 > sizeof(log)) - continue; - memcpy(log + loglen, line, linelen); - loglen += linelen; - log[loglen++] = '\n'; - continue; - - case Members: - if (verbose) - printf("# M: %s\n", line); - if (!linelen) { - commit(); - state = Header; - continue; - } - update_file(line); - continue; - } - } - return 0; -} diff --git a/dotest b/dotest deleted file mode 100755 index c6f44e1c..0000000 --- a/dotest +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh -## -## "dotest" is my stupid name for my patch-application script, which -## I never got around to renaming after I tested it. We're now on the -## second generation of scripts, still called "dotest". -## -## You give it a mbox-format collection of emails, and it will try to -## apply them to the kernel using "applypatch" -## -## dotest [ -q ] mail_archive [Signoff_file] -## -rm -rf .dotest -mkdir .dotest -case $1 in - - -q) touch .dotest/.query_apply - shift;; -esac -mailsplit $1 .dotest || exit 1 -for i in .dotest/* -do - mailinfo .dotest/msg .dotest/patch < $i > .dotest/info || exit 1 - stripspace < .dotest/msg > .dotest/msg-clean - applypatch .dotest/msg-clean .dotest/patch .dotest/info "$2" - ret=$? - if [ $ret -ne 0 ]; then - # 2 is a special exit code from applypatch to indicate that - # the patch wasn't applied, but continue anyway - [ $ret -ne 2 ] && exit $ret - fi -done -# return to pristine -rm -fr .dotest diff --git a/mailinfo.c b/mailinfo.c deleted file mode 100644 index ae279bf..0000000 --- a/mailinfo.c +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Another stupid program, this one parsing the headers of an - * email to figure out authorship and subject - */ -#include -#include -#include -#include - -static FILE *cmitmsg, *patchfile; - -static char line[1000]; -static char date[1000]; -static char name[1000]; -static char email[1000]; -static char subject[1000]; - -static char *sanity_check(char *name, char *email) -{ - int len = strlen(name); - if (len < 3 || len > 60) - return email; - if (strchr(name, '@') || strchr(name, '<') || strchr(name, '>')) - return email; - return name; -} - -static int handle_from(char *line) -{ - char *at = strchr(line, '@'); - char *dst; - - if (!at) - return 0; - - /* - * If we already have one email, don't take any confusing lines - */ - if (*email && strchr(at+1, '@')) - return 0; - - while (at > line) { - char c = at[-1]; - if (isspace(c) || c == '<') - break; - at--; - } - dst = email; - for (;;) { - unsigned char c = *at; - if (!c || c == '>' || isspace(c)) - break; - *at++ = ' '; - *dst++ = c; - } - *dst++ = 0; - - at = line + strlen(line); - while (at > line) { - unsigned char c = *--at; - if (isalnum(c)) - break; - *at = 0; - } - - at = line; - for (;;) { - unsigned char c = *at; - if (!c) - break; - if (isalnum(c)) - break; - at++; - } - - at = sanity_check(at, email); - - strcpy(name, at); - return 1; -} - -static void handle_date(char *line) -{ - strcpy(date, line); -} - -static void handle_subject(char *line) -{ - strcpy(subject, line); -} - -static void add_subject_line(char *line) -{ - while (isspace(*line)) - line++; - *--line = ' '; - strcat(subject, line); -} - -static void check_line(char *line, int len) -{ - static int cont = -1; - if (!memcmp(line, "From:", 5) && isspace(line[5])) { - handle_from(line+6); - cont = 0; - return; - } - if (!memcmp(line, "Date:", 5) && isspace(line[5])) { - handle_date(line+6); - cont = 0; - return; - } - if (!memcmp(line, "Subject:", 8) && isspace(line[8])) { - handle_subject(line+9); - cont = 1; - return; - } - if (isspace(*line)) { - switch (cont) { - case 0: - fprintf(stderr, "I don't do 'Date:' or 'From:' line continuations\n"); - break; - case 1: - add_subject_line(line); - return; - default: - break; - } - } - cont = -1; -} - -static char * cleanup_subject(char *subject) -{ - for (;;) { - char *p; - int len, remove; - switch (*subject) { - case 'r': case 'R': - if (!memcmp("e:", subject+1, 2)) { - subject +=3; - continue; - } - break; - case ' ': case '\t': case ':': - subject++; - continue; - - case '[': - p = strchr(subject, ']'); - if (!p) { - subject++; - continue; - } - len = strlen(p); - remove = p - subject; - if (remove <= len *2) { - subject = p+1; - continue; - } - break; - } - return subject; - } -} - -static void cleanup_space(char *buf) -{ - unsigned char c; - while ((c = *buf) != 0) { - buf++; - if (isspace(c)) { - buf[-1] = ' '; - c = *buf; - while (isspace(c)) { - int len = strlen(buf); - memmove(buf, buf+1, len); - c = *buf; - } - } - } -} - -static void handle_rest(void) -{ - char *sub = cleanup_subject(subject); - cleanup_space(name); - cleanup_space(date); - cleanup_space(email); - cleanup_space(sub); - printf("Author: %s\nEmail: %s\nSubject: %s\nDate: %s\n\n", name, email, sub, date); - FILE *out = cmitmsg; - - do { - if (!memcmp("diff -", line, 6) || - !memcmp("---", line, 3) || - !memcmp("Index: ", line, 7)) - out = patchfile; - - fputs(line, out); - } while (fgets(line, sizeof(line), stdin) != NULL); - - if (out == cmitmsg) { - fprintf(stderr, "No patch found\n"); - exit(1); - } - - fclose(cmitmsg); - fclose(patchfile); -} - -static int eatspace(char *line) -{ - int len = strlen(line); - while (len > 0 && isspace(line[len-1])) - line[--len] = 0; - return len; -} - -static void handle_body(void) -{ - int has_from = 0; - - /* First line of body can be a From: */ - while (fgets(line, sizeof(line), stdin) != NULL) { - int len = eatspace(line); - if (!len) - continue; - if (!memcmp("From:", line, 5) && isspace(line[5])) { - if (!has_from && handle_from(line+6)) { - has_from = 1; - continue; - } - } - line[len] = '\n'; - handle_rest(); - break; - } -} - -static void usage(void) -{ - fprintf(stderr, "mailinfo msg-file path-file < email\n"); - exit(1); -} - -int main(int argc, char ** argv) -{ - if (argc != 3) - usage(); - cmitmsg = fopen(argv[1], "w"); - if (!cmitmsg) { - perror(argv[1]); - exit(1); - } - patchfile = fopen(argv[2], "w"); - if (!patchfile) { - perror(argv[2]); - exit(1); - } - while (fgets(line, sizeof(line), stdin) != NULL) { - int len = eatspace(line); - if (!len) { - handle_body(); - break; - } - check_line(line, len); - } - return 0; -} diff --git a/mailsplit.c b/mailsplit.c deleted file mode 100644 index 9379fbc..0000000 --- a/mailsplit.c +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Totally braindamaged mbox splitter program. - * - * It just splits a mbox into a list of files: "0001" "0002" .. - * so you can process them further from there. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static int usage(void) -{ - fprintf(stderr, "mailsplit \n"); - exit(1); -} - -static int linelen(const char *map, unsigned long size) -{ - int len = 0, c; - - do { - c = *map; - map++; - size--; - len++; - } while (size && c != '\n'); - return len; -} - -static int is_from_line(const char *line, int len) -{ - const char *colon; - - if (len < 20 || memcmp("From ", line, 5)) - return 0; - - colon = line + len - 2; - line += 5; - for (;;) { - if (colon < line) - return 0; - if (*--colon == ':') - break; - } - - if (!isdigit(colon[-4]) || - !isdigit(colon[-2]) || - !isdigit(colon[-1]) || - !isdigit(colon[ 1]) || - !isdigit(colon[ 2])) - return 0; - - /* year */ - if (strtol(colon+3, NULL, 10) <= 90) - return 0; - - /* Ok, close enough */ - return 1; -} - -static int parse_email(const void *map, unsigned long size) -{ - unsigned long offset; - - if (size < 6 || memcmp("From ", map, 5)) - goto corrupt; - - /* Make sure we don't trigger on this first line */ - map++; size--; offset=1; - - /* - * Search for a line beginning with "From ", and - * having smething that looks like a date format. - */ - do { - int len = linelen(map, size); - if (is_from_line(map, len)) - return offset; - map += len; - size -= len; - offset += len; - } while (size); - return offset; - -corrupt: - fprintf(stderr, "corrupt mailbox\n"); - exit(1); -} - -int main(int argc, char **argv) -{ - int fd, nr; - struct stat st; - unsigned long size; - void *map; - - if (argc != 3) - usage(); - fd = open(argv[1], O_RDONLY); - if (fd < 0) { - perror(argv[1]); - exit(1); - } - if (chdir(argv[2]) < 0) - usage(); - if (fstat(fd, &st) < 0) { - perror("stat"); - exit(1); - } - size = st.st_size; - map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); - if (-1 == (int)(long)map) { - perror("mmap"); - exit(1); - } - close(fd); - nr = 0; - do { - char name[10]; - unsigned long len = parse_email(map, size); - assert(len <= size); - sprintf(name, "%04d", ++nr); - fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0600); - if (fd < 0) { - perror(name); - exit(1); - } - if (write(fd, map, len) != len) { - perror("write"); - exit(1); - } - close(fd); - map += len; - size -= len; - } while (size > 0); - return 0; -} diff --git a/stripspace.c b/stripspace.c deleted file mode 100644 index 96cd0a8..0000000 --- a/stripspace.c +++ /dev/null @@ -1,48 +0,0 @@ -#include -#include -#include - -/* - * Remove empty lines from the beginning and end. - * - * Turn multiple consecutive empty lines into just one - * empty line. - */ -static void cleanup(char *line) -{ - int len = strlen(line); - - if (len > 1 && line[len-1] == '\n') { - do { - unsigned char c = line[len-2]; - if (!isspace(c)) - break; - line[len-2] = '\n'; - len--; - line[len] = 0; - } while (len > 1); - } -} - -int main(int argc, char **argv) -{ - int empties = -1; - char line[1024]; - - while (fgets(line, sizeof(line), stdin)) { - cleanup(line); - - /* Not just an empty line? */ - if (line[0] != '\n') { - if (empties > 0) - putchar('\n'); - empties = 0; - fputs(line, stdout); - continue; - } - if (empties < 0) - continue; - empties++; - } - return 0; -} diff --git a/tools/Makefile b/tools/Makefile new file mode 100644 index 0000000..8e7252e --- /dev/null +++ b/tools/Makefile @@ -0,0 +1,14 @@ +CC=gcc +CFLAGS=-Wall -O2 +HOME=$(shell echo $$HOME) + +PROGRAMS=mailsplit mailinfo +SCRIPTS=applymbox applypatch + +all: $(PROGRAMS) + +install: $(PROGRAMS) $(SCRIPTS) + cp -f $(PROGRAMS) $(SCRIPTS) $(HOME)/bin/ + +clean: + rm -f $(PROGRAMS) *.o diff --git a/tools/applymbox b/tools/applymbox new file mode 100755 index 0000000..5ac8d2b --- /dev/null +++ b/tools/applymbox @@ -0,0 +1,35 @@ +#!/bin/sh +## +## "dotest" is my stupid name for my patch-application script, which +## I never got around to renaming after I tested it. We're now on the +## second generation of scripts, still called "dotest". +## +## Update: Ryan Anderson finally shamed me into naming this "applymbox". +## +## You give it a mbox-format collection of emails, and it will try to +## apply them to the kernel using "applypatch" +## +## dotest [ -q ] mail_archive [Signoff_file] +## +rm -rf .dotest +mkdir .dotest +case $1 in + + -q) touch .dotest/.query_apply + shift;; +esac +mailsplit $1 .dotest || exit 1 +for i in .dotest/* +do + mailinfo .dotest/msg .dotest/patch < $i > .dotest/info || exit 1 + git-stripspace < .dotest/msg > .dotest/msg-clean + applypatch .dotest/msg-clean .dotest/patch .dotest/info "$2" + ret=$? + if [ $ret -ne 0 ]; then + # 2 is a special exit code from applypatch to indicate that + # the patch wasn't applied, but continue anyway + [ $ret -ne 2 ] && exit $ret + fi +done +# return to pristine +rm -fr .dotest diff --git a/tools/applypatch b/tools/applypatch new file mode 100755 index 0000000..5a3a44b --- /dev/null +++ b/tools/applypatch @@ -0,0 +1,64 @@ +#!/bin/sh +## +## applypatch takes four file arguments, and uses those to +## apply the unpacked patch (surprise surprise) that they +## represent to the current tree. +## +## The arguments are: +## $1 - file with commit message +## $2 - file with the actual patch +## $3 - "info" file with Author, email and subject +## $4 - optional file containing signoff to add +## +signoff="$4" +final=.dotest/final-commit +## +## If this file exists, we ask before applying +## +query_apply=.dotest/.query_apply +MSGFILE=$1 +PATCHFILE=$2 +INFO=$3 +EDIT=${VISUAL:-$EDITOR} +EDIT=${EDIT:-vi} + +export GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' .dotest/info)" +export GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' .dotest/info)" +export GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' .dotest/info)" +export SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' .dotest/info)" + +if [ -n "$signoff" -a -f "$signoff" ]; then + cat $signoff >> $MSGFILE +fi + +(echo "[PATCH] $SUBJECT" ; if [ -s $MSGFILE ]; then echo ; cat $MSGFILE; fi ) > $final + +f=0 +[ -f $query_apply ] || f=1 + +while [ $f -eq 0 ]; do + echo "Commit Body is:" + echo "--------------------------" + cat $final + echo "--------------------------" + echo -n "Apply? [y]es/[n]o/[e]dit/[a]ccept all " + read reply + case $reply in + y|Y) f=1;; + n|N) exit 2;; # special value to tell dotest to keep going + e|E) $EDIT $final;; + a|A) rm -f $query_apply + f=1;; + esac +done + +echo +echo Applying "'$SUBJECT'" +echo + +git-apply --index $PATCHFILE || exit 1 +tree=$(git-write-tree) || exit 1 +echo Wrote tree $tree +commit=$(git-commit-tree $tree -p $(cat .git/HEAD) < $final) || exit 1 +echo Committed: $commit +echo $commit > .git/HEAD diff --git a/tools/mailinfo.c b/tools/mailinfo.c new file mode 100644 index 0000000..ae279bf --- /dev/null +++ b/tools/mailinfo.c @@ -0,0 +1,270 @@ +/* + * Another stupid program, this one parsing the headers of an + * email to figure out authorship and subject + */ +#include +#include +#include +#include + +static FILE *cmitmsg, *patchfile; + +static char line[1000]; +static char date[1000]; +static char name[1000]; +static char email[1000]; +static char subject[1000]; + +static char *sanity_check(char *name, char *email) +{ + int len = strlen(name); + if (len < 3 || len > 60) + return email; + if (strchr(name, '@') || strchr(name, '<') || strchr(name, '>')) + return email; + return name; +} + +static int handle_from(char *line) +{ + char *at = strchr(line, '@'); + char *dst; + + if (!at) + return 0; + + /* + * If we already have one email, don't take any confusing lines + */ + if (*email && strchr(at+1, '@')) + return 0; + + while (at > line) { + char c = at[-1]; + if (isspace(c) || c == '<') + break; + at--; + } + dst = email; + for (;;) { + unsigned char c = *at; + if (!c || c == '>' || isspace(c)) + break; + *at++ = ' '; + *dst++ = c; + } + *dst++ = 0; + + at = line + strlen(line); + while (at > line) { + unsigned char c = *--at; + if (isalnum(c)) + break; + *at = 0; + } + + at = line; + for (;;) { + unsigned char c = *at; + if (!c) + break; + if (isalnum(c)) + break; + at++; + } + + at = sanity_check(at, email); + + strcpy(name, at); + return 1; +} + +static void handle_date(char *line) +{ + strcpy(date, line); +} + +static void handle_subject(char *line) +{ + strcpy(subject, line); +} + +static void add_subject_line(char *line) +{ + while (isspace(*line)) + line++; + *--line = ' '; + strcat(subject, line); +} + +static void check_line(char *line, int len) +{ + static int cont = -1; + if (!memcmp(line, "From:", 5) && isspace(line[5])) { + handle_from(line+6); + cont = 0; + return; + } + if (!memcmp(line, "Date:", 5) && isspace(line[5])) { + handle_date(line+6); + cont = 0; + return; + } + if (!memcmp(line, "Subject:", 8) && isspace(line[8])) { + handle_subject(line+9); + cont = 1; + return; + } + if (isspace(*line)) { + switch (cont) { + case 0: + fprintf(stderr, "I don't do 'Date:' or 'From:' line continuations\n"); + break; + case 1: + add_subject_line(line); + return; + default: + break; + } + } + cont = -1; +} + +static char * cleanup_subject(char *subject) +{ + for (;;) { + char *p; + int len, remove; + switch (*subject) { + case 'r': case 'R': + if (!memcmp("e:", subject+1, 2)) { + subject +=3; + continue; + } + break; + case ' ': case '\t': case ':': + subject++; + continue; + + case '[': + p = strchr(subject, ']'); + if (!p) { + subject++; + continue; + } + len = strlen(p); + remove = p - subject; + if (remove <= len *2) { + subject = p+1; + continue; + } + break; + } + return subject; + } +} + +static void cleanup_space(char *buf) +{ + unsigned char c; + while ((c = *buf) != 0) { + buf++; + if (isspace(c)) { + buf[-1] = ' '; + c = *buf; + while (isspace(c)) { + int len = strlen(buf); + memmove(buf, buf+1, len); + c = *buf; + } + } + } +} + +static void handle_rest(void) +{ + char *sub = cleanup_subject(subject); + cleanup_space(name); + cleanup_space(date); + cleanup_space(email); + cleanup_space(sub); + printf("Author: %s\nEmail: %s\nSubject: %s\nDate: %s\n\n", name, email, sub, date); + FILE *out = cmitmsg; + + do { + if (!memcmp("diff -", line, 6) || + !memcmp("---", line, 3) || + !memcmp("Index: ", line, 7)) + out = patchfile; + + fputs(line, out); + } while (fgets(line, sizeof(line), stdin) != NULL); + + if (out == cmitmsg) { + fprintf(stderr, "No patch found\n"); + exit(1); + } + + fclose(cmitmsg); + fclose(patchfile); +} + +static int eatspace(char *line) +{ + int len = strlen(line); + while (len > 0 && isspace(line[len-1])) + line[--len] = 0; + return len; +} + +static void handle_body(void) +{ + int has_from = 0; + + /* First line of body can be a From: */ + while (fgets(line, sizeof(line), stdin) != NULL) { + int len = eatspace(line); + if (!len) + continue; + if (!memcmp("From:", line, 5) && isspace(line[5])) { + if (!has_from && handle_from(line+6)) { + has_from = 1; + continue; + } + } + line[len] = '\n'; + handle_rest(); + break; + } +} + +static void usage(void) +{ + fprintf(stderr, "mailinfo msg-file path-file < email\n"); + exit(1); +} + +int main(int argc, char ** argv) +{ + if (argc != 3) + usage(); + cmitmsg = fopen(argv[1], "w"); + if (!cmitmsg) { + perror(argv[1]); + exit(1); + } + patchfile = fopen(argv[2], "w"); + if (!patchfile) { + perror(argv[2]); + exit(1); + } + while (fgets(line, sizeof(line), stdin) != NULL) { + int len = eatspace(line); + if (!len) { + handle_body(); + break; + } + check_line(line, len); + } + return 0; +} diff --git a/tools/mailsplit.c b/tools/mailsplit.c new file mode 100644 index 0000000..9379fbc --- /dev/null +++ b/tools/mailsplit.c @@ -0,0 +1,144 @@ +/* + * Totally braindamaged mbox splitter program. + * + * It just splits a mbox into a list of files: "0001" "0002" .. + * so you can process them further from there. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int usage(void) +{ + fprintf(stderr, "mailsplit \n"); + exit(1); +} + +static int linelen(const char *map, unsigned long size) +{ + int len = 0, c; + + do { + c = *map; + map++; + size--; + len++; + } while (size && c != '\n'); + return len; +} + +static int is_from_line(const char *line, int len) +{ + const char *colon; + + if (len < 20 || memcmp("From ", line, 5)) + return 0; + + colon = line + len - 2; + line += 5; + for (;;) { + if (colon < line) + return 0; + if (*--colon == ':') + break; + } + + if (!isdigit(colon[-4]) || + !isdigit(colon[-2]) || + !isdigit(colon[-1]) || + !isdigit(colon[ 1]) || + !isdigit(colon[ 2])) + return 0; + + /* year */ + if (strtol(colon+3, NULL, 10) <= 90) + return 0; + + /* Ok, close enough */ + return 1; +} + +static int parse_email(const void *map, unsigned long size) +{ + unsigned long offset; + + if (size < 6 || memcmp("From ", map, 5)) + goto corrupt; + + /* Make sure we don't trigger on this first line */ + map++; size--; offset=1; + + /* + * Search for a line beginning with "From ", and + * having smething that looks like a date format. + */ + do { + int len = linelen(map, size); + if (is_from_line(map, len)) + return offset; + map += len; + size -= len; + offset += len; + } while (size); + return offset; + +corrupt: + fprintf(stderr, "corrupt mailbox\n"); + exit(1); +} + +int main(int argc, char **argv) +{ + int fd, nr; + struct stat st; + unsigned long size; + void *map; + + if (argc != 3) + usage(); + fd = open(argv[1], O_RDONLY); + if (fd < 0) { + perror(argv[1]); + exit(1); + } + if (chdir(argv[2]) < 0) + usage(); + if (fstat(fd, &st) < 0) { + perror("stat"); + exit(1); + } + size = st.st_size; + map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (-1 == (int)(long)map) { + perror("mmap"); + exit(1); + } + close(fd); + nr = 0; + do { + char name[10]; + unsigned long len = parse_email(map, size); + assert(len <= size); + sprintf(name, "%04d", ++nr); + fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd < 0) { + perror(name); + exit(1); + } + if (write(fd, map, len) != len) { + perror("write"); + exit(1); + } + close(fd); + map += len; + size -= len; + } while (size > 0); + return 0; +} -- cgit v0.10.2-6-g49f6