Module: Amp::Merges::MergeUI

Included in:
UI
Defined in:
lib/amp/merges/merge_ui.rb

Overview

This module handles figuring out how to merge files using the user’s preferences. It is mixed into the UI class. The UI class must implement the “config” method.

Instance Method Summary collapse

Instance Method Details

#file_merge(repo, parent_node, original_fn, vf_local, vf_other, vf_ancestor) ⇒ Boolean

TODO:

change 1s and 0s to bools

TODO:

consistent return type

Performs a 3-way merge in the working directory from vf_local to vf_other, using the common ancestor vf_ancestor.



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/amp/merges/merge_ui.rb', line 26

def file_merge(repo, parent_node, original_fn, vf_local, vf_other, vf_ancestor)
  is_binary = proc {|ctx| ctx.data.binary? rescue false}
  
  return nil if !(vf_other.cmp vf_local.data)
  
  path = vf_local.path
  binary = is_binary[vf_local] || is_binary[vf_other] || is_binary[vf_ancestor]
  symlink = (vf_local.flags + vf_other.flags).include? "l"
  
  tool, tool_path = pick_tool(repo, path, binary, symlink)
  UI.status("Picked tool #{tool} for #{path} (binary #{binary} symlink #{symlink})")
  
  unless tool
    tool = "internal:local"
    if UI.ask("no tool found to merge #{path}\n"+
              "keep (l)ocal or take (o)ther?") != "l"
      tool = "internal:other"
    end
  end
  
  case tool
  when "internal:local"
    return 0
  when "internal:other"
    repo.working_write(path, vf_other.data, vf_other.flags)
    return 0
  when "internal:fail"
    return 1
  end
  
  a = repo.working_join(path)
  b_file = save_versioned_file_temp("base", vf_ancestor)
  c_file = save_versioned_file_temp("other", vf_other)
  b, c = b_file.path, c_file.path
  
  out = ""
  back = a + ".orig" + File.extname(a)
  File.copy(a, back)
  
  if original_fn != vf_other.path
    UI.status("merging #{original_fn} and #{vf_other.path} to #{path}")
  else
    UI.status("merging #{path}")
  end
  
  if tool_setting(tool, "premerge", !(binary || symlink))
    ret = ThreeWayMerger.three_way_merge(a, b, c, :quiet => true)
    unless ret
      UI.debug("premerge successful")
      File.unlink(back)
      File.safe_unlink(b)
      File.safe_unlink(c)
      return false
    end
    File.copy(back, a) # restore frmo backup and try again
  end
  
  environment = {"HG_FILE" => path,
                 "HG_MY_NODE" => parent_node.hexlify[0..11],
                 "HG_OTHER_NODE" => vf_other.changeset.to_s,
                 "HG_MY_ISLINK" => vf_local.flags.include?("l"),
                 "HG_OTHER_ISLINK" => vf_other.flags.include?("l"),
                 "HG_BASE_ISLINK" => vf_ancestor.flags.include?("l")}
  if tool == "internal:merge"
    ret = ThreeWayMerger.three_way_merge(a, b, c, :label => ['local', 'other'])
  else
    args = tool_setting_string(tool, "args", "$local $base $other")
    if args.include?("$output")
      out, a = a, back # read input from backup, write to original
    end
    replace = {"local" => a, "base" => b, "other" => c, "output" => out}
    args.gsub!(/\$(local|base|other|output)/) { replace[$1]}
    # shelling out
    ret = Amp::Support::system(tool_path+" "+args, :chdir => repo.root, :environ => environment)
  end
  ret = (ret == true ? 1 : (ret == false ? 0 : ret))
  if ret == 0 && tool_setting(tool, "checkconflicts")
    if vf_local.data =~ /^(<<<<<<< .*|=======|>>>>>>> .*)$/
      ret = 1
    end
  end
  
  if ret == 0 && tool_setting(tool, "checkchanged")
    if File.stat(repo.working_join(path)) === File.stat(back)
      if UI::yes_or_no "output file #{path} appears unchanged\nwas merge successful?"
        r = 1
      end
    end
  end
  
  fix_end_of_lines(repo.working_join(path), back) if tool_setting(tool, "fixeol")
  
  if ret == 1
    UI::warn "merging #{path} failed!"
  else
    File.unlink back
  end
  File.unlink b
  File.unlink c
  
  !ret.zero? # return
end