Merge devel.

This commit is contained in:
Marius Vollmer 2008-08-17 02:44:29 +03:00
commit e067f18a2e
3 changed files with 258 additions and 198 deletions

6
NEWS
View file

@ -1,4 +1,10 @@
Changes in magit 0.6:
* Key bindings and command behavior for diffing, history listing, and
resetting have changed. Please see the manual.
* All buffers created by Magit are now in magit-mode and share all key
bindings.
* Bug fix: avoid looking-at-p so that magit.el works on Emacs 22 as
well.

328
magit.el
View file

@ -34,6 +34,8 @@
;;; TODO
;; - Tags
;; - Unified keymaps for status and history, etc
;; - Equivalent of interactive rebase
;; - 'Subsetting', only looking at a subset of all files.
;; - Detect and handle renames and copies.
@ -117,6 +119,71 @@
(put-text-property (line-beginning-position) (line-end-position)
prop val))
;;; Revisions and ranges
(defun magit-list-interesting-revisions ()
(magit-shell-lines "git branch -a | cut -c3-"))
(defun magit-read-rev (prompt &optional def)
(let* ((prompt (if def
(format "%s (default %s): " prompt def)
(format "%s: " prompt)))
(rev (completing-read prompt (magit-list-interesting-revisions)
nil nil nil nil def)))
(if (string= rev "")
nil
rev)))
(defun magit-read-rev-range (op &optional def-beg def-end)
(if current-prefix-arg
(read-string (format "%s range: " op))
(let ((beg (magit-read-rev (format "%s start" op)
def-beg)))
(if (not beg)
nil
(let ((end (magit-read-rev (format "%s end" op) def-end)))
(cons beg end))))))
(defun magit-rev-to-git (rev)
(or rev
(error "No revision specified"))
(if (string= rev ".")
(or (magit-marked-object)
(error "Commit mark not set"))
rev))
(defun magit-rev-range-to-git (range)
(or range
(error "No revision range specified"))
(if (stringp range)
range
(if (cdr range)
(format "%s..%s"
(magit-rev-to-git (car range))
(magit-rev-to-git (cdr range)))
(format "%s" (magit-rev-to-git (car range))))))
(defun magit-rev-describe (rev)
(or rev
(error "No revision specified"))
(if (string= rev ".")
"mark"
(magit-name-rev rev)))
(defun magit-rev-range-describe (range things)
(or range
(error "No revision range specified"))
(if (stringp range)
(format "%s in %s" things range)
(if (cdr range)
(format "%s from %s to %s" things
(magit-rev-describe (car range))
(magit-rev-describe (cdr range)))
(format "%s at %s" things (magit-rev-describe (car range))))))
(defun magit-default-rev ()
(magit-commit-at-point t))
;;; Sections
(defun magit-insert-section (section title washer cmd &rest args)
@ -237,10 +304,14 @@
(define-key map (kbd "?") 'magit-describe-thing-at-point)
(define-key map (kbd ".") 'magit-mark-thing-at-point)
(define-key map (kbd "=") 'magit-diff-with-mark)
(define-key map (kbd "x") 'magit-reset-soft)
(define-key map (kbd "X") 'magit-reset-hard)
(define-key map (kbd "l") 'magit-log-head)
(define-key map (kbd "L") 'magit-log)
(define-key map (kbd "d") 'magit-diff-working-tree)
(define-key map (kbd "D") 'magit-diff)
(define-key map (kbd "x") 'magit-reset-head)
(define-key map (kbd "X") 'magit-reset-working-tree)
(define-key map (kbd "RET") 'magit-visit-thing-at-point)
(define-key map (kbd "b") 'magit-switch-branch)
(define-key map (kbd "b") 'magit-checkout)
(define-key map (kbd "B") 'magit-create-branch)
(define-key map (kbd "m") 'magit-manual-merge)
(define-key map (kbd "M") 'magit-automatic-merge)
@ -249,19 +320,20 @@
(define-key map (kbd "P") 'magit-push)
(define-key map (kbd "c") 'magit-log-edit)
(define-key map (kbd "C") 'magit-add-log)
(define-key map (kbd "l") 'magit-browse-log)
(define-key map (kbd "L") 'magit-browse-branch-log)
(define-key map (kbd "d") 'magit-diff-with-branch)
(define-key map (kbd "p") 'magit-display-process)
map))
(defvar magit-mode-hook nil)
(put 'magit-mode 'mode-class 'special)
(put 'magit-marked-object 'permanent-local t)
(defvar magit-marked-object nil)
(make-variable-buffer-local 'magit-marked-object)
(put 'magit-marked-object 'permanent-local t)
(defvar magit-submode nil)
(make-variable-buffer-local 'magit-submode)
(put 'magit-submode 'permanent-local t)
(defun magit-mode ()
;;; XXX - the formatting is all screwed up because of the \\[...]
@ -325,13 +397,13 @@ that are being merged at the top.
You can `soft reset' your repository by typing
`\\[magit-reset-soft]'. The current head will be set to the
commit that you specify, but your working tree and the staging
area are not changed. Typing `\\[magit-reset-hard]' will do a
`hard reset': all of the current head, your working tree, and the
the index will be reverted to the commit that you specify. Doing
a hard reset without actually changing the current head will thus
throw away all your uncommitted changes. You can do this to
abort a merge, for example.
commit at point, but your working tree and the staging area are
not changed. Typing `\\[magit-reset-hard]' will do a `hard
reset': all of the current head, your working tree, and the the
index will be reverted to the commit at point. Doing a hard
reset without actually changing the current head will thus throw
away all your uncommitted changes. You can do this to abort a
merge, for example.
When you have a remote repository configured for the current
branch (such as when \"git clone\" has done this for you
@ -348,10 +420,16 @@ pushed.
(setq buffer-read-only t)
(setq major-mode 'magit-mode
mode-name "Magit"
mode-line-process "")
mode-line-process ""
truncate-lines t)
(use-local-map magit-mode-map)
(run-mode-hooks 'magit-mode-hook))
(defun magit-mode-init (dir submode)
(setq default-directory dir
magit-submode submode)
(magit-mode))
;;; Status
(defun magit-wash-other-files (status)
@ -470,15 +548,19 @@ pushed.
(magit-goto-section old-section))
(magit-refresh-marks-in-buffer buf)))
(defun magit-find-status-buffer (&optional dir)
(defun magit-find-buffer (submode &optional dir)
(let ((topdir (magit-get-top-dir (or dir default-directory))))
(dolist (buf (buffer-list))
(if (save-excursion
(set-buffer buf)
(and (equal default-directory topdir)
(eq major-mode 'magit-mode)))
(eq major-mode 'magit-mode)
(eq magit-submode submode)))
(return buf)))))
(defun magit-find-status-buffer (&optional dir)
(magit-find-buffer 'status dir))
(defun magit-status (dir)
(interactive (list (magit-read-top-dir current-prefix-arg)))
(save-some-buffers)
@ -487,8 +569,7 @@ pushed.
(create-file-buffer (file-name-nondirectory
(directory-file-name topdir))))))
(switch-to-buffer buf)
(setq default-directory topdir)
(magit-mode)
(magit-mode-init topdir 'status)
(magit-update-status buf)))
;;; Staging
@ -584,39 +665,37 @@ pushed.
;;; Branches
(defun magit-list-branches ()
(magit-shell-lines "git branch -a | cut -c3-"))
(defun magit-read-rev (prompt)
(completing-read prompt (magit-list-branches)))
(defun magit-switch-branch (branch)
(interactive (list (magit-read-rev "Switch to branch: ")))
(if (and branch (not (string= branch "")))
(magit-run "git" "checkout" branch)))
(defun magit-checkout (rev)
(interactive (list (magit-read-rev "Switch to" (magit-default-rev))))
(if rev
(magit-run "git" "checkout" (magit-rev-to-git rev))))
(defun magit-read-create-branch-args ()
(let* ((branches (magit-list-branches))
(cur-branch (magit-get-current-branch))
(let* ((cur-branch (magit-get-current-branch))
(branch (read-string "Create branch: "))
(parent (completing-read "Parent: " branches nil t cur-branch)))
(parent (magit-read-rev "Parent" cur-branch)))
(list branch parent)))
(defun magit-create-branch (branch parent)
(interactive (magit-read-create-branch-args))
(if (and branch (not (string= branch ""))
parent (not (string= parent "")))
(magit-run "git" "checkout" "-b" branch parent)))
parent)
(magit-run "git" "checkout" "-b"
branch
(magit-rev-to-git parent))))
;;; Merging
(defun magit-manual-merge (branch)
(interactive (list (magit-read-rev "Manually merge from branch: ")))
(magit-run "git" "merge" "--no-ff" "--no-commit" branch))
(defun magit-manual-merge (rev)
(interactive (list (magit-read-rev "Manually merge")))
(if rev
(magit-run "git" "merge" "--no-ff" "--no-commit"
(magit-rev-to-git rev))))
(defun magit-automatic-merge (branch)
(interactive (list (magit-read-rev "Merge from branch: ")))
(magit-run "git" "merge" branch))
(defun magit-automatic-merge (rev)
(interactive (list (magit-read-rev "Merge")))
(if rev
(magit-run "git" "merge" (magit-rev-to-git branch))))
;;; Rebasing
@ -636,7 +715,9 @@ pushed.
(interactive)
(let ((info (magit-rebase-info)))
(if (not info)
(magit-run "git" "rebase" (magit-read-rev "Rebase against: "))
(let ((rev (magit-read-rev "Rebase to")))
(if rev
(magit-run "git" "rebase" rev)))
(let ((cursor-in-echo-area t)
(message-log-max nil))
(message "Rebase in progress. Abort, Skip, or Continue? ")
@ -651,17 +732,15 @@ pushed.
;;; Resetting
(defun magit-reset-soft (target)
(interactive (list (read-string "Reset history to: " "HEAD^")))
(magit-run "git" "reset" "--soft" target))
(defun magit-reset-head (rev)
(interactive (list (magit-read-rev "Reset head to")))
(if rev
(magit-run "git" "reset" "--soft" (magit-rev-to-git rev))))
(defun magit-reset-hard (target)
(interactive (list (read-string "Reset working tree (and history) to: "
"HEAD")))
(if (yes-or-no-p
(format "Hard reset to %s and throw away all uncommitted changes? "
target))
(magit-run "git" "reset" "--hard" target)))
(defun magit-reset-working-tree ()
(interactive)
(if (yes-or-no-p "Discard all uncommitted changes? ")
(magit-run "git" "reset" "--hard")))
;;; Push and pull
@ -673,7 +752,7 @@ pushed.
(interactive)
(magit-run "git" "push" "-v"))
;;; Commit
;;; Committing
(defvar magit-log-edit-map nil)
@ -748,76 +827,26 @@ pushed.
(open-line 1)
(insert (format "(%s): " fun)))))))))
;;; History browsing
;;; Commits
(defvar magit-log-mode-map
(let ((map (make-keymap)))
(suppress-keymap map)
(define-key map (kbd "RET") 'magit-show-commit)
(define-key map (kbd ".") 'magit-mark-thing-at-point)
(define-key map (kbd "=") 'magit-diff-with-mark)
(define-key map (kbd "R") 'magit-revert-commit)
(define-key map (kbd "P") 'magit-pick-commit)
(define-key map (kbd "C") 'magit-checkout-commit)
(define-key map (kbd "l") 'magit-log-commit)
(define-key map (kbd "L") 'magit-browse-branch-log)
(define-key map (kbd "q") 'magit-quit)
map))
(defvar magit-log-mode-hook nil)
(put 'magit-log-mode 'mode-class 'special)
(defun magit-log-mode ()
"Review commit history. \\<magit-log-mode-map>
The buffer shows a summary of the (usually non-linear) history of
changes starting form a given commit. You can see the details of
a the commit on the current line by typing
`\\[magit-show-commit]'. Typing `\\[magit-log-commit] will use
the commit on the current line as the new starting point for the
summary. Typing `\\[magit-browse-branch-log]' will ask you for a
branch and show its history.
You can modify your working tree and staging area by using the
commit on the current line in a number of ways. Typing
`\\[magit-revert-commit]' will revert the change made by the
commit in your working tree (and staging area). Typing
`\\[magit-pick-commit]' will apply the commit. You can use this
to `cherry pick' changes from another branch.
Typing `\\[magit-checkout-commit]' will checkout the commit on
the current line into your working tree.
\\{magit-log-mode-map}"
(kill-all-local-variables)
(setq buffer-read-only t)
(toggle-truncate-lines t)
(setq major-mode 'magit-log-mode
mode-name "Magit Log")
(use-local-map magit-log-mode-map)
(run-mode-hooks 'magit-log-mode-hook))
(defun magit-commit-at-point ()
(defun magit-commit-at-point (&optional nil-ok-p)
(let* ((info (get-text-property (point) 'magit-info))
(commit (and info
(eq (car info) 'commit)
(cadr info))))
(or commit
(error "No commit at point."))))
(if nil-ok-p
commit
(or commit
(error "No commit at point.")))))
(defun magit-revert-commit ()
(interactive)
(magit-run "git" "revert" "--no-commit" (magit-commit-at-point)))
(defun magit-pick-commit ()
(defun magit-apply-commit ()
(interactive)
(magit-run "git" "cherry-pick" "--no-commit" (magit-commit-at-point)))
(defun magit-checkout-commit ()
(interactive)
(magit-run "git" "checkout" (magit-commit-at-point)))
(defun magit-log-commit ()
(interactive)
(magit-browse-log (magit-commit-at-point)))
@ -855,50 +884,55 @@ the current line into your working tree.
(magit-put-line-property 'magit-info (list 'commit commit))))
(forward-line)))
(defun magit-browse-log (head)
(interactive (list (magit-get-current-branch)))
(let* ((topdir (magit-get-top-dir default-directory)))
(switch-to-buffer "*magit-log*")
(setq default-directory topdir)
(magit-log-mode)
(let ((inhibit-read-only t))
(save-excursion
(erase-buffer)
(magit-insert-section 'history (format "History of %s" head)
'magit-wash-log
"git" "log" "--graph" "--max-count=10000"
"--pretty=oneline" head)))
(magit-refresh-marks-in-buffer (current-buffer))))
(defun magit-log (range)
(interactive (list (magit-read-rev-range "Log" (magit-get-current-branch))))
(if range
(let* ((topdir (magit-get-top-dir default-directory))
(args (magit-rev-range-to-git range)))
(switch-to-buffer "*magit-log*")
(magit-mode-init topdir 'log)
(let ((inhibit-read-only t))
(save-excursion
(erase-buffer)
(magit-insert-section 'history
(magit-rev-range-describe range "Commits")
'magit-wash-log
"git" "log" "--graph" "--max-count=10000"
"--pretty=oneline" args)))
(magit-refresh-marks-in-buffer (current-buffer)))))
(defun magit-browse-branch-log ()
(defun magit-log-head ()
(interactive)
(magit-browse-log (magit-read-rev "Browse history of branch: ")))
(defun magit-diff-with-mark ()
(interactive)
(let ((commit (magit-commit-at-point))
(marked (or (magit-marked-object)
(error "Nothing marked."))))
(magit-show-diff commit marked)))
(magit-log "HEAD"))
;;; Diffing
(defun magit-show-diff (&rest args)
(let ((dir default-directory)
(buf (get-buffer-create "*magit-diff*")))
(display-buffer buf)
(save-excursion
(set-buffer buf)
(setq buffer-read-only t)
(setq default-directory dir)
(let ((inhibit-read-only t))
(erase-buffer)
(apply 'magit-insert-section 'diff nil 'magit-wash-diff
"git" "diff" args)))))
(defun magit-diff (range)
(interactive (list (magit-read-rev-range "Diff")))
(if range
(let* ((dir default-directory)
(args (magit-rev-range-to-git range))
(buf (get-buffer-create "*magit-diff*")))
(display-buffer buf)
(save-excursion
(set-buffer buf)
(magit-mode-init dir 'diff)
(let ((inhibit-read-only t))
(erase-buffer)
(magit-insert-section 'diff
(magit-rev-range-describe range "Changes")
'magit-wash-diff
"git" "diff" args))))))
(defun magit-diff-with-branch (branch)
(interactive (list (magit-read-rev "Diff against: ")))
(magit-show-diff branch))
(defun magit-diff-working-tree (rev)
(interactive (list (magit-read-rev "Diff with")))
(if rev
(magit-diff rev)))
(defun magit-diff-with-mark ()
(interactive)
(magit-diff (cons (magit-marked-object)
(magit-commit-at-point))))
;;; Markers

View file

@ -57,12 +57,17 @@ put you in Magit's status buffer. You will be using it frequently, so
it is probably a good idea to bind @code{magit-status} to a key of
your choice.
In addition to the status buffer, Magit will also create buffers that
show lists of commits, buffers with diffs, and other kinds of buffers.
All these buffers are in @code{magit-mode} and have the same key
bindings. Not all commands make sense in all contexts, but a given
key will always do the same thing.
@node Status
@chapter Status
Running @kbd{M-x magit-status} displays the main interface of Magit,
the status buffer. Almost all operations are initiated with single
letter keystrokes from that buffer.
the status buffer.
You can have multiple status buffers active at the same time, each
associated with its own Git repository. Running @code{magit-status}
@ -124,9 +129,9 @@ Type @kbd{c} to pop up a buffer where you can write your change
description. Once you are happy with the description, type @kbd{C-c
C-c} in that buffer to commit the staged changes.
Typing @kbd{C} will also pop up the change description buffer, but it
will also try to insert a ChangeLog-style entry for the change that
point is in.
Typing @kbd{C} will also pop up the change description buffer, but in
addition, it will try to insert a ChangeLog-style entry for the change
that point is in.
If the current branch is associated with a remote repository, the
status buffer will show a fourth section, named @emph{Unpushed
@ -137,37 +142,46 @@ Pulling} for more information.
@node History
@chapter History
To browse the repository history, type @kbd{l} or @kbd{L} in the
status buffer. Typing @kbd{l} will show the history starting from the
current head, while @kbd{L} will ask for a starting point.
A new buffer will be shown that displays the history in a terse form.
To show the repository history of your current head, type @kbd{l}. A
new buffer will be shown that displays the history in a terse form.
The first paragraph of each commit message is displayed, next to a
representation of the relationships between commits.
Typing @kbd{L} will ask for the starting and end point of the history.
This can be used to show the commits that are in one branch, but not
in another, for example.
You can move point to a commit and then cause various things to happen
with it.
with it. (The following commands work in any list of commit, such as
the one shown in the @emph{Unpushed commits} section.)
Typing @kbd{RET} will pop up more information about the current
commit and typing @kbd{l} will use it as the new starting point of the
history buffer.
commit.
Typing @kbd{R} will revert the current commit in your working tree and
staging area. Thus, it will apply the changes made by that commit in
reverse. This is obviously useful to cleanly undo changes that turned
out to be wrong.
Typing @kbd{a} will apply the current commit to your working tree and
staging area. This is useful when you are browsing the history of
some other branch and you want to `cherry-pick' some changes from it
for your current branch. A typical situation is applying selected bug
fixes from the development version of a program to a release branch.
Typing @kbd{P} will apply the current commit in the normal way. This
is useful when you are browsing the history of some other branch and
you want to `cherry-pick' some changes from it for your current
branch. A typical situation is applying selected bug fixes from the
development version of a program to a release branch.
Typing @kbd{v} will revert the current commit. Thus, it will apply
the changes made by that commit in reverse. This is obviously useful
to cleanly undo changes that turned out to be wrong.
Typing @kbd{C} will switch your working tree to the current commit.
Typing @kbd{=} will show the differences from the current commit to
the @dfn{marked} commit.
You can also mark the current commit by typing @kbd{.}. Once you have
marked a commit, you can show the differences between it and the
current commit by typing @kbd{=}.
You can mark the current commit by typing @kbd{.}. Some commands,
such as @kbd{=}, will use the current commit and the marked commit as
implicit arguments. Other commands will offer the marked commit as a
default when prompting for their arguments.
@node Diffing
@chapter Diffing
To show the changes from you working tree to another revision, type
@kbd{d}. To show the changes between two arbitrary revisions, type
@kbd{D}.
@node Resetting
@chapter Resetting
@ -176,37 +190,43 @@ Once you have added a commit to your local repository, you can not
change that commit anymore in any way. But you can reset your current
head to an earlier commit and start over.
If you have published your history already, rewriting history in this
way can be confusing and should be avoided. However, rewriting your
local history is fine and it is often cleaner to fix mistakes this way
than by reverting commits (with @kbd{R} in the history buffer, for
example).
If you have published your history already, rewriting it in this way
can be confusing and should be avoided. However, rewriting your local
history is fine and it is often cleaner to fix mistakes this way than
by reverting commits (with @kbd{R}, for example).
Magit gives you two ways to reset your current head: soft and hard.
Type @kbd{x} to do a soft reset. This will change the current head to
the commit that you specify, but your current working tree and staging
area will not be touched. This is useful to redoing the last commit
to correct the commit message, for example.
Typing @kbd{x} will ask for a revision and reset your current head to
it. No changes will be made to your working tree and staging area.
Thus, the @emph{Staged changes} section in the status buffer will show
the changes that you have removed from your commit history. You can
commit the changes again as if you had just made them, thus rewriting
history.
Type @kbd{X} to do a hard reset. This will reset the current head to
the commit you specify and will check it out so that your working tree
and staging area will match it. In other words, a hard reset will
throw away the history completely, which can be useful to abort
experimental changes (like merging a branch just to see what happens).
Typing @kbd{x} while point is in a line that describes a commit will
offer this commit as the default revision to reset to. Thus, you can
move point to one of the commits in the @emph{Unpushed commits}
section and hit @kbd{x RET} to reset your current head to it.
In particular, doing a hard reset to HEAD will have no effect on the
current head, but it will reset your working tree and staging area
back to the last committed state.
Type @kbd{X} to reset your working tree and staging area to the most
recently committed state. This will discard your local modifications,
so be careful.
@node Branching and Merging
@chapter Branching and Merging
The current branch is indicated in the header of the status buffer.
You can check out a different branch by typing @kbd{b}. To create a
new branch and check it out immediately, type @kbd{B}.
You can switch to a different branch by typing @kbd{b}. This will
immediately checkout the branch into your working copy, so you
shouldn't have any local modifications when switching branches.
You can also compare your working tree with some other branch. Type
@kbd{d} and then specify the branch to compare with.
Similar to @kbs{x}, typing @kbd{b} while point is at a commit
description will offer that commit as the default to switch to.
This will result in a detached head.
To create a new branch and switch to it immediately, type @kbd{B}.
@node Merging
@chapter Merging
Magit offers two ways to merge branches: manually and automatic. A
manual merge will apply all changes to your working tree and staging
@ -217,9 +237,9 @@ Type @kbd{m} to initiate a manual merge, and type @kbd{M} for a
automatic merge.
A manual merge is useful when carefully merging a new feature that you
want to review and test before committing it. A automatic merge is
appropriate when you are on a feature branch and want to catch up with
the master, say.
want to review and test before even committing it. A automatic merge
is appropriate when you are on a feature branch and want to catch up
with the master, say.
After initiating a manual merge, the header of the status buffer will
remind you that the next commit will be a merge commit (with more than