Similar to this question, but instead of creating a new file, I'm trying to merge from origin. After creating a new index using Rugged::Repository's merge_commits, and a new merge commit, git reports the new file (coming from origin) as deleted.
Create a merge index,
> origin_target = repo.references['refs/remotes/origin/master'].target
> merge_index = repo.merge_commits(repo.head.target, origin_target)
and a new merge commit,
> options = {
update_ref: 'refs/heads/master',
committer: {name: 'user', email: 'user#foo.com', time: Time.now},
author: {name: 'user', email: 'user#foo.com', time: Time.now},
parents: [repo.head.target, origin_target],
message: "merge `origin/master` into `master`"}
and make sure to use the tree from the merge index.
> options[:tree] = merge_index.write_tree(repo)
Create the commit
> merge_commit = Rugged::Commit.create(repo, options)
Check that our HEAD has been updated:
> repo.head.target.tree
=> #<Rugged::Tree:16816500 {oid: 16c147f358a095bdca52a462376d7b5730e1978e}>
<"first_file.txt" 9d096847743f97ba44edf00a910f24bac13f36e2>
<"second_file.txt" 8178c76d627cade75005b40711b92f4177bc6cfc>
<"newfile.txt" e69de29bb2d1d6434b8b29ae775ad8c2e48c5391>
Looks good. I see the new file in the index. Write it to disk.
> repo.index.write
=> nil
...but git reports the new file as deleted:
$ git st
## master...origin/master [ahead 2]
D newfile.txt
How can I properly update my index and working tree?
There is an important distinction between the Git repository and the working directory. While most common command-line git commands operate on the working directory as well as the repository, the lower-level commands of libgit2 / librugged mostly operate on only the repository. This includes writing the index as in your example.
To update the working directory to match the index, the following command should work (after writing the index):
options = { strategy: force }
repo.checkout_head(options)
Docs for checkout_head: http://www.rubydoc.info/github/libgit2/rugged/Rugged/Repository#checkout_head-instance_method
Note: I tested with update_ref: 'HEAD' for the commit. I'm not sure if update_ref: 'refs/heads/master' will have the same effect.
Related
For my Jenkins pipeline I want to populate all the keys in Json which is in GitRepo as Extended Choice parameters.
[$class: 'ChoiceParameter', choiceType: 'PT_SINGLE_SELECT', description: 'Select an option', name: 'Option', randomName: 'choice-parameter-112', script: [$class: 'GroovyScript', fallbackScript: [classpath: [], sandbox: false, script: ''], script: [classpath: [], sandbox: false,
script: '''
//What should I write here in this block?
return keys
''']]]
Basically all I need is a curl call that I can make from the above block to get single file from Git using the Git Credential ID(I've stored my user name and password as credential id in Git)
This is a frequent case which boils down to this:
You can't issue a call to sh unless you have a node. To have a node, you need to run a pipeline. To run a pipeline, you need to figure out the parameters. To figure out this parameter, you need to issue a call to sh. See the problem?
To overcome the problem, you can cheat the system by running another (small) pipeline before the main one, e.g.
node('master') {
stage('read file') {
def file_contents = sh (returnStdout: true, script: "curl myrepo/myfile.json")
def parsed_data = readJSON text: file_contents
}
}
and then
properties([
parameters([
[$class: 'ChoiceParameter',
etc.
I'm trying to remove an array of directories in a git repo and make 1 commit for each directory removed. I'm using Rugged and Gitlab_git (which is more or less just a wrapper around Rugged) and so far I've managed to do everything I need to except the actually deletion and commit.
I don't see anything in the Rugged Readme that explains how to remove an entire tree/directory. I tried using their commit example for a blob and replacing a single file with a direcotry, but It didnt work
I also tried editing the code they had for the tree builder, but it added a commit to my history that showed every file ever in the repo having been added, and then left staging showing the same thing. Nothing was deleted.
oid = repo.write("Removing folder", :blob)
builder = Rugged::Tree::Builder.new(repo)
builder << { :type => :blob, :name => "_delete", :oid => oid, :filemode => 0100644 }
options = {}
options[:tree] = builder.write
options[:author] = { :email => "testuser#github.com", :name => 'Test Author', :time => Time.now }
options[:committer] = { :email => "testuser#github.com", :name => 'Test Author', :time => Time.now }
options[:message] ||= "Making a commit via Rugged!"
options[:parents] = repo.empty? ? [] : [ repo.head.target ].compact
options[:update_ref] = 'HEAD'
Rugged::Commit.create(repo, options)
Any suggestions? Im still a bit fuzzy on the git internals, so maybe thats my issue.
The git index doesn't track directories explicitly, only their contents. To remove a directory, stage the removal of all of its contents.
You can make a Tree::Builder that is based on an existing tree in the repository, which you can then manipulate as you want.
If you already have the Commit object you want to have as your parent commit, then you can do this:
parent_commit = ... # e.g. this might be repo.head.target
# Create a Tree::Builder containing the current commit tree.
tree_builder = Rugged::Tree::Builder.new(repo, parent_commit.tree)
# Next remove the directory you want from the Tree::Builder.
tree_builder.remove('path/to/directory/to/remove')
# Now create a commit using the contents of the modified tree
# builder. (You might want to include the :update_ref option here
# depending on what you are trying to do - something like
# :update_ref => 'HEAD'.)
commit_data = {:message => "Remove directory with Rugged",
:parents => [commit],
:tree => tree_builder.write
}
Rugged::Commit.create(repo, commit_data)
This will create the commit in the repo with the directory removed, but might not update any branch pointers if you didn’t use :update_ref.
It also won’t update your current working directory or index. If you want to update them you could reset to the new HEAD, but be careful about losing any work. Alternatively you could just remove the directory with Dir.rmdir, mimicking what you would do when removing the directory directly.
Check out the docs for more info, especially Tree::Builder and Commit.create.
I'm trying to create a commit with rugged using the following test script:
require "rugged"
r = Rugged::Repository.new(".")
index = r.index
index.read_tree(r.references["refs/heads/master"].target.tree)
blob = r.write("My test", :blob)
index.add(:oid => blob, :path => "test.md", :mode => 0100644)
tree = index.write_tree
parents = [r.references["refs/heads/master"].target].compact
actor = {:name => "Actor", :email => "actor#bla"}
options = {
:tree => tree,
:parents => parents,
:committer => actor,
:message => "message",
:update_ref => "HEAD"
}
puts Rugged::Commit.create(r, options)
The commit is created, and the script outputs 773d97f453a6df6e8bb5099dc0b3fc8aba5ebaa7 (the SHA of the new commit). The generated commit and tree look like they're supposed to:
ludwig$ git cat-file commit 773d97f453a6df6e8bb5099dc0b3fc8aba5ebaa7
tree 253d0a2b8419e1eb89fd462ef6e0b478c4388ca3
parent bb1593b0534c8a5b506c5c7f2952e245f1fe75f1
author Actor <actor#bla> 1417735899 +0100
committer Actor <actor#bla> 1417735899 +0100
message
ludwig$ git ls-tree 253d0a2b8419e1eb89fd462ef6e0b478c4388ca3
100644 blob a7f8d9e5dcf3a68fdd2bfb727cde12029875260b Initial file
100644 blob 7a76116e416ef56a6335b1cde531f34c9947f6b2 test.md
However, the working directory is not updated:
ludwig$ ls
Initial file rugged_test.rb
ludwig$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
deleted: test.md
I have to do a git reset --hard HEAD to get the missing file test.md to show up in the working directory. I thought creating a Rugged commit, and setting :update_ref => "HEAD", was supposed to update the working directory automatically, but something must be going wrong, because doing r.checkout_head also has no effect. However, I think I'm following the rugged examples correctly. What am I missing here?
EDIT:
ludwig$ gem list rugged
*** LOCAL GEMS ***
rugged (0.21.2)
The steps you're taking are those for when you do not want to affect the workdir or current branch. You are not creating the file and you are not writing the modified index to disk.
If you want to put a file on the filesystem and then track it in a new commit, start by creating the file
# Create the file and give it some content
f = open("test.md", "w")
f << "My test"
f.close
# The file from the workdir from to the index
# and write the changes out to disk
index = repo.index
index.add("test.md")
index.write
# Get the tree for the commit
tree = index.write_tree
...
and then commit as you are doing now.
I'm working on a program that will be adding and updating files in a git repo. Since I can't be sure if a file that I am working with is currently in the repo, I need to check its existence - an action that seems to be harder than I thought it would be.
The 'in' comparison doesn't seem to work on non-root levels on trees in gitpython. Ex.
>>> repo = Repo(path)
>>> hct = repo.head.commit.tree
>>>> 'A' in hct['documents']
False
>>> hct['documents']['A']
<git.Tree "8c74cba527a814a3700a96d8b168715684013857">
So I'm left to wonder, how do people check that a given file is in a git tree before trying to work on it? Trying to access an object for a file that is not in the tree will throw a KeyError, so I can do try-catches. But that feels like a poor use of exception handling for a routine existence check.
Have I missed something really obvious? How does once check for the existence of a file in a commit tree using gitpython (or really any library/method in Python)?
Self Answer
OK, I dug around in the Tree class to see what __contains__ does. Turns out, when searching in sub folders, one has to check for existence of a file using the full relative path from the repo's root. So a working version of the check I did above is:
>>> 'documents/A' in hct['documents']
True
EricP's answer has a bug. Here's a fixed version:
def fileInRepo(repo, filePath):
'''
repo is a gitPython Repo object
filePath is the full path to the file from the repository root
returns true if file is found in the repo at the specified path, false otherwise
'''
pathdir = os.path.dirname(filePath)
# Build up reference to desired repo path
rsub = repo.head.commit.tree
for path_element in pathdir.split(os.path.sep):
# If dir on file path is not in repo, neither is file.
try :
rsub = rsub[path_element]
except KeyError :
return False
return(filePath in rsub)
Usage:
file_found = fileInRepo(repo, 'documents/A')
This is very similar to EricP's code, but handles the case where the folder containing the file is not in the repo. EricP's function raises a KeyError in that case. This function returns False.
(I offered to edit EricP's code but was rejected.)
Expanding on Bill's solution, here is a function that determines whether a file is in a repo:
def fileInRepo(repo,path_to_file):
'''
repo is a gitPython Repo object
path_to_file is the full path to the file from the repository root
returns true if file is found in the repo at the specified path, false otherwise
'''
pathdir = os.path.dirname(path_to_file)
# Build up reference to desired repo path
rsub = repo.head.commit.tree
for path_element in pathdir.split(os.path.sep):
rsub = rsub[path_element]
return(path_to_file in rsub)
Example usage:
file_found = fileInRepo(repo, 'documents/A')
If you want to omit catch try you can check if object is in repo with:
def fileInRepo(repo, path_to_file):
dir_path = os.path.dirname(path_to_file)
rsub = repo.head.commit.tree
path_elements = dir_path.split(os.path.sep)
for el_id, element in enumerate(path_elements):
sub_path = os.path.join(*path_elements[:el_id + 1])
if sub_path in rsub:
rsub = rsub[element]
else:
return False
return path_to_file in rsub
or you can iterate through all items in repo, but it will be for sure slower:
def isFileInRepo(repo, path_to_file):
rsub = repo.head.commit.tree
for element in rsub.traverse():
if element.path == path_to_file:
return True
return False
There already exists a method of Tree that will do what fileInRepo re-implements in Lucidity's answer .
The method is Tree.join:
https://gitpython.readthedocs.io/en/3.1.29/reference.html#git.objects.tree.Tree.join
A less redundant implementation of fileInRepo is:
def fileInRepo(repo, filePath):
try:
repo.head.commit.tree.join(filePath)
return True
except KeyError:
return False
I'm trying to do a very simple thing: Read a diff from a git repo via the ruby gem Grit. I'm creating a file and adding the line "This is me changing the first file". Now I do this to get the diff:
r = Grit::Repo.new("myrepo")
c = r.commits.first
d = r.commit_diff(c.id).first
puts d.first.diff
The output of this is:
--- a/First-File.asciidoc
+++ b/First-File.asciidoc
## -1,2 +1 ##
-This is me changing the first file
See that minus in front of the added line? Why would a commit_diff show in reverse? I know that git reverses the diff if I reverse the commit shas, but this is a Grit library call that only gives the commit diff?
Any clues?
Let me answer that question. The commit shows up in correct form, if you do this insteas:
r = Grit::Repo.new("myrepo")
c = r.commits.first
d = c.diffs.first
puts d.first.diff
Not sure what the difference would be between Commit.diff and Repo.commit_diff.