author | Lars Hjemli <hjemli@gmail.com> | 2006-12-17 22:07:28 (UTC) |
---|---|---|
committer | Lars Hjemli <hjemli@gmail.com> | 2006-12-17 22:07:28 (UTC) |
commit | 6cb326c83b3c0b35d472305294afee3105b3088d (patch) (side-by-side diff) | |
tree | fcceeec36ae6f1b3b8e9f861064e2d6ba962a6e0 | |
parent | 9c5229ea394808f90433ee84439503bee124e1de (diff) | |
download | cgit-6cb326c83b3c0b35d472305294afee3105b3088d.zip cgit-6cb326c83b3c0b35d472305294afee3105b3088d.tar.gz cgit-6cb326c83b3c0b35d472305294afee3105b3088d.tar.bz2 |
Show list of modified files in ui-commit.c
Compare current commit with 1.parent, and for each affected file display
current filemode, old filemode if changed, current filename and source
filename if it was a copy/rename.
Signed-off-by: Lars Hjemli <hjemli@gmail.com>
-rw-r--r-- | cgit.css | 51 | ||||
-rw-r--r-- | cgit.h | 1 | ||||
-rw-r--r-- | git.h | 106 | ||||
-rw-r--r-- | html.c | 20 | ||||
-rw-r--r-- | ui-commit.c | 111 |
5 files changed, 281 insertions, 8 deletions
@@ -1,131 +1,166 @@ body { font-family: arial; font-size: normal; background: white; padding: 0em; - margin: 0.5em; + margin: 0.5em 1em; } h2 { font-size: normal; font-weight: bold; margin-bottom: 0.1em; } a { color: blue; text-decoration: none; } a:hover { text-decoration: underline; } table.list { border: solid 1px black; border-collapse: collapse; border: solid 1px #aaa; } table.list tr { background: white; } table.list tr:hover { background: #eee; } table.list th { font-weight: bold; background: #ddd; border-bottom: solid 1px #aaa; padding: 0.1em 0.5em 0.1em 0.5em; vertical-align: baseline; } table.list td { border: none; padding: 0.1em 0.5em 0.1em 0.5em; } img { border: none; } div#header { - background-color: #ddd; + background-color: #eee; padding: 0.25em 0.25em 0.25em 0.5em; font-size: 150%; font-weight: bold; - border: solid 1px #aaa; + border: solid 1px #ccc; vertical-align: middle; } div#header img#logo { float: right; } +div#header a { + color: black; +} div#content { margin: 0.5em 0.5em; } div#blob { border: solid 1px black; } div.error { color: red; font-weight: bold; margin: 1em 2em; } div.ls-dir a { font-weight: bold; } th.filesize, td.filesize { text-align: right; } th.filemode, td.filemode { text-align: center; } td.blob { white-space: pre; font-family: courier; font-size: 100%; background-color: white; } table.log td { white-space: nowrap; } table.commit-info { border-collapse: collapse; - margin-top: 1em; - + margin-top: 1.5em; } table.commit-info th { text-align: left; font-weight: normal; padding: 0.1em 1em 0.1em 0.1em; } table.commit-info td { font-weight: normal; padding: 0.1em 1em 0.1em 0.1em; } div.commit-subject { font-weight: bold; - font-size: 110%; - margin: 1em 0em 1em; + font-size: 125%; + margin: 1.5em 0em 0.5em 0em; + padding: 0em; } div.commit-msg { white-space: pre; - font-family: courier; + font-family: monospace; +} +table.diffstat { + border-collapse: collapse; + margin-top: 1.5em; +} +table.diffstat th { + font-weight: normal; + text-align: left; + text-decoration: underline; + padding: 0.1em 1em 0.1em 0.1em; font-size: 100%; } +table.diffstat td { + padding: 0.1em 1em 0.1em 0.1em; + font-size: 100%; +} +table.diffstat td span.modechange { + padding-left: 1em; + color: red; +} +table.diffstat td.add a { + color: green; +} +table.diffstat td.del a { + color: red; +} +table.diffstat td.upd a { + color: blue; +} +table.diffstat td.summary { + /* border-top: solid 1px black; */ + color: #888; + padding-top: 0.5em; +} .sha1 { font-family: courier; font-size: 90%; } .left { text-align: left; } .right { text-align: right; } @@ -46,62 +46,63 @@ extern int cgit_cache_static_ttl; extern int cgit_cache_max_create_time; extern char *cgit_repo_name; extern char *cgit_repo_desc; extern char *cgit_repo_owner; extern int cgit_query_has_symref; extern int cgit_query_has_sha1; extern char *cgit_querystring; extern char *cgit_query_repo; extern char *cgit_query_page; extern char *cgit_query_head; extern char *cgit_query_sha1; extern int cgit_query_ofs; extern int htmlfd; extern void cgit_global_config_cb(const char *name, const char *value); extern void cgit_repo_config_cb(const char *name, const char *value); extern void cgit_querystring_cb(const char *name, const char *value); extern void *cgit_free_commitinfo(struct commitinfo *info); extern char *fmt(const char *format,...); extern void html(const char *txt); extern void htmlf(const char *format,...); extern void html_txt(char *txt); extern void html_attr(char *txt); extern void html_link_open(char *url, char *title, char *class); extern void html_link_close(void); +extern void html_filemode(unsigned short mode); extern int cgit_read_config(const char *filename, configfn fn); extern int cgit_parse_query(char *txt, configfn fn); extern struct commitinfo *cgit_parse_commit(struct commit *commit); extern void cache_prepare(struct cacheitem *item); extern int cache_lock(struct cacheitem *item); extern int cache_unlock(struct cacheitem *item); extern int cache_cancel_lock(struct cacheitem *item); extern int cache_exist(struct cacheitem *item); extern int cache_expired(struct cacheitem *item); extern char *cgit_repourl(const char *reponame); extern char *cgit_pageurl(const char *reponame, const char *pagename, const char *query); extern void cgit_print_error(char *msg); extern void cgit_print_date(unsigned long secs); extern void cgit_print_docstart(char *title, struct cacheitem *item); extern void cgit_print_docend(); extern void cgit_print_pageheader(char *title); extern void cgit_print_repolist(struct cacheitem *item); extern void cgit_print_summary(); extern void cgit_print_log(const char *tip, int ofs, int cnt); extern void cgit_print_view(const char *hex); extern void cgit_print_tree(const char *hex); extern void cgit_print_commit(const char *hex); #endif /* CGIT_H */ @@ -99,72 +99,92 @@ static inline ssize_t xread(int fd, void *buf, size_t len) ssize_t nr; while (1) { nr = read(fd, buf, len); if ((nr < 0) && (errno == EAGAIN || errno == EINTR)) continue; return nr; } } static inline ssize_t xwrite(int fd, const void *buf, size_t len) { ssize_t nr; while (1) { nr = write(fd, buf, len); if ((nr < 0) && (errno == EAGAIN || errno == EINTR)) continue; return nr; } } /* * from git:cache.h */ /* Convert to/from hex/sha1 representation */ #define MINIMUM_ABBREV 4 #define DEFAULT_ABBREV 7 +extern const unsigned char null_sha1[20]; + extern int sha1_object_info(const unsigned char *, char *, unsigned long *); extern void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size); extern int get_sha1(const char *str, unsigned char *sha1); extern int get_sha1_hex(const char *hex, unsigned char *sha1); extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ +static inline int is_null_sha1(const unsigned char *sha1) +{ + return !memcmp(sha1, null_sha1, 20); +} +static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2) +{ + return memcmp(sha1, sha2, 20); +} +static inline void hashcpy(unsigned char *sha_dst, const unsigned char *sha_src) +{ + memcpy(sha_dst, sha_src, 20); +} +static inline void hashclr(unsigned char *hash) +{ + memset(hash, 0, 20); +} + + /* * from git:object.h */ struct object_list { struct object *item; struct object_list *next; }; struct object_refs { unsigned count; struct object *base; struct object *ref[FLEX_ARRAY]; /* more */ }; struct object_array { unsigned int nr; unsigned int alloc; struct object_array_entry { struct object *item; const char *name; } *objects; }; #define TYPE_BITS 3 #define FLAG_BITS 27 /* * The object type is stored in 3 bits. */ @@ -228,64 +248,119 @@ struct commit *lookup_commit_reference_gently(const unsigned char *sha1, int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size); int parse_commit(struct commit *item); struct commit_list * commit_list_insert(struct commit *item, struct commit_list **list_p); struct commit_list * insert_by_date(struct commit *item, struct commit_list **list); void free_commit_list(struct commit_list *list); void sort_by_date(struct commit_list **list); /* Commit formats */ enum cmit_fmt { CMIT_FMT_RAW, CMIT_FMT_MEDIUM, CMIT_FMT_DEFAULT = CMIT_FMT_MEDIUM, CMIT_FMT_SHORT, CMIT_FMT_FULL, CMIT_FMT_FULLER, CMIT_FMT_ONELINE, CMIT_FMT_EMAIL, CMIT_FMT_UNSPECIFIED, }; extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject, int relative_date); typedef void (*topo_sort_set_fn_t)(struct commit*, void *data); typedef void* (*topo_sort_get_fn_t)(struct commit*); +/* + * from git:diffcore.h + */ + +struct diff_filespec { + unsigned char sha1[20]; + char *path; + void *data; + void *cnt_data; + unsigned long size; + int xfrm_flags; /* for use by the xfrm */ + unsigned short mode; /* file mode */ + unsigned sha1_valid : 1; /* if true, use sha1 and trust mode; + * if false, use the name and read from + * the filesystem. + */ +#define DIFF_FILE_VALID(spec) (((spec)->mode) != 0) + unsigned should_free : 1; /* data should be free()'ed */ + unsigned should_munmap : 1; /* data should be munmap()'ed */ +}; + +struct diff_filepair { + struct diff_filespec *one; + struct diff_filespec *two; + unsigned short int score; + char status; /* M C R N D U (see Documentation/diff-format.txt) */ + unsigned source_stays : 1; /* all of R/C are copies */ + unsigned broken_pair : 1; + unsigned renamed_pair : 1; +}; + +#define DIFF_PAIR_UNMERGED(p) \ + (!DIFF_FILE_VALID((p)->one) && !DIFF_FILE_VALID((p)->two)) + +#define DIFF_PAIR_RENAME(p) ((p)->renamed_pair) + +#define DIFF_PAIR_BROKEN(p) \ + ( (!DIFF_FILE_VALID((p)->one) != !DIFF_FILE_VALID((p)->two)) && \ + ((p)->broken_pair != 0) ) + +#define DIFF_PAIR_TYPE_CHANGED(p) \ + ((S_IFMT & (p)->one->mode) != (S_IFMT & (p)->two->mode)) + +#define DIFF_PAIR_MODE_CHANGED(p) ((p)->one->mode != (p)->two->mode) + +extern void diff_free_filepair(struct diff_filepair *); + +extern int diff_unmodified_pair(struct diff_filepair *); + +struct diff_queue_struct { + struct diff_filepair **queue; + int alloc; + int nr; +}; + /* * from git:diff.h */ struct rev_info; struct diff_options; struct diff_queue_struct; typedef void (*change_fn_t)(struct diff_options *options, unsigned old_mode, unsigned new_mode, const unsigned char *old_sha1, const unsigned char *new_sha1, const char *base, const char *path); typedef void (*add_remove_fn_t)(struct diff_options *options, int addremove, unsigned mode, const unsigned char *sha1, const char *base, const char *path); typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, struct diff_options *options, void *data); #define DIFF_FORMAT_RAW 0x0001 #define DIFF_FORMAT_DIFFSTAT 0x0002 #define DIFF_FORMAT_NUMSTAT 0x0004 #define DIFF_FORMAT_SUMMARY 0x0008 #define DIFF_FORMAT_PATCH 0x0010 /* These override all above */ #define DIFF_FORMAT_NAME 0x0100 @@ -323,64 +398,90 @@ struct diff_options { int reverse_diff; int rename_limit; int setup; int abbrev; const char *msg_sep; const char *stat_sep; long xdl_opts; int stat_width; int stat_name_width; int nr_paths; const char **paths; int *pathlens; change_fn_t change; add_remove_fn_t add_remove; diff_format_fn_t format_callback; void *format_callback_data; }; enum color_diff { DIFF_RESET = 0, DIFF_PLAIN = 1, DIFF_METAINFO = 2, DIFF_FRAGINFO = 3, DIFF_FILE_OLD = 4, DIFF_FILE_NEW = 5, DIFF_COMMIT = 6, DIFF_WHITESPACE = 7, }; +extern int diff_tree_sha1(const unsigned char *old, const unsigned char *new, + const char *base, struct diff_options *opt); + +extern int diff_root_tree_sha1(const unsigned char *new, const char *base, + struct diff_options *opt); + +extern int git_diff_ui_config(const char *var, const char *value); +extern void diff_setup(struct diff_options *); +extern int diff_opt_parse(struct diff_options *, const char **, int); +extern int diff_setup_done(struct diff_options *); + + +extern void diffcore_std(struct diff_options *); +extern void diff_flush(struct diff_options*); + + +/* diff-raw status letters */ +#define DIFF_STATUS_ADDED 'A' +#define DIFF_STATUS_COPIED 'C' +#define DIFF_STATUS_DELETED 'D' +#define DIFF_STATUS_MODIFIED 'M' +#define DIFF_STATUS_RENAMED 'R' +#define DIFF_STATUS_TYPE_CHANGED 'T' +#define DIFF_STATUS_UNKNOWN 'X' +#define DIFF_STATUS_UNMERGED 'U' + /* * from git:refs.g */ typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data); extern int head_ref(each_ref_fn, void *); extern int for_each_ref(each_ref_fn, void *); extern int for_each_tag_ref(each_ref_fn, void *); extern int for_each_branch_ref(each_ref_fn, void *); extern int for_each_remote_ref(each_ref_fn, void *); /* * from git:revision.h */ struct rev_info; struct log_info; typedef void (prune_fn_t)(struct rev_info *revs, struct commit *commit); struct rev_info { /* Starting list */ struct commit_list *commits; struct object_array pending; /* Basic information */ const char *prefix; void *prune_data; @@ -429,34 +530,39 @@ struct rev_info { const char *mime_boundary; const char *message_id; const char *ref_message_id; const char *add_signoff; const char *extra_headers; /* Filter by commit log message */ struct grep_opt *grep_filter; /* special limits */ int max_count; unsigned long max_age; unsigned long min_age; /* diff info for patches and for paths limiting */ struct diff_options diffopt; struct diff_options pruning; topo_sort_set_fn_t topo_setter; topo_sort_get_fn_t topo_getter; }; extern void init_revisions(struct rev_info *revs, const char *prefix); extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def); extern int handle_revision_arg(const char *arg, struct rev_info *revs,int flags,int cant_be_filename); extern void prepare_revision_walk(struct rev_info *revs); extern struct commit *get_revision(struct rev_info *revs); +/* from git:log-tree.h */ + +int log_tree_commit(struct rev_info *, struct commit *); + + #endif /* GIT_H */ @@ -79,32 +79,52 @@ void html_attr(char *txt) html(">"); else if (c=='<') html("<"); else if (c=='\'') html(""e;"); txt = t+1; } t++; } if (t!=txt) html(txt); } void html_link_open(char *url, char *title, char *class) { html("<a href='"); html_attr(url); if (title) { html("' title='"); html_attr(title); } if (class) { html("' class='"); html_attr(class); } html("'>"); } void html_link_close(void) { html("</a>"); } + +void html_fileperm(unsigned short mode) +{ + htmlf("%c%c%c", (mode & 4 ? 'r' : '-'), + (mode & 2 ? 'w' : '-'), (mode & 1 ? 'x' : '-')); +} + +void html_filemode(unsigned short mode) +{ + if (S_ISDIR(mode)) + html("d"); + else if (S_ISLNK(mode)) + html("l"); + else + html("-"); + html_fileperm(mode >> 6); + html_fileperm(mode >> 3); + html_fileperm(mode); +} + diff --git a/ui-commit.c b/ui-commit.c index 8916212..c5ee8e7 100644 --- a/ui-commit.c +++ b/ui-commit.c @@ -1,66 +1,177 @@ /* ui-commit.c: generate commit view * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" +int files = 0; + +void print_filepair(struct diff_filepair *pair) +{ + char *query; + char *class; + + switch (pair->status) { + case DIFF_STATUS_ADDED: + class = "add"; + break; + case DIFF_STATUS_COPIED: + class = "cpy"; + break; + case DIFF_STATUS_DELETED: + class = "del"; + break; + case DIFF_STATUS_MODIFIED: + class = "upd"; + break; + case DIFF_STATUS_RENAMED: + class = "mov"; + break; + case DIFF_STATUS_TYPE_CHANGED: + class = "typ"; + break; + case DIFF_STATUS_UNKNOWN: + class = "unk"; + break; + case DIFF_STATUS_UNMERGED: + class = "stg"; + break; + default: + die("bug: unhandled diff status %c", pair->status); + } + + html("<tr>"); + htmlf("<td class='mode'>"); + html_filemode(pair->two->mode); + if (pair->one->mode != pair->two->mode) { + html("<span class='modechange'>["); + html_filemode(pair->one->mode); + html("]</span>"); + } + htmlf("</td><td class='%s'>", class); + query = fmt("id=%s", sha1_to_hex(pair->two->sha1)); + html_link_open(cgit_pageurl(cgit_query_repo, "view", query), + NULL, NULL); + if (pair->status == DIFF_STATUS_COPIED || + pair->status == DIFF_STATUS_RENAMED) { + html_txt(pair->two->path); + htmlf("</a> (%s from ", pair->status == DIFF_STATUS_COPIED ? + "copied" : "renamed"); + query = fmt("id=%s", sha1_to_hex(pair->one->sha1)); + html_link_open(cgit_pageurl(cgit_query_repo, "view", query), + NULL, NULL); + html_txt(pair->one->path); + html("</a>)"); + } else { + html_txt(pair->two->path); + html("</a>"); + } + html("<td>"); + + //TODO: diffstat graph + + html("</td></tr>\n"); + files++; +} + +void diff_format_cb(struct diff_queue_struct *q, + struct diff_options *options, void *data) +{ + int i; + + for (i = 0; i < q->nr; i++) { + if (q->queue[i]->status == 'U') + continue; + print_filepair(q->queue[i]); + } +} + +void cgit_diffstat(struct commit *commit) +{ + struct diff_options opt; + int ret; + + diff_setup(&opt); + opt.output_format = DIFF_FORMAT_CALLBACK; + opt.detect_rename = 1; + opt.recursive = 1; + opt.format_callback = diff_format_cb; + diff_setup_done(&opt); + + if (commit->parents) + ret = diff_tree_sha1(commit->parents->item->object.sha1, + commit->object.sha1, + "", &opt); + else + ret = diff_root_tree_sha1(commit->object.sha1, "", &opt); + + diffcore_std(&opt); + diff_flush(&opt); +} + void cgit_print_commit(const char *hex) { struct commit *commit; struct commitinfo *info; struct commit_list *p; unsigned char sha1[20]; char *query; if (get_sha1(hex, sha1)) { cgit_print_error(fmt("Bad object id: %s", hex)); return; } commit = lookup_commit_reference(sha1); if (!commit) { cgit_print_error(fmt("Bad commit reference: %s", hex)); return; } info = cgit_parse_commit(commit); html("<table class='commit-info'>\n"); html("<tr><th>author</th><td>"); html_txt(info->author); html(" "); html_txt(info->author_email); html("</td><td class='right'>"); cgit_print_date(info->author_date); html("</td></tr>\n"); html("<tr><th>committer</th><td>"); html_txt(info->committer); html(" "); html_txt(info->committer_email); html("</td><td class='right'>"); cgit_print_date(info->committer_date); html("</td></tr>\n"); html("<tr><th>tree</th><td colspan='2' class='sha1'><a href='"); query = fmt("id=%s", sha1_to_hex(commit->tree->object.sha1)); html_attr(cgit_pageurl(cgit_query_repo, "tree", query)); htmlf("'>%s</a></td></tr>\n", sha1_to_hex(commit->tree->object.sha1)); for (p = commit->parents; p ; p = p->next) { html("<tr><th>parent</th>" "<td colspan='2' class='sha1'>" "<a href='"); query = fmt("id=%s", sha1_to_hex(p->item->object.sha1)); html_attr(cgit_pageurl(cgit_query_repo, "commit", query)); htmlf("'>%s</a></td></tr>\n", sha1_to_hex(p->item->object.sha1)); } html("</table>\n"); html("<div class='commit-subject'>"); html_txt(info->subject); html("</div>"); html("<div class='commit-msg'>"); html_txt(info->msg); html("</div>"); + html("<table class='diffstat'>"); + html("<tr><th colspan='3'>Affected files</tr>\n"); + cgit_diffstat(commit); + htmlf("<tr><td colspan='3' class='summary'>" + "%d file%s changed</td></tr>\n", files, files > 1 ? "s" : ""); + html("</table>"); cgit_free_commitinfo(info); } |