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.

Parameters:

  • repo (Repository)

    the repository in which we are merging files

  • parent_node (String)

    the node_id of the parent node before the merge

  • original_fn (String)

    the original local filename before the merge

  • vf_local (WorkingVersionedFile)

    the current, working-directory versioned file

  • vf_other (VersionedFile)

    the file’s changeset to which we are migrating

  • vf_ancestor (VersionedFile)

    the common ancestor between vf_local and vf_other

Returns:

  • (Boolean)

    true if there were conflicts during the merge



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