diff --git a/sys/src/cmd/git/save.c b/sys/src/cmd/git/save.c index ef4c57400..bdf0a6589 100644 --- a/sys/src/cmd/git/save.c +++ b/sys/src/cmd/git/save.c @@ -219,7 +219,7 @@ dirent(Dirent **ent, int *nent, char *name) int treeify(Object *t, char **path, char **epath, int off, Hash *h) { - int nent, ne, slash; + int nent, ne, slash, isdir; char *s, **p, **ep; Dirent *e, *ent; Object *o; @@ -248,24 +248,15 @@ treeify(Object *t, char **path, char **epath, int off, Hash *h) s[off + ne] = '\0'; /* skip over children (having s as prefix) */ - for(ep = p + 1; ep != epath; ep++){ + for(ep = p + 1; slash && ep != epath; ep++){ if(strncmp(s, *ep, off + ne) != 0) break; if((*ep)[off+ne] != '\0' && (*ep)[off+ne] != '/') break; } - e = dirent(&ent, &nent, s + off); - d = dirstat(s); - if(d == nil){ - /* delete */ - e->name = nil; - - s[off + ne] = slash; - continue; - } - + e = dirent(&ent, &nent, s + off); if(e->islink) sysfatal("symlinks may not be modified: %s", s); if(e->ismod) @@ -273,8 +264,35 @@ treeify(Object *t, char **path, char **epath, int off, Hash *h) s[off + ne] = slash; - if(slash && (d->mode & DMDIR) != 0){ - free(d); + isdir = d != nil && (d->mode & DMDIR) != 0; + /* + * exist? slash? dir? track? + * n _ _ _ -> remove: file gone + * y n n y -> blob: tracked non-dir + * y n y n -> remove: file untracked + * y n y n -> remove: file -> dir + * y n y y -> remove: file -> dir + * y n y n -> untracked dir, cli junk + * y y y n -> recurse + * y y y y -> recurse + */ + if(d == nil || !slash && isdir && tracked(s)){ + /* + * if a tracked file is removed or turned + * into a dir, we want to delete it. We + * only want to change files passed in, and + * not ones along the way, so ignore files + * that have a '/'. + */ + e->name = nil; + s[off + ne] = slash; + continue; + } else if(slash && isdir){ + /* + * If we have a list of entries that go into + * a directory, create a tree node for this + * entry, and recurse down. + */ e->mode = DMDIR | 0755; o = readobject(e->h); if(o == nil || o->type != GTree) @@ -289,13 +307,18 @@ treeify(Object *t, char **path, char **epath, int off, Hash *h) */ if(treeify(o, p, ep, off + ne + 1, &e->h) == 0) e->name = nil; - }else{ - if((d->mode & DMDIR) == 0 && tracked(s)) + }else if(!slash && !isdir){ + /* + * If the file was explicitly passed in and is + * not a dir, we want to either remove it or + * track it, depending on the state of the index. + */ + if(tracked(s) && !isdir) blobify(d, s, &e->mode, &e->h); else e->name = nil; - free(d); } + free(d); } if(nent == 0) sysfatal("%.*s: refusing to update empty directory", off, *path); diff --git a/sys/src/cmd/git/test/merge.rc b/sys/src/cmd/git/test/merge.rc index 5059fd87e..29c0b45e3 100755 --- a/sys/src/cmd/git/test/merge.rc +++ b/sys/src/cmd/git/test/merge.rc @@ -44,7 +44,7 @@ echo @@ merge different files @@ @{ cd b qq git/pull - git/merge origin/front || status='' + q git/merge origin/front || status='' q git/commit -m merged } } @@ -91,3 +91,33 @@ quux ! test -x b/a || die merge remove exec test -x b/b || die merge add exec ! test -x b/c || die merge preserve nonexec c + +# repro for bug in git/save, around missing +# files when saving the merged commit. +mkdir -p scratch/james +echo @@ james test @@ +@{ + cd scratch/james + q git/init + mkdir -p lib/ndb + touch lib/words + q git/add lib/words + q git/commit -m 'add words' lib/words + q git/branch -n myhead + echo stuff > lib/ndb/local + q git/add lib/ndb/local + q git/commit -m 'Add lib/ndb/local' lib/ndb/local + + q git/branch front + echo cromulent >> lib/words + q git/commit -m 'Some change on front' lib/words + q git/branch myhead + q git/merge front + q git/commit -m 'Merge front' + d=`''{git/diff lib/words lib/ndb/local} # should have no output + if(! ~ $d ''){ + echo $d + exit diff + } + exit '' +}