git/save: handle all cases correctly when walking args

This commit is contained in:
Ori Bernstein 2024-11-18 20:34:35 +00:00
parent 9fcb4c0896
commit 9ad3c2d098
2 changed files with 71 additions and 18 deletions

View file

@ -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);

View file

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