ref: 9ad3c2d098700a84e0d7b3fd7f0939fe4c1de7f8
parent: 9fcb4c0896782a8ccd3136204065ec403c479d6c
author: Ori Bernstein <ori@eigenstate.org>
date: Mon Nov 18 15:34:35 EST 2024
git/save: handle all cases correctly when walking args
--- a/sys/src/cmd/git/save.c
+++ b/sys/src/cmd/git/save.c
@@ -219,7 +219,7 @@
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,7 +248,7 @@
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] != '/')
@@ -255,17 +255,8 @@
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 @@
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 @@
*/
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);
--- a/sys/src/cmd/git/test/merge.rc
+++ b/sys/src/cmd/git/test/merge.rc
@@ -44,7 +44,7 @@
@{
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 @@
! 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 ''
+}
--
⑨