├── mkfile ├── gitfilestat ├── gitfileget ├── COPYING ├── gitfileshow ├── README ├── acme.h ├── wait.c ├── acme.c └── main.c /mkfile: -------------------------------------------------------------------------------- 1 | <$PLAN9/src/mkhdr 2 | 3 | TARG=Gitfiles 4 | 5 | OFILES=\ 6 | acme.$O\ 7 | main.$O\ 8 | wait.$O\ 9 | 10 | HFILES=acme.h 11 | 12 | <$PLAN9/src/mkone 13 | 14 | XTARG=\ 15 | gitfileget\ 16 | gitfilestat\ 17 | gitfileshow 18 | 19 | install:V: 20 | for i in $XTARG; do 21 | cp $i $BIN 22 | done 23 | 24 | -------------------------------------------------------------------------------- /gitfilestat: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rc 2 | 3 | if(! ~ $#* 3){ 4 | echo usage: gitfilestat root treeish path >[1=2] 5 | exit usage 6 | } 7 | 8 | cd $1 9 | 10 | t = `{git ls-tree -d $2 $3} 11 | switch($t(2)){ 12 | case tree 13 | echo directory 14 | case blob 15 | echo file 16 | case * 17 | t = `{git ls-tree $2 $3} 18 | if(~ $#t 0) 19 | echo nonexistent 20 | if not 21 | echo file 22 | } 23 | -------------------------------------------------------------------------------- /gitfileget: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rc 2 | 3 | f=getfile 4 | if(~ $1 -d){ 5 | f=getdir 6 | shift 7 | } 8 | 9 | if(! ~ $#* 3){ 10 | echo 'usage: gitfileget [-d] root treeish path' >[1=2] 11 | exit usage 12 | } 13 | 14 | cd $1 15 | 16 | fn getfile { 17 | git show $1:$2 18 | } 19 | 20 | fn getdir { 21 | git ls-tree $1 $2/ | awk ' 22 | { 23 | sub("'$2'/", "", $4) 24 | if($2 == "tree") 25 | print $4 "/" 26 | else 27 | print $4 28 | }' 29 | } 30 | 31 | $f $2 $3 32 | 33 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | 2 | Based on Netfiles, copyright: 3 | 4 | 5 | Copyright (c) 2005 Russ Cox 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | "Software"), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | These conditions shall not be whined about. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | 28 | -------------------------------------------------------------------------------- /gitfileshow: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rc 2 | 3 | root = `{git rev-parse --show-toplevel} 4 | 5 | if(~ $#* 0) 6 | * = (HEAD) 7 | 8 | t1 = /tmp/diff1.$pid 9 | t2 = /tmp/diff2.$pid 10 | 11 | t1e = '\\/tmp\\/diff1.'$pid 12 | t2e = '\\/tmp\\/diff2.'$pid 13 | 14 | roote = `{echo $root | 9 sed -e 's;/;\\\\/;g'} 15 | 16 | # :100644 100644 7802edc66d552f9178ae98fd84b9810f7e6ac7f7 e192231fff4db1df62dea187db4613b7fc8f14d8 M tests/java/com/twitter/adserver/integration/BUILD 17 | # :000000 100644 0000000000000000000000000000000000000000 e7d8c70e9fc1b1e849cab4131c2d7592ee3e8602 A tests/resources/com/twitter/adserver/integration/BUILD 18 | 19 | git log --stat -1 $1 20 | echo 21 | 22 | git diff-tree -r $1 | awk 'NR==1 {next} { 23 | d1 = "/dev/null" 24 | d2 = "/dev/null" 25 | 26 | if($3 != "0000000000000000000000000000000000000000"){ 27 | print "gitfileget '$root' '$1'~1 " $6 " >'$t1'" 28 | d1 = "'$t1'" 29 | } 30 | 31 | if($4 != "0000000000000000000000000000000000000000"){ 32 | print "gitfileget '$root' '$1' " $6 " >'$t2'" 33 | d2 = "'$t2'" 34 | } 35 | 36 | path = $6 37 | gsub("/", "\\/", path) 38 | 39 | print "9 diff -c "d1" "d2 " | ssam '',x/^\\/.*\\n/ x/[a-zA-Z0-9._\\/]+/ { " 40 | print " g/'$t1e'/ c/'$roote'@'$1'~1\\/"path"/" 41 | print " g/'$t2e'/ c/'$roote'@'$1'\\/"path"/" 42 | print"}''" 43 | }' | rc 44 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Gitfiles provides support for browsing Git repositories inside Acme. 2 | 3 | It works like, and is based on, Netfiles[1]. Gitfiles presents read-only 4 | access to a fake filesystem of files stored in Git. Gitfiles accepts paths 5 | of the form 6 | 7 | /path/to/repo@tree-ish/repository/path 8 | 9 | Where /path/to/repo is a valid Git repository, and tree-ish[2] is a valid 10 | tree, commit or tag object name. 11 | 12 | Gitfiles is best used with the following plumbing rules. The first rule 13 | plumbs paths to Gitfiles; the second rule plumbs Git commits, formatted 14 | so that their diff shows Gitfiles paths. 15 | 16 | kind is text 17 | data matches '[a-zA-Z0-9_\-./~{}@]+('$addr')?' 18 | data matches '((/[a-zA-Z0-9_\-./]+)@[a-zA-Z0-9_\-./~{}@]+)('$addr')?' 19 | data set $1 20 | arg isdir $2 21 | attr add addr=$4 22 | plumb to gitfileedit 23 | plumb client Gitfiles 24 | 25 | type is text 26 | data matches '[a-zA-Z¡-￿0-9_\-./]+' 27 | data matches '([0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f])[0-9a-f]*' 28 | plumb start rc -c 'cd '''$wdir'''; root=`{git rev-parse --show-toplevel}; rev='''$1'''; {gitfileshow $rev} >[2=1] | nobs | plumb -i -d edit -a ''action=showdata filename=''$root/+$rev' 29 | 30 | Installation 31 | 32 | Gitfiles is a plan9ports[3] program. Install with mk: 33 | 34 | % mk install 35 | 36 | which will build and then install the binaries into $PLAN9/bin. 37 | 38 | [1] http://swtch.com/plan9port/man/man1/netfiles.html 39 | [2] http://stackoverflow.com/questions/4044368/what-does-tree-ish-mean-in-git 40 | [3] http://swtch.com/plan9port/ 41 | -------------------------------------------------------------------------------- /acme.h: -------------------------------------------------------------------------------- 1 | typedef struct Event Event; 2 | typedef struct Win Win; 3 | 4 | #define EVENTSIZE 256 5 | struct Event 6 | { 7 | int c1; 8 | int c2; 9 | int oq0; 10 | int oq1; 11 | int q0; 12 | int q1; 13 | int flag; 14 | int nb; 15 | int nr; 16 | char text[EVENTSIZE*UTFmax+1]; 17 | char arg[EVENTSIZE*UTFmax+1]; 18 | char loc[EVENTSIZE*UTFmax+1]; 19 | }; 20 | 21 | struct Win 22 | { 23 | int id; 24 | CFid *ctl; 25 | CFid *tag; 26 | CFid *body; 27 | CFid *addr; 28 | CFid *event; 29 | CFid *data; 30 | CFid *xdata; 31 | Channel *c; /* chan(Event) */ 32 | Win *next; 33 | Win *prev; 34 | 35 | /* events */ 36 | int nbuf; 37 | char name[1024]; 38 | char buf[1024]; 39 | char *bufp; 40 | jmp_buf jmp; 41 | Event e2; 42 | Event e3; 43 | Event e4; 44 | }; 45 | 46 | Win *newwin(void); 47 | 48 | int eventfmt(Fmt*); 49 | int pipewinto(Win *w, char *name, int, char *fmt, ...); 50 | int pipetowin(Win *w, char *name, int, char *fmt, ...); 51 | char *sysrun(int errto, char*, ...); 52 | int winaddr(Win *w, char *fmt, ...); 53 | int winctl(Win *w, char *fmt, ...); 54 | int windel(Win *w, int sure); 55 | int winfd(Win *w, char *name, int); 56 | char *winmread(Win *w, char *file); 57 | int winname(Win *w, char *fmt, ...); 58 | int winprint(Win *w, char *name, char *fmt, ...); 59 | int winread(Win *w, char *file, void *a, int n); 60 | int winseek(Win *w, char *file, int n, int off); 61 | int winreadaddr(Win *w, uint*); 62 | int winreadevent(Win *w, Event *e); 63 | int winwrite(Win *w, char *file, void *a, int n); 64 | int winwriteevent(Win *w, Event *e); 65 | int winopenfd(Win *w, char *name, int mode); 66 | void windeleteall(void); 67 | void winfree(Win *w); 68 | void winclosefiles(Win *w); 69 | Channel *wineventchan(Win *w); 70 | char *winindex(void); 71 | void mountacme(void); 72 | char *wingetname(Win *w); 73 | 74 | void *erealloc(void*, uint); 75 | void *emalloc(uint); 76 | char *estrdup(char*); 77 | char *evsmprint(char*, va_list); 78 | 79 | int twait(int); 80 | void twaitinit(void); 81 | 82 | extern Win *windows; 83 | -------------------------------------------------------------------------------- /wait.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include <9pclient.h> 5 | #include "acme.h" 6 | 7 | extern int debug; 8 | 9 | #define dprint if(debug>1)print 10 | 11 | typedef struct Waitreq Waitreq; 12 | struct Waitreq 13 | { 14 | int pid; 15 | Channel *c; 16 | }; 17 | 18 | /* 19 | * watch the exiting children 20 | */ 21 | Channel *twaitchan; /* chan(Waitreq) */ 22 | void 23 | waitthread(void *v) 24 | { 25 | Alt a[3]; 26 | Waitmsg *w, **wq; 27 | Waitreq *rq, r; 28 | int i, nrq, nwq; 29 | 30 | threadsetname("waitthread"); 31 | a[0].c = threadwaitchan(); 32 | a[0].v = &w; 33 | a[0].op = CHANRCV; 34 | a[1].c = twaitchan; 35 | a[1].v = &r; 36 | a[1].op = CHANRCV; 37 | a[2].op = CHANEND; 38 | 39 | nrq = 0; 40 | nwq = 0; 41 | rq = nil; 42 | wq = nil; 43 | dprint("wait: start\n"); 44 | for(;;){ 45 | cont2:; 46 | dprint("wait: alt\n"); 47 | switch(alt(a)){ 48 | case 0: 49 | dprint("wait: pid %d exited\n", w->pid); 50 | for(i=0; ipid){ 52 | dprint("wait: match with rq chan %p\n", rq[i].c); 53 | sendp(rq[i].c, w); 54 | rq[i] = rq[--nrq]; 55 | goto cont2; 56 | } 57 | } 58 | if(i == nrq){ 59 | dprint("wait: queueing waitmsg\n"); 60 | wq = erealloc(wq, (nwq+1)*sizeof(wq[0])); 61 | wq[nwq++] = w; 62 | } 63 | break; 64 | 65 | case 1: 66 | dprint("wait: req for pid %d chan %p\n", r.pid, r.c); 67 | for(i=0; ipid == r.pid){ 69 | dprint("wait: match with waitmsg\n"); 70 | sendp(r.c, w); 71 | wq[i] = wq[--nwq]; 72 | goto cont2; 73 | } 74 | } 75 | if(i == nwq){ 76 | dprint("wait: queueing req\n"); 77 | rq = erealloc(rq, (nrq+1)*sizeof(rq[0])); 78 | rq[nrq] = r; 79 | dprint("wait: queueing req pid %d chan %p\n", rq[nrq].pid, rq[nrq].c); 80 | nrq++; 81 | } 82 | break; 83 | } 84 | } 85 | } 86 | 87 | Waitmsg* 88 | twaitfor(int pid) 89 | { 90 | Waitreq r; 91 | Waitmsg *w; 92 | 93 | r.pid = pid; 94 | r.c = chancreate(sizeof(Waitmsg*), 1); 95 | send(twaitchan, &r); 96 | w = recvp(r.c); 97 | chanfree(r.c); 98 | return w; 99 | } 100 | 101 | int 102 | twait(int pid) 103 | { 104 | int x; 105 | Waitmsg *w; 106 | 107 | w = twaitfor(pid); 108 | x = w->msg[0] != 0 ? -1 : 0; 109 | free(w); 110 | return x; 111 | } 112 | 113 | void 114 | twaitinit(void) 115 | { 116 | threadwaitchan(); /* allocate it before returning */ 117 | twaitchan = chancreate(sizeof(Waitreq), 10); 118 | threadcreate(waitthread, nil, 128*1024); 119 | } 120 | 121 | -------------------------------------------------------------------------------- /acme.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include <9pclient.h> 5 | #include "acme.h" 6 | 7 | extern int *xxx; 8 | static CFsys *acmefs; 9 | Win *windows; 10 | static Win *last; 11 | 12 | void 13 | mountacme(void) 14 | { 15 | if(acmefs == nil){ 16 | acmefs = nsmount("acme", nil); 17 | if(acmefs == nil) 18 | sysfatal("cannot mount acme: %r"); 19 | } 20 | } 21 | 22 | Win* 23 | newwin(void) 24 | { 25 | Win *w; 26 | CFid *fid; 27 | char buf[100]; 28 | int id, n; 29 | 30 | mountacme(); 31 | fid = fsopen(acmefs, "new/ctl", ORDWR); 32 | if(fid == nil) 33 | sysfatal("open new/ctl: %r"); 34 | n = fsread(fid, buf, sizeof buf-1); 35 | if(n <= 0) 36 | sysfatal("read new/ctl: %r"); 37 | buf[n] = 0; 38 | id = atoi(buf); 39 | if(id == 0) 40 | sysfatal("read new/ctl: malformed message: %s", buf); 41 | 42 | w = emalloc(sizeof *w); 43 | w->id = id; 44 | w->ctl = fid; 45 | w->next = nil; 46 | w->prev = last; 47 | if(last) 48 | last->next = w; 49 | else 50 | windows = w; 51 | last = w; 52 | return w; 53 | } 54 | 55 | void 56 | winclosefiles(Win *w) 57 | { 58 | if(w->ctl){ 59 | fsclose(w->ctl); 60 | w->ctl = nil; 61 | } 62 | if(w->body){ 63 | fsclose(w->body); 64 | w->body = nil; 65 | } 66 | if(w->addr){ 67 | fsclose(w->addr); 68 | w->addr = nil; 69 | } 70 | if(w->tag){ 71 | fsclose(w->tag); 72 | w->tag = nil; 73 | } 74 | if(w->event){ 75 | fsclose(w->event); 76 | w->event = nil; 77 | } 78 | if(w->data){ 79 | fsclose(w->data); 80 | w->data = nil; 81 | } 82 | if(w->xdata){ 83 | fsclose(w->xdata); 84 | w->xdata = nil; 85 | } 86 | } 87 | 88 | void 89 | winfree(Win *w) 90 | { 91 | winclosefiles(w); 92 | if(w->c){ 93 | chanfree(w->c); 94 | w->c = nil; 95 | } 96 | if(w->next) 97 | w->next->prev = w->prev; 98 | else 99 | last = w->prev; 100 | if(w->prev) 101 | w->prev->next = w->next; 102 | else 103 | windows = w->next; 104 | free(w); 105 | } 106 | 107 | void 108 | windeleteall(void) 109 | { 110 | Win *w, *next; 111 | 112 | for(w=windows; w; w=next){ 113 | next = w->next; 114 | winctl(w, "delete"); 115 | } 116 | } 117 | 118 | static CFid* 119 | wfid(Win *w, char *name) 120 | { 121 | char buf[100]; 122 | CFid **fid; 123 | 124 | if(strcmp(name, "ctl") == 0) 125 | fid = &w->ctl; 126 | else if(strcmp(name, "body") == 0) 127 | fid = &w->body; 128 | else if(strcmp(name, "addr") == 0) 129 | fid = &w->addr; 130 | else if(strcmp(name, "tag") == 0) 131 | fid = &w->tag; 132 | else if(strcmp(name, "event") == 0) 133 | fid = &w->event; 134 | else if(strcmp(name, "data") == 0) 135 | fid = &w->data; 136 | else if(strcmp(name, "xdata") == 0) 137 | fid = &w->xdata; 138 | else{ 139 | fid = 0; 140 | sysfatal("bad window file name %s", name); 141 | } 142 | 143 | if(*fid == nil){ 144 | snprint(buf, sizeof buf, "acme/%d/%s", w->id, name); 145 | *fid = fsopen(acmefs, buf, ORDWR); 146 | if(*fid == nil) 147 | sysfatal("open %s: %r", buf); 148 | } 149 | return *fid; 150 | } 151 | 152 | int 153 | winopenfd(Win *w, char *name, int mode) 154 | { 155 | char buf[100]; 156 | 157 | snprint(buf, sizeof buf, "%d/%s", w->id, name); 158 | return fsopenfd(acmefs, buf, mode); 159 | } 160 | 161 | int 162 | winctl(Win *w, char *fmt, ...) 163 | { 164 | char *s; 165 | va_list arg; 166 | CFid *fid; 167 | int n; 168 | 169 | va_start(arg, fmt); 170 | s = evsmprint(fmt, arg); 171 | va_end(arg); 172 | 173 | fid = wfid(w, "ctl"); 174 | n = fspwrite(fid, s, strlen(s), 0); 175 | free(s); 176 | return n; 177 | } 178 | 179 | int 180 | winname(Win *w, char *fmt, ...) 181 | { 182 | char *s; 183 | va_list arg; 184 | int n; 185 | 186 | va_start(arg, fmt); 187 | s = evsmprint(fmt, arg); 188 | va_end(arg); 189 | 190 | n = winctl(w, "name %s\n", s); 191 | free(s); 192 | return n; 193 | } 194 | 195 | int 196 | winprint(Win *w, char *name, char *fmt, ...) 197 | { 198 | char *s; 199 | va_list arg; 200 | int n; 201 | 202 | va_start(arg, fmt); 203 | s = evsmprint(fmt, arg); 204 | va_end(arg); 205 | 206 | n = fswrite(wfid(w, name), s, strlen(s)); 207 | free(s); 208 | return n; 209 | } 210 | 211 | int 212 | winaddr(Win *w, char *fmt, ...) 213 | { 214 | char *s; 215 | va_list arg; 216 | int n; 217 | 218 | va_start(arg, fmt); 219 | s = evsmprint(fmt, arg); 220 | va_end(arg); 221 | 222 | n = fswrite(wfid(w, "addr"), s, strlen(s)); 223 | free(s); 224 | return n; 225 | } 226 | 227 | int 228 | winreadaddr(Win *w, uint *q1) 229 | { 230 | char buf[40], *p; 231 | uint q0; 232 | int n; 233 | 234 | n = fspread(wfid(w, "addr"), buf, sizeof buf-1, 0); 235 | if(n <= 0) 236 | return -1; 237 | buf[n] = 0; 238 | q0 = strtoul(buf, &p, 10); 239 | if(q1) 240 | *q1 = strtoul(p, nil, 10); 241 | return q0; 242 | } 243 | 244 | int 245 | winread(Win *w, char *file, void *a, int n) 246 | { 247 | return fspread(wfid(w, file), a, n, 0); 248 | } 249 | 250 | int 251 | winwrite(Win *w, char *file, void *a, int n) 252 | { 253 | return fswrite(wfid(w, file), a, n); 254 | } 255 | 256 | char* 257 | fsreadm(CFid *fid) 258 | { 259 | char *buf; 260 | int n, tot, m; 261 | 262 | m = 128; 263 | buf = emalloc(m+1); 264 | tot = 0; 265 | while((n = fspread(fid, buf+tot, m-tot, tot)) > 0){ 266 | tot += n; 267 | if(tot >= m){ 268 | m += 128; 269 | buf = erealloc(buf, m+1); 270 | } 271 | } 272 | if(n < 0){ 273 | free(buf); 274 | return nil; 275 | } 276 | buf[tot] = 0; 277 | return buf; 278 | } 279 | 280 | char* 281 | winmread(Win *w, char *file) 282 | { 283 | return fsreadm(wfid(w, file)); 284 | } 285 | 286 | char* 287 | winindex(void) 288 | { 289 | CFid *fid; 290 | char *s; 291 | 292 | mountacme(); 293 | if((fid = fsopen(acmefs, "index", OREAD)) == nil) 294 | return nil; 295 | s = fsreadm(fid); 296 | fsclose(fid); 297 | return s; 298 | } 299 | 300 | int 301 | winseek(Win *w, char *file, int n, int off) 302 | { 303 | return fsseek(wfid(w, file), n, off); 304 | } 305 | 306 | int 307 | winwriteevent(Win *w, Event *e) 308 | { 309 | char buf[100]; 310 | 311 | snprint(buf, sizeof buf, "%c%c%d %d \n", e->c1, e->c2, e->q0, e->q1); 312 | return fswrite(wfid(w, "event"), buf, strlen(buf)); 313 | } 314 | 315 | int 316 | windel(Win *w, int sure) 317 | { 318 | return winctl(w, sure ? "delete" : "del"); 319 | } 320 | 321 | int 322 | winfd(Win *w, char *name, int mode) 323 | { 324 | char buf[100]; 325 | 326 | snprint(buf, sizeof buf, "acme/%d/%s", w->id, name); 327 | return fsopenfd(acmefs, buf, mode); 328 | } 329 | 330 | static void 331 | error(Win *w, char *msg) 332 | { 333 | if(msg == nil) 334 | longjmp(w->jmp, 1); 335 | fprint(2, "%s: win%d: %s\n", argv0, w->id, msg); 336 | longjmp(w->jmp, 2); 337 | } 338 | 339 | static int 340 | getec(Win *w, CFid *efd) 341 | { 342 | if(w->nbuf <= 0){ 343 | w->nbuf = fsread(efd, w->buf, sizeof w->buf); 344 | if(w->nbuf <= 0) 345 | error(w, nil); 346 | w->bufp = w->buf; 347 | } 348 | --w->nbuf; 349 | return *w->bufp++; 350 | } 351 | 352 | static int 353 | geten(Win *w, CFid *efd) 354 | { 355 | int n, c; 356 | 357 | n = 0; 358 | while('0'<=(c=getec(w,efd)) && c<='9') 359 | n = n*10+(c-'0'); 360 | if(c != ' ') 361 | error(w, "event number syntax"); 362 | return n; 363 | } 364 | 365 | static int 366 | geter(Win *w, CFid *efd, char *buf, int *nb) 367 | { 368 | Rune r; 369 | int n; 370 | 371 | r = getec(w, efd); 372 | buf[0] = r; 373 | n = 1; 374 | if(r < Runeself) 375 | goto Return; 376 | while(!fullrune(buf, n)) 377 | buf[n++] = getec(w, efd); 378 | chartorune(&r, buf); 379 | Return: 380 | *nb = n; 381 | return r; 382 | } 383 | 384 | static void 385 | gete(Win *w, CFid *efd, Event *e) 386 | { 387 | int i, nb; 388 | 389 | e->c1 = getec(w, efd); 390 | e->c2 = getec(w, efd); 391 | e->q0 = geten(w, efd); 392 | e->q1 = geten(w, efd); 393 | e->flag = geten(w, efd); 394 | e->nr = geten(w, efd); 395 | if(e->nr > EVENTSIZE) 396 | error(w, "event string too long"); 397 | e->nb = 0; 398 | for(i=0; inr; i++){ 399 | /* e->r[i] = */ geter(w, efd, e->text+e->nb, &nb); 400 | e->nb += nb; 401 | } 402 | /* e->r[e->nr] = 0; */ 403 | e->text[e->nb] = 0; 404 | if(getec(w, efd) != '\n') 405 | error(w, "event syntax 2"); 406 | } 407 | 408 | int 409 | winreadevent(Win *w, Event *e) 410 | { 411 | CFid *efd; 412 | int r; 413 | 414 | if((r = setjmp(w->jmp)) != 0){ 415 | if(r == 1) 416 | return 0; 417 | return -1; 418 | } 419 | efd = wfid(w, "event"); 420 | gete(w, efd, e); 421 | e->oq0 = e->q0; 422 | e->oq1 = e->q1; 423 | 424 | /* expansion */ 425 | if(e->flag&2){ 426 | gete(w, efd, &w->e2); 427 | if(e->q0==e->q1){ 428 | w->e2.oq0 = e->q0; 429 | w->e2.oq1 = e->q1; 430 | w->e2.flag = e->flag; 431 | *e = w->e2; 432 | } 433 | } 434 | 435 | /* chorded argument */ 436 | if(e->flag&8){ 437 | gete(w, efd, &w->e3); /* arg */ 438 | gete(w, efd, &w->e4); /* location */ 439 | strcpy(e->arg, w->e3.text); 440 | strcpy(e->loc, w->e4.text); 441 | } 442 | 443 | return 1; 444 | } 445 | 446 | int 447 | eventfmt(Fmt *fmt) 448 | { 449 | Event *e; 450 | 451 | e = va_arg(fmt->args, Event*); 452 | return fmtprint(fmt, "%c%c %d %d %d %d %q", e->c1, e->c2, e->q0, e->q1, e->flag, e->nr, e->text); 453 | } 454 | 455 | void* 456 | emalloc(uint n) 457 | { 458 | void *v; 459 | 460 | v = mallocz(n, 1); 461 | if(v == nil) 462 | sysfatal("out of memory"); 463 | return v; 464 | } 465 | 466 | void* 467 | erealloc(void *v, uint n) 468 | { 469 | v = realloc(v, n); 470 | if(v == nil) 471 | sysfatal("out of memory"); 472 | return v; 473 | } 474 | 475 | char* 476 | estrdup(char *s) 477 | { 478 | if(s == nil) 479 | return nil; 480 | s = strdup(s); 481 | if(s == nil) 482 | sysfatal("out of memory"); 483 | return s; 484 | } 485 | 486 | char* 487 | evsmprint(char *s, va_list v) 488 | { 489 | s = vsmprint(s, v); 490 | if(s == nil) 491 | sysfatal("out of memory"); 492 | return s; 493 | } 494 | 495 | int 496 | pipewinto(Win *w, char *name, int errto, char *cmd, ...) 497 | { 498 | va_list arg; 499 | char *p; 500 | int fd[3], pid; 501 | 502 | va_start(arg, cmd); 503 | p = evsmprint(cmd, arg); 504 | va_end(arg); 505 | fd[0] = winfd(w, name, OREAD); 506 | fd[1] = dup(errto, -1); 507 | fd[2] = dup(errto, -1); 508 | pid = threadspawnl(fd, "rc", "rc", "-c", p, 0); 509 | free(p); 510 | return pid; 511 | } 512 | 513 | int 514 | pipetowin(Win *w, char *name, int errto, char *cmd, ...) 515 | { 516 | va_list arg; 517 | char *p; 518 | int fd[3], pid, pfd[2]; 519 | char buf[1024]; 520 | int n; 521 | 522 | /* 523 | * cannot use winfd here because of buffering caused 524 | * by pipe. program might exit before final write to acme 525 | * happens. so we might return before the final write. 526 | * 527 | * to avoid this, we tend the pipe ourselves. 528 | */ 529 | if(pipe(pfd) < 0) 530 | sysfatal("pipe: %r"); 531 | va_start(arg, cmd); 532 | p = evsmprint(cmd, arg); 533 | va_end(arg); 534 | fd[0] = open("/dev/null", OREAD); 535 | fd[1] = pfd[1]; 536 | if(errto == 0) 537 | fd[2] = dup(fd[1], -1); 538 | else 539 | fd[2] = dup(errto, -1); 540 | pid = threadspawnl(fd, "rc", "rc", "-c", p, 0); 541 | free(p); 542 | while((n = read(pfd[0], buf, sizeof buf)) > 0) 543 | winwrite(w, name, buf, n); 544 | close(pfd[0]); 545 | return pid; 546 | } 547 | 548 | char* 549 | sysrun(int errto, char *fmt, ...) 550 | { 551 | static char buf[1024]; 552 | char *cmd; 553 | va_list arg; 554 | int n, fd[3], p[2], tot, pid; 555 | 556 | #undef pipe 557 | if(pipe(p) < 0) 558 | sysfatal("pipe: %r"); 559 | fd[0] = open("/dev/null", OREAD); 560 | fd[1] = p[1]; 561 | if(errto == 0) 562 | fd[2] = dup(fd[1], -1); 563 | else 564 | fd[2] = dup(errto, -1); 565 | 566 | va_start(arg, fmt); 567 | cmd = evsmprint(fmt, arg); 568 | va_end(arg); 569 | pid = threadspawnl(fd, "rc", "rc", "-c", cmd, 0); 570 | 571 | tot = 0; 572 | while((n = read(p[0], buf+tot, sizeof buf-tot)) > 0) 573 | tot += n; 574 | close(p[0]); 575 | twait(pid); 576 | if(n < 0) 577 | return nil; 578 | free(cmd); 579 | if(tot == sizeof buf) 580 | tot--; 581 | buf[tot] = 0; 582 | while(tot > 0 && isspace((uchar)buf[tot-1])) 583 | tot--; 584 | buf[tot] = 0; 585 | if(tot == 0){ 586 | werrstr("no output"); 587 | return nil; 588 | } 589 | return estrdup(buf); 590 | } 591 | 592 | static void 593 | eventreader(void *v) 594 | { 595 | Event e[2]; 596 | Win *w; 597 | int i; 598 | 599 | w = v; 600 | i = 0; 601 | for(;;){ 602 | if(winreadevent(w, &e[i]) <= 0) 603 | break; 604 | sendp(w->c, &e[i]); 605 | i = 1-i; /* toggle */ 606 | } 607 | sendp(w->c, nil); 608 | threadexits(nil); 609 | } 610 | 611 | Channel* 612 | wineventchan(Win *w) 613 | { 614 | if(w->c == nil){ 615 | w->c = chancreate(sizeof(Event*), 0); 616 | threadcreate(eventreader, w, 32*1024); 617 | } 618 | return w->c; 619 | } 620 | 621 | char* 622 | wingetname(Win *w) 623 | { 624 | int n; 625 | char *p; 626 | 627 | n = winread(w, "tag", w->name, sizeof w->name-1); 628 | if(n <= 0) 629 | return nil; 630 | w->name[n] = 0; 631 | p = strchr(w->name, ' '); 632 | if(p) 633 | *p = 0; 634 | return w->name; 635 | } 636 | 637 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Remote file system editing client. 3 | * Only talks to acme - external programs do all the hard work. 4 | * 5 | * If you add a plumbing rule: 6 | 7 | kind is text 8 | data matches '[a-zA-Z0-9_\-./~{}@]+('$addr')?' 9 | data matches '((/[a-zA-Z0-9_\-./]+)@[a-zA-Z0-9_\-./~{}@]+)('$addr')?' 10 | data set $1 11 | arg isdir $2 12 | attr add addr=$4 13 | plumb to gitfileedit 14 | plumb client Gitfiles 15 | 16 | * then plumbed paths starting with /n/ will find their way here. 17 | * 18 | * Perhaps on startup should look for windows named /n/ and attach to them? 19 | * Or might that be too aggressive? 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include <9pclient.h> 26 | #include 27 | #include "acme.h" 28 | 29 | void 30 | usage(void) 31 | { 32 | fprint(2, "usage: Gitfiles\n"); 33 | threadexitsall("usage"); 34 | } 35 | 36 | extern int chatty9pclient; 37 | int debug; 38 | #define dprint if(debug>1)print 39 | #define cprint if(debug)print 40 | Win *mkwin(char*); 41 | int do3(Win *w, char *arg); 42 | 43 | enum { 44 | STACK = 128*1024 45 | }; 46 | 47 | enum { 48 | Get, 49 | Del, 50 | Delete, 51 | Debug, 52 | XXX 53 | }; 54 | 55 | char *cmds[] = { 56 | "Get", 57 | "Del", 58 | "Delete", 59 | "Debug", 60 | nil 61 | }; 62 | 63 | char *debugstr[] = { 64 | "off", 65 | "minimal", 66 | "chatty" 67 | }; 68 | 69 | typedef struct Arg Arg; 70 | struct Arg 71 | { 72 | char *file; 73 | char *addr; 74 | Channel *c; 75 | }; 76 | 77 | Arg* 78 | arg(char *file, char *addr, Channel *c) 79 | { 80 | Arg *a; 81 | 82 | a = emalloc(sizeof *a); 83 | a->file = estrdup(file); 84 | a->addr = estrdup(addr); 85 | a->c = c; 86 | return a; 87 | } 88 | 89 | Win* 90 | winbyid(int id) 91 | { 92 | Win *w; 93 | 94 | for(w=windows; w; w=w->next) 95 | if(w->id == id) 96 | return w; 97 | return nil; 98 | } 99 | 100 | /* 101 | * return Win* of a window named name or name/ 102 | * assumes name is cleaned. 103 | */ 104 | Win* 105 | nametowin(char *name) 106 | { 107 | char *index, *p, *next; 108 | int len, n; 109 | Win *w; 110 | 111 | index = winindex(); 112 | len = strlen(name); 113 | for(p=index; p && *p; p=next){ 114 | if((next = strchr(p, '\n')) != nil) 115 | *next++ = 0; 116 | if(strlen(p) <= 5*12) 117 | continue; 118 | if(memcmp(p+5*12, name, len)!=0) 119 | continue; 120 | if(p[5*12+len]!=' ' && (p[5*12+len]!='/' || p[5*12+len+1]!=' ')) 121 | continue; 122 | n = atoi(p); 123 | if((w = winbyid(n)) != nil){ 124 | free(index); 125 | return w; 126 | } 127 | } 128 | free(index); 129 | return nil; 130 | } 131 | 132 | 133 | /* 134 | * look for s in list 135 | */ 136 | int 137 | lookup(char *s, char **list) 138 | { 139 | int i; 140 | 141 | for(i=0; list[i]; i++) 142 | if(strcmp(list[i], s) == 0) 143 | return i; 144 | return -1; 145 | } 146 | 147 | /* 148 | * move to top of file 149 | */ 150 | void 151 | wintop(Win *w) 152 | { 153 | winaddr(w, "#0"); 154 | winctl(w, "dot=addr"); 155 | winctl(w, "show"); 156 | } 157 | 158 | int 159 | isdot(Win *w, uint xq0, uint xq1) 160 | { 161 | uint q0, q1; 162 | 163 | winctl(w, "addr=dot"); 164 | q0 = winreadaddr(w, &q1); 165 | return xq0==q0 && xq1==q1; 166 | } 167 | 168 | /* 169 | * Expand the click further than acme usually does -- all non-white space is okay. 170 | */ 171 | char* 172 | expandarg(Win *w, Event *e) 173 | { 174 | uint q0, q1; 175 | 176 | if(e->c2 == 'l') /* in tag - no choice but to accept acme's expansion */ 177 | return estrdup(e->text); 178 | winaddr(w, ","); 179 | winctl(w, "addr=dot"); 180 | 181 | q0 = winreadaddr(w, &q1); 182 | cprint("acme expanded %d-%d into %d-%d (dot %d-%d)\n", 183 | e->oq0, e->oq1, e->q0, e->q1, q0, q1); 184 | 185 | if(e->oq0 == e->oq1 && e->q0 != e->q1 && !isdot(w, e->q0, e->q1)){ 186 | winaddr(w, "#%ud+#1-/[^ \t\\n]*/,#%ud-#1+/[^ \t\\n]*/", e->q0, e->q1); 187 | q0 = winreadaddr(w, &q1); 188 | cprint("\tre-expand to %d-%d\n", q0, q1); 189 | }else 190 | winaddr(w, "#%ud,#%ud", e->q0, e->q1); 191 | return winmread(w, "xdata"); 192 | } 193 | 194 | /* 195 | * handle a plumbing message 196 | */ 197 | void 198 | doplumb(void *vm) 199 | { 200 | char *addr; 201 | Plumbmsg *m; 202 | Win *w; 203 | 204 | m = vm; 205 | if(m->ndata >= 1024){ 206 | fprint(2, "insanely long file name (%d bytes) in plumb message (%.32s...)\n", 207 | m->ndata, m->data); 208 | plumbfree(m); 209 | return; 210 | } 211 | 212 | addr = plumblookup(m->attr, "addr"); 213 | w = nametowin(m->data); 214 | if(w == nil) 215 | w = mkwin(m->data); 216 | winaddr(w, "%s", addr); 217 | winctl(w, "dot=addr"); 218 | winctl(w, "show"); 219 | /* windecref(w); */ 220 | plumbfree(m); 221 | } 222 | 223 | /* 224 | * dispatch messages from the plumber 225 | */ 226 | void 227 | plumbthread(void *v) 228 | { 229 | CFid *fid; 230 | Plumbmsg *m; 231 | 232 | threadsetname("plumbthread"); 233 | fid = plumbopenfid("gitfileedit", OREAD); 234 | if(fid == nil){ 235 | fprint(2, "cannot open plumb/gitfileedit: %r\n"); 236 | return; 237 | } 238 | while((m = plumbrecvfid(fid)) != nil) 239 | threadcreate(doplumb, m, STACK); 240 | fsclose(fid); 241 | } 242 | 243 | /* 244 | * parse /.../foo@treeish/... 245 | */ 246 | int 247 | parsename(char *name, char **root, char **treeish, char **path) 248 | { 249 | char *p, *q; 250 | 251 | cleanname(name); 252 | if((p=strchr(name, '@')) == nil) 253 | return -1; 254 | 255 | *p = 0; 256 | *root = estrdup(name); 257 | *p++ = '@'; 258 | 259 | if(*p == 0 || *p == '/'){ 260 | free(*root); 261 | return -1; 262 | } 263 | 264 | if((q=strchr(p, '/')) == nil){ 265 | *treeish = estrdup(p); 266 | *path = estrdup("."); 267 | return 0; 268 | } 269 | 270 | if(q[1] == 0){ 271 | q[1] = 0; 272 | *treeish = estrdup(p); 273 | *path = estrdup("."); 274 | q[1] = '/'; 275 | return 0; 276 | } 277 | 278 | *q = 0; 279 | *treeish = estrdup(p); 280 | *q++ = '/'; 281 | *path = estrdup(q); 282 | 283 | return 0; 284 | 285 | /* 286 | if(strncmp(name, "/g/", 3) != 0 && name[3] == 0) 287 | return -1; 288 | 289 | nul = nil; 290 | if((p = strchr(name+3, '/')) == nil) 291 | *path = estrdup("/"); 292 | else{ 293 | *path = estrdup(p); 294 | *p = 0; 295 | nul = p; 296 | } 297 | p = name+3; 298 | if(p[0] == 0){ 299 | free(*path); 300 | *treeish = *path = nil; 301 | if(nul) 302 | *nul = '/'; 303 | return -1; 304 | } 305 | *treeish = estrdup(p); 306 | if(nul) 307 | *nul = '/'; 308 | return 0; 309 | */ 310 | } 311 | 312 | /* 313 | * shell out to find the type of a given file 314 | */ 315 | char* 316 | filestat(char *root, char *treeish, char *path) 317 | { 318 | char *type; 319 | static struct { 320 | char *root; 321 | char *treeish; 322 | char *path; 323 | char *type; 324 | } cache; 325 | 326 | if(cache.root && strcmp(root, cache.root) == 0) 327 | if(cache.treeish && strcmp(treeish, cache.treeish) == 0) 328 | if(cache.path && strcmp(path, cache.path) == 0){ 329 | type = estrdup(cache.type); 330 | cprint("9 gitfilestat %q %q %q => %s (cached)\n", root, treeish, path, type); 331 | return type; 332 | } 333 | 334 | type = sysrun(2, "9 gitfilestat %q %q %q", root, treeish, path); 335 | 336 | free(cache.root); 337 | free(cache.treeish); 338 | free(cache.path); 339 | free(cache.type); 340 | cache.root = estrdup(root); 341 | cache.treeish = estrdup(treeish); 342 | cache.path = estrdup(path); 343 | cache.type = estrdup(type); 344 | 345 | cprint("9 gitfilestat %q %q %q => %s\n", root, treeish, path, type); 346 | return type; 347 | } 348 | 349 | /* 350 | * manage a single window 351 | */ 352 | void 353 | filethread(void *v) 354 | { 355 | char *arg, *name, *p, *treeish, *path, *type, *root; 356 | Arg *a; 357 | Channel *c; 358 | Event *e; 359 | Win *w; 360 | 361 | a = v; 362 | threadsetname("file %s", a->file); 363 | w = newwin(); 364 | winname(w, a->file); 365 | winprint(w, "tag", "Get Look "); 366 | c = wineventchan(w); 367 | 368 | goto caseGet; 369 | 370 | while((e=recvp(c)) != nil){ 371 | if(e->c1!='K') 372 | dprint("acme %E\n", e); 373 | if(e->c1=='M') 374 | switch(e->c2){ 375 | case 'x': 376 | case 'X': 377 | switch(lookup(e->text, cmds)){ 378 | caseGet: 379 | case Get: 380 | treeish = nil; 381 | path = nil; 382 | if(parsename(name=wingetname(w), &root, &treeish, &path) < 0){ 383 | fprint(2, "Gitfiles: bad name %s\n", name); 384 | goto out; 385 | } 386 | type = filestat(root, treeish, path); 387 | if(type == nil) 388 | type = estrdup(""); 389 | if(strcmp(type, "file")==0 || strcmp(type, "directory")==0){ 390 | winaddr(w, ","); 391 | winprint(w, "data", "[reading...]"); 392 | winaddr(w, ","); 393 | cprint("9 gitfileget %s%q %q %q\n", 394 | strcmp(type, "file") == 0 ? "" : "-d", root, treeish, path); 395 | if(strcmp(type, "file")==0) 396 | twait(pipetowin(w, "data", 2, "9 gitfileget %q %q %q", root, treeish, path)); 397 | else 398 | twait(pipetowin(w, "data", 2, "9 gitfileget -d %q %q %q | winid=%d mc", root, treeish, path, w->id)); 399 | cleanname(name); 400 | if(strcmp(type, "directory")==0){ 401 | p = name+strlen(name); 402 | if(p[-1] != '/'){ 403 | p[0] = '/'; 404 | p[1] = 0; 405 | } 406 | } 407 | winname(w, name); 408 | wintop(w); 409 | winctl(w, "clean"); 410 | if(a && a->addr){ 411 | winaddr(w, "%s", a->addr); 412 | winctl(w, "dot=addr"); 413 | winctl(w, "show"); 414 | } 415 | } 416 | free(type); 417 | out: 418 | free(treeish); 419 | free(path); 420 | if(a){ 421 | if(a->c){ 422 | sendp(a->c, w); 423 | a->c = nil; 424 | } 425 | free(a->file); 426 | free(a->addr); 427 | free(a); 428 | a = nil; 429 | } 430 | break; 431 | case Del: 432 | winctl(w, "del"); 433 | break; 434 | case Delete: 435 | winctl(w, "delete"); 436 | break; 437 | case Debug: 438 | debug = (debug+1)%3; 439 | print("Gitfiles debug %s\n", debugstr[debug]); 440 | break; 441 | default: 442 | winwriteevent(w, e); 443 | break; 444 | } 445 | break; 446 | case 'l': 447 | case 'L': 448 | arg = expandarg(w, e); 449 | if(arg!=nil && do3(w, arg) < 0) 450 | winwriteevent(w, e); 451 | free(arg); 452 | break; 453 | } 454 | } 455 | winfree(w); 456 | } 457 | 458 | /* 459 | * handle a button 3 click 460 | */ 461 | int 462 | do3(Win *w, char *text) 463 | { 464 | char *addr, *name, *type, *root, *treeish, *path, *p, *q; 465 | static char lastfail[1000]; 466 | 467 | if(text[0] == '/'){ 468 | p = nil; 469 | name = estrdup(text); 470 | }else{ 471 | p = wingetname(w); 472 | if(text[0] != ':'){ 473 | q = strrchr(p, '/'); 474 | *(q+1) = 0; 475 | } 476 | name = emalloc(strlen(p)+1+strlen(text)+1); 477 | strcpy(name, p); 478 | if(text[0] != ':') 479 | strcat(name, "/"); 480 | strcat(name, text); 481 | } 482 | cprint("button3 %s %s => %s\n", p, text, name); 483 | if((addr = strchr(name, ':')) != nil) 484 | *addr++ = 0; 485 | cleanname(name); 486 | cprint("b3 \t=> name=%s addr=%s\n", name, addr); 487 | if(strcmp(name, lastfail) == 0){ 488 | cprint("b3 \t=> nonexistent (cached)\n"); 489 | free(name); 490 | return -1; 491 | } 492 | if(parsename(name, &root, &treeish, &path) < 0){ 493 | cprint("b3 \t=> parsename failed\n"); 494 | free(name); 495 | return -1; 496 | } 497 | type = filestat(root, treeish, path); 498 | free(root); 499 | free(treeish); 500 | free(path); 501 | if(strcmp(type, "file")==0 || strcmp(type, "directory")==0){ 502 | w = nametowin(name); 503 | if(w == nil){ 504 | w = mkwin(name); 505 | cprint("b3 \t=> creating new window %d\n", w->id); 506 | }else 507 | cprint("b3 \t=> reusing window %d\n", w->id); 508 | if(addr){ 509 | winaddr(w, "%s", addr); 510 | winctl(w, "dot=addr"); 511 | } 512 | winctl(w, "show"); 513 | free(name); 514 | free(type); 515 | return 0; 516 | } 517 | /* 518 | * remember last name that didn't exist so that 519 | * only the first right-click is slow when searching for text. 520 | */ 521 | cprint("b3 caching %s => type %s\n", name, type); 522 | strecpy(lastfail, lastfail+sizeof lastfail, name); 523 | free(name); 524 | free(type); 525 | return -1; 526 | } 527 | 528 | Win* 529 | mkwin(char *name) 530 | { 531 | Arg *a; 532 | Channel *c; 533 | Win *w; 534 | 535 | c = chancreate(sizeof(void*), 0); 536 | a = arg(name, nil, c); 537 | threadcreate(filethread, a, STACK); 538 | w = recvp(c); 539 | chanfree(c); 540 | return w; 541 | } 542 | 543 | void 544 | loopthread(void *v) 545 | { 546 | QLock lk; 547 | 548 | threadsetname("loopthread"); 549 | qlock(&lk); 550 | qlock(&lk); 551 | } 552 | 553 | void 554 | threadmain(int argc, char **argv) 555 | { 556 | ARGBEGIN{ 557 | case '9': 558 | chatty9pclient = 1; 559 | break; 560 | case 'D': 561 | debug = 1; 562 | break; 563 | default: 564 | usage(); 565 | }ARGEND 566 | 567 | if(argc) 568 | usage(); 569 | 570 | cprint("gitfiles starting\n"); 571 | 572 | threadnotify(nil, 0); /* set up correct default handlers */ 573 | 574 | fmtinstall('E', eventfmt); 575 | doquote = needsrcquote; 576 | quotefmtinstall(); 577 | 578 | twaitinit(); 579 | threadcreate(plumbthread, nil, STACK); 580 | threadcreate(loopthread, nil, STACK); 581 | threadexits(nil); 582 | } 583 | 584 | --------------------------------------------------------------------------------