shithub: front

Download patch

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 ''
+}
--