From 2744b2344dc42fa2a1ddf17f4818975cd48f6d42 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 11 Apr 2005 23:46:50 -0700 Subject: [PATCH 01/22] 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. --- Makefile | 14 +++ mailinfo.c | 300 ++++++++++++++++++++++++++++++++++++++++++++++++++++ mailsplit.c | 144 +++++++++++++++++++++++++ 3 files changed, 458 insertions(+) create mode 100644 Makefile create mode 100644 mailinfo.c create mode 100644 mailsplit.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..3c518baca9 --- /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 0000000000..1ca554e92a --- /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 0000000000..9379fbc5e8 --- /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; +} From 853916ff7f1a5b34bd2728fe81059e6270bac134 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 12 Apr 2005 01:40:20 -0700 Subject: [PATCH 02/22] Add "applypatch" and "dotest" scripts to tie it all together. This should be getting it all pretty close to a working setup. --- Makefile | 2 +- applypatch | 35 +++++++++++++++++++++++++++++++++++ dotest | 17 +++++++++++++++++ mailinfo.c | 14 ++++++++++---- 4 files changed, 63 insertions(+), 5 deletions(-) create mode 100755 applypatch create mode 100755 dotest diff --git a/Makefile b/Makefile index 3c518baca9..eca3a5d525 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 0000000000..2791d9139a --- /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 0000000000..7d2c16b9ab --- /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 1ca554e92a..c1dcac1305 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) { From 61096819949d9c7f6e7032ec949daba35cf57226 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 18 Apr 2005 17:40:32 -0700 Subject: [PATCH 04/22] 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. --- applypatch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applypatch b/applypatch index 2791d9139a..9799cf98da 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 From ad4e9ce4f9b34c4e6dd941b3244bcf56a3e3d62d Mon Sep 17 00:00:00 2001 From: James Bottomley Date: Wed, 20 Apr 2005 08:23:00 -0700 Subject: [PATCH 05/22] [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. --- applypatch | 40 +++++++++++++++++++++++++++++++++++++--- dotest | 17 ++++++++++++++++- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/applypatch b/applypatch index 9799cf98da..397e4a0e50 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 7d2c16b9ab..a3e3d35ae0 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 From 50eb31d10390f638be2a99f3b064afcd090c08ea Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Fri, 29 Apr 2005 14:56:18 -0700 Subject: [PATCH 06/22] Fix up "applypatch" for the big git rename Add "git-" prefixes to git commands. --- applypatch | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/applypatch b/applypatch index 397e4a0e50..6e21409682 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 - From 8c7b86632c2e54ed98abf922101ee177de75e0eb Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 30 Apr 2005 10:58:41 -0700 Subject: [PATCH 07/22] Add "stripspace" program to clean up email commentary Remove multiple empty lines, and empty lines at beginning and end. --- Makefile | 2 +- applypatch | 2 +- dotest | 3 ++- stripspace.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 stripspace.c diff --git a/Makefile b/Makefile index eca3a5d525..b5be0c94ef 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 6e21409682..97274a158e 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 a3e3d35ae0..ccc1f0304f 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 0000000000..96cd0a88f0 --- /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; +} From 62c1f6b4afc68f6448d5ce94b00380405aa101eb Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 1 May 2005 21:42:53 -0700 Subject: [PATCH 08/22] 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. --- applypatch | 1 + mailinfo.c | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/applypatch b/applypatch index 97274a158e..500720171f 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 c1dcac1305..13c1e951ac 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 { From c9049d41b8d91a198e4642b266e50a506024f0aa Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 17 May 2005 09:35:01 -0700 Subject: [PATCH 09/22] Update applypatch to use new GIT_-prefix environment variables. Avoid the warnings from newer git versions. --- applypatch | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/applypatch b/applypatch index 500720171f..160931d74a 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 From 6a9853cd4587c9dd16553b429c6d4a9d8111a396 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 17 May 2005 17:15:56 -0700 Subject: [PATCH 10/22] Make "applypatch" use the "-E" flag to patch. Always remove empty files, regardless of how the diff showed them to have become empty. --- applypatch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applypatch b/applypatch index 160931d74a..9c191ae746 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 From 3e91311ae750af9bf2e3517b1e701288ac3066b9 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 24 May 2005 01:07:31 -0700 Subject: [PATCH 11/22] 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. --- Makefile | 2 +- cvs2git.c | 255 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 cvs2git.c diff --git a/Makefile b/Makefile index b5be0c94ef..99986829f8 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 0000000000..c18f904f59 --- /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; +} From e69a19f784b3ff19efc8ab765166e877fffb052e Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 24 May 2005 09:06:51 -0700 Subject: [PATCH 12/22] 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!"). --- cvs2git.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cvs2git.c b/cvs2git.c index c18f904f59..2ac53ea828 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); } From e16c03b5495432579b864c279833d522ea463e5f Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 24 May 2005 09:36:33 -0700 Subject: [PATCH 13/22] cvs2git: escape < .cmitmsg < .cmitmsg < Date: Tue, 24 May 2005 09:59:55 -0700 Subject: [PATCH 14/22] cvs2git: add support for GIT_COMMITTER_DATE Of course, this requires a version of git that cares.. --- cvs2git.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cvs2git.c b/cvs2git.c index 462a8736c8..b63b8bc53d 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) From 1db0819a9cb30234678df73a99b82cb96babeca5 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 24 May 2005 10:15:42 -0700 Subject: [PATCH 15/22] 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. --- cvs2git.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cvs2git.c b/cvs2git.c index b63b8bc53d..8a76734a02 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; From d2b6f7c2fdd4012402b61b0a0a333b8b2011e8bc Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 24 May 2005 12:57:12 -0700 Subject: [PATCH 16/22] 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. --- cvs2git.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cvs2git.c b/cvs2git.c index 8a76734a02..1adc918f51 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; From aff9f97a4fd98e34b0dfde06dacd9dbd82de0b58 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 30 May 2005 21:00:09 -0700 Subject: [PATCH 17/22] 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. --- cvs2git.c | 64 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/cvs2git.c b/cvs2git.c index 1adc918f51..06dd74b368 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); } From 97b865ba7954046072900a95571a308307ed8a00 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 5 Jun 2005 14:09:03 -0700 Subject: [PATCH 18/22] applypatch: use the new git-apply to apply patches Let's test it with some real-world horror schenarios. I'm crazy, I know. --- applypatch | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/applypatch b/applypatch index 9c191ae746..6db3588773 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 From f48516746fada1405e476f04bc855bf91412ed46 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 5 Jun 2005 14:12:53 -0700 Subject: [PATCH 19/22] applypatch: use "--index" to actually make git-apply write the changes to the index file. --- applypatch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applypatch b/applypatch index 6db3588773..789917f3c5 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 From a196d8d4238f582b6441e8e0c56764679c266231 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 23 Jun 2005 09:40:23 -0700 Subject: [PATCH 20/22] 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 "=====") --- applypatch | 10 ++++----- dotest | 4 ++-- mailinfo.c | 59 +++++------------------------------------------------- 3 files changed, 11 insertions(+), 62 deletions(-) diff --git a/applypatch b/applypatch index 789917f3c5..5a3a44b0e6 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 ccc1f0304f..c6f44e1c41 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 13c1e951ac..6c4d589823 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) { From fc7ef1e8ae8c19c06a4fa3d574c13d793e66186a Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 23 Jun 2005 11:00:23 -0700 Subject: [PATCH 21/22] 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. --- mailinfo.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mailinfo.c b/mailinfo.c index 6c4d589823..ae279bffa5 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); From c5f7674a97e621bfab5544165098b4860ee6e247 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 16 Jul 2005 10:05:26 -0700 Subject: [PATCH 22/22] 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. --- cvs2git.c | 307 ------------------------------- stripspace.c | 48 ----- Makefile => tools/Makefile | 4 +- dotest => tools/applymbox | 4 +- applypatch => tools/applypatch | 0 mailinfo.c => tools/mailinfo.c | 0 mailsplit.c => tools/mailsplit.c | 0 7 files changed, 5 insertions(+), 358 deletions(-) delete mode 100644 cvs2git.c delete mode 100644 stripspace.c rename Makefile => tools/Makefile (70%) rename dotest => tools/applymbox (87%) rename applypatch => tools/applypatch (100%) rename mailinfo.c => tools/mailinfo.c (100%) rename mailsplit.c => tools/mailsplit.c (100%) diff --git a/cvs2git.c b/cvs2git.c deleted file mode 100644 index 06dd74b368..0000000000 --- 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/stripspace.c b/stripspace.c deleted file mode 100644 index 96cd0a88f0..0000000000 --- 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/Makefile b/tools/Makefile similarity index 70% rename from Makefile rename to tools/Makefile index 99986829f8..8e7252e592 100644 --- a/Makefile +++ b/tools/Makefile @@ -2,8 +2,8 @@ CC=gcc CFLAGS=-Wall -O2 HOME=$(shell echo $$HOME) -PROGRAMS=mailsplit mailinfo stripspace cvs2git -SCRIPTS=dotest applypatch +PROGRAMS=mailsplit mailinfo +SCRIPTS=applymbox applypatch all: $(PROGRAMS) diff --git a/dotest b/tools/applymbox similarity index 87% rename from dotest rename to tools/applymbox index c6f44e1c41..5ac8d2b4da 100755 --- a/dotest +++ b/tools/applymbox @@ -4,6 +4,8 @@ ## 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" ## @@ -20,7 +22,7 @@ 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 + git-stripspace < .dotest/msg > .dotest/msg-clean applypatch .dotest/msg-clean .dotest/patch .dotest/info "$2" ret=$? if [ $ret -ne 0 ]; then diff --git a/applypatch b/tools/applypatch similarity index 100% rename from applypatch rename to tools/applypatch diff --git a/mailinfo.c b/tools/mailinfo.c similarity index 100% rename from mailinfo.c rename to tools/mailinfo.c diff --git a/mailsplit.c b/tools/mailsplit.c similarity index 100% rename from mailsplit.c rename to tools/mailsplit.c