4

I'm currently running Pygit 0.24.1 (along with libgit 0.24.1), working on a repository where I have two branches (say prod and dev).

Every change is first commited to the dev branch and pushed to the remote repository. To do that, I have this piece of code:

repo = Repository('/foo/bar')
repo.checkout('refs/heads/dev')

index = repo.index
index.add('any_file')
index.write()

tree = index.write_tree()
author = Signature('foo', 'foo@bar')
committer = Signature('foo', 'foo@bar')
repo.create_commit('refs/heads/dev', author, committer, 'Just another commit', tree, [repo.head.get_object().hex])

up = UserPass('foo', '***')
rc = RemoteCallbacks(credentials=up)
repo.remotes['origin'].push(['refs/heads/dev'], rc)

This works quite fine, I can see the local commit and also the remote commit, and the local repo remains clean:

nothing to commit, working directory clean

Next, I check-out to the prod branch and I want to merge the HEAD commit on dev. To do so, I use this other piece of code (assuming I always start checked-out to the dev branch):

head_commit = repo.head
repo.checkout('refs/heads/prod')
prod_branch_tip = repo.lookup_reference('HEAD').resolve()
prod_branch_tip.set_target(head_commit.target)

rc = RemoteCallbacks(credentials=up)
repo.remotes['origin'].push(['refs/heads/prod'], rc)

repo.checkout('refs/heads/dev')

I actually can see the branch being merged both locally and remotely, but after this piece of code runs, the commited file always remains in a modified state on branch dev.

On branch dev

Changes to be committed: (use "git reset HEAD ..." to unstage)

modified: any_file

I'm completely sure noone is modifying that file, though. Actually, a git diff shows nothing. This issue happens only with already commited files (i.e., files that have been commited at least once previously). When files are new, this works perfectly and leaves the file in a clean state.

I'm sure I'm missing some detail but I'm unable to find out what is it. Why is the file left as modified?

EDIT: Just to clarify, my aim is to do a FF (Fast-Forward) merge. I know there's some documentation about doing a non-FF merge in the Pygit2 documentation, but I'd prefer the first method because it keeps commit hashes through branches.

EDIT 2: After @Leon's comment, I double checked and indeed, git diff shows no output while git diff --cached shows the content that the file had before commiting. That's odd since I can see the change successfully commited on the local and remote repositories, but it looks like afterwards the file is changed again to the previous content...

An example of that:

  1. Having a file with content '12345' commited + pushed, I replace that string with '54321'
  2. I run the code above
  3. git log shows the file commited correctly, on the remote repo I see the file with content '54321', while locally git diff --cached shows this:

    @@ -1 +1 @@
    -54321
    +12345
    
nKn
  • 13,691
  • 9
  • 45
  • 62
  • "*Actually, a* `git diff` *shows nothing*". Did you mean "`git diff --staged` *shows nothing*"? – Leon Jul 18 '16 at 10:28
  • @Leon please have a look at *EDIT 2*, since I made some additional tests and helped me get a bit farer. – nKn Jul 18 '16 at 15:21

1 Answers1

3

I would explain the observed problem as follows:

head_commit = repo.head

# This resets the index and the working tree to the old state
# and records that we are in a state corresponding to the commit
# pointed to by refs/heads/prod
repo.checkout('refs/heads/prod')

prod_branch_tip = repo.lookup_reference('HEAD').resolve()

# This changes where refs/heads/prod points. The index and
# the working tree are not updated, but (probably due to a bug in pygit2)
# they are not marked as gone-out-of-sync with refs/heads/prod
prod_branch_tip.set_target(head_commit.target)

rc = RemoteCallbacks(credentials=up)
repo.remotes['origin'].push(['refs/heads/prod'], rc)

# Now we must switch to a state corresponding to refs/heads/dev. It turns
# out that refs/heads/dev points to the same commit as refs/heads/prod.
# But we are already in the (clean) state corresponding to refs/heads/prod!
# Therefore there is no need to update the index and/or the working tree.
# So this simply changes HEAD to refs/heads/prod
repo.checkout('refs/heads/dev')

The solution is to fast-forward the branch without checking it out. The following code is devoid of the described problem:

head_commit = repo.head
prod_branch_tip = repo.lookup_branch('prod')
prod_branch_tip.set_target(head_commit.target)

rc = RemoteCallbacks(credentials=up)
repo.remotes['origin'].push(['refs/heads/prod'], rc)
Leon
  • 31,443
  • 4
  • 72
  • 97
  • This works, thanks. So the only significant differences between my code and yours are the checkouts and how the `prod_branch_tip` is resolved. It's kind of tricky to understand why this approach makes a difference, documentation about this is not quite explanatory so I'll do some additional tests to understand better what's happening. Anyhow, your answer is correct so I'm marking it as such. – nKn Jul 18 '16 at 20:20