diff options
Diffstat (limited to '')
| -rw-r--r-- | cgit.c | 4 | ||||
| -rw-r--r-- | cgit.h | 2 | ||||
| -rw-r--r-- | cgitrc.5.txt | 4 | ||||
| -rw-r--r-- | ui-diff.c | 35 | ||||
| -rw-r--r-- | ui-log.c | 131 | ||||
| -rw-r--r-- | ui-refs.c | 2 | ||||
| -rw-r--r-- | ui-repolist.c | 2 | ||||
| -rw-r--r-- | ui-shared.c | 28 | ||||
| -rw-r--r-- | ui-shared.h | 2 | ||||
| -rw-r--r-- | ui-tree.c | 2 | 
10 files changed, 194 insertions, 18 deletions
| @@ -152,6 +152,8 @@ static void config_cb(const char *name, const char *value)  		ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);  	else if (!strcmp(name, "enable-filter-overrides"))  		ctx.cfg.enable_filter_overrides = atoi(value); +	else if (!strcmp(name, "enable-follow-links")) +		ctx.cfg.enable_follow_links = atoi(value);  	else if (!strcmp(name, "enable-http-clone"))  		ctx.cfg.enable_http_clone = atoi(value);  	else if (!strcmp(name, "enable-index-links")) @@ -333,6 +335,8 @@ static void querystring_cb(const char *name, const char *value)  		ctx.qry.context = atoi(value);  	} else if (!strcmp(name, "ignorews")) {  		ctx.qry.ignorews = atoi(value); +	} else if (!strcmp(name, "follow")) { +		ctx.qry.follow = atoi(value);  	}  } @@ -179,6 +179,7 @@ struct cgit_query {  	int show_all;  	int context;  	int ignorews; +	int follow;  	char *vpath;  }; @@ -221,6 +222,7 @@ struct cgit_config {  	int case_sensitive_sort;  	int embedded;  	int enable_filter_overrides; +	int enable_follow_links;  	int enable_http_clone;  	int enable_index_links;  	int enable_index_owner; diff --git a/cgitrc.5.txt b/cgitrc.5.txt index e21ece9..759f353 100644 --- a/cgitrc.5.txt +++ b/cgitrc.5.txt @@ -150,6 +150,10 @@ enable-filter-overrides::  	Flag which, when set to "1", allows all filter settings to be  	overridden in repository-specific cgitrc files. Default value: none. +enable-follow-links:: +	Flag which, when set to "1", allows users to follow a file in the log +	view.  Default value: "0". +  enable-http-clone::  	If set to "1", cgit will act as an dumb HTTP endpoint for git clones.  	You can add "http://$HTTP_HOST$SCRIPT_NAME/$CGIT_REPO_URL" to clone-url @@ -36,6 +36,7 @@ static struct fileinfo {  static int use_ssdiff = 0;  static struct diff_filepair *current_filepair; +static const char *current_prefix;  struct diff_filespec *cgit_get_current_old_file(void)  { @@ -132,11 +133,30 @@ static void count_diff_lines(char *line, int len)  	}  } +static int show_filepair(struct diff_filepair *pair) +{ +	/* Always show if we have no limiting prefix. */ +	if (!current_prefix) +		return 1; + +	/* Show if either path in the pair begins with the prefix. */ +	if (starts_with(pair->one->path, current_prefix) || +	    starts_with(pair->two->path, current_prefix)) +		return 1; + +	/* Otherwise we don't want to show this filepair. */ +	return 0; +} +  static void inspect_filepair(struct diff_filepair *pair)  {  	int binary = 0;  	unsigned long old_size = 0;  	unsigned long new_size = 0; + +	if (!show_filepair(pair)) +		return; +  	files++;  	lines_added = 0;  	lines_removed = 0; @@ -279,6 +299,9 @@ static void filepair_cb(struct diff_filepair *pair)  	int binary = 0;  	linediff_fn print_line_fn = print_line; +	if (!show_filepair(pair)) +		return; +  	current_filepair = pair;  	if (use_ssdiff) {  		cgit_ssdiff_header_begin(); @@ -365,6 +388,18 @@ void cgit_print_diff(const char *new_rev, const char *old_rev,  	const unsigned char *old_tree_sha1, *new_tree_sha1;  	diff_type difftype; +	/* +	 * If "follow" is set then the diff machinery needs to examine the +	 * entire commit to detect renames so we must limit the paths in our +	 * own callbacks and not pass the prefix to the diff machinery. +	 */ +	if (ctx.qry.follow && ctx.cfg.enable_follow_links) { +		current_prefix = prefix; +		prefix = ""; +	} else { +		current_prefix = NULL; +	} +  	if (!new_rev)  		new_rev = ctx.qry.head;  	if (get_sha1(new_rev, new_rev_sha1)) { @@ -12,7 +12,7 @@  #include "ui-shared.h"  #include "argv-array.h" -static int files, add_lines, rem_lines; +static int files, add_lines, rem_lines, lines_counted;  /*   * The list of available column colors in the commit graph. @@ -67,7 +67,7 @@ void show_commit_decorations(struct commit *commit)  			strncpy(buf, deco->name + 11, sizeof(buf) - 1);  			cgit_log_link(buf, NULL, "branch-deco", buf, NULL,  				      ctx.qry.vpath, 0, NULL, NULL, -				      ctx.qry.showmsg); +				      ctx.qry.showmsg, 0);  		}  		else if (starts_with(deco->name, "tag: refs/tags/")) {  			strncpy(buf, deco->name + 15, sizeof(buf) - 1); @@ -84,7 +84,7 @@ void show_commit_decorations(struct commit *commit)  			cgit_log_link(buf, NULL, "remote-deco", NULL,  				      sha1_to_hex(commit->object.sha1),  				      ctx.qry.vpath, 0, NULL, NULL, -				      ctx.qry.showmsg); +				      ctx.qry.showmsg, 0);  		}  		else {  			strncpy(buf, deco->name, sizeof(buf) - 1); @@ -98,6 +98,74 @@ next:  	html("</span>");  } +static void handle_rename(struct diff_filepair *pair) +{ +	/* +	 * After we have seen a rename, we generate links to the previous +	 * name of the file so that commit & diff views get fed the path +	 * that is correct for the commit they are showing, avoiding the +	 * need to walk the entire history leading back to every commit we +	 * show in order detect renames. +	 */ +	if (0 != strcmp(ctx.qry.vpath, pair->two->path)) { +		free(ctx.qry.vpath); +		ctx.qry.vpath = xstrdup(pair->two->path); +	} +	inspect_files(pair); +} + +static int show_commit(struct commit *commit, struct rev_info *revs) +{ +	struct commit_list *parents = commit->parents; +	struct commit *parent; +	int found = 0, saved_fmt; +	unsigned saved_flags = revs->diffopt.flags; + + +	/* Always show if we're not in "follow" mode with a single file. */ +	if (!ctx.qry.follow) +		return 1; + +	/* +	 * In "follow" mode, we don't show merges.  This is consistent with +	 * "git log --follow -- <file>". +	 */ +	if (parents && parents->next) +		return 0; + +	/* +	 * If this is the root commit, do what rev_info tells us. +	 */ +	if (!parents) +		return revs->show_root_diff; + +	/* When we get here we have precisely one parent. */ +	parent = parents->item; +	parse_commit(parent); + +	files = 0; +	add_lines = 0; +	rem_lines = 0; + +	DIFF_OPT_SET(&revs->diffopt, RECURSIVE); +	diff_tree_sha1(parent->tree->object.sha1, +		       commit->tree->object.sha1, +		       "", &revs->diffopt); +	diffcore_std(&revs->diffopt); + +	found = !diff_queue_is_empty(); +	saved_fmt = revs->diffopt.output_format; +	revs->diffopt.output_format = DIFF_FORMAT_CALLBACK; +	revs->diffopt.format_callback = cgit_diff_tree_cb; +	revs->diffopt.format_callback_data = handle_rename; +	diff_flush(&revs->diffopt); +	revs->diffopt.output_format = saved_fmt; +	revs->diffopt.flags = saved_flags; + +	lines_counted = 1; +	return found; +} +  static void print_commit(struct commit *commit, struct rev_info *revs)  {  	struct commitinfo *info; @@ -177,7 +245,8 @@ static void print_commit(struct commit *commit, struct rev_info *revs)  		cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);  	} -	if (ctx.repo->enable_log_filecount || ctx.repo->enable_log_linecount) { +	if (!lines_counted && (ctx.repo->enable_log_filecount || +			       ctx.repo->enable_log_linecount)) {  		files = 0;  		add_lines = 0;  		rem_lines = 0; @@ -325,7 +394,17 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern  			}  		}  	} -	if (commit_graph) { + +	if (!path || !ctx.cfg.enable_follow_links) { +		/* +		 * If we don't have a path, "follow" is a no-op so make sure +		 * the variable is set to false to avoid needing to check +		 * both this and whether we have a path everywhere. +		 */ +		ctx.qry.follow = 0; +	} + +	if (commit_graph && !ctx.qry.follow) {  		argv_array_push(&rev_argv, "--graph");  		argv_array_push(&rev_argv, "--color");  		graph_set_column_colors(column_colors_html, @@ -337,6 +416,8 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern  	else if (commit_sort == 2)  		argv_array_push(&rev_argv, "--topo-order"); +	if (path && ctx.qry.follow) +		argv_array_push(&rev_argv, "--follow");  	argv_array_push(&rev_argv, "--");  	if (path)  		argv_array_push(&rev_argv, path); @@ -347,10 +428,17 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern  	rev.verbose_header = 1;  	rev.show_root_diff = 0;  	rev.ignore_missing = 1; +	rev.simplify_history = 1;  	setup_revisions(rev_argv.argc, rev_argv.argv, &rev, NULL);  	load_ref_decorations(DECORATE_FULL_REFS);  	rev.show_decorations = 1;  	rev.grep_filter.regflags |= REG_ICASE; + +	rev.diffopt.detect_rename = 1; +	rev.diffopt.rename_limit = ctx.cfg.renamelimit; +	if (ctx.qry.ignorews) +		DIFF_XDL_SET(&rev.diffopt, IGNORE_WHITESPACE); +  	compile_grep_patterns(&rev.grep_filter);  	prepare_revision_walk(&rev); @@ -368,11 +456,12 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern  		cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,  			      NULL, ctx.qry.head, ctx.qry.sha1,  			      ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep, -			      ctx.qry.search, ctx.qry.showmsg ? 0 : 1); +			      ctx.qry.search, ctx.qry.showmsg ? 0 : 1, +			      ctx.qry.follow);  		html(")");  	}  	html("</th><th class='left'>Author</th>"); -	if (commit_graph) +	if (rev.graph)  		html("<th class='left'>Age</th>");  	if (ctx.repo->enable_log_filecount) {  		html("<th class='left'>Files</th>"); @@ -388,13 +477,30 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern  		ofs = 0;  	for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) { +		if (show_commit(commit, &rev)) +			i++;  		free_commit_buffer(commit);  		free_commit_list(commit->parents);  		commit->parents = NULL;  	}  	for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { -		print_commit(commit, &rev); +		/* +		 * In "follow" mode, we must count the files and lines the +		 * first time we invoke diff on a given commit, and we need +		 * to do that to see if the commit touches the path we care +		 * about, so we do it in show_commit.  Hence we must clear +		 * lines_counted here. +		 * +		 * This has the side effect of avoiding running diff twice +		 * when we are both following renames and showing file +		 * and/or line counts. +		 */ +		lines_counted = 0; +		if (show_commit(commit, &rev)) { +			i++; +			print_commit(commit, &rev); +		}  		free_commit_buffer(commit);  		free_commit_list(commit->parents);  		commit->parents = NULL; @@ -406,7 +512,8 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern  			cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,  				      ctx.qry.sha1, ctx.qry.vpath,  				      ofs - cnt, ctx.qry.grep, -				      ctx.qry.search, ctx.qry.showmsg); +				      ctx.qry.search, ctx.qry.showmsg, +				      ctx.qry.follow);  			html("</li>");  		}  		if ((commit = get_revision(&rev)) != NULL) { @@ -414,14 +521,16 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern  			cgit_log_link("[next]", NULL, NULL, ctx.qry.head,  				      ctx.qry.sha1, ctx.qry.vpath,  				      ofs + cnt, ctx.qry.grep, -				      ctx.qry.search, ctx.qry.showmsg); +				      ctx.qry.search, ctx.qry.showmsg, +				      ctx.qry.follow);  			html("</li>");  		}  		html("</ul>");  	} else if ((commit = get_revision(&rev)) != NULL) {  		htmlf("<tr class='nohover'><td colspan='%d'>", columns);  		cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, -			      ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg); +			      ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg, +			      ctx.qry.follow);  		html("</td></tr>\n");  	} @@ -63,7 +63,7 @@ static int print_branch(struct refinfo *ref)  		return 1;  	html("<tr><td>");  	cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, -		      ctx.qry.showmsg); +		      ctx.qry.showmsg, 0);  	html("</td><td>");  	if (ref->object->type == OBJ_COMMIT) { diff --git a/ui-repolist.c b/ui-repolist.c index 2453a7f..edefc4c 100644 --- a/ui-repolist.c +++ b/ui-repolist.c @@ -330,7 +330,7 @@ void cgit_print_repolist(void)  			html("<td>");  			cgit_summary_link("summary", NULL, "button", NULL);  			cgit_log_link("log", NULL, "button", NULL, NULL, NULL, -				      0, NULL, NULL, ctx.qry.showmsg); +				      0, NULL, NULL, ctx.qry.showmsg, 0);  			cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL);  			html("</td>");  		} diff --git a/ui-shared.c b/ui-shared.c index 4f84b7c..6be0c2e 100644 --- a/ui-shared.c +++ b/ui-shared.c @@ -303,7 +303,8 @@ void cgit_plain_link(const char *name, const char *title, const char *class,  void cgit_log_link(const char *name, const char *title, const char *class,  		   const char *head, const char *rev, const char *path, -		   int ofs, const char *grep, const char *pattern, int showmsg) +		   int ofs, const char *grep, const char *pattern, int showmsg, +		   int follow)  {  	char *delim; @@ -332,6 +333,11 @@ void cgit_log_link(const char *name, const char *title, const char *class,  	if (showmsg) {  		html(delim);  		html("showmsg=1"); +		delim = "&"; +	} +	if (follow) { +		html(delim); +		html("follow=1");  	}  	html("'>");  	html_txt(name); @@ -373,6 +379,10 @@ void cgit_commit_link(char *name, const char *title, const char *class,  		html("ignorews=1");  		delim = "&";  	} +	if (ctx.qry.follow) { +		html(delim); +		html("follow=1"); +	}  	html("'>");  	if (name[0] != '\0')  		html_txt(name); @@ -429,6 +439,10 @@ void cgit_diff_link(const char *name, const char *title, const char *class,  		html("ignorews=1");  		delim = "&";  	} +	if (ctx.qry.follow) { +		html(delim); +		html("follow=1"); +	}  	html("'>");  	html_txt(name);  	html("</a>"); @@ -469,7 +483,7 @@ static void cgit_self_link(char *name, const char *title, const char *class)  			      ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL,  			      ctx.qry.path, ctx.qry.ofs,  			      ctx.qry.grep, ctx.qry.search, -			      ctx.qry.showmsg); +			      ctx.qry.showmsg, ctx.qry.follow);  	else if (!strcmp(ctx.qry.page, "commit"))  		cgit_commit_link(name, title, class, ctx.qry.head,  				 ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, @@ -945,7 +959,7 @@ void cgit_print_pageheader(void)  			       ctx.qry.sha1, NULL);  		cgit_log_link("log", NULL, hc("log"), ctx.qry.head,  			      NULL, ctx.qry.vpath, 0, NULL, NULL, -			      ctx.qry.showmsg); +			      ctx.qry.showmsg, ctx.qry.follow);  		cgit_tree_link("tree", NULL, hc("tree"), ctx.qry.head,  			       ctx.qry.sha1, ctx.qry.vpath);  		cgit_commit_link("commit", NULL, hc("commit"), @@ -993,6 +1007,14 @@ void cgit_print_pageheader(void)  		html("<div class='path'>");  		html("path: ");  		cgit_print_path_crumbs(ctx.qry.vpath); +		if (ctx.cfg.enable_follow_links && !strcmp(ctx.qry.page, "log")) { +			html(" ("); +			ctx.qry.follow = !ctx.qry.follow; +			cgit_self_link(ctx.qry.follow ? "follow" : "unfollow", +					NULL, NULL); +			ctx.qry.follow = !ctx.qry.follow; +			html(")"); +		}  		html("</div>");  	}  	html("<div class='content'>"); diff --git a/ui-shared.h b/ui-shared.h index 43d0fa6..788b1bc 100644 --- a/ui-shared.h +++ b/ui-shared.h @@ -31,7 +31,7 @@ extern void cgit_plain_link(const char *name, const char *title,  extern void cgit_log_link(const char *name, const char *title,  			  const char *class, const char *head, const char *rev,  			  const char *path, int ofs, const char *grep, -			  const char *pattern, int showmsg); +			  const char *pattern, int showmsg, int follow);  extern void cgit_commit_link(char *name, const char *title,  			     const char *class, const char *head,  			     const char *rev, const char *path); @@ -166,7 +166,7 @@ static int ls_item(const unsigned char *sha1, struct strbuf *base,  	html("<td>");  	cgit_log_link("log", NULL, "button", ctx.qry.head,  		      walk_tree_ctx->curr_rev, fullpath.buf, 0, NULL, NULL, -		      ctx.qry.showmsg); +		      ctx.qry.showmsg, 0);  	if (ctx.repo->max_stats)  		cgit_stats_link("stats", NULL, "button", ctx.qry.head,  				fullpath.buf); | 
