files.append(name)
return files
-def git_newline_style(rev, name):
- """
- Returns the newline style for a blob, identified by Git revision
- ``rev`` and filename ``name``.
- """
- # XXX This is really expensive
- f = tempfile.NamedTemporaryFile(prefix="wizardResolve", delete=False)
- shell.call("git", "cat-file", "blob", "%s:%s" % (rev, name), stdout=f, log=False)
- f.close()
- nl = get_newline(f.name)
- os.unlink(f.name)
- return nl
-
# only works on Unix
def get_newline(filename):
"""
so we require the common and theirs commits, instead of
using the normal Git algorithm.
"""
+
if prepare_config is None:
prepare_config = lambda: None
+
if resolve_conflicts is None:
resolve_conflicts = lambda: False
+
ours_id = shell.eval("git", "rev-parse", "HEAD")
- theirs_newline_cache = {}
- def get_theirs_newline(file):
- if file not in theirs_newline_cache:
- nl = git_newline_style(theirs_id, file)
- if not isinstance(nl, str):
- if nl is not None:
- # A case of incompetent upstream, unfortunately
- logging.warning("Canonical version (theirs) of %s has mixed newline style, forced to \\n", file)
- else:
- logging.debug("Canonical version (theirs) of %s had no newline style, using \\n", file)
- nl = "\n"
- theirs_newline_cache[file] = nl
- return theirs_newline_cache[file]
+ ours_theirs_diff = git_diff_text(ours_id, theirs_id)
+
+ # What files can the merge fail on? Only if ours is different from
+ # theirs (we don't care about common for this calculation). Of
+ # course, this will be conservative, because we need to apply
+ # prepare_config to ours. Can we miss a file? Not unless
+ # prepare_config introduces a merge conflict, as opposed to
+ # eliminates them; and it is equally likely to do so on common_id as
+ # well. We can deal, since we offer the user the ability to resolve
+ # merges manually.
+ theirs_newlines = {}
+ shell.call("git", "reset", "--hard", theirs_id)
+ for file in ours_theirs_diff:
+ # XXX Should be able to skip stats if file was removed
+ # for the ours tree
+ try:
+ nl = get_newline(file)
+ except IOError:
+ # File not present in theirs, don't bother
+ continue
+ if not isinstance(nl, str):
+ if nl is not None:
+ # A case of incompetent upstream, unfortunately
+ logging.warning("Canonical version (theirs) of %s has mixed newline style, forced to \\n", file)
+ else:
+ logging.debug("Canonical version (theirs) of %s had no newline style, using \\n", file)
+ nl = "\n"
+ theirs_newlines[file] = nl
+
theirs_tree = shell.eval("git", "rev-parse", "%s^{tree}" % theirs_id)
- # XXX Should be able to skip stats if file doesn't exist
- # operations on the ours tree
- for file in git_diff_text(ours_id, theirs_id):
+ for file in ours_theirs_diff:
+ try:
+ theirs_nl = theirs_newlines[file]
+ except KeyError:
+ # No need to handle newlines
+ continue
try:
- theirs_nl = get_theirs_newline(file)
ours_nl = get_newline(file) # current checkout is ours_id
except (IOError, shell.CallError): # hack
continue
else:
logging.info("Converting our file (3) from %s to %s newlines", repr(ours_nl), repr(theirs_nl))
convert_newline(file, theirs_nl)
- shell.eval("git", "add", file)
+ shell.eval("git", "add", file) # XXX batch this
prepare_config() # for Wizard, this usually genericizes config files
ours_tree = shell.eval("git", "write-tree")
logging.info("Merge wrote virtual tree for ours: %s", ours_tree)
+
# operations on the common tree (pretty duplicate with the above)
shell.call("git", "reset", "--hard", common_id)
for file in git_diff_text(common_id, theirs_id):
try:
- theirs_nl = get_theirs_newline(file)
+ theirs_nl = theirs_newlines[file]
+ except KeyError:
+ # The merge trivially succeeds, so don't bother.
+ logging.debug("Merge trivially succeeds for %s, ignoring line check", file)
+ continue
+ try:
common_nl = get_newline(file) # current checkout is common_id
except (IOError, shell.CallError): # hack
continue
else:
logging.info("Converting common file (1) from %s to %s newlines", repr(common_nl), repr(theirs_nl))
convert_newline(file, theirs_nl)
- shell.eval("git", "add", file)
+ shell.eval("git", "add", file) # XXX batch
common_tree = shell.eval("git", "write-tree")
logging.info("Merge wrote virtual tree for common: %s", common_tree)
+
# construct merge commit graph
common_virtual_id = git_commit_tree(common_tree)
ours_virtual_id = git_commit_tree(ours_tree, common_virtual_id)
theirs_virtual_id = git_commit_tree(theirs_tree, common_virtual_id)
+
# perform merge
shell.call("git", "reset", "--hard", ours_virtual_id)
try:
shell.call("git", "commit", "-a", "-m", "merge")
else:
raise MergeError
+
# post-merge operations
result_tree = shell.eval("git", "write-tree")
logging.info("Resolution tree is %s", result_tree)