1 /*
2  *      Copyright 2007-2011 Frank Lanitz <frank(at)frank(dot)uvena(dot)de>
3  *      Copyright 2007-2009 Enrico Tröger <enrico.troeger@uvena.de>
4  *      Copyright 2007 Nick Treleaven <nick.treleaven@btinternet.com>
5  *      Copyright 2007-2009 Yura Siamashka <yurand2@gmail.com>
6  *
7  *      This program is free software; you can redistribute it and/or modify
8  *      it under the terms of the GNU General Public License as published by
9  *      the Free Software Foundation; either version 2 of the License, or
10  *      (at your option) any later version.
11  *
12  *      This program is distributed in the hope that it will be useful,
13  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *      GNU General Public License for more details.
16  *
17  *      You should have received a copy of the GNU General Public License
18  *      along with this program; if not, write to the Free Software
19  *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  */
21 
22 #include <string.h>
23 #include <geanyplugin.h>
24 #include "geanyvc.h"
25 
26 extern GeanyData *geany_data;
27 
28 
29 static const gchar *SVN_CMD_DIFF_FILE[] =
30 	{ "svn", "diff", "--non-interactive", BASE_FILENAME, NULL };
31 static const gchar *SVN_CMD_DIFF_DIR[] = { "svn", "diff", "--non-interactive", BASE_DIRNAME, NULL };
32 static const gchar *SVN_CMD_REVERT_FILE[] = { "svn", "revert", BASENAME, NULL };
33 static const gchar *SVN_CMD_REVERT_DIR[] = { "svn", "revert", BASE_DIRNAME, "-R", NULL };
34 static const gchar *SVN_CMD_STATUS[] = { "svn", "status", NULL };
35 static const gchar *SVN_CMD_ADD[] = { "svn", "add", BASENAME, NULL };
36 static const gchar *SVN_CMD_REMOVE[] = { "svn", "rm", BASENAME, NULL };
37 static const gchar *SVN_CMD_LOG_FILE[] = { "svn", "log", BASENAME, NULL };
38 static const gchar *SVN_CMD_LOG_DIR[] = { "svn", "log", ABS_DIRNAME, NULL };
39 static const gchar *SVN_CMD_COMMIT[] =
40 	{ "svn", "commit", "--non-interactive", "-m", MESSAGE, FILE_LIST, NULL };
41 static const gchar *SVN_CMD_BLAME[] = { "svn", "blame", BASENAME, NULL };
42 static const gchar *SVN_CMD_SHOW[] = { "svn", "cat", "-rBASE", BASENAME, NULL };
43 static const gchar *SVN_CMD_UPDATE[] = { "svn", "up", NULL };
44 
45 static const VC_COMMAND commands[] = {
46 	{
47 		VC_COMMAND_STARTDIR_BASE,
48 		SVN_CMD_DIFF_FILE,
49 		NULL,
50 		NULL},
51 	{
52 		VC_COMMAND_STARTDIR_BASE,
53 		SVN_CMD_DIFF_DIR,
54 		NULL,
55 		NULL},
56 	{
57 		VC_COMMAND_STARTDIR_FILE,
58 		SVN_CMD_REVERT_FILE,
59 		NULL,
60 		NULL},
61 	{
62 		VC_COMMAND_STARTDIR_BASE,
63 		SVN_CMD_REVERT_DIR,
64 		NULL,
65 		NULL},
66 	{
67 		VC_COMMAND_STARTDIR_FILE,
68 		SVN_CMD_STATUS,
69 		NULL,
70 		NULL},
71 	{
72 		VC_COMMAND_STARTDIR_FILE,
73 		SVN_CMD_ADD,
74 		NULL,
75 		NULL},
76 	{
77 		VC_COMMAND_STARTDIR_FILE,
78 		SVN_CMD_REMOVE,
79 		NULL,
80 		NULL},
81 	{
82 		VC_COMMAND_STARTDIR_FILE,
83 		SVN_CMD_LOG_FILE,
84 		NULL,
85 		NULL},
86 	{
87 		VC_COMMAND_STARTDIR_FILE,
88 		SVN_CMD_LOG_DIR,
89 		NULL,
90 		NULL},
91 	{
92 		VC_COMMAND_STARTDIR_FILE,
93 		SVN_CMD_COMMIT,
94 		NULL,
95 		NULL},
96 	{
97 		VC_COMMAND_STARTDIR_FILE,
98 		SVN_CMD_BLAME,
99 		NULL,
100 		NULL},
101 	{
102 		VC_COMMAND_STARTDIR_FILE,
103 		SVN_CMD_SHOW,
104 		NULL,
105 		NULL},
106 	{
107 		VC_COMMAND_STARTDIR_BASE,
108 		SVN_CMD_UPDATE,
109 		NULL,
110 		NULL}
111 };
112 
113 static gchar *
get_base_dir(const gchar * path)114 get_base_dir(const gchar * path)
115 {
116 	gchar *test_dir;
117 	gchar *base;
118 	gchar *base_prev = NULL;
119 
120 	if (g_file_test(path, G_FILE_TEST_IS_DIR))
121 		base = g_strdup(path);
122 	else
123 		base = g_path_get_dirname(path);
124 
125 	do
126 	{
127 		test_dir = g_build_filename(base, ".svn", NULL);
128 		if (!g_file_test(test_dir, G_FILE_TEST_IS_DIR))
129 		{
130 			g_free(test_dir);
131 			break;
132 		}
133 		g_free(test_dir);
134 		g_free(base_prev);
135 		base_prev = base;
136 		base = g_path_get_dirname(base);
137 
138 		/* check for svn layout */
139 		test_dir = g_build_filename(base, "trunk", NULL);
140 		if (!g_file_test(test_dir, G_FILE_TEST_IS_DIR))
141 		{
142 			g_free(test_dir);
143 			continue;
144 		}
145 		setptr(test_dir, g_build_filename(base, "branches", NULL));
146 		if (!g_file_test(test_dir, G_FILE_TEST_IS_DIR))
147 		{
148 			g_free(test_dir);
149 			continue;
150 		}
151 		setptr(test_dir, g_build_filename(base, "tags", NULL));
152 		if (!g_file_test(test_dir, G_FILE_TEST_IS_DIR))
153 		{
154 			g_free(test_dir);
155 			continue;
156 		}
157 		g_free(test_dir);
158 		break;
159 	}
160 	while (strcmp(base, base_prev) != 0);
161 	if (base_prev == NULL)
162 	{
163 		/* fallback for Subversion 1.7: try to climb up the tree until we
164 		* find a .svn subdirectory */
165 		base_prev = find_subdir_path(path, ".svn");
166 	}
167 	g_free(base);
168 	return base_prev;
169 }
170 
171 static gboolean
in_vc_svn(const gchar * filename)172 in_vc_svn(const gchar * filename)
173 {
174 	const gchar *argv[] = { "svn", "info", "--non-interactive", NULL, NULL };
175 	gchar *dir;
176 	gchar *base_name;
177 	gboolean ret = FALSE;
178 	gchar *std_output;
179 
180 	if (!find_dir(filename, ".svn", TRUE))
181 		return FALSE;
182 
183 	if (g_file_test(filename, G_FILE_TEST_IS_DIR))
184 		return TRUE;
185 
186 	dir = g_path_get_dirname(filename);
187 	base_name = g_path_get_basename(filename);
188 	argv[3] = base_name;
189 
190 	execute_custom_command(dir, (const gchar **) argv, NULL, &std_output, NULL,
191 			       dir, NULL, NULL);
192 	if (!EMPTY(std_output))
193 	{
194 		ret = TRUE;
195 		g_free(std_output);
196 	}
197 	g_free(base_name);
198 	g_free(dir);
199 
200 	return ret;
201 }
202 
203 static GSList *
get_commit_files_svn(const gchar * dir)204 get_commit_files_svn(const gchar * dir)
205 {
206 	enum
207 	{
208 		FIRST_CHAR,
209 		SKIP_SPACE,
210 		FILE_NAME,
211 	};
212 
213 	gchar *txt;
214 	GSList *ret = NULL;
215 	gint pstatus = FIRST_CHAR;
216 	const gchar *p;
217 	gchar *base_name;
218 	const gchar *start = NULL;
219 	CommitItem *item;
220 
221 	const gchar *status = NULL;
222 	gchar *filename;
223 	const char *argv[] = { "svn", "status", NULL };
224 
225 	execute_custom_command(dir, argv, NULL, &txt, NULL, dir, NULL, NULL);
226 	if (EMPTY(txt))
227 		return NULL;
228 	p = txt;
229 
230 	while (*p)
231 	{
232 		if (*p == '\r')
233 		{
234 		}
235 		else if (pstatus == FIRST_CHAR)
236 		{
237 			status = NULL;
238 			if (*p == '?')
239 				status = FILE_STATUS_UNKNOWN;
240 			else if (*p == 'M')
241 				status = FILE_STATUS_MODIFIED;
242 			else if (*p == 'D')
243 				status = FILE_STATUS_DELETED;
244 			else if (*p == 'A')
245 				status = FILE_STATUS_ADDED;
246 
247 			if (!status || *(p + 1) != ' ')
248 			{
249 				/* skip unknown status line */
250 				while (*p)
251 				{
252 					p++;
253 					if (*p == '\n')
254 					{
255 						p++;
256 						break;
257 					}
258 				}
259 				pstatus = FIRST_CHAR;
260 				continue;
261 			}
262 			pstatus = SKIP_SPACE;
263 		}
264 		else if (pstatus == SKIP_SPACE)
265 		{
266 			if (*p == ' ' || *p == '\t')
267 			{
268 			}
269 			else
270 			{
271 				start = p;
272 				pstatus = FILE_NAME;
273 			}
274 		}
275 		else if (pstatus == FILE_NAME)
276 		{
277 			if (*p == '\n')
278 			{
279 				if (status != FILE_STATUS_UNKNOWN)
280 				{
281 					base_name = g_malloc0(p - start + 1);
282 					memcpy(base_name, start, p - start);
283 					filename = g_build_filename(dir, base_name, NULL);
284 					g_free(base_name);
285 					item = g_new(CommitItem, 1);
286 					item->status = status;
287 					item->path = filename;
288 					ret = g_slist_append(ret, item);
289 				}
290 				pstatus = FIRST_CHAR;
291 			}
292 		}
293 		p++;
294 	}
295 	g_free(txt);
296 	return ret;
297 }
298 
299 VC_RECORD VC_SVN = {
300 	commands,
301 	"svn",
302 	get_base_dir,
303 	in_vc_svn,
304 	get_commit_files_svn,
305 };
306