Class: Amp::Repositories::LocalRepository
- Inherits:
-
Repository
- Object
- Repository
- Amp::Repositories::LocalRepository
- Includes:
- BranchManager, TagManager, Updatable, Verification, Amp::RevlogSupport::Node
- Defined in:
- lib/amp/repository/repositories/local_repository.rb
Overview
A Local Repository is a repository that works on local repo’s, such as your working directory. This makes it pretty damn important, and also pretty damn complicated. Have fun!
Direct Known Subclasses
Constant Summary
Constants included from Amp::RevlogSupport::Node
Amp::RevlogSupport::Node::NULL_ID, Amp::RevlogSupport::Node::NULL_REV
Constants included from TagManager
TagManager::TAG_FORBIDDEN_LETTERS
Instance Attribute Summary collapse
-
#branch_manager ⇒ Object
readonly
Returns the value of attribute branch_manager.
-
#config ⇒ Object
The config is an AmpConfig for this repo (and uses .hg/hgrc).
-
#hg ⇒ Object
readonly
Returns the value of attribute hg.
-
#hg_opener ⇒ Object
readonly
Returns the value of attribute hg_opener.
-
#root ⇒ Object
readonly
Returns the value of attribute root.
-
#root_pathname ⇒ Object
readonly
save some computation here.
-
#store ⇒ Object
readonly
Returns the value of attribute store.
-
#store_opener ⇒ Object
readonly
Returns the value of attribute store_opener.
Instance Method Summary collapse
-
#[](rev) ⇒ Changeset
Gets the changeset at the given revision.
-
#add(*paths) ⇒ Array<String>
Adds a list of file paths to the repository for the next commit.
-
#add_changegroup(source, type, url, opts = {:empty => []}) ⇒ Object
Add a changegroup to the repo.
-
#add_file(file_name, data, flags) ⇒ Object
Write data to a file in the CODE repo, not the .hg.
-
#annotate(file, revision = nil, opts = {}) ⇒ Object
Returns the requested file at the given revision annotated by line number, so you can see who committed which lines in the file’s history.
-
#between(pairs) ⇒ Object
Finds the nodes between two nodes - this algorithm is ported from the python for mercurial (localrepo.py:1247, for 1.2.1 source).
-
#branches(*nodes) ⇒ Array<String>
The branches available in this repository.
- #changed? ⇒ Boolean
-
#changegroup(base_nodes, source) ⇒ Object
A changegroup, of some sort.
-
#changegroup_info(nodes, source) ⇒ Object
Prints information about the changegroup we are going to receive.
-
#changegroup_subset(bases, new_heads, source, extra_nodes = nil) ⇒ Object
This function generates a changegroup consisting of all the nodes that are descendents of any of the bases, and ancestors of any of the heads.
-
#changelog ⇒ ChangeLog
Returns the changelog for this repository.
-
#clone(remote, opts = {:revs => [], :stream => false}) ⇒ Object
Clone a repository.
-
#commit(opts = {:message => "", :extra => {}, :files => []}) ⇒ String
Commits a changeset or set of files to the repository.
-
#commit_changeset(changeset, opts = {:use_dirstate => true, :update_dirstate => true}) ⇒ String
Commits the given changeset to the repository.
-
#commit_file(file, manifest1, manifest2, link_revision, journal, change_list) ⇒ Object
Commits a file as part of a larger transaction.
-
#common_nodes(remote, opts = {:heads => nil, :force => nil, :base => nil}) ⇒ Object
Find the common nodes, missing nodes, and remote heads.
-
#copy(source, destination, opts) ⇒ Boolean
Copies a file from
source
todestination
, while being careful of the specified options. -
#cwd ⇒ String
(also: #pwd)
Effectively FileUtils.pwd.
-
#dirstate ⇒ DirState
Returns the dirstate for this repository.
-
#dirstate_copy(source, dest) ⇒ Object
Copy a file from
source
todest
. -
#each { ... } ⇒ Object
Iterates over each changeset in the repository, from oldest to newest.
-
#file(f) ⇒ FileLog
Gets the file-log for the given path, so we can look at an individual file’s history, for example.
-
#find_incoming_roots(remote, opts = {:base => nil, :heads => nil, :force => false, :base => nil}) ⇒ Object
Return list of roots of the subsets of missing nodes from remote.
-
#find_outgoing_roots(remote, opts = {:base => nil, :heads => nil, :force => false}) ⇒ Object
Return list of nodes that are roots of subsets not in remote.
-
#forget(list) ⇒ Boolean
Forgets an added file or files from the repository.
-
#get_changegroup(common, source) ⇒ Object
Faster version of changegroup_subset.
-
#heads(start = nil, options = {:closed => true}) ⇒ Object
Returns the node_id’s of the heads of the repository.
-
#init(config = @config) ⇒ Array<String>
Creates this repository’s folders and structure.
-
#initialize(path = "", create = false, config = nil) ⇒ LocalRepository
constructor
Initializes a new directory to the given path, and with the current configuration.
- #inspect ⇒ Object
-
#invalidate! ⇒ Object
Invalidate the repository: delete things and reset others.
-
#join(file) ⇒ String
Joins the path from this repo’s path (.hg), to the file provided.
-
#living_parents ⇒ Object
Returns the parents that aren’t NULL_ID.
- #local? ⇒ Boolean
-
#lock_store(wait = true) { ... } ⇒ Lock
Locks the repository’s .hg/store directory.
-
#lock_working(wait = true) { ... } ⇒ Lock
Locks the repository’s .hg/store directory.
-
#lock_working_and_store(wait = true) ⇒ Object
Takes a block, and runs that block with both the store and the working directory locked.
-
#lookup(key) ⇒ String
Looks up an identifier for a revision in the commit history.
-
#make_lock(lockname, wait, release_proc, acquire_proc, desc) ⇒ Lock
Creates a lock at the given path.
-
#manifest ⇒ Manifest
Returns the manifest for this repository.
-
#merge_state ⇒ MergeState
Returns the merge state for this repository.
-
#open(*args, &block) ⇒ Object
Opens a file using our opener.
-
#parents(change_id = nil) ⇒ Array<Changeset>
Returns the parent changesets of the specified changeset.
-
#path_to(src, dest) ⇒ String
Returns the relative path from
src
todest
. -
#pristine? ⇒ Boolean
Has the repository been changed since the last commit? Returns true if there are NO outstanding changes or uncommitted merges.
-
#pull(remote, opts = {:heads => nil, :force => nil}) ⇒ Object
Pull new changegroups from
remote
This does not apply the changes, but pulls them onto the local server. -
#push(remote_repo, opts = {:force => false, :revs => nil}) ⇒ Object
There are two ways to push to remote repo:.
-
#push_add_changegroup(remote, opts = {}) ⇒ Object
Push and add a changegroup.
-
#push_unbundle(remote, opts = {}) ⇒ Object
Push an unbundled dohickey.
- #relative_join(file, cur_dir = FileUtils.pwd) ⇒ Object
-
#remove(list, opts = {}) ⇒ Boolean
Removes the file (or files) from the repository.
-
#revert(files, opts = {}) ⇒ Boolean
Revert a file or group of files to
revision
. -
#run_hook(call, opts = {:throw => false}) ⇒ Object
Call the hooks that run under
call
. -
#size ⇒ Integer
Returns the number of revisions the repository is tracking.
-
#status(opts = {:node_1 => '.'}) ⇒ Hash<Symbol => Array<String>>
This gives the status of the repository, comparing 2 node in its history.
-
#store_join(file) ⇒ String
Joins the path, with a bunch of other args, to the store’s directory.
-
#stream_in(remote) ⇒ Integer
Stream in the data from
remote
. -
#undelete(list) ⇒ Object
(also: #restore)
Undelete a file.
-
#url ⇒ String
Returns the URL of this repository.
-
#versioned_file(path, opts = {}) ⇒ Object
Gets a versioned file for the given path, so we can look at the individual file’s history with the file object itself.
-
#walk(node = nil, match = Match.create({}) { true }) ⇒ Object
Walk recursively through the directory tree (or a changeset) finding all files matched by the match function.
-
#working_file(path, opts = {}) ⇒ Object
Gets a versioned file, but using the working directory, so we are looking past the last commit.
-
#working_join(path) ⇒ String
Joins the path to the repo’s root (not .hg, the working dir root).
-
#working_read(filename) ⇒ String
Reads from a file, but in the working directory.
-
#working_write(path, data, flags) ⇒ Object
Writes to a file, but in the working directory.
Methods included from Verification
Methods included from Updatable
Methods included from Amp::RevlogSupport::Node
Methods included from TagManager
#apply_tag, #invalidate_tag_cache!, #tag_list, #tag_type, #tags, #tags_for_node
Methods included from BranchManager
#branch_heads, #branch_tags, #save_branch_cache, #save_branches
Methods inherited from Repository
#add_path, #can_copy?, #capable?, #get_capabilities, #require_capability
Constructor Details
#initialize(path = "", create = false, config = nil) ⇒ LocalRepository
Initializes a new directory to the given path, and with the current configuration.
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 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 37 def initialize(path="", create=false, config=nil) @capabilities = {} @root = path[-1, 1] == '/' ? path[0..-2] : path # no ending slashes @root = File. @root @hg = File.join @root, ".hg" @file_opener = Amp::Opener.new @root @file_opener.default = :open_file # these two are the same, pretty much @hg_opener = Amp::Opener.new @root @hg_opener.default = :open_hg # just with different defaults @filters = {} @changelog = nil @manifest = nil @dirstate = nil requirements = [] # make a repo if necessary unless File.directory? @hg if create then requirements = init config else raise RepoError.new("Repository #{path} not found") end end # no point in reading what we _just_ wrote... unless create # read requires # save it if something's up @hg_opener.open("requires", 'r') {|f| f.each {|r| requirements << r.strip } } rescue nil end @store = Stores.pick requirements, @hg, Amp::Opener @config = Amp::AmpConfig.new :parent_config => config @config.read_file File.join(@hg,"hgrc") end |
Instance Attribute Details
#branch_manager ⇒ Object (readonly)
Returns the value of attribute branch_manager.
24 25 26 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 24 def branch_manager @branch_manager end |
#config ⇒ Object
The config is an AmpConfig for this repo (and uses .hg/hgrc)
18 19 20 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 18 def config @config end |
#hg ⇒ Object (readonly)
Returns the value of attribute hg.
22 23 24 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 22 def hg @hg end |
#hg_opener ⇒ Object (readonly)
Returns the value of attribute hg_opener.
23 24 25 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 23 def hg_opener @hg_opener end |
#root ⇒ Object (readonly)
Returns the value of attribute root.
20 21 22 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 20 def root @root end |
#root_pathname ⇒ Object (readonly)
save some computation here
21 22 23 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 21 def root_pathname @root_pathname end |
#store ⇒ Object (readonly)
Returns the value of attribute store.
26 27 28 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 26 def store @store end |
#store_opener ⇒ Object (readonly)
Returns the value of attribute store_opener.
25 26 27 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 25 def store_opener @store_opener end |
Instance Method Details
#[](rev) ⇒ Changeset
Gets the changeset at the given revision.
154 155 156 157 158 159 160 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 154 def [](rev) if rev.nil? return WorkingDirectoryChangeset.new(self) end rev = rev.to_i if rev.to_i.to_s == rev return Changeset.new(self, rev) end |
#add(*paths) ⇒ Array<String>
Adds a list of file paths to the repository for the next commit.
1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 1417 def add(*paths) lock_working do rejected = [] paths.flatten! paths.each do |file| path = working_join file st = File.lstat(path) rescue nil unless st UI.warn "#{file} does not exist!" rejected << file next end if st.size > 10.mb UI.warn "#{file}: files over 10MB may cause memory and" + "performance problems\n" + "(use 'amp revert #{file}' to unadd the file)\n" end state = dirstate[file] if File.ftype(path) != 'file' && File.ftype(path) != 'link' # fail if it's not a file or link UI.warn "#{file} not added: only files and symlinks supported. Type is #{File.ftype path}" rejected << path elsif state.added? || state.modified? || state.normal? # fail if it's being tracked UI.warn "#{file} already tracked!" elsif state.removed? # check back on it if it's being removed dirstate.normal_lookup file else # else add it dirstate.add file #Amp::Logger.info("added #{file}") end end dirstate.write unless rejected.size == paths.size return rejected end end |
#add_changegroup(source, type, url, opts = {:empty => []}) ⇒ Object
Add a changegroup to the repo.
Return values:
-
nothing changed or no source: 0
-
more heads than before: 1+added_heads (2..n)
-
fewer heads than before: -1-removed_heads (-2..-n)
-
number of heads stays the same: 1
Don’t the first and last conflict? they stay the same if nothing has changed…
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 554 def add_changegroup(source, type, url, opts={:empty => []}) run_hook :pre_changegroup, :throw => true, :source => type, :url => url changesets = files = revisions = 0 return 0 if source.string.empty? rev_map = proc {|x| changelog.revision_index_for_node x } cs_map = proc do |x| UI::debug "add changeset #{short x}" changelog.size end # write changelog data to temp files so concurrent readers will not # see inconsistent view changelog.delay_update old_heads = changelog.heads.size new_heads = nil # scoping changesets = nil # scoping cor = nil # scoping cnr = nil # scoping heads = nil # scoping Journal::start join('journal') do |journal| UI::status 'adding changeset' # pull of the changeset group cor = changelog.size - 1 unless changelog.add_group(source, cs_map, journal) || opts[:empty].any? raise abort("received changelog group is empty") end cnr = changelog.size - 1 changesets = cnr - cor # pull off the manifest group UI::status 'adding manifests' # No need to check for empty manifest group here: # if the result of the merge of 1 and 2 is the same in 3 and 4, # no new manifest will be created and the manifest group will be # empty during the pull manifest.add_group source, rev_map, journal # process the files UI::status 'adding file changes' loop do f = Amp::RevlogSupport::ChangeGroup.get_chunk source break if f.empty? UI::debug "adding #{f} revisions" fl = file f o = fl.index_size unless fl.add_group source, rev_map, journal raise abort('received file revlog group is empty') end revisions += fl.index_size - o files += 1 end # end loop new_heads = changelog.heads.size heads = "" unless old_heads.zero? || new_heads == old_heads heads = " (+#{new_heads - old_heads} heads)" end UI::status("added #{changesets} changesets" + " with #{revisions} changes to #{files} files#{heads}") if changesets > 0 changelog.write_pending p = proc { changelog.write_pending && root or "" } run_hook :pre_txnchangegroup, :throw => true, :node => changelog.node_id_for_index(cor+1).hexlify, :source => type, :url => url end changelog.finalize journal end # end Journal::start if changesets > 0 # forcefully update the on-disk branch cache UI::debug 'updating the branch cache' run_hook :post_changegroup, :node => changelog.node_id_for_index(cor+1).hexlify, :source => type, :url => url ((cor+1)..(cnr+1)).to_a.each do |i| run_hook :incoming, :node => changelog.node_id_for_index(i).hexlify, :source => type, :url => url end # end each end # end if hdz = branch_heads # never return 0 here ret = if new_heads < old_heads new_heads - old_heads - 1 else new_heads - old_heads + 1 end # end if class << ret def success?; self <= 1 || hdz.size == 1; end end ret end |
#add_file(file_name, data, flags) ⇒ Object
Write data to a file in the CODE repo, not the .hg
1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 1849 def add_file(file_name, data, flags) data = filter "decode", file_name, data path = "#{@root}/#{file_name}" File.unlink path rescue nil if flags.include? 'l' # if it's a link @file_opener.symlink path, data else @file_opener.open(path, 'w') {|f| f.write data } File.set_flag path, false, true if flags.include? 'x' end end |
#annotate(file, revision = nil, opts = {}) ⇒ Object
Returns the requested file at the given revision annotated by line number, so you can see who committed which lines in the file’s history.
1898 1899 1900 1901 1902 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 1898 def annotate(file, revision=nil, opts={}) changeset = self[revision] file = changeset.get_file(file) return file.annotate(opts[:follow_copies], opts[:line_numbers]) end |
#between(pairs) ⇒ Object
Finds the nodes between two nodes - this algorithm is ported from the python for mercurial (localrepo.py:1247, for 1.2.1 source). Since this is used by servers, it implements their algorithm… which seems to intentionally not return every node between top
and bottom
. Each one is twice as far from top
as the previous.
return [Array<String>] a list of node IDs that are between top
and bottom
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 482 def between(pairs) pairs.map do |top, bottom| node, list, counter = top, [], 0 add_me = 1 while node != bottom && node != NULL_ID if counter == add_me list << node add_me *= 2 end parent = changelog.parents_for_node(node).first node = parent counter += 1 end list end end |
#branches(*nodes) ⇒ Array<String>
The branches available in this repository.
1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 1668 def branches(*nodes) branches = [] nodes = [changelog.tip] if nodes.empty? # for each node, find its first parent (adam and eve, basically) # -- that's our branch! nodes.each do |node| t = node # traverse the tree, staying to the left side # node # / \ # parent1 parent2 # .... .... # This will get us the first parent. When it's finally NULL_ID, # we have a root -- this is the basis for our branch. loop do parents = changelog.parents_for_node t if parents[1] != NULL_ID || parents[0] == NULL_ID branches << [node, t, *parents] break end t = parents.first # get the first parent and start again end end branches end |
#changed? ⇒ Boolean
122 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 122 def changed?; !pristine?; end |
#changegroup(base_nodes, source) ⇒ Object
A changegroup, of some sort.
667 668 669 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 667 def changegroup(base_nodes, source) changegroup_subset(base_nodes, heads, source) end |
#changegroup_info(nodes, source) ⇒ Object
add more debug info
Prints information about the changegroup we are going to receive.
677 678 679 680 681 682 683 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 677 def changegroup_info(nodes, source) # print info if source == :bundle UI.status("#{nodes.size} changesets found") end # debug stuff end |
#changegroup_subset(bases, new_heads, source, extra_nodes = nil) ⇒ Object
This function generates a changegroup consisting of all the nodes that are descendents of any of the bases, and ancestors of any of the heads.
It is fairly complex in determining which filenodes and which manifest nodes need to be included for the changeset to be complete is non-trivial.
Another wrinkle is doing the reverse, figuring out which changeset in the changegroup a particular filenode or manifestnode belongs to.
The caller can specify some nodes that must be included in the changegroup using the extranodes argument. It should be a dict where the keys are the filenames (or 1 for the manifest), and the values are lists of (node, linknode) tuples, where node is a wanted node and linknode is the changelog node that should be transmitted as the linkrev.
MAD SHOUTZ to Eric Hopper, who actually had the balls to document a good chunk of this code in the Python. He is a really great man, and deserves whatever thanks we can give him. Peace
827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 827 def changegroup_subset(bases, new_heads, source, extra_nodes=nil) unless extra_nodes if new_heads.sort! == heads.sort! common = [] # parents of bases are known from both sides bases.each do |base| changelog.parents_for_node(base).each do |parent| common << parent unless parent.null? # == NULL_ID end # end each end # end each # BAIL return get_changegroup(common, source) end # end if end # end unless run_hook :pre_outgoing, :throw => true, :source => source # call dem hooks # missing changelog list, bases, and heads # # Some bases may turn out to be superfluous, and some heads may be as # well. #nodes_between will return the minimal set of bases and heads # necessary to recreate the changegroup. # missing_cl_list, bases, heads = changelog.nodes_between(bases, heads) btw = changelog.nodes_between(bases, heads) missing_cl_list, bases, heads = btw[:between], btw[:roots], btw[:heads] changegroup_info missing_cl_list, source # Known heads are the list of heads about which it is assumed the recipient # of this changegroup will know. known_heads = [] # We assume that all parents of bases are known heads. bases.each do |base| changelog.parents_for_node(base).each do |parent| known_heads << parent end # end each end # end each if known_heads.any? # unless known_heads.empty? # Now that we know what heads are known, we can compute which # changesets are known. The recipient must know about all # changesets required to reach the known heads from the null # changeset. has_cl_set = changelog.nodes_between(nil, known_heads)[:between] # cast to a hash for latter usage has_cl_set = Hash.with_keys has_cl_set else # If there were no known heads, the recipient cannot be assumed to # know about any changesets. has_cl_set = {} end # We don't know which manifests are missing yet missing_mf_set = {} # Nor do we know which filenodes are missing. missing_fn_set = {} ######## # Here are procs for further usage # A changeset always belongs to itself, so the changenode lookup # function for a changenode is +identity+ identity = proc {|x| x } # A function generating function. Sets up an enviroment for the # inner function. cmp_by_rev_function = proc do |rvlg| # Compare two nodes by their revision number in the environment's # revision history. Since the revision number both represents the # most efficient order to read the nodes in, and represents a # topological sorting of the nodes, this function if often useful. proc {|a, b| rvlg.rev(a) <=> rvlg.rev(b) } end # If we determine that a particular file or manifest node must be a # node that the recipient of the changegroup will already have, we can # also assume the recipient will have all the parents. This function # prunes them from the set of missing nodes. prune_parents = proc do |rvlg, hasses, missing| has_list = hasses.keys has_list.sort!(&cmp_by_rev_function(rvlg)) has_list.each do |node| parent_list = revlog.parent_for_node(node).select {|p| p.not_null? } end while parent_list.any? n = parent_list.pop unless hasses.include? n hasses[n] = 1 p = revlog.parent_for_node(node).select {|p| p.not_null? } parent_list += p end end hasses.each do |n| missing.slice!(n - 1, 1) # pop(n, None) end end # This is a function generating function used to set up an environment # for the inner funciont to execute in. manifest_and_file_collector = proc do |changed_fileset| # This is an information gathering function that gathers # information from each changeset node that goes out as part of # the changegroup. The information gathered is a list of which # manifest nodes are potentially required (the recipient may already # have them) and total list of all files which were changed in any # changeset in the changegroup. # # We also remember the first changenode we saw any manifest # referenced by so we can later determine which changenode owns # the manifest. # this is what we're returning proc do |cl_node| c = changelog.read cl_node c[3].each do |f| # This is to make sure we only have one instance of each # filename string for each filename changed_fileset[f] ||= f end # end each missing_mf_set[c[0]] ||= cl_node end # end proc end # end proc # Figure out which manifest nodes (of the ones we think might be part # of the changegroup) the recipients must know about and remove them # from the changegroup. prune_manifest = proc do has_mnfst_set = {} missing_mf_set.values.each do |node| # If a 'missing' manifest thinks it belongs to a changenode # the recipient is assumed to have, obviously the recipient # must have the manifest. link_node = changelog.node manifest.link_rev(manifest.revision_index_for_node(node)) has_mnfst_set[n] = 1 if has_cl_set.include? link_node end # end each prune_parents[manifest, has_mnfst_set, missing_mf_set] # Proc#call end # end proc # Use the information collected in collect_manifests_and_files to say # which changenode any manifestnode belongs to. lookup_manifest_link = proc {|node| missing_mf_set[node] } # A function generating function that sets up the initial environment # the inner function. filenode_collector = proc do |changed_files| next_rev = [] # This gathers information from each manifestnode included in the # changegroup about which filenodes the manifest node references # so we can include those in the changegroup too. # # It also remembers which changenode each filenode belongs to. It # does this by assuming the a filenode belongs to the changenode # the first manifest that references it belongs to. collect_missing_filenodes = proc do |node| r = manifest.rev node if r == next_rev[0] # If the last rev we looked at was the one just previous, # we only need to see a diff. delta_manifest = manifest.read_delta node # For each line in the delta delta_manifest.each do |f, fnode| f = changed_files[f] # And if the file is in the list of files we care # about. if f # Get the changenode this manifest belongs to cl_node = missing_mf_set[node] # Create the set of filenodes for the file if # there isn't one already. ndset = missing_fn_set[f] ||= {} # And set the filenode's changelog node to the # manifest's if it hasn't been set already. ndset[fnode] ||= cl_node end end else # Otherwise we need a full manifest. m = manifest.read node # For every file in we care about. changed_files.each do |f| fnode = m[f] # If it's in the manifest if fnode # See comments above. cl_node = msng_mnfst_set[mnfstnode] ndset = missing_fn_set[f] ||= {} ndset[fnode] ||= cl_node end end end # Remember the revision we hope to see next. next_rev[0] = r + 1 end # end proc end # end proc # We have a list of filenodes we think need for a file, let's remove # all those we know the recipient must have. prune_filenodes = proc do |f, f_revlog| missing_set = missing_fn_set[f] hasset = {} # If a 'missing' filenode thinks it belongs to a changenode we # assume the recipient must have, the the recipient must have # that filenode. missing_set.each do |n| cl_node = changelog.node f_revlog[n].link_rev hasset[n] = true if has_cl_set.include? cl_node end prune_parents[f_revlog, hasset, missing_set] # Proc#call end # end proc # Function that returns a function. lookup_filenode_link_func = proc do |name| missing_set = missing_fn_set[name] # lookup the changenode the filenode belongs to lookup_filenode_link = proc do |node| missing_set[node] end # end proc end # end proc # add the nodes that were explicitly requested. add_extra_nodes = proc do |name, nodes| return unless extra_nodes && extra_nodes[name] extra_nodes[name].each do |node, link_node| nodes[node] = link_node unless nodes[node] end end # Now that we have all theses utility functions to help out and # logically divide up the task, generate the group. generate_group = proc do changed_files = {} group = changelog.group(missing_cl_list, identity, &manifest_and_file_collector[changed_files]) group.each { |chunk| yield chunk } prune_manifests.call add_extra_nodes[1, msng_mnfst_set] msng_mnfst_lst = msng_mnfst_set.keys msng_mnfst_lst.sort!(&cmp_by_rev_function[manifest]) group = manifest.group(msng_mnfst_lst, lookup_filenode_link, filenode_collector[changed_files]) group.each {|chunk| yield chunk } msng_mnfst_lst = nil msng_mnfst_set.clear if extra_nodes extra_nodes.each do |fname| next if fname.kind_of?(Integer) msng_mnfst_set[fname] ||= {} changed_files[fname] = true end end changed_files.sort.each do |fname| file_revlog = file(fname) unless file_revlog.size > 0 raise abort("empty or missing revlog for #{fname}") end if msng_mnfst_set[fname] prune_filenodes[fname, file_revlog] add_extra_nodes[fname, missing_fn_set[fname]] missing_fn_list = missing_fn_set[fname].keys else missing_fn_list = [] end if missing_fn_list.size > 0 yield ChangeGroup.chunk_header(fname.size) yield fname missing_fn_list.sort!(&cmp_by_rev_function[file_revlog]) group = file_revlog.group(missing_fn_list, lookup_filenode_link_func[fname]) group.each {|chunk| yield chunk } end if missing_fn_set[fname] missing_fn_set.delete fname end end yield ChangeGroup.close_chunk if missing_cl_list run_hook :post_outgoing end end # end proc s = StringIO.new "",(ruby_19? ? "w+:ASCII-8BIT" : "w+") generate_group.call do |chunk| s.write chunk end s.seek(0, IO::SEEK_SET) end |
#changelog ⇒ ChangeLog
Returns the changelog for this repository. This changelog basically is the history of all commits.
341 342 343 344 345 346 347 348 349 350 351 352 353 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 341 def changelog return @changelog if @changelog @changelog = ChangeLog.new @store.opener if path = ENV['HG_PENDING'] if path =~ /^#{root}/ @changelog.read_pending('00changelog.i.a') end end @changelog end |
#clone(remote, opts = {:revs => [], :stream => false}) ⇒ Object
Clone a repository.
Here is what this does, pretty much:
% amp init monkey
% cd monkey
% amp pull http://monkey
It’s so simple it’s not even funny.
2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 2040 def clone(remote, opts={:revs => [], :stream => false}) # now, all clients that can request uncompressed clones can # read repo formats supported by all servers that can serve # them. # The streaming case: # if revlog format changes, client will have to check version # and format flags on "stream" capability, and use # uncompressed only if compatible. if opts[:stream] && opts[:revs].any? && remote.capable?('stream') stream_in remote else pull remote, :revs => opts[:revs] end end |
#commit(opts = {:message => "", :extra => {}, :files => []}) ⇒ String
Commits a changeset or set of files to the repository. You will quite often use this method since it’s basically the basis of version control systems.
2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 2142 def commit(opts={:message => "", :extra => {}, :files => []}) opts[:extra] ||= {} opts[:force] = true if opts[:extra]["close"] opts[:files] ||= [] opts[:files].uniq! use_dirstate = opts[:p1] == nil changes = {} lock_working_and_store do if use_dirstate p1, p2 = dirstate.parents update_dirstate = true tests = [opts[:force] , p2 != NULL_ID, opts[:match] ] raise StandardError("cannot partially commit a merge") if tests.all? if opts[:files].any? changes = {:modified => [], :removed => []} # split the files up so we can deal with them appropriately opts[:files].each do |file| state = dirstate[file] if state.normal? || state.merged? || state.added? changes[:modified] << file elsif state.removed? changes[:removed] << file elsif state.untracked? UI.warn "#{file} not tracked!" else UI.err "#{file} has unknown state #{state[0]}" end end else changes = status(:match => opts[:match]) end else p1, p2 = opts[:p1], (opts[:p2] || NULL_ID) update_dirstate = dirstate.parents[0] == p1 changes = {:modified => files} end merge_state = Amp::Merges::MergeState.new self # merge state! changes[:modified].each do |file| if merge_state[file] && merge_state[file] == "u" raise StandardError.new("unresolved merge conflicts (see `amp resolve`)") end end changeset = WorkingDirectoryChangeset.new self, :parents => [p1, p2] , :text => opts[:message], :user => opts[:user] , :date => opts[:date] , :extra => opts[:extra] , :changes => changes revision = commit_changeset changeset, :force => opts[:force] , :force_editor => opts[:force_editor], :empty_ok => opts[:empty_ok] , :use_dirstate => use_dirstate , :update_dirstate => update_dirstate merge_state.reset return revision end end |
#commit_changeset(changeset, opts = {:use_dirstate => true, :update_dirstate => true}) ⇒ String
Commits the given changeset to the repository.
2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 2232 def commit_changeset(changeset, opts = {:use_dirstate => true, :update_dirstate => true}) journal = nil valid = false #don't update the DirState if this is set! commit = ((changeset.modified || []) + (changeset.added || [])).sort remove = changeset.removed extra = changeset.extra.dup branchname = extra["branch"] user = changeset.user text = changeset.description p1, p2 = changeset.parents.map {|p| p.node} c1 = changelog.read(p1) # 1 parent's changeset as an array c2 = changelog.read(p2) # 2nd parent's changeset as an array m1 = manifest.read(c1[0]).dup # 1st parent's manifest m2 = manifest.read(c2[0]) # 2nd parent's manifest if opts[:use_dirstate] oldname = c1[5]["branch"] tests = [ commit.empty?, remove.empty?, ! opts[:force], p2 == NULL_ID, branchname = oldname ] if tests.all? UI::status "nothing changed" return nil end end xp1 = p1.hexlify xp2 = (p2 == NULL_ID) ? "" : p2.hexlify run_hook :pre_commit journal = Journal.new fresh = {} # new = reserved haha changed = [] link_rev = self.size (commit + (remove || [])).each {|file| UI::status file } #Amp::Logger.info("<changeset commit>").indent commit.each do |file| # begin versioned_file = changeset.get_file(file) newflags = versioned_file.flags fresh[file] = commit_file(versioned_file, m1, m2, link_rev, journal, changed) if [ changed.empty? || changed.last != file, m2[file] != fresh[file] ].all? changed << file if m1.flags[file] != newflags end m1.flags[file] = newflags dirstate.normal file if opts[:use_dirstate] #Amp::Logger.section("committing: #{file}") do #Amp::Logger.info("flags: #{newflags.inspect}") #Amp::Logger.info("total changes: #{changed.inspect}") #end # rescue # if opts[:use_dirstate] # UI.warn("trouble committing #{file}") # raise # else # remove << file # end # end end updated, added = [], [] changed.sort.each do |file| if m1[file] || m2[file] updated << file else added << file end end m1.merge!(fresh) removed = remove.sort.select {|f| m1[f] || m2[f]} removed_1 = [] removed.select {|f| m1[f]}.each do |f| m1.delete f removed_1 << f #Amp::Logger.info("Removed: #{f}") end fresh = fresh.map {|k, v| (v) ? k : nil}.reject {|k| k.nil? } man_entry = manifest.add(m1, journal, link_rev, c1[0], c2[0], [fresh, removed_1]) #Amp::Logger.info("Adding/modifying: #{fresh.inspect}") #Amp::Logger.info("Removing: #{removed_1.inspect}") #Amp::Logger.section("New Manifest") do #manifest.read(:tip).each do |file, _| #Amp::Logger.info(file) #end #end if !opts[:empty_ok] && !text template_opts = {:added => added, :updated => updated, :removed => removed, :template_type => :commit } edit_text = changeset.to_templated_s(template_opts) text = UI.edit(edit_text, user) end lines = text.rstrip.split("\n").map {|r| r.rstrip}.reject {|l| l.empty?} if lines.empty? && opts[:use_dirstate] raise abort("empty commit message") end text = lines.join("\n") changelog.delay_update n = changelog.add(man_entry, changed + removed_1, text, journal, p1, p2, user, changeset.date, extra) #Amp::Logger.section("changelog") do #Amp::Logger.info("manifest entry: #{man_entry.inspect}") #Amp::Logger.info("files: #{(changed + removed_1).inspect}") #Amp::Logger.info("text: #{text.inspect}") #Amp::Logger.info("p1: #{p1.inspect}") #Amp::Logger.info("p2: #{p2.inspect}") #Amp::Logger.info("user: #{user.inspect}") #Amp::Logger.info("date: #{changeset.date.inspect}") #Amp::Logger.info("extra: #{extra.inspect}") #end self.changelog.write_pending() changelog.finalize(journal) #Amp::Logger.outdent.info("</changeset commit>") # branchtags if opts[:use_dirstate] || opts[:update_dirstate] dirstate.parents = n removed.each {|f| dirstate.forget(f) } if opts[:use_dirstate] dirstate.write end valid = true journal.close run_hook :post_commit, :added => added, :modified => updated, :removed => removed, :user => user, :date => changeset.date, :text => text, :revision => changelog.index_size return n rescue StandardError => e if !valid dirstate.invalidate! end if e.kind_of?(AbortError) UI::warn "Abort: #{e}" else UI::warn "Got exception while committing. #{e}" UI::warn e.backtrace.join("\n") end journal.delete if journal end |
#commit_file(file, manifest1, manifest2, link_revision, journal, change_list) ⇒ Object
Commits a file as part of a larger transaction.
2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 2400 def commit_file(file, manifest1, manifest2, link_revision, journal, change_list) filename = file.path text = file.data curfile = self.file filename fp1 = manifest1[filename] || NULL_ID fp2 = manifest2[filename] || NULL_ID = {} copied = file.renamed if copied && copied[0] != filename # Mark the new revision of this file as a copy of another # file. This copy data will effectively act as a parent # of this new revision. If this is a merge, the first # parent will be the nullid (meaning "look up the copy data") # and the second one will be the other parent. For example: # # 0 --- 1 --- 3 rev1 changes file foo # \ / rev2 renames foo to bar and changes it # \- 2 -/ rev3 should have bar with all changes and # should record that bar descends from # bar in rev2 and foo in rev1 # # this allows this merge to succeed: # # 0 --- 1 --- 3 rev4 reverts the content change from rev2 # \ / merging rev3 and rev4 should use bar@rev2 # \- 2 --- 4 as the merge base copied_file = copied[0] copied_revision = manifest1[copied_file] new_fp = fp2 if manifest2 # branch merge if fp2 == NULL_ID || copied_revision == nil # copied on remote side if manifest2[copied_file] copied_revision = manifest2[copied_file] new_fp = fp1 end end end if copied_revision.nil? || copied_revision.empty? self["."].ancestors.each do |a| if a[copied_file] copied_revision = a[copied_file].file_node break end end end UI::say "#{filename}: copy #{copied_file}:#{copied_revision.hexlify}" ["copy"] = copied_file ["copyrev"] = copied_revision.hexlify fp1, fp2 = NULL_ID, new_fp elsif fp2 != NULL_ID fpa = curfile.ancestor(fp1, fp2) fp1, fp2 = fp2, NULL_ID if fpa == fp1 fp2 = NULL_ID if fpa != fp2 && fpa == fp2 end if fp2 == NULL_ID && !(curfile.cmp(fp1, text)) && .empty? return fp1 end change_list << filename return curfile.add(text, , journal, link_revision, fp1, fp2) end |
#common_nodes(remote, opts = {:heads => nil, :force => nil, :base => nil}) ⇒ Object
Find the common nodes, missing nodes, and remote heads.
So in this code, we use opts and fetch as hashes instead of arrays. We could very well use arrays, but hashes have O(1) lookup time, and since these could get RFH (Really Fucking Huge), we decided to take the liberty and just use hash for now.
If opts (Hash) is specified, assume that these nodes and their parents exist on the remote side and that no child of a node of base exists in both remote and self. Furthermore base will be updated to include the nodes that exists in self and remote but no children exists in self and remote. If a list of heads is specified, return only nodes which are heads or ancestors of these heads.
All the ancestors of base are in self and in remote.
1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 1265 def common_nodes(remote, opts={:heads => nil, :force => nil, :base => nil}) # variable prep! node_map = changelog.node_map search = [] unknown = [] fetch = {} seen = {} seen_branch = {} opts[:base] ||= {} opts[:heads] ||= remote.heads # if we've got nothing... if changelog.tip == NULL_ID opts[:base][NULL_ID] = true # 1 is stored in the Python return [NULL_ID], [NULL_ID], opts[:heads].dup unless opts[:heads] == [NULL_ID] return [NULL_ID], [], [] # if we didn't trip ^, we're returning this end # assume we're closer to the tip than the root # and start by examining heads UI::status 'searching for changes' opts[:heads].each do |head| if !node_map.include?(head) unknown << head else opts[:base][head] = true # 1 is stored in the Python end end opts[:heads] = unknown # the ol' switcheroo return opts[:base].keys, [], [] if unknown.empty? # BAIL # make a hash with keys of unknown requests = Hash.with_keys unknown count = 0 # Search through the remote branches # a branch here is a linear part of history, with 4 (four) # parts: # # head, root, first parent, second parent # (a branch always has two parents (or none) by definition) # # Here's where we start using the Hashes instead of Arrays # trick. Keep an eye out for opts[:base] and opts[:heads]! unknown = remote.branches(*unknown) until unknown.empty? r = [] while node = unknown.shift next if seen.include?(node[0]) UI::debug "examining #{short node[0]}:#{short node[1]}" if node[0] == NULL_ID # Do nothing... elsif seen_branch.include? node UI::debug 'branch already found' next elsif node_map.include? node[1] UI::debug "found incomplete branch #{short node[0]}:#{short node[1]}" search << node[0..1] seen_branch[node] = true # 1 in the python else unless seen.include?(node[1]) || fetch.include?(node[1]) if node_map.include?(node[2]) and node_map.include?(node[3]) UI::debug "found new changset #{short node[1]}" fetch[node[1]] = true # 1 in the python end # end if node[2..3].each do |p| opts[:base][p] = true if node_map.include? p end end # end unless node[2..3].each do |p| unless requests.include?(p) || node_map.include?(p) r << p requests[p] = true # 1 in the python end # end unless end # end each end # end if seen[node[0]] = true # 1 in the python end # end while unless r.empty? count += 1 UI::debug "request #{count}: #{r.map{|i| short i }}" (0..(r.size-1)).step(10) do |p| remote.branches(r[p..(p+9)]).each do |b| UI::debug "received #{short b[0]}:#{short b[1]}" unknown << b end end end # end unless end # end until # sorry for the ambiguous variable names # the python doesn't name them either, which # means I have no clue what these are find_proc = proc do |item1, item2| fetch[item1] = true opts[:base][item2] = true end # do a binary search on the branches we found search, new_count = *binary_search(:find => search, :repo => remote, :node_map => node_map, :on_find => find_proc) count += new_count # keep keeping track of the total # sanity check, because this method is sooooo fucking long fetch.keys.each do |f| if node_map.include? f raise RepoError.new("already have changeset #{short f[0..3]}") end end if opts[:base].keys == [NULL_ID] if opts[:force] UI::warn 'repository is unrelated' else raise RepoError.new('repository is unrelated') end end UI::debug "found new changesets starting at #{fetch.keys.map{|f| short f }.join ' '}" UI::debug "#{count} total queries" # on with the show! [opts[:base].keys, fetch.keys, opts[:heads]] end |
#copy(source, destination, opts) ⇒ Boolean
Copies a file from source
to destination
, while being careful of the specified options. This method will perform all necessary file manipulation and dirstate changes and so forth. Just give ‘er a source and a destination.
1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 1706 def copy(source, destination, opts) # Traverse repository subdirectories src = relative_join source target = relative_join destination # Is there a tracked file at our destination? If so, get its state. state = dirstate[target].status # abstarget is the full path to the target. Needed for system calls # (just to be safe) abstarget = working_join target # If true, we're copying into a directory, so be smart about it. if File.directory? abstarget abstarget = File.join abstarget, File.basename(src) target = File.join target, File.basename(src) end abssrc = working_join(src) exists = File.exist? abstarget # If the file's there, and we aren't forcing the copy, then we should let # the user know they might overwrite an existing file in the repo. if (!opts[:after] && exists || opts[:after] && [:merged, :normal].include?(state)) unless opts[:force] Amp::UI.warn "#{target} not overwriting, file exists" return false end end return if opts[:after] && !exists unless opts[:"dry-run"] # Performs actual file copy from one locatino to another. # Overwrites file if it's there. begin File.safe_unlink(abstarget) if exists target_dir = File.dirname abstarget File.makedirs target_dir unless File.directory? target_dir File.copy(abssrc, abstarget) rescue Errno::ENOENT # This happens if the file has been deleted between the check up above # (exists = File.exist? abstarget) and the call to File.safe_unlink. Amp::UI.warn("#{target}: deleted in working copy in the last 2 microseconds") rescue StandardError => e Amp::UI.warn("#{target} - cannot copy: #{e}") return false end end # Be nice and give the user some output if opts[:verbose] || opts[:"dry-run"] action = opts[:rename] ? "moving" : "copying" Amp::UI.status("#{action} #{src} to #{target}") end return false if opts[:"dry-run"] # in case the source of the copy is marked as the destination of a # different copy (that hasn't yet been committed either), we should # do some extra handling origsrc = dirstate.copy_map[src] || src if target == origsrc # We're copying back to our original location! D'oh. unless [:merged, :normal].include?(state) dirstate.maybe_dirty target end else if dirstate[origsrc].added? && origsrc == src # we copying an added (but uncommitted) file? UI.warn("#{origsrc} has not been committed yet, so no copy data" + "will be stored for #{target}") if [:untracked, :removed].include?(dirstate[target].status) add [target] end else dirstate_copy src, target end end # Clean up if we're doing a move, and not a copy. remove([src], :unlink => !(opts[:after])) if opts[:rename] end |
#cwd ⇒ String Also known as: pwd
Effectively FileUtils.pwd
128 129 130 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 128 def cwd dirstate.cwd end |
#dirstate ⇒ DirState
Returns the dirstate for this repository. The dirstate keeps track of files status, such as removed, added, merged, and so on. It also keeps track of the working directory.
383 384 385 386 387 388 389 390 391 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 383 def dirstate return @dirstate if @dirstate opener = Amp::Opener.new @root opener.default = :open_hg @dirstate = DirState.new(@root, @config, opener) @dirstate.read! end |
#dirstate_copy(source, dest) ⇒ Object
Copy a file from source
to dest
. Really simple, peeps. The reason this shit is even slightly complicated because it deals with file types. Otherwise I could write this in, what, 3 lines?
1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 1796 def dirstate_copy(source, dest) path = working_join dest if !File.exist?(path) || File.ftype(path) == 'link' UI::warn "#{dest} doesn't exist!" elsif not (File.ftype(path) == 'file' || File.ftype(path) == 'link') UI::warn "copy failed: #{dest} is neither a file nor a symlink" else lock_working do # HOME FREE!!!!!!! i love getting out of school before noon :-D # add it if it makes sense (like it was previously removed or untracked) # and then copy da hoe state = dirstate[dest].status dirstate.add dest if [:untracked, :removed].include?(state) dirstate.copy source => dest dirstate.write #Amp::Logger.info("copy #{source} -> #{dest}") end end end |
#each { ... } ⇒ Object
Iterates over each changeset in the repository, from oldest to newest.
167 168 169 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 167 def each(&block) 0.upto(size - 1) { |i| yield self[i]} end |
#file(f) ⇒ FileLog
Gets the file-log for the given path, so we can look at an individual file’s history, for example.
266 267 268 269 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 266 def file(f) f = f[1..-1] if f[0, 1] == "/" FileLog.new @store.opener, f end |
#find_incoming_roots(remote, opts = {:base => nil, :heads => nil, :force => false, :base => nil}) ⇒ Object
Return list of roots of the subsets of missing nodes from remote
If base dict is specified, assume that these nodes and their parents exist on the remote side and that no child of a node of base exists in both remote and self. Furthermore base will be updated to include the nodes that exists in self and remote but no children exists in self and remote. If a list of heads is specified, return only nodes which are heads or ancestors of these heads.
All the ancestors of base are in self and in remote. All the descendants of the list returned are missing in self. (and so we know that the rest of the nodes are missing in remote, see outgoing)
1239 1240 1241 1242 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 1239 def find_incoming_roots(remote, opts={:base => nil, :heads => nil, :force => false, :base => nil}) common_nodes(remote, opts)[1] end |
#find_outgoing_roots(remote, opts = {:base => nil, :heads => nil, :force => false}) ⇒ Object
Return list of nodes that are roots of subsets not in remote
If base dict is specified, assume that these nodes and their parents exist on the remote side. If a list of heads is specified, return only nodes which are heads or ancestors of these heads, and return a second element which contains all remote heads which get new children.
1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 1620 def find_outgoing_roots(remote, opts={:base => nil, :heads => nil, :force => false}) base, heads, force = opts[:base], opts[:heads], opts[:force] if base.nil? base = {} find_incoming_roots remote, :base => base, :heads => heads, :force => force end UI::debug("common changesets up to "+base.keys.map {|k| k.short_hex}.join(" ")) remain = Hash.with_keys changelog.node_map.keys, nil # prune everything remote has from the tree remain.delete NULL_ID remove = base.keys while remove.any? node = remove.shift if remain.include? node remain.delete node changelog.parents_for_node(node).each {|p| remove << p } end end # find every node whose parents have been pruned subset = [] # find every remote head that will get new children updated_heads = {} remain.keys.each do |n| p1, p2 = changelog.parents_for_node n subset << n unless remain.include?(p1) || remain.include?(p2) if heads && heads.any? updated_heads[p1] = true if heads.include? p1 updated_heads[p2] = true if heads.include? p2 end end # this is the set of all roots we have to push if heads && heads.any? return subset, updated_heads.keys else return subset end end |
#forget(list) ⇒ Boolean
Forgets an added file or files from the repository. Doesn’t delete the files, it just says “don’t add this on the next commit.”
1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 1480 def forget(list) lock_working do list = [*list] successful = list.any? do |f| if dirstate[f].status != :added UI.warn "#{f} not being added! can't forget it" false else dirstate.forget f true end end dirstate.write if successful end true end |
#get_changegroup(common, source) ⇒ Object
Faster version of changegroup_subset. Useful when pushing working dir.
Generate a changegruop of all nodes that we have that a recipient doesn’t
This is much easier than the previous function as we can assume that the recipient has any changegnode we aren’t sending them.
696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 696 def get_changegroup(common, source) # Call the hooks run_hook :pre_outgoing, :throw => true, :source => source nodes = changelog.find_missing common revset = Hash.with_keys(nodes.map {|n| changelog.rev(n)}) changegroup_info nodes, source identity = proc {|x| x } # ok so this method goes through the generic revlog, and looks for nodes # in the changeset(s) we're pushing. Works by the link_rev - basically, # the changelog says "hey we're at revision 35", and any changes to any # files in any revision logs for that commit will have a link_revision # of 35. So we just look for 35! gen_node_list = proc do |log| log.select {|r| revset[r.link_rev] }.map {|r| r.node_id } end # Ok.... I've tried explaining this 3 times and failed. # # Goal of this proc: We need to update the changed_files hash to reflect # which files (typically file logs) have changed since the last push. # # How it works: it generates a proc that takes a node_id. That node_id # will be looked up in the changelog.i file, which happens to store a # list of files that were changed in that commit! So really, this method # just takes a node_id, and adds filenamess to the list of changed files. changed_file_collector = proc do |changed_fileset| proc do |cl_node| c = changelog.read cl_node c[3].each {|fname| changed_fileset[fname] = true } end end lookup_revlink_func = proc do |revlog| # given a revision, return the node # good thing the python has a description of what this does # # *snort* lookup_revlink = proc do |n| changelog.node revlog[n].link_rev end end # This constructs a changegroup, or a list of all changed files. # If you're here, looking at this code, this bears repeating: # - Changelog # -- ChangeSet+ # # A Changelog (history of a branch) is an array of ChangeSets, # and a ChangeSet is just a single revision, containing what files # were changed, who did it, and the commit message. THIS IS JUST A # RECEIPT!!! # # The REASON we construct a changegroup here is because this is called # when we push, and we push a changelog (usually bundled to conserve # space). This is where we make that receipt, called a changegroup. # # 'nuff tangent, time to fucking code generate_group = proc do result = [] changed_files = {} coll = changed_file_collector[changed_files] # get the changelog's changegroups changelog.group(nodes, identity, coll) {|chunk| result << chunk } node_iter = gen_node_list[manifest] look = lookup_revlink_func[manifest] # get the manifest's changegroups manifest.group(node_iter, look) {|chunk| result << chunk } changed_files.keys.sort.each do |fname| file_revlog = file fname # warning: useless comment if file_revlog.index_size.zero? raise abort("empty or missing revlog for #{fname}") end node_list = gen_node_list[file_revlog] if node_list.any? result << RevlogSupport::ChangeGroup.chunk_header(fname.size) result << fname lookup = lookup_revlink_func[file_revlog] # Proc#call # more changegroups file_revlog.group(node_list, lookup) {|chunk| result << chunk } end end result << RevlogSupport::ChangeGroup.closing_chunk run_hook :post_outgoing, :node => nodes[0].hexlify, :source => source result end s = StringIO.new "",(ruby_19? ? "w+:ASCII-8BIT" : "w+") generate_group[].each {|chunk| s.write chunk } s.rewind s end |
#heads(start = nil, options = {:closed => true}) ⇒ Object
Returns the node_id’s of the heads of the repository.
1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 1865 def heads(start=nil, ={:closed => true}) heads = changelog.heads(start) should_show = lambda do |head| return true if [:closed] extras = changelog.read(head)[5] return !(extras["close"]) end heads = heads.select {|h| should_show[h] } heads.map! {|h| [changelog.rev(h), h] } heads.sort! {|arr1, arr2| arr2[0] <=> arr1[0] } heads.map! {|r, n| n} end |
#init(config = @config) ⇒ Array<String>
Creates this repository’s folders and structure.
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 89 def init(config=@config) # make the directory if it's not there FileUtils.makedirs @hg requirements = ["revlogv1"] # add some requirements if config["format"]["usestore", Boolean] || true FileUtils.mkdir "#{@hg}/store" requirements << "store" requirements << "fncache" if config["format"]["usefncache", Boolean, true] # add the changelog make_changelog end # write the requires file write_requires requirements end |
#inspect ⇒ Object
80 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 80 def inspect; "#<LocalRepository @root=#{@root.inspect}>"; end |
#invalidate! ⇒ Object
Invalidate the repository: delete things and reset others.
2108 2109 2110 2111 2112 2113 2114 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 2108 def invalidate! @changelog = nil @manifest = nil invalidate_tag_cache! invalidate_branch_cache! end |
#join(file) ⇒ String
Joins the path from this repo’s path (.hg), to the file provided.
419 420 421 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 419 def join(file) File.join(@hg, file) end |
#living_parents ⇒ Object
Returns the parents that aren’t NULL_ID
1557 1558 1559 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 1557 def living_parents dirstate.parents.select {|p| p != NULL_ID } end |
#local? ⇒ Boolean
72 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 72 def local?; true; end |
#lock_store(wait = true) { ... } ⇒ Lock
Locks the repository’s .hg/store directory. Returns the lock, or if a block is given, runs the block with the lock, and clears the lock afterward.
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 205 def lock_store(wait = true) return @lock_ref if @lock_ref && @lock_ref.weakref_alive? lock = make_lock(store_join("lock"), wait, nil, nil, "repository #{root}") @lock_ref = WeakRef.new(lock) if block_given? begin yield ensure @lock_ref = nil lock.release end else return lock end end |
#lock_working(wait = true) { ... } ⇒ Lock
Locks the repository’s .hg/store directory. Returns the lock, or if a block is given, runs the block with the lock, and clears the lock afterward.
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 231 def lock_working(wait = true) return @working_lock_ref if @working_lock_ref && @working_lock_ref.weakref_alive? lock = make_lock(join("wlock"), wait, nil, nil, "working directory of #{root}") @working_lock_ref = WeakRef.new(lock) if block_given? begin yield ensure @working_lock_ref = nil lock.release end else return lock end end |
#lock_working_and_store(wait = true) ⇒ Object
Takes a block, and runs that block with both the store and the working directory locked.
252 253 254 255 256 257 258 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 252 def lock_working_and_store(wait=true) lock_store(wait) do lock_working(wait) do yield end end end |
#lookup(key) ⇒ String
Looks up an identifier for a revision in the commit history. This key could be an integer (specifying a revision number), “.” for the latest revision, “null” for the null revision, “tip” for the tip of the repository, a node_id (in hex or binary form) for a revision in the changelog. Yeah. It’s a flexible method.
442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 442 def lookup(key) key = key.to_i if key.to_i.to_s == key.to_s # casting for things like "10" case key when Fixnum, Bignum, Integer changelog.node_id_for_index(key) when "." dirstate.parents().first when "null", nil NULL_ID when "tip" changelog.tip else n = changelog.id_match(key) return n if n return [key] if [key] return [key] if [key] n = changelog.partial_id_match(key) return n if n # bail raise RepoError.new("unknown revision #{key}") end end |
#make_lock(lockname, wait, release_proc, acquire_proc, desc) ⇒ Lock
Creates a lock at the given path. At first it tries to just make it straight away. If this fails, we then sleep for up to a given amount of time (defaults to 10 minutes!) and continually try to acquire the lock.
183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 183 def make_lock(lockname, wait, release_proc, acquire_proc, desc) begin lock = Lock.new(lockname, :timeout => 0, :release_fxn => release_proc, :desc => desc) rescue LockHeld => err raise unless wait UI.warn("waiting for lock on #{desc} held by #{err.locker}") lock = Lock.new(lockname, :timeout => @config["ui","timeout","600"].to_i, :release_proc => release_proc, :desc => desc) end acquire_proc.call if acquire_proc return lock end |
#manifest ⇒ Manifest
Returns the manifest for this repository. The manifest keeps track of what files exist at what times, and if they have certain flags (such as executable, or is it a symlink).
370 371 372 373 374 375 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 370 def manifest return @manifest if @manifest changelog #load the changelog @manifest = Manifest.new @store.opener end |
#merge_state ⇒ MergeState
Returns the merge state for this repository. The merge state keeps track of what files need to be merged for an update to be successfully completed.
360 361 362 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 360 def merge_state @merge_state ||= Amp::Merges::MergeState.new(self) end |
#open(*args, &block) ⇒ Object
Opens a file using our opener. Can only access files in .hg/
401 402 403 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 401 def open(*args, &block) @hg_opener.open(*args, &block) end |
#parents(change_id = nil) ⇒ Array<Changeset>
Returns the parent changesets of the specified changeset. Defaults to the working directory, if change_id
is unspecified.
277 278 279 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 277 def parents(change_id = nil) self[change_id].parents end |
#path_to(src, dest) ⇒ String
Returns the relative path from src
to dest
.
140 141 142 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 140 def path_to(src, dest) dirstate.path_to src, dest end |
#pristine? ⇒ Boolean
Has the repository been changed since the last commit? Returns true if there are NO outstanding changes or uncommitted merges.
115 116 117 118 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 115 def pristine? dirstate.parents.last == RevlogSupport::Node::NULL_ID && status(:only => [:modified, :added, :removed, :deleted]).all? {|_, v| v.empty? } end |
#pull(remote, opts = {:heads => nil, :force => nil}) ⇒ Object
Pull new changegroups from remote
This does not apply the changes, but pulls them onto the local server.
509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 509 def pull(remote, opts={:heads => nil, :force => nil}) lock_store do # get the common nodes, missing nodes, and the remote heads # this is findcommonincoming in the Python code, for those with both open common, fetch, remote_heads = *common_nodes(remote, :heads => opts[:heads], :force => opts[:force]) UI::status 'requesting all changes' if fetch == [NULL_ID] if fetch.empty? UI::status 'no changes found' return 0 end if (opts[:heads].nil? || opts[:heads].empty?) && remote.capable?('changegroupsubset') opts[:heads] = remote_heads end opts[:heads] ||= [] cg = if opts[:heads].empty? remote.changegroup fetch, :pull else # check for capabilities unless remote.capable? 'changegroupsubset' raise abort('Partial pull cannot be done because' + 'the other repository doesn\'t support' + 'changegroupsubset') end # end unless remote.changegroup_subset fetch, opts[:heads], :pull end add_changegroup cg, :pull, remote.url end end |
#push(remote_repo, opts = {:force => false, :revs => nil}) ⇒ Object
There are two ways to push to remote repo:
addchangegroup assumes local user can lock remote repo (local filesystem, old ssh servers).
unbundle assumes local user cannot lock remote repo (new ssh servers, http servers).
1569 1570 1571 1572 1573 1574 1575 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 1569 def push(remote_repo, opts={:force => false, :revs => nil}) if remote_repo.capable? "unbundle" push_unbundle remote_repo, opts else push_add_changegroup remote_repo, opts end end |
#push_add_changegroup(remote, opts = {}) ⇒ Object
– add default values for opts
Push and add a changegroup
1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 1580 def push_add_changegroup(remote, opts={}) # no locking cuz we rockz ret = pre_push remote, opts if ret[0] cg, remote_heads = *ret remote.add_changegroup cg, :push, url else ret[1] end end |
#push_unbundle(remote, opts = {}) ⇒ Object
– add default values for opts
Push an unbundled dohickey
1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 1595 def push_unbundle(remote, opts={}) # local repo finds heads on server, finds out what revs it # must push. once revs transferred, if server finds it has # different heads (someone else won commit/push race), server # aborts. ret = pre_push remote, opts if ret[0] cg, remote_heads = *ret remote_heads = ['force'] if opts[:force] remote.unbundle cg, remote_heads, :push else ret[1] end end |
#relative_join(file, cur_dir = FileUtils.pwd) ⇒ Object
74 75 76 77 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 74 def relative_join(file, cur_dir=FileUtils.pwd) @root_pathname ||= Pathname.new(@root) Pathname.new(File.(File.join(cur_dir, file))).relative_path_from(@root_pathname).to_s end |
#remove(list, opts = {}) ⇒ Boolean
Removes the file (or files) from the repository. Marks them as removed in the DirState, and if the :unlink option is provided, the files are deleted from the filesystem.
1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 1512 def remove(list, opts={}) list = [*list] # Should we delete the filez? if opts[:unlink] list.each do |f| ignore_missing_files do FileUtils.safe_unlink working_join(f) end end end lock_working do # Save ourselves a dirstate write successful = list.any? do |f| if opts[:unlink] && File.exists?(working_join(f)) # Uh, why is the file still there? Don't remove it from the dirstate UI.warn("#{f} still exists!") false # no success elsif dirstate[f].added? # Is it already added? if so, forgettaboutit dirstate.forget f #Amp::Logger.info("forgot #{f}") true # success! elsif !dirstate.tracking?(f) # Are we not even tracking this file? dumbass UI.warn("#{f} not being tracked!") false # no success else # Woooo we can delete it dirstate.remove f #Amp::Logger.info("removed #{f}") true end end # Write 'em out boss dirstate.write if successful end true end |
#revert(files, opts = {}) ⇒ Boolean
Revert a file or group of files to revision
. If opts[:unlink]
is true, then the files
1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 1154 def revert(files, opts={}) # get the parents - used in checking if we haven an uncommitted merge parent, p2 = dirstate.parents # get the revision rev = opts[:revision] || opts[:rev] || opts[:to] # check to make sure it's logically possible unless rev || p2 == RevlogSupport::Node::NULL_ID raise abort("uncommitted merge - please provide a specific revision") end # if we have anything here, then create a matcher matcher = if files Amp::Match.create :files => files , :includer => opts[:include], :excluder => opts[:exclude] else # else just return nil # we can return nil because when it gets used in :match => matcher, # it will be as though it's not even there nil end # the changeset we use as a guide changeset = self[rev] # get the files that need to be changed stats = status :node_1 => rev, :match => matcher ### # now make the changes ### ########## # MODIFIED and DELETED ########## # Just write the old data to the files (stats[:modified] + stats[:deleted]).each do |path| File.open path, 'w' do |file| file.write changeset.get_file(path).data end UI::status "restored\t#{path}" end ########## # REMOVED ########## # these files are set to be removed, and have thus far been dropped from the filesystem # we restore them and we alert the repo stats[:removed].each do |path| File.open path, 'w' do |file| file.write changeset.get_file(path).data end dirstate.normal path # pretend nothing happened UI::status "saved\t#{path}" end ########## # ADDED ########## # these files have been added SINCE +rev+ stats[:added].each do |path| remove path UI::status "destroyed\t#{path}" end # pretend these files were never even there true # success marker end |
#run_hook(call, opts = {:throw => false}) ⇒ Object
Call the hooks that run under call
1407 1408 1409 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 1407 def run_hook(call, opts={:throw => false}) Hook.run_hook(call, opts) end |
#size ⇒ Integer
Returns the number of revisions the repository is tracking.
1469 1470 1471 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 1469 def size changelog.size end |
#status(opts = {:node_1 => '.'}) ⇒ Hash<Symbol => Array<String>>
This gives the status of the repository, comparing 2 node in its history. Now, with no parameters, it’s going to compare the last revision with the working directory, which is the most common usage - that answers “what is the current status of the repository, compared to the last time a commit happened?”. However, given any two revisions, it can compare them.
1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 1930 def status(opts={:node_1 => '.'}) run_hook :status node1, node2, match = opts[:node_1], opts[:node_2], opts[:match] match = Match.create({}) { true } unless match node1 = self[node1] unless node1.kind_of? Changeset # get changeset objects node2 = self[node2] unless node2.kind_of? Changeset write_dirstate = false # are we working with working directories? working = node2.revision == nil parent_working = working && node1 == self["."] # load the working directory's manifest node2.manifest if !working && node2.revision < node1.revision if working # get the dirstate's latest status status = dirstate.status(opts[:ignored], opts[:clean], opts[:unknown], match) # this case is run about 99% of the time # do we need to do hashes on any files to see if they've changed? if parent_working && status[:lookup].any? # lookup.any? is a shortcut for !lookup.empty? clean, modified, write_dirstate = *fix_files(status[:lookup], node1, node2) status[:clean] += clean status[:modified] += modified end else status = {:clean => [], :modified => [], :lookup => [], :unknown => [], :ignored => [], :removed => [], :added => [], :deleted => []} end # if we're working with old revisions... unless parent_working # get the older revision manifest mf1 = node1.manifest.dup if working # get the working directory manifest. note, it's a tweaked # manifest to reflect working directory files mf2 = self["."].manifest.dup # mark them as not in the manifest to force checking later files_for_later = status[:lookup] + status[:modified] + status[:added] files_for_later.each {|file| mf2.mark_for_later file, node2 } # remove any files we've marked as removed them from the '.' manifest status[:removed].each {|file| mf2.delete file } else # if we aren't working with the working directory, then we'll # just use the old revision's information status[:removed], status[:unknown], status[:ignored] = [], [], [] mf2 = node2.manifest.dup end # Every file in the later revision (or working directory) mf2.each do |file, node| # Does it exist in the old manifest? If so, it wasn't added. if mf1[file] # the tests to run tests = [ mf1.flags[file] != mf2.flags[file] , mf1[file] != mf2[file] && (mf2[file] || node1[file] === node2[file]) ] # It's in the old manifest, so lets check if its been changed # Else, it must be unchanged if tests.any? status[:modified] << file status[:clean] << file if opts[:clean] end # Remove that file from the old manifest, since we've checked it mf1.delete file else # if it's not in the old manifest, it's been added status[:added] << file end end # Anything left in the old manifest is a file we've removed since the # first revision. status[:removed] = mf1.keys end # We're done! status.delete :lookup # because nobody cares about it delta = status.delete :delta status.map {|k, v| [k, v.sort] }.to_hash # sort dem fuckers status[:delta] = delta status.select {|k, _| opts[:only] ? opts[:only].include?(k) : true }.to_hash end |
#store_join(file) ⇒ String
Joins the path, with a bunch of other args, to the store’s directory. Used for opening FileLogs and whatnot.
429 430 431 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 429 def store_join(file) @store.join file end |
#stream_in(remote) ⇒ Integer
Stream in the data from remote
.
2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 2061 def stream_in(remote) remote.stream_out do |f| l = f.gets # this should be the server code unless Integer(l) raise ResponseError.new("Unexpected response from server: #{l}") end case l.to_i when 1 raise RepoError.new("operation forbidden by server") when 2 raise RepoError.new("locking the remote repository failed") end UI::status "streaming all changes" l = f.gets # this is effectively [total_files, total_bytes].join ' ' total_files, total_bytes = *l.split(' ').map {|i| i.to_i }[0..1] UI::status "#{total_files} file#{total_files == 1 ? '' : 's' } to transfer, #{total_bytes.to_human} of data" start = Time.now total_files.times do |i| l = f.gets name, size = *l.split("\0")[0..1] size = size.to_i UI::debug "adding #{name} (#{size.to_human})" @store.opener.open do |store_file| chunk = f.read size # will return nil if at EOF store_file.write chunk if chunk end end elapsed = Time.now - start elapsed = 0.001 if elapsed <= 0 UI::status("transferred #{total_bytes.to_human} in #{elapsed}" + "second#{elapsed == 1.0 ? '' : 's' } (#{total_bytes.to_f / elapsed}/sec)") invalidate! heads.size - 1 end end |
#undelete(list) ⇒ Object Also known as: restore
Undelete a file. For instance, if you remove something and then find out that you NEED that file, you can use this command.
1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 1823 def undelete(list) manifests = living_parents.map do |p| manifest.read changelog.read(p).first end # now we actually restore the files list.each do |file| unless dirstate[file].removed? UI.warn "#{file} isn't being removed!" else m = manifests[0] || manifests[1] data = file(f).read m[f] add_file file, data, m.flags(f) # add_file is wwrite in the python dirstate.normal f # we know it's clean, we just restored it end end end |
#url ⇒ String
Returns the URL of this repository. Uses the “file:” scheme as such.
397 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 397 def url; "file:#{@root}"; end |
#versioned_file(path, opts = {}) ⇒ Object
Gets a versioned file for the given path, so we can look at the individual file’s history with the file object itself.
290 291 292 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 290 def versioned_file(path, opts={}) VersionedFile.new(self, path, opts) end |
#walk(node = nil, match = Match.create({}) { true }) ⇒ Object
Walk recursively through the directory tree (or a changeset) finding all files matched by the match function
1886 1887 1888 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 1886 def walk(node=nil, match = Match.create({}) { true }) self[node].walk(match) # calls Changeset#walk end |
#working_file(path, opts = {}) ⇒ Object
Gets a versioned file, but using the working directory, so we are looking past the last commit. Important because it uses a different class. Duh.
303 304 305 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 303 def working_file(path, opts={}) VersionedWorkingFile.new(self, path, opts) end |
#working_join(path) ⇒ String
Joins the path to the repo’s root (not .hg, the working dir root)
410 411 412 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 410 def working_join(path) File.join(@root, path) end |
#working_read(filename) ⇒ String
Reads from a file, but in the working directory. Uses encoding if we are set up to do so.
314 315 316 317 318 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 314 def working_read(filename) data = @file_opener.open(filename, "r") {|f| f.read } data = @filters["encode"].call(filename, data) if @filters["encode"] data end |
#working_write(path, data, flags) ⇒ Object
Writes to a file, but in the working directory. Uses encoding if we are set up to do so. Also handles symlinks and executables. Ugh.
327 328 329 330 331 332 333 334 |
# File 'lib/amp/repository/repositories/local_repository.rb', line 327 def working_write(path, data, flags) @file_opener.open(path, "w") do |file| file.write(data) end if flags && flags.include?('x') File.amp_set_executable(working_join(path), true) end end |