Commits

bitsquid  committed f533a62

Commits are now identified by full node ID rather than short name.

  • Participants
  • Parent commits 2e19888

Comments (0)

Files changed (1)

 require 'find'
 require 'optparse'
 
-class HgLogItem
-	attr_accessor :changeset, :user, :date, :summary, :parents, :bookmark, :tag, :branch, :cloned_from
+SEPARATOR = 'akjfawejalejflakjflakjef'
+
+ShortLogItem = Struct.new(:rev, :node, :parents)
+LongLogItem = Struct.new(:rev, :node, :parents, :desc, :branch, :date, :author)
+
+class HgLog
 	def initialize()
-		@summary = ""
-		@parents = []
+		@items = []
+		@lookup = {}
 	end
 
-	def to_s
-		s = ""
-		s << "changeset:\t#{changeset}\n"
-		s << "user:\t#{user}\n"
-		s << "date:\t#{date}\n"
-		s << "summary:\t#{summary}\n"
-		@parents.each do |parent|
-			s << "parent:\t#{parent}\n"
-		end
-		s << "bookmark:\t#{bookmark}\n" if @bookmark
-		s << "tag:\t#{tag}\n" if @tag
-		s << "branch:\t#{branch}\n" if @branch
-		return s
-	end
-end
-
-class HgLog
-	def initialize(data)
-		@items = []
-		@changeset_lookup = {}
-		@cloned_from_lookup = {}
-		parse(data)
+	def add(item)
+		@items << item
+		@lookup[item.rev] = item
+		@lookup[item.node] = item
 	end
 
 	def [](i)
 		return @items[i]
 	end
 
-	def size
-		return @items.size
-	end
-
-	def index_of(changeset)
-		@items.each_with_index do |item, i|
-			return i if item.changeset == changeset
-		end
-		raise "Changeset #{changeset} not found"
-	end
-
-	def each(&proc)
-		@items.each(&proc)
-	end
-
-	def changeset_item(changeset)
-		return @changeset_lookup[changeset]
-	end
-
-	def cloned_from(changeset)
-		return @cloned_from_lookup[changeset] || @changeset_lookup[changeset]
-	end
-
-	def find(changeset)
-		return nil unless changeset
-		@items.each {|item| return item.changeset if item.changeset.start_with?(changeset)}
-		return nil
-	end
-
-private
-	def parse(data)
-		lines = data.lines.to_a
-		i = 0
-		prev = nil
-		while i+3 < lines.size
-			item_lines = []
-			while lines[i][':']
-				item_lines << lines[i]
-				i = i +1
-			end
-			item = parse_item(item_lines)
-			i = i + 1
-
-			if prev && prev.parents.empty?
-				prev.parents << item.changeset
-			end
-			prev = item
-
-			@items << item
-			@changeset_lookup[item.changeset] = item
-			@cloned_from_lookup[item.cloned_from] = item
-		end
-		@items.reverse!
-	end
-
-	def parse_item(item_lines)
-		item = HgLogItem.new
-		item_lines.each do |line|
-			key, value = parse_item_line(line)
-			case key
-			when "parent"		
-				item.parents << value
-			when "changeset"	
-				item.changeset = value
-			when "user"			
-				item.user = value
-			when "date"			
-				item.date = value
-			when "summary"		
-				item.summary = value
-			when "bookmark"		
-				item.bookmark = value
-			when "tag"			
-				item.tag = value
-			when "branch"		
-				item.branch = value
-			else
-				raise "Unknown key #{key}"
-			end
-		end
-		item.cloned_from = item.summary[/\[clonedfrom:(.*?)\]/, 1]
-		return item
-	end
-
-	def parse_item_line(item_line)
-		md = item_line.match(/(\w+):\s*(.*)/)
-		return md[1], md[2]
+	def lookup(r)
+		return @lookup[r]
 	end
 end
 
 	end
 
 	def log()
-		return HgLog.new(command("hg log"))
+		log = HgLog.new
+		lines = command(%Q{hg log --template "{rev},{node},{parents};"}).split(';').reverse
+		lines.each_with_index do |line, i|
+			rev,node,parents = line.split ','
+			if !parents
+				parents = log[i-1] ? [log[i-1].node] : nil
+			else
+				parents = parents.split().collect {|p| log.lookup(p[/(.*):/,1].to_i).node}
+			end
+			item = ShortLogItem.new(rev.to_i, node, parents)
+			log.add(item)
+		end
+		return log
+	end
+
+	def info(item)
+		long = LongLogItem.new(item.rev, item.node, item.parents)
+		long.desc, long.branch, long.date, long.author = command(%Q{hg log -r #{item.node} --template "{desc}#{SEPARATOR}{branches}#{SEPARATOR}{date|isodate}#{SEPARATOR}{author}#{SEPARATOR}"}).split(SEPARATOR)
+		return long
+	end
+
+	def nodes()
+		command(%Q{hg log --template "{node},"}).split ','
+	end
+
+	def mapping()
+		ns = nodes()
+		summaries = command(%Q{hg log --template "{desc}#{SEPARATOR}"}).split(SEPARATOR)
+		mapping = {}
+		ns.each_with_index do |n, i|
+			mapping[n] = n
+			cloned_from = summaries[i][/\[clonedfrom:(.*?)\]/, 1]
+			mapping[cloned_from] = n if cloned_from
+		end
+		return mapping
+	end
+
+	def node(rev)
+		return nil unless rev
+		return command(%Q{hg log -r #{rev} --template "{node}"})
+	end
+
+	def rev(r)
+		return command(%Q{hg log -r #{r} --template "{rev}"}).to_i
+	end
+
+	def parents(node)
+		parents = command(%Q{hg log -r #{node} --template "{parents}"}).split
+		if parents.empty?
+			parents = [rev(node) - 1]
+		end
+		return parents.collect {|rev| node(rev)}
 	end
 
 	def addremove(opts = {})
 		command("hg addremove" + (opts[:similarity] ? " --similarity #{opts[:similarity]}" : ""))
 	end
 
-	def commit_with_log_item(item)
-		command("hg commit -A --message #{quote(item.summary + ' [clonedfrom:' + item.changeset + ']')} --date #{quote(item.date)} --user #{quote(item.user)}")
+	def commit_with_info(info)
+		command("hg commit -A --message #{quote(info.desc + "\n\n[clonedfrom:" + info.node + ']')} --date #{quote(info.date)} --user #{quote(info.author)}")
+		return command("hg parent --template {node}")
 	end
 
 	def set_parents(p1, p2 = '')
 		command("hg branch \"#{name}\"")
 	end
 
+	def earlier_than(hg, rev1, rev2)
+		return rev(rev1) < rev(rev2)
+	end
+
 private
 	def command(s)
 		Dir.chdir(@dir) do
 	`robocopy #{from} #{to} /MIR /XD .hg`
 end
 
-def earlier_than(rev1, rev2)
-	short1 = rev1[/(.*?):/,1].to_i
-	short2 = rev2[/(.*?):/,1].to_i
-	return short1 < short2
-end
-
 def hg_clone_with_filter(from_dir, to_dir, opts = {})
 	from = Hg.new(from_dir)
 	to = Hg.new(to_dir)
 	to.init unless to.exists?
 
 	from_log = from.log()
-	to_log = to.log()
-
-	target = from_log.find(opts[:target]) || from_log[-1].changeset
-	cutoff = from_log.find(opts[:cutoff]) || from_log.find("0")
+	mapping = to.mapping()
+	
+	target = from.node(opts[:target]) || from_log[-1].node
+	cutoff = from.node(opts[:cutoff]) || from_log[0].node
+	cutoff_item = from_log.lookup(cutoff)
+	
 	queue = []
 	queue << target
 
 	while queue.size > 0 do
-		changeset = queue.pop
-		item = from_log.changeset_item(changeset)
-		next if to_log.cloned_from(item.changeset)
+		node = queue.pop
+		next if mapping[node]
 
-		parent1 = item.parents[0]
-		parent2 = item.parents[1]
+		item = from_log.lookup(node)
 
-		if item.changeset == cutoff then
-			parent1 = nil
-			parent2 = nil
+		parent_1, parent_2 = item.parents
+		if node == cutoff
+			parent_1, parent_2 = nil, nil
+		end
+		
+		parent_1_item = parent_1 ? from_log.lookup(parent_1) : nil
+		parent_2_item = parent_2 ? from_log.lookup(parent_2) : nil
+		
+		if parent_1_item && parent_1_item.rev < cutoff_item.rev then
+			parent_1 = cutoff 
+			parent_1_item = cutoff_item
+		end
+		if parent_2_item && parent_2_item.rev < cutoff_item.rev then
+			parent_2 = cutoff 
+			parent_2_item = cutoff_item
+		end
+		if parent_1 && !mapping[parent_1] || parent_2 && !mapping[parent_2]
+			queue << node
+			queue << parent_1
+			queue << parent_2 if parent_2
+			next
 		end
 
-		if parent1 && earlier_than(parent1, cutoff) then parent1 = cutoff end
-		if parent2 && earlier_than(parent2, cutoff) then parent2 = cutoff end
-		if parent1 && !to_log.cloned_from(parent1) || parent2 && !to_log.cloned_from(parent2)
-			queue << item.changeset
-			queue << parent1
-			queue << parent2 if parent2
-			next
+		to_parent_1 = parent_1 ? mapping[parent_1] : nil
+		to_parent_2 = parent_2 ? mapping[parent_2] : nil
+
+		info = from.info(item)
+		puts "#{info.rev} #{info.desc}"
+		from.update(node)
+
+		if to_parent_1 then
+			to.set_parents(to_parent_1, to_parent_2)
+		else
+			to.set_parents("0000000000000000000000000000000000000000")
 		end
-		parent1 = to_log.cloned_from(parent1).changeset if parent1
-		parent2 = to_log.cloned_from(parent2).changeset if parent2
-		
-		puts "#{item.changeset} #{item.summary}"
-		from.update(item.changeset)
-		if parent1 then
-			to.set_parents(parent1, parent2)
-		else
-			to.set_parents("000000000000")
-		end
-		to.branch(item.branch) if item.branch
+		to.branch(info.branch) if info.branch
+
 		copy(from_dir, to_dir)
 
 		# Avoid 'nothing changed' message
 		Dir.chdir(to_dir) do
-			File.open("cloned_revision.txt", "w") {|f| f.write(item.to_s)}
-		end
+			File.open("cloned_revision.txt", "w") {|f| f.write(item.node)}
 
-		if opts[:filter]
-			`#{opts[:filter]}`
-			raise "Error running:\n    #{opts[:filter]}\n" unless $?.exitstatus == 0
+			if opts[:filter]
+				`#{opts[:filter]}`
+				raise "Error running:\n    #{opts[:filter]}\n" unless $?.exitstatus == 0
+			end
 		end
 
 		to.addremove :similarity => 90
-		to.commit_with_log_item(item)
-		to_log = to.log()
+		mapping[node] = to.commit_with_info(info)
 	end
 end