1/*
2 * This file is part of gitg
3 *
4 * Copyright (C) 2012 - Jesse van den Kieboom
5 *
6 * gitg is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * gitg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with gitg. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20namespace Gitg
21{
22
23public enum RefType
24{
25	NONE,
26	BRANCH,
27	REMOTE,
28	TAG,
29	STASH
30}
31
32public enum RefState
33{
34	NONE,
35	SELECTED,
36	PRELIGHT
37}
38
39/**
40 * Parse ref name into components.
41 *
42 * This class parses a refname and splits it into several components.
43 *
44 */
45public class ParsedRefName : Object
46{
47	private string d_shortname;
48	private string d_name;
49	private string d_remote_name;
50	private string d_remote_branch;
51	private string? d_prefix;
52
53	/**
54	 * The type of ref.
55	 */
56	public RefType rtype { get; private set; }
57
58	/**
59	 * The full name of the ref.
60	 */
61	public string name
62	{
63		owned get { return d_name; }
64	}
65
66	/**
67	 * The short name of the ref. This represents the name of the ref
68	 * without the information of the type of ref.
69	 */
70	public string shortname
71	{
72		owned get { return d_shortname; }
73	}
74
75	/**
76	 * The remote name of the ref (only for remote refs)
77	 */
78	public string? remote_name
79	{
80		owned get { return d_remote_name; }
81	}
82
83	/**
84	 * The remote branch name of the ref (only for remote refs)
85	 */
86	public string? remote_branch
87	{
88		owned get { return d_remote_branch; }
89	}
90
91	public ParsedRefName(string name)
92	{
93		parse_name(name);
94	}
95
96	public string? prefix
97	{
98		get { return d_prefix; }
99	}
100
101	private void parse_name(string name)
102	{
103		d_name = name;
104
105		string[] prefixes = {
106			"refs/heads/",
107			"refs/remotes/",
108			"refs/tags/",
109			"refs/stash"
110		};
111
112		d_shortname = name;
113		d_prefix = null;
114
115		if (d_name == "HEAD")
116		{
117			rtype = RefType.BRANCH;
118		}
119
120		for (var i = 0; i < prefixes.length; ++i)
121		{
122			if (!d_name.has_prefix(prefixes[i]))
123			{
124				continue;
125			}
126
127			d_prefix = prefixes[i];
128
129			rtype = (RefType)(i + 1);
130
131			if (rtype == RefType.STASH)
132			{
133				d_prefix = "refs/";
134				d_shortname = "stash";
135			}
136			else
137			{
138				d_shortname = d_name[d_prefix.length:d_name.length];
139			}
140
141			if (rtype == RefType.REMOTE)
142			{
143				var pos = d_shortname.index_of_char('/');
144
145				if (pos != -1)
146				{
147					d_remote_name = d_shortname.substring(0, pos);
148					d_remote_branch = d_shortname.substring(pos + 1);
149				}
150				else
151				{
152					d_remote_name = d_shortname;
153				}
154			}
155		}
156	}
157}
158
159public interface Ref : Ggit.Ref
160{
161	private static Regex? s_remote_key_regex;
162
163	protected abstract ParsedRefName d_parsed_name { get; set; }
164	protected abstract List<Ref>? d_pushes { get; owned set; }
165
166	public abstract RefState state { get; set; }
167	public abstract bool working { get; set; }
168
169	public ParsedRefName parsed_name
170	{
171		owned get
172		{
173			if (d_parsed_name == null)
174			{
175				d_parsed_name = new ParsedRefName(get_name());
176			}
177
178			return d_parsed_name;
179		}
180	}
181
182	public abstract new Gitg.Repository get_owner();
183
184	private void add_push_ref(string spec)
185	{
186		Gitg.Ref rf;
187
188		try
189		{
190			rf = get_owner().lookup_reference(spec);
191		} catch { return; }
192
193		if (d_pushes.find_custom(rf, (a, b) => {
194			return a.get_name().ascii_casecmp(b.get_name());
195		}) == null)
196		{
197			d_pushes.append(rf);
198		}
199	}
200
201	private void add_branch_configured_push(Ggit.Config cfg)
202	{
203		string remote;
204		string merge;
205
206		try
207		{
208			remote = cfg.get_string(@"branch.$(parsed_name.shortname).remote");
209			merge = cfg.get_string(@"branch.$(parsed_name.shortname).merge");
210		} catch { return; }
211
212		var nm = new ParsedRefName(merge);
213
214		add_push_ref(@"refs/remotes/$remote/$(nm.shortname)");
215	}
216
217	private void add_remote_configured_push(Ggit.Config cfg)
218	{
219		Regex valregex;
220
221		try
222		{
223			valregex = new Regex("^%s:(.*)".printf(Regex.escape_string(get_name())));
224
225			if (s_remote_key_regex == null)
226			{
227				s_remote_key_regex = new Regex("remote\\.(.*)\\.push");
228			}
229
230			cfg.match_foreach(s_remote_key_regex, (info, val) => {
231				MatchInfo vinfo;
232
233				if (!valregex.match(val, 0, out vinfo))
234				{
235					return 0;
236				}
237
238				var rname = info.fetch(1);
239				var pref = vinfo.fetch(1);
240
241				add_push_ref(@"refs/remotes/$rname/$pref");
242				return 0;
243			});
244
245		} catch { return; }
246	}
247
248	private void add_branch_same_name_push(Ggit.Config cfg)
249	{
250		string remote;
251
252		try
253		{
254			remote = cfg.get_string(@"branch.$(parsed_name.shortname).remote");
255		} catch { return; }
256
257		add_push_ref(@"refs/remotes/$remote/$(parsed_name.shortname)");
258	}
259
260	private void compose_pushes()
261	{
262		d_pushes = new List<Ref>();
263
264		Ggit.Config cfg;
265
266		try
267		{
268			cfg = get_owner().get_config();
269		} catch { return; }
270
271		/* The possible refspecs of a local $ref (branch) are resolved in the
272		 * following order (duplicates are removed automatically):
273		 *
274		 * 1) Branch configured remote and merge (git push):
275		 *
276		 *    Remote: branch.<name>.remote
277		 *    Spec:   branch.<name>.merge
278		 *
279		 * 2) Remote configured matching push refspec:
280		 *    For each remote.<name>.push matching ${ref.name}:<spec>
281		 *
282		 *    Remote: <name>
283		 *    Spec:   <spec>
284		 *
285		 * 3) Remote branch with the same name
286		 *
287		 *    Remote: branch.<name>.remote
288		 *    Spec:   ${ref.name}
289		 */
290
291		// Branch configured remote and merge
292		add_branch_configured_push(cfg);
293
294		// Remote configured push spec
295		add_remote_configured_push(cfg);
296
297		// Same name push
298		add_branch_same_name_push(cfg);
299	}
300
301	public List<Ref> pushes
302	{
303		get
304		{
305			if (d_pushes == null)
306			{
307				compose_pushes();
308			}
309
310			return d_pushes;
311		}
312	}
313}
314
315}
316
317// ex:set ts=4 noet
318