Skip to content

Add worktree functionality: listing, add, remove and prune #479

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 5, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add worktree functionality
list, add, remove and prune

Signed-off-by: Ofir Petrushka <hatkyinc@gmail.com>
  • Loading branch information
ofir-petrushka committed Aug 17, 2020
commit 255e8e4164be009695751b9bc8ece431cd4fa6d5
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ like:

`@git.log(20).object("some_file").since("2 weeks ago").between('v2.6', 'v2.7').each { |commit| [block] }`

**Git::Worktrees** - Enumerable object that holds `Git::Worktree objects`.

## Examples

Here are a bunch of examples of how to use the Ruby/Git package.
Expand Down Expand Up @@ -145,6 +147,14 @@ Here are the operations that need read permission only.
puts file_diff.blob(:src).contents
end

g.worktrees # returns Git::Worktree objects
g.worktrees.count
g.worktrees.each do |worktree|
worktree.dir
worktree.gcommit
worktree.to_s
end

g.config('user.name') # returns 'Scott Chacon'
g.config # returns whole config hash

Expand Down Expand Up @@ -252,6 +262,11 @@ And here are the operations that will need to write to your git repository.

g.push
g.push(g.remote('name'))

g.worktree('/tmp/new_worktree').add
g.worktree('/tmp/new_worktree', 'branch1').add
g.worktree('/tmp/new_worktree').remove
g.worktrees.prune
```

Some examples of more low-level index and tree operations
Expand Down
2 changes: 2 additions & 0 deletions lib/git.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
require 'git/stashes'
require 'git/version'
require 'git/working_directory'
require 'git/worktree'
require 'git/worktrees'

lib = Git::Lib.new(nil, nil)
unless lib.meets_required_version?
Expand Down
15 changes: 13 additions & 2 deletions lib/git/base/factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Git
class Base

module Factory

# returns a Git::Branch object for branch_name
def branch(branch_name = 'master')
Git::Branch.new(self, branch_name)
Expand All @@ -14,7 +14,18 @@ def branch(branch_name = 'master')
def branches
Git::Branches.new(self)
end


# returns a Git::Worktree object for dir, commitish
def worktree(dir, commitish = nil)
Git::Worktree.new(self, dir, commitish)
end

# returns a Git::worktrees object of all the Git::Worktrees
# objects for this repo
def worktrees
Git::Worktrees.new(self)
end

def commit_tree(tree = nil, opts = {})
Git::Object::Commit.new(self, self.lib.commit_tree(tree, opts))
end
Expand Down
33 changes: 33 additions & 0 deletions lib/git/lib.rb
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,39 @@ def branches_all
arr
end

def worktrees_all
arr = []
directory = ''
# Output example for `worktree list --porcelain`:
# worktree /code/public/ruby-git
# HEAD 4bef5abbba073c77b4d0ccc1ffcd0ed7d48be5d4
# branch refs/heads/master
#
# worktree /tmp/worktree-1
# HEAD b8c63206f8d10f57892060375a86ae911fad356e
# detached
#
command_lines('worktree',['list', '--porcelain']).each do |w|
s = w.split("\s")
directory = s[1] if s[0] == 'worktree'
arr << [directory, s[1]] if s[0] == 'HEAD'
end
arr
end

def worktree_add(dir, commitish = nil)
return command('worktree', ['add', dir, commitish]) if !commitish.nil?
command('worktree', ['add', dir])
end

def worktree_remove(dir)
command('worktree', ['remove', dir])
end

def worktree_prune
command('worktree', ['prune'])
end

def list_files(ref_dir)
dir = File.join(@git_dir, 'refs', ref_dir)
files = []
Expand Down
38 changes: 38 additions & 0 deletions lib/git/worktree.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
require 'git/path'

module Git

class Worktree < Path

attr_accessor :full, :dir, :gcommit

def initialize(base, dir, gcommit = nil)
@full = dir
@full += ' ' + gcommit if !gcommit.nil?
@base = base
@dir = dir
@gcommit = gcommit
end

def gcommit
@gcommit ||= @base.gcommit(@full)
@gcommit
end

def add
@base.lib.worktree_add(@dir, @gcommit)
end

def remove
@base.lib.worktree_remove(@dir)
end

def to_a
[@full]
end

def to_s
@full
end
end
end
47 changes: 47 additions & 0 deletions lib/git/worktrees.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
module Git
# object that holds all the available worktrees
class Worktrees

include Enumerable

def initialize(base)
@worktrees = {}

@base = base

# Array contains [dir, git_hash]
@base.lib.worktrees_all.each do |w|
@worktrees[w[0]] = Git::Worktree.new(@base, w[0], w[1])
end
end

# array like methods

def size
@worktrees.size
end

def each(&block)
@worktrees.values.each(&block)
end

def [](worktree_name)
@worktrees.values.inject(@worktrees) do |worktrees, worktree|
worktrees[worktree.full] ||= worktree
worktrees
end[worktree_name.to_s]
end

def to_s
out = ''
@worktrees.each do |k, b|
out << b.to_s << "\n"
end
out
end

def prune
@base.lib.worktree_prune
end
end
end
1 change: 1 addition & 0 deletions tests/files/worktree/dot_git/FETCH_HEAD
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
545ffc79786f268524c35e1e05b1770c7c74faf1 not-for-merge branch 'master' of ../working
1 change: 1 addition & 0 deletions tests/files/worktree/dot_git/HEAD
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ref: refs/heads/git_grep
15 changes: 15 additions & 0 deletions tests/files/worktree/dot_git/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[user]
name = Scott Chacon
email = schacon@gmail.com
[commit]
gpgsign = false
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[gui]
geometry = 986x682+365+124 211 500
[remote "working"]
url = ../working.git
fetch = +refs/heads/*:refs/remotes/working/*
1 change: 1 addition & 0 deletions tests/files/worktree/dot_git/description
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Unnamed repository; edit this file to name it for gitweb.
15 changes: 15 additions & 0 deletions tests/files/worktree/dot_git/hooks/applypatch-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit. The hook is
# allowed to edit the commit message file.
#
# To enable this hook, make this file executable.

. git-sh-setup
test -x "$GIT_DIR/hooks/commit-msg" &&
exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
:
21 changes: 21 additions & 0 deletions tests/files/worktree/dot_git/hooks/commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/sh
#
# An example hook script to check the commit log message.
# Called by git-commit with one argument, the name of the file
# that has the commit message. The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit. The hook is allowed to edit the commit message file.
#
# To enable this hook, make this file executable.

# Uncomment the below to add a Signed-off-by line to the message.
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"

# This example catches duplicate Signed-off-by lines.

test "" = "$(grep '^Signed-off-by: ' "$1" |
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
echo >&2 Duplicate Signed-off-by lines.
exit 1
}
8 changes: 8 additions & 0 deletions tests/files/worktree/dot_git/hooks/post-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/sh
#
# An example hook script that is called after a successful
# commit is made.
#
# To enable this hook, make this file executable.

: Nothing
16 changes: 16 additions & 0 deletions tests/files/worktree/dot_git/hooks/post-receive
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/sh
#
# An example hook script for the post-receive event
#
# This script is run after receive-pack has accepted a pack and the
# repository has been updated. It is passed arguments in through stdin
# in the form
# <oldrev> <newrev> <refname>
# For example:
# aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
#
# see contrib/hooks/ for an sample, or uncomment the next line (on debian)
#


#. /usr/share/doc/git-core/contrib/hooks/post-receive-email
8 changes: 8 additions & 0 deletions tests/files/worktree/dot_git/hooks/post-update
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/sh
#
# An example hook script to prepare a packed repository for use over
# dumb transports.
#
# To enable this hook, make this file executable by "chmod +x post-update".

exec git-update-server-info
14 changes: 14 additions & 0 deletions tests/files/worktree/dot_git/hooks/pre-applypatch
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed
# by applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit.
#
# To enable this hook, make this file executable.

. git-sh-setup
test -x "$GIT_DIR/hooks/pre-commit" &&
exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
:
70 changes: 70 additions & 0 deletions tests/files/worktree/dot_git/hooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by git-commit with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, make this file executable.

# This is slightly modified from Andrew Morton's Perfect Patch.
# Lines you introduce should not have trailing whitespace.
# Also check for an indentation that has SP before a TAB.

if git-rev-parse --verify HEAD 2>/dev/null
then
git-diff-index -p -M --cached HEAD
else
# NEEDSWORK: we should produce a diff with an empty tree here
# if we want to do the same verification for the initial import.
:
fi |
perl -e '
my $found_bad = 0;
my $filename;
my $reported_filename = "";
my $lineno;
sub bad_line {
my ($why, $line) = @_;
if (!$found_bad) {
print STDERR "*\n";
print STDERR "* You have some suspicious patch lines:\n";
print STDERR "*\n";
$found_bad = 1;
}
if ($reported_filename ne $filename) {
print STDERR "* In $filename\n";
$reported_filename = $filename;
}
print STDERR "* $why (line $lineno)\n";
print STDERR "$filename:$lineno:$line\n";
}
while (<>) {
if (m|^diff --git a/(.*) b/\1$|) {
$filename = $1;
next;
}
if (/^@@ -\S+ \+(\d+)/) {
$lineno = $1 - 1;
next;
}
if (/^ /) {
$lineno++;
next;
}
if (s/^\+//) {
$lineno++;
chomp;
if (/\s$/) {
bad_line("trailing whitespace", $_);
}
if (/^\s* /) {
bad_line("indent SP followed by a TAB", $_);
}
if (/^(?:[<>=]){7}/) {
bad_line("unresolved merge conflict", $_);
}
}
}
exit($found_bad);
'
Loading