]> git.ipfire.org Git - thirdparty/git.git/blobdiff - refs.c
add reflog when moving HEAD to a new branch
[thirdparty/git.git] / refs.c
diff --git a/refs.c b/refs.c
index d911b9e86009aa3d81c6c4d310a26bf057920e8a..4a523086e37fa0bcdd647c826b9221165d630586 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -1,10 +1,8 @@
-#include "refs.h"
 #include "cache.h"
+#include "refs.h"
 #include "object.h"
 #include "tag.h"
 
-#include <errno.h>
-
 /* ISSYMREF=01 and ISPACKED=02 are public interfaces */
 #define REF_KNOWS_PEELED 04
 
@@ -286,7 +284,7 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
                fd = open(path, O_RDONLY);
                if (fd < 0)
                        return NULL;
-               len = read(fd, buffer, sizeof(buffer)-1);
+               len = read_in_full(fd, buffer, sizeof(buffer)-1);
                close(fd);
 
                /*
@@ -311,49 +309,6 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
        return ref;
 }
 
-int create_symref(const char *ref_target, const char *refs_heads_master)
-{
-       const char *lockpath;
-       char ref[1000];
-       int fd, len, written;
-       const char *git_HEAD = git_path("%s", ref_target);
-
-#ifndef NO_SYMLINK_HEAD
-       if (prefer_symlink_refs) {
-               unlink(git_HEAD);
-               if (!symlink(refs_heads_master, git_HEAD))
-                       return 0;
-               fprintf(stderr, "no symlink - falling back to symbolic ref\n");
-       }
-#endif
-
-       len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master);
-       if (sizeof(ref) <= len) {
-               error("refname too long: %s", refs_heads_master);
-               return -1;
-       }
-       lockpath = mkpath("%s.lock", git_HEAD);
-       fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666); 
-       written = write(fd, ref, len);
-       close(fd);
-       if (written != len) {
-               unlink(lockpath);
-               error("Unable to write to %s", lockpath);
-               return -2;
-       }
-       if (rename(lockpath, git_HEAD) < 0) {
-               unlink(lockpath);
-               error("Unable to create %s", git_HEAD);
-               return -3;
-       }
-       if (adjust_shared_perm(git_HEAD)) {
-               unlink(lockpath);
-               error("Unable to fix permissions on %s", lockpath);
-               return -4;
-       }
-       return 0;
-}
-
 int read_ref(const char *ref, unsigned char *sha1)
 {
        if (resolve_ref(ref, sha1, 1, NULL))
@@ -678,7 +633,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
        lock->lk = xcalloc(1, sizeof(struct lock_file));
 
        lock->ref_name = xstrdup(ref);
-       lock->log_file = xstrdup(git_path("logs/%s", ref));
+       lock->orig_ref_name = xstrdup(orig_ref);
        ref_file = git_path("%s", ref);
        lock->force_write = lstat(ref_file, &st) && errno == ENOENT;
 
@@ -728,7 +683,6 @@ static int repack_without_ref(const char *refname)
        }
        if (!found)
                return 0;
-       memset(&packlock, 0, sizeof(packlock));
        fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
        if (fd < 0)
                return error("cannot delete '%s' from packed refs", refname);
@@ -775,10 +729,10 @@ int delete_ref(const char *refname, unsigned char *sha1)
         */
        ret |= repack_without_ref(refname);
 
-       err = unlink(lock->log_file);
+       err = unlink(git_path("logs/%s", lock->ref_name));
        if (err && errno != ENOENT)
                fprintf(stderr, "warning: unlink(%s) failed: %s",
-                       lock->log_file, strerror(errno));
+                       git_path("logs/%s", lock->ref_name), strerror(errno));
        invalidate_cached_refs();
        unlock_ref(lock);
        return ret;
@@ -840,7 +794,12 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
 
  retry:
        if (log && rename(git_path("tmp-renamed-log"), git_path("logs/%s", newref))) {
-               if (errno==EISDIR) {
+               if (errno==EISDIR || errno==ENOTDIR) {
+                       /*
+                        * rename(a, b) when b is an existing
+                        * directory ought to result in ISDIR, but
+                        * Solaris 5.8 gives ENOTDIR.  Sheesh.
+                        */
                        if (remove_empty_directories(git_path("logs/%s", newref))) {
                                error("Directory not empty: logs/%s", newref);
                                goto rollback;
@@ -914,67 +873,81 @@ void unlock_ref(struct ref_lock *lock)
                        rollback_lock_file(lock->lk);
        }
        free(lock->ref_name);
-       free(lock->log_file);
+       free(lock->orig_ref_name);
        free(lock);
 }
 
-static int log_ref_write(struct ref_lock *lock,
-       const unsigned char *sha1, const char *msg)
+static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
+                        const unsigned char *new_sha1, const char *msg)
 {
        int logfd, written, oflags = O_APPEND | O_WRONLY;
        unsigned maxlen, len;
-       char *logrec;
+       int msglen;
+       char *log_file, *logrec;
        const char *committer;
 
+       if (log_all_ref_updates < 0)
+               log_all_ref_updates = !is_bare_repository();
+
+       log_file = git_path("logs/%s", ref_name);
+
        if (log_all_ref_updates &&
-           !strncmp(lock->ref_name, "refs/heads/", 11)) {
-               if (safe_create_leading_directories(lock->log_file) < 0)
+           (!strncmp(ref_name, "refs/heads/", 11) ||
+            !strncmp(ref_name, "refs/remotes/", 13) ||
+            !strcmp(ref_name, "HEAD"))) {
+               if (safe_create_leading_directories(log_file) < 0)
                        return error("unable to create directory for %s",
-                               lock->log_file);
+                                    log_file);
                oflags |= O_CREAT;
        }
 
-       logfd = open(lock->log_file, oflags, 0666);
+       logfd = open(log_file, oflags, 0666);
        if (logfd < 0) {
                if (!(oflags & O_CREAT) && errno == ENOENT)
                        return 0;
 
                if ((oflags & O_CREAT) && errno == EISDIR) {
-                       if (remove_empty_directories(lock->log_file)) {
+                       if (remove_empty_directories(log_file)) {
                                return error("There are still logs under '%s'",
-                                            lock->log_file);
+                                            log_file);
                        }
-                       logfd = open(lock->log_file, oflags, 0666);
+                       logfd = open(log_file, oflags, 0666);
                }
 
                if (logfd < 0)
                        return error("Unable to append to %s: %s",
-                                    lock->log_file, strerror(errno));
+                                    log_file, strerror(errno));
        }
 
-       committer = git_committer_info(1);
+       msglen = 0;
        if (msg) {
-               maxlen = strlen(committer) + strlen(msg) + 2*40 + 5;
-               logrec = xmalloc(maxlen);
-               len = snprintf(logrec, maxlen, "%s %s %s\t%s\n",
-                       sha1_to_hex(lock->old_sha1),
-                       sha1_to_hex(sha1),
-                       committer,
-                       msg);
-       }
-       else {
-               maxlen = strlen(committer) + 2*40 + 4;
-               logrec = xmalloc(maxlen);
-               len = snprintf(logrec, maxlen, "%s %s %s\n",
-                       sha1_to_hex(lock->old_sha1),
-                       sha1_to_hex(sha1),
-                       committer);
+               /* clean up the message and make sure it is a single line */
+               for ( ; *msg; msg++)
+                       if (!isspace(*msg))
+                               break;
+               if (*msg) {
+                       const char *ep = strchr(msg, '\n');
+                       if (ep)
+                               msglen = ep - msg;
+                       else
+                               msglen = strlen(msg);
+               }
        }
-       written = len <= maxlen ? write(logfd, logrec, len) : -1;
+
+       committer = git_committer_info(-1);
+       maxlen = strlen(committer) + msglen + 100;
+       logrec = xmalloc(maxlen);
+       len = sprintf(logrec, "%s %s %s\n",
+                     sha1_to_hex(old_sha1),
+                     sha1_to_hex(new_sha1),
+                     committer);
+       if (msglen)
+               len += sprintf(logrec + len - 1, "\t%.*s\n", msglen, msg) - 1;
+       written = len <= maxlen ? write_in_full(logfd, logrec, len) : -1;
        free(logrec);
        close(logfd);
        if (written != len)
-               return error("Unable to append to %s", lock->log_file);
+               return error("Unable to append to %s", log_file);
        return 0;
 }
 
@@ -989,15 +962,17 @@ int write_ref_sha1(struct ref_lock *lock,
                unlock_ref(lock);
                return 0;
        }
-       if (write(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 ||
-           write(lock->lock_fd, &term, 1) != 1
+       if (write_in_full(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 ||
+           write_in_full(lock->lock_fd, &term, 1) != 1
                || close(lock->lock_fd) < 0) {
                error("Couldn't write %s", lock->lk->filename);
                unlock_ref(lock);
                return -1;
        }
        invalidate_cached_refs();
-       if (log_ref_write(lock, sha1, logmsg) < 0) {
+       if (log_ref_write(lock->ref_name, lock->old_sha1, sha1, logmsg) < 0 ||
+           (strcmp(lock->ref_name, lock->orig_ref_name) &&
+            log_ref_write(lock->orig_ref_name, lock->old_sha1, sha1, logmsg) < 0)) {
                unlock_ref(lock);
                return -1;
        }
@@ -1011,14 +986,88 @@ int write_ref_sha1(struct ref_lock *lock,
        return 0;
 }
 
-int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1)
+int create_symref(const char *ref_target, const char *refs_heads_master,
+                 const char *logmsg)
+{
+       const char *lockpath;
+       char ref[1000];
+       int fd, len, written;
+       char *git_HEAD = xstrdup(git_path("%s", ref_target));
+       unsigned char old_sha1[20], new_sha1[20];
+
+       if (logmsg && read_ref(ref_target, old_sha1))
+               hashclr(old_sha1);
+
+#ifndef NO_SYMLINK_HEAD
+       if (prefer_symlink_refs) {
+               unlink(git_HEAD);
+               if (!symlink(refs_heads_master, git_HEAD))
+                       goto done;
+               fprintf(stderr, "no symlink - falling back to symbolic ref\n");
+       }
+#endif
+
+       len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master);
+       if (sizeof(ref) <= len) {
+               error("refname too long: %s", refs_heads_master);
+               goto error_free_return;
+       }
+       lockpath = mkpath("%s.lock", git_HEAD);
+       fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666);
+       if (fd < 0) {
+               error("Unable to open %s for writing", lockpath);
+               goto error_free_return;
+       }
+       written = write_in_full(fd, ref, len);
+       close(fd);
+       if (written != len) {
+               error("Unable to write to %s", lockpath);
+               goto error_unlink_return;
+       }
+       if (rename(lockpath, git_HEAD) < 0) {
+               error("Unable to create %s", git_HEAD);
+               goto error_unlink_return;
+       }
+       if (adjust_shared_perm(git_HEAD)) {
+               error("Unable to fix permissions on %s", lockpath);
+       error_unlink_return:
+               unlink(lockpath);
+       error_free_return:
+               free(git_HEAD);
+               return -1;
+       }
+
+       done:
+       if (logmsg && !read_ref(refs_heads_master, new_sha1))
+               log_ref_write(ref_target, old_sha1, new_sha1, logmsg);
+
+       free(git_HEAD);
+       return 0;
+}
+
+static char *ref_msg(const char *line, const char *endp)
+{
+       const char *ep;
+       char *msg;
+
+       line += 82;
+       for (ep = line; ep < endp && *ep != '\n'; ep++)
+               ;
+       msg = xmalloc(ep - line + 1);
+       memcpy(msg, line, ep - line);
+       msg[ep - line] = 0;
+       return msg;
+}
+
+int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt)
 {
        const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec;
        char *tz_c;
-       int logfd, tz;
+       int logfd, tz, reccnt = 0;
        struct stat st;
        unsigned long date;
        unsigned char logged_sha1[20];
+       void *log_mapped;
 
        logfile = git_path("logs/%s", ref);
        logfd = open(logfile, O_RDONLY, 0);
@@ -1027,12 +1076,14 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
        fstat(logfd, &st);
        if (!st.st_size)
                die("Log %s is empty.", logfile);
-       logdata = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, logfd, 0);
+       log_mapped = xmmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, logfd, 0);
+       logdata = log_mapped;
        close(logfd);
 
        lastrec = NULL;
        rec = logend = logdata + st.st_size;
        while (logdata < rec) {
+               reccnt++;
                if (logdata < rec && *(rec-1) == '\n')
                        rec--;
                lastgt = NULL;
@@ -1045,13 +1096,21 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
                        die("Log %s is corrupt.", logfile);
                date = strtoul(lastgt + 1, &tz_c, 10);
                if (date <= at_time || cnt == 0) {
+                       tz = strtoul(tz_c, NULL, 10);
+                       if (msg)
+                               *msg = ref_msg(rec, logend);
+                       if (cutoff_time)
+                               *cutoff_time = date;
+                       if (cutoff_tz)
+                               *cutoff_tz = tz;
+                       if (cutoff_cnt)
+                               *cutoff_cnt = reccnt - 1;
                        if (lastrec) {
                                if (get_sha1_hex(lastrec, logged_sha1))
                                        die("Log %s is corrupt.", logfile);
                                if (get_sha1_hex(rec + 41, sha1))
                                        die("Log %s is corrupt.", logfile);
                                if (hashcmp(logged_sha1, sha1)) {
-                                       tz = strtoul(tz_c, NULL, 10);
                                        fprintf(stderr,
                                                "warning: Log %s has gap after %s.\n",
                                                logfile, show_rfc2822_date(date, tz));
@@ -1065,13 +1124,12 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
                                if (get_sha1_hex(rec + 41, logged_sha1))
                                        die("Log %s is corrupt.", logfile);
                                if (hashcmp(logged_sha1, sha1)) {
-                                       tz = strtoul(tz_c, NULL, 10);
                                        fprintf(stderr,
                                                "warning: Log %s unexpectedly ended on %s.\n",
                                                logfile, show_rfc2822_date(date, tz));
                                }
                        }
-                       munmap((void*)logdata, st.st_size);
+                       munmap(log_mapped, st.st_size);
                        return 0;
                }
                lastrec = rec;
@@ -1088,8 +1146,58 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
        tz = strtoul(tz_c, NULL, 10);
        if (get_sha1_hex(logdata, sha1))
                die("Log %s is corrupt.", logfile);
-       munmap((void*)logdata, st.st_size);
-       fprintf(stderr, "warning: Log %s only goes back to %s.\n",
-               logfile, show_rfc2822_date(date, tz));
-       return 0;
+       if (msg)
+               *msg = ref_msg(logdata, logend);
+       munmap(log_mapped, st.st_size);
+
+       if (cutoff_time)
+               *cutoff_time = date;
+       if (cutoff_tz)
+               *cutoff_tz = tz;
+       if (cutoff_cnt)
+               *cutoff_cnt = reccnt;
+       return 1;
+}
+
+int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
+{
+       const char *logfile;
+       FILE *logfp;
+       char buf[1024];
+       int ret = 0;
+
+       logfile = git_path("logs/%s", ref);
+       logfp = fopen(logfile, "r");
+       if (!logfp)
+               return -1;
+       while (fgets(buf, sizeof(buf), logfp)) {
+               unsigned char osha1[20], nsha1[20];
+               char *email_end, *message;
+               unsigned long timestamp;
+               int len, tz;
+
+               /* old SP new SP name <email> SP time TAB msg LF */
+               len = strlen(buf);
+               if (len < 83 || buf[len-1] != '\n' ||
+                   get_sha1_hex(buf, osha1) || buf[40] != ' ' ||
+                   get_sha1_hex(buf + 41, nsha1) || buf[81] != ' ' ||
+                   !(email_end = strchr(buf + 82, '>')) ||
+                   email_end[1] != ' ' ||
+                   !(timestamp = strtoul(email_end + 2, &message, 10)) ||
+                   !message || message[0] != ' ' ||
+                   (message[1] != '+' && message[1] != '-') ||
+                   !isdigit(message[2]) || !isdigit(message[3]) ||
+                   !isdigit(message[4]) || !isdigit(message[5]) ||
+                   message[6] != '\t')
+                       continue; /* corrupt? */
+               email_end[1] = '\0';
+               tz = strtol(message + 1, NULL, 10);
+               message += 7;
+               ret = fn(osha1, nsha1, buf+82, timestamp, tz, message, cb_data);
+               if (ret)
+                       break;
+       }
+       fclose(logfp);
+       return ret;
 }
+