Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
Re: [jgit-dev] How to update index and working-tree?

"Halstrick, Christian" <christian.halstrick@xxxxxxx> wrote:
> how should I use jgit to update the working-tree and the index
> after a successful (conflict-free) merge?

IMHO we don't have good code for this right now.  The older style
implementation uses WorkDirCheckout, which relies upon the GitIndex
class, which I've declared deprecated but failed to finish providing
a replacement for.

> Imagine a merge operation (e.g. the InCoreMerger.java) has
> successfully done a merge. Then I can can get the resulting tree
> from the merger and create a merge commit for this tree.
> What do I have to do in order to bring my index and the working
> tree up to date after doing the commit and refupdates?

You merge the new tree into the working directory, using essentially
the logic that is defined in WorkDirCheckout.  C git calls this
"git read-tree -m", and its manual page has a big table describing
the different cases between the working directory, the current
index file, and the new tree, and how you should resolve them.

In particular, you need to update the index record with the size
and timestamp of the file in the working directory if you have
written out an updated version of that file.

> Here is some code from jgit proposal
> http://egit.eclipse.org/r/#change,320 which doesn't work well and
> which doesn't look optimal:

> GitIndex index = db.getIndex();
> Merger merger = mergeStrategy.newMerger(db);
> boolean merge = merger.merge(...)
> 
> if (!merge) throw die("merge failed");
> 
> // commit the merge
> ...
> final Commit mergeCommit = new Commit(db);
> mergeCommit.setTreeId(merger.getResultTreeId());
> mergeCommit.setParentIds(new ObjectId[] { .... });
> ...
> mergeCommit.setCommitId(merger.getObjectWriter().writeCommit(mergeCommit));
> 
> // update the HEAD

Don't update HEAD until you have proven you can safely update the
working directory.

A concern here is, the merge safely merges in a change to Foo.java,
but the user has a modified (aka uncommitted) Foo.java in their
working directory.  You don't want to complete this merge, or store
it into HEAD, because the user's file can't be safely merged against
the new version.

So actually, HEAD should be the last thing you touch, after the
working directory has been updated.  If you aborted in the middle
of the working directory update, the user can always still undo
the merge by reseting back to HEAD.
 
> // update the index.
> // I am just overwriting the index. This is (if at all) only safe if the
> // index was clean (was matching exactly HEAD state) before the merge
> index.readTree(mergeCommit.getTree());
> index.write();

Right, this is where our code is just bad.  You can't just overwrite
the index with the new tree.  You need to perform a merge of the
current index against the new tree.  In theory, WorkDirCheckout
should be doing this based on that table that is described in the
git read-tree manual page.

My goal though was to get rid of WorkDirCheckout and write something
over again that uses NameConflictTreeWalk with 3 iterators:

 - CanonicalTreeParser
 - DirCacheIterator
 - WorkingTreeIterator

And at each path, evaluate the rules per read-tree and update the
DirCacheBuildIterator with the new records.  Also, if the working
directory has to be modified, push the DirCacheEntry into a list
of files to write.  If everything is sane, we can update those at
the end.

So loosely the logic becomes something like:

  NameConflictTreeWalk tw = new NameConflictTreeWalk(db);
  DirCache dc = DirCache.lock(db);
  DirCacheBuildIterator b = dc.builder();

  tw.reset();
  tw.addTree(mergeCommit.getTreeId());
  tw.addTree(new DirCacheBuildIterator(dc));
  tw.addTree(new WorkingTreeIterator(db.getWorkTree());

  ArrayList<DirCacheEntry> toWrite = new ArrayList<DirCacheEntry>();
  boolean safe = true;

  while (tw.next()) {
    ... check table conditions ...

	if (tw.isSubtree()) {
      tw.enterSubtree();
	}
  }

  if (!safe) {
	dc.unlock();
    throw new IOException("dirty files exist; refusing to merge");
  }

  for (DirCacheEntry e : toWrite) {
    ... create/overwrite this file ...
	e.setLastModified(file.lastModified();
	e.setLength(file.length());
  }

  b.commit();
  ... update HEAD ...

Most of that should all go into a class that replaces WorkDirCheckout
though.  Its all common for the n-way merge onto the filesystem.

I'm afraid I didn't really answer your question, or help you out
here... :-\
 
-- 
Shawn.


Back to the top