|
1 # fetch.py - pull and merge remote changes |
|
2 # |
|
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> |
|
4 # |
|
5 # This software may be used and distributed according to the terms of the |
|
6 # GNU General Public License version 2 or any later version. |
|
7 |
|
8 '''pull, update and merge in one command''' |
|
9 |
|
10 from mercurial.i18n import _ |
|
11 from mercurial.node import nullid, short |
|
12 from mercurial import commands, cmdutil, hg, util, url, error |
|
13 from mercurial.lock import release |
|
14 |
|
15 def fetch(ui, repo, source='default', **opts): |
|
16 '''pull changes from a remote repository, merge new changes if needed. |
|
17 |
|
18 This finds all changes from the repository at the specified path |
|
19 or URL and adds them to the local repository. |
|
20 |
|
21 If the pulled changes add a new branch head, the head is |
|
22 automatically merged, and the result of the merge is committed. |
|
23 Otherwise, the working directory is updated to include the new |
|
24 changes. |
|
25 |
|
26 When a merge occurs, the newly pulled changes are assumed to be |
|
27 "authoritative". The head of the new changes is used as the first |
|
28 parent, with local changes as the second. To switch the merge |
|
29 order, use --switch-parent. |
|
30 |
|
31 See :hg:`help dates` for a list of formats valid for -d/--date. |
|
32 |
|
33 Returns 0 on success. |
|
34 ''' |
|
35 |
|
36 date = opts.get('date') |
|
37 if date: |
|
38 opts['date'] = util.parsedate(date) |
|
39 |
|
40 parent, p2 = repo.dirstate.parents() |
|
41 branch = repo.dirstate.branch() |
|
42 branchnode = repo.branchtags().get(branch) |
|
43 if parent != branchnode: |
|
44 raise util.Abort(_('working dir not at branch tip ' |
|
45 '(use "hg update" to check out branch tip)')) |
|
46 |
|
47 if p2 != nullid: |
|
48 raise util.Abort(_('outstanding uncommitted merge')) |
|
49 |
|
50 wlock = lock = None |
|
51 try: |
|
52 wlock = repo.wlock() |
|
53 lock = repo.lock() |
|
54 mod, add, rem, del_ = repo.status()[:4] |
|
55 |
|
56 if mod or add or rem: |
|
57 raise util.Abort(_('outstanding uncommitted changes')) |
|
58 if del_: |
|
59 raise util.Abort(_('working directory is missing some files')) |
|
60 bheads = repo.branchheads(branch) |
|
61 bheads = [head for head in bheads if len(repo[head].children()) == 0] |
|
62 if len(bheads) > 1: |
|
63 raise util.Abort(_('multiple heads in this branch ' |
|
64 '(use "hg heads ." and "hg merge" to merge)')) |
|
65 |
|
66 other = hg.repository(hg.remoteui(repo, opts), |
|
67 ui.expandpath(source)) |
|
68 ui.status(_('pulling from %s\n') % |
|
69 url.hidepassword(ui.expandpath(source))) |
|
70 revs = None |
|
71 if opts['rev']: |
|
72 try: |
|
73 revs = [other.lookup(rev) for rev in opts['rev']] |
|
74 except error.CapabilityError: |
|
75 err = _("Other repository doesn't support revision lookup, " |
|
76 "so a rev cannot be specified.") |
|
77 raise util.Abort(err) |
|
78 |
|
79 # Are there any changes at all? |
|
80 modheads = repo.pull(other, heads=revs) |
|
81 if modheads == 0: |
|
82 return 0 |
|
83 |
|
84 # Is this a simple fast-forward along the current branch? |
|
85 newheads = repo.branchheads(branch) |
|
86 newchildren = repo.changelog.nodesbetween([parent], newheads)[2] |
|
87 if len(newheads) == 1: |
|
88 if newchildren[0] != parent: |
|
89 return hg.clean(repo, newchildren[0]) |
|
90 else: |
|
91 return 0 |
|
92 |
|
93 # Are there more than one additional branch heads? |
|
94 newchildren = [n for n in newchildren if n != parent] |
|
95 newparent = parent |
|
96 if newchildren: |
|
97 newparent = newchildren[0] |
|
98 hg.clean(repo, newparent) |
|
99 newheads = [n for n in newheads if n != newparent] |
|
100 if len(newheads) > 1: |
|
101 ui.status(_('not merging with %d other new branch heads ' |
|
102 '(use "hg heads ." and "hg merge" to merge them)\n') % |
|
103 (len(newheads) - 1)) |
|
104 return 1 |
|
105 |
|
106 # Otherwise, let's merge. |
|
107 err = False |
|
108 if newheads: |
|
109 # By default, we consider the repository we're pulling |
|
110 # *from* as authoritative, so we merge our changes into |
|
111 # theirs. |
|
112 if opts['switch_parent']: |
|
113 firstparent, secondparent = newparent, newheads[0] |
|
114 else: |
|
115 firstparent, secondparent = newheads[0], newparent |
|
116 ui.status(_('updating to %d:%s\n') % |
|
117 (repo.changelog.rev(firstparent), |
|
118 short(firstparent))) |
|
119 hg.clean(repo, firstparent) |
|
120 ui.status(_('merging with %d:%s\n') % |
|
121 (repo.changelog.rev(secondparent), short(secondparent))) |
|
122 err = hg.merge(repo, secondparent, remind=False) |
|
123 |
|
124 if not err: |
|
125 # we don't translate commit messages |
|
126 message = (cmdutil.logmessage(opts) or |
|
127 ('Automated merge with %s' % |
|
128 url.removeauth(other.url()))) |
|
129 editor = cmdutil.commiteditor |
|
130 if opts.get('force_editor') or opts.get('edit'): |
|
131 editor = cmdutil.commitforceeditor |
|
132 n = repo.commit(message, opts['user'], opts['date'], editor=editor) |
|
133 ui.status(_('new changeset %d:%s merges remote changes ' |
|
134 'with local\n') % (repo.changelog.rev(n), |
|
135 short(n))) |
|
136 |
|
137 return err |
|
138 |
|
139 finally: |
|
140 release(lock, wlock) |
|
141 |
|
142 cmdtable = { |
|
143 'fetch': |
|
144 (fetch, |
|
145 [('r', 'rev', [], |
|
146 _('a specific revision you would like to pull'), _('REV')), |
|
147 ('e', 'edit', None, _('edit commit message')), |
|
148 ('', 'force-editor', None, _('edit commit message (DEPRECATED)')), |
|
149 ('', 'switch-parent', None, _('switch parents when merging')), |
|
150 ] + commands.commitopts + commands.commitopts2 + commands.remoteopts, |
|
151 _('hg fetch [SOURCE]')), |
|
152 } |